前言:
在Android 6.0 之前权限管理存在一些弊端:
Android 6.0 及更高版本中的 Android 应用权限模式旨在使权限更易于用户理解、更实用、更安全。该模式将需要危险权限的 Android 应用从安装时权限模式转移至运行时权限模式:
Runtime permission 向前兼容
权限的使用:
基本 Android 应用默认情况下未关联权限,这意味着它无法执行对用户体验或设备上任何数据产生不利影响的任何操作。要利用受保护的设备功能,必须在应用清单(AndroidManifest.xml)中包含一个或多个
例如,需要监控传入的短信的应用要指定:
...
权限级别:
如果您的应用在其清单中列出正常权限(即,不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出危险权限(即,可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出请求的方式取决于系统版本,而系统版本是应用的目标:
通常,权限失效会导致 SecurityException 被扔回应用。但不能保证每个地方都是这样。例如,sendBroadcast(Intent) 方法在数据传递到每个接收者时会检查权限,在方法调用返回后,即使权限失效,您也不会收到异常。但在几乎所有情况下,权限失效会记入系统日志。
Android 系统提供的权限请参阅 Manifest.permission。此外,任何应用都可定义并实施自己的权限,因此这不是所有可能权限的详尽列表。
可能在程序运行期间的多个位置实施特定权限:
权限自动调整:
随着时间的推移,平台中可能会加入新的限制,要想使用特定 API,您的应用可能必须请求之前不需要的权限。因为现有应用假设可随意获取这些 API 应用的访问权限,所以 Android 可能会将新的权限请求应用到应用清单,以免在新平台版本上中断应用。Android 将根据 targetSdkVersion 属性提供的值决定应用是否需要权限。如果该值低于在其中添加权限的版本,则 Android 会添加该权限。
例如,API 级别 4 中加入了 WRITE_EXTERNAL_STORAGE 权限,用以限制访问共享存储空间。如果您的 targetSdkVersion 为 3 或更低版本,则会向更新 Android 版本设备上的应用添加此权限。
为避免这种情况,并且删除您不需要的默认权限,请始终将 targetSdkVersion 更新至最高版本。可在 Build.VERSION_CODES 文档中查看各版本添加的权限。
权限等级分类:
这里说的等级分类其实是permission中的属性:
android:protectionLevel
被分为几个类别:Normal, Dangerous, Signature, SigatureOrSystem
signature|privileged
”,该类权限除了上述的 Signature Permission以外,还包括那些只赋予Android System Image内的应用的权限。Android并不建议app使用这类,因为Signature Permission已经能满足大部分的需求,不管这些app是否是build在System Image里。
特殊权限:
上面说明了permission 中属性android:protectionLevel 使用意义,其中存在一些特殊的权限,这些权限比较敏感,在使用的时候必须特殊处理。SYSTEM_ALERT_WINDOW
和WRITE_SETTINGS
就是此类比较敏感的权限。
例如 WRITE_SETTINGS:
官方解释如下:
Note: If the app targets API level 23 or higher, the app user must explicitly grant this
permission to the app through a permission management screen.
The app requests the user's approval by sending an intent with action ACTION_MANAGE_WRITE_SETTINGS.
The app can check whether it has this authorization by calling Settings.System.canWrite().
Dangerous Permission:
Android 6.0 及更高版本要求危险权限必须使用运行时权限模式。危险权限是具有更高风险的权限(例如READ_CALENDAR),此类权限允许寻求授权的应用访问用户私人数据或获取可对用户造成不利影响的设备控制权。要查看危险权限列表,请运行以下命令:
adb shell pm list permissions -g -d
Android 6.0 及更高版本不会更改常规权限的行为(包括常规权限、系统权限和签名权限在内的所有非危险权限)。常规权限是具有较低风险的权限(例如 SET_WALLPAPER),它允许请求授权的应用访问隔离的应用级功能,对其他应用、系统或用户的风险非常小。在 Android 5.1 及更低版本中,系统在安装应用时,自动向请求授权的应用授予常规权限,并且无需提示用户进行批准。
permission group:
所有危险的 Android 系统权限都属于权限组。如果设备运行的是 Android 6.0(API 级别 23),并且应用的 targetSdkVersion 是 23 或更高版本,则当用户请求危险权限时系统会发生以下行为:
任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。
如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在安装时要求用户授予权限。再次强调,系统只告诉用户应用需要的权限组,而不告知具体权限。(注意这一点,在国内CTA要求这些组权限必须要细分)
检查权限情况:
//检查某个 uid 和 pid是否有permission
//如果返回PackageManager#PERMISSION_GRANTED,说明该权限已经被allowed
//如果返回PackageManager#PERMISSION_DENIED,说明该权限不被allowed
public int checkPermission(String permission, int pid, int uid)
//同checkPermission,这里提供给当前进程调用
public int checkCallingPermission(String permission)
//同checkPermission,这里跟checkCallingPermission区别是,不需要判断调用者是否是当前进程
public int checkCallingOrSelfPermission(String permission)
//同checkPermission,主要是当返回不是PackageManager.PERMISSION_GRANTED会抛出SecurityException
public void enforcePermission(String permission, int pid, int uid, String message)
//同enforcePermission
public void enforceCallingPermission(String permission, String message)
//同enforcePermission
public void enforceCallingOrSelfPermission(String permission, String message)
详细的source code 可以看ContextImpl.java
需要注意的是,在调入PMS之前还有很多路要走:
public int checkPermission(String permission, int pid, int uid, IBinder callerToken) {
if (permission == null) { //传入的permission 不能为null
throw new IllegalArgumentException("permission is null");
}
try {
return ActivityManager.getService().checkPermissionWithToken(//详细看AMS
permission, pid, uid, callerToken);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
申请权限:
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 reqeust 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;
}
Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
mHasCurrentPermissionsRequest = true;
}
调用该函数,系统会调出一个对话框,提示用户是否要给予相应权限。用户允许或拒绝相应的权限后,app的onRequestPermissionsResult(int, String[], int[])会被调用,告诉app相应的permission是被授权或者拒绝。详细的代码可以看Activity.java,这里需要注意的是:
1、requestCode 是用来匹配的,但是必须是非负数
2、buildRequestPermissionsIntent
注意这里这个函数,这里就是在request permission时候需要弹出对话框的invoke 地方。
public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
if (ArrayUtils.isEmpty(permissions)) {
throw new IllegalArgumentException("permission cannot be null or empty");
}
Intent intent = new Intent(ACTION_REQUEST_PERMISSIONS); //注意这里的Action
intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions); //Extra传的是permission name
intent.setPackage(getPermissionControllerPackageName());//注意,系统直接规定了packageName
return intent;
}
这里build 出来的intent,系统会有地方接收,详细看PackageIntaller 中的GrantPermissionsActivity(详解点击这里):
3、onRequestPermissionResult 有 3 中方式触发,这里是一种,另外是通过:
private void dispatchRequestPermissionsResult(int requestCode, Intent data) {
mHasCurrentPermissionsRequest = false;
// If the package installer crashed we may have not data - best effort.
String[] permissions = (data != null) ? data.getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
final int[] grantResults = (data != null) ? data.getIntArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
onRequestPermissionsResult(requestCode, permissions, grantResults);
}
private void dispatchRequestPermissionsResultToFragment(int requestCode, Intent data,
Fragment fragment) {
// If the package installer crashed we may have not data - best effort.
String[] permissions = (data != null) ? data.getStringArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES) : new String[0];
final int[] grantResults = (data != null) ? data.getIntArrayExtra(
PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS) : new int[0];
fragment.onRequestPermissionsResult(requestCode, permissions, grantResults);
}
提示用户为什么需要:
/**
* Gets whether you should show UI with rationale for requesting a permission.
* You should do this only if you do not have the permission and the context in
* which the permission is requested does not clearly communicate to the user
* what would be the benefit from granting this permission.
*
* For example, if you write a camera app, requesting the camera permission
* would be expected by the user and no rationale for why it is requested is
* needed. If however, the app needs location for tagging photos then a non-tech
* savvy user may wonder how location is related to taking photos. In this case
* you may choose to show UI with rationale of requesting this permission.
*
*
* @param permission A permission your app wants to request.
* @return Whether you can show permission rationale UI.
*
* @see #checkSelfPermission(String)
* @see #requestPermissions(String[], int)
* @see #onRequestPermissionsResult(int, String[], int[])
*/
public boolean shouldShowRequestPermissionRationale(@NonNull String permission) {
return getPackageManager().shouldShowRequestPermissionRationale(permission);
}
returns true 如果app以前要求过这个权限,但是用户拒绝了,这个时候 permission还是可以 request 的。
returns false 如果以前用户拒绝给予这个权限并且在系统对话框中选择了“Don't ask again ”选项。
returns false 如果device policy 禁止app拥有这个权限。这种通常都是在DevicePolicyManager中设置的。
关于自动授权:
自动授权是通过函数grantDefaultPermissions() @DefaultPermissionGrantPolicy.Java实现的。该函数会在system ready和创建新用户时调用。DefaultPermissionGrantPolicy.java文件是PMS中在M上新增的一个类,用于实现自动授权的相关功能。
permission flags:
PackageManager.FLAG_PERMISSION_USER_SET: 权限被用户设置,应用还可以在runtime 的时候request
PackageManager.FLAG_PERMISSION_USER_FIXED:权限被用户设置,但是应用不能再request此权限(用户勾选了“never ask again”)。
PackageManager.FLAG_PERMISSION_POLICY_FIXED:device policy设定的权限,用户和app都不能修改。
PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE:如果permission被标记了这个flag,那么表示,app升级后被deny的permission,会依然是deny的状态。这个flag会在下面的情况中用到。适用于L以前版本的app,安装得到M的device上,如果它的dangerous permission被撤销了,比如通过settings里面的permission管理撤销或者device policy中设定,那么该APP升级到适用于M新的permission模式后,那么升级后这个permission依然是撤销的状态。也就是dangerous permission如果在升级之前被撤销过,升级后依然是撤销的状态。
PackageManager.FLAG_PERMISSION_SYSTEM_FIXED: 系统app获得的自动授权的permission。
PackageManager.FLAG_PERMISSION_GRANTED_BY_DEFAULT: 默认的系统基本功能app获得的自动授权的permission.
FLAG_PERMISSION_REVIEW_REQUIRED:在app 运行之前必须要进行permission review
允许权限:
public void grantRuntimePermission(String packageName, String name, final int userId) {
grantRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
}
详细的source code ,可以看PMS中的具体实现,也可以另一篇博文 grantRuntimePermission 详解
禁止权限:
@Override
public void revokeRuntimePermission(String packageName, String name, int userId) {
revokeRuntimePermission(packageName, name, userId, false /* Only if not fixed by policy */);
}
看完 grantRuntimePermission 详解 之后,revokeRuntimePermission 的代码就简单多了,最终同样的是修改PermissionStates中的mGranted 属性,并且将其属性写入runtime-permissions.xml 中。
相关文章:
android GrantPermissionsActivity 详解
android grantRuntimePermission 详解
AppOps 对于Normal permission 的控制
Android native 权限控制流程
android M 之前应用权限和M 之后的应用权限控制
Provider 权限详解