权限的分类
Android 将权限分为不同的类型,包括安装时权限、运行时权限和特殊权限。每种权限类型都指明了当系统授予应用该权限后,应用可以访问的受限数据范围以及应用可以执行的受限操作范围。
安装时权限
Android在应用安装时展示的权限,这类权限对系统数据操作较为安全也不会对用户隐私造成侵犯,用户如果同意安装,android便会授予先前声明的所有权限。这类权限又可以细分为普通权限和签名权限。
普通权限
此类权限允许访问超出应用沙盒的数据和执行超出应用沙盒的操作(android为每个应用都设置了其私有存储位置,应用可以随意访问但其他应用不能随意访问的区域称之为该应用的沙盒领域,其它应用需要申请相应的权限才可以访问这个“沙盒”里面的数据,换言之也是如此)。但是,这些数据和操作对用户隐私及对其他应用的操作带来的风险非常小,系统会为普通权限分配“normal”保护级别。
签名权限
android允许两个签名一致的应用通过sharedUserID的方式分配同一个uid进行管理,这两个应用可以属于不同进程也可以是同一进程。如果两个应用签名一致,其中一个应用之前已经安装好了(已获得声明的所有普通权限),那么另一个应用如果在manifest文件中声明了这些权限,那么android将会直接给予这些权限,系统会为签名权限分配“signature”保护级别。
运行时权限
又称危险权限。这类权限会访问用户私有数据和进行对系统和其他应用而言危险的操作(访问或者修改数据)。这类权限会在应用需要时动态申请,届时应用会弹出权限请求对话框来让用户选择是否给予权限,如下图所示,系统会为运行时权限分配“dangerous”保护级别。
特殊权限
特殊权限比较少见,Google认为这类权限比危险权限还要敏感,因此需要让用户到专门的设置页面去手动对某一个应用程序授权。
常见的特殊权限:
- 悬浮窗权限 SYSTEM_ALERT_WINDOW
//在AndroidManifest.xml中加入下面的代码
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
//跳转到指定的页面
val intent = Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION)
startActivity(intent)
//使用下面的Api判断权限是否已经授予
boolean b = Settings.canDrawOverlays(context)
- 修改设置权限 WRITE_SETTINGS
//AndroidManifest.xml中加入下面的代码
<uses-permission android:name="android.permission.WRITE_SETTINGS" />
//跳转到指定的页面
val intent = Intent(Settings.ACTION_MANAGE_WRITE_SETTINGS)
startActivity(intent)
//使用下面的Api判断权限是否已经授予
boolean b = Settings.System.canWrite(context)
- 管理外部储存 MANAGE_EXTERNAL_STORAGE
//AndroidManifest.xml中加入下面的代码
<uses-permission android:name="android.permission.MANAGE_EXTERNAL_STORAGE" />
//跳转到指定的页面
val intent = Intent(Settings.ACTION_MANAGE_ALL_FILES_ACCESS_PERMISSION)
startActivity(intent)
//使用下面的Api判断权限是否已经授予
boolean b = Environment.isExternalStorageManager()
声明权限
一般权限声明
这里一般权限指的是,所有搭载了android系统的设备都具有的权限,为与下文提到的硬件权限做出区分。这类权限声明在manifest文件中完成,且是所有类别权限获得的前提(安装时权限和运行时权限等)。所以安装时权限秩序在manifest文件中声明即可获得。
<manifest ...>
<uses-permission android:name="android.permission.CAMERA"/>
<application ...>
...
</application>
</manifest>
硬件权限声明
某些权限(例如 CAMERA)可让您的应用访问只有部分 Android 设备具有的硬件组件。如果您的应用声明了这类硬件相关权限,请考虑您的应用在没有该硬件的设备上会不会完全无法运行。在大多数情况下,硬件是可选的,因此最好在<uses-feature> 声明中将 android:required 设置为 false,从而将硬件声明为可选项。<user-permission>和<uses-feature>常常搭配使用,如果在<uses-feature>中将required声明为true,那么谷歌将组织在没有摄像头的设备上安装您的应用。
<manifest ...>
<uses-permission android:name="android.permission.CAMERA"/>
<application>
...
</application>
<uses-feature android:name="android.hardware.camera"
android:required="false" />
<manifest>
如果,在<uses-feature>中将required声明为false,表示应用可以在没有摄像头支持的情况下运行,但是某些功能可能不可用,所以会在代码中加入对硬件设备是否可用的检测,这样您就可以有针对性的停止对某些功能的使用了。
if (getApplicationContext().getPackageManager().hasSystemFeature(
PackageManager.FEATURE_CAMERA_FRONT)) {
// TODO
} else {
// TODO
}
运行时权限声明
需要运行时声明的权限属于危险权限,因此比安装时权限只需要在manifest文件中声明麻烦。其不仅需要咋manifest中声明,还需要在代码中动态申请。动态申请的方式有:
- 系统替我们管理权限请求的代码(简化逻辑,推荐使用,需要更高版本的库支持)
- 自行管理请求代码
- 系统管理权限请求
依赖添加
//build.gradle 文件中添加以下库的依赖项:
//androidx.activity,1.2.0 或更高版本。
//androidx.fragment,1.3.0 或更高版本。
可使用的类
RequestPermission
RequestMultiplePermission
处理权限授予结果相应
//保存为成员变量已便于在启动时机到来时调用
private ActivityResultLauncher<String> requestPermissionLauncher =
registerForActivityResult(new RequestPermission(), isGranted -> {
if (isGranted) {
// 权限已授予
} else {
}
});
检查权限并根据需要向用户请求权限的建议流程:
if (ContextCompat.checkSelfPermission(CONTEXT, Manifest.permission.REQUESTED_PERMISSION)==PackageManager.PERMISSION_GRANTED) {
//已拥有该权限
} else if (shouldShowRequestPermissionRationale(...)) {
// shouldShowRequestPermissionRationale()返回true,说明之前用户绝拒绝了授权,并且勾选了不再提示,这个时候应用可以提示用户去设置中开启权限。
//下面可进行提示,比如Toast之类的提示消息
Toast.makeText(CONTEXT, "请到设置-应用管理中开启此应用的读写权限",Toast.LENGTH_SHORT).show();
} else {
// 首次请求该权限,或者用户在上次权限请求中选择了取消或者点击旁白强制关闭了权限请求对话框
requestPermissionLauncher.launch(
Manifest.permission.REQUESTED_PERMISSION);
}
- 自行管理权限请求
请求权限(使用requestPermission()方法请求权限)
if (ContextCompat.checkSelfPermission(
CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// 已拥有该权限
} else if (shouldShowRequestPermissionRationale(...)) {
// 与前文一致
} else {
// 直接请求权限
requestPermissions(CONTEXT,
new String[] { Manifest.permission.REQUESTED_PERMISSION },
REQUEST_CODE);
}
重写onRequestPermissionsResult()方法来处理请求结果
//permissions:权限申请的字符串数组
//grantResults:用户的权限授予结果
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
//用户取消授权时,grantResults中的对应各项权限的申请结果也为拒绝
//注意:权限Android6.0以后,授予的权限可以被用户再次取消,所以应该在每次进行敏感操作前都应该进行权限检查
Log.d(TAG, "onRequestPermissionsResult: permissions length is " + permissions.length);
Log.d(TAG, "onRequestPermissionsResult: grantResults length is " + grantResults.length);
for (int i = 0; i < permissions.length; i++) {
Log.d(TAG, "onRequestPermissionsResult: permission[" + i + "]" + " is " + permissions[i]);
Log.d(TAG, "onRequestPermissionsResult: grantResults[" + i + "]" + " is " + grantResults[i]);
}
break;
// 其它请求的权限(一个请求码对应一组权限)
}
}