从本文开始将介绍权限管理系统,权限管理系统同样也是一个系列文章,本文是权限管理系统的第一篇文章。通过本文您将了解到权限管理系统都做了哪些事情,权限管理系统分为哪些模块,模块之间是如何协同合作的。(文中代码基于Android13)
为了对权限管理系统有一个深入的认识,我觉得非常有必要先把权限介绍清楚,故第一小节的题目就是权限为何物 (如果您对权限了解,可以跳过此节)在Android中权限主要指的是App对于别的App提供的私有数据或者系统的私有资源是否具有访问或使用的权利,那该如何理解呢,举个例子比如联系人App是拥有所有联系人数据的,而这些数据是非常重要的,别的App想要使用的话就需要用户授予相应的权限给使用者;再比如App想要使用录音功能,则也需要用户授予录音权限给该App。权限也有分类,比如可以分为静态权限、运行时权限 (runtime permission)、特权权限等,静态权限是指在Apk安装期间不需要经过用户同意就直接授予的权限,比如网络访问权限;运行时权限是指在App运行期间需要用户授权的权限,比如录音、地理位置权限等;特权权限是指只有特权App可以使用的权限。后面会有章节对权限进行详细的介绍。1.1 权限的使用
在Android中App使用某个权限,就是在AndroidManifest中通过uses-permission标签来使用权限,如下代码:(使用了录音权限)<uses-permission android:name="android.permission.RECORD_AUDIO" />
声明权限就是指App私有数据或者系统资源提供者会定义一些权限,使用者只有用户授予了这些权限才可以访问或使用这些资源。权限管理系统都已经定义了一套框架,权限声明者只需要声明相应的运行时权限 (当然可以声明别的权限),在资源使用者使用资源之前,对该资源使用者进行授权鉴定,如果授予了相应权限,则可以使用资源;否则禁止使用资源。声明权限需要在AndroidManifest.xml文件中使用permission标签,如下例子:"string resource"
android:icon="drawable resource"
android:label="string resource"
android:name="string"
android:permissionGroup="string"
android:protectionLevel=["normal" | "dangerous" |
"signature" | ...] />
关于权限就介绍到此,那我们还是回归到我们的主题权限管理系统。
权限管理系统所做的事情大体可以分为管理声明的权限、管理App使用的权限、权限的授予/撤销、资源使用记录这四类。管理声明的权限就是会对Android设备上所有声明的权限进行管理,其中所有声明的权限包括系统声明的权限和App声明的权限。管理工作包括权限的存储、删除、查询等,其中的存储指的是存储到内存和文件中。比如某新安装Apk声明了权限,则会把声明的权限保存;比如某Apk被卸载了,则会把声明的权限删除。而查询服务是使用最多的,凡是App使用到的权限都需要为它们提供查询服务,比如某App使用了某个权限,是需要经过权限管理系统确认该权限是否是存在的,要确定是否存在,就需要从所有声明的权限中来进行查询。管理App使用的权限就是会对Android设备上所有App使用的权限及权限状态进行管理,比如某App使用了哪些权限,这些权限哪些是被用户授予了、哪些是被用户拒绝了等都会进行存储、删除、更新、查询等操作。每个App使用的权限和权限状态都会被保存到文件中,下次Android设备重新启动的时候会从保存的文件中把这些信息读取出来。App使用某个权限时,是需要确认该权限是否被授予,而这个确认过程是需要从权限管理系统来查询的。同样App权限的声明者也需要知道某个App使用的权限是否被用户授予了,同样也需要从权限管理系统来查询。权限的授予/撤销是指App使用的权限是需要用户授予该权限或者撤销该权限的。资源使用记录指的是记录下资源使用的时间点以及时长 (如果有时长的话),比如录音操作,权限管理系统会记录下该App在哪个时刻使用了录音功能,以及录音功能使用了多久。权限管理系统为了能把它所做的事情做得井井有条,全靠它的模块之间完美的协同合作,那就允许我把它的各个模块介绍给大家,老规矩为了大家有一个直观的感受,先来看如下的图:如上图,权限管理系统大体分为权限控制器App、管理类、服务类三大模块,而有的模块中又会被进一步的划分,那我就从下往上的顺序来介绍。
2.1 服务类
权限管理系统中的服务类都是位于systemserver进程,那我们就来介绍下它们。2.1.1 权限管理服务 (PermissionManagerService)
PermissionManagerService它负责了权限管理系统中大部分的功能,凡是声明权限的管理、App使用权限的管理、权限的授予/撤销都归该服务负责,当然这里面要排除一点就是不管是App声明权限还是App使用的权限的存储可是不归它负责,而是归PackageManagerService负责。当然在后面章节会再次详细的介绍它。2.1.2 App操作服务 (AppOpsService)
AppOpsService负责App操作方面的事情,我第一次接触这个类的时候,对它的存在充满了疑惑,PermissionManagerService服务已经把权限大部分的功能都负责了,那该类存在的作用是啥呢,当我看到对该类的功能描述是权限控制和跟踪,我更是满脑子的疑惑,PermissionManagerService不是也有权限控制的功能吗,为啥AppOpsService也有权限控制的功能,这不重复了吗?我持着要解开该类谜团的目的对该类进行分析,最终得出的结论是这个类负责的权限控制主要是对运行时权限的补充,那我就来解释下:PermissionManagerService管理的App使用的权限状态,它其实是一个静态值 (被授予或者被拒绝),该权限状态并没有结合App的状态来做出相应的调整。为啥需要结合App状态进行调整呢,运行时权限的状态分为授予和拒绝两种,其中授予还可以分为前台授予和授予两种,前台授予指该权限被授予后,该权限保护的资源在开始被使用的时机只能是App处于前台的时候。这句话是不是很绕,那我就举个例子:比如某App的录音权限已经被授予了,如果App这时候在后台,这时候App想要开启录音功能的话,肯定是禁止的,你想啊一个App悄悄的在后台录取你的谈话内容是多么恐怖的一件事情啊,因此对于已经授予录音权限的App想要开启录音功能只能是App处于前台。也就是针对录音、拍照、定位等这类长时使用资源的权限才具有
前台授予的权限状态。就是因为PermissionManagerService是没有结合App的状态的原因,故AppOpsService就诞生了,它的权限控制是指它结合了App的状态 (如App前后台)来对已经授予的运行时权限再次重新计算,给出一个真正的权限状态,比如某App的录音权限已经授予了,AppOpsService会结合当前App的前后台状态,如果App位于前台则会告知App录音权限真正的被授予,可以开启录音功能;如果App位于后台,则会告知App录音权限被拒绝了,不能开启录音功能。AppOpsService除了权限控制,还可以记录被权限包含的资源的使用情况,如何时使用的,使用时长是多久等。在后面的章节还会有详细的文章介绍到它。2.1.3 包管理服务 (PackageManagerService)
PackageManagerService它会为权限管理系统中的权限 (App声明的权限和使用的权限) 提供权限存储的功能。当Android设备重新启动的时候,会把存储的这些信息读取出来。App使用的权限及权限状态存储,如下存储了某度App使用到的权限和权限状态:<package name="com.baidu.searchbox">
# DOWNLOAD_WITHOUT_NOTIFICATION权限被允许
<permission name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" granted="true" flags="0" />
# 定位权限 被拒绝
<permission name="android.permission.POST_NOTIFICATIONS" granted="false" flags="301" />
<permission name="android.permission.ACCESS_FINE_LOCATION" granted="false" flags="10301" />
<permission name="android.permission.RESTART_PACKAGES" granted="true" flags="0" />
package>
App声明的权限存储,Android设备所有App声明的权限都会被存储在 /data/system/package.xml 文件中,如下截取了部分信息:
<permissions>
# 以下权限是系统声明的权限 (也就是包名为android的Apk)
<item name="android.permission.LAUNCH_DEVICE_MANAGER_SETUP" package="android" protection="67108866" />
<item name="android.permission.REAL_GET_TASKS" package="android" protection="18" />
<item name="android.permission.ACCESS_CACHE_FILESYSTEM" package="android" protection="18" />
<item name="android.permission.REMOTE_AUDIO_PLAYBACK" package="android" protection="2" />
<item name="android.permission.START_CROSS_PROFILE_ACTIVITIES" package="android" protection="67108866" />
# 以下是包名为com.android.providers.downloads的Apk声明的权限
<item name="android.permission.DOWNLOAD_WITHOUT_NOTIFICATION" package="com.android.providers.downloads" />
<item name="android.permission.NFC_PREFERRED_PAYMENT_INFO" package="android" />
<item name="android.permission.WIFI_ACCESS_COEX_UNSAFE_CHANNELS" package="android" protection="67108866" />
省略其他部分······
permissions>
2.2 管理类
管理类就非常容易理解了就是各种manager,管理类的作用就是为App提供访问各种服务的能力,访问各种服务是通过binder通信访问的。管理类是属于framework.jar,而framework.jar在zygote进程中就已经被预加载了,因此在App被zygote孵化成功后,就已经拥有了各种管理类了,因此每个App都是可以直接使用自己进程内的管理类的。权限管理系统的管理类主要有PackageManager、PermissionManager、PermissionCheckerManager、AppOpsManager。PackageManager是属于PackageManagerService服务,为啥出现在权限管理系统呢?其主要原因是PackageManagerService与权限管理之间是没有明显的界限划分的,它们是“你中有我,我中有你的”一个互相服务的关系。权限管理系统会使用到PackageManagerService中的一小部分功能。权限控制器App会使用到该类。PermissionManager和PermissionCheckerManager主要与PermissionManagerService进行交互,而AppOpsManager与AppOpsService进行交互。管理类的作用就是把访问各种服务的能力封装了起来,这样App就可以直接使用了,比如App想要检查某个权限是否被授予了,就可以使用PermissionCheckerManager进行检查,进而PermissionManagerService会把检查结果返回。2.3 权限控制器App
权限控制器App它的“英文名”是PermissionController,它是一个App是一个独立运行的进程,它的主要作用就是权限的授予/撤销。比如某App在运行过程中发现自己没有录音权限,则会弹出如下界面:上面的界面就是由该App弹出来的。再比如在某App的应用详情--权限列表界面,也是又该App提供的,如下图:
用户在此界面是可以撤销/授权某些权限的。
2.4 小结
以上就是权限管理系统的模块划分,但是上面只是对模块做了一个相关介绍,但是模块之间是如何协同合作来保证权限管理系统的正常运作是没有一个完整体现的,因此我从权限的“归宿”、权限的授予、权限的鉴定、权限的“消亡”这四个方面来展示权限管理系统的模块之间是如何协同合作的,当然这四个方面也基本就是权限管理系统的“日常所有工作内容”了。
权限的“归宿”指的是当Apk被安装成功后,Apk使用的权限 (若存在) 和 Apk声明的权限 (若存在)将被存放于何处?答案显而易见,那就是PermissionManagerService服务会把Apk的权限信息保存起来。但是这里的保存只是保存在内存中,当Android设备重新启动的时候,内存中保存的各种Apk的权限信息就会丢掉,那该如何是好呢?为了解决以上问题那就需要PackageManagerService的记录存储模块 (Settings)来帮忙 (关于记录存储模块可以看这篇文章),它会把安装的Apk的包名、版本号、Apk文件路径等信息,当然还有最重要的权限信息 (声明的权限和使用的权限)存储到文件中,这样当Android设备重新启动的时候,就可以把已安装的Apk信息及它的权限都读取出来。(关于如何存储权限信息可以看这篇文章)还有一个非常重要的点AppOpsService也需要知道Apk使用的权限及权限状态,为啥AppOpsService需要知道呢?还记得上面提到过AppOpsService其中的一个作用是权限控制,也就是使用者是可以从它这了解到某个App使用的某个权限是否真正的授予,而计算权限真正授予是需要结合权限原先的状态和App的前后台状态计算得出的,因此AppOpsService是需要知道的。并且AppOpsService会把权限及权限状态信息也保存到/data/system/appops.xml文件中,当设备重新启动的时候,AppOpsService也会从文件中把保存的信息读取出来。实际例子
为了加深对权限“归宿”的理解,现安装一个Apk,该Apk的AndroidManifest.xml文件中声明和使用的权限如下:<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<permission android:name="com.niu" android:protectionLevel="dangerous"/>
manifest>
当该Apk被安装后,声明的权限com.baidu.niu放入/data/system/packages.xml文件中,如下- "com.baidu.niu" package="com.example.myapplication2" protection="1" />
该Apk使用的权限及状态放入/data/misc_de/0/apexdata/com.android.permission/runtime-permissions.xml文件中,如下:<package name="com.example.myapplication2">
<permission name="android.permission.READ_MEDIA_IMAGES" granted="false" flags="300" />
<permission name="android.permission.READ_MEDIA_AUDIO" granted="false" flags="300" />
<permission name="android.permission.READ_MEDIA_VIDEO" granted="false" flags="300" />
<permission name="com.example.myapplication2.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" granted="true" flags="0" />
<permission name="android.permission.RECORD_AUDIO" granted="false" flags="300" />
package>
AppOpsService会把使用的权限及状态放入/data/system/appops.xml文件,如下:
<uid n="10115">
<op n="27" m="1" />
<op n="81" m="1" />
<op n="83" m="1" />
<op n="85" m="1" />
uid>
3.1 小结
Apk安装成功后,Apk使用的权限 (若存在) 和 Apk声明的权限 (若存在)会被放入PermissionManagerService中;同时PackageManagerService的Settings会把Apk声明的权限 (若存在)存储在/data/system/packages.xml文件中,会把Apk使用的权限 (若存在) 存储在/data/misc_de/0/apexdata/com.android.permission/runtime-permissions.xml (0代表userid为0的用户)文件中;AppOpsService也会保存Apk使用的权限及状态,并且存放在/data/system/appops.xml文件。
Apk已经安装上来,是不是该打开App使用了,当第一次使用时候是不是会弹出一些权限请求的界面,那咱们就来聊聊权限的授予。权限的授予指的是打开App后,App会检查某个权限是否被授予,若没有被授予就会弹出请求权限的页面,用户点击允许则代表该权限被授予了。大家在进行权限授予的时候代码模板是不是下面这样的:(下面是伪代码)//先检查某个权限是否授予
if (ContextCompat.checkSelfPermission(this, 权限) != PackageManager.PERMISSION_GRANTED) {
//没有授予则调用requestPermissions方法
ActivityCompat.requestPermissions(this, 请求权限, REQUEST_PERMISSION_CODE);
}
那就从检查权限和请求授予权限两步来介绍权限的授予过程,并且在这个过程中看下模块之间是如何配合的。4.1 检查权限
如上图,检查权限是否被授予的过程:在App进程中调用PermissionManager的相关检查方法,该方法调用是一个binder通信过程,最终请求到达PermissionManagerService服务,而PermissionManagerService服务在内存中存储了Apk使用权限及权限的状态的,PermissionManagerService服务根据Apk包名、权限把最终的结果返回给使用者。4.2 请求授予权限
结合上图,请求授予权限过程如下:
4.2.1 通过Intent启动一个Activity
在App进程的某个Activity界面,是需要调用ActivityCompat.requestPermissions方法来请求权限的,而该方法最终会把请求的权限、包名等关键信息放入Intent中,在通过该Intent启动PermissionController App中的相应Activity。4.2.2 启动Activity
通过ActivityTaskManagerService处理后,PermissionController中的授权Activity被启动。4.2.3 通知PermissionManagerService服务
授权Activity的界面大致如下 (每个厂家都有自己的页面样式)当用户选择了仅在使用该应用时允许,会通知PermissionManagerService服务修改相应权限的状态,PermissionManagerService服务根据包名、uid、请求的权限、权限状态,找到对应的权限信息并修改它的权限状态,并且把状态值更新到对应文件中 (上面提到过更新哪个文件),修改成功后把结果返回给PermissionController中的授权Activity。
4.2.4 通知AppOpsService服务
为啥要通知AppOpsService服务呢?上面已经交代过AppOpsService服务也起到权限访问控制的功能,因此它有必要知道App使用的权限的状态,AppOpsService服务根据包名、uid、请求的权限、权限状态,做一些检查操作,修改相应状态值。并且把修改的值更新到/data/system/appops.xml文件中。4.2.5 返回授权结果
返回授权结果画为虚线的原因:是因为返回过程要经过ActivityTaskManagerService服务来处理,为了清晰故把这个过程省略掉了。返回授权结果,在App进程的Activity的onRequestPermissionsResult方法中就可以拿到。实际例子
同样还拿【权限“归宿”】中的例子,那个Apk中使用了录音权限,那就以请求录音权限来举例子,看下相应模块的数据都发生了哪些变化。因为该Apk的录音权限已经被授予,因此PermissionManagerService服务会把新的权限状态保存到/data/misc_de/0/apexdata/com.android.permission/runtime-permissions.xml文件中,如下:<package name="com.example.myapplication2">
<permission name="android.permission.READ_MEDIA_IMAGES" granted="false" flags="300" />
<permission name="android.permission.READ_MEDIA_AUDIO" granted="false" flags="300" />
<permission name="android.permission.READ_MEDIA_VIDEO" granted="false" flags="300" />
<permission name="com.example.myapplication2.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" granted="true" flags="0" />
<permission name="android.permission.RECORD_AUDIO" granted="true" flags="300" />
package>
而AppOpsService服务会把权限状态放入/data/system/appops.xml文件,如下:
<uid n="10115">
<op n="27" m="4" />
<op n="81" m="1" />
<op n="83" m="1" />
<op n="85" m="1" />
uid>
AppOpsService服务对权限状态做了更进一步的细分,因为录音只能在App处于前台的情况下开启,因此录音权限会对应一个前台状态值。4.2 小结
权限的授予,涉及到了PermissionController、PermissionManagerService服务、AppOpsService服务,其中PermissionController会负责展示一个权限授予的Activity,当用户选择了允许后,就会通知PermissionManagerService服务和AppOpsService服务更新相应权限的状态。权限的拒绝流程和授予也是一样的。既然【权限授予】例子中的Apk已经被授予了录音权限,那就来使用它来开启录音吧,在App层是看不到开启录音做了哪些工作的,开启录音之前是需要对该Apk做权限鉴定的,需要坚定该Apk是不是真正的获取了录音权限的,那我们就来介绍权限的鉴定吧。权限的鉴定是指受权限保护的资源需要对使用者进行权限鉴定,确定它是否真正的拥有权限。不能完全凭使用者的“一面之词”说自己已经授权了,就相信它,因此必须得进行鉴定。就拿开启录音来说吧,mediaserver进程是需要对使用者进行权限鉴定的。如下图:那就结合上图来分析下开启录音权限鉴定的过程吧。
5.1 App进程请求开启录音
App请求开启录音,这个请求会通过binder通信进入mediaserver进程,mediaserver进程是可以拿到开启者的包名、uid (App的唯一id)等信息的。5.2 鉴定权限
mediaserver进程会通过binder通信去PermissionManagerService服务对开启录音的使用者进行权限鉴定,这个鉴定过程是非常简单的,PermissionManagerService服务根据包名、uid、权限去查询下该权限是否被授予,如果授予了该权限,则会进入下一步;否则终止后面流程,直接告诉mediaserver进程对应使用者是没有获取录音权限的。5.3 再次鉴定权限
PermissionManagerService服务会到AppOpsService服务再次进程权限鉴定,而此次鉴定会结合开启录音的使用者的前后台状态等信息,重新计算一个新的权限状态值。如果开启录音的使用者位于前台,并且顺利经过其他检查,则会做一件很重要的事情开始把使用者使用录音的开始时间点、包名等信息记录并且存储到文件中,并且会把结果返回给mediaserver进程,告诉它授予了该权限;否则告诉mediaserver进程对应使用者是没有获取录音权限。5.4 收到鉴定结果
mediaserver进程收到鉴定结果后,如果鉴定结果是成功的,则为使用者开启录音;否则不开启录音。当App结束录音的时候,App会把结束录音的事件通知给mediaserver进程,并且会执行如下操作,如下图:同样结合上图来分析下结束录音过程,权限管理都做了哪些事情。
5.5 结束录音
App进程会通过binder通信把结束录音的信息发送给mediaserver进程。5.6 发送finishDataDelivery事件
mediaserver进程会调用Permissionmanager的finishDataDelivery方法,通知PermissionManagerService服务结束某个数据传递过程,PermissionManagerService服务处理了对应事情后,会调用AppOpsService服务的finishOperation。5.7 发送finishOperation
AppOpsService服务收到该事件后,做的最重要的一件事情就是停止记录使用者使用录音的信息,也就是说该使用者使用录音的信息已经记录完毕,如下展示了都记录了哪些信息:Uid u0a115:
state=top
省略······
Package com.example.myapplication2:
//录音权限 allow代表对该app已经授予了录音权限
RECORD_AUDIO (allow):
null=[
//Access 代表该app被允许访问录音资源, 2024-10-18 14:45:05.191 代表访问的起始时间,duration代表使用录音的时长
Access: [cch-s] 2024-10-18 14:45:05.191 (-1d6h19m47s305ms) duration=+717ms
//Reject 代表该app被禁止访问录音资源
Reject: [cch-s]2024-10-18 20:13:36.768 (-1d0h51m15s728ms)
]
5.8 小结
通过开启录音的例子,展示了权限的鉴定过程,录音权限在进行权限鉴定的时候除了要经过PermissionManagerService服务进行鉴定外,还需要经过AppOpsService服务进行再次鉴定的,AppOpsService服务会结合录音使用者的前后台状态重新计算权限状态,只有使用者位于前台,才代表使用者被授予了录音权限,并且这时候会把使用者使用录音的开始时间等信息记录下来。当App使用完录音后,AppOpsService服务也需要知道该信息,进而停止记录使用者使用录音的时间。像系统声明大部分的权限 (如录音权限、定位权限、camera相关的权限,存储权限) 除了要经过PermissionManagerService服务进行鉴定外,还需要经过AppOpsService服务进行再次鉴定的,AppOpsService服务除了鉴定外,还会把使用者使用资源的时间等信息记录下来。
权限的“消亡”指的是某Apk的所有权限从Android设备上被移除了,只有Apk被卸载了才会出现这种情况。当Apk被卸载时,PermissionManagerService服务和AppOpsService服务都会把保存在内存和文件中的该Apk的权限信息都删除掉,同时PackageManagerService服务的Settings也会把该Apk使用到的保存在内存和文件中的所有权限信息都删除掉。这样一个Apk的所有权限也就从这台设备上“消亡”了。
本文主要介绍了权限管理系统都做了哪些事情,以及相应的模块划分,同时从权限的“归宿”、权限的授予、权限的鉴定、权限的“消亡”这四个方面来展示权限管理系统的模块之间是如何协同合作的。本文主要带您先了解权限管理系统的全貌,后面的章节会对权限管理系统的各个模块再次进行详细的介绍。
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!