您当前的位置: 首页 >  android

Kevin-Dev

暂无认证

  • 2浏览

    0关注

    544博文

    0收益

  • 0浏览

    0点赞

    0打赏

    0留言

私信
关注
热门博文

Android 项目必备(四)--> 获取设备唯一标识

Kevin-Dev 发布时间:2017-09-07 11:54:17 ,浏览量:2

在这里插入图片描述

文章目录
      • 一、设备ID的作用
      • 二、获取设备ID的API
      • 三、设备ID的特性分析
      • 四、具体实现

设备唯一标识对于 app 开发是很重要的一个点,主要应用于统计,有时也应用于业务。 Android 平台提供了很多获取唯一标识的 API,但都不是很稳定。

一、设备ID的作用

关于设备ID的作用,大概可以分为下面几点:

  • 统计需求 统计需求是设备ID最常见的用途,包括DAU, MAU的统计,行为统计,广告激活的统计等。

  • 业务需求 设备ID通常也用于业务中。 比如结合行为统计做用户画像,以为用户提供个性化的服务,大家感受比较明显的就是新闻类和电商类的APP了。 这类操作,有利有弊,仁者见仁智者见智。 又如,定向推送,不一定是广告推送,错误修复,内测推送等也会用到设备ID。 还有是一些和特定业务结合的用途,比如构造分布式ID等。

  • 风控需求 设备ID还可用于防刷单,反作弊等。 当然,风控需求仅靠设备ID是无法完成的,通常需要建立一套反作弊系统。 关于这方面的内容,难以一言以蔽之,这里我们不多作展开。

二、获取设备ID的API

IMEI

IMEI 本该最理想的设备 ID,具备唯一性,恢复出厂设置不会变化(真正的设备相关)。

然而,获取IMEI需要 READ_PHONE_STATE 权限,估计大家也知道这个权限有多麻烦了。

尤其是Android 6.0以后, 这类权限要动态申请,很多用户可能会选择拒绝授权。

我们看到,有的APP不授权这个权限就无法使用, 这可能会降低用户对APP的好感度。

而且,Android 10.0 将彻底禁止第三方应用获取设备的IMEI, 即使申请了 READ_PHONE_STATE 权限。 所以,如果是新 APP,不建议用IMEI作为设备标识;

如果已经用 IMEI 作为标识,要赶紧做兼容工作了,尤其是做新设备标识和IMEI的映射。

MAC 地址

一般是指 wifi 模块的 mac 地址。 此处分析 wifi 模块:

  • 优点: 1、硬件标识,刷机和恢复出厂设置不擦除; 2、大多android设备都有wifi模块。
  • 缺点: 1、基于隐私考虑,官方不建议获取;6.0之后通过WifiManager 获取不到真正的mac地址,7.0之后访问不了/sys/class/net/wlan0/address; 2、不同的厂商有不同的限制,比如同样是7.0,一加3可以访问,小米6不可以访问。

如今,还是可以从NetworkInterface中获取到MAC的,但说不好后面也不可用了。

public static String getWifiMac() {
    try {
        Enumeration enumeration = NetworkInterface.getNetworkInterfaces();
        if (enumeration == null) {
            return "";
        }
        while (enumeration.hasMoreElements()) {
            NetworkInterface netInterface = enumeration.nextElement();
            if (netInterface.getName().equals("wlan0")) {
                return formatMac(netInterface.getHardwareAddress());
            }
        }
    } catch (Exception e) {
        Log.e("tag", e.getMessage(), e);
    }
    return "";
}

设备序列号

通过android.os.Build.SERIAL获得,由厂商提供。

如果厂商比较规范的话,设备序列号+Build.MANUFACTURER应该能唯一标识设备。

但现实是并非所有厂商都按规范来,尤其是早期的设备。

最致命的是,Android 8.0 以上,android.os.Build.SERIAL 总返回 “unknown”;

若要获取序列号,可调用Build.getSerial() ,但是需要申请 READ_PHONE_STATE 权限。

到了Android 10.0以上,则和IMEI一样,也被禁止获取了。

总体来说,设备序列号有点鸡肋:食之无味,弃之可惜。

ANDROID_ID

Android ID 是获取门槛最低的,不需要任何权限,64bit 的取值范围,唯一性算是很好的了。 但是不足之处也很明显:

  • 刷机、root、恢复出厂设置等会使得 Android ID 改变;
  • Android 8.0 之后,Android ID 的规则发生了变化:

对于升级到8.0之前安装的应用,ANDROID_ID 会保持不变。如果卸载后重新安装的话,ANDROID_ID将会改变。

对于安装在8.0系统的应用来说,ANDROID_ID根据应用签名和用户的不同而不同。ANDROID_ID的唯一决定于应用签名、用户和设备三者的组合。

两个规则导致的结果就是: 第一,如果用户安装APP设备是8.0以下,后来卸载了,升级到8.0之后又重装了应用,Android ID不一样;

第二,不同签名的APP,获取到的Android ID不一样。

其中第二点可能对于广告联盟之类的有所影响。

三、设备ID的特性分析

唯一性

唯一性: 两台不同的设备获取到的设备ID不相同。 分析唯一性,我们可以从ID的分配来入手:

1、按规则构造 比如自增ID(包括分步自增),分段构造的ID(如snowflake算法)等,此类ID能保证唯一性。 设备ID中的IMEI,设备序列号,MAC等,都是按照规则构造的,理论上能保证唯一性。 设备序列号是对厂商本身唯一,全局唯一需要在加上 Build.MANUFACTURER。 不过,设备序列号和MAC的唯一要打个问号,因为要看厂商是否遵守规则。 但随着手机产业的日渐成熟,传统意义上的山寨设备已越来越少,所以大多数情况下还是唯一的。

2、随机生成 比如UUID和Android ID,这类ID有一定的概率会重复,关键是看ID的长度(有多少bit)。

稳定性

稳定性:同一台设备在不同的时间, 获取到设备ID相同。

稳定性有两个层面: 1、ID的生命周期 IMEI,序列号,MAC等都是硬件相关,即使刷机也不会改变; Android ID则稳定性较弱,恢复出厂设置和刷机都会改变Android ID。

2、受版本的变化的影响 随着Android版本的提升,Google对权限是越收越紧了。 获取设备ID的API,要么收起不给用(IMEI), 要么获取变得困难(SERIAL ),要么不同签名的APP获取的值不一样(Android ID)。 同时,Android 10中存储权限也收缩了,之前的那种生成唯一ID写到SD卡的某个角落的,以求卸载重装后读之前的ID等方法也不奏效了。 加强隐私方面的权限,对用户而言是好事,但对开发者而言就比较难受了。 尤其是有的API本来可以用,升级后就获取不到了,这种断崖式的变化,可能会对数据统计造成影响。

四、具体实现
private fun matchDeviceId(deviceIdList: List, r: DeviceId): DeviceId? {
	if (deviceIdList.isEmpty()) {
		return null
	}
	var maxPriorityDid : DeviceId? = null
	var priority = 0
	deviceIdList.forEach { did ->
		val s = idMatch(did.serial_no, r.serial_no)
		val a = idMatch(did.android_id, r.android_id)
		val m = idMatch(did.mac, r.mac)
		if (s && m && a) {
			return did
		}

		if(priority == 3) return@forEach
		if ((s && (a || m)) || (a && m)) {
			priority = 3
			maxPriorityDid = did
		}

		if(priority >= 2) return@forEach
		val p = idMatch(did.physics_info, r.physics_info)
				|| idMatch(did.dark_physics_info, r.dark_physics_info)
		if (p && a) {
			priority = 2
			maxPriorityDid = did
		}

		if(priority >= 1) return@forEach
		if (p && m) {
			priority = 1
			maxPriorityDid = did
		}
	}
	return maxPriorityDid
}

解释说明:

  • 如果设备序列号、Android ID、MAC全都不等,则前面的SQL查询不会返回记录(也就是没有匹配的设备)。
  • 如果设备序列号,Android ID 和 MAC 全部相同,直接返回。
  • 否则,遍历列表,取优先级最高的deviceId返回。
  • 如果只有Android ID 或 MAC 之一相等,但是设备信息都匹配不上的话,也认为不是同一个设备。
关注
打赏
1658837700
查看更多评论
立即登录/注册

微信扫码登录

0.0390s