Android Framework权限篇一之RuntimePermission全体流程
Android Framework权限篇二之RuntimePermission数据结构解析
Android Framework权限篇三之后台定位权限源码分析
Android Framework权限篇四之AppOps机制
Android Framework权限篇五之实现敏感权限行为提示
概述
权限的作用是维护Android用戶的隐私。
假如设备搭载的是Android 6.0(API 等级 23)或更高版别,而且运用的targetSdkVersion 是23或更高版别,用戶在装置时不会收到任何运用权限的通知。您的运用必须在运行时恳求用戶颁发风险权限。当运用恳求权限时,用戶会看到一个系统对话框,告知用戶运用正在测验拜访的权限组。该对话框包括回绝和答应按钮。假如用戶选中不再问询复选框并点按回绝,当您以后测验恳求相同权限时,系统不会再提示用戶。
假如设备搭载的是Android5.1.1(API等级22)或更低版别,或者运用在任何版别的Android上运行时其targetSdkVersion 是22或更低版别,系统将在装置时主动恳求用戶向运用颁发一切风险权限
权限维护等级
权限的分类总共有如下几种,先有个总览,本篇文章后边是打开讨论dangerous类型即运行时权限的全体流程。
等级 | 描述 |
---|---|
normal | 假如运用在清单中声明需求一般权限,系统会在装置时主动向运用颁发该权限。系统不会提示用户颁发一般权限,用户也无法撤消这些权限 |
dangerous | 为了运用风险权限,运用必须在运行时提示用户颁发权限 |
signature | 在装置时颁发这些运用权限,但仅会在测验运用某权限的运用签名证书为定义该权限的同一证书时才会颁发(即相同签名) |
signatureOrSystem signature|privileged | 同signature权限;假如没有运用相同签名,在/system/priv-app目录下的app也能够获得该权限 google文档 |
这儿做Android Framework权限开发的经常会遇到系统app的搭档过来问权限问题,我这个app是在/system/app目录下的,而且需求的这个权限是signature等级的;为什么在AndroidManifest声明晰之后没有默认颁发?能够结合下图总结看下;
运行时权限恳求
这儿开始看下运行时权限的全体流程;正常运用的权限恳求进程如下,先看下运用是怎么恳求运行时权限,再从运用侧到Framework侧看下去; 更多运用权限恳求细节能够参阅这儿
private fun requestPermmission() {
// 判别是否需求运行时恳求权限
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
// 判别是否需求对用户进行提示,用户点击过回绝&&没有勾选不再提示时,即回绝一次时进行提示
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 给用户予以权限解说, 对于现已回绝过的状况,先提示恳求理由,再经过点击弹出的Dialog进行恳求
showDialog("需求翻开电话权限直接进行拨打电话,便利您的操作")
} else {
// 无需阐明理由的状况下,直接进行恳求。如:1.第一次运用该功用(第一次恳求权限),2.用户回绝权限并勾选了不再提示,3.已授权 这3种状况shouldShowRequestPermissionRationale()回来false
ActivityCompat.requestPermissions(
this, new String[]{Manifest.permission. CALL_PHONE,}, 1);
}
} else {
// 具有权限直接进行功用调用
callPhone();
}
}
/**
* 权限恳求回调
*/
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
// 依据requestCode判别是那个权限恳求的回调
if (requestCode == REQUEST_PERMISSION_CODE_CALL_PHONE) {
// 判别用户是否赞同了恳求
if (grantResults.size > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
callPhone()
} else {
// 未赞同的状况
if (ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.CALL_PHONE)) {
// 判别是回绝一次的状况,则相同先是弹Dialog提示用户,再重新恳求权限
showDialog("需求翻开电话权限直接进行拨打电话,便利您的操作")
} else {
// 用户勾选了不再提示,引导用户进入设置界面进行开启权限
// 假如是用户点击了回绝(回绝而且设置不再问询)则requestPermissions完会直接回调onRequestPermissionsResult回来成果PERMISSION_DENIED, 走到这儿的逻辑去提示用户去设置翻开权限
showDialog("需求跳转去到设置翻开权限");
}
}
} else {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
}
}
}
查看权限流程
从上面的ContextCompat.checkSelfPermission(context, Manifest.permission.CALL_PHONE)
看下源码的查看流程
这儿在源码环境下加断点的形式能够看到调用堆栈,如上图最终是调用到PermissionManagerService.checkUidPermission()
一同看下这个办法里的详细逻辑,这儿先对PersmissionsState
数据类有个形象,权限授权耐久化是经过此类进行操作,先过下流程,后边再打开。
...
if (pkg != null) {
...
//1.这儿获取pkg对应的PermissionsState
final PermissionsState permissionsState =
((PackageSetting) pkg.mExtras).getPermissionsState();
//2.再判别该权限是否授权
if (permissionsState.hasPermission(permName, userId)) {
if (isUidInstantApp) {
if (mSettings.isPermissionInstant(permName)) {
return PackageManager.PERMISSION_GRANTED;
}
} else {
//正常流程假如权限授权了是走到这儿进行回来
return PackageManager.PERMISSION_GRANTED;
}
}
if (isImpliedPermissionGranted(permissionsState, permName, userId)) {
return PackageManager.PERMISSION_GRANTED;
}
...
//3.最终以上条件都没满意则回来回绝
return PackageManager.PERMISSION_DENIED;
恳求权限进程
接着从之前的ActivityCompat.requestPermissions(this, new String[]{Manifest.permission. CALL_PHONE,}, 1);
看下源码的恳求进程,这儿实际是发动一个权限恳求弹窗页面。
public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
if (requestCode < 0) {
throw new IllegalArgumentException("requestCode should be >= 0");
}
if (mHasCurrentPermissionsRequest) {
Log.w(TAG, "Can request only one set of permissions at a time");
// Dispatch the callback with empty arrays which means a cancellation.
onRequestPermissionsResult(requestCode, new String[0], new int[0]);
return;
}
//1. 生成指定action的intent,该intent会发动权限办理的权限弹窗界面
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
//2. 经过startActivityForResult发动,成果会回调到onRequestPermissionsResult()
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
权限授权流程
运用恳求权限弹窗后,假如点击答应或者回绝,此刻底下源码会发生什么,一同往下看
假如权限弹窗点击答应,最终会走到PermissionManagerService.grantRuntimePermission()
,这儿我们看下中心代码,能够看到这儿仍是经过PermissionsState.grantRuntimePermission()
进行授权,详细关于权限内存状况和耐久化存储在后续文章讲解,这儿先有个形象。
private void grantRuntimePermission(String permName, String packageName, boolean overridePolicy,int callingUid, final int userId, PermissionCallback callback) {
...
final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
...
//1. 以上省掉了许多判别条件,这儿会先获取对应包名的权限状况PermissionsState和其flag
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(permName, userId);
...
//2. 这儿对权限进行授权并回来成果
final int result = permissionsState.grantRuntimePermission(bp, userId);
...
//3. 权限授权成功后,除了更新内存中的状况值,还会耐久化到本地xml
if (callback != null) {
callback.onPermissionGranted(uid, userId);
}
...
}
相同看下吊销权限流程,对应权限弹窗上的回绝按钮,这儿能够看到是经过PermissionsState.revokeRuntimePermission()
进行吊销授权
private void revokeRuntimePermission(String permName, String packageName,boolean overridePolicy, int userId, PermissionCallback callback) {
final PackageParser.Package pkg = mPackageManagerInt.getPackage(packageName);
...
//1. 以上省掉了许多判别条件,这儿会先获取对应包名的权限状况PermissionsState和其flag
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final int flags = permissionsState.getPermissionFlags(permName, userId);
...
//2. 吊销权限并判别回来成果
if (permissionsState.revokeRuntimePermission(bp, userId) ==
PERMISSION_OPERATION_FAILURE) {
return;
}
...
//3. 吊销成功后,除了更新内存中的状况值,还会耐久化到本地xml
if (callback != null) {
callback.onPermissionRevoked(pkg.applicationInfo.uid, userId);
}
...
}
flag更新
上述权限授权流程的123执行完之后,从4开始会更新flag,这个flag的作用是什么?这儿先说下结论,这个flag对运用户选择权限弹窗的答应、回绝并勾选了不再提示、回绝并未勾选不再提示。下一次权限弹窗时会经过此标志位判别是否不再弹窗。
//答应的标志位为0
packageManager.updatePermissionFlags(permission,
packageName,
PackageManager.FLAG_PERMISSION_USER_FIXED
| PackageManager.FLAG_PERMISSION_USER_SET,
0, userHandle);
//回绝并未勾选不再提示的标志位为PackageManager.FLAG_PERMISSION_USER_SET
packageManager.updatePermissionFlags(permission,
packageName,
PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_USER_FIXED,
PackageManager.FLAG_PERMISSION_USER_SET,
userHandle);
//回绝并勾选了不再提示的标志位为PackageManager.FLAG_PERMISSION_USER_FIXED
packageManager.updatePermissionFlags(permission,
packageName,
PackageManager.FLAG_PERMISSION_USER_SET
| PackageManager.FLAG_PERMISSION_USER_FIXED,
PackageManager. FLAG_PERMISSION_USER_FIXED,
userHandle);
接下来看下详细办法调用下去是怎么作用的,这儿关于flag更新的当地单独画了个图看下比较好理解,flagMask参数对应前面传的PackageManager.FLAG_PERMISSION_USER_SET | PackageManager.FLAG_PERMISSION_USER_FIXED
,而PackageManager.FLAG_PERMISSION_USER_SET
对应的值是1, PackageManager.FLAG_PERMISSION_USER_FIXED
对应的值是2;两个值二进制相或是11。
private void updatePermissionFlags(String permName, String packageName, int flagMask,int flagValues, int callingUid, int userId, boolean overridePolicy,
PermissionCallback callback) {
...
//1.获取对应包名的权限状况PermissionsState
final PackageSetting ps = (PackageSetting) pkg.mExtras;
final PermissionsState permissionsState = ps.getPermissionsState();
final boolean hadState =
permissionsState.getRuntimePermissionState(permName, userId) != null;
//2.更新flag
final boolean permissionUpdated =
permissionsState.updatePermissionFlags(bp, userId, flagMask, flagValues);
...
}
PermissionsState.java下的PermissionData内部类
//真实更新flag的当地
public boolean updateFlags(int userId, int flagMask, int flagValues) {
//flagMask与flagValues相与后为新的flag
final int newFlags = flagValues & flagMask;
PermissionState userState = mUserStates.get(userId);
if (userState != null) {
final int oldFlags = userState.mFlags;
//userState.mFlags & ~flagMask: 将旧的falg上关于flagMask的标志位清0
//| newFlags再与上新的flag,即在关于flagMask上的标志位上赋值
userState.mFlags = (userState.mFlags & ~flagMask) | newFlags;
if (userState.isDefault()) {
mUserStates.remove(userId);
}
return userState.mFlags != oldFlags;
} else if (newFlags != 0) {
userState = new PermissionState(mPerm.getName());
//假如是新的状况则直接将newFlags赋值即可
userState.mFlags = newFlags;
mUserStates.put(userId, userState);
return true;
}
return false;
}
结语
到这儿,权限的等级、运行时权限的运用恳求,源码查看权限流程、授权和吊销权限流程和flag更新流程都现已讲完了;下一篇我们继续讲下运用装置时权限授权流程、以及运行时权限的数据结构