是否有唯一的Android设备ID?

Tyl*_*ler 2645 android uniqueidentifier

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

Ant*_*ney 1937

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

import android.provider.Settings.Secure;

private String android_id = Secure.getString(getContext().getContentResolver(),
                                                        Secure.ANDROID_ID); 
Run Code Online (Sandbox Code Playgroud)

  • 它有时被称为null,它被记录为"可以在出厂重置时更改".使用风险自负,可以在root手机上轻松更改. (456认同)
  • 请注意,此解决方案存在巨大限制:http://android-developers.blogspot.com/2011/03/identifying-app-installations.html (46认同)
  • ANDROID_ID不再唯一标识设备(截至4.2):http://stackoverflow.com/a/13465373/150016 (33认同)
  • http://groups.google.com/group/android-developers/browse_thread/thread/53898e508fab44f6/84e54feb28272384?lnk=raot (30认同)
  • 我认为我们需要注意在第一个答案中使用哈希中的ANDROID_ID,因为它可能在首次运行app时未设置,可能在以后设置,或者甚至可能在理论上改变,因此唯一ID可能会更改 (15认同)
  • **Android O**将更改此https://android-developers.googleblog.com/2017/04/changes-to-device-identifiers-in.html (9认同)
  • 这个问题的2020版答案是什么?当然事情已经改变了。 (6认同)
  • 此ANDROID_ID仅适用于Android选项卡,如果您可以在手机中运行,那么如果您将在Tab中运行,您将获得null,然后您可以获得唯一ID.-Girish (4认同)
  • **不建议使用 getString 获取设备标识符。** 为什么? (3认同)
  • 如果用户清除Play服务缓存数据,这似乎也会改变.http://www.androidpolice.com/2013/11/20/google-engineer-dan-morrill-sheds-some-light-on-the-nexus-ota-process-urges-you-to-never-clear- google-service-framework-data /提示_"这样做会改变Google知道你设备的主要ID.就服务器而言,设备基本上是出厂重置."_我假设是`ANDROID_ID` - 但我无法在测试中复制这一点 - 无论如何都值得注意...... (2认同)
  • 萨拉姆。看看@Joe 的回答 [http://stackoverflow.com/a/2853253/1676736](http://stackoverflow.com/a/2853253/1676736) (2认同)
  • 我一直在我的应用程序中使用 Android ID 来唯一标识用户,因为我觉得这比使用电子邮件 ID 进行常规注册更容易,到目前为止我还没有发现这个解决方案的任何问题,到目前为止我们的订阅者都没有抱怨,我们知道如果再次发生后备选项不会那么具有破坏性这一切都取决于那种应用程序,除非应用程序需要严格的安全性我认为 ANDROID ID 可能不是问题 (2认同)
  • Android Lint 表示“不建议使用 getString 获取设备标识符。检查信息:除了高价值的欺诈预防和高级电话用例之外,不建议使用这些设备标识符。对于广告用例,请使用 AdvertisingIdClient$Info# getId 并进行分析,请使用 InstanceId#getId。 (2认同)
  • @AaronUllal 抱歉有点晚了,不,我不这么认为,设置下的“关于手机”有 Android 版本、IMEI 等内容,在我检查的一部 Android 手机中,也许这与用户的相关性不如与应用程序的相关性 (2认同)

Joe*_*Joe 1123

更新:截至Android的最新版本,许多问题ANDROID_ID已经解决,我相信这种方法已不再需要.请看看安东尼的回答.

完全披露:我的应用程序最初使用了以下方法但不再使用此方法,我们现在使用Android开发人员博客条目中概述的方法,即emmby的答案链接(即生成和保存a 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() 应该是足够的.显然,有些用户比其他用户更偏执,因此对这些标识符中的一个或多个进行散列可能很有用,这样该字符串对于设备来说几乎是唯一的,但是没有明确标识用户的实际设备.例如,使用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();
Run Code Online (Sandbox Code Playgroud)

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

它对我来说效果很好.

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

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Run Code Online (Sandbox Code Playgroud)

导入库

import android.content.Context;
import android.telephony.TelephonyManager;
import android.view.View;
Run Code Online (Sandbox Code Playgroud)

  • 平板设备不会出现基于电话的ID,对吗? (147认同)
  • 代码示例效果很好.记得在清单文件中添加`<uses-permission android:name ="android.permission.READ_PHONE_STATE"/>`.如果存储在数据库中,则返回的字符串长度为36个字符. (31认同)
  • 因此,为什么我说大多数都不会一直工作:)我还没有看到这个问题的任何答案对所有设备,所有设备类型和所有硬件配置都是可靠的.这就是为什么这个问题从这里开始的原因.很明显,没有最终解决方案.单个设备制造商可能具有设备序列号,但这些设备序列号不会公开供我们使用,并且不是必需的.因此,我们留给我们可用的东西. (22认同)
  • @softarn:我相信你所指的是emmby已经链接到的Android开发人员博客,它解释了你正在尝试*的内容,所以也许你应该简单地赞成他的评论.无论哪种方式,正如emmby在他的回答中提到的那样,即使博客信息也存在问题.该问题要求一个唯一的**DEVICE**标识符(不是安装标识符),所以我不同意你的陈述.博客假设你想要*不一定*跟踪设备,而问题就是要求.我同意博客. (18认同)
  • 请注意,此解决方案存在巨大限制:http://android-developers.blogspot.com/2011/03/identifying-app-installations.html (9认同)
  • 我认为这种方法存在一个严重的缺陷.这取决于SIM卡信息,用户有时会切换到飞行模式=没有连接SIM卡.在这种情况下,您需要具备回退机制,可能需要使用Android ID. (5认同)
  • @Daniel Novak:公平点.对于我的情况,它是不相关的,因为id用于API通信,因此飞行模式=没有app :)我们也这样设计它,例如,如果用户交换SIM卡,我们故意想要"设备id"与众不同.很显然,对于每个人来说都不是这样,所以感谢你指出来. (4认同)
  • 我有点惊讶,没有人提起这个,但代码示例在某些情况下真的很不幸 - 它会返回一个不同的ID,具体取决于手机是否在飞行模式,因为如果关闭设备gsm网络,则simSerialNumber将为null.如果您的应用程序依赖于唯一ID,但只需要互联网,不一定需要网络,那么最好确保代码始终返回相同的ID,而不管当前手机的网络状态如何. (4认同)
  • 我强烈建议您不要使用此代码。它有几个严重的错误:首先,不能保证电话和 Android ID 都存在,如果两者都不存在,您的设备将仅根据它们的型号进行映射。**其次,也是更重要的**,当您使用 Android ID 的哈希码时,您会将数字的 64 位字符串表示形式转换为 32 位数字,因此您失去了很多唯一性。 (3认同)
  • 更多测试后你会更新你的答案吗?这将非常有趣. (2认同)
  • 仅供参考,TelephonyManager.getDeviceId() 在我的 Xoom 上返回一个有效的唯一 ID,因此这似乎也适用于平板电脑。 (2认同)
  • 此答案已过时,不应使用。不要跟踪设备,跟踪安装! (2认同)
  • 我已经使用此代码 6 个月了。它不会生成唯一 ID。我不建议你们所有人都使用这个代码。目前我正在使用这篇文章中的代码:http://stackoverflow.com/a/17625641/365229 (2认同)

Jar*_*ows 425

最后更新时间:2015年6月2日


在阅读关于创建唯一ID,Google开发人员博客和Android文档的每篇Stack Overflow帖子后,我觉得好像'Pseudo ID'是最好的选择.

主要问题:硬件与软件

硬件

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

软件

  • 如果root用户可以擦除/更改他们的ROM
  • 您可以跨平台(iOS,Android,Windows和Web)跟踪用户
  • 最好的想要在他们同意的情况下追踪个人用户只需让他们登录(使用OAuth使其无缝)

Android的总体细分

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

- 没有额外的权限

Psuedo代码:

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)
Run Code Online (Sandbox Code Playgroud)

感谢@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',我们可以检查'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 "/>
  • Pseudo-Unique ID - 软件(适用于所有Android设备)

    • 很可能,可能包含碰撞 - 请参阅下面发布的方法!
    • 这使您可以从用户那里获得"几乎唯一"的ID,而无需使用任何私有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();
}
Run Code Online (Sandbox Code Playgroud)

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

来自Google Play Developer的控制台:

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

实施:

允许:

<uses-permission android:name="android.permission.INTERNET" />
Run Code Online (Sandbox Code Playgroud)

码:

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();
}
Run Code Online (Sandbox Code Playgroud)

来源/文档:

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).Google Play服务不可用的情况由getAdvertisingIdInfo()抛出的GooglePlayServicesNotAvailableException指示.

警告,用户可以重置:

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

我试图引用我从中获取信息的每个链接.如果您遗失并需要加入,请发表评论!

Google Player服务实例ID

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

  • 我在我的应用程序中使用了您的方法来发送评论。我有坏消息。不幸的是,PsuedoID 并不是完全唯一的。我的服务器记录了 5 个 ID 超过 100 个,几乎 30 个 ID 记录了 30 个以上。重复次数最多的 ID 是 'ffffffff-fc8f-6093-ffff-ffffd8'(159 次记录)和 'ffffffff-fe99-b334-ffff-ffffef'(154 次)。同样根据时间和评论,很明显有不同的人。迄今为止的总记录数为 10,000。请让我知道为什么会这样。坦克。 (6认同)
  • 我是 1.5 多年前写的。我不知道为什么它对你来说不是独一无二的。你可以试试广告ID。如果没有,您可以提出自己的解决方案。 (2认同)
  • sorta ..如果你仔细阅读并提出自己的想法,我真的很感激 (2认同)

emm*_*mby 334

正如Dave Webb所提到的,Android开发人员博客有一篇文章介绍了这一点.他们首选的解决方案是跟踪应用安装而不是设备,这对大多数用例都有效.博客文章将向您展示完成这项工作所需的代码,我建议您查看它.

但是,如果您需要设备标识符而不是应用程序安装标识符,则博客文章会继续讨论解决方案.我与Google的某位人士进行了交谈,以便在您需要的情况下对一些项目进行一些额外的澄清.以下是我在上述博文中未提及的设备标识符的发现:

  • ANDROID_ID是首选的设备标识符.ANDROID_ID在Android <= 2.1或> = 2.3的版本上非常可靠.只有2.2有帖子中提到的问题.
  • 几个制造商的几个设备受2.2中的ANDROID_ID错误的影响.
  • 据我所知,所有受影响的设备都具有相同的ANDROID_ID,即9774d56d682e549c.这也是模拟器报告的相同设备ID,顺便说一下.
  • 谷歌相信原始设备制造商已经为他们的许多或大部分设备修补了这个问题,但我能够验证到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;
    }
}
Run Code Online (Sandbox Code Playgroud)

  • ANDROID_ID可以在出厂重置时更改,因此它也无法识别设备 (8认同)
  • 好点,我更新了评论,强烈建议用户使用应用程序安装ID而不是设备ID.但是,我认为这种解决方案对于需要设备而非安装ID的人来说仍然很有价值. (7认同)
  • 您是否应该对各种ID进行哈希处理以使它们的大小相同?此外,您应该对设备ID进行哈希处理,以免意外泄露私人信息. (6认同)
  • 好的,史蒂夫.我更新了代码以始终返回UUID.这确保了a)生成的ID总是相同的大小,并且b)在返回之前对android和设备ID进行哈希处理以避免意外地暴露个人信息.我还更新了描述,注意设备ID将在出厂重置时保持不变,并且这对某些用户来说可能并不理想. (2认同)
  • 我相信你是不正确的;首选的解决方案是跟踪安装,而不是设备标识符。您的代码比博客文章中的代码更长、更复杂,对我来说它增加了任何价值并不明显。 (2认同)
  • 正如我在 [here](http://stackoverflow.com/questions/2322234/how-to-find-serial-number-of-android-device#comment27850992_5626213) 中提到的同样的答案,不能保证你会得到同样的跨进程生成 UUID - SharedPrefs 副本是 _local_ ! (2认同)

Ant*_*lan 176

以下是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;
}
Run Code Online (Sandbox Code Playgroud)

如果你把这个与备份策略结合起来将偏好发送到云端(在Reto的谈话中也有描述,你应该有一个与用户绑定的ID,并在设备被擦除甚至更换后坚持使用.我打算使用这个在未来的分析中(换句话说,我还没有完成那一点:).

  • 这不适用于卸载或清除数据. (12认同)
  • 如果您在卸载并重新安装后不需要唯一ID(例如,您有三次获胜机会的促销活动/游戏,期间),那么这是一个很好的选择. (3认同)
  • Meier演示依赖于使用Android备份管理器,而后者依赖于用户选择打开该功能.对于应用程序用户首选项(Meier的使用)来说这很好,因为如果用户没有选择该选项,她就不会获得那些备份.但是,最初的问题是关于为*device*生成唯一ID,并且每个应用程序生成此ID,甚至不是每个安装,更不用说每个设备,并且由于它依赖于用户选择备份选项,因此它的用途超出用户偏好(例如,限时试用)是有限的. (2认同)

Sev*_*yev 103

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

WifiManager wm = (WifiManager)Ctxt.getSystemService(Context.WIFI_SERVICE);
return wm.getConnectionInfo().getMacAddress();
Run Code Online (Sandbox Code Playgroud)

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

报告即使未连接Wi-Fi也可用.如果上面的答案中的Joe在他的许多设备上尝试了这个,那就太好了.

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

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

  • @Sanandrea - 让我们面对它,在一个有根的设备上,一切都可以被欺骗. (12认同)
  • 这需要`android.permission.ACCESS_WIFI_STATE` (8认同)
  • 他们存在吗?我宁愿设想一个无电话设备(AKA平板电脑)...... (8认同)
  • 我想你会发现,当WiFi关闭时,它几乎在所有Android设备上都无法使用.关闭WiFi可以在内核级别删除设备. (5认同)
  • 在Android M上阻止访问WiFi MAC地址:/sf/ask/2193081341/ (5认同)
  • 从Android 6.x开始,它返回一致的伪mac地址:`02:00:00:00:00:00` (5认同)
  • 我知道这个问题很老 - 但这是个好主意.我在我的应用程序中使用了BT mac ID,但仅仅因为它需要BT才能运行.给我看一个值得开发的Android设备没有WiFi. (4认同)
  • 没有wifi的设备呢? (2认同)
  • 正如谷歌博客所述,MAC不是一个好选择."可以从设备的WiFi或蓝牙硬件中检索Mac地址.我们不建议将其用作唯一标识符.首先,并非所有设备都具有WiFi.此外,如果WiFi未打开,则硬件可能无法报告Mac地址." http://android-developers.blogspot.com.es/2011/03/identifying-app-installations.html (2认同)
  • 最值得注意的是,本地WiFi和蓝牙MAC地址不再可用.aWifiInfo对象的getMacAddress()方法和BluetoothAdapter.getDefaultAdapter().getAddress()方法将从现在开始返回02:00:00:00:00:00 (2认同)
  • 有些设备可能没有Wi-Fi(亲身经历) (2认同)

sta*_*ult 84

有相当有用的信息在这里.

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

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

  • 最值得注意的是,本地 WiFi 和蓝牙 MAC 地址不再可用。 aWifiInfo 对象的 getMacAddress() 方法和 BluetoothAdapter.getDefaultAdapter().getAddress() 方法将从现在开始返回 02:00:00:00:00:00 (4认同)
  • 重要的一点被遗漏了(这里和文章中):除非打开它们,否则您无法获得 WLAN 或 BT MAC!否则我认为 WLAN MAC 将是完美的标识符。你不能保证用户会打开他们的 Wi-Fi,我真的不认为自己打开它是“合适的”。 (3认同)
  • @sarikakate仅在6.0棉花糖及以上的情况下才真实...它仍然在6.0以下的Marshmallow中按预期工作. (3认同)
  • @Tom 你错了。即使关闭 WLAN 或 BT MAC,您仍然可以读取它们。但是,不能保证设备具有可用的 WLAN 或 BT 模块。 (2认同)

Nik*_*tin 82

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

更重要的是,这里所有现有的答案是否过时或不可靠。

因此,如果您正在寻找 2020 年的解决方案

以下是一些需要注意的事项:

所有基于硬件的标识符(SSAID、IMEI、MAC 等)对于非谷歌的设备(Pixels 和 Nexuses 除外的所有设备)都不可靠,这些设备占全球活跃设备的 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.2.4'
Run Code Online (Sandbox Code Playgroud)

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

String reliableIdentifier = FirebaseInstanceId.getInstance().getId();
Run Code Online (Sandbox Code Playgroud)

如果您需要在远程服务器上存储设备标识,则不要按原样(纯文本)存储它,而是使用 salt进行哈希存储

今天,这不仅是最佳实践,您实际上还必须根据GDPR - 标识符和类似规定依法执行。

  • 目前,这是最好的答案,第一句话是最好的总结:“这是一个简单的问题,没有简单的答案 - 只是喜欢它。 (14认同)
  • @famfamfam 因为跟踪设备对用户来说是一种隐私和安全威胁。没有合法的案例需要这样做,因此没有理由使其成为可能。事实证明,硬件 ID 是错误的,Google 正在慢慢修复它们。 (3认同)
  • @M.UsmanKhan,答案写在后面:“_今天这不仅是最佳实践,您实际上必须根据 GDPR - 标识符和类似法规通过法律来做到这一点。_” (2认同)

BoD*_*BoD 49

官方的Android开发者博客现在有一篇关于这个主题的完整文章,识别应用程序安装.

  • 这个论点的关键点在于,如果你试图从硬件中获取一个唯一的ID,那么你可能犯了一个错误. (4认同)
  • 如果您允许通过恢复出厂设置锁定设备锁定,那么您的试用软件模型就像死机一样好. (3认同)

Tec*_*ony 41

谷歌I/O上, Reto Meier发布了一个强有力的答案,解决了如何解决这个问题,这应该满足大多数开发人员在跨安装时跟踪用户的需求 安东尼诺兰在他的回答中显示了方向,但我认为我会写出完整的方法,以便其他人可以轻松地看到如何做到(我花了一些时间来弄清楚细节).

这种方法将为您提供一个匿名,安全的用户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>
Run Code Online (Sandbox Code Playgroud)

然后,您需要创建备份代理并告诉它使用帮助代理进行共享优先:

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);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

BackupManager backupManager = new BackupManager(context);
Run Code Online (Sandbox Code Playgroud)

最后创建一个用户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;
}
Run Code Online (Sandbox Code Playgroud)

即使用户移动设备,此User_ID现在也将在安装期间保持不变.

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

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

  • 当用户有多个设备时,这是否会导致具有相同ID的多个设备?例如平板电脑和手机. (5认同)

小智 37

我认为这肯定是为一个独特的ID构建骨架的方法......检查一下.

伪唯一ID,适用于所有Android设备某些设备没有手机(例如平板电脑)或由于某种原因,您不希望包含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
Run Code Online (Sandbox Code Playgroud)

大多数构建成员都是字符串,我们在这里做的是获取它们的长度并通过模数转换它.我们有13个这样的数字,我们在前面添加两个(35)以具有与IMEI(15位数)相同的大小ID.这里有其他可能性很好,只需看看这些字符串.返回类似的东西355715565309247.无需特殊许可,这种方法非常方便.


(额外信息:上面给出的技术是从Pocket Magic上的一篇文章中复制而来的.)

  • 你应该赞扬你的消息来源......这已经直接取消了以下文章:http://www.pocketmagic.net/?p = 1662 (20认同)
  • 这个ID对你不知道什么的冲突是开放的.实际上保证在同一载体上的相同设备上是相同的. (8认同)
  • 有趣的解决方案.听起来这是一种情况,你真的应该只是哈希所有数据连接而不是试图想出你自己的"哈希"函数.在许多情况下,即使每个值都有不同的实质数据,您也会遇到冲突.我的建议:使用哈希函数,然后将二进制结果转换为十进制,并根据需要截断它.尽管你应该使用UUID或完整的哈希字符串,但要做得对. (7认同)
  • 如果设备升级,这也可能会改变. (7认同)
  • 非常非常糟糕的解决方案.测试了两个Nexus 5 ...返回相同的数字. (6认同)
  • 我不认为使用这个解决方案是个好主意.据我所知,所有Build字段对于所有制作的手机都是一样的. (3认同)
  • 根据 David Given 的评论,这似乎是一个严重的问题。应仔细清点 Build 属性,以确定哪些属性不太可能因无线更新而发生变化。*例如*,根据Javadoc,上面的Build.DISPLAY 是“用于向用户显示的构建ID 字符串”,所以在更新发生时它不会改变吗?我仍然认为这种方法可以结合来自设备的其他信息添加有用的区分属性,但前提是可以仔细剔除此类 OTA 可更改属性。 (2认同)
  • 同样,Build.HOST 被定义为“一个字符串,以人类可读的格式唯一标识构建构建所在的主机。” 似乎完全有可能使用不同的 BUILD 机器来为 OTA 更新创建构建。 (2认同)

小智 35

以下代码使用隐藏的Android API返回设备序列号.但是,此代码不适用于三星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) {

}
Run Code Online (Sandbox Code Playgroud)


Moh*_*ada 25

使用下面的代码,您可以将Android OS设备的唯一设备ID作为字符串获取.

deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
Run Code Online (Sandbox Code Playgroud)


ron*_*y l 19

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

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

  • 不幸的是,这是"未知". (2认同)

Ton*_*aro 16

我要补充一点 - 我有一个独特的情况.

使用:

deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);
Run Code Online (Sandbox Code Playgroud)

事实证明,即使我的Viewsonic G平板电脑报告的DeviceID不是Null,每个G平板电脑都报告相同的数字.

使用"Pocket Empires"让它变得有趣,它可以让您根据"唯一的"DeviceID即时访问某人的帐户.

我的设备没有手机收音机.

  • @Treewallie 有效果吗?您可以从不同的应用程序获取相同的设备ID吗? (2认同)

Kev*_*ker 15

有关如何为安装应用程序的每个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();
    }
}
Run Code Online (Sandbox Code Playgroud)


And*_*oid 10

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

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);
Run Code Online (Sandbox Code Playgroud)

在AndroidManifest.xml中添加:

<uses-permission android:name="android.permission.READ_PHONE_STATE" />
Run Code Online (Sandbox Code Playgroud)


Jor*_*sys 9

Android操作系统设备的唯一设备ID为String,使用TelephonyManagerANDROID_ID,通过以下方式获取:

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);
}
Run Code Online (Sandbox Code Playgroud)

但我强烈推荐Google建议的方法,请参阅识别应用安装.


And*_*ber 8

有很多不同的方法可以解决这些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

这里有30多个答案,有些是相同的,有些是独一无二的.这个答案基于这些答案中的一小部分.其中一个是@Lenn Dolling的回答.

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

3个ID是:
Pseudo-ID - 它是根据物理设备规格生成的
ANDROID_ID - Settings.Secure.ANDROID_ID
蓝牙地址 - 蓝牙适配器地址

它将返回如下内容: 551F27C060712A72730B0A0F734064B1

注意:您始终可以向longId字符串添加更多ID .例如,Serial#.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 "";
}
Run Code Online (Sandbox Code Playgroud)

  • 如果一切都失败了,如果用户确实低于 API 9(低于 Gingerbread),则重置了他们的手机或“Secure.ANDROID_ID”。如果返回 'null',则返回的 ID 将完全基于他们的 Android 设备信息。这是可能发生碰撞的地方。尽量不要使用 DISPLAY、HOST 或 ID - 这些项目可能会改变。如果有冲突,就会有重叠的数据。来源:https://gist.github.com/pedja1/fe69e8a80ed505500caa (3认同)
  • 将 [UUID](https://en.wikipedia.org/wiki/Universally_unique_identifier) 添加到 `longId` 并将其存储在文件中,将使其成为最独特的标识符:`String uuid = UUID.randomUUID()。 toString();` (2认同)
  • @Ninja 由于 BLE mac 地址是唯一的,所以生成的 ID 将始终是唯一的。但是,如果您真的想确定,我建议在 `longId` 中添加一个 UUID。像这样更改这一行:`String longId = pseudoId + androidId + btId + UUID.randomUUID().toString();` 这保证生成的 ID 将是唯一的。 (2认同)

Kir*_*iya 8

1.使用电话管理器,它提供了一个唯一的ID(即IMEI)。看例子,

import android.telephony.TelephonyManager;
import android.content.Context;
// ...
TelephonyManager telephonyManager;
telephonyManager = (TelephonyManager) getSystemService(Context.
                TELEPHONY_SERVICE);
/*
* getDeviceId() returns the unique device ID.
* For example,the IMEI for GSM and the MEID or ESN for CDMA phones.
*/
String deviceId = telephonyManager.getDeviceId();
/*
* getSubscriberId() returns the unique subscriber ID,
*/
String subscriberId = telephonyManager.getSubscriberId();
Run Code Online (Sandbox Code Playgroud)

这需要android.permission.READ_PHONE_STATE您的用户根据您制作的应用程序类型很难证明其合理性。

  1. 没有电话服务的设备(如平板电脑)必须报告android.os.Build.SERIAL自 Android 2.3 Gingerbread 起可用的唯一设备 ID 。某些具有电话服务的电话还可以定义序列号。与并非所有 Android 设备都有序列号一样,此解决方案并不可靠。

  2. 在设备首次启动时,会生成并存储一个随机值。该值可通过Settings.Secure.ANDROID_ID. 它是一个 64 位数字,应该在设备的生命周期内保持不变。ANDROID_ID似乎是唯一设备标识符的不错选择,因为它可用于智能手机和平板电脑。要检索该值,您可以使用以下代码,

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

但是,如果在设备上执行恢复出厂设置,该值可能会更改。制造商生产的流行手机也存在一个已知错误,其中每个实例都具有相同的ANDROID_ID. 显然,该解决方案并非 100% 可靠。

  1. 使用 UUID。由于大多数应用程序的要求是识别特定安装而不是物理设备,因此如果使用 UUID 类,则是获取用户唯一 ID 的好方法。以下解决方案由来自 Google 的 Reto Meier 在 Google I/O 演示中提出,
import android.telephony.TelephonyManager;
import android.content.Context;
// ...
TelephonyManager telephonyManager;
telephonyManager = (TelephonyManager) getSystemService(Context.
                TELEPHONY_SERVICE);
/*
* getDeviceId() returns the unique device ID.
* For example,the IMEI for GSM and the MEID or ESN for CDMA phones.
*/
String deviceId = telephonyManager.getDeviceId();
/*
* getSubscriberId() returns the unique subscriber ID,
*/
String subscriberId = telephonyManager.getSubscriberId();
Run Code Online (Sandbox Code Playgroud)

更新:选项#1#2在 android 10 之后不再可用,因为谷歌的隐私更新。因为选项 2 和 3 需要关键权限。

  • 哪款手机的每个实例都具有相同的 ANDROID_ID? (2认同)
  • `DeviceInfoProvider` 它不是 Android SDK 的一部分 (2认同)

Cod*_*fee 8

借助以下函数获取设备 UUID、型号、品牌名称及其版本号。

完美运行在 Android 10 中,无需允许读取手机状态权限。

代码片段:

private void fetchDeviceInfo() {
    String uniquePseudoID = "35" +
            Build.BOARD.length() % 10 +
            Build.BRAND.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 serial = Build.getRadioVersion();
    String uuid=new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
    String brand=Build.BRAND;
    String modelno=Build.MODEL;
    String version=Build.VERSION.RELEASE;
    Log.e(TAG, "fetchDeviceInfo: \n "+
            "\n uuid is : "+uuid+
            "\n brand is: "+brand+
            "\n model is: "+modelno+
            "\n version is: "+version);
}
Run Code Online (Sandbox Code Playgroud)

调用上述函数并检查上述代码的输出。请在 android studio 中查看您的日志猫。它看起来像下面这样:

在此输入图像描述


Elz*_*ugi 7

IMEI怎么样?这对Android或其他移动设备来说是独一无二的.

  • 不适用于没有IMEI的平板电脑,因为它们没有连接到我的移动运营商. (8认同)
  • 更不用说具有 ESN 而不是 IMEI 的 CDMA 设备了。 (3认同)
  • @ElzoValugi这是"今天"已经并且仍然不是所有平板电脑都有SIM卡. (3认同)
  • 它只会这样做,因为它*是*一部电话:) 平板电脑可能不会。 (2认同)

Eng*_*uad 7

以下是我如何生成唯一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;
}
Run Code Online (Sandbox Code Playgroud)


Mr_*_*s_D 7

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

值得注意的是,@ emmby提供的解决方案会回退到每个应用程序ID,因为SharedPreferences不会跨进程同步(请参阅此处此处).所以我完全避免了这一点.

相反,我封装了在枚举中获取(设备)ID的各种策略 - 更改枚举常量的顺序会影响获取ID的各种方式的优先级.返回第一个非null ID或抛出异常(根据不给出null含义的优秀Java实践).所以例如我首先使用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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)


Her*_*ess 7

Google 现在有一个广告 ID
这也可以使用,但请注意:

广告 ID 是用户特定的、唯一的、可重置的 ID

允许用户在 Google Play 应用中重置他们的标识符或选择退出基于兴趣的广告。

所以虽然这个 id 可能会改变,但似乎很快我们可能没有选择了,这取决于这个 id 的目的。

更多信息@ develper.android

在这里复制粘贴代码

HTH

  • (@JaredBurrows 是的,帖子中提到了......) (2认同)

ins*_*sec 6

另一种方法是/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
Run Code Online (Sandbox Code Playgroud)

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

至少以下设备已知此文件具有全局可读性:

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

您还可以看到我的博客发布泄漏Android硬件序列号到非特权应用程序,在那里我讨论哪些其他文件可用于获取信息.

  • 你是对的。这只是可以跟踪您的设备的另一种方式,正如您所说,这两种方式都不需要应用程序权限。 (2认同)

Asa*_*ssi 6

我使用以下代码来获取IMEI或使用安全.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);
Run Code Online (Sandbox Code Playgroud)


Ila*_*n.b 6

对于特定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();
}
Run Code Online (Sandbox Code Playgroud)

在每个Android设备中,它们至少是一个"wlan0"接口巫婆是WI-FI芯片.即使未打开WI-FI,此代码也能正常工作.

PS他们是从包含MACS的列表中获得的一堆其他接口但这可以在手机之间切换.


小智 6

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

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


Wah*_*zir 6

了解Android设备中可用的唯一ID。使用此官方指南。

唯一标识符的最佳做法:

IMEI,Mac地址,实例ID,GUID,SSAID,广告ID,用于验证设备的Safety Net API。

https://developer.android.com/training/articles/user-data-ids


Jor*_*sys 5

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

final TelephonyManager mTelephony = (TelephonyManager) getSystemService(Context.TELEPHONY_SERVICE);            
String myAndroidDeviceId = mTelephony.getDeviceId(); 
Run Code Online (Sandbox Code Playgroud)

但我建议使用:

Settings.Secure.ANDROID_ID,它将Android ID作为唯一的64位十六进制字符串返回.

    String   myAndroidDeviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID); 
Run Code Online (Sandbox Code Playgroud)

有时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;
}
Run Code Online (Sandbox Code Playgroud)


Tom*_*Tom 5

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
Run Code Online (Sandbox Code Playgroud)

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

它识别安装而不是设备,但是再次,ANDROID_ID(这是接受的答案)现在不再识别设备.使用ARC运行时,为每个安装生成一个新的ANDROID_ID(详情请参见此处),就像这个新的实例ID一样.此外,我认为识别安装(而不是设备)是我们大多数人真正想要的.

实例ID的优点

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

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

缺点/问题

在当前实现(GPS 7.5)中,当您的应用请求时,将从服务器检索实例ID.这意味着上面的调用是阻塞调用 - 在我不科学的测试中,如果设备在线则需要1-3秒,如果离线需要0.5-1.0秒(可能这是在放弃和生成之前等待的时间长度随机ID).这是在北美使用Android 5.1.1和GPS 7.5在Nexus 5上测试的.

如果您将ID用于他们想要的目的 - 例如.应用程序身份验证,应用程序识别,GCM - 我认为这1-3秒可能会令人讨厌(当然,取决于您的应用程序).

  • instanceID 的另一个显着缺点是,如果用户清除应用程序的数据,则会为您生成一个新的 instanceID。 (3认同)

小智 5

Android设备mac id也是一个唯一的id,如果我们格式化设备本身就不会改变,所以使用下面的代码来获取mac id

WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo info = manager.getConnectionInfo();
String address = info.getMacAddress();
Run Code Online (Sandbox Code Playgroud)

另外,不要忘记在AndroidManifest.xml中添加适当的权限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
Run Code Online (Sandbox Code Playgroud)

  • 不幸的是,如果没有当前的WiFi连接,这将不起作用.从[文档](https://developer.android.com/reference/android/net/wifi/WifiManager.html#getConnectionInfo%28%29)(重点添加):_"返回有关当前Wi-Fi的动态信息连接,**如果有活动**."_ (2认同)
  • 同样通过在设备上授予 root 访问权限,可以欺骗 mac 地址 (2认同)

bug*_*ral 5

只是提醒每个阅读并寻找更多最新信息的人。在 Android O 中,系统管理这些 id 的方式发生了一些变化。

https://android-developers.googleblog.com/2017/04/changes-to-device-identifiers-in.html

tl;dr Serial 将需要 PHONE 权限,并且 Android ID 将根据不同的应用程序的包名称和签名而更改。

Google 还整理了一份很好的文档,其中提供了有关何时使用硬件和软件 ID 的建议。

https://developer.android.com/training/articles/user-data-ids.html