阅读 1018

Android权限申请解析

前言

google在 Android 6.0 开始引入了权限申请机制,将所有权限分成了正常权限危险权限。App每次在使用危险权限时需要动态的申请并得到用户的授权才能使用,否则会抛出异常。

那么权限申请、校验又是如何实现的呢?这也是我们这篇笔记的要分析的重点。

权限的分类:

系统权限分为两类:正常权限 和 危险权限。

正常权限不会直接给用户隐私权带来风险。如果您的应用在其清单中列出了正常权限,系统将自动授予 该权限。

危险权限会授予应用访问用户机密数据的权限。如果您的应用在其清单中列出了正常权限,系统将自动 授予该权限。如果您列出了危险权限,则用户必须明确批准您的应用使用这些权限。

核心方法简介

  • ContextCompat.checkSelfPermission 检查应用是否具有某个危险权限。
    • 如果应用具有此权限,方法将返回 PackageManager.PERMISSION_GRANTED,并且应用可以继续操作。
    • 如果应用不具有此权限,方法将返回 PackageManager.PERMISSION_DENIED,且应用必须明确向用户要求权限。
  • ActivityCompat.shouldShowRequestPermissionRationale 判断是否之前拒绝过某个权限的申请
    • 如果应用之前请求过此权限但用户拒绝了请求,此方法将返回 true。
    • 如果用户在过去拒绝了权限请求,并在 权限请求系统对话框中选择了 Don't ask again 选项,此方法将返回 false。
    • 如果设备禁止应用具有该权限,此方法也会返回 false。
  • ActivityCompat.requestPermissions 应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。
  • Activity.onRequestPermissionsResult 当应用请求权限时,系统将向用户显示一个对话框。当用户响应时,系统将调用应用的 onRequestPermissionsResult() 方法,向其传递用户响应,处理对应的场景。

Android 权限申请流程开放给开发人员核心的方法主要有4个,通过这4个核心方法开发者就能实现我们应用的权限申请。

权限申请流程

从业务层面上拆解权限申请步骤如下图所示

通过上面的步骤,我们来进一步深入源码层来了解PKMS 是如何控制我们应用的权限的

权限检查一 ContextCompat.checkSelfPermission

ContextCompat.checkSelfPermission => ContextImpl.checkPermission => AMS.checkPermission =>AMS.checkComponentPermission => AM.checkComponentPermission

权限检查会跨进程Binder 调用AMS.checkPermission接着一步步调用到AM.checkComponentPermission

AM.checkComponentPermission

public static int checkComponentPermission(String permission, int uid,
        int owningUid, boolean exported) {
    // Root, system server get to do everything.
    final int appId = UserHandle.getAppId(uid);
  	//如果uid 是ROOT_UID 或者 系统Uid 则直接拥有权限
    if (appId == Process.ROOT_UID || appId == Process.SYSTEM_UID) {
        return PackageManager.PERMISSION_GRANTED;
    }
    // Isolated processes don't get any permissions.
  	//孤立进程没有任何权限,普通应用进程都不是孤立进程
    if (UserHandle.isIsolated(uid)) {
        return PackageManager.PERMISSION_DENIED;
    }
    // If there is a uid that owns whatever is being accessed, it has
    // blanket access to it regardless of the permissions it requires.
  	//拥有权限的id 和申请的id 是相同的 则直接拥有该权限
    if (owningUid >= 0 && UserHandle.isSameApp(uid, owningUid)) {//owningUid == -1 不执行下面代码
        return PackageManager.PERMISSION_GRANTED;
    }
    // If the target is not exported, then nobody else can get to it.
    if (!exported) { //exported == true 不执行下面代码
        /*
        RuntimeException here = new RuntimeException("here");
        here.fillInStackTrace();
        Slog.w(TAG, "Permission denied: checkComponentPermission() owningUid=" + owningUid,
                here);
        */
        return PackageManager.PERMISSION_DENIED;
    }
    if (permission == null) {
        return PackageManager.PERMISSION_GRANTED;
    }
    try {
      	//获取PAKMS Binder
        return AppGlobals.getPackageManager()
                .checkUidPermission(permission, uid);
    } catch (RemoteException e) {
        throw e.rethrowFromSystemServer();
    }
}
复制代码

AppGlobals.getPackageManager => ActivityThread.getPackageManager

ActivityThread.getPackageManager

public static IPackageManager getPackageManager() {
    if (sPackageManager != null) {
        return sPackageManager;
    }
    final IBinder b = ServiceManager.getService("package");
  	//sPackageManager == PackageManagerService 实例
    sPackageManager = IPackageManager.Stub.asInterface(b);
    return sPackageManager;
}
复制代码

因为PKMS 和 AMS 在同一个进程,所以这边的 sPackageManager 就是继承了IPackageManager.Stub 的 PackageManagerService 实例。接着就进入PackageManagerService.checkUidPermission

PackageManagerService.checkUidPermission => PermissionManagerService.checkUidPermission

PermissionManagerService.checkUidPermission

public int checkUidPermission(String permName, int uid) {
    // Not using Objects.requireNonNull() here for compatibility reasons.
    if (permName == null) {
        return PackageManager.PERMISSION_DENIED;
    }
    final int userId = UserHandle.getUserId(uid);
    if (!mUserManagerInt.exists(userId)) {
        return PackageManager.PERMISSION_DENIED;
    }
		
    final CheckPermissionDelegate checkPermissionDelegate;
    synchronized (mLock) {
        checkPermissionDelegate = mCheckPermissionDelegate;
    }
    if (checkPermissionDelegate == null)  {
      	//默认情况执行这里
        return checkUidPermissionImpl(permName, uid);
    }
  	//这个用于外面拦截,可以做一些额外操作,不过最终还是执行 checkUidPermissionImpl
    return checkPermissionDelegate.checkUidPermission(permName, uid,
            this::checkUidPermissionImpl);
}
复制代码

PermissionManagerService.checkUidPermissionImpl

private int checkUidPermissionImpl(String permName, int uid) {
  	//1.PackageManagerInternalImpl 获取应用的package信息
    final AndroidPackage pkg = mPackageManagerInt.getPackage(uid);
  	//2.校验该应用是否已经拥有该权限
    return checkUidPermissionInternal(pkg, uid, permName);
}
复制代码
//PackageManagerService.java PackageManagerInternalImpl内部类
public AndroidPackage getPackage(int uid) {
    synchronized (mLock) {
        final String[] packageNames = getPackagesForUidInternal(uid, Process.SYSTEM_UID);
        AndroidPackage pkg = null;
        final int numPackages = packageNames == null ? 0 : packageNames.length;
        for (int i = 0; pkg == null && i < numPackages; i++) {
            pkg = mPackages.get(packageNames[i]);
        }
        return pkg;
    }
}
复制代码

PackageManagerServicemPackages集合中获取对应app的package

PermissionManagerService.checkUidPermissionInternal => PermissionManagerService.checkPermissionInternal => PermissionManagerService.checkSinglePermissionInternal

PermissionManagerService.checkSinglePermissionInternal

private boolean checkSinglePermissionInternal(int uid,
        @NonNull PermissionsState permissionsState, @NonNull String permissionName) {
  	//当前应用 permissionsState存有应用权限形象,调用该方法确认是否有权限
    if (!permissionsState.hasPermission(permissionName, UserHandle.getUserId(uid))) {
        return false;
    }
  	//InstantApp 相关逻辑
    if (mPackageManagerInt.getInstantAppPackageName(uid) != null) {
        return mSettings.isPermissionInstant(permissionName);
    }
    return true;
}
复制代码

PermissionsState.hasPermission

public boolean hasPermission(String name, int userId) {
    synchronized (mLock) {
        if (mPermissions == null) {
            return false;
        }
      	//permissionData 保存应用权限申请情况
        PermissionData permissionData = mPermissions.get(name);
        return permissionData != null && permissionData.isGranted(userId);
    }
}
复制代码

权限检查二 ActivityCompat.shouldShowRequestPermissionRationale

检查权限拒绝情况

ActivityCompat.shouldShowRequestPermissionRationale => Activity.shouldShowRequestPermissionRationale => PKMS.shouldShowRequestPermissionRationale 跨进程 Binder 调用

PKMS.shouldShowRequestPermissionRationale

public boolean shouldShowRequestPermissionRationale(String permName,
        String packageName, int userId) {
    final int callingUid = Binder.getCallingUid();
		//...
  	//如果权限已申请也会返回false,所以先要进行权限检查一在进入权限检查二
    if (checkPermission(permName, packageName, userId)
            == PackageManager.PERMISSION_GRANTED) {
        return false;
    }

    final int flags;

    final long identity = Binder.clearCallingIdentity();
    try {
      	//返回权限申请操作的标记
        flags = getPermissionFlagsInternal(permName, packageName, callingUid, userId);
    } finally {
        Binder.restoreCallingIdentity(identity);
    }

    final int fixedFlags = PackageManager.FLAG_PERMISSION_SYSTEM_FIXED
            | PackageManager.FLAG_PERMISSION_POLICY_FIXED
            | PackageManager.FLAG_PERMISSION_USER_FIXED;
		//判断是否是用户强制拒绝(不在提醒)或系统拒绝或政策拒绝
    if ((flags & fixedFlags) != 0) {
        return false;
    }
		//...
		// 判断是否是用户点击取消
    return (flags & PackageManager.FLAG_PERMISSION_USER_SET) != 0;
}
复制代码

PKMS.getPermissionFlagsInternal

获取权限申请标记

private int getPermissionFlagsInternal(
        String permName, String packageName, int callingUid, int userId) {
    //...
    synchronized (mLock) {
      	//如果没有权限申请记录,直接返回 0
        if (mSettings.getPermissionLocked(permName) == null) {
            return 0;
        }
    }
    //...
    PermissionsState permissionsState = ps.getPermissionsState();
  	//获取权限标记
    return permissionsState.getPermissionFlags(permName, userId);
}
复制代码

权限申请 ActivityCompat.requestPermissions

应用可以通过这个方法动态申请权限,调用后会弹出一个对话框提示用户授权所申请的权限。

ActivityCompat.requestPermissions => Activity.requestPermissions

Activity.requestPermissions

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
  	//通过PKMS来封装隐式跳转 permission.ui.GrantPermissionsActivity Intent 
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
    mHasCurrentPermissionsRequest = true;
}
复制代码

Activity.getPackageManager => ContextImpl.getPackageManager => ApplicationPackageManager.getPackageManager

ApplicationPackageManager 派生自PackageManager 所以 getPackageManager().buildRequestPermissionsIntent == PackageManager.buildRequestPermissionsIntent

PackageManager.buildRequestPermissionsIntent

public static final String ACTION_REQUEST_PERMISSIONS = "android.content.pm.action.REQUEST_PERMISSIONS";
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,用来打开系统的应用。那么打开什么应用呢?我们可以从ACTION_REQUEST_PERMISSIONS 的值入手

<!-- PackageInstaller AndroidManifest.xml -->
<activity android:name="com.android.permissioncontroller.permission.ui.GrantPermissionsActivity"
        android:configChanges="keyboardHidden|screenSize"
        android:excludeFromRecents="true"
        android:theme="@style/GrantPermissions.FilterTouches"
        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>
复制代码

我们通过 android.content.pm.action.REQUEST_PERMISSIONS 这个action 一个个系统应用的找,最终找到对应Activity

GrantPermissionsActivity.showNextPermissionGroupGrantRequest

GrantPermissionsActivity.onCreate中调用GrantPermissionsActivity.showNextPermissionGroupGrantRequest来进行权限组的申请,这也就表明当你申请权限组某个权限时,也就是申请整个权限组的权限,申请成功你将拥有整个权限组的权限

private boolean showNextPermissionGroupGrantRequest() {
    int numGroupStates = mRequestGrantPermissionGroups.size();
  	//确定权限申请个数
    int numGrantRequests = 0;
    for (int i = 0; i < numGroupStates; i++) {
      	//做过滤,排除不需要申请和重复的权限申请
        if (shouldShowRequestForGroupState(mRequestGrantPermissionGroups.valueAt(i))) {
            numGrantRequests++;
        }
    }
    int currentIndex = 0;
  	//...
    for (GroupState groupState : groupStates) {
        if (!shouldShowRequestForGroupState(groupState)) {
            continue;
        }
        if (groupState.mState == GroupState.STATE_UNKNOWN) {

            //...权限申请UI内容初始化

            // Set the permission message as the title so it can be announced.
            setTitle(message);
						//显示权限申请UI numGrantRequests 总的权限组申请个数; currentIndex 当前申请到第几个
            mViewHandler.updateUi(groupState.mGroup.getName(), numGrantRequests, currentIndex,
                    icon, message, detailMessage, mButtonVisibilities);

            return true;
        }

        if (groupState.mState != GroupState.STATE_SKIPPED) {
            currentIndex++;
        }
    }
		//没有可申请的权限了
    return false;
}
复制代码

mViewHandler 在 onCreate 时候创建的 com.android.permissioncontroller.permission.ui.handheld.GrantPermissionsViewHandlerImpl 实例。接下来我们看mViewHandler相关的点击事件

mViewHandler.onClick

public void onClick(View view) {
    int id = view.getId();
    if (id == R.id.grant_singleton) {
        if (mResultListener != null) {
          	//取消权限申请
            mResultListener.onPermissionGrantResult(mGroupName, CANCELED);
        } else {
            mActivity.finish();
        }
        return;
    }
    int button = -1;
    try {
        button = BUTTON_RES_ID_TO_NUM.get(id);
    } catch (NullPointerException e) {
        // Clicked a view which is not one of the defined buttons
        return;
    }
    switch (button) {
        case ALLOW_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//始终同意权限申请
                mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ALWAYS);
            }
            break;
        case ALLOW_FOREGROUND_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//只允许前台使用权限
                mResultListener.onPermissionGrantResult(mGroupName,
                        GRANTED_FOREGROUND_ONLY);
            }
            break;
        case ALLOW_ALWAYS_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//始终同意权限申请
                mResultListener.onPermissionGrantResult(mGroupName,
                        GRANTED_ALWAYS);
            }
            break;
        case ALLOW_ONE_TIME_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//仅此一次
                mResultListener.onPermissionGrantResult(mGroupName, GRANTED_ONE_TIME);
            }
            break;
        case DENY_BUTTON:
        case NO_UPGRADE_BUTTON:
        case NO_UPGRADE_OT_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//拒绝权限申请
                mResultListener.onPermissionGrantResult(mGroupName, DENIED);
            }
            break;
        case DENY_AND_DONT_ASK_AGAIN_BUTTON:
        case NO_UPGRADE_AND_DONT_ASK_AGAIN_BUTTON:
        case NO_UPGRADE_OT_AND_DONT_ASK_AGAIN_BUTTON:
            if (mResultListener != null) {
                view.performAccessibilityAction(
                        AccessibilityNodeInfo.ACTION_CLEAR_ACCESSIBILITY_FOCUS, null);
              	//拒绝权限申请 并不再提示
                mResultListener.onPermissionGrantResult(mGroupName,
                        DENIED_DO_NOT_ASK_AGAIN);
            }
            break;
    }
}
复制代码

mResultListener负责点击交互回调,在GrantPermissionsActivity.onCreate中设置。接着看下回调的处理。

GrantPermissionsActivity.onPermissionGrantResult

public void onPermissionGrantResult(String name,
        @GrantPermissionsViewHandler.Result int result) {
    GroupState foregroundGroupState = getForegroundGroupState(name);
    GroupState backgroundGroupState = getBackgroundGroupState(name);

  	//...
    switch (result) {
        case CANCELED://取消
            if (foregroundGroupState != null) {
                reportRequestResult(foregroundGroupState.affectedPermissions,
                        PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED);
            }
            if (backgroundGroupState != null) {
                reportRequestResult(backgroundGroupState.affectedPermissions,
                        PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_IGNORED);
            }
            setResultAndFinish();
            return;
        case GRANTED_ALWAYS :
            if (foregroundGroupState != null) {
                onPermissionGrantResultSingleState(foregroundGroupState, true, false, false);
            }
            if (backgroundGroupState != null) {
                onPermissionGrantResultSingleState(backgroundGroupState, true, false, false);
            }
            break;
        case GRANTED_FOREGROUND_ONLY :
            if (foregroundGroupState != null) {
                onPermissionGrantResultSingleState(foregroundGroupState, true, false, false);
            }
            if (backgroundGroupState != null) {
                onPermissionGrantResultSingleState(backgroundGroupState, false, false, false);
            }
            break;
        case GRANTED_ONE_TIME:
            if (foregroundGroupState != null) {
                onPermissionGrantResultSingleState(foregroundGroupState, true, true, false);
            }
            if (backgroundGroupState != null) {
                onPermissionGrantResultSingleState(backgroundGroupState, false, true, false);
            }
            break;
        case DENIED :
            if (foregroundGroupState != null) {
                onPermissionGrantResultSingleState(foregroundGroupState, false, false, false);
            }
            if (backgroundGroupState != null) {
                onPermissionGrantResultSingleState(backgroundGroupState, false, false, false);
            }
            break;
        case DENIED_DO_NOT_ASK_AGAIN :
            if (foregroundGroupState != null) {
                onPermissionGrantResultSingleState(foregroundGroupState, false, false, true);
            }
            if (backgroundGroupState != null) {
                onPermissionGrantResultSingleState(backgroundGroupState, false, false, true);
            }
            break;
    }
		//进入下一个权限申请,直到所有权限申请完毕
    if (!showNextPermissionGroupGrantRequest()) {
        setResultAndFinish();
    }
}
复制代码

onPermissionGrantResult方法里主要是以下两个方法

  • onPermissionGrantResultSingleState 更新权限状态
  • showNextPermissionGroupGrantRequest申请下一个权限,重复权限申请第一步

GrantPermissionsActivity.onPermissionGrantResultSingleState

private void onPermissionGrantResultSingleState(GroupState groupState, boolean granted,
        boolean isOneTime, boolean doNotAskAgain) {
    if (groupState != null && groupState.mGroup != null
            && groupState.mState == GroupState.STATE_UNKNOWN) {
        if (granted) {
            int permissionGrantRequestResult =
                    PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED;

            if (isOneTime) {
                groupState.mGroup.setOneTime(true);
                permissionGrantRequestResult =
                        PERMISSION_GRANT_REQUEST_RESULT_REPORTED__RESULT__USER_GRANTED_ONE_TIME;
            } else {
                groupState.mGroup.setOneTime(false);
            }

            groupState.mGroup.grantRuntimePermissions(true, doNotAskAgain,
                    groupState.affectedPermissions);
            groupState.mState = GroupState.STATE_ALLOWED;
        } else {
            groupState.mGroup.revokeRuntimePermissions(doNotAskAgain,
                    groupState.affectedPermissions);
            groupState.mGroup.setOneTime(false);
            groupState.mState = GroupState.STATE_DENIED;
        }
    }
}
复制代码

这个方法的核心目的就是将groupState权限状态更新,更新完之后就是将结果返回setResultAndFinish

权限回调

在GrantPermissionsActivity权限申请完毕之后并调用setResultAndFinish。通过AMS跨进程调用,将结果返回ActivityThread.handleResumeActivity => ActivityThread.performResumeActivity => ActivityThread.deliverResults => Activity.dispatchActivityResult

Activity.dispatchActivityResult

void dispatchActivityResult(String who, int requestCode, int resultCode, Intent data,
        String reason) {
    mFragments.noteStateNotSaved();
    if (who == null) {
        onActivityResult(requestCode, resultCode, data);
    } else if (who.startsWith(REQUEST_PERMISSIONS_WHO_PREFIX)) {
      	//activity的权限申请回调
        who = who.substring(REQUEST_PERMISSIONS_WHO_PREFIX.length());
        if (TextUtils.isEmpty(who)) {
          	//分发权限申请结果
            dispatchRequestPermissionsResult(requestCode, data);
        } else {
          	//fragment的权限申请回调
            Fragment frag = mFragments.findFragmentByWho(who);
            if (frag != null) {
                dispatchRequestPermissionsResultToFragment(requestCode, data, frag);
            }
        }
    } //...
}
复制代码

Activity.dispatchRequestPermissionsResult

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);
}

public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions,
        @NonNull int[] grantResults) {
    /* callback - no nothing */
}
复制代码

最后应用重载onRequestPermissionsResult处理权限结果回调

完结

整个权限申请逻辑还是比较清晰,期间的跨进程调用需要对Binder,AMS,PKMS 有一定的了解。

PKMS 分析 juejin.cn/post/688292…

Activity启动流程 juejin.cn/post/687819…

AMS 启动流程 juejin.cn/post/687705…

Binder 原理 gityuan.com/2015/10/31/…

文章分类
Android
文章标签