Android权限管理原理(4.3-6.0)下篇

猪小花1号2018-08-29 11:23


Android6.0动态申请权限

申请权限可以通过V4包里面的ActivityCompat,它已经对不同版本做了兼容

ActivityCompat.java

 public static void requestPermissions(final @NonNull Activity activity,
            final @NonNull String[] permissions, final int requestCode) {
        if (Build.VERSION.SDK_INT >= 23) {
            ActivityCompatApi23.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);
                }
            });
        }
    }

可以看到,如果是6.0以下,直接通过PKMS查询是否在Manifest里面申请了权限,并把查询结果通过onRequestPermissionsResult回调传给Activity或者Fragment。其实这里只要在Manifest中声明了,就会默认是Granted。接着往下看:ActivityCompatApi23最终会调用activity.requestPermissions去请求权限。

Activity

public final void requestPermissions(@NonNull String[] permissions, int requestCode) {
    Intent intent = getPackageManager().buildRequestPermissionsIntent(permissions);
    startActivityForResult(REQUEST_PERMISSIONS_WHO_PREFIX, intent, requestCode, null);
}

Intent其实是通过PackageManager(ApplicationPackageManager实现类)获取的Intent

    public Intent buildRequestPermissionsIntent(@NonNull String[] permissions) {
    if (ArrayUtils.isEmpty(permissions)) {
       throw new NullPointerException("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;
}

这里首先是隐式的获取授权Activity组件相关信息(GrantPermissionsActivity),其实就是对话框样式的授权Activity,它是PackageInstaller系统应用里面的一个Activity。这里的getPermissionControllerPackageName其实就是获取相应的包名,

ApplicationPackageManager.java (android-6.0\frameworks\base\core\java\android\app)

@Override
public String getPermissionControllerPackageName() {
    synchronized (mLock) {
        if (mPermissionsControllerPackageName == null) {
            try {
                mPermissionsControllerPackageName = mPM.getPermissionControllerPackageName();
            } catch (RemoteException e) {
                throw new RuntimeException("Package manager has died", e);
            }
        }
        return mPermissionsControllerPackageName;
    }
}

最终通过PackageManagerService获取包名

PackageManagerService.java (android-6.0\frameworks\base\services\core\java\com\android\server\pm)

@Override
public String getPermissionControllerPackageName() {
    synchronized (mPackages) {
        return mRequiredInstallerPackage;
    }
}

mRequiredInstallerPackage这个变量具体赋值是在PMS的构造器中:对于原生Android 6.0,权限管理的APP跟安装器是同一个

mRequiredInstallerPackage = getRequiredInstallerLPr();

这里会得到PackageInstaller应用的相关信息,PackageInstaller负责应用的安装与卸载,里面还包含了对授权管理的一些逻辑。startActivityForResult启动的就是PackageInstaller中的GrantPermissionsActivity,该Activity主要负责权限的授予工作。

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

这是一个类似于对话框的悬浮窗样式的Activity

<style name="GrantPermissions" parent="Settings">
    <item name="android:windowIsFloating">true</item>
    <item name="android:windowElevation">@dimen/action_dialog_z</item>
    <item name="android:windowSwipeToDismiss">false</item>
</style>

之后就是动态更新权限流程:

如何动态更新RuntimePermission

通过上面的流程,我们进入了GrantPermissionsActivity,在这个Activity里面,如果一开始没有获得权限,就会弹出权限申请对话框,根据用户的操作去更新PKMS中的权限信息,同时将更新的结构持久化到runtime-permissions.xml中去。

GrantPermissionsActivity

GrantPermissionsActivity其实是利用GroupState对象与PKMS通信,远程更新权限的,当然,如果权限都已经授予了,那么就不需要再次弹出权限申请对话框。

public class GrantPermissionsActivity extends OverlayTouchActivity
        implements GrantPermissionsViewHandler.ResultListener {

    private LinkedHashMap<String, GroupState> mRequestGrantPermissionGroups = new LinkedHashMap<>();
    ....

    @Override
    public void onPermissionGrantResult(String name, boolean granted, boolean doNotAskAgain) {
        GroupState groupState = mRequestGrantPermissionGroups.get(name);
        if (groupState.mGroup != null) {
            if (granted) {

            <!--权限更新时机-->
                groupState.mGroup.grantRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_ALLOWED;
            } else {
                groupState.mGroup.revokeRuntimePermissions(doNotAskAgain);
                groupState.mState = GroupState.STATE_DENIED;
            }
            updateGrantResults(groupState.mGroup);
        }
        if (!showNextPermissionGroupGrantRequest()) {
            setResultAndFinish();
        }
    }

具体更新流程:

public boolean grantRuntimePermissions(boolean fixedByTheUser, String[] filterPermissions) {
    final int uid = mPackageInfo.applicationInfo.uid;

    // 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;
        }
            ...
            <!--一些关键点-->

            // Grant the permission if needed.
            if (!permission.isGranted()) {
                permission.setGranted(true);
                mPackageManager.grantRuntimePermission(mPackageInfo.packageName,
                        permission.getName(), mUserHandle);
            }
            // 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(true);
                    mPackageManager.updatePermissionFlags(permission.getName(),
                            mPackageInfo.packageName,
                            PackageManager.FLAG_PERMISSION_USER_FIXED
                                    | PackageManager.FLAG_PERMISSION_USER_SET,
                            0, mUserHandle);

可以看到最终还是调用PackageManager去更新App的运行时权限,最终走进PackageManagerService服务,

PackageManagerService

 @Override
    public void grantRuntimePermission(String packageName, String name, final int userId) {
        if (!sUserManager.exists(userId)) {
            Log.e(TAG, "No such user:" + userId);
            return;
        }

        ...一些检查

        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.GRANT_RUNTIME_PERMISSIONS,
                "grantRuntimePermission");

        enforceCrossUserPermission(Binder.getCallingUid(), userId,
                true /* requireFullPermission */, true /* checkShell */,
                "grantRuntimePermission");
                。。。。。
                ...
            uid = UserHandle.getUid(userId, pkg.applicationInfo.uid);
            sb = (SettingBase) pkg.mExtras;
            if (sb == null) {
                throw new IllegalArgumentException("Unknown package: " + packageName);
            }

            final PermissionsState permissionsState = sb.getPermissionsState();

              ...
              ...授权

            final int result = permissionsState.grantRuntimePermission(bp, userId);
            switch (result) {
                case PermissionsState.PERMISSION_OPERATION_FAILURE: {
                    return;
                }

                case PermissionsState.PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED: {
                    final int appId = UserHandle.getAppId(pkg.applicationInfo.uid);
                    mHandler.post(new Runnable() {
                        @Override
                        public void run() {
                            killUid(appId, userId, KILL_APP_REASON_GIDS_CHANGED);
                        }
                    });
                }
                break;
            }

            mOnPermissionChangeListeners.onPermissionsChanged(uid);


            <!--持久化-->    

            // Not critical if that is lost - app has to request again.
            mSettings.writeRuntimePermissionsForUserLPr(userId, false);
        }
 private static void enforceDeclaredAsUsedAndRuntimeOrDevelopmentPermission(PackageParser.Package pkg,
        BasePermission bp) {
    int index = pkg.requestedPermissions.indexOf(bp.name);
    if (index == -1) {
        throw new SecurityException("Package " + pkg.packageName
                + " has not requested permission " + bp.name);
    }
    if (!bp.isRuntime() && !bp.isDevelopment()) {
        throw new SecurityException("Permission " + bp.name
                + " is not a changeable permission type");
    }
}

首先要更新内存中的权限授予情况

PermissionsState.java

 private int grantPermission(BasePermission permission, int userId) {
    if (hasPermission(permission.name, userId)) {
        return PERMISSION_OPERATION_FAILURE;
    }

    final boolean hasGids = !ArrayUtils.isEmpty(permission.computeGids(userId));
    final int[] oldGids = hasGids ? computeGids(userId) : NO_GIDS;

    PermissionData permissionData = ensurePermissionData(permission);

    if (!permissionData.grant(userId)) {
        return PERMISSION_OPERATION_FAILURE;
    }

    if (hasGids) {
        final int[] newGids = computeGids(userId);
        if (oldGids.length != newGids.length) {
            return PERMISSION_OPERATION_SUCCESS_GIDS_CHANGED;
        }
    }

    return PERMISSION_OPERATION_SUCCESS;
}
    private PermissionData ensurePermissionData(BasePermission permission) {
    if (mPermissions == null) {
        mPermissions = new ArrayMap<>();
    }
    PermissionData permissionData = mPermissions.get(permission.name);
    if (permissionData == null) {
        permissionData = new PermissionData(permission);
        mPermissions.put(permission.name, permissionData);
    }
    return permissionData;
}

下一步,要将更新的权限持久化到文件中去 mSettings.writeRuntimePermissionsForUserLPr


RuntimePermission持久化

Settings.java

 public void writeRuntimePermissionsForUserLPr(int userId, boolean sync) {
    if (sync) {
        mRuntimePermissionsPersistence.writePermissionsForUserSyncLPr(userId);
    } else {
        mRuntimePermissionsPersistence.writePermissionsForUserAsyncLPr(userId);
    }
}

Settings.getPackageLPw这个方法,这是在安装应用扫描的时候scanPackageDirtyLI方法调用的,里面可以看到Settings类中的mUserIds、mPackages里面存的value还有PackageManagerService中的mPackages.pkg. mExtras都是同一个玩意奏是个PackageSetting。

  private File getUserRuntimePermissionsFile(int userId) {
    // TODO: Implement a cleaner solution when adding tests.
    // This instead of Environment.getUserSystemDirectory(userId) to support testing.
    File userDir = new File(new File(mSystemDir, "users"), Integer.toString(userId));
    return new File(userDir, RUNTIME_PERMISSIONS_FILE_NAME);
}

在目录data/system/0/runtime-permissions.xml存放需要运行时申请的权限,Android6.0以上才有

  <pkg name="com.snail.labaffinity">
    <item name="android.permission.CALL_PHONE" granted="true" flags="0" />
    <item name="android.permission.CAMERA" granted="false" flags="1" />
  </pkg>

RuntimePermission恢复(其实这里也包含普通权限)

这些持久化的数据会在手机启动的时候由PMS读取,开机启动,PKMS扫描Apk,并更新package信息,检查/data/system/packages.xml是否存在,这个文件是在解析apk时由writeLP()创建的,里面记录了系统的permissions,以及每个apk的name,codePath,flags,ts,version,uesrid等信息,这些信息主要通过apk的AndroidManifest.xml解析获取,解析完apk后将更新信息写入这个文件并保存到flash,下次开机直接从里面读取相关信息添加到内存相关列表中,当有apk升级,安装或删除时会更新这个文件,packages.xml放的只包括installpermission,runtimepermissiono由runtime-permissions.xml存放。

public PackageManagerService(Context context, Installer installer,
        boolean factoryTest, boolean onlyCore) {
    ....
    mSettings = new Settings(mPackages);

          //汇总并更新和Permission相关的信息

  updatePermissionsLPw(null, null, true,

                           regrantPermissions,regrantPermissions);

   //将信息写到package.xml、package.list及package-stopped.xml文件中
   mSettings.writeLPr();

    ....
    mFirstBoot = !mSettings.readLPw(sUserManager.getUsers(false));

Settings(File dataDir, Object lock) {

    mRuntimePermissionsPersistence = new RuntimePermissionPersistence(mLock);

<!--加载package信息-->

根据SettingsFile或者BackupSettingsFile读取相应的设置信息 生成PackageSetting对象,里面有权限列表字段protected final PermissionsState mPermissionsState;,之后再运行中,动态权限的操作都是针对这个对象

boolean readLPw(@NonNull List<UserInfo> users) {
    FileInputStream str = null;
    if (mBackupSettingsFilename.exists()) {
        try {
            str = new FileInputStream(mBackupSettingsFilename);
            mReadMessages.append("Reading from backup settings file\n");
     ...
        while ((type = parser.next()) != XmlPullParser.END_DOCUMENT
                && (type != XmlPullParser.END_TAG || parser.getDepth() > outerDepth)) {

            String tagName = parser.getName();
            if (tagName.equals("package")) {

       !--读取package信息,包括install权限信息(对于Android6.0package.xml)-->

    readPackageLPw(parser); 
         ...

      <!--读取runtime权限信息-->

    for (UserInfo user : users) {
        mRuntimePermissionsPersistence.readStateForUserSyncLPr(user.id);
    }
}


private void readPackageLPw(XmlPullParser parser) throws XmlPullParserException, IOException {
    String name = null;
     ...
    (tagName.equals(TAG_PERMISSIONS)) {
            readInstallPermissionsLPr(parser,
                        packageSetting.getPermissionsState());

之后就可以checkpermission了

   @Override
    public int checkUidPermission(String permName, int uid) {
        final int userId = UserHandle.getUserId(uid);

        if (!sUserManager.exists(userId)) {
            return PackageManager.PERMISSION_DENIED;
        }

        synchronized (mPackages) {
            Object obj = mSettings.getUserIdLPr(UserHandle.getAppId(uid));
            if (obj != null) {
                final SettingBase ps = (SettingBase) obj;
                final PermissionsState permissionsState = ps.getPermissionsState();
                if (permissionsState.hasPermission(permName, userId)) {
                    return PackageManager.PERMISSION_GRANTED;
                }

原来的权限存放位置在哪?不会都从Android Manifest清单去读取,只会在启动时读取一次。Android6.0之前会吧所有的权限都放置在data/system/packages.xml文件中。Android6.0之后,分为运行时权限跟普通权限,普通权限还是放在data/system/packages.xml中,运行时权限防止在data/system/users/0/runtime-permissions.xml文件中。根据运行时是否动态申请去更新权限。

Android6.0申请普通权限会怎么样

Android6.0里,普通权限仍然按照运行时权限的模型,只是granted="true",就是永远是取得授权的。所以可以直接获得权限申请成功的回调。如果查看packages.xml,就会发现:如下信息:

<perms>
    <item name="android.permission.INTERNET" granted="true" flags="0" />
    <item name="android.permission.ACCESS_WIFI_STATE" granted="true" flags="0" />
</perms>

Android的关键节点,在哪里?

关键节点并不是查询是否具有该权限,Android6.0之前,权限查询是不会触发权限申请与授权的,只有在请求系统服务的时候,由系统服务调用AppopsManager去查询是否赋予了该权限,第一次未操作肯定是null,未赋予就可能会触发权限申请逻辑,这个点在各个系统服务内部,由AppOpsService服务统一管理,不过对于官方的Release版本,其实只有系统通知APP才有动态权限管理的能力,其他都没有操作能力。Android6.0之后,便是显示的runtime-permission,将申请逻辑交给App分别申请,虽然仍有后端维护,但是,相对还是灵活些。

作者水平有限,文章有不准确、不完善的地方,希望大家包涵指正!


相关阅读:Android权限管理原理(4.3-6.0)上篇

网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者李尚授权发布。