在分析 Activity 的启动过程中会涉及到 Android 中的各种各样的 ID,很多同学不太清楚这些 ID 的作用和区别,本文做一个简单的介绍.
1. Linux 中的 uid 与 gid
Linux 是一个多用户操作系统,系统中可以同时存在有多个用户。
每个用户有一个用户名,也就是我们登录时输入的的用户名。用户名在系统中会对应一个整数值 UID,是用户在系统中的唯一标识,就像现实生活中一个身份证号标识一个具体的人。
在终端中可以通过 whoami 打印当前登录的用户名:
bash
whoami
zzh0838
可以通过 id 命令查看 uid
bash
id
uid=1000(zzh0838) gid=1000(zzh0838) groups=1000(zzh0838),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),132(lxd),133(sambashare)**
输出的第一组数据 uid=1000(zzh0838)
表示当前用户 zzh0838 的 uid 是 1000。
为了方便管理系统中的多个用户,系统将用户进行了分组,每个用户可以在一个或者多个组中。每个组也有自己的组名和组 id(gid)。一个用户可以同时在多个组中。这个有点类似大学里面的社团,一个社团就是一个用户组,一个学生就是一个用户,一个学生可以同时参加多个社团,就是说一个用户可以同时属于多个用户组。
我们可以通过 id 命令查看当前用户的组信息:
bash
id
uid=1000(zzh0838) gid=1000(zzh0838) groups=1000(zzh0838),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),132(lxd),133(sambashare)
后半部分就是当前用户的用户组信息:
gid=1000(zzh0838) groups=1000(zzh0838),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),120(lpadmin),132(lxd),133(sambashare)
gid=1000(zzh0838)
表示当前用户的初始组是 zzh0838,初始组 zzh0838 的 gid 是 1000。每个用户的初始组只能有一个,通常就是将和此用户的用户名相同的组名作为该用户的初始组。
后面的内容就是当前用户所在的组,包括了组名和组 id。
2. Android 中的 uid
2.1 进程的 uid
Andorid 基于 Linux 内核打造,在 Android 4.2 之前,Android 是不支持多用户,但是借用了 linux 的用户体系和文件权限体系实现了 App 之间的数据隔离,所谓数据隔离就是每个 App 有自己的一个数据文件夹,App 只能访问自己的数据文件夹,不能访问其他的 App 的数据文件夹和其他路径下的文件。
那 Android 是怎么实现的呢?
在 App 的安装过程中,会给每个 App 分配一个 userId,这个 userId 保存在手机的 /data/system/packages.xml
文件中,文件中记录了 App 的包名和 userId 的对应关系。
如果要查看我们的一个 Demo yuandaima.ahao.myactivitytext
应用的 userId,可以在模拟器的 shell 中执行下面的命令:
bash
cat /data/system/packages.xml | grep yuandaima.ahao.myactivitytext
# ......
<package name="yuandaima.ahao.myactivitytext" codePath="/data/app/yuandaima.ahao.myactivitytext-FicL4FdzvHE8_c4bWCbN9A==" nativeLibraryPath="/data/app/yuandaima.ahao.myactivitytext-FicL4FdzvHE8_c4bWCbN9A==/lib" publicFlags="810073926" privateFlags="0" ft="18cf7636700" it="18cf76369b1" ut="18cf76369b1" version="1" userId="10101">
# ......
可以看到应用 yuandaima.ahao.myactivitytext 的 userId 是 10101。
这里的 userId 就是上一节说到的 linux 中的 uid。
在 Linux 系统中,每个进程也有个 uid 属性,表示是哪个用户启动了当前进程,传统的 linux 系统中,用户启动的进程的 uid 都是当前登录的 uid。
在 Android 中有一些差异,每个进程的 uid 属性,在启动时会被设置成 data/system/packages.xml
文件中记录的进程的 userId。
比如当我们启动 yuandaima.ahao.myactivitytext
应用时,应用的 uid 会被设置为 10101
查看进程信息:
bash
ps -elf | grep yuandaima.ahao.myactivitytext
u0_a101 4718 1505 0 13:27:30 ? 00:00:00 yuandaima.ahao.myactivitytext
root 4749 4636 0 13:33:21 pts/0 00:00:00 grep yuandaima.ahao.myactivitytext
可以看到启动 yuandaima.ahao.myactivitytext
进程的用户是 u0_a101。
我们接着使用 id 命令看 u0_a101 用户的相关信息:
bash
id u0_a101
uid=10101(u0_a101) gid=10101(u0_a101) groups=10101(u0_a101), context=u:r:su:s0
可以看到 u0_a101 对应的 uid 是 10101,这个 uid 就是来自 data/system/packages.xml
文件中记录的进程的 userId。
2.2 应用的数据
Android 中应用的数据通常保存在 /data/data
目录下:
bash
cd /data/data
ls -l
# ......
drwx------ 4 u0_a48 u0_a48 4096 2024-01-11 09:37 com.android.timezone.updater
drwx------ 5 u0_a77 u0_a77 4096 2024-01-11 09:37 com.android.traceur
drwx------ 4 u0_a53 u0_a53 4096 2024-01-11 09:37 com.android.vpndialogs
drwx------ 4 u0_a58 u0_a58 4096 2024-01-11 09:37 com.android.wallpaper.livepicker
drwx------ 4 system system 4096 2024-01-11 09:37 com.android.wallpaperbackup
drwx------ 4 u0_a83 u0_a83 4096 2024-01-11 09:37 com.android.wallpapercropper
drwxr-x--x 4 u0_a89 u0_a89 4096 2024-01-11 09:37 com.android.wallpaperpicker
drwx------ 4 u0_a92 u0_a92 4096 2024-01-13 09:31 com.android.webview
drwx------ 4 u0_a95 u0_a95 4096 2024-01-11 09:37 org.chromium.webview_shell
drwx------ 4 u0_a101 u0_a101 4096 2024-01-11 15:18 yuandaima.ahao.myactivitytext
这里的文件夹的名字均为应用的包名,文件夹中保存了对应应用的数据。比如 yuandaima.ahao.myactivitytext
文件夹就保存了 yuandaima.ahao.myactivitytext
app 的数据。
需要注意的是这里文件夹的权限均为 rwx------
(除了少数特例),表示仅文件的所有者可读可写可执行,其他用户均没有读写执行权限。
同时,很重要的一点文件的所属用户和对应 app 进程的所属用户相同。比如 yuandaima.ahao.myactivitytext
文件夹的所属用户是 u0_a101
,yuandaima.ahao.myactivitytext
进程的所属用户也是 u0_a101
。
也就是说 yuandaima.ahao.myactivitytext
文件夹中的文件只能又 yuandaima.ahao.myactivitytext
进程来读写执行。
这样就利用 linux 的用户体系和文件权限体系实现了 App 之间的数据隔离。妙~
3. Android 中的多用户
早期的 Android 并不支持多用户,因为 Linux 本来的多用户体系被用来实现 App 之间的数据隔离了。但是用户对于多用户功能的需求还是存在的。在 Android 4.2 的时候,google 再一次对 Linux 进行了魔改,重新开发了一套多用户体系。
国产手机的分身功能一般就是基于这套魔改的多用户体系实现的。
假设我们的 Android 手机上有两个用户,用户 id 是 0 和 10,这里的 id 在 Android 中称为 UserHandle
我们先登录 id 为 10 的用户,安装一个应用,在 data/system/packages.xml
中查看, 此应用的 userId="10068"
我们在应用中调用:
java
Process.myUid() // 返回值为 1010068
Process.myUserHandle() // 返回值为 userHandle{10}
接着重新登录 id 为 0 的用户,此时是看不到上一步安装的应用,接着我们再次安装同一个应用。在 data/system/packages.xml
中查看,userId 仍然是 10068
我们在应用中调用:
java
Process.myUid() // 返回值为 10068
Process.myUserHandle() // 返回值为 userHandle{0}
这里可以看出这里的多用户其实是通过 UserHandle 和 userId 的组合来实现的。