Android权限模型:从分级到实战
权限,是Android安全的第一道防线。
我刚开始做Android开发时,总觉得权限就是个弹窗,用户点同意就完事了。直到有一次,我负责的App因为权限声明不当,被Google Play直接下架。嗯,从那以后,我才真正开始认真研究这套权限体系。
权限分级:Normal、Dangerous、Signature
Android把权限分成了三个等级。说白了,就是根据风险大小,决定系统怎么处理。
| 等级 | 特点 | 典型权限 | 安装时行为 |
|---|---|---|---|
| Normal | 低风险,不涉及用户隐私 | INTERNET、ACCESS_NETWORK_STATE | 自动授予,无需用户确认 |
| Dangerous | 高风险,涉及用户隐私 | CAMERA、READ_CONTACTS、ACCESS_FINE_LOCATION | 需要运行时弹窗申请 |
| Signature | 系统级,仅同签名应用可获取 | BIND_ACCESSIBILITY_SERVICE、INSTALL_PACKAGES | 系统自动授予,但需同签名 |
你可能会问:为什么要有Signature级别?
我举个例子。我在做系统级应用开发时,需要调用一个接口来管理WiFi热点。这个接口被保护在Signature权限下。普通App根本拿不到,只有系统签名或者和我同签名的App才能调用。这就防止了恶意应用滥用系统能力。
关键点:Normal权限在Android 6.0之后依然是自动授予的。很多新手以为所有权限都要弹窗申请,其实不是。只有Dangerous级别的才需要运行时处理。
运行时权限申请的最佳实践
运行时权限,是Android 6.0引入的机制。它的核心思想是:在需要的时候才申请,而不是安装时一股脑全要。
我见过太多App一启动就弹五六个权限申请框。用户烦不烦?肯定烦。而且Google Play现在对这种做法会直接打回。
最佳实践一:按需申请
不要提前申请权限。等用户真正要用到某个功能时,再弹窗。
// 错误示范:一启动就申请所有权限
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA,
Manifest.permission.READ_CONTACTS,
Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_CODE_ALL);
}
// 正确做法:用到相机时才申请
public void openCamera() {
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CAMERA)
!= PackageManager.PERMISSION_GRANTED) {
// 先解释为什么需要这个权限
if (ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
showRationaleDialog("我们需要相机权限来拍照");
}
ActivityCompat.requestPermissions(this,
new String[]{Manifest.permission.CAMERA},
REQUEST_CODE_CAMERA);
} else {
// 权限已授予,直接打开相机
startCamera();
}
}
我的习惯:每次申请权限前,先调用shouldShowRequestPermissionRationale()。如果返回true,说明用户之前拒绝过。这时候我会弹一个解释对话框,告诉用户为什么需要这个权限。用户体验会好很多。
最佳实践二:处理拒绝和"不再询问"
用户可能会拒绝权限,甚至勾选"不再询问"。这时候怎么办?
我曾经遇到一个坑:用户拒绝了位置权限,然后App直接崩溃了。原因是代码里没做空指针判断。从那以后,我养成了一个习惯——所有依赖权限的功能,都要做降级处理。
@Override
public void onRequestPermissionsResult(int requestCode,
@NonNull String[] permissions,
@NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == REQUEST_CODE_CAMERA) {
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// 权限授予,继续
startCamera();
} else {
// 权限被拒绝
if (!ActivityCompat.shouldShowRequestPermissionRationale(this,
Manifest.permission.CAMERA)) {
// 用户勾选了"不再询问"
showSettingsDialog("请在设置中手动开启相机权限");
} else {
// 用户只是这次拒绝
showToast("需要相机权限才能拍照");
}
}
}
}
注意:如果用户勾选了"不再询问",你再次调用requestPermissions()是不会弹窗的。系统会直接回调拒绝。这时候只能引导用户去设置页面手动开启。
最佳实践三:权限组的概念
Android把权限分成了组。比如STORAGE组包含READ_EXTERNAL_STORAGE和WRITE_EXTERNAL_STORAGE。如果你申请了读权限,用户同意了,那么写权限也会自动被授予。
但要注意:不同Android版本的分组可能不一样。我建议还是每个权限单独申请,不要依赖分组行为。
自定义权限的声明与使用
有时候,你的App需要暴露一些接口给其他App调用。这时候就需要自定义权限了。
声明自定义权限
在AndroidManifest.xml中声明:
<permission
android:name="com.example.myapp.permission.MY_CUSTOM_PERMISSION"
android:label="我的自定义权限"
android:description="允许访问我的App的某些功能"
android:protectionLevel="dangerous" />
protectionLevel可以设置为normal、dangerous、signature等。我一般用dangerous,因为这样其他App调用时,系统会弹窗让用户确认。
使用自定义权限保护组件
声明好权限后,就可以用它来保护你的Activity、Service、ContentProvider等组件了。
<activity
android:name=".ProtectedActivity"
android:permission="com.example.myapp.permission.MY_CUSTOM_PERMISSION">
<intent-filter>
<action android:name="com.example.myapp.action.PROTECTED_ACTION" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
其他App想启动这个Activity,必须先声明并使用这个权限:
<uses-permission android:name="com.example.myapp.permission.MY_CUSTOM_PERMISSION" />
避坑指南:我曾经犯过一个错误——自定义权限的protectionLevel设置成了signature,结果第三方App死活调不了我的接口。后来才发现,只有和我同签名的App才能获取这个权限。所以,如果你希望其他App也能调用,用dangerous级别。
知识体系总览
下面这张图,是我梳理的Android权限模型核心脉络。你可以对照着看,思路会更清晰。
这张图把权限分级、运行时申请、自定义权限串在了一起。你可以看到,Normal和Signature权限基本不需要开发者操心运行时逻辑,而Dangerous权限才是我们日常开发中需要重点处理的。
至于自定义权限,它更像是一种"协议"——你声明,别人遵守。我在做SDK开发时经常用到,用来保护一些内部接口不被随意调用。
权限这块,说难不难,说简单也不简单。关键是养成好习惯:按需申请、优雅降级、合理声明。做到这三点,你的App在权限安全方面基本就及格了。