Android多用户模式下获取当前UserId的方式

1. Linux uid/gid

Linux下的用户id(uid)和群组id(gid)。Linux是多用户系统,每个用户都拥有一个uid,这个uid由系统和用户名做映射绑定。同时,为了便于用户管理(譬如管理文档权限),Linux引入了群组的概念,可以将多个用户归于一个群组。每一个群组拥有一个群组id(gid)。 
root用户:Linux下的唯一的超级用户,拥有所有的系统权限。root用户所在的组就是root组。

2. Android uid(4.2(API Level 17))

Android 4.2开始支持多用户。Linux的uid/gid多用户体系已经被用在App管理上。

Android重新开发了一套多用户体系,在UserManagerService中管理,PackageManagerService和ActivityManagerService中也有相关逻辑。Android的多用户可以做到不同用户的应用的物理文件级(数据)的区分,以实现不同用户有不同的壁纸、密码,以及不同的应用等。

例如:在一个有两个用户(用户id分别为0和10)的安卓设备上,在用户10下安装一个应用,此时,在0下是看不到这个应用的。

从data/system/packages.xml查看此应用的uid:userId=”10078”

Process.myUid()得到uid为”1010078”

Process.myUserHandle()得到”userHandle{10}”

在另一个用户0下安装此应用。

查看packages.xml,看到uid没有变化10078

Process.myUid()得到uid为”10078”

Process.myUserHandle()得到”userHandle{0}”

adb shell进入命令行,分别查看data/user/0和data/user/10下面此应用的数据区:

用户0: 

 

用户10:

 

可以看到,实际上应用在内部虽然有多用户,但只有一个uid,在不同的用户下,通过uid和用户id合成一个新的uid,以保证在每个用户下能够区分。 

(可以看到文件拥有者是u0_a78,所在群组为u0_a78。从data/system/packages.xml根据包名查看此应用信息,可以看到:userId=”10078”。)

3. android.os.UserHandle

这个类对外提供有关多用户的接口。 从里面的一些api代码可以看到uid在多用户下的处理逻辑:

多用户支持开关: 

注意一个api getUid()。这就清楚了,将用户id 10作为第一个参数,packages.xml中记录的该应用的uid 10078作为第二个参数传入,得到了这个应用在10用户下的uid——1010078! 


 

通过应用的uid得到当前用户的userId,以上过程的逆过程: 

 

从另一个核心的api myUserId()更能清楚地看到应用uid和用户id的关系: 

 

当一个应用使用UserHandle.myUserId()来获取当前的用户id的时候,其实就是从他自己的进程得到应用的uid,然后通过上述逻辑计算出当前的用户id。 

从Process.myUserHandle()也能清楚地看到这个逻辑:

从概念和API命名上,确实有些混乱,但Android也情非得已,Process的API Level是1,UserHandle的API Level是17,可见在最初的Android上面,已经将Linux uid/gid给了应用id了,当时应该也没有考虑Android有一天需要支持多用户。直到4.2(API Level 17),引入了多用户时,已经是若干年过去了,Process已经被无数的开发者使用,无法改变。只能接受这个概念上混淆了。 

可以用如下的几点来简单地澄清这些id概念: 

  1. Process中的xxid相关的概念和API是关于应用id的。 
  2. UserHandle中的xxid相关的概念和API是关于Android用户id的。 
  3. Process有接口得到UserHandle实例。

4. 应用层获取UserId

有时候,我们需要根据不同的用户ID来进行兼容性处理,比如魅族的系统在分身模式下,生物识别相关的Keystore调用(setUserAuthenticationRequired(true))会抛出异常。

我们需要针对这种情况进行兼容性处理,已知的是,魅族的应用分身模式下,用户的ID一定是 999

Process.myUserHandle() 可以得到 UserHandle 对象,但是却不能直接从 UserHandle 对象中获取到用户ID

目前有三种已知的做法

  1. android.os.Process.myUid()/100000 这行代码的原理是依据 Process.myUserHandle() 实现的代码进行逆向操作来获取到真正的用户ID,如果不了解源代码的人会感觉莫名其妙。不需要特殊权限,但是总感觉不够优雅。

  2. ActivityManager.getCurrentUser() 需要申请权限,需要系统应用,需要反射调用。感觉更不优雅了。

  3. UserHandle.myUserId() 不需要特殊权限,需要反射调用。这个感觉也不够优雅。但是当看到androidx.os.UserHandleCompat 也是通过反射调用这些函数的时候,瞬间感觉无所谓了。
    代码参考如下:

参考链接


发布者

发表回复

您的电子邮箱地址不会被公开。