一、Android 14/15 隐私新特性全景

说实话,每次 Android 大版本更新,最让我头疼的就是隐私合规这块。不是技术难,而是变化太快。我记得 Android 13 刚适配完,14 又来了,15 紧接着跟上。今天我就把这三个最关键的隐私特性——照片选择器、后台活动限制、通知权限和精确位置权限变更——给你讲透。

先看一张总览图,了解这几个特性在隐私体系中的位置:

Android 14/15 隐私新特性核心体系 照片选择器 • 无需 READ_MEDIA_IMAGES 权限 • 用户选择后返回 URI • 支持图片/视频选择 • 系统级 UI,不可篡改 后台活动限制 • 后台启动 Activity 受限 • 通知冷启动需 PendingIntent • 前台服务更严格 • 后台位置访问需审批 通知权限 • POST_NOTIFICATIONS 运行时权限 • 用户可拒绝通知 • 通知渠道需适配 • 前台服务通知必须显示 精确位置权限 • ACCESS_FINE_LOCATION 需单独申请 • 用户可选「大致位置」 • 后台位置需额外审批 • 权限弹窗只能显示一次 核心原则:最小权限 + 用户透明 + 合规适配

二、照片选择器:告别 READ_MEDIA_IMAGES

这个改动其实挺大的。以前我们要读取用户相册,得申请 READ_EXTERNAL_STORAGE 或者 READ_MEDIA_IMAGES 权限。但从 Android 14 开始,Google 强烈推荐用系统自带的照片选择器。

2.1 为什么推荐照片选择器?

说白了,就是让用户自己选,而不是你直接扫整个相册。你想想看,一个修图 App 要读你所有照片,合理吗?其实大部分场景只需要用户选的那一两张。

我在项目中遇到过,有些团队为了省事,直接申请了 READ_MEDIA_IMAGES,结果审核被拒。Google Play 现在对这类权限审查特别严,没有充分理由基本过不了。

2.2 适配代码示例

// 使用照片选择器(Android 14+)
val pickMedia = registerForActivityResult(
    ActivityResultContracts.PickVisualMedia()
) { uri: Uri? ->
    uri?.let {
        // 用户选择了照片,处理 URI
        imageView.setImageURI(it)
    }
}

// 启动选择器
pickMedia.launch(PickVisualMediaRequest(
    ActivityResultContracts.PickVisualMedia.ImageOnly
))

// 如果需要视频,可以这样
pickMedia.launch(PickVisualMediaRequest(
    ActivityResultContracts.PickVisualMedia.VideoOnly
))
小提示:照片选择器返回的是 content URI,有效期是临时的。如果需要长期访问,记得用 takePersistableUriPermission 申请持久化权限。

2.3 兼容性处理

如果你的 App 还要支持 Android 13 以下,怎么办?我建议做降级处理:

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    // 使用照片选择器
    pickMedia.launch(...)
} else {
    // 回退到传统方式,申请 READ_EXTERNAL_STORAGE
    requestStoragePermission()
}
注意:Android 15 进一步收紧了照片选择器的使用。如果你的 App targetSdkVersion 设为 35,系统会强制使用照片选择器,传统权限申请可能直接失效。

三、后台活动限制:别想偷偷启动 Activity

这个改动我印象特别深。以前有些流氓 App 会在后台偷偷弹广告,或者拉起其他 Activity。Android 14 直接把这个口子堵死了。

3.1 限制规则

从 Android 14 开始,App 在后台时不能随意启动 Activity。除非满足以下条件之一:

  • App 有一个可见的窗口(比如前台服务通知)
  • 用户主动与 App 交互(比如点击通知)
  • 系统发出的 PendingIntent(比如闹钟、来电)
  • App 拥有 SYSTEM_ALERT_WINDOW 权限

3.2 避坑指南

我曾经踩过一个坑:App 收到推送后,想在后台直接打开一个 Activity 展示详情。结果在 Android 14 上直接崩溃,日志显示 BackgroundActivityStartNotAllowedException

正确的做法是:

// 错误做法:直接在广播接收器里启动 Activity
// Intent intent = new Intent(context, DetailActivity.class);
// context.startActivity(intent);  // 会崩溃!

// 正确做法:通过通知的 PendingIntent 启动
val notificationIntent = Intent(context, DetailActivity::class.java)
val pendingIntent = PendingIntent.getActivity(
    context,
    0,
    notificationIntent,
    PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
)

// 构建通知时传入 PendingIntent
val notification = NotificationCompat.Builder(context, CHANNEL_ID)
    .setContentIntent(pendingIntent)
    .build()
核心原则:后台启动 Activity 的唯一安全路径就是通过通知的 PendingIntent。别想着走捷径,系统会直接拦截。

四、通知权限:用户终于可以关掉你的通知了

这个改动其实从 Android 13 就开始了,但 Android 14/15 进一步强化了。现在用户安装 App 后,系统会弹窗询问是否允许通知。如果用户拒绝,你的通知就发不出去了。

4.1 权限声明

首先,你需要在 Manifest 中声明权限:

<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />

4.2 运行时申请

// 检查通知权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
    if (ContextCompat.checkSelfPermission(
            this,
            Manifest.permission.POST_NOTIFICATIONS
        ) != PackageManager.PERMISSION_GRANTED
    ) {
        // 申请权限
        ActivityCompat.requestPermissions(
            this,
            arrayOf(Manifest.permission.POST_NOTIFICATIONS),
            NOTIFICATION_PERMISSION_REQUEST_CODE
        )
    }
}

4.3 适配建议

我个人习惯是在用户第一次需要通知功能时再申请权限,而不是一启动就弹窗。比如用户点击「开启消息提醒」按钮时再申请,这样成功率更高。

经验之谈:如果用户拒绝了通知权限,不要反复弹窗。可以在 UI 上显示一个「通知已关闭」的提示,引导用户去系统设置里手动开启。

五、精确位置权限:用户可以选择「大概位置」

这个改动对地图、外卖、打车类 App 影响最大。以前用户要么给精确位置,要么不给。现在用户可以选择只给「大致位置」(精度约 500 米)。

5.1 权限变更

权限 说明 Android 14/15 变化
ACCESS_FINE_LOCATION 精确位置(GPS 级别) 用户可选「大致位置」替代
ACCESS_COARSE_LOCATION 大致位置(基站/WiFi 级别) 无变化
ACCESS_BACKGROUND_LOCATION 后台位置访问 需单独申请,审核更严

5.2 适配代码

// 申请精确位置权限
val locationPermissionRequest = registerForActivityResult(
    ActivityResultContracts.RequestMultiplePermissions()
) { permissions ->
    when {
        permissions.getOrDefault(
            Manifest.permission.ACCESS_FINE_LOCATION, false
        ) -> {
            // 用户授予了精确位置
        }
        permissions.getOrDefault(
            Manifest.permission.ACCESS_COARSE_LOCATION, false
        ) -> {
            // 用户只授予了大致的置
        }
        else -> {
            // 用户拒绝了所有位置权限
        }
    }
}

locationPermissionRequest.launch(
    arrayOf(
        Manifest.permission.ACCESS_FINE_LOCATION,
        Manifest.permission.ACCESS_COARSE_LOCATION
    )
)

5.3 避坑指南

我曾经遇到一个外卖 App,用户只给了大致位置,结果 App 直接崩溃了——因为代码里强转 Location.getAccuracy() 时没做空判断。后来我加了个兜底逻辑:如果精度不够,就提示用户手动输入地址。

重要提醒:Android 15 对后台位置访问的审核更严格了。如果你的 App 不是地图导航类,基本申请不下来。建议重新评估业务场景,看是否真的需要后台位置。

六、总结与适配清单

好了,这四个特性讲完了。我最后给你列个检查清单,方便你对照适配:

  • 照片选择器:替换 READ_MEDIA_IMAGES 为系统照片选择器,做好降级兼容
  • 后台活动限制:所有后台启动 Activity 改为通过通知 PendingIntent
  • 通知权限:声明 POST_NOTIFICATIONS,在合适时机申请
  • 精确位置权限:支持用户选择「大致位置」,做好精度不足的兜底

适配这些特性,说白了就是尊重用户的选择权。你想想看,用户把照片、位置、通知这些敏感权限交给你,你得对得起这份信任。合规不是负担,而是产品长期发展的基石。


公众号:蓝海资料掘金营,微信deep3321