uniqueidentifier get - 是否有唯一的Android設備ID?




device using (25)

使用下面的代碼,您可以將Android OS設備的唯一設備ID作為字符串獲取。

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

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


以下是我如何生成唯一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);
        }
    }
}

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

值得注意的是,@ emmby提供的solution會回退到每個應用程序ID,因為SharedPreferences不會跨進程同步(請參閱here和here)。所以我完全避免了這一點。

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

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

谷歌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>

然後,您需要創建備份代理並告訴它使用幫助代理進行共享優先:

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

要完成備份,您需要在主Activity中創建一個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現在也將在安裝期間保持不變。

有關此方法的更多信息,請參閱talk

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


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

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


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

ANDROID_ID似乎是唯一設備標識符的不錯選擇。有一些缺點:首先,它在2.2之前的Android發行版上並非100%可靠。(“Froyo”).此外,在主要製造商的流行手機中至少有一個被廣泛觀察到的錯誤,其中每個實例都具有相同的ANDROID_ID。


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


我要補充一點 - 我有一個獨特的情況。

使用:

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

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

使用“Pocket Empires”讓它變得有趣,它可讓您根據“唯一”DeviceID即時訪問某人的帳戶。

我的設備沒有手機收音機。


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

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

我自己更喜歡使用Android 的現有OpenUDID實現(請參閱https://github.com/ylechelle/OpenUDID)(請參閱https://github.com/vieux/OpenUDID)。它易於集成,並利用ANDROID_ID上述問題的後備。


以下代碼使用隱藏的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設備獲取唯一標識符的詳細說明,請參閱官方Android開發人員博客發布android-developers.blogspot.com/2011/03/…

看來最好的方法是在安裝時自己生成一個,然後在重新啟動應用程序時讀取它。

我個人覺得這個可以接受但不理想。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();
    }
}

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

以下是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的talk也有描述,你應該有一個與用戶綁定的ID,並在設備被擦除甚至更換後堅持使用。我計劃使用這個在未來的分析中(換句話說,我還沒有完成那一點:)。


這裡有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 "";
}

您也可以考慮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


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


這裡有相當有用的信息。

它涵蓋了五種不同的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

最後更新時間:2015年6月2日

在閱讀關於創建唯一ID,Google開發人員博客和Android文檔的每篇帖子後,我覺得好像'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 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',我們可以檢查'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; 但是,有時我們只需要跟踪設備安裝。 在創建唯一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://.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://.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 Developer的控制台:

從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 Player服務實例ID

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


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

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

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

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

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


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的優點

在我看來,谷歌打算將它用於此目的(識別您的安裝),它是跨平台的,並且可以用於許多其他目的(請參閱上面的鏈接)。

如果您使用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秒可能會令人討厭(當然,取決於您的應用程序)。


我認為這肯定是為一個獨特的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

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

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


我的問題是我只在應用程序生命週期中需要持久性(即單個執行包括在同一個應用程序中啟動其他子活動並旋轉設備等)。我嘗試了上述答案的各種組合,但在所有情況下都沒有得到我想要的東西。最後,對我有用的是在onCreate期間獲取對savedInstanceState的引用:

mySavedInstanceState=savedInstanceState;

並在我需要時使用它來獲取變量的內容,類似於:

if (mySavedInstanceState !=null) {
   boolean myVariable = mySavedInstanceState.getBoolean("MyVariable");
}

我使用onSaveInstanceStateonRestoreInstanceState如上所述,但我想我也可以或者使用我的方法來保存變量(例如使用putBoolean





android uniqueidentifier