一、基本介绍
1、正常权限(安装时权限):
不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予该权限。
例如,设置时区的权限就是正常权限。如果应用声明其需要正常权限,系统会自动向应用授予该权限。
2、危险权限(运行时权限):
会授予应用访问用户机密数据的权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。
3、注意:
- 在 Android 5.1(API 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。(沿用之前的权限系统),也就是在程序安装的时候程序会将所有需要的权限全部列出来,其设计思路非常简单,就是用户如果认可你所申请的权限,那么就会安装你的程序,如果不认可你所申请的权限,则会拒绝安装。
- 从Android6.0版本开始才开始加入运行时权限,也就是说用户不需要在安装软件的时候一次性授权所有的权限,而是可以在软件的使用过程中再对某一项权限进行申请使用,这就是运行时权限。
- 在请求权限时,系统只告诉用户应用需要的权限组,而不告知具体权限。(如上图)
二、运行时权限(使用原生API)
1、步骤
-
首先要在清单文件中申请所需要的权限。
-
将硬件声明为可选(uses-feature)以及要确定硬件可用性
3. 按API级别申明权限
当目标sdk大于等于29的时候配置android.permission.WRITE_EXTERNAL_STORAGE
会出现下面的警报:
WRITE_EXTERNAL_STORAGE no longer provides write access when targeting Android 10+
这时候需要设置根据Api级别申明权限android:maxSdkVersion
,该属性表示设备搭载的系统版本高于 maxSdkVersion
时不需要特定权限。
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"
android:maxSdkVersion="28"
tools:ignore="ScopedStorage"/>
-
请求应用权限,其逻辑应该如下图所示
-
首先检查是否已经授予了某个权限,使用
ContextCompat.checkSelfPermission()
此方法会返回PERMISSION_GRANTED
或PERMISSION_DENIED
。如果ContextCompat.checkSelfPermission()
方法返回PERMISSION_DENIED
,请调用shouldShowRequestPermissionRationale()
。如果此方法返回true
,请向用户显示指导界面,在此界面中说明用户希望启用的功能为何需要特定权限。(返回true说明应用之前请求过此权限但用户拒绝了请求,此方法将返回 true),之后再调用requestPermissions
函数进行权限的请求,这个函数会引起onRequestPermissionsResult
的回调。private void callPhone(){ if (ActivityCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) { // 应用没有授予拨打电话权限,请求权限 requestPhoneCallPermission(); } else { // 应用被授予拨打电话权限 PackageManager.PERMISSION_GRANTED makeCall(); } }
private void requestPhoneCallPermission() { // 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)){ // 向用户详细解释申请该权限的原因 new AlertDialog.Builder(this) .setCancelable(false) .setMessage("拨打电话需要使用电话权限,如果不授予权限会导致该功能无法正常使用") .setPositiveButton("好的", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { ActivityCompat.requestPermissions( MainActivity.this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALLPHONE//注意这个REQUEST_CALLPHONE会传给回调函数的requestCode ); } }) .setNegativeButton("拒绝", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); } }) .show(); } else { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, REQUEST_CALLPHONE); } }
下面是回调函数:
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { if (requestCode == REQUEST_CALLPHONE) { if (grantResults.length == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { // 授予权限,拨打电话 makeCall(); } else { Toast.makeText(this, "请求权限被拒绝", Toast.LENGTH_SHORT).show(); } } else { super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
2、结果展示
当需要打电话的时候第一次请求:
三、运行时权限(使用PermissionsDispatcher)
该工具直接使用注解来自动帮我们生成权限请求代码,使用时需要在项目中添加如下两个注解:
implementation "com.github.permissions-dispatcher:permissionsdispatcher:4.8.0"
annotationProcessor "com.github.permissions-dispatcher:permissionsdispatcher-processor:4.8.0"
注解 | 是否必须 | 作用 |
---|---|---|
@RuntimePermissions | 是 | 这是必须使用的注解,标记Activity/Fragment,则注解解释器会生成对应类的代码 |
@NeedsPermission | 是 | 标记需要请求使用权限的方法,也就是说你获取了相应的权限之后就会执行这个方法,可以在括号里面加一个权限或者多个权限。 |
@OnShowRationale | 否 | 对应之前的shouldShowRequestPermissionRationale(),当应用之前请求过此权限但用户拒绝了请求,再次请求时调用,告知用户为什么必须要授权这个权限,注解括号里面有参数,传入想要申请的权限,而且这个方法还要传入一个PermissionRequest对象,这个对象有两种方法:proceed()让权限请求继续,cancel()让请求中断。也就是说,这个方法会拦截你发出的请求,这个方法用于告诉用户你接下来申请的权限是干嘛的,说服用户给你权限。 |
@OnPermissionDenied | 否 | 当请求权限遭拒绝时调用,可以告知用户已经拒绝该权限 |
@OnNeverAskAgain | 否 | 当用户勾选不再提示,并拒绝权限时,再次请求时调用(如上图中禁止之后不再询问 按钮)也就是说,我们可以在这个方法做申请权限失败并选择不再询问之后的处理。例如,可以告诉作者想开启权限的就从手机设置里面开启。 |
注意:上面这些注解的方法都不能是private,原因看下面
使用PermissionsDispatcher除了要实现注解之外,还要重写Activity的onRequestPermissionsResult()方法,在里面让一个PermissionsDispatcher执行回调。这个PermissionsDispatcher是什么来的呢?
原来只要我们实现了@RuntimePermissions和@NeedsPermission这两个必须的注解之后,再build一次project之后,编译器就会在在app\build\intermediates\classes\debug目录下与被注解的Activity同一个包下生成一个辅助类,名称为 “被注解的Activity的名称+PermissionsDispatcher” 的辅助类,用来调用被注解的Activity的方法(就是因为这个所以被注解的方法不能private,private方法的作用域不在其他的类)。所以,第一次用的话,要注解好之后,build一次,下面的方法里面的PermissionsDispatcherActivityPermissionsDispatcher
才不会令AS报红。
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
PermissionsDispatcherActivityPermissionsDispatcher.onRequestPermissionsResult(this, requestCode, grantResults);
}
注意代码中的onclick方法PermissionsDispatcherActivityPermissionsDispatcher
的takePhotoWithPermissionCheck
方法在第一次编译完成之后会自动生成。
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
PermissionsDispatcherActivityPermissionsDispatcher.takePhotoWithPermissionCheck(PermissionsDispatcherActivity.this);
}
});
上面的具体代码链接:
https://github.com/Haoocker/Android_Learn/tree/master/RuntimePermissionDemo
参考阅读
- https://developer.android.com/guide/topics/permissions/overview?hl=zh-cn
- https://www.jianshu.com/p/d6b3e16cc1d9
- https://github.com/permissions-dispatcher/PermissionsDispatcher
- https://blog.csdn.net/s13383754499/article/details/79034758