Android设备有唯一的ID吗?如果有,使用Java访问它的简单方法是什么?


当前回答

正如DaveWebb提到的,Android开发者博客有一篇文章介绍了这一点。他们的首选解决方案是跟踪应用程序安装而不是设备,这对于大多数使用情况都很有效。博客文章将向您展示实现这一功能所需的代码,我建议您查看一下。

然而,如果您需要设备标识符而不是应用程序安装标识符,博客文章将继续讨论解决方案。我与谷歌的某位人士进行了交谈,以获得一些额外的澄清,以防您需要这样做。以下是我发现的有关设备标识符的信息,这些信息在上述博客文章中没有提及:

ANDROID_ID是首选设备标识符。ANDROID_ID在ANDROID<=2.1或>=2.3版本上非常可靠。只有2.2存在帖子中提到的问题。多个制造商的多个设备受到2.2中ANDROID_ID错误的影响。据我所知,所有受影响的设备都具有相同的ANDROID_ID,即9774d56d682e549c。顺便说一下,这也是模拟器报告的相同设备id。谷歌相信,原始设备制造商已经为他们的许多或大部分设备修补了这个问题,但我能够证实,至少在2011年4月初,找到ANDROID_ID损坏的设备仍然很容易。

根据谷歌的建议,我实现了一个类,该类将为每个设备生成一个唯一的UUID,在适当的情况下使用ANDROID_ID作为种子,必要时返回TelephonyManager.getDeviceId(),如果失败,则使用随机生成的唯一UUID,该UUID将在应用程序重新启动(但不是应用程序重新安装)期间保持。

请注意,对于必须在设备ID上回退的设备,唯一ID将在出厂重置期间保持。这是需要注意的。如果您需要确保出厂重置将重置您的唯一ID,您可能需要考虑直接返回到随机UUID而不是设备ID。

同样,此代码用于设备ID,而不是应用安装ID。对于大多数情况,应用安装ID可能是您要查找的。但是,如果您确实需要设备ID,那么下面的代码可能适用于您。

import android.content.Context;
import android.content.SharedPreferences;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;

import java.io.UnsupportedEncodingException;
import java.util.UUID;

public class DeviceUuidFactory {

    protected static final String PREFS_FILE = "device_id.xml";
    protected static final String PREFS_DEVICE_ID = "device_id";
    protected volatile static UUID uuid;

    public DeviceUuidFactory(Context context) {
        if (uuid == null) {
            synchronized (DeviceUuidFactory.class) {
                if (uuid == null) {
                    final SharedPreferences prefs = context
                            .getSharedPreferences(PREFS_FILE, 0);
                    final String id = prefs.getString(PREFS_DEVICE_ID, null);
                    if (id != null) {
                        // Use the ids previously computed and stored in the
                        // prefs file
                        uuid = UUID.fromString(id);
                    } else {
                        final String androidId = Secure.getString(
                            context.getContentResolver(), Secure.ANDROID_ID);
                        // Use the Android ID unless it's broken, in which case
                        // fallback on deviceId,
                        // unless it's not available, then fallback on a random
                        // number which we store to a prefs file
                        try {
                            if (!"9774d56d682e549c".equals(androidId)) {
                                uuid = UUID.nameUUIDFromBytes(androidId
                                        .getBytes("utf8"));
                            } else {
                                final String deviceId = (
                                    (TelephonyManager) context
                                    .getSystemService(Context.TELEPHONY_SERVICE))
                                    .getDeviceId();
                                uuid = deviceId != null ? UUID
                                    .nameUUIDFromBytes(deviceId
                                            .getBytes("utf8")) : UUID
                                    .randomUUID();
                            }
                        } catch (UnsupportedEncodingException e) {
                            throw new RuntimeException(e);
                        }
                        // Write the value out to the prefs file
                        prefs.edit()
                                .putString(PREFS_DEVICE_ID, uuid.toString())
                                .commit();
                    }
                }
            }
        }
    }

    /**
     * Returns a unique UUID for the current android device. As with all UUIDs,
     * this unique ID is "very highly likely" to be unique across all Android
     * devices. Much more so than ANDROID_ID is.
     * 
     * The UUID is generated by using ANDROID_ID as the base key if appropriate,
     * falling back on TelephonyManager.getDeviceID() if ANDROID_ID is known to
     * be incorrect, and finally falling back on a random UUID that's persisted
     * to SharedPreferences if getDeviceID() does not return a usable value.
     * 
     * In some rare circumstances, this ID may change. In particular, if the
     * device is factory reset a new device ID may be generated. In addition, if
     * a user upgrades their phone from certain buggy implementations of Android
     * 2.2 to a newer, non-buggy version of Android, the device ID may change.
     * Or, if a user uninstalls your app on a device that has neither a proper
     * Android ID nor a Device ID, this ID may change on reinstallation.
     * 
     * Note that if the code falls back on using TelephonyManager.getDeviceId(),
     * the resulting ID will NOT change after a factory reset. Something to be
     * aware of.
     * 
     * Works around a bug in Android 2.2 for many devices when using ANDROID_ID
     * directly.
     * 
     * @see http://code.google.com/p/android/issues/detail?id=10603
     * 
     * @return a UUID that may be used to uniquely identify your device for most
     *         purposes.
     */
    public UUID getDeviceUuid() {
        return uuid;
    }
}

其他回答

要获取用户id,您可以使用Google Play授权库。

要下载此库,请打开SDK管理器=>SDK工具。下载的库文件的路径为:

path_to_android_sdk_on_your_pc/extras/google/market_licensing/library

将库包含在项目中(只需复制其文件)。

接下来,您需要一些策略接口的实现(只需使用库中的两个文件之一:ServerManagedPolicy或StrictPolicy)。

将在processServerResponse()函数中为您提供用户id:

public void processServerResponse(int response, ResponseData rawData) {
    if(rawData != null) {
        String userId = rawData.userId
        // use/save the value
    }
    // ...
}

接下来,需要使用策略构造LicenseChecker,并调用checkAccess()函数。使用MainActivity.java作为示例说明如何执行此操作。MainActivity.ava位于以下文件夹中:

path_to_android_sdk_on_your_pc/extras/google/market_licensing/sample/src.com/sample/android/market/licensing

不要忘记将CHECK_LICENSE权限添加到AndroidManifest.xml。

有关授权库的详细信息:https://developer.android.com/google/play/licensing

只获取一次设备ID,然后将其存储在数据库或文件中。在这种情况下,如果它是应用程序的第一次启动,它将生成一个ID并存储它。下次,它将只获取存储在文件中的ID。

这个示例演示了如何在Android中获取和存储设备ID,但我使用的是Kotlin。

      val textView: TextView = findViewById(R.id.textView)
      val uniqueId: String = Settings.Secure.getString(contentResolver, Settings.Secure.ANDROID_ID)
      textView.text = "Device ID: $uniqueId"

API级别9(Android 2.3-姜饼)中的Build类中添加了一个Serial字段。文档表示它代表硬件序列号。因此,如果设备上存在,它应该是唯一的。

我不知道API级别>=9的所有设备是否都支持(=不为空)。

为了包括Android 9,我只有一个想法可以继续工作,那就是(可能)不违反任何条款,需要权限,并且可以跨安装和应用程序工作。

涉及服务器的指纹应该能够唯一地识别设备。硬件信息+已安装的应用程序和安装时间的组合应该会起到作用。除非卸载并再次安装应用程序,否则首次安装时间不会更改。但这必须对设备上的所有应用程序进行,以便无法识别设备(即,在工厂重置后)。

我会这样做:

提取硬件信息、应用程序包名称和首次安装时间。

这是从Android中提取所有应用程序的方法(无需权限):

final PackageManager pm = application.getPackageManager();
List<ApplicationInfo> packages = 
pm.getInstalledApplications(PackageManager.GET_META_DATA);

for (ApplicationInfo packageInfo : packages) {
    try {
        Log.d(TAG, "Installed package :" + packageInfo.packageName);
        Log.d(TAG, "Installed :" + pm.getPackageInfo(packageInfo.packageName, 0).firstInstallTime);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}

在将每个包名称和安装时间戳组合发送到服务器之前,您可能需要对其进行哈希,因为用户在设备上安装的内容可能与您的业务无关,也可能与您无关。有些应用程序(实际上很多)是系统应用程序。这些可能具有相同的安装时间戳,与出厂重置后的最新系统更新相匹配。因为它们具有相同的安装时间戳,所以用户无法安装它们,并且可以过滤掉它们。将信息发送到服务器,并让它在先前存储的信息中查找最近的匹配。在安装和卸载应用程序时,您需要在与以前存储的设备信息进行比较时设置阈值。但我的猜测是,这一门槛可能很低,因为任何软件包名称和首次安装时间戳组合对于一个设备来说都是非常独特的,而且应用程序的安装和卸载并不频繁。拥有多个应用程序只会增加独一无二的可能性。返回为匹配项生成的唯一id,或生成唯一id,存储设备信息并返回此新id。

注:这是一种未经测试和验证的方法!我相信它会奏效,但我也非常确信,如果这一点流行起来,他们会以某种方式关闭它。