美文网首页
从源码角度看Android如何完成动态权限申请

从源码角度看Android如何完成动态权限申请

作者: 蜗牛是不是牛 | 来源:发表于2023-06-18 15:25 被阅读0次

    伙伴们应该都了解,在Android6.0之后,对于部分权限,例如Camera、读写存储权限等,都需要用户授权才可以使用,除非你的应用为系统应用,否则这些“危险权限”将不会自动授予,那么为什么Google在Android 6.0之后会推出动态权限申请,主要是避免一些“流氓”软件在后台获取用户隐私,从而将责任从技术侧转移到用户侧,既然用户选择了允许这些权限使用,那么责任就由用户承担了。

    那么我们在动态申请权限时,系统是如何处理并保存这些状态,接下来我们深入源码一看究竟。

    1 权限申请

    假如我们的app需要申请读写权限,那么就可以在清单文件中进行权限的声明

    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
    
    

    当然我们仅仅声明并不起作用,我们需要在页面启动时进行权限的动态声明,我们常用的做法就是:

    class MainActivity : AppCompatActivity() {
        private val REQUEST_WRITE_STORAGE_PERMISSION = 0
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
    
            //首先判断权限是否申请过,或者说是否已经有这个权限了
            if (ContextCompat.checkSelfPermission(
                    this,
                    Manifest.permission.WRITE_EXTERNAL_STORAGE
                ) != PackageManager.PERMISSION_GRANTED
            ) {
                //如果已经拿到过了,就不需要申请了
                //没拿到过,需要动态申请
                if (ActivityCompat.shouldShowRequestPermissionRationale(
                        this,
                        Manifest.permission.WRITE_EXTERNAL_STORAGE
                    )
                ) {
                    Log.e("TAG", "之前拒绝过权限,现在重新再次申请")
                    ActivityCompat.requestPermissions(
                        this,
                        arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                        REQUEST_WRITE_STORAGE_PERMISSION
                    )
                    return
                }
                Log.e("TAG", "之前没有拒绝过权限,现在第一次申请")
                ActivityCompat.requestPermissions(
                    this,
                    arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE),
                    REQUEST_WRITE_STORAGE_PERMISSION
                )
            }
        }
    
        override fun onRequestPermissionsResult(
            requestCode: Int,
            permissions: Array<out String>,
            grantResults: IntArray
        ) {
            super.onRequestPermissionsResult(requestCode, permissions, grantResults)
            when (requestCode) {
                REQUEST_WRITE_STORAGE_PERMISSION -> {
                    Log.e("TAG", "onRequestPermissionsResult 获取到了权限")
                    permissions.forEach {
                        Log.e("TAG", "permissions $it")
                    }
                    grantResults.forEach {
                        Log.e("TAG", "grantResults $it")
                    }
                }
            }
        }
    }
    
    

    这是官方的写法,在申请权限之前,首先会判断 是否获取过这个权限,如果没有获取过,那么就会进行动态权限的申请。当然用户可能之前拒绝过权限,所以这里也会进行一次用户是否拒绝过申请权限的操作,可以做对应的一些交互上的处理,以及权限申请的目的让用户打消顾虑。下面就是用户可能存在的居中操作,做了对应日志的打印:

    用户第一次申请权限时拒绝了

    2023-06-17 13:43:22.111 30040-30040/com.lay.nowinandroid E/TAG: 之前没有拒绝过权限,现在第一次申请
    2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
    2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
    2023-06-17 13:43:31.824 30040-30040/com.lay.nowinandroid E/TAG: grantResults -1
    
    

    用户第二次拒绝了权限

    通过日志发现,这时已经走到了shouldShowRequestPermissionRationale代码块中,系统是知道用户之前拒绝过了权限。

    2023-06-17 13:44:32.813 30868-30868/com.lay.nowinandroid E/TAG: 之前拒绝过权限,现在重新再次申请
    2023-06-17 13:44:49.968 30868-30868/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
    2023-06-17 13:44:49.969 30868-30868/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
    2023-06-17 13:44:49.969 30868-30868/com.lay.nowinandroid E/TAG: grantResults -1
    
    

    用户接受了权限

    2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: onRequestPermissionsResult 获取到了权限
    2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: permissions android.permission.WRITE_EXTERNAL_STORAGE
    2023-06-17 13:45:14.135 30868-30868/com.lay.nowinandroid E/TAG: grantResults 0
    
    

    我们看到,当我们申请权限时,其实在onRequestPermissionsResult回调中,无论是同意还是拒绝都会有对应的输出,如果没有同意权限,那么此时grantResults的值就为-1,如果接受了权限,那么grantResults的值就为0,可以看下面对应的介绍。

    /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has been granted to the given package.
     */
    public static final int PERMISSION_GRANTED = 0;
    
    /**
     * Permission check result: this is returned by {@link #checkPermission}
     * if the permission has not been granted to the given package.
     */
    public static final int PERMISSION_DENIED = -1;
    
    

    我们看到这里其实结果为一个数组,因为我们在申请权限时,可以以组为单位,与permissions是一一对应的,我们可以知道,到底哪些权限我们拿到了,哪些没有拿到。

    2 requestPermissions源码分析

    前面在介绍权限申请时,我们调用的是ActivityCompat的requestPermissions方法,接下来我们跟随源码,看系统是如何完成权限申请的。

    ActivityCompat # requestPermissions

    public static void requestPermissions(final @NonNull Activity activity,
           final @NonNull String[] permissions, final @IntRange(from = 0) int requestCode) {
       if (sDelegate != null
               && sDelegate.requestPermissions(activity, permissions, requestCode)) {
           // Delegate has handled the permission request.
           return;
       }
    
       for (String permission : permissions) {
           if (TextUtils.isEmpty(permission)) {
               throw new IllegalArgumentException("Permission request for permissions "
                       + Arrays.toString(permissions) + " must not contain null or empty values");
           }
       }
    
       if (Build.VERSION.SDK_INT >= 23) {
           if (activity instanceof RequestPermissionsRequestCodeValidator) {
               ((RequestPermissionsRequestCodeValidator) activity)
                       .validateRequestPermissionsRequestCode(requestCode);
           }
           Api23Impl.requestPermissions(activity, permissions, requestCode);
       } else if (activity instanceof OnRequestPermissionsResultCallback) {
           Handler handler = new Handler(Looper.getMainLooper());
           handler.post(new Runnable() {
               @Override
               public void run() {
                   final int[] grantResults = new int[permissions.length];
    
                   PackageManager packageManager = activity.getPackageManager();
                   String packageName = activity.getPackageName();
    
                   final int permissionCount = permissions.length;
                   for (int i = 0; i < permissionCount; i++) {
                       grantResults[i] = packageManager.checkPermission(
                               permissions[i], packageName);
                   }
    
                   ((OnRequestPermissionsResultCallback) activity).onRequestPermissionsResult(
                           requestCode, permissions, grantResults);
               }
           });
       }
    }
    
    

    首先在requestPermissions方法中,判断了SDK的版本,如果大于等于23,也就是包括Android 6.0以上的版本,会执行对应的逻辑,在Android 6.0版本以下,则是直接通过PKMS来检查权限,并做onRequestPermissionsResult的回调,我们关注的重点不在这里,我们重点看Android 6.0以上的版本。

    Api23Impl # requestPermissions

    @DoNotInline
    static void requestPermissions(Activity activity, String[] permissions, int requestCode) {
        activity.requestPermissions(permissions, requestCode);
    }
    
    

    通过源码我们看到是调用了Api23Impl的requestPermissions方法,在这个方法内部,直接调用了Activity的requestPermissions方法。

    Activity # requestPermissions

    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;
        }
    
        if (!getAttributionSource().getRenouncedPermissions().isEmpty()) {
            final int permissionCount = permissions.length;
            for (int i = 0; i < permissionCount; i++) {
                if (getAttributionSource().getRenouncedPermissions().contains(permissions[i])) {
                    throw new IllegalArgumentException("Cannot request renounced permission: "
                            + permissions[i]);
                }
            }
        }
    
        final Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
        startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
        mHasCurrentPermissionsRequest = true;
    }
    
    

    在这个方法中,前面主要做了一些判断,包括requestCode的校验、并发的处理(同一时间只能进行一次权限申请)等,最后通过PackManager创建一个Intent对象,因为需要回调状态到onRequestPermissionsResult,所以通过startActivityForResult方式启动了一个Activity。

    PackageManager # buildRequestPermissionsIntent

    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);
       intent.putExtra(EXTRA_REQUEST_PERMISSIONS_NAMES, permissions);
       intent.setPackage(getPermissionControllerPackageName());
       return intent;
    }
    
    

    这个方法非常简单,就是创建了一个Intent对象,但是这个Intent对象的具体配置我们需要看一下。

    public static final String ACTION_REQUEST_PERMISSIONS =
            "android.content.pm.action.REQUEST_PERMISSIONS";
    
    
    public static final String EXTRA_REQUEST_PERMISSIONS_NAMES =
            "android.content.pm.extra.REQUEST_PERMISSIONS_NAMES";
    
    

    在创建一个Intent对象之后,将ACTION_REQUEST_PERMISSIONS作为参数传递到Intent构造函数中,意味着这里是创建了一个隐式意图,这里会启动一个系统的页面

    因为通常我们在启动一个Activity的时候,通常是一个显示意图,通过setClass将目的地声明在此,而buildRequestPermissionsIntent中则是创建了一个隐式意图,这里的Activity就是我们看到的那个弹窗页面。那么这些Activity是存在哪里呢,其实就是隐式安装器PackageInstaller提供的。

    PackageInstaller

    这里我简单提一下,PackageInstaller其实也是一个系统应用,看名字应该知道它的作用是用来安装或者卸载应用程序的,除此之外,还可以管理应用程序的权限,在安装应用程序时,它会向用户显示应用程序要求的权限,并允许用户对这些权限进行管理和控制。

    所以我们在安装或者卸载应用时,系统出现的弹窗,都是在PackageInstaller app中提供的,前面我们提到的android.content.pm.action.REQUEST_PERMISSIONS这个action,在PackageInstaller中就对应一个页面,我们可以去看下PackageInstaller的清单文件。

    <activity android:name="com.android.packageinstaller.permission.ui.GrantPermissionsActivity"
            android:configChanges="keyboardHidden|screenSize"
            android:excludeFromRecents="true"
            android:theme="@style/GrantPermissions"
            android:visibleToInstantApps="true"
            android:inheritShowWhenLocked="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>
    
    

    所以当我们申请权限时弹出的系统弹窗,就是GrantPermissionsActivity;除此之外,在构建隐式意图时,还把需要申请的权限组作为参数传进去了,所以在GrantPermissionsActivity中,会接收这些数据。

    GrantPermissionsActivity

    接下来我们分析GrantPermissionsActivity中处理逻辑,这里我把系统源码中GrantPermissionsActivity拷贝了一份,方便分析权限申请的流程。

    public class GrantPermissionsActivity extends Activity
            implements GrantPermissionsViewHandler.ResultListener {
    
        private static final String LOG_TAG = "GrantPermissionsActivity";
    
        private static final String KEY_REQUEST_ID = GrantPermissionsActivity.class.getName()
                + "_REQUEST_ID";
    
        public static int NUM_BUTTONS = 5;
        public static int LABEL_ALLOW_BUTTON = 0;
        public static int LABEL_ALLOW_ALWAYS_BUTTON = 1;
        public static int LABEL_ALLOW_FOREGROUND_BUTTON = 2;
        public static int LABEL_DENY_BUTTON = 3;
        public static int LABEL_DENY_AND_DONT_ASK_AGAIN_BUTTON = 4;
    
        /** Unique Id of a request */
        private long mRequestId;
    
        private String[] mRequestedPermissions;
        private CharSequence[] mButtonLabels;
    
        private ArrayMap<Pair<String, Boolean>, GroupState> mRequestGrantPermissionGroups =
                new ArrayMap<>();
    
        private GrantPermissionsViewHandler mViewHandler;
        private AppPermissions mAppPermissions;
    
        boolean mResultSet;
    
        /**
         * Listens for changes to the permission of the app the permissions are currently getting
         * granted to. {@code null} when unregistered.
         */
        private @Nullable PackageManager.OnPermissionsChangedListener mPermissionChangeListener;
    
        /**
         * Listens for changes to the app the permissions are currently getting granted to. {@code null}
         * when unregistered.
         */
        private @Nullable PackageRemovalMonitor mPackageRemovalMonitor;
        // .....
    
        /**
         * Report the result of a grant of a permission.
         *
         * @param permission The permission that was granted or denied
         * @param result The permission grant result
         */
        private void reportRequestResult(@NonNull String permission, int result) {
            boolean isImplicit = !ArrayUtils.contains(mRequestedPermissions, permission);
    
            Log.v(LOG_TAG,
                    "Permission grant result requestId=" + mRequestId + " callingUid=" + mCallingUid
                            + " callingPackage=" + mCallingPackage + " permission=" + permission
                            + " isImplicit=" + isImplicit + " result=" + result);
    
            PermissionControllerStatsLog.write(
                    PermissionControllerStatsLog.PERMISSION_GRANT_REQUEST_RESULT_REPORTED, mRequestId,
                    mCallingUid, mCallingPackage, permission, isImplicit, result);
        }
    
        /**
         * Report the result of a grant of a permission.
         *
         * @param permissions The permissions that were granted or denied
         * @param result The permission grant result
         */
        private void reportRequestResult(@NonNull String[] permissions, int result) {
            for (String permission : permissions) {
                reportRequestResult(permission, result);
            }
        }
    
        @Override
        public void onCreate(Bundle icicle) {
            super.onCreate(icicle);
            // ......
            mRequestedPermissions = getIntent().getStringArrayExtra(
                    PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES);
            if (mRequestedPermissions == null) {
                mRequestedPermissions = new String[0];
            }
            //......
            
            //这里会通过判断硬件设备类型,展示不同的UI
            if (DeviceUtils.isTelevision(this)) {
                mViewHandler = new com.android.packageinstaller.permission.ui.television
                        .GrantPermissionsViewHandlerImpl(this,
                        mCallingPackage).setResultListener(this);
            } else if (DeviceUtils.isWear(this)) {
                mViewHandler = new GrantPermissionsWatchViewHandler(this).setResultListener(this);
            } else if (DeviceUtils.isAuto(this)) {
                mViewHandler = new GrantPermissionsAutoViewHandler(this, mCallingPackage, userHandle)
                        .setResultListener(this);
            } else {
                mViewHandler = new com.android.packageinstaller.permission.ui.handheld
                        .GrantPermissionsViewHandlerImpl(this, mCallingPackage, userHandle)
                        .setResultListener(this);
            }
    
            // ......
    
            setContentView(mViewHandler.createView());
    
            // ......
        }
    
        //......
    
        @Override
        public void onPermissionGrantResult(String name,
                @GrantPermissionsViewHandler.Result int result) {
            logGrantPermissionActivityButtons(name, result);
            GroupState foregroundGroupState = getForegroundGroupState(name);
            GroupState backgroundGroupState = getBackgroundGroupState(name);
    
            if (result == GRANTED_ALWAYS || result == GRANTED_FOREGROUND_ONLY
                    || result == DENIED_DO_NOT_ASK_AGAIN) {
                KeyguardManager kgm = getSystemService(KeyguardManager.class);
    
                if (kgm.isDeviceLocked()) {
                    kgm.requestDismissKeyguard(this, new KeyguardManager.KeyguardDismissCallback() {
                                @Override
                                public void onDismissError() {
                                    Log.e(LOG_TAG, "Cannot dismiss keyguard perm=" + name + " result="
                                            + result);
                                }
    
                                @Override
                                public void onDismissCancelled() {
                                    // do nothing (i.e. stay at the current permission group)
                                }
    
                                @Override
                                public void onDismissSucceeded() {
                                    // Now the keyguard is dismissed, hence the device is not locked
                                    // anymore
                                    onPermissionGrantResult(name, result);
                                }
                            });
    
                    return;
                }
            }
    
            switch (result) {
                case GRANTED_ALWAYS :
                    if (foregroundGroupState != null) {
                        onPermissionGrantResultSingleState(foregroundGroupState, true, false);
                    }
                    if (backgroundGroupState != null) {
                        onPermissionGrantResultSingleState(backgroundGroupState, true, false);
                    }
                    break;
                case GRANTED_FOREGROUND_ONLY :
                    if (foregroundGroupState != null) {
                        onPermissionGrantResultSingleState(foregroundGroupState, true, false);
                    }
                    if (backgroundGroupState != null) {
                        onPermissionGrantResultSingleState(backgroundGroupState, false, false);
                    }
                    break;
                case DENIED :
                    if (foregroundGroupState != null) {
                        onPermissionGrantResultSingleState(foregroundGroupState, false, false);
                    }
                    if (backgroundGroupState != null) {
                        onPermissionGrantResultSingleState(backgroundGroupState, false, false);
                    }
                    break;
                case DENIED_DO_NOT_ASK_AGAIN :
                    if (foregroundGroupState != null) {
                        onPermissionGrantResultSingleState(foregroundGroupState, false, true);
                    }
                    if (backgroundGroupState != null) {
                        onPermissionGrantResultSingleState(backgroundGroupState, false, true);
                    }
                    break;
            }
    
            if (!showNextPermissionGroupGrantRequest()) {
                setResultAndFinish();
            }
        }
    
        /**
         * Grants or revoked the affected permissions for a single {@link groupState}.
         *
         * @param groupState The group state with the permissions to grant/revoke
         * @param granted {@code true} if the permissions should be granted, {@code false} if they
         *        should be revoked
         * @param doNotAskAgain if the permissions should be revoked should be app be allowed to ask
         *        again for the same permissions?
         */
        private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
                boolean doNotAskAgain) {
            if (groupState != null && groupState.mGroup != null
                    && groupState.mState == GroupState.STATE_UNKNOWN) {
                if (granted) {
                    groupState.mGroup.grantRuntimePermissions(doNotAskAgain,
                            groupState.affectedPermissions);
                    groupState.mState = GroupState.STATE_ALLOWED;
    
                    reportRequestResult(groupState.affectedPermissions,
                            PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED);
                } else {
                    groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
                            groupState.affectedPermissions);
                    groupState.mState = GroupState.STATE_DENIED;
    
                    reportRequestResult(groupState.affectedPermissions, doNotAskAgain
                            ?
                            PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED_WITH_PREJUDICE
                            : PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_DENIED);
                }
            }
        }
        // ......
    
        private void setResultIfNeeded(int resultCode) {
            if (!mResultSet) {
                mResultSet = true;
                logRequestedPermissionGroups();
                Intent result = new Intent(PackageManager.ACTION_REQUEST_PERMISSIONS);
                result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_NAMES, mRequestedPermissions);
    
                PackageManager pm = getPackageManager();
                int numRequestedPermissions = mRequestedPermissions.length;
                int[] grantResults = new int[numRequestedPermissions];
                for (int i = 0; i < numRequestedPermissions; i++) {
                    grantResults[i] = pm.checkPermission(mRequestedPermissions[i], mCallingPackage);
                }
    
                result.putExtra(PackageManager.EXTRA_REQUEST_PERMISSIONS_RESULTS, grantResults);
                setResult(resultCode, result);
            }
        }
    
        private void setResultAndFinish() {
            setResultIfNeeded(RESULT_OK);
            finish();
        }
    
        // ......
    
        private static final class GroupState {
            static final int STATE_UNKNOWN = 0;
            static final int STATE_ALLOWED = 1;
            static final int STATE_DENIED = 2;
            static final int STATE_SKIPPED = 3;
    
            final AppPermissionGroup mGroup;
            int mState = STATE_UNKNOWN;
    
            /** Permissions of this group that need to be granted, null == no permissions of group */
            String[] affectedPermissions;
    
            GroupState(AppPermissionGroup group) {
                mGroup = group;
            }
        }
    
        private class PermissionChangeListener implements PackageManager.OnPermissionsChangedListener {
            final int mCallingPackageUid;
    
            PermissionChangeListener() throws NameNotFoundException {
                mCallingPackageUid = getPackageManager().getPackageUid(mCallingPackage, 0);
            }
    
            @Override
            public void onPermissionsChanged(int uid) {
                if (uid == mCallingPackageUid) {
                    updateIfPermissionsWereGranted();
                }
            }
        }
    }
    
    

    GrantPermissionsActivity # onPermissionGrantResult

    当用户点击按钮时,会调用onPermissionGrantResult方法,在这个方法中,会判断用户行为,一般会有以下几种:始终允许、仅允许一次、禁止、禁止而且不需要再提醒,针对每种结果,都会调用onPermissionGrantResultSingleState方法来具体的实施。

    在onPermissionGrantResultSingleState中,会判断granted参数,也就是否允许权限,调用AppPermissionGroup的grantRuntimePermissions方法做具体的运行时权限申请。

    AppPermissionGroup # grantRuntimePermissions

    注意这个方法中的参数,fixedByTheUser其实对应的就是dontAskAgain,是否需要再次询问,如果用户选择拒绝权限而且不再询问,那么就只能去设置中打开权限。

    public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
        boolean killApp = false;
        boolean wasAllGranted = true;
    
        // We toggle permissions only to apps that support runtime
        // permissions, otherwise we toggle the app op corresponding
        // to the permission if the permission is granted to the app.
        for (Permission permission : mPermissions.values()) {
            if (filterPermissions != null
                    && !ArrayUtils.contains(filterPermissions, permission.getName())) {
                continue;
            }
    
            if (!permission.isGrantingAllowed(mIsEphemeralApp, mAppSupportsRuntimePermissions)) {
                // Skip unallowed permissions.
                continue;
            }
    
            boolean wasGranted = permission.isGrantedIncludingAppOp();
    
            if (mAppSupportsRuntimePermissions) {
                // Do not touch permissions fixed by the system.
                if (permission.isSystemFixed()) {
                    wasAllGranted = false;
                    break;
                }
    
                // Ensure the permission app op enabled before the permission grant.
                if (permission.affectsAppOp() && !permission.isAppOpAllowed()) {
                    permission.setAppOpAllowed(true);
                }
    
                // Grant the permission if needed.
                if (!permission.isGranted()) {
                    permission.setGranted(true);
                }
    
                // Update the permission flags.
                if (!fixedByTheUser) {
                    // Now the apps can ask for the permission as the user
                    // no longer has it fixed in a denied state.
                    if (permission.isUserFixed() || permission.isUserSet()) {
                        permission.setUserFixed(false);
                        permission.setUserSet(false);
                    }
                }
            } else {
                // Legacy apps cannot have a not granted permission but just in case.
                if (!permission.isGranted()) {
                    continue;
                }
    
                // If the permissions has no corresponding app op, then it is a
                // third-party one and we do not offer toggling of such permissions.
                if (permission.affectsAppOp()) {
                    if (!permission.isAppOpAllowed()) {
                        permission.setAppOpAllowed(true);
    
                        // Legacy apps do not know that they have to retry access to a
                        // resource due to changes in runtime permissions (app ops in this
                        // case). Therefore, we restart them on app op change, so they
                        // can pick up the change.
                        killApp = true;
                    }
    
                    // Mark that the permission should not be be granted on upgrade
                    // when the app begins supporting runtime permissions.
                    if (permission.shouldRevokeOnUpgrade()) {
                        permission.setRevokeOnUpgrade(false);
                    }
                }
    
                // Granting a permission explicitly means the user already
                // reviewed it so clear the review flag on every grant.
                if (permission.isReviewRequired()) {
                    permission.unsetReviewRequired();
                }
            }
    
            // If we newly grant background access to the fine location, double-guess the user some
            // time later if this was really the right choice.
            if (!wasGranted && permission.isGrantedIncludingAppOp()) {
                if (permission.getName().equals(ACCESS_FINE_LOCATION)) {
                    Permission bgPerm = permission.getBackgroundPermission();
                    if (bgPerm != null) {
                        if (bgPerm.isGrantedIncludingAppOp()) {
                            mTriggerLocationAccessCheckOnPersist = true;
                        }
                    }
                } else if (permission.getName().equals(ACCESS_BACKGROUND_LOCATION)) {
                    ArrayList<Permission> fgPerms = permission.getForegroundPermissions();
                    if (fgPerms != null) {
                        int numFgPerms = fgPerms.size();
                        for (int fgPermNum = 0; fgPermNum < numFgPerms; fgPermNum++) {
                            Permission fgPerm = fgPerms.get(fgPermNum);
    
                            if (fgPerm.getName().equals(ACCESS_FINE_LOCATION)) {
                                if (fgPerm.isGrantedIncludingAppOp()) {
                                    mTriggerLocationAccessCheckOnPersist = true;
                                }
    
                                break;
                            }
                        }
                    }
                }
            }
        }
        //默认 mDelayChanges = false,这里真正进行权限申请
        if (!mDelayChanges) {
            persistChanges(false);
    
            if (killApp) {
                killApp(KILL_REASON_APP_OP_CHANGE);
            }
        }
    
        return wasAllGranted;
    }
    
    

    前面主要是进行一系列的配置,persistChanges方法中会向PKMS发起进程间通信,调用PKMS的grantRuntimePermission方法。

    
    void persistChanges(boolean mayKillBecauseOfAppOpsChange) {
            int uid = mPackageInfo.applicationInfo.uid;
    
            int numPermissions = mPermissions.size();
            boolean shouldKillApp = false;
    
            for (int i = 0; i < numPermissions; i++) {
                Permission permission = mPermissions.valueAt(i);
    
                if (!permission.isSystemFixed()) {
                    if (permission.isGranted()) {
                        mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                                permission.getName(), mUserHandle);
                    } else {
                        boolean isCurrentlyGranted = mContext.checkPermission(permission.getName(), -1,
                                uid) == PERMISSION_GRANTED;
    
                        if (isCurrentlyGranted) {
                            mPackageManager.revokeRuntimePermission(mPackageInfo.packageName,
                                    permission.getName(), mUserHandle);
                        }
                    }
                }
    
                int flags = (permission.isUserSet() ? PackageManager.FLAG_PERMISSION_USER_SET : 0)
                        | (permission.isUserFixed() ? PackageManager.FLAG_PERMISSION_USER_FIXED : 0)
                        | (permission.shouldRevokeOnUpgrade()
                        ? PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE : 0)
                        | (permission.isPolicyFixed() ? PackageManager.FLAG_PERMISSION_POLICY_FIXED : 0)
                        | (permission.isReviewRequired()
                        ? PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED : 0);
    
                mPackageManager.updatePermissionFlags(permission.getName(),
                        mPackageInfo.packageName,
                        PackageManager.FLAG_PERMISSION_USER_SET
                                | PackageManager.FLAG_PERMISSION_USER_FIXED
                                | PackageManager.FLAG_PERMISSION_REVOKE_ON_UPGRADE
                                | PackageManager.FLAG_PERMISSION_POLICY_FIXED
                                | PackageManager.FLAG_PERMISSION_REVIEW_REQUIRED,
                        flags, mUserHandle);
    
                if (permission.affectsAppOp()) {
                    if (!permission.isSystemFixed()) {
                        // Enabling/Disabling an app op may put the app in a situation in which it has
                        // a handle to state it shouldn't have, so we have to kill the app. This matches
                        // the revoke runtime permission behavior.
                        if (permission.isAppOpAllowed()) {
                            shouldKillApp |= allowAppOp(permission, uid);
                        } else {
                            shouldKillApp |= disallowAppOp(permission, uid);
                        }
                    }
                }
            }
    
            if (mayKillBecauseOfAppOpsChange && shouldKillApp) {
                killApp(KILL_REASON_APP_OP_CHANGE);
            }
    
            if (mTriggerLocationAccessCheckOnPersist) {
                new LocationAccessCheck(mContext, null).checkLocationAccessSoon();
                mTriggerLocationAccessCheckOnPersist = false;
            }
        }
    
    

    PKMS # grantRuntimePermission

    @Override
    public void grantRuntimePermission(String packageName, String permName, final int userId) {
        // Because this is accessed via the package manager service AIDL,
        // go through the permission manager service AIDL
        mContext.getSystemService(PermissionManager.class)
                .grantRuntimePermission(packageName, permName, UserHandle.of(userId));
    }
    
    

    这里我们看到,是拿到PermissionManager服务对象,调用PMMS的grantRuntimePermission方法,所以我们需要去PermissionManagerService中查找。

    @Override
    public void grantRuntimePermission(String packageName, String permName, final int userId) {
        final int callingUid = Binder.getCallingUid();
        final boolean overridePolicy =
                checkUidPermission(callingUid, ADJUST_RUNTIME_PERMISSIONS_POLICY)
                        == PackageManager.PERMISSION_GRANTED;
    
        grantRuntimePermissionInternal(packageName, permName, overridePolicy,
                callingUid, userId, mDefaultPermissionCallback);
    }
    
    private void grantRuntimePermissionInternal(String packageName, String permName,
            boolean overridePolicy, int callingUid, final int userId, PermissionCallback callback) {
        // ..... 
        
        final int uid = UserHandle.getUid(userId, pkg.getUid());
        if (callback != null) {
            if (isRuntimePermission) {
                callback.onPermissionGranted(uid, userId);
            } else {
                callback.onInstallPermissionGranted();
            }
            if (permissionHasGids) {
                callback.onGidsChanged(UserHandle.getAppId(pkg.getUid()), userId);
            }
        }
    
        if (isRuntimePermission) {
            notifyRuntimePermissionStateChanged(packageName, userId);
        }
    }
    
    

    其实在PermissionManagerService的grantRuntimePermissionInternal前面所有的判断,都是在判断当前这个权限是不是已经获取到了,如果获取到了就退出;如果没有获取到,那么就会通过PermissionCallback回调,并判断是否为运行时权限,如果是运行时权限,会回调onPermissionGranted方法,非运行时权限会回调onInstallPermissionGranted方法。

    private final PermissionCallback mDefaultPermissionCallback = new PermissionCallback() {
        @Override
        public void onGidsChanged(int appId, int userId) {
            mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED));
        }
        @Override
        public void onPermissionGranted(int uid, int userId) {
            mOnPermissionChangeListeners.onPermissionsChanged(uid);
    
            // Not critical; if this is lost, the application has to request again.
            mPackageManagerInt.writeSettings(true);
        }
        @Override
        public void onInstallPermissionGranted() {
            mPackageManagerInt.writeSettings(true);
        }
        @Override
        public void onPermissionRevoked(int uid, int userId, String reason) {
            mOnPermissionChangeListeners.onPermissionsChanged(uid);
    
            // Critical; after this call the application should never have the permission
            mPackageManagerInt.writeSettings(false);
            final int appId = UserHandle.getAppId(uid);
            if (reason == null) {
                mHandler.post(() -> killUid(appId, userId, KILL_APP_REASON_PERMISSIONS_REVOKED));
            } else {
                mHandler.post(() -> killUid(appId, userId, reason));
            }
        }
        @Override
        public void onInstallPermissionRevoked() {
            mPackageManagerInt.writeSettings(true);
        }
        @Override
        public void onPermissionUpdated(int[] userIds, boolean sync) {
            mPackageManagerInt.writePermissionSettings(userIds, !sync);
        }
        @Override
        public void onInstallPermissionUpdated() {
            mPackageManagerInt.writeSettings(true);
        }
        @Override
        public void onPermissionRemoved() {
            mPackageManagerInt.writeSettings(false);
        }
        public void onPermissionUpdatedNotifyListener(@UserIdInt int[] updatedUserIds, boolean sync,
                int uid) {
            onPermissionUpdated(updatedUserIds, sync);
            for (int i = 0; i < updatedUserIds.length; i++) {
                int userUid = UserHandle.getUid(updatedUserIds[i], UserHandle.getAppId(uid));
                mOnPermissionChangeListeners.onPermissionsChanged(userUid);
            }
        }
        public void onInstallPermissionUpdatedNotifyListener(int uid) {
            onInstallPermissionUpdated();
            mOnPermissionChangeListeners.onPermissionsChanged(uid);
        }
    };
    
    

    在onPermissionGranted回调方法中,会调用PackageManagerInternal的writeSettings方法,将权限信息写入到xml文件中。

    PackageManagerInternal # writeSettings

    @Override
    public void writeSettings(boolean async) {
        synchronized (mLock) {
            if (async) {
                scheduleWriteSettingsLocked();
            } else {
                writeSettingsLPrTEMP();
            }
        }
    }
    
    

    这里是可以选择同步或者异步,因为涉及到了IO操作,所以这里传入的是true。

    case WRITE_SETTINGS: {
        Process.setThreadPriority(Process.THREAD_PRIORITY_DEFAULT);
        synchronized (mLock) {
            removeMessages(WRITE_SETTINGS);
            removeMessages(WRITE_PACKAGE_RESTRICTIONS);
            writeSettingsLPrTEMP();
            mDirtyUsers.clear();
        }
        Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
    } 
    break;
    
    

    最终是调用writeLPr方法,创建xml文件

    void writeLPr() {
        //Debug.startMethodTracing("/data/system/packageprof", 8 * 1024 * 1024);
    
        final long startTime = SystemClock.uptimeMillis();
    
        // Whenever package manager changes something on the system, it writes out whatever it
        // changed in the form of a settings object change, and it does so under its internal
        // lock --- so if we invalidate the package cache here, we end up invalidating at the
        // right time.
        invalidatePackageCache();
    
        // Keep the old settings around until we know the new ones have
        // been successfully written.
        if (mSettingsFilename.exists()) {
            // Presence of backup settings file indicates that we failed
            // to persist settings earlier. So preserve the older
            // backup for future reference since the current settings
            // might have been corrupted.
            if (!mBackupSettingsFilename.exists()) {
                if (!mSettingsFilename.renameTo(mBackupSettingsFilename)) {
                    Slog.wtf(PackageManagerService.TAG,
                            "Unable to backup package manager settings, "
                            + " current changes will be lost at reboot");
                    return;
                }
            } else {
                mSettingsFilename.delete();
                Slog.w(PackageManagerService.TAG, "Preserving older settings backup");
            }
        }
    
        mPastSignatures.clear();
    
        try {
            final FileOutputStream fstr = new FileOutputStream(mSettingsFilename);
            final TypedXmlSerializer serializer = Xml.resolveSerializer(fstr);
            serializer.startDocument(null, true);
            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
    
            serializer.startTag(null, "packages");
    
            for (int i = 0; i < mVersion.size(); i++) {
                final String volumeUuid = mVersion.keyAt(i);
                final VersionInfo ver = mVersion.valueAt(i);
    
                serializer.startTag(null, TAG_VERSION);
                XmlUtils.writeStringAttribute(serializer, ATTR_VOLUME_UUID, volumeUuid);
                serializer.attributeInt(null, ATTR_SDK_VERSION, ver.sdkVersion);
                serializer.attributeInt(null, ATTR_DATABASE_VERSION, ver.databaseVersion);
                XmlUtils.writeStringAttribute(serializer, ATTR_FINGERPRINT, ver.fingerprint);
                serializer.endTag(null, TAG_VERSION);
            }
    
            if (mVerifierDeviceIdentity != null) {
                serializer.startTag(null, "verifier");
                serializer.attribute(null, "device", mVerifierDeviceIdentity.toString());
                serializer.endTag(null, "verifier");
            }
    
            serializer.startTag(null, "permission-trees");
            mPermissions.writePermissionTrees(serializer);
            serializer.endTag(null, "permission-trees");
    
            serializer.startTag(null, "permissions");
            mPermissions.writePermissions(serializer);
            serializer.endTag(null, "permissions");
    
            for (final PackageSetting pkg : mPackages.values()) {
                writePackageLPr(serializer, pkg);
            }
    
            for (final PackageSetting pkg : mDisabledSysPackages.values()) {
                writeDisabledSysPackageLPr(serializer, pkg);
            }
    
            for (final SharedUserSetting usr : mSharedUsers.values()) {
                serializer.startTag(null, "shared-user");
                serializer.attribute(null, ATTR_NAME, usr.name);
                serializer.attributeInt(null, "userId", usr.userId);
                usr.signatures.writeXml(serializer, "sigs", mPastSignatures.untrackedStorage());
                serializer.endTag(null, "shared-user");
            }
    
            if (mRenamedPackages.size() > 0) {
                for (Map.Entry<String, String> e : mRenamedPackages.entrySet()) {
                    serializer.startTag(null, "renamed-package");
                    serializer.attribute(null, "new", e.getKey());
                    serializer.attribute(null, "old", e.getValue());
                    serializer.endTag(null, "renamed-package");
                }
            }
    
            mDomainVerificationManager.writeSettings(serializer, false /* includeSignatures */,
                    UserHandle.USER_ALL);
    
            mKeySetManagerService.writeKeySetManagerServiceLPr(serializer);
    
            serializer.endTag(null, "packages");
    
            serializer.endDocument();
    
            fstr.flush();
            FileUtils.sync(fstr);
            fstr.close();
    
            // New settings successfully written, old ones are no longer
            // needed.
            mBackupSettingsFilename.delete();
            FileUtils.setPermissions(mSettingsFilename.toString(),
                    FileUtils.S_IRUSR|FileUtils.S_IWUSR
                    |FileUtils.S_IRGRP|FileUtils.S_IWGRP,
                    -1, -1);
    
            writeKernelMappingLPr();
            writePackageListLPr();
            writeAllUsersPackageRestrictionsLPr();
            writeAllRuntimePermissionsLPr();
            com.android.internal.logging.EventLogTags.writeCommitSysConfigFile(
                    "package", SystemClock.uptimeMillis() - startTime);
            return;
    
        } catch(java.io.IOException e) {
            Slog.wtf(PackageManagerService.TAG, "Unable to write package manager settings, "
                    + "current changes will be lost at reboot", e);
        }
        // Clean up partially written files
        if (mSettingsFilename.exists()) {
            if (!mSettingsFilename.delete()) {
                Slog.wtf(PackageManagerService.TAG, "Failed to clean up mangled file: "
                        + mSettingsFilename);
            }
        }
        //Debug.stopMethodTracing();
    }
    
    

    具体文件为:

    mSettingsFilename = new File(mSystemDir, "packages.xml");
    
    

    也就是在data/system/packages.xml文件中永久保存,如果应用卸载那么就会清除权限.

    <package name="com.lay.nowinandroid" codePath="/data/app/~~Z3KlFzqUZ3vXhgizpl-R4Q==/com.lay.nowinandroid-NcpT91M9gQfcPkeJJhPoxA==" nativeLibraryPath="/data/app/~~Z3KlFzqUZ3vXhgizpl-R4Q==/com.lay.nowinandroid-NcpT91M9gQfcPkeJJhPoxA==/lib" publicFlags="810073926" privateFlags="-1400893440" ft="188c89ab490" it="188c7e11f01" ut="188c89ab614" version="1" userId="10131">
        <sigs count="1" schemeVersion="2">
            <cert index="9" key="/>
        </sigs>
        <perms>
            <item name="android.permission.INTERNET" granted="true" flags="0" />
        </perms>
        <proper-signing-keyset identifier="10" />
    </package>
    
    

    我们可以看到,在xml文件中的perms标签下有对应权限的声明,以及granted的参数,也就是说下次再进来之后,会检查这个xml文件中我们要申请的这个权限是不是已经获取到了,如果granted = true || flags = 0,那么就不会再弹窗了。

    相关文章

      网友评论

          本文标题:从源码角度看Android如何完成动态权限申请

          本文链接:https://www.haomeiwen.com/subject/dzqsydtx.html