一、引言
这篇文章以Android v28的源码为参考,介绍Android多用户的特性、使用方式和系统原理。
二、初识Android多用户
2.1 Android多用户简介
从Android 4.0开始,Google就开始在Android上布局多用户,UserManager因此而诞生,然而此时还没有对应的Binder服务。真正支持多用户是从Android 4.2 开始,即便如此,系统中也依然存在各种Bug和兼容性问题。直到Android 6.0,Android多用户才比较完善,国内外的厂家也纷纷开始针对多用户这个噱头来做各种 “花里胡哨” 的操作,“手机分身”、“分身应用”、“应用双开” 应运而生,不得不说,国内的厂家在多用户这方面定制化到如今已经非常稳定和完善了。
下图从左到右分别为小米手机的手机分身、应用双开以及华为手机的多用户:



2.2 基础概念
要学习多用户,首先我们需要了解一些基础概念:
-
Uid(用户Id):在Linux上,一个用户Uid标识着一个给定的用户。Android上也沿用了Linux用户的概念,Root用户Uid为0,System Uid为1000,并且,每个应用程序在安装时也被赋予了单独的Uid,这个Uid将伴随着应用从安装到卸载。
-
Gid(用户组Id):Linux上规定每个应用都应该有一个用户组,对于Android应用程序来说,每个应用的所属用户组与Uid相同。
-
Gids:应用在安装后所获得权限的Id集合。在Android上,每个权限都可能对应一个或多个group,每个group有个gid name,gids就是通过对每个gid name计算得出的id集合,一个UID可以关联GIDS,表明该UID拥有多种权限。
对于Android中的每个进程,都有一个单独的Uid、Gid以及Gids集合(这个地方有点疑惑,Android Framework中的权限控制不依赖这个Gids,这个是Linux上有的东西,有待考证),通过这三者,Android系统实现了一套文件和数据访问权限规则系统。如:
- 访问某个文件,文件系统规定了该文件在磁盘中的rwx(read/write/excute)和 SELinux 权限:
root@virgo:/ # ls -lZ /system/xbin/su
-rwsr-sr-x root shell u:object_r:su_exec:s0 su
- 访问Framework中提供的某个服务功能,Android规定了该功能的访问权限:
// 网络访问权限,通过Binder.getCallingUid()
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"ConnectivityService");
}
Android权限知识拓展:
- 安装时权限的获取记录存储在:/data/system/packages.xml 中
<package name="com.ulangch.multiuser" codePath="/data/app/com.ulangch.multiuser-1" nativeLibraryPath="/data/app/com.ulangch.multiuser-1/lib" publicFlags="944291654" privateFlags="0" ft="16bb6087cd0" it="16b8e15f7d1" ut="16bb6088d8e" version="1" userId="10110" cpuAbiDerived="true">
<sigs count="1">
<cert index="15" key="308201dd30820146020101300d06092a864886f70d010105050030373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b3009060355040613025553301e170d3139303330353033303931385a170d3439303232353033303931385a30373116301406035504030c0d416e64726f69642044656275673110300e060355040a0c07416e64726f6964310b300906035504061302555330819f300d06092a864886f70d010101050003818d0030818902818100c300f621e550ca5e5ff09af965f02c8114c6836070b6d67b7b4f29b2335aff7d3ab389b76dede529ceac9071b17728dbaaa86951c68af5f6b4bec504f8c636cc425b8f6c8cee74cddf0f371edcb312dd5f3ae2cd1019d6e8bcadba98b69025012164f0fa981b560089dd864be89395e50e1dafd6c1d6a11a25e36f9b2563d7f90203010001300d06092a864886f70d010105050003818100339308982bc2fcb971f91774e054bb3a7debbbd3f7588c265650ec65e0d61b51645e975f617814adde7a0371e60b5a84baf3932676071e72feda59c050d1befa530d8c9e3f90567f725e4597399017f6df3ac7cdddb00eedc9c365d396cc7225a30ded45656073ce75e1fa3a330786c0874bb728558fa8338b4651cf990f755f" />
</sigs>
<perms>
<item name="android.permission.INTERNET" granted="true" flags="0" />
<item name="android.permission.ACCESS_NETWORK_STATE" granted="true" flags="0" />
</perms>
<proper-signing-keyset identifier="27" />
</package>
- 运行时权限的获取记录存储在:/data/system/users/$userId/runtime-permissions.xml 中:
<pkg name="com.ulangch.multiuser">
<item name="android.permission.READ_EXTERNAL_STORAGE" granted="true" flags="0" />
<item name="android.permission.WRITE_EXTERNAL_STORAGE" granted="true" flags="0" />
</pkg>
Uid/Gid/Gids 的知识延伸:
- 查看方式:
root@virgo:/ # ps |grep system_server
system 2074 357 1905236 264236 sys_epoll_ b6d2c99c S system_server
root@virgo:/ # cat /proc/2074/status
Name: system_server
State: S (sleeping)
Tgid: 2074
Pid: 2074
PPid: 357
TracerPid: 0
Uid: 1000 1000 1000 1000
Gid: 1000 1000 1000 1000
FDSize: 512
Groups: 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1018 1021 1032 3001 3002 3003 3006 3007 9801
...
- Gids的真面目及来龙去脉:
Android系统和应用安装后的权限声明保存在 “/etc/permissions/” 目录下:
1|root@virgo:/ # ls /etc/permissions/
ConnectivityExt.xml
android.hardware.bluetooth_le.xml
android.hardware.camera.flash-autofocus.xml
android.hardware.camera.front.xml
...
handheld_core_hardware.xml
imscm.xml
micloud-sdk.xml
platform-miui.xml
platform.xml
看下最常用的platform权限:
root@virgo:/ # cat /etc/permissions/platform.xml
...
<permissions>
<!-- The following tags are associating low-level group IDs with
permission names. By specifying such a mapping, you are saying
that any application process granted the given permission will
also be running with the given group ID attached to its process,
so it can perform any filesystem (read, write, execute) operations
allowed for that group. -->
<permission name="android.permission.BLUETOOTH" >
<group gid="net_bt" />
</permission>
<permission name="android.permission.INTERNET" >
<group gid="inet" />
</permission>
<permission name="android.permission.WRITE_MEDIA_STORAGE" >
<group gid="media_rw" />
<group gid="sdcard_rw" />
</permission>
...
这里只截取了部分权限,我们发现每个我们常见的权限都可能对应一个或多个group gid,而我们上面说的gids就是由这个group gid生成的集合。具体的权限读取和gids生成流程如下:
三、多用户的特性
3.1 独立的userId
Android在创建每个用户时,都会分配一个整型的userId。对于主用户(正常下的默认用户)来说,userId为0,之后创建的userId将从10开始计算,每增加一个userId加1:
root@virgo:/ # pm list users
Users:
UserInfo{
0:机主:13} running
UserInfo{
10:security space:11} running
创建一个名为"ulangch"的用户:
root@virgo:/ # pm create-user "ulangch"
Success: created user id 11
root@virgo:/ # pm list users
Users:
UserInfo{
0:机主:13} running
UserInfo{
10:security space:11} running
UserInfo{
11:ulangch:10} running
启动和切换到该用户:
root@virgo:/ # am start-user 11
Success: user started
root@virgo:/ # am switch-user 11
3.2 独立的文件存储
为了多用户下的数据安全性,在每个新用户创建之初,不管是外部存储(External Storage)还是app data目录,Android都为其准备了独立的文件存储。
多用户下的/storage分区:
root@virgo:/ # ls -l /storage/emulated/
drwxrwx--x root sdcard_rw 2019-06-21 17:44 0
drwxrwx--x root sdcard_rw 2019-06-25 14:04 10
drwxrwx--x root sdcard_rw 2019-06-25 17:32 11
root@virgo:/ # ls -l /sdcard
lrwxrwxrwx root root 2019-06-21 10:47 sdcard -> /storage/self/primary
root@virgo:/ # ls -l /storage/self/primary
lrwxrwxrwx root root 2019-06-21 10:47 primary -> /mnt/user/0/primary
root@virgo:/ # ls -l /mnt/user/0/primary
lrwxrwxrwx root root 2019-06-21 10:47 primary -> /storage/emulated/0
新用户创建时,Android在 “/storage/emulated” 目录下为每个用户都创建了名为用户id的目录,当我们在代码中使用 “Environment.getExternalStorageDirectory().absolutePath” 获取外部存储路径时,返回的就是当前用户下的对应目录(如:userId = 11, 则返回为 “/storage/emulated/11”)。
另外,可以看出,我们平常说到的 “/sdcard” 目录其实最终也是软链到了 “/storage/emulated/0”
多用户下的/data分区:
root@virgo:/ # ls -l data/user/
lrwxrwxrwx root root 2019-05-28 22:15 0 -> /data/data/
drwxrwx--x system system 2019-06-24 15:30 10
drwxrwx--x system system 2019-06-25 17:30 11
root@virgo:/ # ls -l /data/user/11/com.ulangch.multiuser/
drwxrwx--x u11_a110 u11_a110 2019-06-25 18:02 cache
drwxrwx--x u11_a110 u11_a110 2019-06-25 18:30 code_cache
drwxrwx--x u11_a110 u11_a110 2019-06-25 18:28 files
与External Storage相同,新用户创建时,Android也会在 “data/user” 目录下创建了名为userId的目录,用于存储该用户中所有App的隐私数据,如果在代码中使用 “Context.getFilesDir()” 来获取应用的data目录,不同User下也会有不同。
另外,也可以看出,平常说到的 “/data/data” 目录其实也是软链到了 “/data/user/0”。
下图是两个用户下同一个App的获取结果:


注:在Android中,应用的uid是和当前的用户有关的,同一个应用具有相同的appId,其uid的计算方式为: uid = userId * 1000000 + appId
,在主用户中,uid = appId。
3.3 独立的权限控制
- 不同用户具有的权限不同,如:访客用户的默认权限限制就有:
perseus:/ $ dumpsys user
...
Guest restrictions:
no_sms // 限制发送短信
no_install_unknown_sources // 限制安装
no_config_wifi // 限制配置WiFi
no_outgoing_calls // 限制拨打电话
(注:使用 “adb shell dumpsys user” 可以查看所有的用户信息,如userId、name、restrictions等)
这些权限可以在创建用户时规定,也可以后期由系统动态设置。
- 不同用户下App的应用权限是独立的
前面说到,uid与userId存在一种计算关系(uid = userId * 1000000 + appId),而在系统中对于权限控制也是根据uid和对应的userId来判定的,因此不同用户下相同应用可以具有不同的权限。
3.4 App安装的唯一性
虽然前面说到,App的文件存储和数据目录在不同用户下都是独立的,但是对于App的安装,多个用户下同一个App却保持着同一个安装目录,即:
- 普通三方app:/data/app/
- 普通系统应用:/system/app/
- 特权系统应用:/system/priv-app/
root@virgo:/ # ls /data/app/com.ulangch.multiuser-1/
base.apk
lib
oat
拓展:权限在声明时安全等级(protectionLevel)分为3类:
<permission android:name="android.permission.READ_EXTERNAL_STORAGE"
android:permissionGroup="android.permission-group.STORAGE"
android:label="@string/permlab_sdcardRead"
android:description="@string/permdesc_sdcardRead"
android:protectionLevel="dangerous" />
<!-- Allows applications to access information about Wi-Fi networks.
<p>Protection level: normal
-->
<permission android:name="android.permission.ACCESS_WIFI_STATE"
android:description="@string/permdesc_accessWifiState"
android:label="@string/permlab_accessWifiState"
android:protectionLevel="normal" />
<!-- @SystemApi Allows applications to set the system time.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
- normal:普通权限,在AndroidManifest.xml中声明就可以获取的权限,如INTERNET权限
- dangerous:敏感权限,需要动态申请告知用户才能获取
- signature|privileged:具有系统签名的系统应用才可以获取的权限,对应上方的 “/system/priv-app”
因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。
3.5 kernel及系统进程的不变性
在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。
而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote:
root@virgo: