主题
AdMix SDK 隐私合规说明
文档版本 v1.0.0
本页对应 SDK 1.0.0,与离线包内 docs/02-隐私合规说明.md 是同一份内容。其他版本见文档中心。
本文档是接入方 App 上架应用商店的必备材料。 各大应用商店与《移动互联网应用程序信息服务管理规定》 均要求在隐私政策中完整披露所嵌入的第三方 SDK。请将本文第二、三节内容并入 App 隐私政策。
金融类等强监管行业 App 另见
05-金融类App合规清单.md—— 那份是「提交材料」视角的裁剪与补全 (上架前必做清单、可直接粘贴的第三方 SDK 目录、权限逐条来源、遗留风险)。本文是技术性的完整说明。
适用版本:AdMix SDK 1.0.0
一、AdMix SDK 自身
AdMix 是聚合调度层,自身不收集、不存储、不传输任何个人信息。 它的作用是调度下方各广告平台 SDK 并统一接口。
它有且只有两类网络行为,都只发往 SDK 内置的平台服务 https://api.maemi.cn,两者都不携带任何用户标识或设备标识:
| 行为 | 内容 | 时机 |
|---|---|---|
| 拉取策略配置 | 下行:广告位、代码位、底价、超时等投放策略。上行只有 AppID 与签名参数 | start() 之后 |
| 上报广告埋点 | 上行:广告位、ADN、代码位、价格、耗时、错误码等广告维度数据。详见第六节 | start() 之后 |
两者都严格晚于 AdMix.start(),也就是用户明确同意隐私政策之后。 在此之前 SDK 不发起任何网络请求、不读写任何持久化数据。这一点可实测验证,方法见第六节末尾。
二、内嵌第三方 SDK 清单(须写入隐私政策)
穿山甲(Pangle / GroMore)
| 项目 | 内容 |
|---|---|
| SDK 名称 | 穿山甲广告 SDK(含 GroMore 聚合能力,同一个 aar) |
| 提供方 | 北京巨量引擎网络技术有限公司 |
| 使用目的 | 广告投放、广告归因、反作弊 |
| 收集信息 | 设备标识(OAID、Android ID)、网络状态、设备型号、操作系统版本 |
| 隐私政策 | https://www.csjplatform.com/privacy |
位置信息:穿山甲 aar 不声明定位权限,且 AdMix 已设
isCanUseLocation() = false(见 4.3), 因此不列入收集清单。按更保守的口径披露也可以,但不要写成"已采集"。
⚠️ 穿山甲走的是 GroMore 聚合通道,广告可能由 GroMore 瀑布流里的其他平台(快手 / 百度 / Sigmob)提供。 当前瀑布流只配置了穿山甲一家,App 内也只集成了穿山甲 SDK,因此本文清单是完整的。
若今后在 GroMore 后台的瀑布流里加入其他平台,App 必须同时集成对应平台的 SDK 与适配器, 并在隐私政策中补充披露那一家的名称、提供方与收集信息 —— 只改后台不改 App 拿不到广告, 而集成了却不披露则构成违规。这一步不能只由运营侧决定。
优量汇(腾讯广告联盟)
| 项目 | 内容 |
|---|---|
| SDK 名称 | 优量汇广告 SDK |
| 提供方 | 深圳市腾讯计算机系统有限公司 |
| 使用目的 | 广告投放、广告归因、反作弊 |
| 收集信息 | 设备标识(OAID、Android ID)、网络状态、设备型号、操作系统版本、粗略位置信息(aar 声明了 ACCESS_COARSE_LOCATION)、应用安装情况(见 5.1) |
| 隐私政策 | https://e.qq.com/dev/help_detail.html?cid=2005&pid=5983 |
优量汇 SDK 内含腾讯自有的安全 / 反作弊模块(
gdt_plugin中的 yaq / Turing), 该模块在 SDK 初始化后会做设备环境与应用安装情况检测,这部分必须披露,见 5.1。接入方若只集成其中一家,隐私政策中只需披露实际集成的那一家(下一小节给出取证方法)。
「只披露实际集成的那一家」为什么是可证的
这不是口头承诺,是构建产物层面的事实,做隐私自查时可以直接取证:
每家 ADN 的适配器是独立的 aar(
com.admix:adapter-csj/adapter-gdt), 各自把自家的广告 SDK 作为运行期依赖带进来。不引那个坐标,那家的 SDK 文件就不在 APK 里聚合核心不在代码里写死"支持哪几家"。每个适配器通过 Java SPI (
META-INF/services/com.admix.adapter.IAdnAdapter)自己声明身份,core启动时扫描一遍 —— 没被引入的那家在包里连一条声明都没有,不存在"代码在包里但没启用"这种灰色状态取证方式(随便一个能解压 APK 的工具都行):
bashunzip -p your-app.apk META-INF/services/com.admix.adapter.IAdnAdapter # 实际集成了哪几家适配器 # 只引穿山甲时输出只有一行:com.admix.adapter.csj.CsjAdapter再加一步反查:
com.qq.e.(优量汇)/com.bytedance.sdk.openadsdk.(穿山甲)的类在不在 dex 里运行期日志也能对上:启动时每个被发现的适配器打一行
adapter 就绪: csj (SDK 7.7.1.6)
另外,被引入但没用到的 ADN 在这台设备上也不会运行 —— 见 4.1「延迟初始化」与按需初始化: 某家 ADN 只在首次加载到配置了它的广告位时才初始化。 所以隐私政策仍需披露它(代码在包里),但"从未采集"这一点在日志里可证。
三、申请的权限及用途
AdMix 自己的三个 aar 一个权限都不声明(清单为空)。下表是同时引入两家 adapter 后, 合并进宿主清单的全部权限,逐条来自打包产物的清单合并报告(外部最小工程实测,非文档推测)。
| 权限 | 来源 | 用途 | 敏感 |
|---|---|---|---|
INTERNET | 两家 | 请求广告、上报曝光点击 | 否 |
ACCESS_NETWORK_STATE | 两家 | 判断网络类型,决定是否加载视频素材 | 否 |
ACCESS_WIFI_STATE | 两家 | 网络质量判断 | 否 |
CHANGE_NETWORK_STATE | 两家 | 网络切换 | 否 |
CHANGE_WIFI_STATE | 穿山甲 | 网络切换 | 否 |
VIBRATE | 两家 | 互动广告振动反馈 | 否 |
WAKE_LOCK | 穿山甲 | 视频播放期间保持屏幕常亮 | 否 |
READ_EXTERNAL_STORAGE / WRITE_EXTERNAL_STORAGE | 穿山甲 | 素材缓存(SDK 侧 isCanUseWriteExternal()=false 已关闭写入) | 是 |
REQUEST_INSTALL_PACKAGES | 优量汇 | 下载类广告安装应用 | 是 |
ACCESS_COARSE_LOCATION | 优量汇 | 广告地域定向 | 是 |
REORDER_TASKS | 优量汇 | 广告落地页 / 任务栈调整 | 否 |
com.asus.msa.SupplementaryDID.ACCESS | 穿山甲 | 华硕机型取 OAID(MSA 服务) | 否 |
${applicationId}.openadsdk.permission.TT_PANGOLIN | 穿山甲 | 穿山甲自定义的应用内私有权限 | 否 |
接入方须在自己的清单里移除这两项(QUERY_ALL_PACKAGES 由优量汇 aar 声明; GET_INSTALLED_APPS 不是任何 SDK 声明的,是华为 ROM 运行时自己加的,写它只是显式表态):
xml
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" tools:node="remove" />
<uses-permission android:name="com.android.permission.GET_INSTALLED_APPS" tools:node="remove" />移除后打包产物中确实不存在 QUERY_ALL_PACKAGES(合并报告可证)。 第二行会产生一条 tagged ... to remove other declarations but no other declaration present 的 构建 Warning,属正常 —— 它恰好证明没有任何 SDK 声明这一项。
另有一个 <queries> 段也来自两家 SDK(Android 11+ 包可见性声明,合规检测工具会扫): 穿山甲声明了抖音 / 抖音极速版两个包名与一个 https VIEW intent; 优量汇声明了 MAIN + LAUNCHER intent —— 有它在,即使没有 QUERY_ALL_PACKAGES, queryIntentActivities 也能列出全部可启动应用。详见 05-金融类App合规清单.md 3.1。
四、合规控制能力
4.1 延迟初始化(最重要)
SDK 采用 init() / start() 两段式设计:
init()仅读取本地配置,不联网、不采集任何设备信息,可在 App 启动时调用start()才真正初始化各广告 SDK,必须在用户明确同意隐私政策之后调用
接入方务必遵守此顺序。用户未同意前调用 start() 即构成违规采集。
正确的时序(Demo 的 DemoApplication + MainActivity 就是这么写的,可直接照抄):
java
// ① Application.onCreate —— 只读本地配置,不联网、不采集、不碰任何 ADN SDK
AdMix.init(this, appId, appKey);
// ② 首次启动弹隐私政策弹窗;用户点「同意」之后才:
AdMix.start(listener);三条容易被驳回的细节:
- 同意结果必须持久化(Demo 用 SharedPreferences),否则每次启动都弹,用户会当成弹窗广告
- 不同意不能不让用:只是没有广告,App 其余功能照常。Demo 的做法是禁掉广告入口、 留一个「重新查看隐私政策」按钮
- 弹窗文案要能对应到实际集成的 SDK:把第二节的第三方 SDK 清单(名称、提供方、 收集信息、隐私政策地址)写进隐私政策全文,弹窗里给出入口
用户点「不同意」时一次 start() 都不要调。此时 SDK 的状态是可实测的: 一家 ADN 都没初始化、零网络请求、私有目录零文件,验证方法见 6.7。
4.2 个性化广告开关
java
AdMix.init(context, "AppID", "App密钥", new AdMixConfig.Builder()
.limitPersonalAds(true) // true = 用户已关闭个性化推荐
.build());该设置会透传至各 ADN:
- 穿山甲:
MediationPrivacyConfig.isLimitPersonalAds()—— 该配置由 GroMore 继续下发给瀑布流里的每一家平台, 不需要为新增平台再写一遍 - 优量汇:
GlobalSetting.setPersonalizedState(1)
4.2.1 流量分组标识
java
AdMix.init(context, "AppID", "App密钥", new AdMixConfig.Builder()
.channel("渠道").subChannel("子渠道") // 可选,不填不影响出广告
.build());用于把广告收入拆分到不同接入方 / 产品线,会随广告请求上传给穿山甲(GroMore)。
⚠️ 这些字段以及 segmentCustomInfo(key, value) 上传的内容,不得包含任何个人信息 (手机号、身份证号、精确位置、可关联到自然人的账号 ID 等)。它们的定位是渠道标签, 放入个人信息即构成未告知的个人信息出境/共享,属违规。
4.3 设备信息采集控制
AdMix 在 Adapter 层已按最小必要原则默认收紧,接入方无需额外配置:
| 项目 | 默认 | 说明 |
|---|---|---|
| 位置信息 | 关闭 | isCanUseLocation() = false |
| 手机状态 / IMEI | 关闭 | isCanUsePhoneState() = false,getDevImei() 返回空 |
| MAC 地址 | 关闭 | getMacAddress() 返回空 |
| 外部存储读写 | 关闭 | isCanUseWriteExternal() = false |
| 录音权限 | 关闭 | isCanUsePermissionRecordAudio() = false |
| 短信 | 关闭 | isCanUseMessage() = false |
| 已安装应用列表 | 关闭 | alist() = false |
| Android ID | 开启 | 广告归因必需 |
| OAID | 开启 | 广告归因必需(未覆写 getDevOaid(),按 SDK 默认取值) |
| WiFi 状态 | 开启 | 网络质量判断 |
优量汇侧对应设置(全部在 SDK 初始化之前完成,顺序不可颠倒):
| 调用 | 值 | 作用 |
|---|---|---|
setAgreePrivacyStrategy | true | 已由聚合层保证用户已同意隐私政策 |
setAgreeReadAndroidId | true | 广告归因必需 |
setAgreeReadDeviceId | false | 不读设备号 |
setEnableCollectAppInstallStatus | false | 关闭后台收集已安装应用列表,见下节 5.1 |
setPersonalizedState | 按 limitPersonalAds | 个性化推荐开关 |
五、已知问题(须知悉)
5.1 应用列表读取(弹窗已修复,仍有一项遗留)
结论先行:读取应用列表的是优量汇,不是穿山甲。 华为弹窗已修复并两轮真机验证通过; 遗留的是一项不弹窗但仍属采集的行为,见下方「仍然存在」。 完整证据(真机调用栈、logcat、静态分析、修复前后对照)在仓库
读取应用列表排查-证据包/。
已修复:枚举应用列表 + 华为系统弹窗
优量汇初始化约 60 秒后,其内置的腾讯安全模块(gdt_plugin 中的 yaq / Turing, 调用链 com.qq.e.comm.plugin.u3$c.run → yaq.pro.* native)在定时任务里调 getInstalledPackages + queryIntentActivities(MAIN + LAUNCHER) 把应用全部枚举一遍, 华为 ROM 随即弹出「是否允许读取已安装应用列表」。
弹窗与展示哪家广告无关,只与「优量汇初始化后约 60 秒」这个定时有关 —— 早期误判成穿山甲,是因为它常常刚好出现在穿山甲广告展示期间。
修法:GdtAdapter 在优量汇 init 之前调 GlobalSetting.setEnableCollectAppInstallStatus(false)(优量汇客服已确认该开关即为 「关闭后台收集已安装包名列表」)。Debug 两轮 + Release 一轮真机验证:零枚举调用、 无权限申请、无弹窗,三种广告位广告正常。
清单这一侧的事实也要更正:QUERY_ALL_PACKAGES 是优量汇 aar 声明的(不是穿山甲); GET_INSTALLED_APPS 不是任何 SDK 声明的,是华为在运行时枚举发生后自己加进 requested permissions 的(系统日志 add requested permission),所以宿主清单里的 tools:node="remove" 挡不住弹窗 —— 真正的开关是上面那一行。
仍然存在:按名单逐个探测约 276 个包名(不弹窗)
开关关掉之后,同一个安全模块(yaq.pro.getJresult ← com.qq.e.comm.plugin.u3$d, 线程 GDT_NET_THREAD)改为逐个调用 getPackageInfo 探测约 276 个指定包名 (美团外卖、陌陌、B 站、饿了么、携程,以及一批 com.yw.cps.new.tencent.*), 约 0.2 秒查完,同样在优量汇初始化后约 60 秒。
华为不为此弹窗,但它本质上仍在探测应用是否安装。名单是写死还是服务端下发未确认 (方法体加壳,静态读不到),用什么开关能关未确认 —— 已向优量汇提工单。
接入方应对建议:
- 隐私政策里把「应用安装情况」写进优量汇的收集信息。优量汇客服原话要点: 第三方 SDK 在静默或后台运行时收集个人信息的,需在隐私政策中明示收集频率并经用户同意 (频率可写「SDK 初始化后约 1 分钟,每进程一次」)
- 金融、政务等强监管行业 App:该项属工信部通报高发项,建议纳入上架前合规检查项, 详见
05-金融类App合规清单.md第 7 节 - 风险不能接受时只引
adapter-csj,优量汇 SDK 与这段行为就都不在包里,可按上一节方法取证 - 穿山甲侧对照结论(供备查):逐类扫过 9444 个类 + 真机观察两轮约 25 分钟, 没有任何枚举类调用、没有触发弹窗;
queryIntentActivities只用于判断单个 Intent 能否打开
5.2 广告交互样式
开屏广告的「摇一摇」「扭一扭」「上滑」等交互样式由广告平台后台配置, 热区设置过大即构成诱导点击,是监管通报的高发情形。
接入方应在各平台后台逐项确认交互样式配置。
AdMix 在开屏请求时已显式关闭聚合侧的摇一摇按钮(MediationAdSlot.setSplashShakeButton(false)), 但平台后台配置的交互样式优先级更高,仍须在后台逐项确认。
六、AdMix 自身的广告埋点上报
这一节描述 AdMix SDK 自己采集并上报的数据,与第二节的第三方 SDK 无关。 接入方需要在隐私政策中披露的是第二、三节;本节说明的是「AdMix 到底往自己的服务端传了什么」, 供接入方做 SDK 自查和上架材料备查。
6.1 采集目的
出广告投放报表:填充率、曝光率、点击率、平均填充耗时、eCPM 估算。 数据用于投放策略优化、异常流量识别与对账核查,不用于用户画像、不用于个性化推荐、不参与广告定向。
6.2 采集的字段(完整清单,一条不多)
| 字段 | 含义 | 举例 |
|---|---|---|
event_type | 事件类型 | ad_request / fill / impression / click / close / reward 等 10 种 |
unit_id | 聚合广告位 ID(我方定义) | reward_main |
ad_type | 广告形态 | splash / reward / banner |
adn | 广告平台标识 | csj / gdt |
slot_id | 广告平台的代码位 ID | 形如 9890xxxxx |
bidding | 该代码位是竞价位还是瀑布位 | true / false |
ecpm | 广告价格,单位分 | 1500 |
cost_ms | 该阶段耗时 | 820 |
err_code / err_msg | 广告平台返回的错误码与错误信息,原样透传 | 5004 / 无匹配广告 |
sdk_version | AdMix SDK 版本 | 1.0.0 |
request_id | 一轮广告调度的随机标识 | 随机串,每轮不同 |
session_id | 单次启动的随机标识 | 随机串,见 7.3 |
event_time | 事件发生时刻 | 毫秒时间戳 |
ext.adn_source | 聚合通道下真正出广告的平台名 | pangle / ks |
app_id | 接入方在配置服务中的应用标识 | 平台后台创建应用时生成 |
全部是广告维度的数据。清单之外的任何字段都不会被上报。
6.3 明确不采集的内容
| 类别 | 例子 | 是否采集 |
|---|---|---|
| 设备标识 | IMEI、OAID、Android ID、MAC、序列号 | 否 |
| 网络标识 | IP 地址、基站信息、WiFi 名称 | 否(服务端也不把 IP 记进埋点表) |
| 个人信息 | 手机号、身份证、用户 ID、昵称、位置 | 否 |
| 设备画像 | 机型、系统版本、分辨率、运营商、应用列表 | 否 |
| 可拼接标识 | 任何能跨 App 启动、跨设备关联到同一个人的稳定串 | 否 |
关于 session_id:它是每次进程启动随机生成的串,不落盘、不跨启动复用。 App 杀掉重启就是另一个值,无法用于跨启动或跨设备识别同一台设备或同一个人。 它唯一的用途是把一次启动内的事件串成漏斗。
关于 request_id:每轮广告调度随机生成,作用域只在这一轮之内,同样不具备任何标识能力。
接入方通过 channel() / subChannel() / userValueGroup() / segmentCustomInfo() 传入的内容一律不进埋点上报。 这是刻意的:这些字段的内容完全由接入方填写,SDK 无从保证 其中不含个人信息,把它们接进埋点等于开了一条不受控的管道。 报表侧的归属拆分由服务端用 app_id 反查开发者完成,不需要客户端上报。
6.4 上报时机与前提
- 前提:用户明确同意隐私政策、宿主调用
AdMix.start()之后。 在此之前 SDK 不采集任何事件、不写任何文件、不发任何请求 - 频率:攒够 50 条或间隔 30 秒发一批,不是一条一发
- 传输:HTTPS POST 至 SDK 内置地址
https://api.maemi.cn/v1/events,带 AppID 与签名参数(签名用的 App 密钥不随请求发送) - 本地缓存:待上报事件暂存于 App 私有目录(
files/admix_events.log), 其他应用无法读取;上报成功即删除。队列上限 1000 条,超出丢弃最早的
6.5 为什么不提供关闭开关
上报内容全部是广告维度的数据(见 6.2 / 6.3),不含个人信息和设备标识,关闭它不会减少任何个人信息处理; 而这些数据是平台做流量质量风控、与广告平台结算数据交叉校验的依据,因此 SDK 不提供关闭上报的配置项。 接入方能控制的是上报何时开始 —— 在用户同意隐私政策、调用 AdMix.start() 之前,SDK 不采集也不上报任何事件。
(AdMix.setEventListener() 注册的事件回调是另一条路径,只在 App 进程内传递,不出设备。)
6.6 数据保留
埋点明细在服务端保留 90 天,之后只保留不可还原到单次事件的聚合统计数据。
6.7 如何自行验证「同意之前不联网」
上架自查时可按以下步骤实测:
bash
adb shell pm clear <包名> # 清空应用数据
adb shell am start -n <包名>/<启动 Activity> # 启动,停在隐私弹窗,不要点同意
adb shell run-as <包名> ls files/ # 应为空:没有任何埋点队列文件同时在服务端访问日志(或抓包)中确认此阶段没有任何 /v1/config 与 /v1/events 请求。 点击「同意」之后才会出现这两个请求。本 SDK 已按此方法实测通过。
七、参考依据
- GB/T 43435-2023《信息安全技术 移动互联网应用程序(App)软件开发工具包(SDK)安全要求》
- GB/T 41391-2022《信息安全技术 移动互联网应用程序(App)收集个人信息基本要求》
- 《App 违法违规收集使用个人信息行为认定方法》