簡體   English   中英

是否有唯一的 Android 設備 ID?

[英]Is there a unique Android device ID?

Android 設備是否有唯一 ID,如果有,使用 Java 訪問它的簡單方法是什么?

Settings.Secure#ANDROID_ID返回 Android ID 作為每個用戶唯一64 位十六進制字符串。

import android.provider.Settings.Secure;

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

另請閱讀唯一標識符的最佳實踐https : //developer.android.com/training/articles/user-data-ids

更新:從最近的 Android 版本開始, ANDROID_ID許多問題已經解決,我相信這種方法不再需要了。 請看看安東尼的回答

完全披露:我的應用程序最初使用以下方法但不再使用這種方法,我們現在使用Android 開發者博客條目中概述的方法, emmby 的答案鏈接到(即,生成和保存UUID#randomUUID() )。


這個問題有很多答案,其中大部分只在“某些”時間有效,不幸的是,這還不夠好。

根據我對設備的測試(所有手機,至少其中一部未激活):

  1. 所有測試的設備都返回了TelephonyManager.getDeviceId()的值
  2. 所有 GSM 設備(均使用 SIM 卡進行測試)返回TelephonyManager.getSimSerialNumber()的值
  3. 所有 CDMA 設備為getSimSerialNumber()返回 null(如預期)
  4. 添加了 Google 帳戶的所有設備都返回了ANDROID_ID的值
  5. 只要在設置期間添加了 Google 帳戶,所有 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;

#上次更新:6/2/15


在閱讀了每一篇關於創建唯一 ID 的 Stack Overflow 帖子、Google 開發者博客和 Android 文檔后,我覺得“偽 ID”似乎是最好的選擇。

主要問題:硬件與軟件

硬件

  • 用戶可以更改他們的硬件、Android 平板電腦或手機,因此基於硬件的唯一 ID 不是跟蹤用戶的好主意
  • 對於跟蹤硬件,這是一個好主意

軟件

  • 用戶可以擦除/更改他們的 ROM 如果他們已經植根
  • 您可以跨平台(iOS、Android、Windows 和 Web)跟蹤用戶
  • 最好在他們同意的情況下跟蹤個人用戶,就是讓他們登錄(使用 OAuth 實現無縫連接)

#Android 整體細分

###- 保證 API >= 9/10(99.5% 的 Android 設備)的唯一性(包括 root 設備)###- 沒有額外的權限

偽代碼:

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 - 硬件(可以為空,可以在恢復出廠設置時更改,可以在有 root 權限的設備上更改)

  • 由於它可以是 '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();
}

#New(適用於帶有廣告和 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)。 Google Play 服務不可用的情況由 getAdvertisingIdInfo() 拋出的 GooglePlayServicesNotAvailableException 指示。

##警告,用戶可以重置:

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

我試圖引用我從中獲取信息的每個鏈接。 如有遺漏需要補充,請留言!

Google 播放器服務實例 ID

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

正如 Dave Webb 所提到的, Android Developer Blog 有一篇文章介紹了這一點。 他們首選的解決方案是跟蹤應用安裝而不是設備,這對大多數用例都適用。 這篇博文將向您展示實現該功能所需的代碼,我建議您查看一下。

但是,如果您需要設備標識符而不是應用程序安裝標識符,博客文章繼續討論解決方案。 我與 Google 的某人進行了交談,以便在您需要時對一些項目進行額外說明。 以下是我發現的有關上述博客文章中未提及的設備標識符的信息:

  • ANDROID_ID 是首選設備標識符。 ANDROID_ID 在 Android <=2.1 或 >=2.3 版本上完全可靠。 只有2.2有帖子中提到的問題。
  • 多個制造商的多個設備受到 2.2 中 ANDROID_ID 錯誤的影響。
  • 據我所知,所有受影響的設備都具有相同的 ANDROID_ID ,即9774d56d682e549c 這也是模擬器報告的相同設備 ID,順便說一句。
  • Google 認為 OEM 已經為他們的許多或大部分設備修復了該問題,但我能夠驗證,至少在 2011 年 4 月初,找到具有損壞 ANDROID_ID 的設備仍然很容易。

根據谷歌的建議,我實現了一個將為每個設備生成唯一 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;
    }
}

這是 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。我計划使用這個在未來的分析中(換句話說,我還沒有做過那一點:)。

您還可以考慮 Wi-Fi 適配器的 MAC 地址。 檢索如下:

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

需要清單中的權限android.permission.ACCESS_WIFI_STATE

據說即使未連接 Wi-Fi 也可用。 如果上面答案中的喬在他的許多設備上嘗試了這個,那就太好了。

在某些設備上,當 Wi-Fi 關閉時它不可用。

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

有相當有用的信息在這里

它涵蓋五種不同的 ID 類型:

  1. IMEI (僅適用於使用手機的 Android 設備;需要android.permission.READ_PHONE_STATE
  2. 偽唯一 ID (適用於所有 Android 設備)
  3. Android ID (可以為空,可以在恢復出廠設置時更改,可以在root手機上更改)
  4. WLAN MAC 地址字符串(需要android.permission.ACCESS_WIFI_STATE
  5. BT MAC 地址字符串(帶藍牙的設備,需要android.permission.BLUETOOTH

這是一個簡單的問題,沒有簡單的答案。

更重要的是,這里所有現有的答案是否過時或不可靠。

因此,如果您正在尋找 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'

並在后台線程中使用以下代碼:

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

如果您需要在遠程服務器上存儲設備標識,則不要按原樣存儲(純文本),而是使用 salt進行哈希存儲

今天,這不僅是最佳實踐,您實際上還必須根據GDPR - 標識符和類似規定依法執行。

官方 Android 開發者博客現在有一篇關於這個主題的完整文章, 識別應用程序安裝

Google I/O大會上,Reto Meier 發布了一個關於如何解決這個問題的可靠答案,這應該可以滿足大多數開發人員在跨安裝跟蹤用戶的需求。 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 的演講

有關如何實施備份代理的完整詳細信息,請參閱 數據備份 我特別推薦底部的測試部分,因為備份不會立即發生,因此要進行測試,您必須強制備份。

我認為這肯定是為唯一 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

大多數 Build 成員都是字符串,我們在這里做的是獲取它們的長度並通過數字取模進行轉換。 我們有 13 個這樣的數字,我們在前面 (35) 再添加兩個,以具有與 IMEI 相同的大小 ID(15 位)。 這里還有其他可能性,看看這些字符串。 返回類似355715565309247 不需要特殊許可,使這種方法非常方便。


(額外信息:上面給出的技術是從Pocket Magic上的一篇文章中復制的。)

以下代碼使用隱藏的 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) {

}

使用下面的代碼,您可以以字符串的形式獲取 Android 操作系統設備的唯一設備 ID。

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

API 級別 9 (Android 2.3 - Gingerbread) 中的Build類中添加了一個Serial字段。 文檔說它代表硬件序列號。 因此它應該是唯一的,如果它存在於設備上。

我不知道 API 級別 >= 9 的所有設備是否實際支持(= 非空)。

我要補充的一件事 - 我有一種獨特的情況。

使用:

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

事實證明,即使我的 Viewsonic G Tablet 報告的 DeviceID 不是 Null,但每個 G Tablet 報告的數字都是相同的。

使玩“口袋帝國”變得有趣,它使您可以根據“唯一”DeviceID 即時訪問某人的帳戶。

我的設備沒有蜂窩無線電。

有關如何為安裝應用程序的每個 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();
    }
}

在類文件中添加以下代碼:

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" />

有很多不同的方法可以解決這些ANDROID_ID問題(有時可能為null或特定型號的設備總是返回相同的 ID),各有利弊:

  • 實現自定義 ID 生成算法(基於應該是靜態且不會改變的設備屬性 -> 誰知道)
  • 濫用其他 ID,如IMEI 、序列號、Wi-Fi/Bluetooth-MAC 地址(它們不會存在於所有設備上或需要額外的權限)

我自己更喜歡為 Android 使用現有的 OpenUDID 實現(請參閱https://github.com/ylechelle/OpenUDID )(請參閱https://github.com/vieux/OpenUDID )。 很容易集成並利用ANDROID_ID與上述問題的回退。

使用TelephonyManagerANDROID_ID作為字符串的 Android 操作系統設備的唯一設備 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);
}

但我強烈推薦 Google 建議的方法,請參閱識別應用安裝

這里有 30 多個答案,有些是相同的,有些是獨一無二的。 這個答案是基於其中的幾個答案。 其中之一是@Lenn Dolling 的回答。

它結合了 3 個 ID 並創建了一個 32 位的十六進制字符串。 它對我來說非常有效。

3個ID是:
Pseudo-ID - 它是根據物理設備規格生成的
ANDROID_ID - Settings.Secure.ANDROID_ID
藍牙地址- 藍牙適配器地址

它將返回如下內容: 551F27C060712A72730B0A0F734064B1

注意:您始終可以向longId字符串添加更多 ID。 例如,序列號。 wifi適配器地址。 串號。 通過這種方式,您可以使每台設備更加獨特。

@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 "";
}

IMEI怎么樣。 這對於 Android 或其他移動設備來說是獨一無二的。

這是我生成唯一 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;
}

我的兩分錢 - 注意這是一個設備(錯誤)唯一 ID - 不是Android 開發人員博客中討論的安裝ID

請注意,@emmby 提供的解決方案回退到每個應用程序 ID,因為 SharedPreferences 不會跨進程同步(請參閱此處此處)。 所以我完全避免了這個。

相反,我在枚舉中封裝了獲取(設備)ID 的各種策略——更改枚舉常量的順序會影響獲取 ID 的各種方式的優先級。 返回第一個非空 ID 或拋出異常(根據不賦予 null 含義的良好 Java 實踐)。 因此,例如,我首先使用 TELEPHONY - 但一個不錯的默認選擇是 ANDROID_ID測試版:

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);
        }
    }
}

另一種方法是在沒有任何權限的情況下在應用程序中使用/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

要在 Java 中執行此操作,只需使用 FileInputStream 打開 iSerial 文件並讀出字符即可。 請確保將其包裝在異常處理程序中,因為並非所有設備都有此文件。

至少已知以下設備具有該文件世界可讀:

  • 銀河系
  • Nexus S
  • 摩托羅拉 Xoom 3G
  • 東芝AT300
  • 宏達一V
  • 迷你MK802
  • 三星 Galaxy S II

您還可以查看我的博客文章Leaking Android hardware serial number to unprivileged apps ,在那里我討論了哪些其他文件可用於信息。

我使用以下代碼獲取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);

TelephonyManger.getDeviceId()返回唯一的設備 ID,例如,GSM 的 IMEI 和 CDMA 電話的 MEID 或 ESN。

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

但我建議使用:

Settings.Secure.ANDROID_ID將 Android ID 作為唯一的 64 位十六進制字符串返回。

    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;
}

Google 實例 ID

在 I/O 2015 上發布; 在 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

谷歌似乎打算使用這個 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 秒可能很麻煩(當然,這取決於您的應用程序)。

對於特定 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 的列表中獲得但是這可以在手機之間改變。

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();

這需要android.permission.READ_PHONE_STATE給您的用戶,這很難證明您所創建的應用程序類型是合理的。

  1. 自 Android 2.3 Gingerbread 起,沒有電話服務的設備(如平板電腦)必須報告可通過android.os.Build.SERIAL獲得的唯一設備 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 演示中提出,
SharedPreferences sharedPrefs = context.getSharedPreferences(
         PREF_UNIQUE_ID, Context.MODE_PRIVATE);
uniqueID = sharedPrefs.getString(PREF_UNIQUE_ID, null);

更新:選項#1#2在 android 10 之后不再可用,因為谷歌的隱私更新。 因為選項 2 和 3 需要關鍵權限。

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

唯一標識符的最佳實踐:

IMEI、Mac 地址、實例 ID、GUID、SSAID、廣告 ID、用於驗證設備的 Safety Net API。

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

Android 設備是否有唯一 ID,如果有,使用 Java 訪問它的簡單方法是什么?

更具體地說,是Settings.Secure.ANDROID_ID 這是設備首次啟動時生成並存儲的64位數量。 擦拭設備時將其重置。

對於唯一的設備標識符, ANDROID_ID似乎是一個不錯的選擇。 有缺點:首先,它在2.2之前的Android版本(“Froyo”).上並非100%可靠(“Froyo”). 而且,在一家主要制造商的流行手機中,至少有一個廣為人知的錯誤,其中每個實例都具有相同的ANDROID_ID。

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"/>

只是為每個閱讀尋找更多最新信息的人提個醒。 在 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

通常,我為我的應用程序使用設備唯一 ID。 但有時我會使用 IMEI。 兩者都是唯一的數字。

獲取IMEI國際移動設備標識符

public String getIMEI(Activity activity) {
    TelephonyManager telephonyManager = (TelephonyManager) activity
            .getSystemService(Context.TELEPHONY_SERVICE);
    return telephonyManager.getDeviceId();
}

獲取設備唯一ID

public String getDeviceUniqueID(Activity activity){
    String device_unique_id = Secure.getString(activity.getContentResolver(),
            Secure.ANDROID_ID);
    return device_unique_id;
}

幾年前我遇到過這個問題,並且已經學會了基於各種答案來實現一個通用的解決方案。

我已經在實際產品中使用通用解決方案多年。 到目前為止,它對我很有用。 這是基於各種提供的答案的代碼片段。

請注意,大多數情況下getEmail將返回 null,因為我們沒有明確請求許可。

private static UniqueId getUniqueId() {
    MyApplication app = MyApplication.instance();

    // Our prefered method of obtaining unique id in the following order.
    // (1) Advertising id
    // (2) Email
    // (2) ANDROID_ID
    // (3) Instance ID - new id value, when reinstall the app.

    ////////////////////////////////////////////////////////////////////////////////////////////
    // ADVERTISING ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    AdvertisingIdClient.Info adInfo = null;
    try {
        adInfo = AdvertisingIdClient.getAdvertisingIdInfo(app);
    } catch (IOException e) {
        Log.e(TAG, "", e);
    } catch (GooglePlayServicesNotAvailableException e) {
        Log.e(TAG, "", e);
    } catch (GooglePlayServicesRepairableException e) {
        Log.e(TAG, "", e);
    }

    if (adInfo != null) {
        String aid = adInfo.getId();
        if (!Utils.isNullOrEmpty(aid)) {
            return UniqueId.newInstance(aid, UniqueId.Type.aid);
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // EMAIL
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String email = Utils.getEmail();
    if (!Utils.isNullOrEmpty(email)) {
        return UniqueId.newInstance(email, UniqueId.Type.eid);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // ANDROID ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String sid = Settings.Secure.getString(app.getContentResolver(), Settings.Secure.ANDROID_ID);
    if (!Utils.isNullOrEmpty(sid)) {
        return UniqueId.newInstance(sid, UniqueId.Type.sid);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////
    // INSTANCE ID
    ////////////////////////////////////////////////////////////////////////////////////////////
    final String iid = com.google.android.gms.iid.InstanceID.getInstance(MyApplication.instance()).getId();
    if (!Utils.isNullOrEmpty(iid)) {
        return UniqueId.newInstance(iid, UniqueId.Type.iid);
    }

    return null;
}

public final class UniqueId implements Parcelable {
    public enum Type implements Parcelable {
        aid,
        sid,
        iid,
        eid;

        ////////////////////////////////////////////////////////////////////////////
        // Handling Parcelable nicely.

        public static final Parcelable.Creator<Type> CREATOR = new Parcelable.Creator<Type>() {
            public Type createFromParcel(Parcel in) {
                return Type.valueOf(in.readString());
            }

            public Type[] newArray(int size) {
                return new Type[size];
            }
        };

        @Override
        public int describeContents() {
            return 0;
        }

        @Override
        public void writeToParcel(Parcel parcel, int flags) {
            parcel.writeString(this.name());
        }

        // Handling Parcelable nicely.
        ////////////////////////////////////////////////////////////////////////////
    }

    public static boolean isValid(UniqueId uniqueId) {
        if (uniqueId == null) {
            return false;
        }
        return uniqueId.isValid();
    }

    private boolean isValid() {
        return !org.yccheok.jstock.gui.Utils.isNullOrEmpty(id) && type != null;
    }

    private UniqueId(String id, Type type) {
        if (org.yccheok.jstock.gui.Utils.isNullOrEmpty(id) || type == null) {
            throw new java.lang.IllegalArgumentException();
        }
        this.id = id;
        this.type = type;
    }

    public static UniqueId newInstance(String id, Type type) {
        return new UniqueId(id, type);
    }

    @Override
    public int hashCode() {
        int result = 17;
        result = 31 * result + id.hashCode();
        result = 31 * result + type.hashCode();
        return result;
    }

    @Override
    public boolean equals(Object o) {
        if (o == this) {
            return true;
        }

        if (!(o instanceof UniqueId)) {
            return false;
        }

        UniqueId uniqueId = (UniqueId)o;
        return this.id.equals(uniqueId.id) && this.type == uniqueId.type;
    }

    @Override
    public String toString() {
        return type + ":" + id;
    }

    ////////////////////////////////////////////////////////////////////////////
    // Handling Parcelable nicely.

    public static final Parcelable.Creator<UniqueId> CREATOR = new Parcelable.Creator<UniqueId>() {
        public UniqueId createFromParcel(Parcel in) {
            return new UniqueId(in);
        }

        public UniqueId[] newArray(int size) {
            return new UniqueId[size];
        }
    };

    private UniqueId(Parcel in) {
        this.id = in.readString();
        this.type = in.readParcelable(Type.class.getClassLoader());
    }

    @Override
    public int describeContents() {
        return 0;
    }

    @Override
    public void writeToParcel(Parcel parcel, int flags) {
        parcel.writeString(this.id);
        parcel.writeParcelable(this.type, 0);
    }

    // Handling Parcelable nicely.
    ////////////////////////////////////////////////////////////////////////////

    public final String id;
    public final Type type;
}

public static String getEmail() {
    Pattern emailPattern = Patterns.EMAIL_ADDRESS; // API level 8+
    AccountManager accountManager = AccountManager.get(MyApplication.instance());
    Account[] accounts = accountManager.getAccountsByType("com.google");
    for (Account account : accounts) {
        if (emailPattern.matcher(account.name).matches()) {
            String possibleEmail = account.name;
            return possibleEmail;
        }
    }

    accounts = accountManager.getAccounts();
    for (Account account : accounts) {
        if (emailPattern.matcher(account.name).matches()) {
            String possibleEmail = account.name;
            return possibleEmail;
        }
    }

    return null;
} 

不推薦,因為 deviceId 可以在 3rd 方手中用作跟蹤,但這是另一種方式。

@SuppressLint("HardwareIds")
private String getDeviceID() {
    deviceId = Settings.Secure.getString(getApplicationContext().getContentResolver(),
                    Settings.Secure.ANDROID_ID);
    return deviceId;
}

這是獲得 AAID 的簡單答案,經測試正常工作 2019 年 6 月

 AsyncTask<Void, Void, String> task = new AsyncTask<Void, Void, String>() {
        @Override
        protected String doInBackground(Void... params) {
            String token = null;
            Info adInfo = null;
            try {
                adInfo = AdvertisingIdClient.getAdvertisingIdInfo(getApplicationContext());
            } catch (IOException e) {
                // ...
            } catch ( GooglePlayServicesRepairableException e) {
                // ...
            } catch (GooglePlayServicesNotAvailableException e) {
                // ...
            }
            String android_id = adInfo.getId();
            Log.d("DEVICE_ID",android_id);

            return android_id;
        }

        @Override
        protected void onPostExecute(String token) {
            Log.i(TAG, "DEVICE_ID Access token retrieved:" + token);
        }

    };
    task.execute();

這里詳細閱讀完整答案:

借助以下功能獲取設備 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);
}

調用上面的函數並檢查上面代碼的輸出。 請在 android studio 中查看您的日志貓。 它看起來像下面:

在此處輸入圖片說明

為了包含Android 9,我只有一個仍然可以工作的想法(可能)不違反任何條款,需要權限,並且可以跨安裝和應用程序工作。

涉及服務器的指紋識別應該能夠唯一地識別設備。 硬件信息 + 已安裝的應用程序和安裝時間的組合應該可以解決問題。 除非卸載並再次安裝應用程序,否則首次安裝時間不會改變。 但這必須對設備上的所有應用程序進行,以便無法識別設備(即在恢復出廠設置后)。

這就是我將如何去做:

  1. 提取硬件信息、應用程序包名稱和首次安裝時間。

這是從 Android 中提取所有應用程序的方式(無需權限):

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

for (ApplicationInfo packageInfo : packages) {
    try {
        Log.d(TAG, "Installed package :" + packageInfo.packageName);
        Log.d(TAG, "Installed :" + pm.getPackageInfo(packageInfo.packageName, 0).firstInstallTime);
    } catch (PackageManager.NameNotFoundException e) {
        e.printStackTrace();
    }
}
  1. 在將其發送到服務器之前,您可能希望對每個包名稱和安裝時間戳組合進行散列,因為它可能與您的業務有關,也可能與用戶在設備上安裝的無關。
  2. 一些應用程序(實際上很多)是系統應用程序。 這些可能具有相同的安裝時間戳,與恢復出廠設置后的最新系統更新相匹配。 由於它們具有相同的安裝時間戳,因此用戶無法安裝它們,並且可以過濾掉它們。
  3. 將信息發送到服務器,讓它在先前存儲的信息中尋找最接近的匹配項。 在安裝和卸載應用程序時與之前存儲的設備信息進行比較時,您需要設定一個閾值。 但我的猜測是這個閾值可能非常低,因為任何包名稱和首次安裝時間戳組合對於設備來說都是非常獨特的,並且應用程序的安裝和卸載並不那么頻繁。 擁有多個應用程序只會增加獨特的可能性。
  4. 返回為匹配生成的唯一 id,或生成唯一 id,存儲設備信息並返回此新 id。

注意:這是一種未經測試和未經證實的方法! 我相信它會起作用,但我也很確定,如果這種情況流行起來,他們會以一種或另一種方式關閉它。

如果添加:

Settings.Secure.getString(context.contentResolver,
    Settings.Secure.ANDROID_ID)

Android Lint 會給你以下警告:

不推薦使用 getString 來獲取設備標識符。 檢查信息:除了用於高價值欺詐預防和高級電話用例外,不建議使用這些設備標識符。 對於廣告用例,請使用 AdvertisingIdClient$Info#getId,對於分析,請使用 InstanceId#getId。

所以,你應該避免使用它。

Android 開發人員文檔中所述

1:避免使用硬件標識符。

在大多數用例中,您可以避免使用硬件標識符,例如 SSAID (Android ID) 和 IMEI,而不限制所需的功能。

2:僅將廣告 ID 用於用戶分析或廣告用例。

使用廣告 ID 時,請始終尊重用戶對廣告跟蹤的選擇。 此外,請確保標識符無法連接到個人身份信息 (PII),並避免橋接廣告 ID 重置。

3:對於所有其他用例,盡可能使用實例 ID 或私有存儲的 GUID,支付欺詐預防和電話除外。

對於絕大多數非廣告用例,實例 ID 或 GUID 就足夠了。

4:使用適合您的用例的 API 以最大程度地降低隱私風險。

使用 DRM API 進行高價值內容保護,使用 SafetyNet API 進行濫用保護。 SafetyNet API 是在不產生隱私風險的情況下確定設備是否為真品的最簡單方法。

String SERIAL_NUMER = Build.SERIAL;

將序列號作為在每個設備中唯一的字符串返回。

序列號是一個唯一的設備 ID,可通過 android.os.Build.SERIAL 獲得。

public static String getSerial() {
    String serial = "";
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O){
        serial = Build.getSerial();
    }else{ 
        serial = Build.SERIAL;    
    }
    return serial;
}

在調用 getSerial() 之前,請確保您擁有READ_PHONE_STATE權限。

注意:- 它不適用於沒有電話的設備(如僅限 wifi 的平板電腦)。

為了完整Xamarin.Android ,您可以通過以下方式在Xamarin.Android和 C# 中獲取Id

var id = Settings.Secure.GetString(ContentResolver, Settings.Secure.AndroidId);

或者,如果您不在Activity

var id = Settings.Secure.GetString(context.ContentResolver, Settings.Secure.AndroidId);

其中context是傳入的上下文。

android.telephony.TelephonyManager.getDeviceId()

這將返回唯一標識設備的任何字符串(GSM 上的 IMEI,CDMA 上的 MEID)。

您需要在 AndroidManifest.xml 中獲得以下權限:

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

是的,每個 Android 設備都有一個唯一的序列號,您可以通過此代碼獲取它。 Build.SERIAL 請注意,它僅在 API 級別 9 中添加,並且可能不會出現在所有設備上。 要在早期平台上獲得唯一 ID,您需要讀取 MAC 地址或 IMEI 之類的信息。

經過一番尋找。 我意識到沒有確定的方法來擁有一個唯一的 ID。

假設我們希望每個用戶只能在一部手機上使用該應用程序。

我要做的是:

當用戶在我的應用程序中注冊時,我將當前時間保存為服務器和應用程序數據庫中的唯一 ID。

當用戶嘗試在另一部手機上登錄時,我從服務器獲取用戶信息並意識到該用戶已經登錄,因為唯一 ID 字段已滿,因此向他/她顯示他/她已經登錄的對話框在另一個設備上,他是否想離開之前的 session,如果他說是,我將為他創建一個新的唯一 ID,並在服務器上更新唯一 ID 詳細信息。

在我自己的應用程序中,每次運行時我都會從服務器獲取用戶配置文件。 如果服務器上存儲的唯一 ID 與應用程序數據庫中存儲的唯一 ID 不同,我會自動注銷用戶。

僅獲取一次設備 ID,然后將其存儲在數據庫或文件中。 在這種情況下,如果是應用程序的第一次啟動,它會生成一個 ID 並存儲它。 下一次,它將只獲取存儲在文件中的 ID。

檢查 SystemInfo.deviceUniqueIdentifier

文檔: http : //docs.unity3d.com/Documentation/ScriptReference/SystemInfo-deviceUniqueIdentifier.html

唯一的設備標識符。 它保證對每個設備都是唯一的(只讀)。

iOS:在 iOS7 之前的設備上,它將返回 MAC 地址的哈希值。 在 iOS7 設備上,它將是 UIDevice identifierForVendor,或者,如果由於任何原因失敗,則是 ASIdentifierManager AdvertisingIdentifier。

要獲取用戶 ID,您可以使用 Google Play 許可庫。

要下載此庫,請打開 SDK Manager => SDK Tools。 下載庫文件的路徑是:

path_to_android_sdk_on_your_pc/extras/google/market_licensing/library

在您的項目中包含該庫(您可以簡單地復制其文件)。

接下來,您需要一些Policy接口的實現(您可以簡單地使用庫中的兩個文件之一: ServerManagedPolicyStrictPolicy )。

將在processServerResponse()函數中為您提供用戶 ID:

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

接下來,您需要使用策略構建LicenseChecker並調用checkAccess()函數。 使用MainActivity.java作為如何執行此操作的示例。 MainActivity.java位於此文件夾中:

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

不要忘記將 CHECK_LICENSE 權限添加到您的 AndroidManifest.xml。

有關許可庫的更多信息: https : //developer.android.com/google/play/licensing

Android 在Android O 之后限制了硬件相關的Id,因此Android_Id 是唯一id 的解決方案,但是當reflector 設備時會生成新的android_id 來解決這個問題我們可以使用DRUMID 來解決這個問題。

val WIDEVINE_UUID = UUID(-0x121074568629b532L, -0x5c37d8232ae2de13L)
val drumIDByteArray = MediaDrm(WIDEVINE_UUID).getPropertyByteArray(MediaDrm.PROPERTY_DEVICE_UNIQUE_ID)

val drumID = android.util.Base64.encodeToString(drumIDByteArray,android.util.Base64.DEFAULT)

 package com.aapbd.appbajarlib.common; import android.os.Build; import java.util.Locale; import java.util.UUID; public class DeviceID { public static String getDeviceLanguage() { Locale locale=Locale.getDefault(); return locale.getDisplayLanguage(); } public static String getDeviceCountry() { Locale locale=Locale.getDefault(); return locale.getDisplayCountry(); } public static String getDeviceName() { String manufacturer = Build.MANUFACTURER; String model = Build.MODEL; if (model.startsWith(manufacturer)) { return capitalize(model); } else { return capitalize(manufacturer) + " " + model; } } public static String getAndroidVersion() { String release = Build.VERSION.RELEASE; int sdkVersion = Build.VERSION.SDK_INT; return sdkVersion + " (" + release +")"; } public static int getAndroidAPILevel() { int sdkVersion = Build.VERSION.SDK_INT; return sdkVersion; } private static String capitalize(String s) { if (s == null || s.length() == 0) { return ""; } char first = s.charAt(0); if (Character.isUpperCase(first)) { return s; } else { return Character.toUpperCase(first) + s.substring(1); } } /** * 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! // http://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! // http://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(); } }

我正在使用它,它在過去 6 年里一直在工作。

這是圖書館: https : //github.com/nillbiplob/AppBajarLIB

此示例演示如何在 Android 中獲取和存儲設備 ID,但我使用的是 Kotlin。

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

生成設備標識符

 private String generateDeviceIdentifier() {
    String uniqueDevicePseudoID = "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(uniqueDevicePseudoID.hashCode(), serial.hashCode()).toString();
    Log.e("DeviceIdentifier ", "\nDeviceIdentifier uuid is : " + uuid);
    return uuid;
}

Output

DeviceIdentifier uuid is : 00000000-36ab-9c3c-0000-0000714a4f37

在此處輸入圖像描述

您將通過使用以下代碼獲得 wifi mac 地址,無論您嘗試連接 wifi 時是否使用了隨機地址,也無論 wifi 是打開還是關閉。

我使用了下面鏈接中的一種方法,並添加了一個小的修改來獲取確切地址而不是隨機地址:

在 Android 6.0 中獲取 MAC 地址

public static String getMacAddr() {
StringBuilder res1 = new StringBuilder();
try {
List<NetworkInterface> all =     
Collections.list(NetworkInterface.getNetworkInterfaces());
for (NetworkInterface nif : all) {    
if (!nif.getName().equalsIgnoreCase("p2p0")) continue;

        byte[] macBytes = nif.getHardwareAddress();
        if (macBytes == null) {
            continue;
        }

        res1 = new StringBuilder();
        for (byte b : macBytes) {
            res1.append(String.format("%02X:",b));
        }

        if (res1.length() > 0) {
            res1.deleteCharAt(res1.length() - 1);
        }
    }
} catch (Exception ex) {
}
return res1.toString();

}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM