哈喽,朋友!我是Agnes。既然你点开了这篇文章,我猜你可能刚被一个“App已停止运行”的弹窗搞得头大,或者看着Logcat里那一长串红色的 SecurityException 怀疑人生。别慌,这在Android开发的新手村简直是“必经之路”。今天咱们不整那些枯燥的官方文档翻译腔,我就用我带过无数萌新的经验,给你扒一扒那些让APP当场去世的权限坑,顺便附上能直接抄作业的解法。
一、 最大的误解:以为申请了权限就万事大吉
很多新手(包括当年的我)有个根深蒂固的错误认知:“我在 Manifest 里写了 <uses-permission>,我的代码就能随便访问数据了。”
大错特错!在 Android 6.0(API 23)之后,权限系统发生了翻天覆地的变化。Android 把权限分成了两类:普通权限(Normal Permissions)和危险权限(Dangerous Permissions)。
- 普通权限:比如访问网络、访问蓝牙。这些在 Manifest 里声明就行,系统会自动批准,不用用户点头。
- 危险权限:比如读取通讯录、访问位置、拍摄照片。这些必须在运行时向用户申请,并且要用户点击“允许”。
如果你只声明了权限,却不在运行时申请,或者申请了但没处理用户的拒绝,一旦代码试图访问受保护的资源,SecurityException 就会像定时炸弹一样爆炸,App 直接崩溃。
🚨 真实案例:崩溃的开始
想象一下,你正在做一个天气 App,需要获取用户的位置来显示当地天气。你在 AndroidManifest.xml 里自信地写下了:
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
然后,你在 Activity 里直接调用了定位 API:
// ❌ 这是新手最常犯的错误代码
LocationManager locationManager = (LocationManager) getSystemService(LOCATION_SERVICE);
Location location = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
double latitude = location.getLatitude(); // boom!
结果: 如果用户没有在设置里预先开启位置权限(或者你的 App 还没申请过),getLastKnownLocation 返回 null,紧接着 location.getLatitude() 就会抛出一个 NullPointerException。
更糟糕的情况是,如果你用的是需要运行时权限的新 API(比如 FusedLocationProviderClient),在权限未授予时调用,系统会直接抛出 SecurityException,日志里会赫然写着:
java.lang.SecurityException: "getBestLocation" needs access to fine location...
这就是典型的“以为声明了就行”导致的崩溃。
二、 运行时权限申请的“三重门”
要彻底解决权限问题,你得跨过三道门。每一道门都有坑,踩上去都会掉进崩溃的深渊。
第一重门:checkSelfPermission —— 别偷懒,先检查
在每次执行敏感操作之前,必须检查当前权限状态。不要假设用户会一直授权,因为用户随时可以在设置里取消授权。
if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION)
!= PackageManager.PERMISSION_GRANTED) {
// 权限未授予,需要申请
} else {
// 权限已授予,可以执行操作
}
坑点提醒: 有些新手喜欢在 onCreate 里一次性检查所有权限,然后直接开始干活。但如果此时权限还没申请,代码直接执行后续逻辑,必然崩溃。权限检查必须包裹在逻辑判断里,而不是只在初始化时执行一次。
第二重门:requestPermissions —— 别忽略回调
当你调用 ActivityCompat.requestPermissions 时,系统会弹出那个熟悉的对话框。但很多人只做了申请,没处理结果!
// 申请权限
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_LOCATION_CODE);
关键来了: 你必须重写 onRequestPermissionsResult 方法来处理用户的响应。如果用户点了“拒绝”,你的代码该怎么办?如果什么都不做,下次用户打开 App 点击“查找附近”时,依然会崩溃。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_LOCATION_CODE) {
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 用户同意了,可以开始获取位置
startLocationService();
} else {
// 用户拒绝了!这时候不能什么都不干,要给用户提示,或者引导他去设置里开启
showPermissionDeniedDialog();
}
}
}
真实案例:被忽视的“不再询问”
如果用户在申请弹窗上勾选了“不再询问”并选择了拒绝,下次你再调用 requestPermissions,系统不会再弹窗,而是直接回调 onRequestPermissionsResult 并返回 PERMISSION_DENIED。
很多新手在这里栽跟头:他们以为弹窗还在,结果没有,然后代码逻辑断裂,导致后续操作异常。这时,你应该调用 shouldShowRequestPermissionRationale 来判断是否需要向用户解释为什么需要这个权限。
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.ACCESS_FINE_LOCATION)) {
// 显示一个友好的提示,解释为什么需要位置权限
} else {
// 第一次申请,或者直接申请
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_LOCATION_CODE);
}
第三重门:多权限批量申请 —— 别混为一谈
现在的 App 往往需要多个权限,比如相机、存储、麦克风。新手喜欢用一个请求码去申请所有权限,然后在回调里一把梭哈。
// ❌ 错误示范:混乱的批量申请
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA, Manifest.permission.READ_EXTERNAL_STORAGE},
ALL_PERMISSIONS_REQUEST_CODE);
@Override
public void onRequestPermissionsResult(...) {
if (grantResults[0] == PackageManager.PERMISSION_GRANTED &&
grantResults[1] == PackageManager.PERMISSION_GRANTED) {
// 两个都同意了才执行
}
}
问题: 如果用户只同意了相机,拒绝了存储,你的逻辑就卡住了。而且,如果两个权限的用途完全不同,放在一个弹窗里会让用户困惑,增加拒绝的概率。
最佳实践: 尽量分开申请,或者使用更智能的权限库。如果必须批量申请,要遍历 grantResults,分别判断每个权限的状态。
// ✅ 推荐做法:分开处理,或者使用库
Map<String, Integer> permissionsMap = new LinkedHashMap<>();
permissionsMap.put(Manifest.permission.CAMERA, CAMERA_PERMISSION_CODE);
permissionsMap.put(Manifest.permission.READ_EXTERNAL_STORAGE, STORAGE_PERMISSION_CODE);
if (!permissionsMap.isEmpty()) {
ActivityCompat.requestPermissions(this,
permissionsMap.keySet().toArray(new String[0]),
permissionsMap.values().stream().mapToInt(Integer::intValue).max().orElse(0));
}
三、 特殊场景的“隐形杀手”
除了基本的定位和相机,还有几个场景特别容易让新手崩溃。
1. 后台位置权限(Android 10+)
从 Android 10(API 29)开始,访问后台位置需要额外的权限 ACCESS_BACKGROUND_LOCATION。如果你只申请了 ACCESS_FINE_LOCATION,但在 App 退到后台时尝试获取位置,就会崩溃。
<!-- 必须在 Manifest 中声明 -->
<uses-permission android:name="android.permission.ACCESS_BACKGROUND_LOCATION" />
解决方案: 在前台权限申请通过后,如果功能确实需要后台定位,再单独申请 ACCESS_BACKGROUND_LOCATION。
2. 文件系统权限(Android 11+)
Android 11(API 30)引入了分区存储(Scoped Storage)。以前 READ_EXTERNAL_STORAGE 就能读写所有文件,现在不行了。如果你还在用老方法读写文件,尤其是在 Android 11 以上的设备上,很容易遇到 FileNotFoundException 或者 SecurityException。
建议: 尽量使用 MediaStore API 来访问媒体文件,或者申请 MANAGE_EXTERNAL_STORAGE(但这需要严格的审核理由)。对于一般应用,避免直接访问公共目录。
3. 生物识别权限
使用指纹或面部识别时,虽然不需要在 Manifest 中声明特殊权限,但如果设备不支持(比如老款手机),BiometricPrompt 会直接回调错误。新手常常忘记处理 BIOMETRIC_ERROR_HW_UNAVAILABLE 等情况,导致 App 无响应。
biometricPrompt.authenticate(promptInfo, new BiometricPrompt.AuthenticationCallback() {
@Override
public void onAuthenticationError(int errorCode, @NonNull CharSequence errString) {
if (errorCode == BiometricPrompt.ERROR_HW_UNAVAILABLE) {
// 硬件不可用,提示用户改用密码登录
showToast("指纹功能暂时不可用,请使用密码登录");
}
}
// ... 其他回调
});
四、 给新手的“防崩溃”工具箱
手动写权限逻辑太繁琐了,还容易出错。我建议新手直接借助成熟的库,既能减少代码量,又能避免很多边界情况。
方案一:使用 ActivityCompat 和 ContextCompat
这是官方推荐的最基础方案,必须掌握。记住口诀:先检查,再申请,必回调,分场景。
方案二:使用 RxPermissions 或 PermissionDispatcher
这些库封装了复杂的权限回调逻辑,让代码更简洁。比如 RxPermissions:
RxPermissions rxPermissions = new RxPermissions(this);
rxPermissions.request(Manifest.permission.CAMERA)
.subscribe(granted -> {
if (granted) {
startCamera();
} else {
showPermissionDeniedMessage();
}
});
方案三:最佳实践清单
- 按需申请: 不要一次性申请所有权限。用户在第一次进入 App 时就看到一堆权限请求,会直接卸载。根据功能模块,在用户触发相关功能时再申请。
- 提供理由: 在申请前,弹出一个 dialog 告诉用户“为什么我们需要这个权限”。比如,“我们需要您的位置信息来为您展示附近的餐厅”。这能显著提高授权率。
- 优雅降级: 如果用户拒绝权限,不要崩溃,也不要强行再次申请。提供替代方案,比如“您拒绝了位置权限,可以在设置中手动开启,或者我们使用默认城市为您展示信息”。
- 测试各种情况: 在真机上测试:首次申请、用户拒绝、用户拒绝且勾选“不再询问”、权限被系统撤销(用户去设置里手动关闭权限)。
五、 总结:权限不是障碍,是信任的桥梁
最后,我想说,权限系统虽然给开发者带来了麻烦,但它的初衷是保护用户隐私。当你的 App 能够优雅地处理权限请求,甚至在用户拒绝时提供合理的替代方案,用户会对你的产品产生更多的信任。
记住,崩溃是因为我们没处理好“意外”情况。权限申请中的每一个 if-else,每一行 try-catch,都是我们在为用户的安全和体验负责。
希望这篇文章能帮你避开那些让人头疼的权限坑。如果还有问题,欢迎在评论区留言,我们一起讨论!毕竟,代码是人类最优雅的舞蹈,而权限管理,只是其中一步稍微复杂的舞步罢了。
