Android 13 运行时权限适配:targetSdk 33 关键变更与避坑

发布时间:2026/9/29 5:22:52
Android 13 运行时权限适配:targetSdk 33 关键变更与避坑 把 targetSdkVersion 从 32 改成 33编译一次通过装到机器上测试同学马上来敲门通知不弹了、相册选图一片空白、WiFi 扫描列表一条都出不来。这三个现象看着八竿子打不着根因却是同一个——Android 13API 33把运行时权限又切了一刀。我前前后后给三个线上项目做过 33 的权限适配踩的坑基本都集中在这几处全新的通知权限 POST_NOTIFICATIONS、被拆成三份的媒体权限 READ_MEDIA_IMAGES / READ_MEDIA_VIDEO / READ_MEDIA_AUDIO以及 NEARBY_WIFI_DEVICES、BODY_SENSORS_BACKGROUND 这两个新面孔。下面把这几个变更逐个拆开代码怎么写、清单怎么配、拒绝之后怎么判断、adb 怎么快速复现都按我实际改过的顺序讲一遍能直接照着抄。1. Android 13 运行时权限变更总览先搞清楚影响面每次遇到系统大版本升级我的习惯是先不写代码而是把哪些权限被新增、哪些被废弃、哪些只是规则变了列成一张表再对着自己项目的清单文件一条条勾。Android 13 这次看起来条目不多但每一条都卡在真实业务路径上漏一条就是一个线上事故。先把全貌铺开后面的细节才好对号入座。1.1 一份可以直接对照的变更清单下面这张表是我自己适配时用的对照表左边是变化的权限右边是它在 Android 13 上的真实状态。建议你把项目里的 AndroidManifest.xml 打开逐行核对一遍凡是命中的都标记出来。权限Android 13 上的状态影响范围POST_NOTIFICATIONS全新运行时权限API 33 起生效所有发通知的功能READ_EXTERNAL_STORAGE对 target 33 应用已废弃请求直接返回拒绝相册、文件选择、图片扫描WRITE_EXTERNAL_STORAGE早已是空壳Android 13 上彻底不再授予保存图片到公共目录READ_MEDIA_IMAGES新增替代读图片能力图片读取READ_MEDIA_VIDEO新增替代读视频能力视频读取READ_MEDIA_AUDIO新增独立成组音频读取NEARBY_WIFI_DEVICES新增属于附近设备类WiFi 扫描、直连、配网BODY_SENSORS_BACKGROUND新增硬限制权限后台读取身体传感器这张表里最容易被忽略的是 READ_EXTERNAL_STORAGE 那一行。它不是请求了但用户可能拒绝而是请求这个动作本身就失效了系统会立刻回调拒绝压根不弹框。很多同学看到权限被拒就去查用户设置其实用户那边什么都没做过是代码在走一条已经不存在的路。另外要提醒一句Android 13 新增的这几个权限都遵循同一个基本原则——只有 targetSdkVersion 提升到 33 的应用才会看到新规则。target 还停在 32 的应用系统会按旧规则处理但会给你一些兼容性的过渡提示。这个差异后面单独讲因为它直接决定了你要不要立刻改代码。1.2 targetSdkVersion 33 与 32 的差异到底在哪这是适配时问得最多的一个问题我不升 33 行不行短期行长期不行而且中间态最容易出错。原因在于系统对不同 target 的应用走的是两套分支逻辑尤其是通知权限和媒体权限。以通知权限为例。targetSdk 33 的应用必须显式声明并主动请求 POST_NOTIFICATIONS不请求就发不出通知。而 targetSdk ≤ 32 的应用系统会代你弹一次对话框时机是应用第一次创建 NotificationChannel 的时候如果渠道在旧版本里早就建好了那就在应用第一次启动 Activity 时弹。这就解释了一个常见困惑——我明明没写请求代码怎么测试机上弹了个通知权限框。那不是你写的是系统补的。媒体权限同理。Android 13 设备上跑一个 target 32 的应用请求 READ_EXTERNAL_STORAGE 依然会被授予读取方式也还是老样子但一旦你把 target 升到 33同一条请求立刻返回拒绝代码路径整个断掉。所以我一般建议权限适配和 target 升级同步做不要拆成两个迭代否则中间会有一段时间你既吃不到新规则的好处又要维护两套判断。注意不要为了省事把 targetSdk 卡在 32。应用商店的 target 要求是逐年抬高的而且低 target 在 Android 13 上虽然能跑但系统会自动替你弹一些对话框实际用户体验反而更不可控——你连弹窗时机都掌握不了就没法在弹之前做引导说明。还有一个小细节值得记一下从 Android 12 升级到 Android 13 的设备上已经安装且通知处于开启状态的应用会被系统预先授予POST_NOTIFICATIONS。也就是说老用户升级系统后通知照常收到不会突然静默只有全新安装的应用才需要走请求流程。这个设计挺人性化但也意味着你在开发机上反复卸载重装测试时看到的永远是需要请求的路径很容易忽略升级用户的场景。2. 通知权限 POST_NOTIFICATIONS新装应用最容易被投诉的那一个通知权限是 Android 13 里存在感最强的一项变更因为它影响的不只是推送还包括前台服务的常驻通知、下载进度条、播放控制条——只要走 NotificationManager 发出去的东西全都在这个权限的管辖范围内。而且它失效时的表现非常安静不会抛异常只会什么都没发生。2.1 为什么升级用户没事新装用户却收不到通知前端时间我们一个项目做灰度反馈很奇怪老用户里零星有人说收不到推送新装用户里则是一大片。查下来就是这个权限的两套逻辑在作怪。新装用户走的是 target 33 的显式请求路径代码里没写请求就永远拿不到老用户则是升级系统时被预先授权了属于运气好。这里有一个隐蔽的坑没有通知权限时调用 notify()系统通常不会抛 SecurityException通知就是静默消失。这跟相机、定位那种没权限直接崩的权限完全不同。所以如果你的埋点只统计有没有调用发送方法而不是通知有没有真正展示这个 bug 可以在线上躺很久。我的做法是在发送通知之前加一道检查同时兼顾 target 33 和低版本设备fun canPostNotification(context: Context): Boolean NotificationManagerCompat.from(context).areNotificationsEnabled()这个判断在 Android 13 上等价于用户是否授予了 POST_NOTIFICATIONS在低版本上等价于用户是否在设置里关掉了通知。用同一个方法覆盖所有版本比按 Build.VERSION 分支要干净得多。另外提醒一句关于通知渠道的事。Android 8.0 引入 NotificationChannel 之后一旦某个渠道被用户手动关闭应用就无法再打开它只能引导用户去设置页面。Android 13 把通知权限做成了应用级的总开关再叠加渠道级的开关两层都可能掐断你的通知。排查收不到通知时这两层都要看。2.2 清单声明与请求代码的完整落地先看清单一行就够但位置很关键建议放在文件靠前的位置和定位权限排在一起uses-permission android:nameandroid.permission.POST_NOTIFICATIONS /请求时机我一般选在用户即将收到第一条通知之前而不是应用冷启动。理由很简单冷启动就弹通知权限用户根本不知道你要通知他什么拒绝率会明显偏高。比如一个订单类应用在用户首次下单成功、要推送订单状态时再请求通过率会好很多。代码用 ActivityResultContracts 这套新 API 写不要再用 onRequestPermissionsResult 了private val notificationLauncher registerForActivityResult(ActivityResultContracts.RequestPermission()) { granted - if (granted) { // 授权成功正常发送 sendOrderNotification() } else { handleDenied() } } private fun requestNotificationPermission() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { val granted ContextCompat.checkSelfPermission( this, Manifest.permission.POST_NOTIFICATIONS ) PackageManager.PERMISSION_GRANTED if (granted) { sendOrderNotification() } else { notificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } } else { // Android 13 以下没有这个运行时权限直接走通知开关判断 if (NotificationManagerCompat.from(this).areNotificationsEnabled()) { sendOrderNotification() } else { handleDenied() } } }这段代码有两个容易被忽略的地方。第一Build.VERSION_CODES.TIRAMISU是常量 33但不要用设备版本判断代替代码路径判断——低版本设备上调用 requestPermissions 传一个不认识的权限名某些 ROM 会有奇怪表现加一层版本判断最稳。第二checkSelfPermission那一步不能省用户可能已经在设置里手动开了通知这时候再弹一次请求框会显得很蠢。实操心得如果你是在已有通知渠道的项目里加这个权限务必确认请求发生在创建渠道之后。低 target 应用由系统代弹的对话框依赖渠道存在高 target 应用虽然不依赖但把请求权限和创建渠道放在同一个初始化流程里代码逻辑更清晰也方便后面排查。2.3 拒绝之后怎么判断临时拒绝、永久拒绝与跳设置Android 的权限拒绝分两种状态这是所有运行时权限的通用规则但通知权限上表现得特别典型。用户第一次点不允许属于临时拒绝下次你再请求系统还会弹框当用户明确拒绝到一定次数各厂商 ROM 略有差异通常是第二次明确拒绝之后系统就不再弹框了requestPermissions 会直接回调 denied。判断方法还是那个老三样组合granted false且shouldShowRequestPermissionRationale返回 false基本可以认定是永久拒绝。private fun handleDenied() { val canAskAgain ActivityCompat.shouldShowRequestPermissionRationale( this, Manifest.permission.POST_NOTIFICATIONS ) if (canAskAgain) { // 临时拒绝先解释为什么需要用户同意后再请求一次 showRationaleDialog { notificationLauncher.launch(Manifest.permission.POST_NOTIFICATIONS) } } else { // 永久拒绝只能引导到设置页 showGoToSettingsDialog() } }跳设置的时候有个细节值得注意通知权限不要跳应用详情页而是直接跳通知设置页路径更短用户更容易找到那个开关。val intent Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS) .putExtra(Settings.EXTRA_APP_PACKAGE, packageName) startActivity(intent)这个 Intent 从 Android 8.0 开始支持Android 13 上会直接打开应用通知页面比让用户从应用详情里翻要友好得多。我实测下来跳这一页之后的开启率比跳详情页高出不少因为详情页里权限和通知是分开的两块用户容易找不到。最后一个坑用户从设置里手动关掉通知后应用侧的 shouldShowRequestPermissionRationale 会返回 false和永久拒绝的表现一样。所以不要把这个判断写死成永久拒绝直接统一引导到设置页就行反正用户最终都得去那儿操作。3. 媒体权限大拆分READ_MEDIA_IMAGES / VIDEO / AUDIO媒体权限的拆分是 Android 13 改动最大的一块。以前一个 READ_EXTERNAL_STORAGE 走天下现在要按媒体类型分别申请。这个改动背后其实是 Android 10 分区存储思路的延续既然系统已经知道每个文件属于哪个应用那读取权限就不该是全有或全无而应该按类型、按范围精细控制。3.1 旧权限为什么一夜之间变成空壳先说结论在 targetSdk 33 的应用上READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE已经被标记为废弃请求它们会立刻返回拒绝而且不会弹任何对话框。这个静默失败是最坑的地方因为你的权限回调里只会看到 granted false然后代码里通常会提示用户请授予存储权限用户一脸懵——根本没有那个框可点。具体行为可以拆成三种情况在 Android 13 设备 target 33 应用上请求 READ_EXTERNAL_STORAGE 返回拒绝必须改用 READ_MEDIA_*在 Android 13 设备 target 32 应用上旧权限仍然会被授予读取方式也还是旧的能读到全部媒体文件在 Android 12 及以下设备上无论 target 多少都还是老规矩READ_EXTERNAL_STORAGE 照常工作。这三个分支决定了你的代码必须按设备版本 媒体类型两维判断而不是简单的一句版本号比较。还有一个容易混淆的点应用读取自己创建的媒体文件本来就不需要任何权限。这是分区存储带来的特性MediaStore 会把文件归属到创建它的应用后续读写都是自己的东西不用申请。所以如果你的功能只是拍照后立刻在应用内展示理论上不申请权限也能跑通。真正需要权限的是读取其他应用创建的媒体文件比如相册扫描、图片选择器的全量列表。3.2 清单与代码的多版本兼容写法清单文件里最稳妥的写法是新旧都留但旧的限版本。这样低版本设备能正常走旧逻辑高版本自动落到新权限上!-- Android 12 及以下使用 -- uses-permission android:nameandroid.permission.READ_EXTERNAL_STORAGE android:maxSdkVersion32 / uses-permission android:nameandroid.permission.WRITE_EXTERNAL_STORAGE android:maxSdkVersion29 / !-- Android 13 起使用 -- uses-permission android:nameandroid.permission.READ_MEDIA_IMAGES / uses-permission android:nameandroid.permission.READ_MEDIA_VIDEO / uses-permission android:nameandroid.permission.READ_MEDIA_AUDIO /WRITE_EXTERNAL_STORAGE 的 maxSdkVersion 我习惯写 29因为从 Android 11API 30开始它对第三方应用就已经没有实际作用了留着只是为了让 Android 10 及以下的老设备能正常保存文件。代码侧的请求分支建议封装成一个方法各个业务模块统一调用避免每个页面各写一套private fun mediaPermissionsFor(vararg types: MediaType): ArrayString { if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { return types.map { when (it) { MediaType.IMAGE - Manifest.permission.READ_MEDIA_IMAGES MediaType.VIDEO - Manifest.permission.READ_MEDIA_VIDEO MediaType.AUDIO - Manifest.permission.READ_MEDIA_AUDIO } }.toTypedArray() } return arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE) }这里有个实操细节READ_MEDIA_IMAGES 和 READ_MEDIA_VIDEO 在系统对话框里会合并成一条照片和视频用户点一次允许两个权限同时到手而 READ_MEDIA_AUDIO 会单独弹一条音乐和音频。所以如果你的应用只用图片和视频一次请求就够如果还涉及音频用户要面对两次对话框。我一般会把音频请求往后放等用户真正要播放本地音频时再弹而不是在首页一次性弹两个框那样拒绝率会翻倍。注意事项三个权限虽然拆开了但它们的授权状态是独立的。用户可能只允许了照片和视频拒绝音乐和音频。所以代码里不能用任意一个授予就算通过这种偷懒判断每个类型都要单独 checkSelfPermission。我见过一个项目在权限检查里写了个 OR 条件结果用户只授权图片后点开音乐列表直接空白。3.3 改别人的媒体文件从 WRITE_EXTERNAL_STORAGE 到 createWriteRequest读取说完了写入更绕一点。Android 11 之后第三方应用已经无法用 WRITE_EXTERNAL_STORAGE 往公共目录随便写文件了。想修改或删除其他应用创建的媒体文件正规路径是走 MediaStore 的请求 APIval uris: ListUri listOf(targetUri) val intentSender MediaStore.createWriteRequest(contentResolver, uris).intentSender // 用 ActivityResultLauncher 启动等用户确认这个调用会弹一个系统确认框问用户是否允许修改这张照片。删除则是MediaStore.createDeleteRequest。这套机制在 Android 10 上就有了Android 13 继续沿用属于没有写入权限也能完成的合法操作。我在适配时总结出一个判断原则挺好用凡是自己应用创建的文件永远不需要权限凡是别的应用创建的文件读取走 READ_MEDIA_*修改走 createWriteRequest删除走 createDeleteRequest。按这个原则去梳理业务代码基本不会漏。另外补一句前瞻性的提醒Android 14 在图片和视频上进一步引入了仅选择部分照片的授权模式对应的权限是 READ_MEDIA_VISUAL_USER_SELECTED。如果你的应用现在正准备做媒体权限重构建议把部分授权这条路径也考虑进去把媒体读取封装成带回调的统一入口后面接新权限时改一处就行不用再翻全项目。4. 两个新面孔NEARBY_WIFI_DEVICES 与 BODY_SENSORS_BACKGROUND通知和媒体是人人都会碰到的变更这两个则是碰上了就很头疼的类型。它们的共同特点是权限组新、文档少、行为依赖 target 版本而且都不是简单的加一行声明就能解决。4.1 附近 WiFi 设备权限neverForLocation 该不该加Android 13 把发现和连接附近 WiFi 设备的能力从定位权限里剥离出来新增了 NEARBY_WIFI_DEVICES归在附近设备这一大类里。这个改动的意图很明确过去很多应用只是想扫描 WiFi 列表或者给智能设备配网却被迫申请精确定位权限用户看着心里也不舒服。清单有两种写法区别在于你要不要用 WiFi 结果去推断物理位置!-- 不用于推断位置只需要扫描和连接 -- uses-permission android:nameandroid.permission.NEARBY_WIFI_DEVICES android:usesPermissionFlagsneverForLocation / !-- 需要用 WiFi 结果推断位置 -- uses-permission android:nameandroid.permission.NEARBY_WIFI_DEVICES /加了neverForLocation之后系统就认为你不是拿 WiFi 当定位用不用额外申请 ACCESS_FINE_LOCATION。对配网类、设备发现类应用来说这个标记非常划算能省掉一个用户最敏感的定位权限。但要注意两点。第一neverForLocation只对 target 33 及以上生效target 还停在 32 的应用系统仍然按旧规则要求 ACCESS_FINE_LOCATION而且扫描结果还会被限流比如扫描频率受限、返回的列表被裁剪。第二如果你确实用 WiFi 信息做室内定位或者地理围栏老老实实申请精确定位别为了省事加这个标记否则被系统判定为违规使用会有下架风险。踩坑记录我们做过一个智能硬件的配网页面代码里同时保留了旧版定位逻辑和新版权限逻辑。升级 target 33 后发现扫描列表为空排查半天才想起来新设备上根本没申请 NEARBY_WIFI_DEVICES只申请了定位。系统在 target 33 下不再把定位权限当作 WiFi 扫描的替代品这条路径彻底断了。后来统一改成33 以上申请 NEARBY_WIFI_DEVICES33 以下申请 ACCESS_FINE_LOCATION问题消失。4.2 后台身体传感器一个普通应用申请不到的硬限制权限BODY_SENSORS_BACKGROUND 是 Android 13 新增的另一个权限它的特殊之处在于属于硬限制权限hard restricted permission。这类权限的特点是普通第三方应用在清单里声明了、代码里申请了系统也不会弹框直接返回拒绝。只有满足特定条件比如属于系统预置、或者被判定为合格的特定类型应用才能被授予。它的设计目的是把前台读身体传感器和后台持续读取这两件事分开。Android 13 之前一个 BODY_SENSORS 权限拿到手理论上后台也能持续读心率、步数这类数据系统拦不住。拆分之后target 33 的应用如果要在后台读就需要额外的 BODY_SENSORS_BACKGROUND。所有涉及前台服务读取传感器的场景都要重新审视一遍。清单里加上这一行uses-permission android:nameandroid.permission.BODY_SENSORS_BACKGROUND /但更重要的是产品层面的调整如果拿不到这个权限后台采集就没法做那就要考虑改成用户主动打开应用时前台采集或者用前台服务 常驻通知的方式保持前台状态。这不是技术能绕过去的得从功能设计上解决。调试阶段如果需要验证后台路径的逻辑有没有写对可以用 adb 手动授予adb shell pm grant com.example.app android.permission.BODY_SENSORS_BACKGROUND这类硬限制权限在真机上是走不到授权流程的用命令行模拟授权是验证代码分支的有效手段。记得验证完执行adb shell pm revoke还原不然会污染后面的测试结果。5. 顺带被改掉的相关行为广播注册、包查询与权限自动重置Android 13 还有几处变更严格说不是运行时权限但和权限模型紧密相关经常在适配时一起冒出来。我把它们归到这一节因为很多团队是在排查权限问题时才发现这些改动也生效了。5.1 registerReceiver 的导出标志从 target 33 开始注册非系统广播接收器时必须显式声明这个接收器是否对外导出不写会直接抛异常。这个改动的出发点是防止内部广播被其他应用监听或伪造属于安全加固。ContextCompat.registerReceiver( context, receiver, IntentFilter(ACTION_INTERNAL_UPDATE), ContextCompat.RECEIVER_NOT_EXPORTED )只用应用内部通信的写RECEIVER_NOT_EXPORTED需要接收外部应用发来的广播写RECEIVER_EXPORTED但同时要考虑加签名级权限做保护不能裸奔。这里有个冷知识当你用RECEIVER_NOT_EXPORTED注册时系统在安装阶段会往你的应用清单里自动注入一个名为DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION的签名级权限用来确保发出的广播只有自己这个 UID 能收到。你在打包后的清单里能看到它别以为是自己手抖加进去的。实操心得如果你用的是老版本 androidx.core低于 1.9.0ContextCompat.registerReceiver这个方法还不存在得手动按版本分支调用registerReceiver并传 flags。升级 androidx.core 是更省事的做法顺手还能兼容 Android 14 的进一步收紧。5.2 休眠应用与权限自动重置对适配的影响权限自动重置从 Android 11 就有了应用几个月没被使用系统就会撤销它已经拿到的运行时权限。Android 13 在这个基础上又加了一层休眠机制长期未使用的应用会被置入休眠状态除了撤销权限还会停止通知、清理临时缓存文件。这对开发者的实际影响是你不能假设权限一旦拿到就永远有效。每次用到敏感权限之前都要重新 checkSelfPermission不能用一次请求后存个标记位就完事。我之前接手过一个项目代码里在首次启动时申请权限通过后写进 SharedPreferences后面所有地方都读这个标记位。结果用户三个月没打开应用再进来时权限早被系统撤销了功能直接瘫痪。如果某个应用的权限确实不能自动重置比如企业设备管理类可以在清单里禁用application android:autoRevokePermissionsdisallow但这个属性要慎用用得不对可能引起用户投诉。另一个思路是用PackageManager.isAutoRevokeWhitelisted()在运行时判断当前状态只做提示不强行干预。顺带说一句用户手动清理应用数据设置里点清除数据也会重置所有权限状态这跟休眠是两回事但排查问题时经常一起出现。测试同学报权限又没了的时候先问一句是不是清过数据能省不少时间。6. 调试与排查adb 命令、速查表与测试矩阵权限相关的问题光看代码很难定位因为你不知道系统内记录的状态到底是什么。我这边有几个常用命令基本上遇到权限问题先跑一遍比读代码快得多。6.1 我常用的几条 adb 命令# 撤销某个运行时权限模拟未授权状态 adb shell pm revoke com.example.app android.permission.POST_NOTIFICATIONS # 手动授予用来验证拿到权限后的代码路径 adb shell pm grant com.example.app android.permission.POST_NOTIFICATIONS # 查看应用当前的权限授予情况 adb shell dumpsys package com.example.app | grep -A 20 runtime permissions # 通过 appops 直接开关通知权限比 revoke 更底层 adb shell cmd appops set com.example.app POST_NOTIFICATION ignore adb shell cmd appops set com.example.app POST_NOTIFICATION allow # 列出所有危险权限及其所属权限组 adb shell pm list permissions -d -g其中dumpsys package那条最有用它能告诉你每个权限是 granted 还是 denied、有没有被标记为不再询问flags 里的 user-fixed 字段。比跑一遍应用再猜快得多。pm list permissions -d -g适合刚开始适配时用它能一次性列出 Android 13 上所有危险权限和权限组的对应关系对着看就能确认自己用的权限名有没有写错。权限名字拼错这种事听起来很蠢但在改了几十行清单之后真的会发生而且拼错的表现也是静默拒绝很难查。6.2 常见问题速查表下面这张表是我在实际项目里遇到过的典型问题整理出来的按现象查会快一些。现象可能原因处理方式新装应用收不到通知不报错未请求 POST_NOTIFICATIONS加权限声明和请求逻辑低 target 应用突然弹通知框系统代弹渠道创建或启动时触发把引导文案提前避免用户困惑请求存储权限立刻被拒无弹框target 33 下旧权限已废弃换成 READ_MEDIA_*只授权图片后音频列表空白三个媒体权限状态独立每个类型单独判断WiFi 扫描列表为空未申请 NEARBY_WIFI_DEVICES按版本分支申请后台传感器读不到数据BODY_SENSORS_BACKGROUND 未被授予改前台采集或走特殊授权流程注册广播时崩溃未指定导出标志用 RECEIVER_NOT_EXPORTED几个月后权限失效系统自动重置或应用休眠每次使用前重新检查权限用这张表的时候有个建议先确认设备版本和 targetSdk 的组合再去对现象。同一段代码在 Android 12 和 13 上表现可能完全相反不确认这两个前提很容易查错方向。6.3 上线前必跑的测试矩阵权限问题最容易在版本组合上翻车所以我一般会在发版前跑一个固定矩阵。不需要覆盖所有机型覆盖这四个组合就能把绝大多数分支打出来系统版本targetSdk重点验证项Android 1333全部新权限的请求、拒绝、永久拒绝路径Android 1332系统代弹通知框的时机、旧媒体权限是否仍可用Android 1233低版本设备上的降级分支是否正确Android 1033老设备上的存储写入路径跑矩阵的时候有个细节每换一个组合都要先卸载重装。因为权限状态是跟着安装实例走的直接覆盖安装会保留上一次的授权结果测试结论不可信。我是吃过这个亏的——在 Android 13 上测通知权限拒绝路径怎么测都是已授权后来发现是上一轮的 pm grant 还留着。另外建议在通知权限的测试里加两条用例一是升级路径模拟从 Android 12 升到 13 的老用户验证通知会不会突然中断二是长期未使用用 adb 手动触发权限重置验证代码有没有在每次使用前重新检查权限。这两条用例在线下跑不难但能挡住最典型的线上事故。我在实际做 Android 13 权限适配的过程中最大的体会是这次的变更没有一条是加个权限声明就完事的每一条都牵涉到请求时机、拒绝后的引导、以及不同系统版本上的分支判断。真正省时间的做法不是赶紧把编译错误消掉而是先把权限清单和业务路径对齐一遍把每个权限的首次请求、临时拒绝、永久拒绝、系统撤销四种状态都过一遍脑子再动手写代码。这样改完之后后面几个版本基本不用再回头返工。

关于本文作者

来自尧图内容编辑团队

尧图内容编辑团队 内容团队

尧图内容编辑团队

本文由尧图网络内容编辑团队执笔。团队由资深项目经理、前端工程师与设计师组成,所有内容均来自亲手交付的真实项目,先讲清问题、再给出可落地的解法。尧图深耕北京网站建设十年,服务过京华建材集团、智造科技等各行业客户,把一线经验沉淀为可复用的行业观察。

  • 十年建站经验,覆盖建材、制造、服务、文创等
  • 项目经理把关选题与事实准确性
  • 工程师与设计师联合撰写专业细节
  • 统一编辑规范,保证文风与排版一致
  • 每月复盘转化数据,迭代选题方向

延伸阅读

相关资讯与近期热门内容

深度阅读推荐

建站决策前值得细读的三篇

网站改版的5个关键决策
2024-08-12

网站改版的5个关键决策

什么时候该改版、改到什么程度、如何避免流量掉光,京华建材集团改版复盘给出答案。

获取专属建站方案

看完文章,把您的行业与预算告诉我们,免费获取一份量身定制的官网建设方案与报价。

立即免费咨询