3. 权限未声明导致的运行时崩溃
权限崩溃,说白了就是你的 App 在运行时突然摔了个跟头。用户正用得顺手呢,界面一闪就退了。这种崩溃在 Android 6.0 以后非常常见。我自己做 SDK 集成这几年,光是跟权限相关的线上问题就排过不下几十个。
为什么会出现权限未声明的崩溃?核心原因就一个:你在调某个 API 之前,没有在 AndroidManifest 里声明对应的权限。或者说你声明了,但没在运行时动态申请。Android 系统会在你的代码试图访问受保护资源时,直接抛出一个 SecurityException。
AndroidManifest.xml 里写上 <uses-permission> 就万事大吉。对于危险权限,你还需要在运行时通过 requestPermissions() 去申请。否则,系统会在 Android 6.0+ 设备上静默拒绝你的请求,或者直接崩溃。
3.1 典型的崩溃现场
我记得有一次集成一个地图 SDK,客户反馈在部分华为手机上打开地图就闪退。抓日志一看,核心堆栈是这样的:
java.lang.SecurityException:
Permission Denial:
getLocationProvider() requires android.permission.ACCESS_FINE_LOCATION
or android.permission.ACCESS_COARSE_LOCATION
at android.os.Parcel.createException(Parcel.java:2071)
at android.location.ILocationManager$Stub$Proxy.getProviders(LocationManager.java)
at android.location.LocationManager.getProviders(LocationManager.java:1200)
at com.map.sdk.MapEngine.init(MapEngine.java:58)
at com.example.app.MainActivity.onCreate(MainActivity.java:35)
看到了吧?SecurityException 后面直接告诉你缺少哪个权限。很直白,不怕你读不懂。
3.2 权限分类速查表
Android 把权限分成了几类。我整理了一个表格,方便你快速对照排查:
| 权限类型 | 是否需要声明 | 是否需要运行时申请 | 常见 API 示例 |
|---|---|---|---|
| Normal(普通) | ✅ 需要 | ❌ 不需要 | INTERNET、ACCESS_NETWORK_STATE |
| Dangerous(危险) | ✅ 需要 | ✅ 必须申请 | ACCESS_FINE_LOCATION、CAMERA、READ_EXTERNAL_STORAGE |
| Signature(签名) | ✅ 需要 | ❌ 不需要(仅系统应用可用) | BIND_ACCESSIBILITY_SERVICE |
| Special(特殊) | ❌ 不声明 | ✅ 需要跳转设置页面申请 | SYSTEM_ALERT_WINDOW、WRITE_SETTINGS |
3.3 避坑指南:我踩过的三个坑
3.3.1 坑一:只写 XML 不申请运行时
几年前我刚做 SDK 集成时,认为在 AndroidManifest.xml 里写了 <uses-permission android:name="android.permission.CAMERA" /> 就够了。结果在 Android 8.0 的真机上,一调相机预览就闪退。那会儿我花了整整一个下午才意识到——危险权限必须动态申请。
正确的做法是调用 checkSelfPermission() 检查,如果没授权就调 requestPermissions()。示例:
// 检查权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 申请权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA}, CAMERA_REQUEST_CODE);
}
3.3.2 坑二:Android 11 的包可见性
这个坑在 SDK 集成里太隐蔽了。Android 11 引入了 QUERY_ALL_PACKAGES 权限,如果你的 SDK 里通过 PackageManager.getInstalledApplications() 去查询其他 App 信息,但没在 Manifest 里加 <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />,系统会直接抛 SecurityException。我曾经见过一个广告 SDK 因为这个原因在 Android 12 设备上大面积崩溃。
3.3.3 坑三:第三方 SDK 隐式调用了高危 API
有时候你自己的代码没直接调高危 API,但你集成的某个第三方 SDK 内部调了。比如一个支付 SDK 内部调用了 TelephonyManager.getDeviceId(),这就声明了 READ_PHONE_STATE 权限。如果你忘了声明这个权限,崩溃就会从 SDK 内部抛出。排查这类问题时,我建议在崩溃堆栈里找 com.xxx.sdk 的调用帧,然后去对应 SDK 的文档里确认权限依赖。
3.4 集成 SDK 时的权限配置清单
为了避免反复在同一个地方摔倒,我总结了下面这个检查清单。每次集成新的 SDK 前,我都会逐一核对:
- 读文档 —— SDK 官方文档里有没有"Permissions"章节?仔细看。
- 加声明 —— 在
AndroidManifest.xml添加所有需要的<uses-permission>。 - 测低版本 —— 在 Android 6.0 以下的设备上,危险权限不需要动态申请,但声明还是必须的。
- 测高版本 —— 在 Android 12 以上,额外注意
BLUETOOTH_CONNECT、QUERY_ALL_PACKAGES等新规。 - 模拟用户拒绝 —— 在测试中勾选"不再询问"并拒绝权限,确认 App 不会直接崩溃。
- 查看运行时日志 —— 用
adb logcat -s SecurityException过滤,可以快速发现遗漏的权限。
adb logcat | grep -i "permission\|SecurityException",基本上能提前发现问题。
3.5 一个小而美的防御性写法
我自己在封装 SDK 调用的时候,习惯在最外层加一个 try-catch,把 SecurityException 兜住。虽然不能真正解决权限缺失的问题,但至少能让 App 不闪退,给用户一个友好的提示:
try {
SDKManager.init(this);
SDKManager.startScan();
} catch (SecurityException e) {
Log.e("SDK_WRAPPER", "权限不足,请先授予必要权限", e);
Toast.makeText(this, "请前往设置授予位置权限", Toast.LENGTH_LONG).show();
}
这种做法呢,更多的是给开发者自己留一条后路。线上环境里,有些用户可能因为各种原因拒了权限,你的 App 如果直接闪退,那体验就太糟糕了。
3.6 总结一下核心要点
- 权限崩溃的根源:SecurityException,异常信息里会告诉你缺失哪个权限。
- 解决方案:AndroidManifest 声明 + 运行时动态申请(危险权限)。
- 排查路径:看堆栈 → 定位哪个 API 触发 → 去对应源码或 SDK 文档找权限依赖 → 补声明/补动态申请。
- 别忘了 Android 11/12 新增的权限规则,有些权限(如
MANAGE_EXTERNAL_STORAGE)需要跳转到系统设置页面。
嗯,就这些。记住,权限问题其实不复杂,多数情况都是粗心漏掉了声明。按我上面那张流程图走一遍,基本半小时内能搞定。下一类要面对的崩溃问题,我会讲到另一个高频场景——资源加载异常导致的运行时崩溃。