Android设备是否具有唯一的ID,如果是,则使用Java访问它的简单方法是什么?
#1楼
有许多不同的方法可以解决这些ANDROID_ID问题(有时可能为null或特定模型的设备总是返回相同的ID),但各有利弊:
- 实施自定义ID生成算法(基于应该是静态且不会更改的设备属性->谁知道)
- 滥用其他ID,例如IMEI ,序列号,Wi-Fi /蓝牙MAC地址(它们不会在所有设备上都存在,或者需要其他权限)
我本人更喜欢使用适用于Android的现有OpenUDID实现(请参阅https://github.com/ylechelle/OpenUDID )(请参阅https://github.com/vieux/OpenUDID )。 它很容易集成,并且可以将ANDROID_ID与备用功能结合使用来解决上述问题。
#2楼
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的演讲 。
有关如何实施备份代理的完整详细信息,请参见数据备份
#3楼
另一种方法是在没有任何许可的情况下在应用程序中使用/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硬件序列号泄漏到非特权应用中
#4楼
在类文件中添加以下代码:
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" />
#5楼
我使用以下代码获取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);
#6楼
这是我生成唯一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;
}
#7楼
上次更新时间: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发布了我们所有的选项
选项列表-为什么/为什么不使用它们的原因:
- 用户电子邮件-软件
- 用户可以更改电子邮件-不太可能
- 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/
#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);
}
}
}
#9楼
Google现在有一个广告ID 。 也可以使用,但请注意:
广告ID是特定于用户的,唯一的,可重置的ID
和
使用户可以重置其标识符或选择退出Google Play应用中基于兴趣的广告。
因此,尽管此ID可能会更改,但似乎不久之后我们可能无法选择 ,取决于此ID的目的。
更多@ cn.pngtree.com
在此处复制粘贴代码
高温超导
#10楼
使用TelephonyManager和ANDROID_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建议一种方法,请参阅识别应用程序安装
#11楼
Settings.Secure#ANDROID_ID 以每个用户 64位十六进制字符串的唯一身份返回Android ID。
import android.provider.Settings.Secure;
private String android_id = Secure.getString(getContext().getContentResolver(),
Secure.ANDROID_ID);
#12楼
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;
}
#13楼
更新 :从Android的最新版本开始, ANDROID_ID许多问题已解决,我相信不再需要这种方法。 请看一下安东尼的回答 。
全面披露:我的应用最初使用以下方法,但不再使用该方法,现在我们使用emmby的答案链接到的Android开发者博客条目中概述的方法(即,生成并保存UUID#randomUUID() )。
这个问题有很多答案,其中大多数只会在某些时候起作用,但是不幸的是,这还不够好。
根据我对设备的测试(所有电话,其中至少一个未激活):
- 所有测试的设备都返回TelephonyManager.getDeviceId()的值
- 所有GSM设备(均已通过SIM测试)均返回TelephonyManager.getSimSerialNumber()的值
- 所有CDMA设备的getSimSerialNumber()返回null(按预期方式)
- 所有添加了Google帐户的设备都返回了ANDROID_ID的值
- 所有CDMA设备对于ANDROID_ID和TelephonyManager.getDeviceId()都返回相同的值(或得出相同的值TelephonyManager.getDeviceId() - 只要在设置过程中添加了Google帐户即可。
- 我还没有机会测试没有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;
#14楼
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秒可能会很麻烦(当然,这取决于您的应用)。
#15楼
您也可以考虑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
#16楼
对于特定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的列表中获得,但这在手机之间可能会有所不同。
#17楼
更具体地说,是Settings.Secure.ANDROID_ID 。 这是设备首次启动时生成并存储的64位数量。 擦拭设备时将其重置。
对于唯一的设备标识符, ANDROID_ID似乎是一个不错的选择。 有缺点:首先,它在2.2之前的Android版本(“Froyo”).上并非100%可靠(“Froyo”). 而且,在一家主要制造商的流行手机中,至少有一个广为人知的错误,其中每个实例都具有相同的ANDROID_ID。
#18楼
Android设备的Mac ID也是唯一ID,假设我们格式化设备本身不会更改,因此使用以下代码获取Mac ID
WifiManager manager = (WifiManager) getSystemService(Context.WIFI_SERVICE);
WifiInfo info = manager.getConnectionInfo();
String address = info.getMacAddress();
同样不要忘记将适当的权限添加到您的AndroidManifest.xml中
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
#19楼
这里有30多个答案,有些是相同的,有些是独特的。 该答案基于其中一些答案。 其中之一就是@Lenn Dolling的答案。
它结合了3个ID,并创建了一个32位十六进制字符串。 它对我来说非常有效。
3个ID: 伪ID-根据物理设备规范生成 ANDROID_ID - Settings.Secure.ANDROID_ID 蓝牙地址 -蓝牙适配器地址
它将返回如下内容: 551F27C060712A72730B0A0F734064B1
注意:您始终可以将更多ID添加到longId字符串中。 例如,序列号。 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 "";
}
#20楼
以下代码使用隐藏的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) {
}
#21楼
在API级别9(Android 2.3-Gingerbread)中,将“ 序列”字段添加到Build类。 文档说它代表硬件序列号。 因此,如果设备上存在它,则它应该是唯一的。
我不知道API级别> = 9的所有设备是否实际上都支持(=不为null)。
#22楼
我要添加的一件事-我遇到了其中一种独特的情况。
使用方法:
deviceId = Secure.getString(this.getContext().getContentResolver(), Secure.ANDROID_ID);
事实证明,即使我的Viewsonic G Tablet报告的DeviceID不为Null,每个G Tablet都报告相同的数字。
玩“ Pocket Empires”使它变得有趣,它使您可以基于“唯一” DeviceID即时访问某人的帐户。
我的设备没有手机。
#23楼
我认为这无疑是为唯一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上的文章中复制的。)
#24楼
现在,Android开发者官方博客正式版中有一篇完整的文章,即确定应用程序安装
#25楼
了解Android设备中可用的唯一ID。 使用此官方指南。
唯一标识符的最佳做法:
IMEI,Mac地址,实例ID,GUID,SSAID,广告ID,用于验证设备的Safety Net API。
https://developer.android.com/training/articles/user-data-ids
#26楼
正如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;
}
}
#27楼
使用下面的代码,您可以获得字符串形式的Android OS设备的唯一设备ID。
deviceId = Secure.getString(getApplicationContext().getContentResolver(), Secure.ANDROID_ID);
#28楼
IMEI呢 。 这对于Android或其他移动设备是唯一的。
#29楼
有关如何获取安装了应用程序的每个Android设备的唯一标识符的详细说明,请参阅发布“ 标识应用程序安装”的官方Android开发者博客。
似乎最好的方法是让您在安装时自行生成一个文件,然后在重新启动该应用程序时阅读该文件。
我个人认为这可以接受,但并不理想。 Android提供的任何标识符都不能在所有情况下正常工作,因为大多数标识符都取决于手机的无线电状态(Wi-Fi开/关,蜂窝式开/关,蓝牙开/关)。 其他Settings.Secure.ANDROID_ID (例如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();
}
}
#30楼
这是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。在未来的分析中(换句话说,我还没有做到这一点:)。
#31楼
有相当有用的信息在这里
它涵盖了五种不同的ID类型:
- IMEI (仅适用于使用Phone的Android设备;需要android.permission.READ_PHONE_STATE )
- 伪唯一ID (适用于所有Android设备)
- Android ID (可以为null,可以在恢复出厂设置时更改,可以在有根电话上更改)
- WLAN MAC地址字符串(需要android.permission.ACCESS_WIFI_STATE )
- BT MAC地址字符串(具有蓝牙的设备,需要android.permission.BLUETOOTH )