【轉(zhuǎn)】深入理解Android系統(tǒng)多用戶

一、引言
這篇文章以Android v28的源碼為參考,介紹Android多用戶的特性、使用方式和系統(tǒng)原理。

二、初識Android多用戶

2.1 Android多用戶簡介
從Android 4.0開始,Google就開始在Android上布局多用戶,UserManager因此而誕生,然而此時還沒有對應(yīng)的Binder服務(wù)。真正支持多用戶是從Android 4.2 開始,即便如此,系統(tǒng)中也依然存在各種Bug和兼容性問題。直到Android 6.0,Android多用戶才比較完善,國內(nèi)外的廠家也紛紛開始針對多用戶這個噱頭來做各種 “花里胡哨” 的操作,“手機分身”、“分身應(yīng)用”、“應(yīng)用雙開” 應(yīng)運而生,不得不說,國內(nèi)的廠家在多用戶這方面定制化到如今已經(jīng)非常穩(wěn)定和完善了。

下圖從左到右分別為小米手機的手機分身、應(yīng)用雙開以及華為手機的多用戶:

2.2 基礎(chǔ)概念
要學(xué)習(xí)多用戶,首先我們需要了解一些基礎(chǔ)概念:

Uid(用戶Id):在Linux上,一個用戶Uid標(biāo)識著一個給定的用戶。Android上也沿用了Linux用戶的概念,Root用戶Uid為0,System Uid為1000,并且,每個應(yīng)用程序在安裝時也被賦予了單獨的Uid,這個Uid將伴隨著應(yīng)用從安裝到卸載。

Gid(用戶組Id):Linux上規(guī)定每個應(yīng)用都應(yīng)該有一個用戶組,對于Android應(yīng)用程序來說,每個應(yīng)用的所屬用戶組與Uid相同。

Gids:應(yīng)用在安裝后所獲得權(quán)限的Id集合。在Android上,每個權(quán)限都可能對應(yīng)一個或多個group,每個group有個gid name,gids就是通過對每個gid name計算得出的id集合,一個UID可以關(guān)聯(lián)GIDS,表明該UID擁有多種權(quán)限。

對于Android中的每個進(jìn)程,都有一個單獨的Uid、Gid以及Gids集合(這個地方有點疑惑,Android Framework中的權(quán)限控制不依賴這個Gids,這個是Linux上有的東西,有待考證),通過這三者,Android系統(tǒng)實現(xiàn)了一套文件和數(shù)據(jù)訪問權(quán)限規(guī)則系統(tǒng)。如:

訪問某個文件,文件系統(tǒng)規(guī)定了該文件在磁盤中的rwx(read/write/excute)和 SELinux 權(quán)限:
root@virgo:/ # ls -lZ /system/xbin/su
-rwsr-sr-x root shell u:object_r:su_exec:s0 su
1
2
訪問Framework中提供的某個服務(wù)功能,Android規(guī)定了該功能的訪問權(quán)限:
// 網(wǎng)絡(luò)訪問權(quán)限,通過Binder.getCallingUid()
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"ConnectivityService");
}
1
2
3
4
5
6

Android權(quán)限知識拓展:

安裝時權(quán)限的獲取記錄存儲在:/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
運行時權(quán)限的獲取記錄存儲在:/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系統(tǒng)和應(yīng)用安裝后的權(quán)限聲明保存在 “/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權(quán)限:

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
這里只截取了部分權(quán)限,我們發(fā)現(xiàn)每個我們常見的權(quán)限都可能對應(yīng)一個或多個group gid,而我們上面說的gids就是由這個group gid生成的集合。具體的權(quán)限讀取和gids生成流程如下:

三、多用戶的特性

3.1 獨立的userId
Android在創(chuàng)建每個用戶時,都會分配一個整型的userId。對于主用戶(正常下的默認(rèn)用戶)來說,userId為0,之后創(chuàng)建的userId將從10開始計算,每增加一個userId加1:

root@virgo:/ # pm list users
Users:
UserInfo{0:機主:13} running
UserInfo{10:security space:11} running
1
2
3
4
創(chuàng)建一個名為"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 獨立的文件存儲
為了多用戶下的數(shù)據(jù)安全性,在每個新用戶創(chuàng)建之初,不管是外部存儲(External Storage)還是app data目錄,Android都為其準(zhǔn)備了獨立的文件存儲。

多用戶下的/storage分區(qū):

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
新用戶創(chuàng)建時,Android在 “/storage/emulated” 目錄下為每個用戶都創(chuàng)建了名為用戶id的目錄,當(dāng)我們在代碼中使用 “Environment.getExternalStorageDirectory().absolutePath” 獲取外部存儲路徑時,返回的就是當(dāng)前用戶下的對應(yīng)目錄(如:userId = 11, 則返回為 “/storage/emulated/11”)。

另外,可以看出,我們平常說到的 “/sdcard” 目錄其實最終也是軟鏈到了 “/storage/emulated/0”

多用戶下的/data分區(qū):

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相同,新用戶創(chuàng)建時,Android也會在 “data/user” 目錄下創(chuàng)建了名為userId的目錄,用于存儲該用戶中所有App的隱私數(shù)據(jù),如果在代碼中使用 “Context.getFilesDir()” 來獲取應(yīng)用的data目錄,不同User下也會有不同。

另外,也可以看出,平常說到的 “/data/data” 目錄其實也是軟鏈到了 “/data/user/0”。

下圖是兩個用戶下同一個App的獲取結(jié)果:

注:在Android中,應(yīng)用的uid是和當(dāng)前的用戶有關(guān)的,同一個應(yīng)用具有相同的appId,其uid的計算方式為: uid = userId * 1000000 + appId,在主用戶中,uid = appId。

3.3 獨立的權(quán)限控制
不同用戶具有的權(quán)限不同,如:訪客用戶的默認(rèn)權(quán)限限制就有:
perseus:/ $ dumpsys user
...
Guest restrictions:
no_sms // 限制發(fā)送短信
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等)

這些權(quán)限可以在創(chuàng)建用戶時規(guī)定,也可以后期由系統(tǒng)動態(tài)設(shè)置。

不同用戶下App的應(yīng)用權(quán)限是獨立的
前面說到,uid與userId存在一種計算關(guān)系(uid = userId * 1000000 + appId),而在系統(tǒng)中對于權(quán)限控制也是根據(jù)uid和對應(yīng)的userId來判定的,因此不同用戶下相同應(yīng)用可以具有不同的權(quán)限。

3.4 App安裝的唯一性
雖然前面說到,App的文件存儲和數(shù)據(jù)目錄在不同用戶下都是獨立的,但是對于App的安裝,多個用戶下同一個App卻保持著同一個安裝目錄,即:

普通三方app:/data/app/
普通系統(tǒng)應(yīng)用:/system/app/
特權(quán)系統(tǒng)應(yīng)用:/system/priv-app/
root@virgo:/ # ls /data/app/com.ulangch.multiuser-1/
base.apk
lib
oat
1
2
3
4
拓展:權(quán)限在聲明時安全等級(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:普通權(quán)限,在AndroidManifest.xml中聲明就可以獲取的權(quán)限,如INTERNET權(quán)限
dangerous:敏感權(quán)限,需要動態(tài)申請告知用戶才能獲取
signature|privileged:具有系統(tǒng)簽名的系統(tǒng)應(yīng)用才可以獲取的權(quán)限,對應(yīng)上方的 “/system/priv-app”
因此,多用戶下的應(yīng)用其實只安裝一次,不同用戶下同一個應(yīng)用的版本和簽名都應(yīng)該相同,不同用戶下相同App能夠獨立運行是因為系統(tǒng)為他們創(chuàng)造了不同的運行環(huán)境和權(quán)限。

3.5 kernel及系統(tǒng)進(jìn)程的不變性
在不同用戶下,雖然能夠看到不同的桌面,不同的運行環(huán)境,一切都感覺是新的,但是我們系統(tǒng)本身并沒有發(fā)生改變,kernel進(jìn)程、system_server進(jìn)程以及所有daemon進(jìn)程依然是同一個,并不會重啟。

而如果我們在不同用戶中開啟相同的app,我們可以看到可以有多個app進(jìn)程,而他們的父進(jìn)程都是同一個,即 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

四、流程分析
多用戶的創(chuàng)建、啟動、停止等行為是系統(tǒng)級的,因此只有具有root、system權(quán)限的進(jìn)程才能操作。

3.1 多用戶的創(chuàng)建
adb shell pm create-user [–profileOf USER_ID] [–managed] USER_NAME

多用戶的創(chuàng)建流程主要在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 鎖中
// 用戶創(chuàng)建條件判斷
// 一個用戶只能有一個ManagedProfile
if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
return null;
}
// 判斷是否達(dá)到最大用戶數(shù),Android 6.0上最大用戶數(shù)為5,可配置
if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
return null;
}
// 系統(tǒng)中只能有一個訪客用戶
if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// ... 省略關(guān)于 restricted 和 ephemeral 類型的創(chuàng)建判斷

// 獲取下一個userId,userId從10開始遞增,除非達(dá)到"上限",否則前面的userId不會復(fù)用
userId = getNextAvailableId();
// 創(chuàng)建 "/data/system/users/{userId}"目錄 Environment.getUserSystemDirectory(userId).mkdirs(); synchronized (mUsersLock) { // 構(gòu)造UserInfo和UserData,后面會進(jìn)行固化保存 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表明用戶還沒有創(chuàng)建完成,為了防止創(chuàng)建用戶進(jìn)程被突然停止,下次系統(tǒng)重啟時 // 需要檢查文件中該屬性,如果為true則需要繼續(xù)用戶的創(chuàng)建 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);
// 將新創(chuàng)建的userId固化到 "/data/system/users/userlist.xml"
writeUserListLP();
// ... 省略一部分profile和restricted類型的寫文件操作

// 為新用戶準(zhǔn)備文件系統(tǒng)
final StorageManager storage = mContext.getSystemService(StorageManager.class);
// 通過vold對新用戶進(jìn)行文件系統(tǒng)加密(相關(guān):http://www.itdecent.cn/p/d25f73805729
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
// 通過vold創(chuàng)建以下目錄,并賦予相關(guān)rwx權(quán)限:
// "/data/system/users/{userId}" : 0700 // "/data/misc/users/{userId}" : 0750
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
// 為已安裝應(yīng)用創(chuàng)建"/data/system/user/{userId}/{packageName}"目錄
// ...
mPm.createNewUser(userId, disallowedPackages);
// 用戶已經(jīng)創(chuàng)建完成,固化用戶創(chuàng)建狀態(tài)
userInfo.partial = false;
synchronized (mPackagesLock) {
writeUserLP(userData);
}
// 更新所有緩存的用戶
updateUserIds();
// ...省略guest 和 restrictions

// 為新創(chuàng)建的用戶賦予默認(rèn)權(quán)限
mPm.onNewUserCreated(userId);
// 向所有用戶發(fā)送 "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
從上面的代碼分析可以看出,用戶創(chuàng)建的過程主要是應(yīng)用運行環(huán)境(文件系統(tǒng)、權(quán)限等)的準(zhǔn)備過程,主要可以分為以下幾個關(guān)鍵的步驟:(忽略訪客用戶相關(guān)的操作)

  1. 為新用戶創(chuàng)建一個新的userId (新用戶的userId從10開始遞增)

  2. 固化新用戶信息和創(chuàng)建狀態(tài)

構(gòu)造包含新用戶信息的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
將新創(chuàng)建新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. 準(zhǔn)備文件系統(tǒng):

通過vold(Android存儲守護(hù)進(jìn)程)為新用戶進(jìn)行文件系統(tǒng)加密
創(chuàng)建"/data/system/users/{userId}" 并設(shè)置 “0700” 權(quán)限 創(chuàng)建 “/data/misc/users/{userId}” 并設(shè)置 “0750” 權(quán)限

  1. 為已安裝應(yīng)用準(zhǔn)備數(shù)據(jù)目錄并記錄其組件和默認(rèn)權(quán)限配置:

在 “/data/user/{userId}/” 下創(chuàng)建各個已安裝應(yīng)用的package目錄 在 “data/system/users/{userId}/package-restrictions.xml” 中寫入非默認(rèn)啟動組件的信息
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可能會改變。(這個改變的可能性是根據(jù)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. 固化新用戶創(chuàng)建完成的狀態(tài)、通知PMS為新用戶和應(yīng)用賦予默認(rèn)的權(quán)限

  2. 發(fā)送 “ACTION_USER_ADDED” 廣播,新用戶創(chuàng)建完成

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多用戶的切換函數(shù)入口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,最終都會調(diào)用到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. 凍結(jié)輸入事件
            // 2. 強制結(jié)束所有動畫
            // 3. 截取當(dāng)前屏幕并展示
            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);
            // 初始狀態(tài)為 STATE_BOOTING
            mInjector.getUserManagerInternal().setUserState(userId, userState.state);
            updateStartedUserArrayLocked();
            needStart = true;
        }

        final UserState uss = mStartedUsers.get(userId);
        final Integer userIdInt = userId;
        // 修改當(dāng)前用戶歷史
        mUserLru.remove(userIdInt);
        mUserLru.add(userIdInt);

        if (foreground) {
            mCurrentUserId = userId;
            // 從Setting Provider讀取需要切換用戶的字體、語言、地區(qū)等配置并更新
            // 如果是初創(chuàng)用戶,對于字體則使用默認(rèn)配置,語言和地區(qū)使用當(dāng)前用戶的配置:https://android.googlesource.com/platform/frameworks/base/+/ea906b3%5E!/
            mInjector.updateUserConfigurationLocked();
            mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up

            // 更新當(dāng)前用戶附屬的ManageProfile
            updateCurrentProfileIdsLocked();
            // 設(shè)置當(dāng)前用戶下所有window的可見性
            // 設(shè)置切換用戶的屏幕分辨率
            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) {
                // 切換過程中關(guān)閉Keyguard的指紋監(jiān)聽
                mInjector.getWindowManager().setSwitchingUser(true);
                // 設(shè)置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

            // 設(shè)置新用戶的權(quán)限,校驗或準(zhǔn)備新用戶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.

            // 通知系統(tǒng)所有的服務(wù)新用戶已經(jīng)啟動 (如:JobSchedulerService會根據(jù)Job對應(yīng)的用戶是否啟動來確定是否需要再繼續(xù)維護(hù)Job)
            mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
        }

        if (foreground) {
            // 通知系統(tǒng)所有服務(wù)用戶切換 (如:如果當(dāng)前用戶連接的是私有網(wǎng)絡(luò)(如隱藏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));
            // 設(shè)置3s超時,如果3s內(nèi)沒有完成用戶切換,則停止切換并解凍屏幕。
            // 上述任務(wù)完成后會取消掉該延遲消息,并最終都會調(diào)用到continueUserSwitch方法,最終調(diào)用 dispatchUserSwitchComplete
            mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                    oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
        }

        if (needStart) {
            // Send USER_STARTED broadcast
            // 發(fā)送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當(dāng)前用戶的Activity,如果待切換用戶之前存在前臺Activity,則在對應(yīng)的ActivityStack中將其拉到Top并resume該Activity,否則啟動桌面Activity
            // 在待切換新用戶使用Handler并首次調(diào)用MessageQueue.next()方法時,會調(diào)用AMS的activityIdle方法,進(jìn)而調(diào)用activityIdleInternalLocked方法,此時會檢查mStartingUsers列表, 根據(jù)待啟動用戶的userId調(diào)用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的方法分支也很多。切換分為前臺切換和后臺切換,這里從前臺切換側(cè)并且對用戶未啟動的情況總結(jié)下關(guān)鍵的切換過程:

  1. 切換前凍結(jié)屏幕,禁止一切輸入操作

凍結(jié)輸入事件
強制結(jié)束App動畫
截取當(dāng)前屏幕并顯示
// 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
上述這個過程在屏幕旋轉(zhuǎn)的過程中也會執(zhí)行,因此截取屏幕并展示也是采用和橫豎屏切換一樣的方式(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:如果是待啟動用戶,則初始化待啟動用戶的狀態(tài)為STATE_BOOTING,

  1. 為待切換用戶更改系統(tǒng)配置,設(shè)置Keyguard

從SettingsProvider讀取待切換用戶的字體、語言、地區(qū)等配置并更新到系統(tǒng)。如果是初創(chuàng)用戶,則字體使用默認(rèn)配置,語言和地區(qū)使用當(dāng)前用戶的配置
為待切換用戶更新資源:如Attributes、Drawable、Color、Animator、StateList等。(有興趣可以重點看下AMS的updateGlobalConfiguration()方法)

修改當(dāng)前用戶下所有Window的可見性,啟動Keyguard,切換過程中關(guān)閉Keyguard的指紋監(jiān)聽,并設(shè)置鎖屏

注:在Android8.0以前,Keyguard是一個單獨的System App,8.0后將其移至SystemUI中。該模塊的功能主要有:1. 展示和隱藏鎖屏界面;2. 認(rèn)證和校驗鎖屏密碼、指紋密碼等

Start2:如果是待啟動用戶

為待啟動用戶設(shè)置權(quán)限,校驗或準(zhǔn)備待啟動用戶的App存儲目錄
通知系統(tǒng)所有服務(wù)新用戶正在啟動(如JobSchedulerService會根據(jù)Job對應(yīng)的用戶是否啟動來決定Job的維護(hù))

  1. 并行通知系統(tǒng)所有服務(wù)用戶開始切換:

系統(tǒng)所有服務(wù)及相關(guān)監(jiān)聽者在收到開始切換的消息后進(jìn)行一系列的操作也是用戶切換所要完成的核心任務(wù)。

  1. 設(shè)置切換超時定時器

設(shè)置3s的延遲消息,如果3s內(nèi)沒有完成用戶切換(取消該消息),則終止切換過程并執(zhí)行UserController.continueUserSwitch()方法。(在步驟3中所有系統(tǒng)服務(wù)及相關(guān)監(jiān)聽者完成切換任務(wù)后,也會執(zhí)行UserController.continueUserSwitch()方法)

  1. 將待切換用戶拉到前臺

stop當(dāng)前用戶下所有的Activity
修改所有ActivityStack中TaskRecord的順序,將切換用戶或者在兩個用戶中都能運行的Task移動到棧頂
將最頂端Task對應(yīng)的Window移動到最頂端
取出切換應(yīng)用之前存在的前臺Activity置于前臺并resume,如果沒有前臺應(yīng)用,則啟動HomeActivity
發(fā)送用戶切換廣播。(如果是后臺切換,則發(fā)送ACTION_USER_BACKGROUND,如果是后臺切換,則發(fā)送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) {
// 如果之前沒有前臺應(yīng)用,則啟動HomeActivity
mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
} else {
// 如果之前有前臺應(yīng)用,則resume該Activity
mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
// 對于切換前的用戶,發(fā)送ACTION_USER_BACKGROUND廣播,對于切換后的用戶,發(fā)送 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畫中畫對應(yīng)ActivityStack中所有的Activity
removeStackLocked(PINNED_STACK_ID);

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

// 后續(xù)會根據(jù)這個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對應(yīng)的Window置頂
            stack.positionChildWindowContainerAtTop(task);
        }
    }
}

// 取出待切換用戶之前存在的前臺應(yīng)用對應(yīng)的ActivityStack
ActivityStack stack = getStack(restoreStackId);
if (stack == null) {
    stack = mHomeStack;
}
final boolean homeInFront = stack.isHomeStack();
if (stack.isOnHomeDisplay()) {
    // 將之前存在的前臺應(yīng)用置于前臺
    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中切換超時消息到達(dá)時需要繼續(xù)進(jìn)行的切換操作(continueUserSwitch)

解凍屏幕和輸入
設(shè)置Keyguard,如果切換用戶設(shè)置了指紋,則需要開始監(jiān)聽指紋信息
通知監(jiān)聽者用戶已經(jīng)完成了切換
UserController.continueUserSwitch()方法執(zhí)行流程:

  1. 完成切換用戶

如果是后臺切換,則直接調(diào)用UserController.finishUserBoot()方法
如果是前臺切換,ActivityThread會在handleResumeActivity時設(shè)置Main線程MessageQueue的mIdleHandlers,在MessageQueue執(zhí)行next()方法會檢查該列表并最終調(diào)用到AMS的activityIdle()方法中,此時會檢查正在切換的用戶列表并調(diào)用最終調(diào)用到UserController.finishUserBoot()方法
設(shè)置切換用戶的狀態(tài)為STATE_RUNNING_LOCKED
前臺切換情況下finishUserBoot()方法的調(diào)用流程:

Start3:如果是新啟動的用戶,則通知系統(tǒng)所有用戶監(jiān)聽者用戶已經(jīng)啟動,并發(fā)送ACTION_LOCKED_BOOT_COMPLETED廣播,在Keyguard第一次解鎖時,會發(fā)送ACTION_BOOT_COMPLETED廣播

3.3 多用戶的刪除
adb shell pm remove-user ${userId}

入口是UserManagerService.removeUser(),這里不詳細(xì)分析,與創(chuàng)建于切換用戶的流程類似。

3.4 多用戶的真面目
從上面對Android多用戶的創(chuàng)建和切換流程來看,我們可以總結(jié)出:

多用戶其實是系統(tǒng)為應(yīng)用的data目錄和storage目錄分配了一份不同且獨立的存儲空間,不同用戶下的存儲空間互不影響且沒有權(quán)限訪問。同時,系統(tǒng)中的AMS、PMS、WMS等各大服務(wù)都會針對userId/UserHandle進(jìn)行多用戶適配,并在用戶啟動、切換、停止、刪除等生命周期時做出相應(yīng)策略的改變。通過以上兩點,Android創(chuàng)造出來一個虛擬的多用戶運行環(huán)境。

五、多用戶下的四大組件和數(shù)據(jù)共享

5.1 獲取當(dāng)前用戶userId的方式
UserHandle中提供myUserId()方法,但是被hide的,可以反射獲?。海ɑ蛘咧苯痈鶕?jù)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() 方法,可以傳入對應(yīng)用戶的UserHandle來達(dá)到跨用戶啟動Activity的目的,Context中對該方法進(jìn)行了注釋:

/**

  • 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"的系統(tǒng)應(yīng)用才可以調(diào)用該方法,經(jīng)過反射測試,的確會拋出SecurityException:

// 反射調(diào)用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中的權(quán)限檢查拋出異常,具有 “android.permission.INTERACT_ACROSS_USERS_FULL” 和 “android.permission.INTERACT_ACROSS_USERS” 權(quán)限的系統(tǒng)應(yīng)用才可以,以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() 方法來做跨用戶的權(quán)限檢查:

// 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() 方法,經(jīng)過反射測試,也是跨不過AMS的權(quán)限檢查:

// 反射調(diào)用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 跨用戶發(fā)送廣播
Context中提供了sendBroadcastAsUser() 方法,但與Activity 和 Service 相同,反射調(diào)用也會拋出異常。

5.5 跨用戶Query
系統(tǒng)沒有提供類似getContentResolverAsUser的方法,但ContentResolver提供了跨用戶query的能力。ContentProvider中提供了hide 的 maybeAddUserId() 方法,被query的Uri中可以攜帶userId(如: "content://10@com.android.contacts/contacts","http://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
但是對于三方應(yīng)用來說,仍然不可以通過這種方式實現(xiàn)跨用戶共享數(shù)據(jù),AMS還是會檢查權(quán)限:

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 跨用戶的數(shù)據(jù)共享
通過上面的介紹,對于系統(tǒng)和系統(tǒng)應(yīng)用來說,實現(xiàn)多用戶的數(shù)據(jù)共享很方便,但系統(tǒng)對三方應(yīng)用屏蔽了這些跨用戶的能力。的確,三方應(yīng)用在絕大數(shù)據(jù)場景下無需關(guān)心自己處于哪個用戶下,也不會涉及到跨用戶的數(shù)據(jù)共享,但我們依然可以進(jìn)行一些嘗試,來了解如何在多用戶下進(jìn)行數(shù)據(jù)共享。

方式一:使用本地回環(huán)地址(127.0.0.1)socket進(jìn)行跨用戶通信

創(chuàng)建本地ServerSocket,在另一個用戶使用Socket發(fā)送消息,是可以收到的,說明多用戶間可以通過Socket進(jìn)行通信。

另:以下方法經(jīng)過嘗試證明了不可行

Settings.Global (不可行,需要 “android.permission.WRITE_SECURE_SETTINGS” 權(quán)限)

“/storage/emulated/obb” (不可行,Permission denied)
————————————————
版權(quán)聲明:本文為CSDN博主「ulangch」的原創(chuàng)文章,遵循CC 4.0 BY-SA版權(quán)協(xié)議,轉(zhuǎn)載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/qq_14978113/article/details/94654401

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容