安卓设备是否有唯一的设备ID?

3079

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


48
如果你正在使用 ANDROID_ID,请务必阅读这个答案这个漏洞 - Dheeraj Vepakomma
54个回答

8

Android设备的MAC地址也是一种唯一标识符,即使设备本身被格式化,它也不会改变。

使用以下代码获取MAC地址:

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

4
如果没有当前的WiFi连接,这种方法就行不通。从文档中可以看到(已加粗):“返回关于当前Wi-Fi连接的动态信息,如果有任何一个处于活动状态”。 - Ted Hopp
3
授予设备的root访问权限后,可以欺骗MAC地址。 - user6796473
运行Android 10(API级别29)及更高版本的设备会向所有非设备所有者应用程序报告随机化的MAC地址。因此,在使用Android 10或更高版本时,它将不再是唯一的。 - Saddan

8

它可以被重置:广告 ID 是一个独特但可由用户重置的字符串标识符,它允许广告网络和其他应用程序匿名地识别用户。 - Jared Burrows
2
(@JaredBurrows 是的,在帖子中提到了...) - Hertzel Guinness

8

使用以下函数可以获取设备UUID、品牌名称及其型号和版本号。

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

代码片段:

private void fetchDeviceInfo() {
    String uniquePseudoID = "35" +
            Build.BOARD.length() % 10 +
            Build.BRAND.length() % 10 +
            Build.DEVICE.length() % 10 +
            Build.DISPLAY.length() % 10 +
            Build.HOST.length() % 10 +
            Build.ID.length() % 10 +
            Build.MANUFACTURER.length() % 10 +
            Build.MODEL.length() % 10 +
            Build.PRODUCT.length() % 10 +
            Build.TAGS.length() % 10 +
            Build.TYPE.length() % 10 +
            Build.USER.length() % 10;

    String serial = Build.getRadioVersion();
    String uuid=new UUID(uniquePseudoID.hashCode(), serial.hashCode()).toString();
    String brand=Build.BRAND;
    String modelno=Build.MODEL;
    String version=Build.VERSION.RELEASE;
    Log.e(TAG, "fetchDeviceInfo: \n "+
            "\n uuid is : "+uuid+
            "\n brand is: "+brand+
            "\n model is: "+modelno+
            "\n version is: "+version);
}

调用以上函数并检查以上代码的输出,请在Android Studio中查看您的Logcat。如下所示:

enter image description here


你的代码中 %10 和 35+"..." 是什么意思?我的意思是为什么你要使用这种方法来构建唯一标识符?为什么不简单地将这些字符串组合在一起并生成唯一的 UUID 呢?这种方法的输出对于世界上所有设备来说完全是唯一的吗? - porya74
1
Build.getRadioVersion() 返回 null。 - Nishant
@Nishant请在此分享您的代码,让我来帮助您。 - Nimesh Patel

4
通常我会在应用中使用设备唯一标识符,但有时会使用IMEI号。两者均是唯一的数字。
要获取IMEI(国际移动设备识别码)。
public String getIMEI(Activity activity) {
    TelephonyManager telephonyManager = (TelephonyManager) activity
            .getSystemService(Context.TELEPHONY_SERVICE);
    return telephonyManager.getDeviceId();
}

获取设备唯一标识符

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

Android Q已经限制了对IMEI和序列号的访问。 - Vakas

4

我几年前就遇到了这个问题,并学会了根据不同的答案实现通用解决方案。

我已经在一个实际产品中使用了几年的通用解决方案。目前为止它对我非常有用。以下是基于各种提供的答案的代码片段。

注意,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;
} 

但是如果用户重置了手机,这个方法还有效吗?在这种情况下,Android ID 会改变,广告 ID 是可重置的,电子邮件也是如此。 - G3n1t0

4

以下是获取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();

请在此处详细阅读完整答案:


4

提醒各位读者,如果您正在寻找更加实时的信息,请注意。随着Android O的推出,系统管理这些ID的方式也发生了一些变化。

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

串行将需要电话权限,而Android ID将根据应用程序的包名称和签名不同而更改。此外,谷歌已经准备了一份很好的文档,提供了关于何时使用硬件和软件ID的建议。

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


4
为了包含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. 返回生成的唯一标识符进行匹配,或生成一个唯一标识符,与设备信息一起存储并返回这个新标识符。

NB:这是一种未经过测试和证明的方法!我相信它会起作用,但我也很确定,如果这种方法流传开来,他们会采取某种方式关闭它。


4

不建议使用deviceId作为追踪标识,因为在第三方手中可以被用于追踪用户,但这是另一种选择。

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

1
Android正在对以下内容进行更改:Settings.Secure.ANDROID_ID;在Android 8.0(API级别26)及更高版本的平台上,一个64位数字(表示为十六进制字符串),对于每个应用签名密钥、用户和设备组合都是唯一的。这意味着Settings.Secure.ANDROID_ID现在返回的ID是针对应用程序/设备组合唯一的,这使得用户更加安全。 - Jeff Padgett

3
如果你添加:
Settings.Secure.getString(context.contentResolver,
    Settings.Secure.ANDROID_ID)

Android Lint会给出以下警告:

不建议使用getString获取设备标识符。 检查信息:除了高价值欺诈预防和高级电话用例之外,不建议使用这些设备标识符。对于广告用例,请使用AdvertisingIdClient$Info#getId;对于分析,请使用InstanceId#getId。

因此,您应避免使用此功能。

Android开发者文档中所述:

1:避免使用硬件标识符。

在大多数情况下,您可以避免使用硬件标识符,例如SSAID(Android ID)和IMEI,而不会限制所需功能。

2:仅在用户个人档案或广告用例中使用广告标识符。

使用广告标识符时,请始终尊重用户有关广告跟踪的选择。此外,请确保标识符无法与个人身份信息(PII)相关联,并避免连接广告标识符重置。

3:除了支付欺诈预防和电话之外,尽可能在所有其他用例中使用实例ID或私下存储的GUID。

对于绝大多数非广告用例,实例ID或GUID应该足够。

4:使用适合您用例的API以最小化隐私风险。

使用DRM API进行高价值内容保护,使用SafetyNet API进行滥用保护。 SafetyNet API是确定设备是否真实而不产生隐私风险的最简单方法。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接