戳蓝字“牛晓伟”关注我哦!
用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。
前言
在阅读本篇之前,建议先阅读下列文章:
本文摘要
本文是权限管理系统的第二篇文章。通过本文您将了解到PermissionManagerService服务的作用,如何管理权限,权限授予/撤销过程。(文中代码基于Android13)
本文大纲
1. 权限管理服务
2. 权限
3. 权限相关的数据类
4. 权限数据初始化
5. 权限管理
6. 权限授予/撤销
7. 总结
本文大纲每一小节的内容都是在为后面小节做铺垫,也就是理解了前一小节,那再去理解后一小节就更容易了。
1. 权限管理服务
大家好啊,我是PermissionManagerService,我的中文名是权限管理服务,我存在于systemserver进程,我也像PackageManagerService、ActivityManagerService这些服务一样也是一个binder服务,只不过没有它们“出名而已”。
既然是一个binder服务,那使用者如果想使用我提供的功能的话,可以通过PermissionManager和PermissionCheckerManager这两个管理类,这两个管理类已经把各种接口和binder通信的工作都已经封装好了,使用者直接调用即可。
我是权限管理系统中最核心的模块没有之一 (关于权限管理系统可以看这篇文章
)
,如果不相信可以看下我负责的工作,我主要负责权限管理、权限的授予/撤销、权限的检查这些工作,其中管理权限又分为管理声明的权限和管理App使用的权限。
关于我工作内容的事情先放一放,我先来给大家介绍下我管理的对象权限,因为权限对于我来说就是核心,没有权限那我也就没有存在的意义了,因此要想对我的工作有更深刻的认识,请允许我把权限介绍给大家。(若您对权限已经了解了,可以跳过此小节)
2. 权限
在Android中权限主要指的是App对于别的App提供的私有数据或者系统的私有资源是否具有访问或使用的权利。那该如何理解呢,举个例子比如联系人App是拥有所有联系人数据的,而这些数据是非常重要的,别的App想要使用的话就需要用户授予相应的权限给使用者;再比如App想要使用录音功能,则也需要用户授予录音权限给该App。
权限又分为声明权限和使用权限,声明权限就是权限的声明者进行权限的声明或者权限的定义,声明的权限是用来保护私有数据或者资源的。而使用权限就是私有数据或者资源的使用者使用相应的权限,只有相应权限经过用户授权后,使用者才可以访问或者使用相应私有数据或者资源。
关于权限的相关内容就简单介绍到此,在介绍我的主要工作内容之前,我还需要给大家介绍下权限相关的数据类。
2. 权限相关的数据类
权限相关的数据类就是权限管理服务设计了各种类来承载权限数据。我把权限相关的数据类介绍给大家,就是希望大家能明白我是使用了哪些类才把Android设备上各种各样的权限数据承载下来的。同时权限管理服务的工作内容其实就是对权限相关的数据类对象的属性进程查询、修改、删除操作,比如权限的授予/撤销就是找到相应的权限数据类对象,把该对象的相应属性置为授予/撤销;权限的检查就是找到相应的权限数据类对象,在把该对象的属性值返回。
权限分为声明权限和使用权限,那就先从声明权限相关的数据类讲起吧。
2.1 声明权限相关的数据类
我先给大家透漏个秘密,其实声明权限可以分为内置声明的权限、App声明的权限、动态声明的权限三种类型。
内置声明的权限非常的容易理解,就是系统内置了各种各样的权限,因为权限都得有一个所有者,也就是谁声明了权限,而内置声明权限的归属者就是包名为android的进程也就是systemserver进程。
App声明的权限这个就是在Apk的AndroidManifest.xml文件中声明的权限,该种类型的权限的所有者就是当前的App了。
动态声明的权限指的App运行期间,通过代码的方式声明一个权限 (调用ApplicationPackageManager的addPermission方法)。
不论是何种类型的声明权限,它们拥有的信息基本都是一致的,那我们就先来回顾下App中是如何声明权限的,App声明权限需要在AndroidManifest.xml文件中使用permission标签,如下样例:
<permission android:description="string resource"
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |
"signature" | ...] />
声明的权限包含以下字段:
description:指权限的描述,给用户解释这个权限的作用是啥。
icon、label:权限的icon和label,这个不用细说了。
name:权限的名字,它的作用类似于Map中的key,通过它可以找到声明的权限,它可是唯一的。
permissionGroup:权限所属的组,意思就是把各种权限可以分组,进行统一授权、统一管理。
protectionLevel:权限的级别,其中normal指明该权限是在Apk安装成功以后就会被授予,这种类型权限的授予不需要经过用户同意,比如网络权限;dangerous指明该权限是危险权限,是动态权限,只有在App运行期间,由用户来授予或者拒绝。当然还有别的类型的权限就不细说了。
以上就是声明一个权限的包含的关键信息,那就有请Permission类,是它承载了声明权限的所有信息,它的类图如下:
类图解释
Permission
mType:指明权限是哪种类型的,它的类型有TYPE_MANIFEST、TYPE_CONFIG、TYPE_DYNAMIC,它们分别对应上面的App声明的权限、内置声明的权限、动态声明的权限。
mUid:指明权限的所有者是谁,这用App的uid来指明,uid在Apk安装成功后就会分配一个全局唯一的id,就如人的身份证一样。
mPermissionInfo:该对象包含了权限的详细信息,如在AndroidManifest.xml文件中声明权限时候的关键要素。
PermissionInfo
protectionLevel:权限的级别,对应AndroidManifest.xml中声明权限时候的android:protectionLevel属性的值。
group:同样也是对应AndroidManifest.xml中声明权限时候的android:permissionGroup属性的值。
descriptionRes:指明描述的res。
name:代表权限的名字
通过一个Permission对象,可以知道声明权限的类型是啥,是哪个App声明了该权限,权限的详细信息是啥,一个Permission对象就对应一个声明的权限,不管该权限是App声明的权限,还是内置声明的权限,还是App动态声明的权限。而PermissionInfo类对象可以知道权限的详细信息,它被独立出来的原因是它会被共有。
一个Permission对象只对应一个声明的权限,而Android设备上所有声明的权限是如何存储的呢?答案非常简单就是使用ArrayMap这样的数据结构,它的key值为权限的name,它的value就是Permission对象。如下代码:
//PermissionRegistry类
private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
2.2 使用权限相关的数据类
要设计数据类来承载使用权限信息可就没有声明权限那么简单了,声明权限相关的数据类是不是非常的简单就用一个ArrayMap的数据结构就可以把Android设备上所有的声明权限全部承载下来。而使用权限却要复杂的多,到底复杂在哪呢?那就听我细细道来。
使用权限指的是App使用某个权限,因此使用权限它是与App相关联的,一个App可以使用多个权限,每一个被使用的权限还有状态一说,其中状态值有授予/拒绝,授予就是指该权限已经被用户授予给了该App,拒绝就是指该权限已经被用户拒绝了。而Android设备上安装的App还与用户有关,其中的用户指的是Android设备上的用户,就如PC上是可以存在多用户的,假如Android设备上存在多用户情况,那就有可能某个App在该用户下安装了,而在另一个用户下是没有被安装的。综上所述的复杂情况,最终得出的结论是使用权限与App有关,并且还与Android设备的用户有关,那基于如此复杂的情况该如何设计数据类来承载这些信息呢?
DevicePermissionState
那就先从Android设备的用户开始设计,我设计了一个类名字叫DevicePermissionState,它的定义如下:
public final class DevicePermissionState {
//mUserStates是SparseArray类型的,它的key值就是用户id,而它的value就是UserPermissionState
private final SparseArray<UserPermissionState> mUserStates = new SparseArray<>();
}
因为每个设备会可能包含多个用户,因此DevicePermissionState类有一个属性mUserStates,它的key值是用户id,而它的value值是UserPermissionState,也就是可以通过用户id从mUserStates拿到UserPermissionState(Android设备默认用户id是0)。DevicePermissionState就代表当前设备的所有用户的所有App的所有使用权限及权限状态,那UserPermissionState是啥呢?
UserPermissionState
同样也是先来看下它的定义,如下:
public final class UserPermissionState {
省略其他代码······
//mUidStates同样也是SparseArray类型的,它的key值就是uid,而它的value值是UidPermissionState
private final SparseArray<UidPermissionState> mUidStates = new SparseArray<>();
}
UserPermissionState就代表当前用户的所有App的所有使用权限及权限状态,而每一个App是用它的uid标注的 (uid就是App的唯一id),那UidPermissionState代表啥呢?
UidPermissionState
同样也是先来看下它的定义,如下:
public final class UidPermissionState {
省略其他代码······
//mPermissions是ArrayMap类型的,其他它的key值是权限name,它的value值是PermissionState
private ArrayMap<String, PermissionState> mPermissions;
}
UidPermissionState就代表某个App所有使用权限及权限状态,到了这是不是就能想到每个App在它的AndroidManifest.xml文件中声明使用多个权限。而这些权限和权限状态就是对应mPermissions属性,那PermissionState有代表啥呢?
PermissionState
同样也是想来看他的定义,如下:
public final class PermissionState {
//一个权限
private final e mPermission;
//权限是否授予
private boolean mGranted;
private int mFlags;
省略其他代码······
}
PermissionState就代表一个权限和它的状态,其中mPermission属性就代表权限信息,看到它是不是很熟悉啊,没错刚刚介绍过它。而mGranted则代表权限是否授予。
关于使用权限相关的数据类都已经介绍完毕了,那我用一张图总结下它们的关系吧:
2.3 小结
声明权限相关的数据类
一个声明权限对应一个Permission对象,而Android设备上所有声明的权限是使用ArrayMap这样的数据结构来存储的,它的key值为权限的name,它的value就是Permission对象,下面是相应代码:
//PermissionRegistry类
private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
使用权限相关的数据类
- DevicePermissionState对象存储了当前设备的所有用户的所有App的所有使用权限及权限状态,而通过设备的当前用户id是可以从该对象找到UserPermissionState对象。
- UserPermissionState对象存储了当前用户的所有App的所有使用权限及权限状态,通过某个App的uid是可以从该对象找到UidPermissionState对象。
- UidPermissionState对象存储了某个App的所有使用权限及权限状态,通过某个权限的name可以从该对象找到PermissionState对象。
- PermissionState对象存储了某个权限和它的权限状态。
也就是Android设备上所有App使用的所有权限及权限状态都是可以从DevicePermissionState对象找到的。
权限相关的数据类就介绍完毕了,不好意思大家,在介绍我的主要工作之前,还需要插入一个话题权限数据初始化,我权限管理服务在刚启动的时候,是没有任何的权限数据的,而只有进行了数据的初始化,才可以开始进入我的主要工作。因此权限数据初始化的内容是必须要提前介绍的。
4. 权限数据初始化
要介绍权限数据初始化,先从权限管理服务的启动开始讲起,我权限管理服务不像ActivityManagerService服务、PackageManagerService服务一样是直接在SystemServer中被启动的,而我是在PackageManagerService服务中被启动的,从此也可以看出我是率属于PackageManagerService服务的。
在我被启动后,要做的工作就是权限数据初始化,还记得上面介绍过,声明权限分为内置声明的权限、App声明的权限、动态声明的权限,而权限数据初始化第一步要做的事情就是内置声明的权限初始化。内置的权限数据被从系统文件中读取出来后,这些数据就会被交给我权限管理模块,进而把这些数据转换为声明权限相关的数据类对象。
权限数据初始化第二步要做的事情是App声明的权限初始化,还记得凡是App声明的权限都会被PackageManagerService服务的Settings存储到文件中吧,那该步所要做的事情就是从Settings中拿到已经读取出来的App声明的权限数据,进而把这些数据转换为声明权限相关的数据类对象。
权限数据初始化第三步要做的事情是App使用权限初始化,同样所有App使用的所有权限和权限状态也是被PackageManagerService服务的Settings存储到文件中的,那该步所要做的事情就是从Settings中拿到已经读取出来的App声明的权限数据,进而把这些数据转换为使用权限相关的数据类对象。
经过上面三步,权限管理服务中已经拥有了所有声明的权限数据和所有App使用的所有权限和权限状态数据,它们分别被放在如下代码中:
//下面是PermissionRegistry类
//声明的权限数据存放于mPermissions
private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
//下面是PermissionManagerServiceImpl类的属性
//所有App使用的所有权限和权限状态数据存放于mState
private final DevicePermissionState mState = new DevicePermissionState();
别以为做了上面三步,权限数据初始化就结束了,其实还有最后一步权限数据调整,PackageManagerService服务在启动的时候会做一件非常重要的事情扫描所有Apk,而这个扫描所有Apk对于权限管理服务来说是需要对已经准备好的权限数据进行一些调整的,这里的调整就是指有可能需要增加一些权限,有可能需要删除一些权限。为啥会有这些调整呢?其主要原因是因为在扫描所有Apk过程中,有些系统Apk有可能会升级,升级的话就有可能增加某些声明的权限,也有可能会删除某些声明的权限,因此就有此调整的过程。
在经过以上四个步骤后,权限数据初始化工作就结束了,权限管理服务也拥有了正确的全部的权限数据。有了这些铺垫工作,那在理解我的工作内容时就轻而易举了,先来介绍下我是如何管理权限的。
下面是相关代码,自行取阅:
//初始化内置声明权限代码
public PermissionManagerServiceImpl(@NonNull Context context,
@NonNull ArrayMap<String, FeatureInfo> availableFeatures) {
省略代码······
//systemConfig中已经从系统文件中把内置声明的权限已经读取出来了
SystemConfig systemConfig = SystemConfig.getInstance();
mSystemPermissions = systemConfig.getSystemPermissions();
// propagate permission configuration
final ArrayMap<String, SystemConfig.PermissionEntry> permConfig =
SystemConfig.getInstance().getPermissions();
synchronized (mLock) {
for (int i = 0; i < permConfig.size(); i++) {
final SystemConfig.PermissionEntry perm = permConfig.valueAt(i);
Permission bp = mRegistry.getPermission(perm.name);
if (bp == null) {
//初始化Permission,并且添加到mRegistry
bp = new Permission(perm.name, "android", Permission.TYPE_CONFIG);
mRegistry.addPermission(bp);
}
if (perm.gids != null) {
bp.setGids(perm.gids, perm.perUser);
}
}
}
}
//下面代码位于PackageManagerService的构造方法中
//mSettings.mPermissions中存储了所有的App声明的权限,交给PermissionManagerService进行初始化
mPermissionManager.readLegacyPermissionsTEMP(mSettings.mPermissions);
//初始化所有App使用的所有权限及权限状态
mPermissionManager.readLegacyPermissionStateTEMP();
5. 权限管理
有了上面内容的铺垫后,再来看权限管理就特别简单了,所有声明的权限数据是存放在类型为ArrayMap<String, Permission> 的数据结构中,而所有App使用的所有权限和权限状态数据是存放在DevicePermissionState对象中,相应代码如下:
//下面是PermissionRegistry类
//声明的权限数据存放于mPermissions
private final ArrayMap<String, Permission> mPermissions = new ArrayMap<>();
//下面是PermissionManagerServiceImpl类的属性
//所有App使用的所有权限和权限状态数据存放于mState
private final DevicePermissionState mState = new DevicePermissionState();
权限管理是权限管理服务最重要的功能,因为权限分为声明权限和使用权限,因此权限管理也分为声明权限的管理和使用权限的管理,而不管是哪种权限的管理所要做的核心事情就是对所有的权限进行增、删、查操作。
增加权限
当新安装一个Apk时,若Apk中存在声明权限,则会在PermissionRegistry的mPermissions属性中增加声明的权限;若Apk中存在使用权限,则会从DevicePermissionState对象中根据当前的用户id找到对应的UserPermissionState对象,在把使用权限及权限状态放入UidPermissionState对象中,并且把UidPermissionState对象放入UserPermissionState对象中。
当某个Apk升级时,若Apk中新增了使用权限或者新增了声明权限,则也基本上和上面的操作一致。
不管是增加使用权限还是增加声明权限,都需要PackageManagerService服务的Settings把新增的权限存储到文件中,新增的声明权限是放入/data/system/packages.xml文件中,而新增的使用权限是放入/data/misc_de/0/apexdata/com.android.permission/runtime-permissions.xml文件中 (路径中的0是用户id)。这样当Android设备重新启动的时候,Settings就会从这些文件中把权限信息读取出来,供权限管理服务初始化权限数据。
删除权限
当某个Apk被卸载,若Apk中存在声明权限,会从PermissionRegistry的mPermissions属性中把该权限移除,同时还需要对所有使用该权限的App进行调整;若Apk中存在使用权限,则会从DevicePermissionState对象中找到当前用户的UserPermissionState对象和该Apk对应的UidPermissionState对象,从UserPermissionState对象中把UidPermissionState对象信息删除。
当某个Apk升级时,若Apk中原先使用的权限信息删除了,则则会从DevicePermissionState对象中找到该Apk对应的UidPermissionState对象,进而从该对象中把使用的权限信息删除。
同样不管是删除使用权限还是删除声明权限,都需要PackageManagerService服务的Settings把删除的权限信息从文件中删除。
查询权限
对于声明权限来说,查询某个权限是非常简单的,只需要根据权限name从PermissionRegistry的mPermissions属性中查询到该权限信息并且返回即可。
对于使用权限来说,是要查询某个App的使用权限,这个查询过程就有些复杂,首先需要从DevicePermissionState对象中根据当前设备的用户id查询到UserPermissionState对象,进而从UserPermissionState对象中根据该App的uid查询到该App对应的UidPermissionState对象,进而根据权限name从该对象中查询到某个App的使用权限信息。
关于权限管理就介绍到这,接下来介绍下权限授予/撤销
6. 权限授予/撤销
同样有了上面权限管理的基础后,在来认识权限授予/撤销就非常简单了,权限授予就是用户为某App授予了某个权限,权限撤销就是用户撤销了某个App的某个权限。其实权限的授予/撤销流程基本都是一样的,我就单独介绍权限授予的流程。
权限授予的发起方是PermissionController App,它是一个单独的进程 (关于它的介绍可以看这篇文章
),PermissionController App进程应App的请求,会打开相应的授予权限界面,当用户点击该界面的允许按钮的时候,该请求会通过binder通信到达权限管理模块,而授予权限的过程就是首先根据请求权限的App的uid查询从DevicePermissionState对象中一步一步查询到该App对应的UidPermissionState对象,在从该对象中根据权限name查找到该权限对应的PermissionState对象,进而把该对象的mGranted属性值置为true,进而在把结果返回给PermissionController App。
以上就是权限授予的整个过程,是不是非常的简单。
7. 总结
本文主要介绍了PermissionManagerService服务,介绍了它的作用,它是如何管理权限的,权限授予/撤销的流程是啥样的。
下是我的知识星球,欢迎大家加入哦