运行时权限
https://developer.android.google.cn/guide/topics/permissions/overview#runtime
运行时权限不是新特性,但其具体流程还未细看过,这里大致走读下相关流程梳理下相关逻辑
要了解运行时权限最好先看下上面开发者网站上的介绍
在早期的时候,一般应用需要什么权限,只需要在AndroidManifest.xml中进行声明就行了,这里以读取外部存储权限为例:
以前的外部存储读取权限声明:
image-20210915221452179.png
但后来该权限的级别变了
image-20210915221539087.png
明显的,其protectionLevel从normal变为了dangerous,一般权限的级别为dangerous的就是运行时权限,对于运行时权限,若需要该权限,一般的应用只在AndroidManifest.xml中声明需要该权限在安装后是默认不会有该权限的,还需要代码申请权限(requestPermissions),然后会有弹框,用户选择允许后才会拥有该权限:
image-20210915222930197.png
而如果拒绝后,再次调用代码申请权限,则会再次弹框:
image-20210915223410814.png
这时,如果选择允许,则应用拥有该权限,如果选择“拒绝,不要再询问”,则应用无该权限,且再次调用代码申请该权限时不会弹框
开发者网站上推荐的申请运行时权限的流程(https://developer.android.google.cn/training/permissions/requesting)如下:
-
在应用的清单文件中,声明应用可能需要请求的权限。
-
设计应用的用户体验,使应用中的特定操作与特定运行时权限相关联。应当让用户知道哪些操作可能会要求他们向您的应用授予访问其私人数据的权限。
-
等待用户调用应用中需要访问特定用户私人数据的任务或操作。届时,您的应用可以请求访问相应数据所需的运行时权限。
-
检查用户是否已授予应用所需的运行时权限。如果已授权,那么您的应用可以访问用户私人数据。如果没有,请继续执行下一步。
每次执行需要该权限的操作时,您都必须检查自己是否具有该权限。
-
检查您的应用是否应向用户显示理由,说明您的应用需要用户授予特定运行时权限的原因。如果系统确定您的应用不应显示理由,请继续直接执行下一步,无需显示界面元素。
不过,如果系统确定您的应用应该显示一个理由,请在界面元素中向用户显示理由,明确说明您的应用试图访问哪些数据,以及应用获得运行时权限后可为用户提供哪些好处。用户确认理由后,请继续执行下一步。
-
检查用户的响应,他们可能会选择同意或拒绝授予运行时权限。
-
如果用户向您的应用授予权限,您就可以访问用户私人数据。如果用户拒绝授予该权限,请适当降低应用体验,使应用在未获得受该权限保护的信息时也能向用户提供功能。
其中申请运行时权限可仿照如下代码
if (ContextCompat.checkSelfPermission(
CONTEXT, Manifest.permission.REQUESTED_PERMISSION) ==
PackageManager.PERMISSION_GRANTED) {
// You can use the API that requires the permission.
performAction(...);
} else if (shouldShowRequestPermissionRationale(...)) {
// In an educational UI, explain to the user why your app requires this
// permission for a specific feature to behave as expected. In this UI,
// include a "cancel" or "no thanks" button that allows the user to
// continue using your app without granting the permission.
showInContextUI(...);
} else {
// You can directly ask for the permission.
requestPermissions(CONTEXT,
new String[] { Manifest.permission.REQUESTED_PERMISSION },
REQUEST_CODE);
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions,
int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_CODE:
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0 &&
grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission is granted. Continue the action or workflow
// in your app.
} else {
// Explain to the user that the feature is unavailable because
// the features requires a permission that the user has denied.
// At the same time, respect the user's decision. Don't link to
// system settings in an effort to convince the user to change
// their decision.
}
return;
}
// Other 'case' lines to check for other
// permissions this app might request.
}
}
也可参考下代码工程samples中的RuntimePermissions
其中主要代码有
1、checkSelfPermission鉴权
2、shouldShowRequestPermissionRationale是否应该显示申请权限的理由(结合示例代码和逻辑,旨在其返回true时应用能显示具体的权限的用处)
3、requestPermissions申请权限
1、Activity.shouldShowRequestPermissionRationale
image-20210915223744044.png显然,Activity的shouldShowRequestPermissionRationale方法调用的是packageManager的,这里getPackageManager获取的是ApplicationPackageManager的对象,查看ApplicationPackageManager的shouldShowRequestPermissionRationale方法:
image-20210915224030937.png
显然这里会调用到PackageManagerService的shouldShowRequestPermissionRationale方法
image-20210915224401907.png
image-20210915224517757.png
这里既是该方法的主要逻辑部分,其主要部分有:
1、如果已经有对应权限,则shouldShowRequestPermissionRationale返回false
2、如果对应permission的flags包含fixedFlags,则返回false(这里对于一般应用如在申请权限时用户选择了“拒绝,不要再询问”后,则属于该场景)
3、如果对应permission的flags包含FLAG_PERMISSION_USER_SET则返回true(这里一般对于应用在申请权限时用户选择了拒绝(不是“拒绝,不要再询问”)后,再次申请权限时的场景),否则返回flase
1.1、申请权限用户选择与flags变化
这里仍以外部存储读取权限为例(android.permission.READ_EXTERNAL_STORAGE)(这里使用adb shell dumpsys package命令获取对应应用的信息,如果手机root了,可以直接查看/data/system/users/0/runtime-permissions.xml文件进行查看):
1、应用刚安装时:
image-20210915225605621.png
2、代码申请权限后弹框,用户选择拒绝后(在之后如再次申请权限弹框,仍选择拒绝后,其flags仍是下面的不变)(显然,相对于刚安装时,多了USER_SET的flag):
image-20210915225826560.png
3、代码权限申请后弹框,用户选择“拒绝,不要再询问”后(显然,相对于刚安装时,多了USER_FIXED的flag):
image-20210915230209348.png
4、如果在有弹框的时候,用户选择允许后(granted变为了true):
image-20210915230742936.png
2、Activity.requestPermissions
image-20210915232730639.png从上述代码可以看到,其实Activity.requestPermissions的主要逻辑即是启动了一个Activity,这里PackageManager.buildRequestPermissionsIntent逻辑如下
image-20210915232840189.png
其实这里启动的是PermissionController应用的GrantPermissionsActivity
image-20210915233326976.png
这里简要摘取些代码片段介绍下大致流程
在GrantPermissionsActivity的onCreate方法中设置显示布局:
image-20210915233528665.png
其中mViewHandler是GrantPermissionsViewHandlerImpl的对象
image-20210915233619193.png
查看其createView方法:
image-20210915233752223.png
查看下其点击允许按钮(mAllowButton)的大致逻辑,查看GrantPermissionsViewHandlerImpl中onClick逻辑:
image-20210915233918597.png
显然,在点击允许的时候,其主要是回调GrantPermissionsActivity的onPermissionGrantResult方法:
image-20210915234116407.png
继续查看GrantPermissionsActivity的onPermissionGrantResultSingleState方法
image-20210915234210324.png
这里选择允许时,主要逻辑是调用上面groupState.mGroup.grantRuntimePermissions方法,其中mGroup是AppPermissionGroup对象
在AppPermissionGroup的grantRuntimePermissions中会先对对应权限持有的permission对象进行对应的赋值,然后其会调用persistChanges方法进行权限的处理,如赋予权限、更新flags等
image-20210915234647841.png
另外在用户选择了“拒绝,不要再询问”后,再次调用requestPermissions方法时,仍是去启动上面GrantPermissionsActivity,只是在其onCreate方法中会调用setResultAndFinish方法退出,所以看起来没有反应
image-20210915234927744.png