是否有唯一的Android设备ID?


Answers:


2024

Settings.Secure#ANDROID_ID对于每个用户 64位十六进制字符串,返回Android ID作为唯一 ID 。

import android.provider.Settings.Secure;

private String android_id = Secure.getString(getContext().getContentResolver(),
                                                        Secure.ANDROID_ID); 

476
有时它为null,被记录为“可以在恢复出厂设置时更改”。使用时需要您自担风险,并且可以轻松地在有根电话上对其进行更改。
塞瓦·阿列克谢耶夫


18
我认为我们在第一个答案的哈希中使用ANDROID_ID时需要谨慎,因为它可能在应用程序首次运行时未设置,可能在以后设置,甚至可能在理论上发生了变化,因此唯一ID可能会发生变化

46
要知道有这个解决方案巨大的局限性:android-developers.blogspot.com/2011/03/...
emmby

35
ANDROID_ID不再唯一标识设备(从4.2版开始):stackoverflow.com/a/13465373/150016
Tom

1144

更新:从最新版本的Android开始,许多问题ANDROID_ID已得到解决,我相信不再需要这种方法。请看一下安东尼的回答

全面披露:我的应用最初使用以下方法,但不再使用该方法,现在我们使用emmby的答案链接到的Android开发者博客条目中概述的方法(即,生成和保存)。UUID#randomUUID()


这个问题有很多答案,其中大多数只会在某些时候起作用,但是不幸的是,这还不够好。

根据我对设备的测试(所有电话,其中至少一个未激活):

  1. 所有测试的设备都返回了一个值 TelephonyManager.getDeviceId()
  2. 所有GSM设备(均已通过SIM测试)返回了一个值 TelephonyManager.getSimSerialNumber()
  3. 所有CDMA设备返回的null为getSimSerialNumber()(预期)
  4. 所有添加了Google帐户的设备均返回的值 ANDROID_ID
  5. 所有CDMA设备同时用于返回的值相同(或同等价值的推导)ANDROID_IDTelephonyManager.getDeviceId()- 只要一个谷歌帐户设置过程中被添加。
  6. 我还没有机会测试没有SIM卡的GSM设备,没有添加Google帐户的GSM设备或任何处于飞行模式的设备。

所以,如果你想要独一无二的设备本身的东西,TM.getDeviceId() 应该是足够的。显然,某些用户比其他用户更偏执,因此散列1个或多个这些标识符可能很有用,因此该字符串实际上对于该设备仍然是唯一的,但未明确标识该用户的实际设备。例如,String.hashCode()结合使用和UUID:

final TelephonyManager tm = (TelephonyManager) getBaseContext().getSystemService(Context.TELEPHONY_SERVICE);

final String tmDevice, tmSerial, androidId;
tmDevice = "" + tm.getDeviceId();
tmSerial = "" + tm.getSimSerialNumber();
androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(), android.provider.Settings.Secure.ANDROID_ID);

UUID deviceUuid = new UUID(androidId.hashCode(), ((long)tmDevice.hashCode() << 32) | tmSerial.hashCode());
String deviceId = deviceUuid.toString();

可能会导致类似: 00000000-54b3-e7c7-0000-000046bffd97

对我来说效果很好。

正如Richard在下面提到的,不要忘记您需要获得读取TelephonyManager属性的权限,因此请将其添加到清单中:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

导入库

import android.content.Context;
import android.telephony.TelephonyManager;
import android.view.View;

151
基于电话的ID不会在平板电脑上显示,对吗?
塞瓦·阿列克谢耶夫

22
因此,为什么我说大多数都不总是有效:)我还没有看到对所有设备,所有设备类型和所有硬件配置都可靠的答案。这就是为什么这个问题从这里开始。很显然,对此没有最终解决方案。各个设备制造商可能拥有设备序列号,但这些序列号不暴露给我们使用,也不是必需的。因此,我们剩下的是可用的东西。

31
代码示例效果很好。记住要添加<uses-permission android:name="android.permission.READ_PHONE_STATE" />到清单文件中。如果存储在数据库中,则返回的字符串长度为36个字符。
理查德

10
要知道有这个解决方案巨大的局限性:android-developers.blogspot.com/2011/03/...
emmby

18
@softarn:我相信你指的是emmby已经链接到,这说明你是什么在Android开发者博客试图说,因此,或许你应该只是upvoted他的评论来代替。无论哪种方式,正如emmby在他的回答中提到的那样,即使博客信息仍然存在问题。该问题要求一个唯一的DEVICE标识符(而不是安装标识符),因此我不同意您的说法。该博客假设您想要的不一定是跟踪设备,而问题只是要求该设备。否则我同意博客。

438

上次更新时间:6/2/15


阅读了有关创建唯一ID的所有Stack Overflow帖子,Google开发者博客和Android文档之后,我觉得“伪ID”似乎是最好的选择。

主要问题:硬件与软件

硬件

  • 用户可以更改其硬件,Android平板电脑或手机,因此基于硬件的唯一ID并不是跟踪用户的好主意
  • 对于跟踪硬件,这是一个好主意

软件

  • 如果用户是root用户,则可以擦除/更改其ROM
  • 您可以跨平台(iOS,Android,Windows和Web)跟踪用户
  • 征得他们的同意后跟踪个人用户的最好方法就是简单地让他们登录(使用OAuth使其无缝连接)

Android的整体故障

-保证API> = 9/10的唯一性(包括根设备)(占Android设备的99.5%)

-没有额外的权限

伪代码:

if API >= 9/10: (99.5% of devices)

return unique ID containing serial id (rooted devices may be different)

else

return the unique ID of build information (may overlap data - API < 9)

感谢@stansult发布了我们所有的选项(在此Stack Overflow问题中)。

选项列表-为什么/为什么不使用它们的原因:

  • 用户电子邮件-软件

    • 用户可以更改电子邮件-不太可能
    • API 5+ <uses-permission android:name="android.permission.GET_ACCOUNTS" />
    • API 14+ <uses-permission android:name="android.permission.READ_PROFILE" /> <uses-permission android:name="android.permission.READ_CONTACTS" />如何获取Android设备的主要电子邮件地址
  • 用户电话号码-软件

    • 用户可以更改电话号码-不太可能
    • <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • IMEI-硬件(仅手机,需要android.permission.READ_PHONE_STATE

    • 大多数用户讨厌在权限中显示“电话”的事实。一些用户的评分很差,因为他们认为当您真正想要做的只是跟踪设备安装时,您只是在窃取他们的个人信息。很明显,您正在收集数据。
    • <uses-permission android:name="android.permission.READ_PHONE_STATE" />
  • Android ID-硬件(可以为空,可以在恢复出厂设置时更改,可以在有根设备上更改)

    • 由于它可以为“ null”,因此我们可以检查“ null”并更改其值,但这意味着它将不再是唯一的。
    • 如果您的用户具有恢复出厂设置的设备,则该值可能已在有根设备上更改或更改,因此如果您要跟踪用户安装,则可能会有重复的条目。
  • WLAN MAC地址-硬件(需要android.permission.ACCESS_WIFI_STATE

    • 这可能是第二好的选择,但是您仍在收集和存储直接来自用户的唯一标识符。很明显,您正在收集数据。
    • <uses-permission android:name="android.permission.ACCESS_WIFI_STATE "/>
  • 蓝牙MAC地址-硬件(需要蓝牙的设备android.permission.BLUETOOTH

    • 市场上的大多数应用程序都不使用蓝牙,因此如果您的应用程序不使用蓝牙而您将其包括在内,则用户可能会变得可疑。
    • <uses-permission android:name="android.permission.BLUETOOTH "/>
  • 伪唯一ID-软件(适用于所有Android设备)

    • 很有可能,可能包含冲突-请参阅下面的我的方法!
    • 这使您可以从用户那里获得一个“几乎唯一的” ID,而无需获取任何私有的东西。您可以根据设备信息创建自己的匿名ID。

我知道不使用权限就没有任何“完美”的方式来获得唯一ID。但是,有时我们只需要真正跟踪设备安装即可。在创建唯一ID时,我们可以仅基于Android API向我们提供的信息来创建“伪唯一ID”,而无需使用额外的权限。这样,我们可以显示用户的尊重并尝试提供良好的用户体验。

使用伪唯一ID,您实际上只会遇到这样的事实,即基于存在相似设备的事实,可能存在重复项。您可以调整组合方法以使其更加独特。但是,某些开发人员需要跟踪设备安装,这将根据相似的设备来解决问题或提高性能。

API> = 9:

如果他们的Android设备是API 9或更高版本,则由于“ Build.SERIAL”字段,因此可以保证其唯一。

请记住,从技术上讲,您仅会丢失大约0.5%的API <9用户。因此,您可以专注于其余部分:这是99.5%的用户!

API <9:

如果用户的Android设备低于API 9;希望他们没有恢复出厂设置,并且他们的“ Secure.ANDROID_ID”将保留或不为“ null”。(请参阅http://developer.android.com/about/dashboards/index.html

如果其他所有方法均失败:

如果所有其他操作均失败,则如果用户确实低于API 9(低于Gingerbread),已重置其设备或“ Secure.ANDROID_ID”返回“ null”,则返回的ID仅基于其Android设备信息。这是可能发生碰撞的地方。

变化:

  • 由于恢复出厂设置而删除了“ Android.SECURE_ID”,可能导致值更改
  • 编辑代码以更改API
  • 更改了伪

请看下面的方法:

/**
 * Return pseudo unique ID
 * @return ID
 */
public static String getUniquePsuedoID() {
    // If all else fails, if the user does have lower than API 9 (lower
    // than Gingerbread), has reset their device or 'Secure.ANDROID_ID'
    // returns 'null', then simply the ID returned will be solely based
    // off their Android device information. This is where the collisions
    // can happen.
    // Thanks http://www.pocketmagic.net/?p=1662!
    // Try not to use DISPLAY, HOST or ID - these items could change.
    // If there are collisions, there will be overlapping data
    String m_szDevIDShort = "35" + (Build.BOARD.length() % 10) + (Build.BRAND.length() % 10) + (Build.CPU_ABI.length() % 10) + (Build.DEVICE.length() % 10) + (Build.MANUFACTURER.length() % 10) + (Build.MODEL.length() % 10) + (Build.PRODUCT.length() % 10);

    // Thanks to @Roman SL!
    // https://stackoverflow.com/a/4789483/950427
    // Only devices with API >= 9 have android.os.Build.SERIAL
    // http://developer.android.com/reference/android/os/Build.html#SERIAL
    // If a user upgrades software or roots their device, there will be a duplicate entry
    String serial = null;
    try {
        serial = android.os.Build.class.getField("SERIAL").get(null).toString();

        // Go ahead and return the serial for api => 9
        return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
    } catch (Exception exception) {
        // String needs to be initialized
        serial = "serial"; // some value
    }

    // Thanks @Joe!
    // https://stackoverflow.com/a/2853253/950427
    // Finally, combine the values we have found by using the UUID class to create a unique identifier
    return new UUID(m_szDevIDShort.hashCode(), serial.hashCode()).toString();
}

新功能(适用于带有广告和Google Play服务的应用):

在Google Play开发者控制台中:

从2014年8月1日开始,Google Play开发者计划政策要求所有新的应用上载和更新都必须使用广告ID代替任何其他永久性标识符用于任何广告目的。学到更多

实施方式

允许:

<uses-permission android:name="android.permission.INTERNET" />

码:

import com.google.android.gms.ads.identifier.AdvertisingIdClient;
import com.google.android.gms.ads.identifier.AdvertisingIdClient.Info;
import com.google.android.gms.common.GooglePlayServicesAvailabilityException;
import com.google.android.gms.common.GooglePlayServicesNotAvailableException;
import java.io.IOException;
...

// Do not call this function from the main thread. Otherwise, 
// an IllegalStateException will be thrown.
public void getIdThread() {

  Info adInfo = null;
  try {
    adInfo = AdvertisingIdClient.getAdvertisingIdInfo(mContext);

  } catch (IOException exception) {
    // Unrecoverable error connecting to Google Play services (e.g.,
    // the old version of the service doesn't support getting AdvertisingId).

  } catch (GooglePlayServicesAvailabilityException exception) {
    // Encountered a recoverable error connecting to Google Play services. 

  } catch (GooglePlayServicesNotAvailableException exception) {
    // Google Play services is not available entirely.
  }
  final String id = adInfo.getId();
  final boolean isLAT = adInfo.isLimitAdTrackingEnabled();
}

来源/文件:

http://developer.android.com/google/play-services/id.html http://developer.android.com/reference/com/google/android/gms/ads/identifier/AdvertisingIdClient.html

重要:

当Google Play服务可用时,广告ID旨在完全替代出于广告目的而使用的其他其他标识符(例如,在Settings.Secure中使用ANDROID_ID)。由getAdvertisingIdInfo()引发的GooglePlayServicesNotAvailableException指示了Google Play服务不可用的情况。

警告,用户可以重置:

http://en.kioskea.net/faq/34732-android-reset-your-advertising-id

我试图参考我从中获取信息的每个链接。如果您不见了并且需要包括在内,请发表评论!

Google Player服务InstanceID

https://developers.google.com/instance-id/


但是Build在操作系统更新时,类不会改变吗?特别是如果API已更新?如果是这样,您如何保证这是唯一的?(谈论您编写的方法)
LuckyMe

2
我在我的应用程序中使用了您的方法来发送评论。我有坏消息。不幸的是PsuedoID并不是唯一的。我的服务器记录了5个ID超过100个,几乎30个ID记录了30个以上。重复次数最多的ID是“ ffffffff-fc8f-6093-ffff-ffffd8”(159条记录)和“ ffffffff-fe99-b334-ffff-ffffef”(154次)。同样基于时间和评论,很明显有不同的民族。到目前为止的总记录为10,000。请让我知道为什么会这样。坦克。
hojjat reyhane 2015年

1
我是1.5年前写的。我不确定为什么它不是您唯一的。您可以尝试使用广告ID。如果没有,您可以提出自己的解决方案。
Jared Burrows 2015年

2
sorta ..如果您要提出这个问题并提出您的想法,我将不胜感激
Durai Amuthan.H 2015年

1
@ user1587329谢谢。我正在努力使每个人都保持最新状态。当涉及硬件与软件以及跨平台时,这个问题很棘手。
Jared Burrows 2015年

340

正如Dave Webb所提到的,Android开发者博客上有一篇文章对此进行了介绍。他们的首选解决方案是跟踪应用程序的安装而不是设备,并且在大多数情况下都能很好地工作。博客文章将向您显示完成该工作所需的代码,我建议您检查一下。

但是,如果您需要设备标识符而不是应用程序安装标识符,那么该博客文章将继续讨论解决方案。如果需要,我与Google的某人交谈,以进一步澄清一些事项。这是我发现的有关设备标识符的内容,在上述博客文章中未提及:

  • ANDROID_ID是首选的设备标识符。ANDROID_ID在Android <= 2.1或> = 2.3的版本上完全可靠。只有2.2具有帖子中提到的问题。
  • 一些制造商的一些设备受2.2中ANDROID_ID错误的影响。
  • 据我所能确定的,所有受影响的设备都具有相同的ANDROID_ID,即9774d56d682e549c。这也是仿真器btw报告的相同设备ID。
  • Google相信OEM已为其许多或大多数设备修复了该问题,但我能够验证,至少从2011年4月开始,找到具有ANDROID_ID损坏的设备还是很容易的。

根据Google的建议,我实现了一个类,该类将为每个设备生成一个唯一的UUID,并在适当情况下使用ANDROID_ID作为种子,并在必要时使用TelephonyManager.getDeviceId(),如果失败,则采用随机生成的唯一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;
    }
}

6
您不应该对各种ID进行哈希处理,以使它们的大小相同吗?此外,您应该对设备ID进行哈希处理,以免意外泄露私人信息。
史蒂夫·波默罗伊

2
好点,史蒂夫。我更新了代码以始终返回UUID。这样可以确保a)生成的ID始终具有相同的大小,b)android和设备ID在返回之前经过哈希处理,以避免意外泄露个人信息。我还更新了说明,以注意设备ID在出厂重置之前将一直存在,并且某些用户可能不希望这样做。
emmby 2011年

1
我相信你是不对的。首选解决方案是跟踪安装,而不是设备标识符。您的代码比博客文章中的代码更长,更复杂,并且对我来说增加任何价值都不是显而易见的。
Tim Bray)的

7
好一点,我更新了评论以强烈建议用户使用应用程序安装ID而不是设备ID。但是,我认为该解决方案对于确实需要设备而不是安装ID的人仍然很有价值。
emmby 2011年

8
ANDROID_ID可以在恢复出厂设置时更改,因此也无法识别设备
塞缪尔

180

这是Reto Meier今年在Google I / O演示中使用的代码,用于为用户获取唯一的ID:

private static String uniqueID = null;
private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";

public synchronized static String id(Context context) {
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                PREF_UNIQUE_ID, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();
        }
    }
    return uniqueID;
}

如果您将其与备份策略结合使用,以将首选项发送到云(也在Reto的演讲中进行了介绍,则您应该具有一个与用户相关联的ID,并在擦除或什至更换了设备后仍会保留该ID。我计划使用此ID在未来的分析中(换句话说,我还没有做到这一点:)。


我正在使用@Lenn Dolling的方法加上当前时间作为唯一ID。但这似乎是更简单可靠的方法。感谢雷托•梅耶尔和安东尼诺兰
格克汗阿克尔巴里斯

太好了,但是植根设备又如何呢?他们可以访问它并将uid轻松更改为另一个。
塔斯马尼亚克(tasomaniac)

3
如果您不需要唯一ID即可在卸载并重新安装后继续存在(例如,促销活动/游戏,您有机会赢得三个机会,期限),这是一个不错的选择。
凯尔·克莱格

2
Meier演示依赖于使用Android备份管理器,而后者又取决于用户选择打开该功能。这对于应用程序用户首选项(Meier的使用)很好,因为如果用户未选择该选项,她将不会获得那些备份。但是,最初的问题是有关为设备生成唯一ID的问题,并且该ID是针对每个应用(甚至不是针对安装)生成的,更不用说针对每个设备生成的ID ,并且由于它依赖于用户选择备份选项,因此它的用途超出了用户偏好(例如,限时试用)受到限制。
卡尔,

12
这不适用于卸载或清除数据。
John Shelley 2014年

106

您也可以考虑Wi-Fi适配器的MAC地址。因此检索:

WifiManager wm = (WifiManager)Ctxt.getSystemService(Context.WIFI_SERVICE);
return wm.getConnectionInfo().getMacAddress();

需要android.permission.ACCESS_WIFI_STATE清单中的权限。

报告为即使未连接Wi-Fi也可以使用。如果Joe从上面的答案中尝试使用多种设备,那将是不错的选择。

在某些设备上,关闭Wi-Fi时不可用。

注意:从Android 6.x,它返回一致的假mac地址:02:00:00:00:00:00


8
这需要android.permission.ACCESS_WIFI_STATE
ohhorob

5
我想您会发现,几乎所有的android设备上,当WiFi关闭时,它都不可用。关闭WiFi将在内核级别删除设备。
chrisdowney

12
@Sanandrea-让我们面对现实吧,在有根的设备上,所有东西都可以被欺骗。
ocodo

5
访问无线网络的MAC地址已被阻止在Android L:stackoverflow.com/questions/31329733/...
BREEZ

6
从Android 6.x,它会返回一致的假Mac地址:02:00:00:00:00:00
Behrouz.M 2016年

87

有相当有用的信息在这里

它涵盖了五种不同的ID类型:

  1. IMEI(仅适用于使用手机的Android设备;需要android.permission.READ_PHONE_STATE
  2. 伪唯一ID(适用于所有Android设备)
  3. Android ID(可以为空,可以在恢复出厂设置时更改,可以在有根电话上更改)
  4. WLAN MAC地址字符串(需要android.permission.ACCESS_WIFI_STATE
  5. BT MAC地址字符串(带有蓝牙的设备,需要android.permission.BLUETOOTH

2
遗漏的重要点(此处和本文中):除非打开它们,否则您将无法获得WLAN或BT MAC!否则,我认为WLAN MAC将是完美的标识符。您不能保证用户会打开他们的Wi-Fi,我真的不认为自己打开Wi-Fi是“适当的”。
汤姆(Tom)

1
@汤姆,你错了。即使关闭WLAN或BT MAC,您仍然可以阅读它们。但是,不能保证设备具有可用的WLAN或BT模块。
Marqs 2014年

2
最值得注意的是,本地WiFi和蓝牙MAC地址不再可用.aWifiInfo对象的getMacAddress()方法和BluetoothAdapter.getDefaultAdapter()。getAddress()方法从现在开始都将返回02:00:00:00:00:00
萨里卡·凯特

4
@sarikakate仅在6.0棉花糖及更高版本中是正确的……在6.0棉花糖以下仍按预期运行。
Smeet

@Smeet是的,您是对的。我忘了提及它的工作在6.0以下
sarika kate

51

现在,Android开发者官方博客正式版中有一篇完整的文章,即确定应用程序安装


4
该论点的关键是,如果您试图从硬件中获取唯一的ID,则可能是在犯错误。
Bray)

3
而且,如果您允许通过工厂重置来重置设备锁,那么您的试用版软件模型就好坏了。
Seva Alekseyev

43

Reto Meier 在Google I / O上发布了一个强有力的答案,说明了如何解决此问题,这应满足大多数开发人员跟踪安装之间用户的需求。安东尼·诺兰(Anthony Nolan)给出了答案的方向,但我想我会写出完整的方法,以便其他人可以轻松地了解如何做到这一点(花了我一段时间才弄清楚细节)。

这种方法将为您提供一个匿名的安全用户ID,该ID对于不同设备(基于主要Google帐户)和安装之间的用户都是永久的。基本方法是生成随机用户ID,并将其存储在应用程序的共享首选项中。然后,您使用Google的备份代理将链接到Google帐户的共享首选项存储在云中。

让我们通过完整的方法。首先,我们需要使用Android备份服务为SharedPreferences创建备份。首先通过来注册您的应用http://developer.android.com/google/backup/signup.html

Google将为您提供备份服务密钥,您需要将其添加到清单中。您还需要告知应用程序使用BackupAgent,如下所示:

<application android:label="MyApplication"
         android:backupAgent="MyBackupAgent">
    ...
    <meta-data android:name="com.google.android.backup.api_key"
        android:value="your_backup_service_key" />
</application>

然后,您需要创建备份代理,并告诉它对共享首选项使用助手代理:

public class MyBackupAgent extends BackupAgentHelper {
    // The name of the SharedPreferences file
    static final String PREFS = "user_preferences";

    // A key to uniquely identify the set of backup data
    static final String PREFS_BACKUP_KEY = "prefs";

    // Allocate a helper and add it to the backup agent
    @Override
    public void onCreate() {
        SharedPreferencesBackupHelper helper = new SharedPreferencesBackupHelper(this,          PREFS);
        addHelper(PREFS_BACKUP_KEY, helper);
    }
}

要完成备份,您需要在主活动中创建一个BackupManager实例:

BackupManager backupManager = new BackupManager(context);

最后,创建一个用户ID(如果尚不存在),并将其存储在SharedPreferences中:

  public static String getUserID(Context context) {
            private static String uniqueID = null;
        private static final String PREF_UNIQUE_ID = "PREF_UNIQUE_ID";
    if (uniqueID == null) {
        SharedPreferences sharedPrefs = context.getSharedPreferences(
                MyBackupAgent.PREFS, Context.MODE_PRIVATE);
        uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);
        if (uniqueID == null) {
            uniqueID = UUID.randomUUID().toString();
            Editor editor = sharedPrefs.edit();
            editor.putString(PREF_UNIQUE_ID, uniqueID);
            editor.commit();

            //backup the changes
            BackupManager mBackupManager = new BackupManager(context);
            mBackupManager.dataChanged();
        }
    }

    return uniqueID;
}

现在,即使用户移动设备,该User_ID也将在安装过程中保持不变。

有关此方法的更多信息,请参见Reto的演讲

有关如何实施备份代理的完整详细信息,请参见数据备份。我特别推荐测试底部的部分,因为备份不会立即发生,因此要测试您必须强制执行备份。


5
当用户拥有多个设备时,这是否导致多个具有相同ID的设备?例如平板电脑和手机。
Tosa 2013年

为此需要最低目标8。
halxinate

在进行应用内购买时,这是创建验证有效载荷的首选方法吗?来自应用内结算示例代码的注释:“因此,好的开发人员有效负载具有以下特征:1.如果两个不同的用户购买了一个商品,则有效负载在他们之间是不同的,因此一个用户的购买不能重播给另一个用户。 2.有效负载必须确保即使应用程序不是发起购买流程的应用程序,您也可以对其进行验证(以便用户在一个设备上购买的商品可以在用户拥有的其他设备上使用)。”
TouchBoarder

@Tosa我有同样的问题。但是我们不能再次使用这种相同的技术来创建虚拟设备ID,只是不能以相同的方式进行备份吗?设备ID不会在擦除或重新安装后仍然存在,但是如果我们有一个持久的用户ID,则可能不需要太多设备ID。
jwehrle '16

39

我认为这无疑是为唯一ID构建骨架的一种行之有效的方法。

在所有Android设备上均可使用的伪唯一ID。某些设备没有电话(例如平板电脑),或者由于某些原因,您不希望包括READ_PHONE_STATE权限。您仍然可以阅读诸如ROM版本,制造商名称,CPU类型以及其他硬件详细信息之类的详细信息,如果要将ID用于序列密钥检查或其他常规用途,这些信息将非常适合。以这种方式计算出的ID不会是唯一的:可以找到两个具有相同ID(基于相同的硬件和ROM映像)的设备,但实际应用程序中的更改可以忽略不计。为此,您可以使用Build类:

String m_szDevIDShort = "35" + //we make this look like a valid IMEI
            Build.BOARD.length()%10+ Build.BRAND.length()%10 +
            Build.CPU_ABI.length()%10 + Build.DEVICE.length()%10 +
            Build.DISPLAY.length()%10 + Build.HOST.length()%10 +
            Build.ID.length()%10 + Build.MANUFACTURER.length()%10 +
            Build.MODEL.length()%10 + Build.PRODUCT.length()%10 +
            Build.TAGS.length()%10 + Build.TYPE.length()%10 +
            Build.USER.length()%10 ; //13 digits

大多数Build成员都是字符串,我们在这里要做的是获取它们的长度,并通过以位为模的方式对其进行转换。我们有13个这样的数字,并且在前面增加了两个(35),以具有与IMEI相同的大小ID(15个数字)。这里还有其他可能性,只看这些字符串即可。返回类似的内容355715565309247。不需要特殊许可,这使此方法非常方便。


(额外信息:以上给出的技术是从Pocket Magic上的文章中复制的。)


7
有趣的解决方案。听起来这是一种情况,您实际上应该只是对所有串联的数据进行哈希处理,而不是尝试使用自己的“哈希”函数。在很多情况下,即使每个值都有不同的大量数据,您也会遇到冲突。我的建议:使用哈希函数,然后将二进制结果转换为十进制并根据需要截断。要正确执行此操作,尽管您实际上应该使用UUID或完整的哈希字符串。
Steve Pomeroy

21
您应该相信您的消息来源...这已经直接从下面的文章中删除
史蒂夫·海利

8
这个ID很容易发生冲突,就像您不知道是什么。实际上,它保证来自同一运营商的同一设备上的相同。
塞瓦·阿列克谢耶夫

7
如果设备升级,这也可能会改变。
大卫

8
非常非常糟糕的解决方案。在两个Nexus 5上进行了测试...返回相同的数字。
SinanDizdarević15年

38

以下代码使用隐藏的Android API返回设备序列号。但是,此代码在Samsung Galaxy Tab上不起作用,因为未在此设备上设置“ ro.serialno”。

String serial = null;

try {
    Class<?> c = Class.forName("android.os.SystemProperties");
    Method get = c.getMethod("get", String.class);
    serial = (String) get.invoke(c, "ro.serialno");
}
catch (Exception ignored) {

}

我只是在xda开发人员上阅读过,ro.serialno用于生成Settings.Secure.ANDROID_ID。因此,它们基本上是相同值的不同表示形式。
马丁

@Martin:但是重置设备时序列号可能不会改变。是不是 ANDROID_ID从中可以得到一个新的值。
罗尼

实际上,在所有设备上我都测试过它们是否相同。或至少哈希值相同(出于隐私原因,我不将真实值写入日志文件)。
马丁

该值等于android.os.Build.SERIAL
eugeneek

android.os.Build.SERIAL将在Android O中弃用,请参见android-developers.googleblog.com/2017/04/…–
EpicPandaForce

31

这是一个简单的问题,没有简单的答案。

而且,这里所有现有的答案都是过时的还是不可靠的。

因此,如果您要在2020年寻求解决方案

这里有几件事要牢记:

所有基于硬件的标识符(SSAID,IMEI,MAC等)对于非Google的设备(除Pixels和Nexus之外的所有设备)都不可靠,这些设备占全球活动设备的50%以上。因此,官方Android标识符最佳做法明确指出:

避免使用硬件标识符,例如SSAID(Android ID),IMEI,MAC地址等。

这使得上面的大多数答案无效。同样由于不同的android安全更新,其中一些需要更新的和更严格的运行时权限,用户可以简单地拒绝它们。

作为CVE-2018-9489影响上述所有基于WIFI的技术的示例。

这使得这些标识符不仅不可靠,而且在许多情况下也不可访问。

所以用简单的话来说:不要使用那些技术

这里的许多其他答案都建议使用AdvertisingIdClient,这也是不兼容的,因为它的设计应仅用于广告配置文件。官方参考资料中也有说明

仅将广告ID用于用户配置文件或广告用例

这不仅会导致设备识别不可靠,而且您还必须遵守用户隐私权有关的广告跟踪政策,该政策明确规定用户可以随时重置或阻止它。

所以也不要使用它

由于您无法获得所需的静态全局唯一可靠的设备标识符。Android的官方参考建议:

对于所有其他用例,请尽可能使用FirebaseInstanceId或私有存储的GUID,但要防止付款欺诈和电话。

它对于在设备上安装应用程序来说是唯一的,因此当用户卸载应用程序时-它被清除了,因此并不是100%可靠的,但这是下一件好事。

要使用,FirebaseInstanceId请将最新的firebase -messaging依赖项添加到gradle中

implementation 'com.google.firebase:firebase-messaging:20.1.0'

并在后台线程中使用以下代码:

String reliableIdentifier = FirebaseInstanceId.getInstance().getId();

如果您需要将设备标识存储在远程服务器上,则不要按原样存储(纯文本),而应存储带有salt哈希

今天,这不仅是一种最佳实践,您实际上还必须根据GDPR(标识符和类似法规)依法进行。


3
就目前而言,这是最好的答案,第一句话就是最好的总结:“这是一个简单的问题,没有一个简单的答案-只是喜欢它。
b2mob

您必须赞赏“防止支付欺诈和电话之外的”注释,但没有提供有关如何解决该用例的答案。
Eran Boudjnah

@EranBoudjnah引用中引用了官方参考中的那句话。我可以尝试解决该用例,但是由于它不是特定于OP问题的,根据stackoverflow的策略-应该在一个单独的专用问题中完成。
Nikita Kurtin

我只是说答案不完整。但是我知道这不是您的错,因为那是报价。
Eran Boudjnah

1
@ M.UsmanKhan,答案写在那之后:“ 今天,这不仅是一种最佳实践,实际上您还必须根据GDPR-标识符和类似规定,通过法律来做到这一点。
Nikita Kurtin

27

使用下面的代码,您可以获得字符串形式的Android OS设备的唯一设备ID。

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

21

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

我不知道API级别> = 9的所有设备是否实际上都支持(= not null)。


2
不幸的是,这是“未知的”。
m0skit0 2013年

18

我要添加的一件事-我遇到了其中一种独特的情况。

使用:

deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);

事实证明,即使我的Viewsonic G Tablet报告的DeviceID不是Null,每个G Tablet报告的编号都是相同的。

玩“ Pocket Empires”使它变得有趣,它使您可以基于“唯一” DeviceID即时访问某人的帐户。

我的设备没有手机。


身份证是什么?有魅力9774d56d682e549c吗?
Mr_and_Mrs_D

哇,那是很久以前的事,我早就抛弃了这款平板电脑。不能说。
Tony Maro 2013年

作品。而且,它比我从随机UUID获得的ID更紧凑。
Treewallie

1
@Treewallie有效吗?您可以从其他应用程序获得相同的设备ID吗?
阿诺德·布朗

@ArnoldBrown是的。确保对其进行彻底测试。祝您有美好的一天:D
Treewallie

16

有关如何获取安装了应用程序的每个Android设备的唯一标识符的详细说明,请参阅发布了“ 标识应用程序安装”的官方Android开发者博客。

似乎最好的方法是让您在安装时自行生成一个文件,然后在重新启动该应用程序时阅读该文件。

我个人认为这可以接受,但并不理想。Android提供的任何标识符都无法在所有情况下正常工作,因为大多数标识符都取决于手机的无线电状态(Wi-Fi开/关,蜂窝式开/关,蓝牙开/关)。其他的,例如Settings.Secure.ANDROID_ID必须由制造商实施,并且不能保证是唯一的。

以下是将数据写入安装文件的示例,该文件将与应用程序本地保存的任何其他数据一起存储。

public class Installation {
    private static String sID = null;
    private static final String INSTALLATION = "INSTALLATION";

    public synchronized static String id(Context context) {
        if (sID == null) {
            File installation = new File(context.getFilesDir(), INSTALLATION);
            try {
                if (!installation.exists())
                    writeInstallationFile(installation);
                sID = readInstallationFile(installation);
            } 
            catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
        return sID;
    }

    private static String readInstallationFile(File installation) throws IOException {
        RandomAccessFile f = new RandomAccessFile(installation, "r");
        byte[] bytes = new byte[(int) f.length()];
        f.readFully(bytes);
        f.close();
        return new String(bytes);
    }

    private static void writeInstallationFile(File installation) throws IOException {
        FileOutputStream out = new FileOutputStream(installation);
        String id = UUID.randomUUID().toString();
        out.write(id.getBytes());
        out.close();
    }
}

如果您想跟踪应用程序安装,这是完美的选择。跟踪设备要复杂得多,而且似乎还没有完全密封的解决方案。
卡·史派勒

植根设备呢?他们可以轻松更改此安装ID,对吗?
塔斯马尼亚克2012年

绝对。根目录可以更改安装ID。:您可以使用此代码块检查根stackoverflow.com/questions/1101380/...
凯文·帕克

如果恢复出厂设置,该文件会删除吗?
Jamshid

如果您恢复出厂设置并删除或格式化/ data分区,则UUID是不同的。
凯文·帕克

12

在类文件中添加以下代码:

final TelephonyManager tm = (TelephonyManager) getBaseContext()
            .getSystemService(SplashActivity.TELEPHONY_SERVICE);
    final String tmDevice, tmSerial, androidId;
    tmDevice = "" + tm.getDeviceId();
    Log.v("DeviceIMEI", "" + tmDevice);
    tmSerial = "" + tm.getSimSerialNumber();
    Log.v("GSM devices Serial Number[simcard] ", "" + tmSerial);
    androidId = "" + android.provider.Settings.Secure.getString(getContentResolver(),
            android.provider.Settings.Secure.ANDROID_ID);
    Log.v("androidId CDMA devices", "" + androidId);
    UUID deviceUuid = new UUID(androidId.hashCode(),
            ((long) tmDevice.hashCode() << 32) | tmSerial.hashCode());
    String deviceId = deviceUuid.toString();
    Log.v("deviceIdUUID universally unique identifier", "" + deviceId);
    String deviceModelName = android.os.Build.MODEL;
    Log.v("Model Name", "" + deviceModelName);
    String deviceUSER = android.os.Build.USER;
    Log.v("Name USER", "" + deviceUSER);
    String devicePRODUCT = android.os.Build.PRODUCT;
    Log.v("PRODUCT", "" + devicePRODUCT);
    String deviceHARDWARE = android.os.Build.HARDWARE;
    Log.v("HARDWARE", "" + deviceHARDWARE);
    String deviceBRAND = android.os.Build.BRAND;
    Log.v("BRAND", "" + deviceBRAND);
    String myVersion = android.os.Build.VERSION.RELEASE;
    Log.v("VERSION.RELEASE", "" + myVersion);
    int sdkVersion = android.os.Build.VERSION.SDK_INT;
    Log.v("VERSION.SDK_INT", "" + sdkVersion);

在AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />

10

通过TelephonyManagerANDROID_ID获取Android OS设备的唯一设备ID为String,方法是:

String deviceId;
final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
if (mTelephony.getDeviceId() != null) {
    deviceId = mTelephony.getDeviceId();
}
else {
    deviceId = Secure.getString(
                   getApplicationContext().getContentResolver(),
                   Secure.ANDROID_ID);
}

但我强烈建议Google建议一种方法,请参阅识别应用程序安装


9

有很多不同的方法可以解决这些ANDROID_ID问题(null有时可能会或某些特定型号的设备总是返回相同的ID),但各有利弊:

  • 实施自定义ID生成算法(基于应该是静态且不会更改的设备属性->谁知道)
  • 滥用其他ID,例如IMEI,序列号,Wi-Fi /蓝牙MAC地址(它们不会在所有设备上都存在,或者需要其他权限)

我本人更喜欢使用适用于Android 的现有OpenUDID实现(请参阅https://github.com/ylechelle/OpenUDID)(请参阅https://github.com/vieux/OpenUDID)。它很容易集成,并可以利用ANDROID_ID带有后备功能的上述问题。


8

怎么样的IMEI。这对于Android或其他移动设备是唯一的。


9
不适合我的平板电脑,因为它们没有连接到我的移动运营商,所以没有IMEI。
Brill Pappin 2012年

2
更不用说具有ESN而不是IMEI的CDMA设备。
大卫

@David Given有安卓的CDMA吗?
Elzo Valugi 2012年

1
它只会做那就是它一个电话:)平板电脑可能不会。
Brill Pappin 2012年

3
@ElzoValugi已经是“今天”了,仍然不是所有的平板电脑都有SIM卡。
马修·奎罗斯

8

这是我生成唯一ID的方法:

public static String getDeviceId(Context ctx)
{
    TelephonyManager tm = (TelephonyManager) ctx.getSystemService(Context.TELEPHONY_SERVICE);

    String tmDevice = tm.getDeviceId();
    String androidId = Secure.getString(ctx.getContentResolver(), Secure.ANDROID_ID);
    String serial = null;
    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.FROYO) serial = Build.SERIAL;

    if(tmDevice != null) return "01" + tmDevice;
    if(androidId != null) return "02" + androidId;
    if(serial != null) return "03" + serial;
    // other alternatives (i.e. Wi-Fi MAC, Bluetooth MAC, etc.)

    return null;
}

如果我们使用6.0版本的ReadPhoneState要求运行时权限
Harsha

8

我的两分钱-注意,这是针对设备(err)的唯一ID-而不是Android开发者博客中讨论的安装ID

值得注意的是,@emmby提供的解决方案将按每个应用程序ID返回,因为SharedPreferences在各个进程之间不同步(请参见此处此处)。因此,我完全避免了这种情况。

相反,我封装了在枚举中获取(设备)ID的各种策略-更改枚举常量的顺序会影响获取ID的各种方式的优先级。返回第一个非null的ID或引发异常(按照Java的良好做法,即不赋予null含义)。例如,我首先使用TELEPHONY-但一个很好的默认选择是ANDROID_ID beta:

import android.Manifest.permission;
import android.bluetooth.BluetoothAdapter;
import android.content.Context;
import android.content.pm.PackageManager;
import android.net.wifi.WifiManager;
import android.provider.Settings.Secure;
import android.telephony.TelephonyManager;
import android.util.Log;

// TODO : hash
public final class DeviceIdentifier {

    private DeviceIdentifier() {}

    /** @see http://code.google.com/p/android/issues/detail?id=10603 */
    private static final String ANDROID_ID_BUG_MSG = "The device suffers from "
        + "the Android ID bug - its ID is the emulator ID : "
        + IDs.BUGGY_ANDROID_ID;
    private static volatile String uuid; // volatile needed - see EJ item 71
    // need lazy initialization to get a context

    /**
     * Returns a unique identifier for this device. The first (in the order the
     * enums constants as defined in the IDs enum) non null identifier is
     * returned or a DeviceIDException is thrown. A DeviceIDException is also
     * thrown if ignoreBuggyAndroidID is false and the device has the Android ID
     * bug
     *
     * @param ctx
     *            an Android constant (to retrieve system services)
     * @param ignoreBuggyAndroidID
     *            if false, on a device with the android ID bug, the buggy
     *            android ID is not returned instead a DeviceIDException is
     *            thrown
     * @return a *device* ID - null is never returned, instead a
     *         DeviceIDException is thrown
     * @throws DeviceIDException
     *             if none of the enum methods manages to return a device ID
     */
    public static String getDeviceIdentifier(Context ctx,
            boolean ignoreBuggyAndroidID) throws DeviceIDException {
        String result = uuid;
        if (result == null) {
            synchronized (DeviceIdentifier.class) {
                result = uuid;
                if (result == null) {
                    for (IDs id : IDs.values()) {
                        try {
                            result = uuid = id.getId(ctx);
                        } catch (DeviceIDNotUniqueException e) {
                            if (!ignoreBuggyAndroidID)
                                throw new DeviceIDException(e);
                        }
                        if (result != null) return result;
                    }
                    throw new DeviceIDException();
                }
            }
        }
        return result;
    }

    private static enum IDs {
        TELEPHONY_ID {

            @Override
            String getId(Context ctx) {
                // TODO : add a SIM based mechanism ? tm.getSimSerialNumber();
                final TelephonyManager tm = (TelephonyManager) ctx
                        .getSystemService(Context.TELEPHONY_SERVICE);
                if (tm == null) {
                    w("Telephony Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.READ_PHONE_STATE);
                return tm.getDeviceId();
            }
        },
        ANDROID_ID {

            @Override
            String getId(Context ctx) throws DeviceIDException {
                // no permission needed !
                final String andoidId = Secure.getString(
                    ctx.getContentResolver(),
                    android.provider.Settings.Secure.ANDROID_ID);
                if (BUGGY_ANDROID_ID.equals(andoidId)) {
                    e(ANDROID_ID_BUG_MSG);
                    throw new DeviceIDNotUniqueException();
                }
                return andoidId;
            }
        },
        WIFI_MAC {

            @Override
            String getId(Context ctx) {
                WifiManager wm = (WifiManager) ctx
                        .getSystemService(Context.WIFI_SERVICE);
                if (wm == null) {
                    w("Wifi Manager not available");
                    return null;
                }
                assertPermission(ctx, permission.ACCESS_WIFI_STATE); // I guess
                // getMacAddress() has no java doc !!!
                return wm.getConnectionInfo().getMacAddress();
            }
        },
        BLUETOOTH_MAC {

            @Override
            String getId(Context ctx) {
                BluetoothAdapter ba = BluetoothAdapter.getDefaultAdapter();
                if (ba == null) {
                    w("Bluetooth Adapter not available");
                    return null;
                }
                assertPermission(ctx, permission.BLUETOOTH);
                return ba.getAddress();
            }
        }
        // TODO PSEUDO_ID
        // http://www.pocketmagic.net/2011/02/android-unique-device-id/
        ;

        static final String BUGGY_ANDROID_ID = "9774d56d682e549c";
        private final static String TAG = IDs.class.getSimpleName();

        abstract String getId(Context ctx) throws DeviceIDException;

        private static void w(String msg) {
            Log.w(TAG, msg);
        }

        private static void e(String msg) {
            Log.e(TAG, msg);
        }
    }

    private static void assertPermission(Context ctx, String perm) {
        final int checkPermission = ctx.getPackageManager().checkPermission(
            perm, ctx.getPackageName());
        if (checkPermission != PackageManager.PERMISSION_GRANTED) {
            throw new SecurityException("Permission " + perm + " is required");
        }
    }

    // =========================================================================
    // Exceptions
    // =========================================================================
    public static class DeviceIDException extends Exception {

        private static final long serialVersionUID = -8083699995384519417L;
        private static final String NO_ANDROID_ID = "Could not retrieve a "
            + "device ID";

        public DeviceIDException(Throwable throwable) {
            super(NO_ANDROID_ID, throwable);
        }

        public DeviceIDException(String detailMessage) {
            super(detailMessage);
        }

        public DeviceIDException() {
            super(NO_ANDROID_ID);
        }
    }

    public static final class DeviceIDNotUniqueException extends
            DeviceIDException {

        private static final long serialVersionUID = -8940090896069484955L;

        public DeviceIDNotUniqueException() {
            super(ANDROID_ID_BUG_MSG);
        }
    }
}

8

这里有30多个答案,有些是相同的,有些是独特的。该答案基于其中一些答案。其中之一就是@Lenn Dolling的答案。

它结合了3个ID,并创建了一个32位十六进制字符串。它对我来说非常有效。

3个ID是:
伪ID -它是基于物理设备的规格而生成
ANDROID_ID - Settings.Secure.ANDROID_ID
蓝牙地址 -蓝牙适配器地址

它将返回如下内容: 551F27C060712A72730B0A0F734064B1

注意:您始终可以在longId字符串中添加更多ID 。例如,序列号。wifi适配器地址。IMEI。这样,您就可以使每个设备变得更加独特。

@SuppressWarnings("deprecation")
@SuppressLint("HardwareIds")
public static String generateDeviceIdentifier(Context context) {

        String pseudoId = "35" +
                Build.BOARD.length() % 10 +
                Build.BRAND.length() % 10 +
                Build.CPU_ABI.length() % 10 +
                Build.DEVICE.length() % 10 +
                Build.DISPLAY.length() % 10 +
                Build.HOST.length() % 10 +
                Build.ID.length() % 10 +
                Build.MANUFACTURER.length() % 10 +
                Build.MODEL.length() % 10 +
                Build.PRODUCT.length() % 10 +
                Build.TAGS.length() % 10 +
                Build.TYPE.length() % 10 +
                Build.USER.length() % 10;

        String androidId = Settings.Secure.getString(context.getContentResolver(), Settings.Secure.ANDROID_ID);

        BluetoothAdapter bluetoothAdapter = BluetoothAdapter.getDefaultAdapter();
        String btId = "";

        if (bluetoothAdapter != null) {
            btId = bluetoothAdapter.getAddress();
        }

        String longId = pseudoId + androidId + btId;

        try {
            MessageDigest messageDigest = MessageDigest.getInstance("MD5");
            messageDigest.update(longId.getBytes(), 0, longId.length());

            // get md5 bytes
            byte md5Bytes[] = messageDigest.digest();

            // creating a hex string
            String identifier = "";

            for (byte md5Byte : md5Bytes) {
                int b = (0xFF & md5Byte);

                // if it is a single digit, make sure it have 0 in front (proper padding)
                if (b <= 0xF) {
                    identifier += "0";
                }

                // add number to string
                identifier += Integer.toHexString(b);
            }

            // hex string to uppercase
            identifier = identifier.toUpperCase();
            return identifier;
        } catch (Exception e) {
            Log.e("TAG", e.toString());
        }
        return "";
}

1
UUID添加到longId并将其存储在文件中,将使其成为最唯一的标识符:String uuid = UUID.randomUUID().toString();
Mousa Alfhaily

1
如果其他所有方法均失败,则如果用户确实低于API 9(低于Gingerbread),则已重置其电话或“ Secure.ANDROID_ID”。如果返回“ null”,则返回的ID仅基于其Android设备信息。这是可能发生碰撞的地方。尽量不要使用DISPLAY,HOST或ID-这些项目可能会更改。如果有冲突,将有重叠的数据。来源:gist.github.com/pedja1/fe69e8a80ed505500caa
Mousa Alfhaily

如果我们尝试通过此行代码获取唯一编号,那么我们可以说它是唯一编号,它永远不会与任何其他设备发生冲突吗?
忍者

1
@Ninja由于BLE mac地址是唯一的,因此生成的ID将始终是唯一的。但是,如果您确实想确定的话,建议您将UUID添加到中longId。像这样更改一行:String longId = pseudoId + androidId + btId + UUID.randomUUID().toString();这样可以保证生成的ID是唯一的。
ᴛʜᴇᴘᴀᴛᴇʟ

@ᴛʜᴇᴘᴀᴛᴇʟ非常感谢您提供的巨大帮助。实际上我的应用程序的数据非常敏感,因此我需要确定这一点,这就是为什么我只是在确认这一点。
忍者

7

另一种方法是/sys/class/android_usb/android0/iSerial在没有任何权限的情况下在应用中使用。

user@creep:~$ adb shell ls -l /sys/class/android_usb/android0/iSerial
-rw-r--r-- root     root         4096 2013-01-10 21:08 iSerial
user@creep:~$ adb shell cat /sys/class/android_usb/android0/iSerial
0A3CXXXXXXXXXX5

为此,只需使用FileInputStream打开iSerial文件并读出字符即可。只要确保将其包装在异常处理程序中即可,因为并非所有设备都具有此文件。

至少已知以下设备对此文件具有世界可读性:

  • Galaxy Nexus
  • Nexus S
  • 摩托罗拉Xoom 3G
  • 东芝AT300
  • HTC One V
  • 迷你MK802
  • 三星Galaxy S II

您还可以在我的博客文章中讨论将Android硬件序列号泄漏到非特权应用中,在此我讨论了哪些其他文件可供参考。


我刚刚读了您的博客文章。我认为这不是唯一的:Build.SERIAL也可以不带任何权限使用,并且(在理论上)是唯一的硬件序列号。
汤姆(Tom)

1
你是对的。这只是可以跟踪您的设备的另一种方式,正如您所说的,这两种方式都不需要应用程序许可。
insitusec 2014年

7

TelephonyManger.getDeviceId()返回唯一的设备ID,例如,GSM的IMEI和CDMA电话的MEID或ESN。

final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);            
String myAndroidDeviceId = mTelephony.getDeviceId(); 

但我建议使用:

Settings.Secure.ANDROID_ID,它以唯一的64位十六进制字符串形式返回Android ID。

    String   myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 

有时TelephonyManger.getDeviceId()将返回null,因此,为确保唯一的ID,您将使用此方法:

public String getUniqueID(){    
    String myAndroidDeviceId = "";
    TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);
    if (mTelephony.getDeviceId() != null){
        myAndroidDeviceId = mTelephony.getDeviceId(); 
    }else{
         myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
    }
    return myAndroidDeviceId;
}

我最近发现,客户端设备类型为SM-G928F / Galaxy S6 edge +的Android ID仅传送15个而不是16个十六进制数字。
Holger Jakobs

7

对于特定Android设备的硬件识别,您可以检查MAC地址。

你可以这样:

在AndroidManifest.xml中

<uses-permission android:name="android.permission.INTERNET" />

现在在您的代码中:

List<NetworkInterface> interfacesList = Collections.list(NetworkInterface.getNetworkInterfaces());

for (NetworkInterface interface : interfacesList) {
   // This will give you the interface MAC ADDRESS
   interface.getHardwareAddress();
}

在每个Android设备中,它们至少是一个“ wlan0”接口,它是WI-FI芯片。即使未打开WI-FI,此代码也有效。

PS他们还有很多其他接口,您可以从包含MACS的列表中获得,但这在手机之间可能会有所不同。


7

我使用以下代码获取IMEI或使用Secure。ANDROID_ID或者,当设备不具备电话功能时:

String identifier = null;
TelephonyManager tm = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE));
if (tm != null)
      identifier = tm.getDeviceId();
if (identifier == null || identifier .length() == 0)
      identifier = Secure.getString(activity.getContentResolver(),Secure.ANDROID_ID);

7

更具体地说,Settings.Secure.ANDROID_ID。这是设备首次启动时生成并存储的64位数量。擦拭设备时将其重置。

ANDROID_ID唯一的设备标识符似乎是一个不错的选择。有缺点:首先,它在2.2之前的Android版本上不是100%可靠的。(“Froyo”).而且,在主要制造商的流行手机中,至少有一个被广泛观察到的错误,其中每个实例都具有相同的ANDROID_ID。


1
此答案是旧Google博客android-developers.googleblog.com/2011/03/…的复制粘贴。那么错误已经解决了吗?
塞尔吉(Sergii)


6

Google实例ID

在2015年I / O上发布;在Android上需要播放服务7.5。

https://developers.google.com/instance-id/
https://developers.google.com/instance-id/guides/android-implementation

InstanceID iid = InstanceID.getInstance( context );   // Google docs are wrong - this requires context
String id = iid.getId();  // blocking call

Google似乎打算将此ID用于识别Android,Chrome和iOS上的安装。

它标识的是安装而不是设备,但是,ANDROID_ID(这是公认的答案)现在也不再标识设备。借助ARC运行时,将为每个安装生成一个新的ANDROID_ID(此处有详细信息),就像此新实例ID一样。另外,我认为识别安装(不是设备)是我们大多数人真正想要的。

实例ID的优点

在我看来,Google打算将其用于此目的(标识您的安装),它是跨平台的,并且可以用于许多其他目的(请参见上面的链接)。

如果您使用的是GCM,则最终将需要使用该实例ID,因为您需要它才能获取GCM令牌(它将替换旧的GCM注册ID)。

缺点/问题

在当前的实施方式(GPS 7.5)中,当您的应用请求实例ID时,将从服务器中检索实例ID。这意味着上面的呼叫是一个阻塞呼叫-在我不科学的测试中,如果设备在线,则需要1-3秒,如果设备离线则需要0.5-1.0秒(大概这是在放弃并生成信号之前等待的时间)随机ID)。该产品已在北美的Nexus 5上通过Android 5.1.1和GPS 7.5进行了测试。

如果您将ID用于目的,例如 应用验证,应用标识,GCM-我认为这1-3秒可能会很麻烦(当然,这取决于您的应用)。


1
instanceID的另一个重要缺点是,如果用户清除了应用程序的数据,则会为您生成一个新的instanceID。
idanakav 2015年

有趣的是,但我认为它不会真正改变潜在的用例:实例ID(例如android_id)不适合标识设备。因此,您的服务器将看到用户清除数据,就像用户卸载并重新安装您的应用程序一样-这并非不合理。
汤姆(Tom)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.