一亩进、引言
這篇文章以Android v28的源碼為參考器罐,介紹Android多用戶的特性跟束、使用方式和系統(tǒng)原理。
二萨咕、初識(shí)Android多用戶
2.1 Android多用戶簡(jiǎn)介
從Android 4.0開(kāi)始差油,Google就開(kāi)始在Android上布局多用戶,UserManager因此而誕生,然而此時(shí)還沒(méi)有對(duì)應(yīng)的Binder服務(wù)蓄喇。真正支持多用戶是從Android 4.2 開(kāi)始发侵,即便如此,系統(tǒng)中也依然存在各種Bug和兼容性問(wèn)題妆偏。直到Android 6.0刃鳄,Android多用戶才比較完善,國(guó)內(nèi)外的廠家也紛紛開(kāi)始針對(duì)多用戶這個(gè)噱頭來(lái)做各種 “花里胡哨” 的操作钱骂,“手機(jī)分身”叔锐、“分身應(yīng)用”、“應(yīng)用雙開(kāi)” 應(yīng)運(yùn)而生见秽,不得不說(shuō)愉烙,國(guó)內(nèi)的廠家在多用戶這方面定制化到如今已經(jīng)非常穩(wěn)定和完善了。
下圖從左到右分別為小米手機(jī)的手機(jī)分身解取、應(yīng)用雙開(kāi)以及華為手機(jī)的多用戶:
2.2 基礎(chǔ)概念
要學(xué)習(xí)多用戶步责,首先我們需要了解一些基礎(chǔ)概念:
Uid(用戶Id):在Linux上,一個(gè)用戶Uid標(biāo)識(shí)著一個(gè)給定的用戶禀苦。Android上也沿用了Linux用戶的概念蔓肯,Root用戶Uid為0,System Uid為1000振乏,并且蔗包,每個(gè)應(yīng)用程序在安裝時(shí)也被賦予了單獨(dú)的Uid,這個(gè)Uid將伴隨著應(yīng)用從安裝到卸載慧邮。
Gid(用戶組Id):Linux上規(guī)定每個(gè)應(yīng)用都應(yīng)該有一個(gè)用戶組调限,對(duì)于Android應(yīng)用程序來(lái)說(shuō),每個(gè)應(yīng)用的所屬用戶組與Uid相同误澳。
Gids:應(yīng)用在安裝后所獲得權(quán)限的Id集合旧噪。在Android上,每個(gè)權(quán)限都可能對(duì)應(yīng)一個(gè)或多個(gè)group脓匿,每個(gè)group有個(gè)gid name,gids就是通過(guò)對(duì)每個(gè)gid name計(jì)算得出的id集合宦赠,一個(gè)UID可以關(guān)聯(lián)GIDS陪毡,表明該UID擁有多種權(quán)限。
對(duì)于Android中的每個(gè)進(jìn)程勾扭,都有一個(gè)單獨(dú)的Uid毡琉、Gid以及Gids集合(這個(gè)地方有點(diǎn)疑惑,Android Framework中的權(quán)限控制不依賴這個(gè)Gids妙色,這個(gè)是Linux上有的東西桅滋,有待考證),通過(guò)這三者,Android系統(tǒng)實(shí)現(xiàn)了一套文件和數(shù)據(jù)訪問(wèn)權(quán)限規(guī)則系統(tǒng)丐谋。如:
訪問(wèn)某個(gè)文件芍碧,文件系統(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
訪問(wèn)Framework中提供的某個(gè)服務(wù)功能,Android規(guī)定了該功能的訪問(wèn)權(quán)限:
// 網(wǎng)絡(luò)訪問(wèn)權(quán)限号俐,通過(guò)Binder.getCallingUid()
private void enforceInternetPermission() {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERNET,
"ConnectivityService");
}
1
2
3
4
5
6
Android權(quán)限知識(shí)拓展:
安裝時(shí)權(quán)限的獲取記錄存儲(chǔ)在:/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
運(yùn)行時(shí)權(quán)限的獲取記錄存儲(chǔ)在:/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 的知識(shí)延伸:
查看方式:
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的真面目及來(lái)龍去脈:
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)每個(gè)我們常見(jiàn)的權(quán)限都可能對(duì)應(yīng)一個(gè)或多個(gè)group gid,而我們上面說(shuō)的gids就是由這個(gè)group gid生成的集合吏饿。具體的權(quán)限讀取和gids生成流程如下:
三踪危、多用戶的特性
3.1 獨(dú)立的userId
Android在創(chuàng)建每個(gè)用戶時(shí),都會(huì)分配一個(gè)整型的userId猪落。對(duì)于主用戶(正常下的默認(rèn)用戶)來(lái)說(shuō)贞远,userId為0,之后創(chuàng)建的userId將從10開(kāi)始計(jì)算笨忌,每增加一個(gè)userId加1:
root@virgo:/ # pm list users
Users:
UserInfo{0:機(jī)主:13} running
UserInfo{10:security space:11} running
1
2
3
4
創(chuàng)建一個(gè)名為"ulangch"的用戶:
root@virgo:/ # pm create-user "ulangch"
Success: created user id 11
root@virgo:/ # pm list users
Users:
UserInfo{0:機(jī)主:13} running
UserInfo{10:security space:11} running
UserInfo{11:ulangch:10} running
1
2
3
4
5
6
7
8
啟動(dòng)和切換到該用戶:
root@virgo:/ # am start-user 11
Success: user started
root@virgo:/ # am switch-user 11
1
2
3
3.2 獨(dú)立的文件存儲(chǔ)
為了多用戶下的數(shù)據(jù)安全性蓝仲,在每個(gè)新用戶創(chuàng)建之初,不管是外部存儲(chǔ)(External Storage)還是app data目錄蜜唾,Android都為其準(zhǔn)備了獨(dú)立的文件存儲(chǔ)杂曲。
多用戶下的/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)建時(shí),Android在 “/storage/emulated” 目錄下為每個(gè)用戶都創(chuàng)建了名為用戶id的目錄袁余,當(dāng)我們?cè)诖a中使用 “Environment.getExternalStorageDirectory().absolutePath” 獲取外部存儲(chǔ)路徑時(shí)擎勘,返回的就是當(dāng)前用戶下的對(duì)應(yīng)目錄(如:userId = 11, 則返回為 “/storage/emulated/11”)颖榜。
另外棚饵,可以看出,我們平常說(shuō)到的 “/sdcard” 目錄其實(shí)最終也是軟鏈到了 “/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)建時(shí)噪漾,Android也會(huì)在 “data/user” 目錄下創(chuàng)建了名為userId的目錄,用于存儲(chǔ)該用戶中所有App的隱私數(shù)據(jù)且蓬,如果在代碼中使用 “Context.getFilesDir()” 來(lái)獲取應(yīng)用的data目錄欣硼,不同User下也會(huì)有不同。
另外恶阴,也可以看出诈胜,平常說(shuō)到的 “/data/data” 目錄其實(shí)也是軟鏈到了 “/data/user/0”。
下圖是兩個(gè)用戶下同一個(gè)App的獲取結(jié)果:
注:在Android中冯事,應(yīng)用的uid是和當(dāng)前的用戶有關(guān)的焦匈,同一個(gè)應(yīng)用具有相同的appId,其uid的計(jì)算方式為: uid = userId * 1000000 + appId昵仅,在主用戶中缓熟,uid = appId。
3.3 獨(dú)立的權(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)建用戶時(shí)規(guī)定版述,也可以后期由系統(tǒng)動(dòng)態(tài)設(shè)置梯澜。
不同用戶下App的應(yīng)用權(quán)限是獨(dú)立的
前面說(shuō)到,uid與userId存在一種計(jì)算關(guān)系(uid = userId * 1000000 + appId)渴析,而在系統(tǒng)中對(duì)于權(quán)限控制也是根據(jù)uid和對(duì)應(yīng)的userId來(lái)判定的晚伙,因此不同用戶下相同應(yīng)用可以具有不同的權(quán)限。
3.4 App安裝的唯一性
雖然前面說(shuō)到俭茧,App的文件存儲(chǔ)和數(shù)據(jù)目錄在不同用戶下都是獨(dú)立的咆疗,但是對(duì)于App的安裝,多個(gè)用戶下同一個(gè)App卻保持著同一個(gè)安裝目錄母债,即:
普通三方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)限在聲明時(shí)安全等級(jí)(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)限毡们,需要?jiǎng)討B(tài)申請(qǐng)告知用戶才能獲取
signature|privileged:具有系統(tǒng)簽名的系統(tǒng)應(yīng)用才可以獲取的權(quán)限迅皇,對(duì)應(yīng)上方的 “/system/priv-app”
因此,多用戶下的應(yīng)用其實(shí)只安裝一次衙熔,不同用戶下同一個(gè)應(yīng)用的版本和簽名都應(yīng)該相同登颓,不同用戶下相同App能夠獨(dú)立運(yùn)行是因?yàn)橄到y(tǒng)為他們創(chuàng)造了不同的運(yùn)行環(huán)境和權(quán)限。
3.5 kernel及系統(tǒng)進(jìn)程的不變性
在不同用戶下红氯,雖然能夠看到不同的桌面框咙,不同的運(yùn)行環(huán)境,一切都感覺(jué)是新的痢甘,但是我們系統(tǒng)本身并沒(méi)有發(fā)生改變喇嘱,kernel進(jìn)程、system_server進(jìn)程以及所有daemon進(jìn)程依然是同一個(gè)塞栅,并不會(huì)重啟者铜。
而如果我們?cè)诓煌脩糁虚_(kāi)啟相同的app,我們可以看到可以有多個(gè)app進(jìn)程放椰,而他們的父進(jìn)程都是同一個(gè)作烟,即 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)建庄敛、啟動(dòng)、停止等行為是系統(tǒng)級(jí)的科汗,因此只有具有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()方法中,方法太長(zhǎng)怖亭,截取部分分析:
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)建條件判斷
// 一個(gè)用戶只能有一個(gè)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)中只能有一個(gè)訪客用戶
if (isGuest && findCurrentGuestUser() != null) {
return null;
}
// ... 省略關(guān)于 restricted 和 ephemeral 類型的創(chuàng)建判斷
// 獲取下一個(gè)userId期吓,userId從10開(kāi)始遞增,除非達(dá)到"上限"倾芝,否則前面的userId不會(huì)復(fù)用
userId = getNextAvailableId();
// 創(chuàng)建 "/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);
// 通過(guò)vold對(duì)新用戶進(jìn)行文件系統(tǒng)加密(相關(guān):http://www.reibang.com/p/d25f73805729)
storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
// 通過(guò)vold創(chuàng)建以下目錄,并賦予相關(guān)rwx權(quán)限:
// "/data/system/users/{userId}" : 0750
mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
// 為已安裝應(yīng)用創(chuàng)建"/data/system/user/{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)建的過(guò)程主要是應(yīng)用運(yùn)行環(huán)境(文件系統(tǒng)、權(quán)限等)的準(zhǔn)備過(guò)程路翻,主要可以分為以下幾個(gè)關(guān)鍵的步驟:(忽略訪客用戶相關(guān)的操作)
為新用戶創(chuàng)建一個(gè)新的userId (新用戶的userId從10開(kāi)始遞增)
固化新用戶信息和創(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
- 準(zhǔn)備文件系統(tǒng):
通過(guò)vold(Android存儲(chǔ)守護(hù)進(jìn)程)為新用戶進(jìn)行文件系統(tǒng)加密
創(chuàng)建"/data/system/users/{userId}” 并設(shè)置 “0750” 權(quán)限
- 為已安裝應(yīng)用準(zhǔn)備數(shù)據(jù)目錄并記錄其組件和默認(rèn)權(quán)限配置:
在 “/data/user/{userId}/package-restrictions.xml” 中寫入非默認(rèn)啟動(dòng)組件的信息
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可能會(huì)改變茂契。(這個(gè)改變的可能性是根據(jù)permUser的配置來(lái)決定蝶桶,目前使用6.0的小米Note是沒(méi)有改變的)
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
固化新用戶創(chuàng)建完成的狀態(tài)、通知PMS為新用戶和應(yīng)用賦予默認(rèn)的權(quán)限
發(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);
// 這個(gè)消息將會(huì)彈Dialog展示切換的過(guò)程
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莫瞬,最終都會(huì)調(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. 強(qiáng)制結(jié)束所有動(dòng)畫
// 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讀取需要切換用戶的字體、語(yǔ)言郭蕉、地區(qū)等配置并更新
// 如果是初創(chuàng)用戶疼邀,對(duì)于字體則使用默認(rèn)配置,語(yǔ)言和地區(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的可見(jiàn)性
// 設(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ò)程中關(guān)閉Keyguard的指紋監(jiān)聽(tīng)
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)限召锈,校驗(yàn)或準(zhǔn)備新用戶app存儲(chǔ)
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)啟動(dòng) (如:JobSchedulerService會(huì)根據(jù)Job對(duì)應(yīng)的用戶是否啟動(dòng)來(lái)確定是否需要再繼續(xù)維護(hù)Job)
mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
}
if (foreground) {
// 通知系統(tǒng)所有服務(wù)用戶切換 (如:如果當(dāng)前用戶連接的是私有網(wǎng)絡(luò)(如隱藏WiFi)旁振,則切換到新用戶需要斷開(kāi))
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超時(shí),如果3s內(nèi)沒(méi)有完成用戶切換涨岁,則停止切換并解凍屏幕拐袜。
// 上述任務(wù)完成后會(huì)取消掉該延遲消息,并最終都會(huì)調(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蹬铺,如果待切換用戶之前存在前臺(tái)Activity,則在對(duì)應(yīng)的ActivityStack中將其拉到Top并resume該Activity秉撇,否則啟動(dòng)桌面Activity
// 在待切換新用戶使用Handler并首次調(diào)用MessageQueue.next()方法時(shí)甜攀,會(huì)調(diào)用AMS的activityIdle方法秋泄,進(jìn)而調(diào)用activityIdleInternalLocked方法,此時(shí)會(huì)檢查mStartingUsers列表, 根據(jù)待啟動(dòng)用戶的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
方法很長(zhǎng)规阀,涉及到AMS和WMS的方法分支也很多恒序。切換分為前臺(tái)切換和后臺(tái)切換,這里從前臺(tái)切換側(cè)并且對(duì)用戶未啟動(dòng)的情況總結(jié)下關(guān)鍵的切換過(guò)程:
- 切換前凍結(jié)屏幕谁撼,禁止一切輸入操作
凍結(jié)輸入事件
強(qiáng)制結(jié)束App動(dòng)畫
截取當(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
上述這個(gè)過(guò)程在屏幕旋轉(zhuǎn)的過(guò)程中也會(huì)執(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:如果是待啟動(dòng)用戶,則初始化待啟動(dòng)用戶的狀態(tài)為STATE_BOOTING厉碟,
- 為待切換用戶更改系統(tǒng)配置喊巍,設(shè)置Keyguard
從SettingsProvider讀取待切換用戶的字體、語(yǔ)言墨榄、地區(qū)等配置并更新到系統(tǒng)玄糟。如果是初創(chuàng)用戶,則字體使用默認(rèn)配置袄秩,語(yǔ)言和地區(qū)使用當(dāng)前用戶的配置
為待切換用戶更新資源:如Attributes阵翎、Drawable、Color之剧、Animator郭卫、StateList等。(有興趣可以重點(diǎn)看下AMS的updateGlobalConfiguration()方法)
修改當(dāng)前用戶下所有Window的可見(jiàn)性背稼,啟動(dòng)Keyguard贰军,切換過(guò)程中關(guān)閉Keyguard的指紋監(jiān)聽(tīng),并設(shè)置鎖屏
注:在Android8.0以前蟹肘,Keyguard是一個(gè)單獨(dú)的System App词疼,8.0后將其移至SystemUI中。該模塊的功能主要有:1. 展示和隱藏鎖屏界面帘腹;2. 認(rèn)證和校驗(yàn)鎖屏密碼贰盗、指紋密碼等
Start2:如果是待啟動(dòng)用戶
為待啟動(dòng)用戶設(shè)置權(quán)限,校驗(yàn)或準(zhǔn)備待啟動(dòng)用戶的App存儲(chǔ)目錄
通知系統(tǒng)所有服務(wù)新用戶正在啟動(dòng)(如JobSchedulerService會(huì)根據(jù)Job對(duì)應(yīng)的用戶是否啟動(dòng)來(lái)決定Job的維護(hù))
- 并行通知系統(tǒng)所有服務(wù)用戶開(kāi)始切換:
系統(tǒng)所有服務(wù)及相關(guān)監(jiān)聽(tīng)者在收到開(kāi)始切換的消息后進(jìn)行一系列的操作也是用戶切換所要完成的核心任務(wù)阳欲。
- 設(shè)置切換超時(shí)定時(shí)器
設(shè)置3s的延遲消息舵盈,如果3s內(nèi)沒(méi)有完成用戶切換(取消該消息),則終止切換過(guò)程并執(zhí)行UserController.continueUserSwitch()方法球化。(在步驟3中所有系統(tǒng)服務(wù)及相關(guān)監(jiān)聽(tīng)者完成切換任務(wù)后秽晚,也會(huì)執(zhí)行UserController.continueUserSwitch()方法)
- 將待切換用戶拉到前臺(tái)
stop當(dāng)前用戶下所有的Activity
修改所有ActivityStack中TaskRecord的順序,將切換用戶或者在兩個(gè)用戶中都能運(yùn)行的Task移動(dòng)到棧頂
將最頂端Task對(duì)應(yīng)的Window移動(dòng)到最頂端
取出切換應(yīng)用之前存在的前臺(tái)Activity置于前臺(tái)并resume筒愚,如果沒(méi)有前臺(tái)應(yīng)用赴蝇,則啟動(dòng)HomeActivity
發(fā)送用戶切換廣播。(如果是后臺(tái)切換巢掺,則發(fā)送ACTION_USER_BACKGROUND句伶,如果是后臺(tái)切換芍耘,則發(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) {
// 如果之前沒(méi)有前臺(tái)應(yīng)用,則啟動(dòng)HomeActivity
mInjector.startHomeActivityLocked(newUserId, "moveUserToForeground");
} else {
// 如果之前有前臺(tái)應(yīng)用熄阻,則resume該Activity
mInjector.getActivityStackSupervisor().resumeFocusedStackTopActivityLocked();
}
EventLogTags.writeAmSwitchUser(newUserId);
// 對(duì)于切換前的用戶,發(fā)送ACTION_USER_BACKGROUND廣播倔约,對(duì)于切換后的用戶秃殉,發(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畫中畫對(duì)應(yīng)ActivityStack中所有的Activity
removeStackLocked(PINNED_STACK_ID);
mUserStackInFront.put(mCurrentUser, focusStackId);
final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
mCurrentUser = userId;
// 后續(xù)會(huì)根據(jù)這個(gè)List來(lái)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);
// 這里會(huì)移動(dòng)ActivityStack中所有的Task,并將能在待切換用戶中運(yùn)行的Task置于棧頂
stack.switchUserLocked(userId);
TaskRecord task = stack.topTask();
if (task != null) {
// 將最頂端的Task對(duì)應(yīng)的Window置頂
stack.positionChildWindowContainerAtTop(task);
}
}
}
// 取出待切換用戶之前存在的前臺(tái)應(yīng)用對(duì)應(yīng)的ActivityStack
ActivityStack stack = getStack(restoreStackId);
if (stack == null) {
stack = mHomeStack;
}
final boolean homeInFront = stack.isHomeStack();
if (stack.isOnHomeDisplay()) {
// 將之前存在的前臺(tái)應(yīng)用置于前臺(tái)
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
- 步驟3完成或者4中切換超時(shí)消息到達(dá)時(shí)需要繼續(xù)進(jìn)行的切換操作(continueUserSwitch)
解凍屏幕和輸入
設(shè)置Keyguard浸剩,如果切換用戶設(shè)置了指紋钾军,則需要開(kāi)始監(jiān)聽(tīng)指紋信息
通知監(jiān)聽(tīng)者用戶已經(jīng)完成了切換
UserController.continueUserSwitch()方法執(zhí)行流程:
- 完成切換用戶
如果是后臺(tái)切換,則直接調(diào)用UserController.finishUserBoot()方法
如果是前臺(tái)切換绢要,ActivityThread會(huì)在handleResumeActivity時(shí)設(shè)置Main線程MessageQueue的mIdleHandlers吏恭,在MessageQueue執(zhí)行next()方法會(huì)檢查該列表并最終調(diào)用到AMS的activityIdle()方法中,此時(shí)會(huì)檢查正在切換的用戶列表并調(diào)用最終調(diào)用到UserController.finishUserBoot()方法
設(shè)置切換用戶的狀態(tài)為STATE_RUNNING_LOCKED
前臺(tái)切換情況下finishUserBoot()方法的調(diào)用流程:
Start3:如果是新啟動(dòng)的用戶重罪,則通知系統(tǒng)所有用戶監(jiān)聽(tīng)者用戶已經(jīng)啟動(dòng)樱哼,并發(fā)送ACTION_LOCKED_BOOT_COMPLETED廣播,在Keyguard第一次解鎖時(shí)剿配,會(huì)發(fā)送ACTION_BOOT_COMPLETED廣播
3.3 多用戶的刪除
adb shell pm remove-user ${userId}
入口是UserManagerService.removeUser()搅幅,這里不詳細(xì)分析,與創(chuàng)建于切換用戶的流程類似呼胚。
3.4 多用戶的真面目
從上面對(duì)Android多用戶的創(chuàng)建和切換流程來(lái)看茄唐,我們可以總結(jié)出:
多用戶其實(shí)是系統(tǒng)為應(yīng)用的data目錄和storage目錄分配了一份不同且獨(dú)立的存儲(chǔ)空間,不同用戶下的存儲(chǔ)空間互不影響且沒(méi)有權(quán)限訪問(wèn)蝇更。同時(shí)沪编,系統(tǒng)中的AMS、PMS年扩、WMS等各大服務(wù)都會(huì)針對(duì)userId/UserHandle進(jìn)行多用戶適配蚁廓,并在用戶啟動(dòng)、切換常遂、停止纳令、刪除等生命周期時(shí)做出相應(yīng)策略的改變。通過(guò)以上兩點(diǎn)克胳,Android創(chuàng)造出來(lái)一個(gè)虛擬的多用戶運(yùn)行環(huán)境平绩。
五、多用戶下的四大組件和數(shù)據(jù)共享
5.1 獲取當(dāng)前用戶userId的方式
UserHandle中提供myUserId()方法漠另,但是被hide的捏雌,可以反射獲取:(或者直接根據(jù)uid / 100000計(jì)算)
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 跨用戶啟動(dòng)Activity
Activity/Context提供了startActivityAsUser() 方法笆搓,可以傳入對(duì)應(yīng)用戶的UserHandle來(lái)達(dá)到跨用戶啟動(dòng)Activity的目的,Context中對(duì)該方法進(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)過(guò)反射測(cè)試兔辅,的確會(huì)拋出SecurityException:
// 反射調(diào)用startActivityAsUser會(huì)拋出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.IActivityManagerProxy.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.IActivityManagerstartActivityAsUser$(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
最終會(huì)使用UserController.handleIncomingUser() 方法來(lái)做跨用戶的權(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 跨用戶啟動(dòng)Service
Context中提供了startServiceAsUser() 方法,經(jīng)過(guò)反射測(cè)試宵荒,也是跨不過(guò)AMS的權(quán)限檢查:
// 反射調(diào)用startServiceAsUser會(huì)拋出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.IActivityManagerProxy.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.IActivityManagerstartService$(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)用也會(huì)拋出異常报咳。
5.5 跨用戶Query
系統(tǒng)沒(méi)有提供類似getContentResolverAsUser的方法侠讯,但ContentResolver提供了跨用戶query的能力。ContentProvider中提供了hide 的 maybeAddUserId() 方法暑刃,被query的Uri中可以攜帶userId(如: "content://10@com.android.contacts/contacts"厢漩,"http://10@" 中的10就是userId),通過(guò)Uri中的userId岩臣,可以訪問(wèn)到不同用戶下相同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
但是對(duì)于三方應(yīng)用來(lái)說(shuō),仍然不可以通過(guò)這種方式實(shí)現(xiàn)跨用戶共享數(shù)據(jù)架谎,AMS還是會(huì)檢查權(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目錄的跨用戶訪問(wèn)
不可以互相訪問(wèn)粱胜。如用戶10的app無(wú)法訪問(wèn) /storage/emulated/0 下的文件
5.7 跨用戶的數(shù)據(jù)共享
通過(guò)上面的介紹,對(duì)于系統(tǒng)和系統(tǒng)應(yīng)用來(lái)說(shuō)狐树,實(shí)現(xiàn)多用戶的數(shù)據(jù)共享很方便焙压,但系統(tǒng)對(duì)三方應(yīng)用屏蔽了這些跨用戶的能力。的確抑钟,三方應(yīng)用在絕大數(shù)據(jù)場(chǎng)景下無(wú)需關(guān)心自己處于哪個(gè)用戶下涯曲,也不會(huì)涉及到跨用戶的數(shù)據(jù)共享,但我們依然可以進(jìn)行一些嘗試在塔,來(lái)了解如何在多用戶下進(jìn)行數(shù)據(jù)共享幻件。
方式一:使用本地回環(huán)地址(127.0.0.1)socket進(jìn)行跨用戶通信
創(chuàng)建本地ServerSocket,在另一個(gè)用戶使用Socket發(fā)送消息蛔溃,是可以收到的绰沥,說(shuō)明多用戶間可以通過(guò)Socket進(jìn)行通信。
另:以下方法經(jīng)過(guò)嘗試證明了不可行
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)載請(qǐng)附上原文出處鏈接及本聲明秃臣。
原文鏈接:https://blog.csdn.net/qq_14978113/article/details/94654401