【转】深入理解Android系统多用户

一、引言
这篇文章以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
1
2
访问Framework中提供的某个服务功能,Android规定了该功能的访问权限:
// 网络访问权限,通过Binder.getCallingUid()
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"ConnectivityService");
}
1
2
3
4
5
6

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>
1
2
3
4
5
6
7
8
9
10
运行时权限的获取记录存储在:/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>
1
2
3
4

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
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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
1
2
3
4
5
6
7
8
9
10
11

看下最常用的platform权限:

root@virgo:/ # cat /etc/permissions/platform.xml
...
<permissions>

<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>
...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
这里只截取了部分权限,我们发现每个我们常见的权限都可能对应一个或多个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
1
2
3
4
创建一个名为"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
1
2
3
4
5
6
7
8
启动和切换到该用户:

root@virgo:/ # am start-user 11
Success: user started
root@virgo:/ # am switch-user 11
1
2
3

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
1
2
3
4
5
6
7
8
9
10
11
新用户创建时,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
1
2
3
4
5
6
7
8
9
与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 // 限制拨打电话
1
2
3
4
5
6
7
(注:使用 “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
1
2
3
4
拓展:权限在声明时安全等级(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" />

<permission android:name="android.permission.ACCESS_WIFI_STATE"
android:description="@string/permdesc_accessWifiState"
android:label="@string/permlab_accessWifiState"
android:protectionLevel="normal" />

<permission android:name="android.permission.SET_TIME"
android:protectionLevel="signature|privileged" />
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
normal:普通权限,在AndroidManifest.xml中声明就可以获取的权限,如INTERNET权限
dangerous:敏感权限,需要动态申请告知用户才能获取
signature|privileged:具有系统签名的系统应用才可以获取的权限,对应上方的 “/system/priv-app”
因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。

3.5 kernel及系统进程的不变性
在不同用户下,虽然能够看到不同的桌面,不同的运行环境,一切都感觉是新的,但是我们系统本身并没有发生改变,kernel进程、system_server进程以及所有daemon进程依然是同一个,并不会重启。

而如果我们在不同用户中开启相同的app,我们可以看到可以有多个app进程,而他们的父进程都是同一个,即 zygote:

root@virgo:/ # ps |grep multiuser
u11_a110 9805 357 788188 54628 sys_epoll_ b6d2c99c S com.ulangch.multiuser
u10_a110 13335 357 816516 54588 sys_epoll_ b6d2c99c S com.ulangch.multiuser
u0_a110 13746 357 788448 54056 sys_epoll_ b6d2c99c S com.ulangch.multiuser

root@virgo:/ # ps |grep 357
root 357 1 1542716 65560 poll_sched b6d2cb64 S zygote
1
2
3
4
5
6
7

四、流程分析
多用户的创建、启动、停止等行为是系统级的,因此只有具有root、system权限的进程才能操作。

3.1 多用户的创建
adb shell pm create-user [–profileOf USER_ID] [–managed] USER_NAME

多用户的创建流程主要在UserManagerService.createUserInternalUnchecked()方法中,方法太长,截取部分分析:

private UserInfo createUserInternalUnchecked(String name, int flags, int parentId, String[} {
final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;] disallowedPackages);
// ... 省略,以下操作在 mPackagesLock 锁中
// 用户创建条件判断
// 一个用户只能有一个ManagedProfile
if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
// 判断是否达到最大用户数,Android 6.0上最大用户数为5,可配置
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
return null;
}
// 系统中只能有一个访客用户
if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// ... 省略关于 restricted 和 ephemeral 类型的创建判断

// 获取下一个userId,userId从10开始递增,除非达到"上限",否则前面的userId不会复用
userId = getNextAvailableId();
// 创建 "/data/system/users/{userId}"目录 Environment.getUserSystemDirectory(userId).mkdirs(); synchronized (mUsersLock) { // 构造UserInfo和UserData,后面会进行固化保存 userInfo = new UserInfo(userId, name, null, flags); userInfo.serialNumber = mNextSerialNumber++; long now = System.currentTimeMillis(); userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0; // partial = true表明用户还没有创建完成,为了防止创建用户进程被突然停止,下次系统重启时 // 需要检查文件中该属性,如果为true则需要继续用户的创建 userInfo.partial = true; userInfo.lastLoggedInFingerprint = Build.FINGERPRINT; if (isManagedProfile && parentId != UserHandle.USER_NULL) { userInfo.profileBadge = getFreeProfileBadgeLU(parentId); } userData = new UserData(); userData.info = userInfo; mUsers.put(userId, userData); } // 将上述UserData信息固化到 "/data/system/users/{userId}.xml"
writeUserLP(userData);
// 将新创建的userId固化到 "/data/system/users/userlist.xml"
writeUserListLP();
// ... 省略一部分profile和restricted类型的写文件操作

// 为新用户准备文件系统
final StorageManager storage = mContext.getSystemService(StorageManager.class);
// 通过vold对新用户进行文件系统加密(相关:https://www.jianshu.com/p/d25f73805729
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
// 通过vold创建以下目录,并赋予相关rwx权限:
// "/data/system/users/{userId}" : 0700 // "/data/misc/users/{userId}" : 0750
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
// 为已安装应用创建"/data/system/user/{userId}/{packageName}"目录
// ...
mPm.createNewUser(userId, disallowedPackages);
// 用户已经创建完成,固化用户创建状态
userInfo.partial = false;
synchronized (mPackagesLock) {
writeUserLP(userData);
}
// 更新所有缓存的用户
updateUserIds();
// ...省略guest 和 restrictions

// 为新创建的用户赋予默认权限
mPm.onNewUserCreated(userId);
// 向所有用户发送 "ACTION_USER_ADDED" 广播
Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
android.Manifest.permission.MANAGE_USERS);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
从上面的代码分析可以看出,用户创建的过程主要是应用运行环境(文件系统、权限等)的准备过程,主要可以分为以下几个关键的步骤:(忽略访客用户相关的操作)

  1. 为新用户创建一个新的userId (新用户的userId从10开始递增)

  2. 固化新用户信息和创建状态

构造包含新用户信息的UserData,并固化到 “/data/system/users/${userId}.xml”
root@virgo:/ # cat data/system/users/10.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<user id="10" serialNumber="10" flags="17" created="1561361447098" lastLoggedIn="1561460313625">
<name>security space</name>
<restrictions no_install_unknown_sources="false" no_usb_file_transfer="false" no_debugging_features="false" />
</user>
1
2
3
4
5
6
将新创建新userId固化到 “/data/system/users/userlist.xml”
root@virgo:/ # cat data/system/users/userlist.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<users nextSerialNumber="12" version="5">
<guestRestrictions>
<restrictions no_config_wifi="true" no_outgoing_calls="true" no_sms="true" />
</guestRestrictions>
<user id="0" />
<user id="10" />
<user id="11" />
</users>
1
2
3
4
5
6
7
8
9
10

  1. 准备文件系统:

通过vold(Android存储守护进程)为新用户进行文件系统加密
创建"/data/system/users/{userId}" 并设置 “0700” 权限 创建 “/data/misc/users/{userId}” 并设置 “0750” 权限

  1. 为已安装应用准备数据目录并记录其组件和默认权限配置:

在 “/data/user/{userId}/” 下创建各个已安装应用的package目录 在 “data/system/users/{userId}/package-restrictions.xml” 中写入非默认启动组件的信息
root@virgo:/ # cat data/system/users/10/package-restrictions.xml
<?xml version='1.0' encoding='utf-8' standalone='yes' ?>
<package-restrictions>
<pkg name="com.ss.android.ugc.aweme" stopped="true">
<enabled-components>
<item name="com.bytedance.performance.doctorx.leakcanary.internal.DisplayLeakActivity" />
<item name="com.bytedance.ttnet.hostmonitor.ConnectivityReceiver" />
<item name="com.bytedance.performance.doctorx.leakcanary.internal.RequestStoragePermissionActivity" />
<item name="com.huawei.android.pushagent.PushBootReceiver" />
</enabled-components>
</pkg>
...
1
2
3
4
5
6
7
8
9
10
11
12
更新"data/system/packages.list",主要是最后一串gids可能会改变。(这个改变的可能性是根据permUser的配置来决定,目前使用6.0的小米Note是没有改变的)
root@virgo:/ # cat data/system/packages.list
com.miui.screenrecorder 1000 0 /data/data/com.miui.screenrecorder platform 2001,3002,1023,1015,3003,3001,1021,3004,3005,1000,2002,3009,1010,1007,3006,3007
com.ss.android.ugc.aweme 10132 1 /data/data/com.ss.android.ugc.aweme default 3002,3003,3001
com.ulangch.multiuser 10110 1 /data/data/com.ulangch.multiuser default none
// ...
1
2
3
4
5

  1. 固化新用户创建完成的状态、通知PMS为新用户和应用赋予默认的权限

  2. 发送 “ACTION_USER_ADDED” 广播,新用户创建完成

3.2 多用户的切换
adb shell am start-user: start USER_ID in background if it is currently stopped,
use switch-user if you want to start the user in foreground.

adb shell am switch-user: switch to put USER_ID in the foreground, starting
execution of that user if it is currently stopped.

Android多用户的切换函数入口ActivityManagerService.switchUser方法:

// ActivityManagerService.java
@Override
public boolean switchUser(final int targetUserId) {
enforceShellRestriction(UserManager.DISALLOW_DEBUGGING_FEATURES, targetUserId);
int currentUserId;
UserInfo targetUserInfo;
synchronized (this) {
currentUserId = mUserController.getCurrentUserIdLocked();
targetUserInfo = mUserController.getUserInfo(targetUserId);
// ... 省略一堆判断
mUserController.setTargetUserIdLocked(targetUserId);
}
if (mUserController.mUserSwitchUiEnabled) {
UserInfo currentUserInfo = mUserController.getUserInfo(currentUserId);
Pair<UserInfo, UserInfo> userNames = new Pair<>(currentUserInfo, targetUserInfo);
mUiHandler.removeMessages(START_USER_SWITCH_UI_MSG);
// 这个消息将会弹Dialog展示切换的过程
mUiHandler.sendMessage(mHandler.obtainMessage(
START_USER_SWITCH_UI_MSG, userNames));
} else {
mHandler.removeMessages(START_USER_SWITCH_FG_MSG);
mHandler.sendMessage(mHandler.obtainMessage(
START_USER_SWITCH_FG_MSG, targetUserId, 0));
}
return true;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
AMS的startUser方法只是判断了是否展示切换用户的Dialog,最终都会调用到UserController.startUser方法中:

boolean startUser(final int userId, final boolean foreground) {
if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
!= PackageManager.PERMISSION_GRANTED) {
String msg = "Permission Denial: switchUser() from pid="
+ Binder.getCallingPid()
+ ", uid=" + Binder.getCallingUid()
+ " requires " + INTERACT_ACROSS_USERS_FULL;
Slog.w(TAG, msg);
throw new SecurityException(msg);
}

Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);

final long ident = Binder.clearCallingIdentity();
try {
    synchronized (mLock) {
        final int oldUserId = mCurrentUserId;
        if (oldUserId == userId) {
            return true;
        }

        if (foreground) {
            mInjector.getActivityStackSupervisor().setLockTaskModeLocked(
                    null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
        }

        final UserInfo userInfo = getUserInfo(userId);
        if (userInfo == null) {
            Slog.w(TAG, "No user info for user #" + userId);
            return false;
        }
        if (foreground && userInfo.isManagedProfile()) {
            Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
            return false;
        }

        if (foreground && mUserSwitchUiEnabled) {
            // 1. 冻结输入事件
            // 2. 强制结束所有动画
            // 3. 截取当前屏幕并展示
            mInjector.getWindowManager().startFreezingScreen(
                    R.anim.screen_user_exit, R.anim.screen_user_enter);
        }

        boolean needStart = false;

        // If the user we are switching to is not currently started, then
        // we need to start it now.
        if (mStartedUsers.get(userId) == null) {
            UserState userState = new UserState(UserHandle.of(userId));
            mStartedUsers.put(userId, userState);
            // 初始状态为 STATE_BOOTING
            mInjector.getUserManagerInternal().setUserState(userId, userState.state);
            updateStartedUserArrayLocked();
            needStart = true;
        }

        final UserState uss = mStartedUsers.get(userId);
        final Integer userIdInt = userId;
        // 修改当前用户历史
        mUserLru.remove(userIdInt);
        mUserLru.add(userIdInt);

        if (foreground) {
            mCurrentUserId = userId;
            // 从Setting Provider读取需要切换用户的字体、语言、地区等配置并更新
            // 如果是初创用户,对于字体则使用默认配置,语言和地区使用当前用户的配置:https://android.googlesource.com/platform/frameworks/base/+/ea906b3%5E!/
            mInjector.updateUserConfigurationLocked();
            mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up

            // 更新当前用户附属的ManageProfile
            updateCurrentProfileIdsLocked();
            // 设置当前用户下所有window的可见性
            // 设置切换用户的屏幕分辨率
            mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
            // Once the internal notion of the active user has switched, we lock the device
            // with the option to show the user switcher on the keyguard.
            if (mUserSwitchUiEnabled) {
                // 切换过程中关闭Keyguard的指纹监听
                mInjector.getWindowManager().setSwitchingUser(true);
                // 设置Keyguard锁屏
                mInjector.getWindowManager().lockNow(null);
            }
        } else {
            final Integer currentUserIdInt = mCurrentUserId;
            updateCurrentProfileIdsLocked();
            mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
            mUserLru.remove(currentUserIdInt);
            mUserLru.add(currentUserIdInt);
        }

        // Make sure user is in the started state.  If it is currently
        // stopping, we need to knock that off.
        if (uss.state == UserState.STATE_STOPPING) {
            // If we are stopping, we haven't sent ACTION_SHUTDOWN,
            // so we can just fairly silently bring the user back from
            // the almost-dead.
            uss.setState(uss.lastState);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            updateStartedUserArrayLocked();
            needStart = true;
        } else if (uss.state == UserState.STATE_SHUTDOWN) {
            // This means ACTION_SHUTDOWN has been sent, so we will
            // need to treat this as a new boot of the user.
            uss.setState(UserState.STATE_BOOTING);
            mInjector.getUserManagerInternal().setUserState(userId, uss.state);
            updateStartedUserArrayLocked();
            needStart = true;
        }

        if (uss.state == UserState.STATE_BOOTING) {
            // Give user manager a chance to propagate user restrictions
            // to other services and prepare app storage

            // 设置新用户的权限,校验或准备新用户app存储
            mInjector.getUserManager().onBeforeStartUser(userId);

            // Booting up a new user, need to tell system services about it.
            // Note that this is on the same handler as scheduling of broadcasts,
            // which is important because it needs to go first.

            // 通知系统所有的服务新用户已经启动 (如:JobSchedulerService会根据Job对应的用户是否启动来确定是否需要再继续维护Job)
            mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
        }

        if (foreground) {
            // 通知系统所有服务用户切换 (如:如果当前用户连接的是私有网络(如隐藏WiFi),则切换到新用户需要断开)
            mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
                    oldUserId));
            mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
            mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
            mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                    oldUserId, userId, uss));
            // 设置3s超时,如果3s内没有完成用户切换,则停止切换并解冻屏幕。
            // 上述任务完成后会取消掉该延迟消息,并最终都会调用到continueUserSwitch方法,最终调用 dispatchUserSwitchComplete
            mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                    oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
        }

        if (needStart) {
            // Send USER_STARTED broadcast
            // 发送ACTION_USER_STARTED广播
            Intent intent = new Intent(Intent.ACTION_USER_STARTED);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                    | Intent.FLAG_RECEIVER_FOREGROUND);
            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
            mInjector.broadcastIntentLocked(intent,
                    null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                    null, false, false, MY_PID, SYSTEM_UID, userId);
        }

        if (foreground) {
            // Stop当前用户的Activity,如果待切换用户之前存在前台Activity,则在对应的ActivityStack中将其拉到Top并resume该Activity,否则启动桌面Activity
            // 在待切换新用户使用Handler并首次调用MessageQueue.next()方法时,会调用AMS的activityIdle方法,进而调用activityIdleInternalLocked方法,此时会检查mStartingUsers列表, 根据待启动用户的userId调用UserController的finishUserSwitch
            moveUserToForegroundLocked(uss, oldUserId, userId);
        } else {
            finishUserBoot(uss);
        }

        if (needStart) {
            Intent intent = new Intent(Intent.ACTION_USER_STARTING);
            intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
            intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
            mInjector.broadcastIntentLocked(intent,
                    null, new IIntentReceiver.Stub() {
                        @Override
                        public void performReceive(Intent intent, int resultCode,
                                String data, Bundle extras, boolean ordered, boolean sticky,
                                int sendingUser) throws RemoteException {
                        }
                    }, 0, null, null,
                    new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                    null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
        }
    }
} finally {
    Binder.restoreCallingIdentity(ident);
}

return true;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
方法很长,涉及到AMS和WMS的方法分支也很多。切换分为前台切换和后台切换,这里从前台切换侧并且对用户未启动的情况总结下关键的切换过程:

  1. 切换前冻结屏幕,禁止一切输入操作

冻结输入事件
强制结束App动画
截取当前屏幕并显示
// WindowManagerService.java
void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim, DisplayContent displayContent) {
//...
mInputMonitor.freezeInputDispatchingLw();

// Clear the last input window -- that is just used for
// clean transitions between IMEs, and if we are freezing
// the screen then the whole world is changing behind the scenes.
mPolicy.setLastInputMethodWindowLw(null, null);

if (mAppTransition.isTransitionSet()) {
    mAppTransition.freeze();
}
// ...
displayContent.updateDisplayInfo();
screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent, mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, this);
mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId, screenRotationAnimation);
// ...

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
上述这个过程在屏幕旋转的过程中也会执行,因此截取屏幕并展示也是采用和横竖屏切换一样的方式(ScreenRotationAnimation):

// ScreenRotationAnimation.java
// ...
int flags = SurfaceControl.HIDDEN;
if (isSecure) {
flags |= SurfaceControl.SECURE;
}

if (DEBUG_SURFACE_TRACE) {
mSurfaceControl = new SurfaceTrace(session, "ScreenshotSurface",
mWidth, mHeight,
PixelFormat.OPAQUE, flags);
Slog.w(TAG, "ScreenRotationAnimation ctor: displayOffset="
+ mOriginalDisplayRect.toShortString());
} else {
mSurfaceControl = new SurfaceControl(session, "ScreenshotSurface",
mWidth, mHeight,
PixelFormat.OPAQUE, flags);
}
// capture a screenshot into the surface we just created
Surface sur = new Surface();
sur.copyFrom(mSurfaceControl);
// TODO(multidisplay): we should use the proper display
SurfaceControl.screenshot(SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN), sur);
mSurfaceControl.setLayerStack(display.getLayerStack());
mSurfaceControl.setLayer(SCREEN_FREEZE_LAYER_SCREENSHOT);
mSurfaceControl.setAlpha(0);
mSurfaceControl.show();
sur.destroy();
// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
Start1:如果是待启动用户,则初始化待启动用户的状态为STATE_BOOTING,

  1. 为待切换用户更改系统配置,设置Keyguard

从SettingsProvider读取待切换用户的字体、语言、地区等配置并更新到系统。如果是初创用户,则字体使用默认配置,语言和地区使用当前用户的配置
为待切换用户更新资源:如Attributes、Drawable、Color、Animator、StateList等。(有兴趣可以重点看下AMS的updateGlobalConfiguration()方法)

修改当前用户下所有Window的可见性,启动Keyguard,切换过程中关闭Keyguard的指纹监听,并设置锁屏

注:在Android8.0以前,Keyguard是一个单独的System App,8.0后将其移至SystemUI中。该模块的功能主要有:1. 展示和隐藏锁屏界面;2. 认证和校验锁屏密码、指纹密码等

Start2:如果是待启动用户

为待启动用户设置权限,校验或准备待启动用户的App存储目录
通知系统所有服务新用户正在启动(如JobSchedulerService会根据Job对应的用户是否启动来决定Job的维护)

  1. 并行通知系统所有服务用户开始切换:

系统所有服务及相关监听者在收到开始切换的消息后进行一系列的操作也是用户切换所要完成的核心任务。

  1. 设置切换超时定时器

设置3s的延迟消息,如果3s内没有完成用户切换(取消该消息),则终止切换过程并执行UserController.continueUserSwitch()方法。(在步骤3中所有系统服务及相关监听者完成切换任务后,也会执行UserController.continueUserSwitch()方法)

  1. 将待切换用户拉到前台

stop当前用户下所有的Activity
修改所有ActivityStack中TaskRecord的顺序,将切换用户或者在两个用户中都能运行的Task移动到栈顶
将最顶端Task对应的Window移动到最顶端
取出切换应用之前存在的前台Activity置于前台并resume,如果没有前台应用,则启动HomeActivity
发送用户切换广播。(如果是后台切换,则发送ACTION_USER_BACKGROUND,如果是后台切换,则发送ACTION_USER_FOREGROUND和ACTION_USER_SWITCHED)
// UserController.java
void moveUserToForegroundLocked(UserState uss, int oldUserId, int newUserId) {
boolean homeInFront =
mInjector.getActivityStackSupervisor().switchUserLocked(newUserId, uss);
if (homeInFront) {
// 如果之前没有前台应用,则启动HomeActivity
mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
} else {
// 如果之前有前台应用,则resume该Activity
mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
// 对于切换前的用户,发送ACTION_USER_BACKGROUND广播,对于切换后的用户,发送 ACTION_USER_FOREGROUND和ACTION_USER_SWITCHED广播
sendUserSwitchBroadcastsLocked(oldUserId, newUserId);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
ActivityStackSupervisor.switchUserLocked():

boolean switchUserLocked(int userId, UserState uss) {
final int focusStackId = mFocusedStack.getStackId();
// We dismiss the docked stack whenever we switch users.
moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
// Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will also cause all tasks to be moved to the fullscreen stack at a position that is appropriate.
// Stop画中画对应ActivityStack中所有的Activity
removeStackLocked(PINNED_STACK_ID);

mUserStackInFront.put(mCurrentUser, focusStackId);
final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
mCurrentUser = userId;

// 后续会根据这个List来finishUserSwitch
mStartingUsers.add(uss);
for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
    final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
    for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
        final ActivityStack stack = stacks.get(stackNdx);
        // 这里会移动ActivityStack中所有的Task,并将能在待切换用户中运行的Task置于栈顶
        stack.switchUserLocked(userId);
        TaskRecord task = stack.topTask();
        if (task != null) {
            // 将最顶端的Task对应的Window置顶
            stack.positionChildWindowContainerAtTop(task);
        }
    }
}

// 取出待切换用户之前存在的前台应用对应的ActivityStack
ActivityStack stack = getStack(restoreStackId);
if (stack == null) {
    stack = mHomeStack;
}
final boolean homeInFront = stack.isHomeStack();
if (stack.isOnHomeDisplay()) {
    // 将之前存在的前台应用置于前台
    stack.moveToFront("switchUserOnHomeDisplay");
} else {
    // Stack was moved to another display while user was swapped out.
    resumeHomeStackTask(null, "switchUserOnOtherDisplay");
}
return homeInFront;

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

  1. 步骤3完成或者4中切换超时消息到达时需要继续进行的切换操作(continueUserSwitch)

解冻屏幕和输入
设置Keyguard,如果切换用户设置了指纹,则需要开始监听指纹信息
通知监听者用户已经完成了切换
UserController.continueUserSwitch()方法执行流程:

  1. 完成切换用户

如果是后台切换,则直接调用UserController.finishUserBoot()方法
如果是前台切换,ActivityThread会在handleResumeActivity时设置Main线程MessageQueue的mIdleHandlers,在MessageQueue执行next()方法会检查该列表并最终调用到AMS的activityIdle()方法中,此时会检查正在切换的用户列表并调用最终调用到UserController.finishUserBoot()方法
设置切换用户的状态为STATE_RUNNING_LOCKED
前台切换情况下finishUserBoot()方法的调用流程:

Start3:如果是新启动的用户,则通知系统所有用户监听者用户已经启动,并发送ACTION_LOCKED_BOOT_COMPLETED广播,在Keyguard第一次解锁时,会发送ACTION_BOOT_COMPLETED广播

3.3 多用户的删除
adb shell pm remove-user ${userId}

入口是UserManagerService.removeUser(),这里不详细分析,与创建于切换用户的流程类似。

3.4 多用户的真面目
从上面对Android多用户的创建和切换流程来看,我们可以总结出:

多用户其实是系统为应用的data目录和storage目录分配了一份不同且独立的存储空间,不同用户下的存储空间互不影响且没有权限访问。同时,系统中的AMS、PMS、WMS等各大服务都会针对userId/UserHandle进行多用户适配,并在用户启动、切换、停止、删除等生命周期时做出相应策略的改变。通过以上两点,Android创造出来一个虚拟的多用户运行环境。

五、多用户下的四大组件和数据共享

5.1 获取当前用户userId的方式
UserHandle中提供myUserId()方法,但是被hide的,可以反射获取:(或者直接根据uid / 100000计算)

private fun readUserIdByReflect(): Int {
var userId = 0
try {
val clz = UserHandle::class.java
val myUserIdMethod = clz.getDeclaredMethod("myUserId")
userId = myUserIdMethod.invoke(null) as Int
Log.i("ulangch-r", "userId=$userId")
} catch (e: Exception) {
}
return userId
}
1
2
3
4
5
6
7
8
9
10
11

5.2 跨用户启动Activity
Activity/Context提供了startActivityAsUser() 方法,可以传入对应用户的UserHandle来达到跨用户启动Activity的目的,Context中对该方法进行了注释:

/**

  • Version of {@link #startActivity(Intent)} that allows you to specify the
  • user the activity will be started for. This is not available to applications
  • that are not pre-installed on the system image.
  • @param intent The description of the activity to start.
  • @param user The UserHandle of the user to start this activity for.
  • @throws ActivityNotFoundException  
  • @hide
    */
    @RequiresPermission(android.Manifest.permission.INTERACT_ACROSS_USERS_FULL)
    public void startActivityAsUser(@RequiresPermission Intent intent, UserHandle user) {
    throw new RuntimeException("Not implemented. Must override in a subclass.");
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    只有具有"android.Manifest.permission.INTERACT_ACROSS_USERS_FULL"的系统应用才可以调用该方法,经过反射测试,的确会抛出SecurityException:

// 反射调用startActivityAsUser会抛出SecurityException
07-01 20:59:40.445 25832 25832 W System.err: Caused by: java.lang.SecurityException: Permission Denial: startActivityAsUser asks to run as user 0 but is calling from user 13; this requires android.permission.INTERACT_ACROSS_USERS_FULL
07-01 20:59:40.445 25832 25832 W System.err: at android.os.Parcel.createException(Parcel.java:1953)
07-01 20:59:40.445 25832 25832 W System.err: at android.os.Parcel.readException(Parcel.java:1921)
07-01 20:59:40.445 25832 25832 W System.err: at android.os.Parcel.readException(Parcel.java:1871)
07-01 20:59:40.446 25832 25832 W System.err: at android.app.IActivityManagerStubProxy.startActivityAsUser(IActivityManager.java:6787)
07-01 20:59:40.446 25832 25832 W System.err: at android.app.Instrumentation.execStartActivity(Instrumentation.java:1887)
07-01 20:59:40.446 25832 25832 W System.err: at android.app.Activity.startActivityAsUser(Activity.java:4782)
07-01 20:59:40.446 25832 25832 W System.err: ... 16 more
07-01 20:59:40.446 25832 25832 W System.err: Caused by: android.os.RemoteException: Remote stack trace:
07-01 20:59:40.446 25832 25832 W System.err: at com.android.server.am.UserController.handleIncomingUser(UserController.java:1581)
07-01 20:59:40.446 25832 25832 W System.err: at com.android.server.am.ActivityStartController.checkTargetUser(ActivityStartController.java:240)
07-01 20:59:40.446 25832 25832 W System.err: at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:5309)
07-01 20:59:40.446 25832 25832 W System.err: at com.android.server.am.ActivityManagerService.startActivityAsUser(ActivityManagerService.java:5298)
07-01 20:59:40.446 25832 25832 W System.err: at android.app.IActivityManagerStub.onTransactstartActivityAsUser$(IActivityManager.java:11005)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
AMS中的权限检查抛出异常,具有 “android.permission.INTERACT_ACROSS_USERS_FULL” 和 “android.permission.INTERACT_ACROSS_USERS” 权限的系统应用才可以,以startActivityAsUser为例:

// ActivityManagerService.java
@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
enforceNotIsolatedCaller("startActivity");
userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null);
// TODO: Switch to user app stacks here.
return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
}
1
2
3
4
5
6
7
8
最终会使用UserController.handleIncomingUser() 方法来做跨用户的权限检查:

// UserController.java
int handleIncomingUser(int callingPid, int callingUid, int userId, boolean allowAll, int allowMode, String name, String callerPackage) {
final int callingUserId = UserHandle.getUserId(callingUid);
if (callingUserId == userId) {
return userId;
}
int targetUserId = unsafeConvertIncomingUserLocked(userId);

if (callingUid != 0 && callingUid != SYSTEM_UID) {
    final boolean allow;
    if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS_FULL, callingPid, callingUid, -1, true) == PackageManager.PERMISSION_GRANTED) {
        allow = true;
    } else if (allowMode == ALLOW_FULL_ONLY) {
        allow = false;
    } else if (mInjector.checkComponentPermission(INTERACT_ACROSS_USERS, callingPid, callingUid, -1, true) != PackageManager.PERMISSION_GRANTED) {
        allow = false;
    } else if (allowMode == ALLOW_NON_FULL) {
        allow = true;
    } else if (allowMode == ALLOW_NON_FULL_IN_PROFILE) {
        allow = isSameProfileGroup(callingUserId, targetUserId);
    } else {
        throw new IllegalArgumentException("Unknown mode: " + allowMode);
    }
    if (!allow) {
        if (userId == UserHandle.USER_CURRENT_OR_SELF) {
            targetUserId = callingUserId;
        } else {
            StringBuilder builder = new StringBuilder(128);
            builder.append("Permission Denial: ");
            // ...
            throw new SecurityException(msg);
        }
    }
}

// ...
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

5.3 跨用户启动Service
Context中提供了startServiceAsUser() 方法,经过反射测试,也是跨不过AMS的权限检查:

// 反射调用startServiceAsUser会抛出SecurityException
07-01 20:56:33.759 25832 25832 W System.err: Caused by: java.lang.SecurityException: Permission Denial: service asks to run as user 0 but is calling from user 13; this requires android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS
07-01 20:56:33.760 25832 25832 W System.err: at android.os.Parcel.createException(Parcel.java:1953)
07-01 20:56:33.760 25832 25832 W System.err: at android.os.Parcel.readException(Parcel.java:1921)
07-01 20:56:33.760 25832 25832 W System.err: at android.os.Parcel.readException(Parcel.java:1871)
07-01 20:56:33.760 25832 25832 W System.err: at android.app.IActivityManagerStubProxy.startService(IActivityManager.java:4243)
07-01 20:56:33.760 25832 25832 W System.err: at android.app.ContextImpl.startServiceCommon(ContextImpl.java:1572)
07-01 20:56:33.761 25832 25832 W System.err: at android.app.ContextImpl.startServiceAsUser(ContextImpl.java:1559)
07-01 20:56:33.761 25832 25832 W System.err: at android.content.ContextWrapper.startServiceAsUser(ContextWrapper.java:690)
07-01 20:56:33.761 25832 25832 W System.err: ... 16 more
07-01 20:56:33.761 25832 25832 W System.err: Caused by: android.os.RemoteException: Remote stack trace:
07-01 20:56:33.761 25832 25832 W System.err: at com.android.server.am.UserController.handleIncomingUser(UserController.java:1581)
07-01 20:56:33.761 25832 25832 W System.err: at com.android.server.am.ActiveServices.retrieveServiceLocked(ActiveServices.java:1914)
07-01 20:56:33.762 25832 25832 W System.err: at com.android.server.am.ActiveServices.startServiceLocked(ActiveServices.java:427)
07-01 20:56:33.762 25832 25832 W System.err: at com.android.server.am.ActivityManagerService.startService(ActivityManagerService.java:21017)
07-01 20:56:33.762 25832 25832 W System.err: at android.app.IActivityManagerStub.onTransactstartService$(IActivityManager.java:10318)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

5.4 跨用户发送广播
Context中提供了sendBroadcastAsUser() 方法,但与Activity 和 Service 相同,反射调用也会抛出异常。

5.5 跨用户Query
系统没有提供类似getContentResolverAsUser的方法,但ContentResolver提供了跨用户query的能力。ContentProvider中提供了hide 的 maybeAddUserId() 方法,被query的Uri中可以携带userId(如: "content://10@com.android.contacts/contacts","//10@" 中的10就是userId),通过Uri中的userId,可以访问到不同用户下相同Uri的ContentProvider。

// ContentProvider.java
/** @hide */
public static Uri maybeAddUserId(Uri uri, int userId) {
if (uri == null) return null;
if (userId != UserHandle.USER_CURRENT
&& ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
if (!uriHasUserId(uri)) {
//We don't add the user Id if there's already one
Uri.Builder builder = uri.buildUpon();
builder.encodedAuthority("" + userId + "@" + uri.getEncodedAuthority());
return builder.build();
fe }
}
return uri;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
但是对于三方应用来说,仍然不可以通过这种方式实现跨用户共享数据,AMS还是会检查权限:

07-01 21:24:17.555 26991 26991 E AndroidRuntime: Caused by: android.os.RemoteException: Remote stack trace:
07-01 21:24:17.555 26991 26991 E AndroidRuntime: at com.android.server.am.UserController.handleIncomingUser(UserController.java:1581)
07-01 21:24:17.555 26991 26991 E AndroidRuntime: at com.android.server.am.ActivityManagerService.checkContentProviderPermissionLocked(ActivityManagerService.java:12408)
07-01 21:24:17.555 26991 26991 E AndroidRuntime: at com.android.server.am.ActivityManagerService.getContentProviderImpl(ActivityManagerService.java:12687)
07-01 21:24:17.555 26991 26991 E AndroidRuntime: at com.android.server.am.ActivityManagerService.getContentProvider(ActivityManagerService.java:13137)
07-01 21:24:17.555 26991 26991 E AndroidRuntime: at android.app.IActivityManager$Stub.onTransact(IActivityManager.java:358)
1
2
3
4
5
6

5.6 /storage目录的跨用户访问
不可以互相访问。如用户10的app无法访问 /storage/emulated/0 下的文件

5.7 跨用户的数据共享
通过上面的介绍,对于系统和系统应用来说,实现多用户的数据共享很方便,但系统对三方应用屏蔽了这些跨用户的能力。的确,三方应用在绝大数据场景下无需关心自己处于哪个用户下,也不会涉及到跨用户的数据共享,但我们依然可以进行一些尝试,来了解如何在多用户下进行数据共享。

方式一:使用本地回环地址(127.0.0.1)socket进行跨用户通信

创建本地ServerSocket,在另一个用户使用Socket发送消息,是可以收到的,说明多用户间可以通过Socket进行通信。

另:以下方法经过尝试证明了不可行

Settings.Global (不可行,需要 “android.permission.WRITE_SECURE_SETTINGS” 权限)

“/storage/emulated/obb” (不可行,Permission denied)
————————————————
版权声明:本文为CSDN博主「ulangch」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/qq_14978113/article/details/94654401

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 160,165评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,720评论 1 298
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,849评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,245评论 0 213
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,596评论 3 288
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,747评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,977评论 2 315
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,708评论 0 204
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,448评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,657评论 2 249
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,141评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,493评论 3 258
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,153评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,108评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,890评论 0 198
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,799评论 2 277
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,685评论 2 272

推荐阅读更多精彩内容