Android Runtime Permission 详解

前言:

在Android 6.0 之前权限管理存在一些弊端:

  • 权限系统只会在安装的时候询问一次,用户可以选择性的授予应用相关权限。但是一旦安装了,应用软件会在用户毫不知情的情况下访问权限内的所有东西。
  • 在Android 6.0 之前权限都称为install time permission,应用软件在安装以后用户不能修改permission 的授权情况,也不允许对permission 进行单独的授权和撤销。

Android 6.0 及更高版本中的 Android 应用权限模式旨在使权限更易于用户理解、更实用、更安全。该模式将需要危险权限的 Android 应用从安装时权限模式转移至运行时权限模式:

  • 安装时权限(Android 5.1 及更低版本,或者应用目标SDK为22或更低版本)。用户在安装或更新应用时,向应用授予危险权限。OEM/运营商可以在不通知用户的情况下,预先安装具有预授权的应用。
  • 运行时权限(Android 6.0 及更高版本,或者应用目标SDK为23或更高版本)。用户在应用运行时向应用授予危险权限。应用决定何时申请权限(例如,在应用启动或用户访问特定功能时申请权限),但必须允许用户授予/拒绝授予应用访问特定权限组的权限。OEM/运营商可以预安装应用,但不得预先授予权限。

Runtime permission 向前兼容

  • 如果是M 之前的应用安装在M 或更高的版本上,permission 会安装之前旧的方式管理,也就是install time permission model。需要注意的是,在M 的系统上用户可以在设置里对permission 进行授权和撤销,应用软件没有相应的权限可能会出现crash。
  • 如果M 之后的应用安装在M 之前的版本上,permission 没有runtime 一说,还是会安装以前的install time permission。


系统权限的使用

权限的使用:

基本 Android 应用默认情况下未关联权限,这意味着它无法执行对用户体验或设备上任何数据产生不利影响的任何操作。要利用受保护的设备功能,必须在应用清单(AndroidManifest.xml)中包含一个或多个 <uses-permission> 标记。
例如,需要监控传入的短信的应用要指定:

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.android.app.myapp" >
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    ...
</manifest>


权限级别:
如果您的应用在其清单中列出正常权限(即,不会对用户隐私或设备操作造成很大风险的权限),系统会自动授予这些权限。如果您的应用在其清单中列出危险权限(即,可能影响用户隐私或设备正常操作的权限),系统会要求用户明确授予这些权限。Android 发出请求的方式取决于系统版本,而系统版本是应用的目标:
  • 如果设备运行的是 Android 6.0(API 级别 23)或更高版本,并且应用的 targetSdkVersion 是 23 或更高版本,则应用在运行时向用户请求权限。用户可随时调用权限,因此应用在每次运行时均需检查自身是否具备所需的权限。如需了解有关在应用中请求权限的详细信息,请参阅使用系统权限培训指南。
  • 如果设备运行的是 Android 5.1(API 级别 22)或更低版本,并且应用的 targetSdkVersion 是 22 或更低版本,则系统会在用户安装应用时要求用户授予权限。如果将新权限添加到更新的应用版本,系统会在用户更新应用时要求授予该权限。用户一旦安装应用,他们撤销权限的唯一方式是卸载应用。
通常,权限失效会导致 SecurityException 被扔回应用。但不能保证每个地方都是这样。例如,sendBroadcast(Intent) 方法在数据传递到每个接收者时会检查权限,在方法调用返回后,即使权限失效,您也不会收到异常。但在几乎所有情况下,权限失效会记入系统日志。
Android 系统提供的权限请参阅 Manifest.permission。此外,任何应用都可定义并实施自己的权限,因此这不是所有可能权限的详尽列表。
可能在程序运行期间的多个位置实施特定权限:
  • 在调用系统时,防止应用执行某些功能。
  • 在启动 Activity 时,防止应用启动其他应用的 Activity。
  • 在发送和接收广播时,控制谁可以接收您的广播,谁可以向您发送广播。
  • 在访问和操作内容提供程序时。
  • 绑定至服务或启动服务。


权限自动调整:
随着时间的推移,平台中可能会加入新的限制,要想使用特定 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

  • Normal Permission指的是那些 app获取它所在的sandbox(每个进程都有独立的一个沙箱)以外的数据和资源所对应的权限,这些权限一般不会对用户的隐私信息造成风险. 比如,设置时区的权限(SET_TIME_ZONE)。对于此类权限,app申请后系统会自动赋予。
  • Dangerous Permission指的是那些可能对用户的隐私信息造成风险,或者可能影响用户数据的行为权限。比如读取用户的联系人。对于Dangerous Permission,app必须显示的获取用户的允许才可以正常使用。Runtime Permission机制针对的即是此类 dangerous permission。
  • Signature permission:权限请求者只有使用和[权限声明者]相同的证书来签名的情况下,才可以使用的权限。如果证书匹配,系统会自动赋予这些权限,不需要通知或请求用户。
  • SignatureOrSystem: SDK版本23之前叫“signature|privileged”,该类权限除了上述的 Signature Permission以外,还包括那些只赋予Android System Image内的应用的权限。Android并不建议app使用这类,因为Signature Permission已经能满足大部分的需求,不管这些app是否是build在System Image里。


特殊权限:

上面说明了permission 中属性android:protectionLevel 使用意义,其中存在一些特殊的权限,这些权限比较敏感,在使用的时候必须特殊处理。SYSTEM_ALERT_WINDOWWRITE_SETTINGS就是此类比较敏感的权限。

例如 WRITE_SETTINGS:

    <permission android:name="android.permission.WRITE_SETTINGS"
        android:label="@string/permlab_writeSettings"
        android:description="@string/permdesc_writeSettings"
        android:protectionLevel="signature|preinstalled|appop|pre23" />

官方解释如下:

  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 或更高版本,则当用户请求危险权限时系统会发生以下行为:

  • 如果应用请求其清单中列出的危险权限,而应用目前在权限组中没有任何权限,则系统会向用户显示一个对话框,描述应用要访问的权限组。对话框不描述该组内的具体权限。例如,如果应用请求 READ_CONTACTS 权限,系统对话框只说明该应用需要访问设备的联系信息。如果用户批准,系统将向应用授予其请求的权限。
  • 如果应用请求其清单中列出的危险权限,而应用在同一权限组中已有另一项危险权限,则系统会立即授予该权限,而无需与用户进行任何交互。例如,如果某应用已经请求并且被授予了 READ_CONTACTS 权限,然后它又请WRITE_CONTACTS,系统将立即授予该权限
任何权限都可属于一个权限组,包括正常权限和应用定义的权限。但权限组仅当权限危险时才影响用户体验。可以忽略正常权限的权限组。
如果设备运行的是 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(详解点击这里):

        <activity android:name=".permission.ui.GrantPermissionsActivity"
                android:configChanges="orientation|keyboardHidden|screenSize"
                android:excludeFromRecents="true"
                android:theme="@style/GrantPermissions"
                android:visibleToInstantApps="true">
            <intent-filter android:priority="1">
                <action android:name="android.content.pm.action.REQUEST_PERMISSIONS" />
                <category android:name="android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>

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.
     * <p>
     * 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.
     * </p>
     *
     * @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中设置的。

  • 应用安装后第一次访问,直接返回false;
  • 第一次请求权限时,用户拒绝了,下一次shouldShowRequestPermissionRationale()返回 true,这时候可以显示一些为什么需要这个权限的说明;
  • 第二次请求权限时,用户拒绝了,并选择了“不再提醒”的选项时:shouldShowRequestPermissionRationale()返回 false;
  • 设备的系统设置中禁止当前应用获取这个权限的授权,shouldShowRequestPermissionRationale()返回false;


关于自动授权:
  • 带有PRIVATE_FLAG_PRIVILEGED & FLAG_PERSISTENT flag的app会被自动给予授权。
  • 系统默认的基础app会被自动给予相应的permission.比如默认的电话本app自动会拥有CONTACT相关的permission。
自动授权是通过函数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 中。






猜你喜欢

转载自blog.csdn.net/jingerppp/article/details/80194908
今日推荐