最近这段时间太忙了,很久没发文章了。上一次的Binder系列估计要鸽很久了,因为纯粹分析理论不去研究实战,学习的效率其实是很低的。从这一篇开始,尝试将理论与应用结合一下,先从理论讲起,然后再写写相关知识点在framework层的实战内容,同时理论分析避免大段大段地贴代码,尽量用文字和图表描述其中的关键流程。先从android系统比较简单的模块入手———多用户模块,并利用理论基础实现应用多开、创建临时用户的功能。
1.多用户介绍
多用户操作系统是指一台计算机在同一时间或不同时间可以由多个用户使用,多个用户共同享用系统的全部硬件和软件资源。Android从4.0开始布置多用户功能,到6.0之后,多用户功能开始逐渐完善。通过分离用户帐号和应用数据,Android支持在一台 Android设备上添加多个用户。例如,父母可能会允许他们的孩子使用家庭平板电脑,一个家庭可以共用一辆汽车,或者应急响应团队可能会共用一台移动设备用于呼叫值班。
针对多用户功能,国内的手机厂商研发了各种黑科技的操作,如"手机分身"、"分身应用"、"隐私保护" 功能。下图为Oppo手机系统的应用分身功能和多用户功能示意图:
|
|
|
在Android 11上,系统在UserManager中定义了如下7种用户类型,不同的用户类型具有不同的资源访问权限及使用权限:
| 用户类型 | 描述 |
|---|---|
| SYSTEM | 系统用户,此类用户无法人为创建,只能在第一次启动时由系统创建 |
| SECONDARY | 除系统用户之外添加到设备的任何用户。次要用户可以移除(由次要用户自行移除或由管理员用户移除),且不会影响设备上的其他用户。此类用户可以在后台运行,在后台运行时可以继续连接到网络。 |
| GUEST | 临时的次要用户。一次只能有一个访客用户 |
| DEMO | 用户类型仅用于演示目的,可以随时删除 |
| RESTRICTED | 表示"受限配置文件"用户的用户类型,该用户是受父用户某些限制的完整用户。 |
| PROFILE_MANAGED | 由设备策略控制器(DPC)管理的配置文件配置的用户类型,一般用于公司实体管理。 |
| HEADLESS | 非自然人用户,无法人为创建,主要用于车载系统。 |
2.基础概念
android framework中定义了一系列的id支持系统的运作,包括且不限于userId、uid、gid、pid等。为了更好地了解多系统的功能,首先需要将这些id的含义弄清楚。
-
userId: 需要注意的是 userId ≠ uid,userId与android多用户系统中的概念,每一个用户都具有唯一的userId作为标识。
-
uid: 和linux的uid有所不同,android uid作为应用的唯一标识。uid在应用安装时由系统分配,且在重新安装前都不会发生改变。一个应用程序只能有一个uid,多个应用可以使用sharedUserId 方式共享同一个uid,前提是这些应用的签名要相同。在多用户下,uid的计算公式为:
userId * 100000 + (appId % 100000)
单用户下,uid 等于 appid。
-
pid: 进程id,当应用进程被创建时,由系统分配,作为进程的唯一标识。
-
gid:对于普通应用而言,android 中 gid 等于uid。除此之外,android还有gids的概念,一个uid可以对应一组gids。gids是一组面向于应用的权限,与permission不同的是,gids定义的粒度更细,如果一个应用申请了某个permission,则它会获得和该permission所对应的更底层的权限。
-
Appid:appId用于标识应用程序实例,每一个可执行的应用都会在安装时被分配唯一的appId。
3.多用户特性
3.1 独立的UserId
Android在创建每个用户时,都会分配一个整型的UseId,除了系统用户id已经固定分配为0以外,其他的userId都是从10开始,如下图所示:
3.2 独立的文件存储
在每个用户创建时,android系统会为该用户及用户下的应用准备了独立的内部存储空间。
多用户下的data/user分区:
多用户下的data/misc/user分区:
3.3 独立的权限控制
不同用户具有的权限不同,比如:
上图表明Guest用户不具备app安装、卸载、系统重置、删除用户的权限,具体的权限内容定义在config_user_types.xml文件中。
3.4 App的唯一性
虽然每个用户下的同一个应用具有独立的文件存储和权限管理,但它们共用的是同一个安装目录和apk运行文件,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。
3.5 kernel及系统进程的不变性
在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。
而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote。
4 工作流程
4.1 用户类型
系统内置的用户权限配置主要实现类为:UserTypeFactory.class,它负责管理AOSP及开发者自定义的各用户类型的配置详情,包括且不限于名称、默认权限、最大创建数等。在UserManagerService创建时,其构造方法创建了UserTypeFactory对象,并初始化了所有用户类型的配置详情。各用户类型默认的主要配置详情如下表所示:
| 用户类型 | 最大创建数 | 默认权限 |
|---|---|---|
| PROFILE_MANAGED | 依赖父用户,每个父用户最多1个 | 与父用户相同 |
| Secondary | 未限制 | no_outgoing_calls、no_sms |
| SYSTEM | 0 | 未限制 |
| GUEST | 1 | no_config_wifi、no_install_unknown_sources、no_outgoing_calls、no_sms |
| DEMO | 未限制 | 未限制 |
| RESTRICTED | 未限制 | no_modify_accounts、no_share_location |
| SYSTEM_HEADLESS | 0 | 未限制 |
UserTypeFactory初始化时,会去读取com.android.internal.R.xml.config_user_types文件内配置的用户类型数据。成功读取后,会覆盖AOSP默认的配置内容。因此开发者需要对用户的权限等属性修改时,可以通过修改config_user_types文件里的内容来实现。
4.2 创建用户
开发者可以通过以下adb命令创建一个新的用户:
adb shell pm create-user [--profileOf USER_ID] [--managed][--restricted] [--ephemeral] [--guest] [--pre-create-only][--user-type USER_TYPE] USER_NAME"
当调用UserManager的createUser方法后,其创建用户的关键流程如下所示:
1、为新用户创建一个新的userId (新用户的userId从10开始递增)。
2、存储新用户信息。构造包含新用户信息的UserData,并固化到 /data/system/users/${userId}.xml文件中:
将新创建新userId固化到 "/data/system/users/userlist.xml"文件中:
3、准备文件系统:
-
通过vold(Android存储守护进程)为新用户进行文件系统加密;
-
创建
/data/misc/users/${userId}目录并设置 "0750" 权限; -
创建新用户的数据空间与设备加密数据存储空间,对应的路径为
/data/user/${userId}、/data/user_ce/${userId}等等。
4、为已安装应用准备数据目录并记录其组件和默认权限配置:
-
在 /data/user/${userId}/下创建各个已安装应用的package目录。
-
在 data/system/users/${userId}/package-restrictions.xml文件中写入包限制信息。
-
更新data/system/packages.list文件信息。
-
固化新用户创建完成的状态、通知PMS为新用户和应用赋予默认的权限。
-
发送 "ACTION_USER_ADDED"广播,通知新用户创建完成。
通过以上工作流程可以看到,多用户其实是系统为应用的data目录分配了一份不同且独立的存储空间,不同用户下的存储空间互不影响且没有权限访问。同时,系统中的AMS、PMS、WMS等各大服务都会针对userId/UserHandle进行多用户适配,并在用户启动、切换、停止、删除等生命周期时做出相应策略的改变。通过以上两点,Android创造出来一个虚拟的多用户运行环境。
4.3 多用户启动Activity
在AMS中经常可以看到startActivityAsUser
这么一个方法,方法的最后一个参数通常是userId,说明android系统允许跨用户启动activity,且只有拥有android.Manifest.permission.INTERACT_ACROSS_USERS_FULL权限的系统应用才能调用该方法。