尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

SSAID深度解析:从设备标识到安装实例,Android标识选型避坑指南

SSAID深度解析:从设备标识到安装实例,Android标识选型避坑指南 1. 一个“用户全变新”的诡异Bug先认识SSAID有段时间我们运营后台的数据特别怪某次版本灰度之后老用户数量骤降“新用户”却一夜之间翻了好几倍。产品经理以为是渠道投放爆了技术侧一查发现很多老设备拿到的设备标识变了后端按设备标识做用户去重直接把一批活跃用户判定成了新设备。问题出在一个字段上SSAID。这东西全称叫 Settings.Secure.ANDROID_ID是 Android 系统里一个存在很久的匿名设备标识。不要把它和 IMEI 混为一谈它既不和设备硬件绑定也不是拿不到授权的敏感权限项。它本质上是一串在设备首次开机时随机生成的字符串存在系统设置里后续系统、应用都能读到。你可以在自己的应用里用两行代码把它取出来val ssAid Settings.Secure.getString( contentResolver, Settings.Secure.ANDROID_ID )很多做数据统计、广告归因、反作弊的老开发对这个值又爱又恨。爱的是它不需要任何运行时权限不像 IMEI 那样在 Android 10 之后几乎拿不到恨的是它的生命周期和取值规则在不同 Android 版本里变了好几次稍微没注意线上就出各种“灵异事件”。这篇文章不想复述官方文档我想从实际踩坑的角度把 SSAID 是什么、怎么取、有哪些边界情况、以及今天做设备标识到底该怎么选型一次性讲清楚。1.1 名字拆解为什么叫 SSAID 而不是直接叫 Android IDAndroid 系统内部有很多“设置表”其中 Settings.Secure 是存放一些不直接暴露给用户、但也不至于敏感到需要系统级权限才能读的键值对。ANDROID_ID 就是存在这张表里的一个 key值为 16 位十六进制字符串在某些实现里也可能是不同长度。所以大家口语里的 SSAID其实就是 Settings.Secure.ANDROID_ID 的缩写。很多 SDK 文档里写“获取 SSAID”和你去读 Settings.Secure.ANDROID_ID 是同一个东西。早期的项目里还有人把它写成 ANDROID_ID、AndroidID、AID都是一回事。1.2 它到底是怎么生成的不是硬件 ID是“第一次开机”的随机数我第一次看源码时以为 ANDROID_ID 会从设备序列号、MAC 地址之类的东西里算出来结果完全不是。系统在设备第一次开机时如果发现 Settings.Secure 里没有 android_id 这个键就会用一个安全的随机数生成器生成一个 64 位随机数再转成十六进制字符串写进去。之后这台设备的所有用户空间正常情况下都会一直用这个值。换句话说它更像一个“设备实例标识”而不是厂商定义的硬件序列号。它不保证全球唯一也不保证不可变。恢复了出厂设置、刷机、部分品牌的数据备份恢复都可能让这个值重新生成。这也解释了为什么它不需要任何权限——系统从一开始就没打算让它承担硬件级标识的职责。2. Android 版本演进SSAID 的“身份规则”改了三轮如果你只写过 Android 8 之前的老代码然后直接把它搬到现在的项目里一定会踩坑。因为 SSAID 在不同版本的 Android 里取值规则发生过非常明显的调整。2.1 Android 8.0 之前一机一号跨应用随便读在 Android 8.0API 26之前SSAID 的取值逻辑非常简单一台设备上所有应用读到的值都一样。也就是说你用 App A 读到的 SSAID和用 App B 读到的 SSAID完全一致。这在那个年代特别适合做设备级归因、渠道去重、风控关联。但问题也随之而来Android 2.2Froyo时期出现过广为人知的“万能 Android ID”大量设备返回同一个固定值9774d56d682e549c导致很多 App 把所有用户都识别成了同一个人。不少厂商的定制 ROM 在恢复出厂设置时没有正确清除 Settings.Secure导致用户恢复出厂后 SSAID 不变。因为所有应用都能拿到同一个值第三方 SDK 之间悄悄互传设备标识做用户画像几乎没有任何技术门槛。也是从那个时期开始SSAID 在广告行业和风控体系里被用到了极致。但也正是因为这样它的“可跨应用追踪”能力逐渐变成了隐私问题。2.2 Android 8.0 之后签名、用户、设备三个维度绑定Android 8.0 开始Google 把 SSAID 的取值规则改了核心变化是同一台设备上不同签名密钥签名的应用读到的 SSAID 不一样。也就是说现在的 ANDROID_ID 是由“应用签名密钥 用户 设备”三个维度共同决定的。具体表现是同一台设备上你的 App 和别的公司签名的 App拿到的 SSAID 是不同的。同一个签名密钥签名的多个 App在同一个用户空间下拿到的 SSAID 是相同的。如果某个应用更换了签名密钥升级后读到的 SSAID 会发生变化。同一台设备上的不同用户空间比如手机分身、应用双开、访客模式读到的 SSAID 也不同。这直接导致了一个常见线上事故App 升级到 Android 8.0 及以上系统后老用户突然被识别成新设备。我在第四节会专门复盘这个问题。2.3 Android 10 之后系统从“硬件 ID”全面转向“安装实例 ID”在 Android 10 和之后的版本里系统对设备标识的整体态度越来越明确普通应用不应该再拿到跨应用通用的硬件级标识IMEI、MAC 地址这些限制越来越严权限门槛越来越高。SSAID 虽然还能被普通应用直接读取但它的“跨应用唯一性”已经被削弱了。它现在更多适用于作为你自己应用内的设备实例标识用来做数据缓存键、安装去重、以及账号弱绑定。如果你想拿它做跨 App 的用户关联那基本走不通了Android 8 之后不同签名 App 拿到的 SSAID 本身就不一样。所以我的判断是SSAID 并不是一个已经过气的字段而是它的定位变了。从“设备唯一标识”变成了“应用维度下的设备实例标识”。理解这一点后面所有方案选型都会清晰很多。3. 代码获取 SSAID最短示例与最容易翻车的边界情况3.1 最短可用示例获取 SSAID 不需要在 AndroidManifest.xml 里申请任何权限直接读即可。Kotlin 写法val ssAid: String? Settings.Secure.getString( context.contentResolver, Settings.Secure.ANDROID_ID )Java 写法同样简单String ssAid Settings.Secure.getString( getContentResolver(), Settings.Secure.ANDROID_ID );如果你只是为了在本地做一个标识这个值基本可以拿过来直接用。但请一定注意它可能为 null也可能是特殊值。很多线上事故就是从这里开始的。3.2 为什么不需要权限却总有人误以为需要原因很简单很多团队的旧代码是从 IMEI 迁移过来的。原来拿 IMEI 要申请 READ_PHONE_STATE 权限后来发现拿不到就改成读 SSAID却还沿用老一套流程去申请权限。实际上SSAID 被设计为“系统设置中的匿名标识”读取它不需要任何权限。这里有个容易误解的地方不需要权限不代表它是敏感字段。它只是一个随机生成的标识符。它不包含手机号、型号、序列号等信息。除非你在后端做了关联否则它本身并不泄露用户隐私。3.3 返回 null、全 0、固定值厂商定制系统的“惊喜”我见过最离谱的几种情况建议你在使用前至少做一个兜底过滤情况现象原因返回 null某些系统应用或受限环境下读不到厂商定制 ROM 对 SettingsProvider 做了改动或系统未完成初始化全 00000000000000000部分模拟器、虚拟化环境、测试固件固定值大量设备返回同一个 ID老版本 Android 2.2 的 bug或某些定制 ROM 的兼容问题恢复出厂后不变设备重置后 ID 仍然一致厂商备份恢复机制把 Settings.Secure 一起恢复了针对 null 和异常值最稳妥的做法是不要直接把异常值写入业务库。可以先生成一个本地 UUID 作为备用标识同时记录 SSAID 是否异常。等设备进入正常状态后再尝试读取一次并做关联。这也是很多 SDK 的通用做法。3.4 拿去做哈希之前务必想清楚这三件事很多人有个习惯把 SSAID 做一次 SHA-256 再存到后台觉得这样就“脱敏”了。这个思路对了一半但有几个前提必须想清楚。第一哈希不加盐等于白做。同一个 SSAID 哈希出来是确定的值别人同样可以反查。建议在服务端用带盐的 HMAC 或加盐哈希保存盐单独管理。第二哈希只能解决“存储侧”的明文问题不能解决 SSAID 本身语义变化的问题。该在 Android 8 之后变化的还是会变。第三不要在客户端只存哈希值。如果你在后端需要根据原始 SSAID 做人工排查只存哈希会让你什么都查不了。我一般建议后台存两份一份是哈希后的关联 ID一份是设备指纹相关的辅助信息原始值则尽量减少留存周期。4. 我踩过的 SSAID 相关坑完整复盘四个典型场景下面这四个坑每一个都是我真实遇到过的。我把排查链路写出来比直接给你结论更有用。4.1 用户重置手机后SSAID 居然没变现象线上反馈某品牌手机恢复了出厂设置重新安装 App 后后台仍然识别成同一个设备。第一反应是“恢复出厂肯定会变”但数据不会骗人。排查过程先确认版本行为。大部分设备恢复出厂设置后Settings.Secure 被清空SSAID 会重新生成。但有个别品牌的“换机助手”或“云备份”功能会把系统设置也一起恢复。用户重置后系统又从备份里把 android_id 写回去了。根因不是系统 bug是备份恢复机制把 SSAID 也当成了可恢复设置。解决建议不要依赖“恢复出厂必变”这个假设。如果业务上需要区分“设备恢复后的新实例”可以结合其他信号判断比如应用首次安装时间、设备启动时间戳等。单纯拿 SSAID 做设备生命周期判断在部分机型上会失真。4.2 升级 Android 8 后老用户全部掉线现象某 App 的用户量没有变化但 DAU 统计里的“新设备数”突然暴涨老设备大量流失。查后端日志发现同一台设备升级到 Android 8.0 系统后上报的 SSAID 变了。排查过程先查代码里 SSAID 的读取方式没有变化。再查是否是升级过程中应用数据被清也没发现。最后对比设备型号和系统版本才发现变化的设备全部集中在 Android 8.0 及以上系统。结合文档一看Android 8.0 之后 SSAID 已经把“应用签名”纳入了取值因子App 签名没变但系统从旧版本升级到新版本之后取值算法变了老值自然对不上。根因Android 版本升级导致 SSAID 取值规则变化不是应用数据丢失。解决建议如果你维护老项目建议在后台同时维护两个字段历史 SSAID 和当前 SSAID并在首次发现变化时做一次自动关联。这比直接按新值建用户要稳妥得多。尤其是做账号体系绑定的场景别让 SSAID 成为唯一主键。4.3 应用双开、手机分身里的“一人多号”现象用户在同一台手机上开启应用双开两个实例被后台识别成了两个独立设备。这在某些业务里不算 bug但在做“一设备一账号”限制的业务里就成了误杀。排查过程应用双开在大部分 ROM 里是基于多用户机制实现的每个用户空间有独立的 Settings.Secure所以 SSAID 不同。如果你以为它是“设备唯一”的不做兼容就会出现同一个物理设备产生多个逻辑设备的情况。根因SSAID 按用户空间隔离它就是会不同的。解决建议如果业务必须识别“同一个物理设备”单靠 SSAID 不够需要叠加其他设备特征型号、分辨率、传感器列表、系统版本、时区等做综合设备指纹。如果业务只是需要“安装实例级标识”那 SSAID 在双开场景下反而更合理每个用户空间一个标识互不干扰。4.4 把 SSAID 当密钥因子结果用户数据全毁现象某团队做本地数据加密用 SSAID 作为 AES 密钥的一部分。结果用户恢复出厂设置后SSAID 变化之前加密的本地数据再也解不开。用户反馈是“App 白屏”“登录后数据全没了”。排查过程看日志本地数据库文件还在但解密失败。再查密钥派生逻辑发现用了 SSAID 作为因子。恢复出厂后 SSAID 变化密钥自然就变了。根因把“标识”当“密钥因子”混淆了两种完全不同的概念。标识要求稳定可读密钥要求随机保密两者属性天然冲突。解决建议本地加密密钥应该使用 Android Keystore 生成的密钥或者由服务端下发的密钥保护。SSAID 最多只能作为加密后的数据索引不能参与密钥派生。这点务必记住否则丢数据的责任谁都扛不住。5. 设备标识选型SSAID、OAID、GAID、IMEI、安装 ID 到底怎么选5.1 一张表格看差异标识获取成本跨应用一致性可重置性适用场景主要限制SSAIDANDROID_ID无需权限Android 8 前一致之后同签名一致恢复出厂、刷机、备份恢复可变本地缓存、安装去重、内部关联Android 8 后受签名影响OAID需要厂商 SDK/接口各厂商策略不同一般跨应用一致可重置广告归因、个性化推荐依赖厂商支持海外设备支持差GAIDGoogle Advertising ID需要 Google Play 服务跨应用一致用户可重置海外广告场景国内设备基本不可用IMEI需要高级权限跨应用一致不可重置运营商、特殊企业场景Android 10 后普通应用无法获取Installation ID自建 UUID完全自主仅本应用内一致卸载重装即变用户级会话跟踪、本地缓存无法跨设备跨应用这张表的核心结论是没有万能标识。你只能在“设备级”“安装级”“用户级”三个层级里选一个最贴近业务诉求的组合。5.2 常见场景的选型建议如果你是做数据统计 SDK我推荐以自建的 Installation ID 为主键SSAID 作为辅助字段。因为卸载重装后 Installation ID 虽然变了但通过 SSAID 还能判断是否同一台设备数据可以做串联。如果你是做广告归因国内建议接 OAID海外建议接 GAIDSSAID 只能作为兜底。因为广告归因特别看重“跨应用一致性”而 Android 8 之后的 SSAID 已经做不到这一点。如果你是做风控和反作弊不要只依赖任何单一标识。SSAID 可以做稳定因子IMEI 拿不到就放弃OAID 做补充再叠加设备基础信息做综合判断。设备指纹模型虽然复杂但比单一 ID 可靠得多。如果你只是需要给某台机器上的某个安装实例生成一个唯一索引比如本地数据库表主键、日志上报的 device_id那 SSAID 完全够用。它比 UUID 多了一个优势App 升级不会变。5.3 后端生成的 Installation ID 到底香不香自建 UUID 最大的好处是简单客户端首次启动时生成一个 UUID保存到 SharedPreferences 或 DataStore同时上报服务端。后续所有关联都基于这个 UUID。但它的致命弱点是用户卸载重装、清除应用数据、或换一台新手机UUID 就没了。所以它只能代表“安装实例”不能代表“设备”或“用户”。我见过很多团队一开始偷懒只用 Installation ID等业务需要跨安装识别设备时才发现重建关联非常痛苦。因此我的建议是从项目第一天就把 SSAID 作为辅助字段和数据一起上报哪怕当时用不到以后做数据清洗时你会感谢这个决定。6. 关于 SSAID 的使用原则隐私合规与工程安全边界6.1 先回答三个问题需要设备级、安装级还是用户级标识每接一个新项目我习惯先问三个问题业务需要识别“同一台设备”吗如果是SSAID 不够需要设备指纹或 OAID 这类跨应用标识。业务需要识别“同一个安装”吗如果是SSAID 或自建 UUID 都行。业务需要识别“同一个用户”吗如果是老老实实做账号体系别指望任何设备标识能替代登录态。这三个问题想清楚很多选型纠结会自动消失。SSAID 最大的误用场景就是被拿来当月疯狂薅羊毛时的“唯一凭证”。6.2 最小化收集和合理匿名化不管从合规角度还是从工程维护角度设备标识都属于“能不存就不存、能短存就短存”的数据。我现在的落地做法是客户端只把 SSAID 原始值用于本地逻辑比如缓存 key、加密索引。上报服务端时不传原始 SSAID而是传一个用服务端盐做的 HMAC 哈希值。原始 SSAID 如果需要排查问题只在有明确需要时临时拉取用完即删。隐私政策里明确说明会收集设备匿名标识并说明用途是数据统计和优化服务。这套流程并不复杂但能省掉不少后续风险。行业里因为设备标识不规范收集翻车的案例太多了没必要为了省事把基础数据管控丢了。6.3 别把标识当密钥也别忘了它只是“权宜标识”前面提到过用 SSAID 派生密钥导致数据全毁的案例。更稳妥的做法是设备上的密钥只放 Android Keystore服务端密钥只从服务端下发。SSAID 只作为数据索引不承担任何安全职责。同时我认为它只是一个“过渡标识”。Android 系统在逐渐压缩普通应用的设备识别空间未来的应用大概率要更依赖账号体系和第一方数据而不是某个字符串。如果你的新项目架构上还有余力可以优先降低对 SSAID 的耦合度而不是设计一套完全围绕它转的存储结构。我手上的处理方式是把 SSAID 当作“增强型辅助字段”主标识永远是一个后端生成且可迁移的内部 ID。这样就算某天某台设备取不到这个值、或者厂商又改了规则核心链路也不会瘫痪。这个思路算是我在多次线上故障之后最想分享的一条经验。
返回列表