前言
2021 年 PIPL(《个人信息保护法》)落地,2023 年起应用市场(华为、小米、应用宝、OPPO、vivo)的合规审查越来越刚性。一个不留神,App 就被下架或拒绝上架。这篇不讲法条字眼,从工程角度讲清楚——一个 Android 工程应该怎么改,才能在审核员眼里"看起来合规"。
一、PIPL 三大原则
这三条是大多数过审失败的根因。
二、Android 工程层面的改造点
2.1 隐私弹窗与"延迟同意"
过审检查的第一件事:用户首次启动 App 后,是否有"知情同意"动作,之后才发生任何数据采集行为。
// 错误:Splash 阶段就在启动 SDK
class App : Application() {
override fun onCreate() {
super.onCreate()
Bugly.init(this, "app-id")
PushAgent.init(this)
UMeng.init(this)
}
}
// 正确:隐私同意后再初始化
class App : Application() {
override fun onCreate() {
super.onCreate()
PrivacyCenter.checkAgreed {
if (it) {
Bugly.init(this, "app-id")
PushAgent.init(this)
}
}
}
}
注意 PrivacyCenter.checkAgreed 必须是同步可读的 SharedPreferences 或 DataStore,不要在初始化阶段做网络请求或 IO。
2.2 Manifest 权限声明的真实使用
清单里列的权限,必须在代码里都用得到,否则会被静态扫描当成"虚假权限"。Android 12+ 还有 READ_PHONE_STATE 等敏感权限单独申请的硬要求。
<!-- AndroidManifest.xml -->
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:required="false" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
required="false" 表示声明但不强制要求,市场审核更友好。但注意:审核员会 grep 你的代码看你到底用没用。
2.3 SDK 自带合规开关
主流 SDK(Bugly、友盟、个推、极光、TalkingData)都内置了一个"延迟初始化"开关。开启后,SDK 主动不采集任何数据,等到外部代码显式调 init() 才开始。
// 友盟例
UMConfigure.setLogEnabled(false)
UMConfigure.setEncrypt(true)
// 在隐私弹窗回调中:
MobclickAgent.onEvent(this, "first_open")
// 极光推送
JPushInterface.setEnable(true); // 这里控制是否上报
每个 SDK 的合规开关名字不一样,建议维护一张内部 SDK 合规表。
三、隐私政策页与协议跳转
App 内必须有一份完整的隐私协议页面,并且隐私弹窗里要有可点击的"查看协议"链接。
// 常见做法:在 Assets 里带一份 HTML
val privacyHtml = assets.open("privacy_policy.html")
.bufferedReader().use { it.readText() }
// 或者指向服务器,但服务器要可访问
webView.loadUrl("https://your-cdn.com/privacy.html")
协议页内容必须包含:
公司主体名称(与营业执照一致)
收集哪些个人信息(要逐条列出,不可以"包括但不限于"这种模糊表达)
与哪些第三方共享
用户权利(访问、更正、删除、注销)
联系方式(邮箱或电话)
审核员把这些当成硬指标,缺一项过不了。
四、清单上的"敏感权限"专项处理
4.1 地理位置
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
如果你的 App 只是用来显示大体城市(粗略位置),只声明 COARSE 即可。审核员会看你代码里到底用了 LocationManager 还是 FusedLocationProvider,请求的是 FINE 还是 COARSE。
4.2 通讯录
// 错误:直接读全量通讯录上传
val phones = contentResolver.query(ContactsContract.Contacts.CONTENT_URI,
null, null, null, null)
// 正确:弹窗允许 + 单条记录选择
pickContact.launch(arrayOf(ContactsContract.CommonDataKinds.Phone.CONTENT_URI))
只用到某个联系人头像或电话?必须用户主动选一条,而不是"批量读取通讯录"。
4.3 相机 / 麦克风
Android 12 起,相机/麦克风有"指示灯"系统——被使用时要展示标识。所以运行时权限请求之外,业务代码也要做最小化:
// 拍一张头像即可
takePicture.launch(...) // 不需要持续的后台预览
五、个人信息删除与注销
PIPL 要求 App 必须提供"账号注销 + 个人信息删除"路径:
// SettingsScreen 里一定要有的两个入口
fun routeToDeleteAccount() {
startActivity(Intent(this, DeleteAccountActivity::class.java))
}
fun routeToClearPersonalData() {
lifecycleScope.launch {
DataRepository.clearAllUserData()
Toast.makeText(this@Settings, "已清除", Toast.LENGTH_LONG).show()
}
}
后端审核员会真的点这两个按钮,看是否真的有请求落到服务端。如果你只做了前端 UI、过两天又来一次"拒绝",得不偿失。
六、第三方 SDK 的合规审计
应用市场审核现在会查 SDK 列表。建议维护一个 SDK 清单:
每季度抽检一次:调用栈里还活着的 SDK、对应的初始化代码、用到的字段。
七、审核被拒后的高频理由与改法
小结
合规改造不是一个一次性事情,每一次业务变更、新接入 SDK,都可能让 App 回到"违规"状态。建议把"隐私合规"当成一道工程流水线——CI 里加一个静态扫描任务,扫描 manifest 权限、SDK 初始化点、权限申请代码路径。
下一篇我们聊一个更技术的方向:Android 与鸿蒙(HarmonyOS)迁移对比。在国内双系统策略下,懂得两边写法的工程师越来越值钱。
参考阅读
信通院《App 个人信息保护合规评估工具》
华为开发者联盟:隐私合规自检
个人信息保护法(PIPL)原文