精华内容
下载资源
问答
  • Linux是多用户操作系统

    千次阅读 2019-09-09 23:44:35
    操作系统按用户数量可以分为单用户系统和多用户系统。比如Windows就是典型的单用户系统,Linux就是典型的多用户系统。 有人可能会问,我的windows下有两个用户,怎么是单用户系统?Windows系统可以存在多个用户,...

    操作系统按用户数量可以分为单用户系统和多用户系统。比如Windows就是典型的单用户系统,Linux就是典型的多用户系统。
    有人可能会问,我的windows下有两个用户,怎么是单用户系统?Windows系统可以存在多个用户,但是多用户不可以同时在线操作(你见过一个pc两个屏幕,分别登录两个用户么?)。Linux的多用户是指可多用户同时在线,每打开一个终端就可以用一个新的用户登录。
    用户与用户组:
    用户(User)就是我们广义上的使用者,用户可以对主机进行操作,可以将某个特殊用户赋予权限,成为系统管理员(如root账户)。用户组(group)就是广义上的组,但是这个组可以是由用户构成的,也可以是nobody。
    下面我就来介绍一下在Linux中的常用用户操作:

    useradd命令:
    这个命令可以根据字面意思理解,就是添加用户名。其语法如下:

    useradd 用户名 -g 组名–G 组名-d Home 目录名-p 密码
    

    -g 指定首要用户组
    -G 指定次要用户组
    -d 指定用户Home目录
    -p 设置密码

    userdel命令:删除用户的命令

    groupadd命令:添加用户组

    groupdel命令:删除用户组

    以上的后三个命令语法相同,都是command user/group,所以我在这里就不做过多的阐述。

    展开全文
  • 单用户操作系统 多用户操作系统

    千次阅读 2018-04-18 09:16:15
    单用户、多用户、单任务、多任务,这么多种操作系统容易让人迷糊。根据老羊快跑查的资料,下面将这几种操作系统给大家直观的说下。1.单用户、多用户。根据在同一时间使用计算机用户的多少,操作系统可分为单用户操作...

    单用户、多用户、单任务、多任务,这么多种操作系统容易让人迷糊。根据老羊快跑查的资料,下面将这几种操作系统给大家直观的说下。

    1.单用户、多用户。根据在同一时间使用计算机用户的多少,操作系统可分为单用户操作系统和多用户操作系统。单用户操作系统是指一台计算机在同一时间只能由一个用户使用,一个用户独自享用系统的全部硬件和软件资源,而如果在同一时间允许多个用户同时使用计算机,则称为多用户操作系统。

    2.单任务、多任务。用户在同一时间可以运行多个应用程序(每个应用程序被称作一个任务),则这样的操作系统被称为多任务操作系统。如果一个用户在同一时间只能运行一个应用程序,则对应的操作系统称为单任务操作系统。

        现代操作系统一般属于多用户、多任务的操作系统,也就是说,同一台机器可以为多个用户建立各自的账户,也允许拥有这些账户的用户同时登录这台计算机,每个账号可以同时运行多个程序。

        多个用户能够同时访问和使用同一台计算机,其中的一个用户具有管理所有这些用户账户和整个计算机的资源的权限,在Windows上,这个具有管理其他用户和计算机资源的用户一般叫administrator。

        个人计算机操作系统早期一般都是单用户操作系统,其主要特点是在某一时间为单个用户服务。早期的DOS操作系统是单用户单任务操作系统,Windows XP则是单用户多任务操作系统。LinuxUNIX是多用户多任务操作系统。现在常用的Windows操作系统都是多用户、多任务的操作系统,使用最广泛的win7win10都是多用户,多任务操作系统。

    展开全文
  • 单用户、多用户、单任务、多任务,这么多种操作系统容易让人迷糊。根据老羊快跑查的资料,下面将这几种操作系统给大家直观的说下。

    单用户、多用户、单任务、多任务,这么多种操作系统容易让人迷糊。根据老羊快跑查的资料,下面将这几种操作系统给大家直观的说下。

    1.单用户、多用户。根据在同一时间使用计算机用户的多少,操作系统可分为单用户操作系统和多用户操作系统。单用户操作系统是指一台计算机在同一时间只能由一个用户使用,一个用户独自享用系统的全部硬件和软件资源,而如果在同一时间允许多个用户同时使用计算机,则称为多用户操作系统。

    2.单任务、多任务。用户在同一时间可以运行多个应用程序(每个应用程序被称作一个任务),则这样的操作系统被称为多任务操作系统。如果一个用户在同一时间只能运行一个应用程序,则对应的操作系统称为单任务操作系统。

        现代操作系统一般属于多用户、多任务的操作系统,也就是说,同一台机器可以为多个用户建立各自的账户,也允许拥有这些账户的用户同时登录这台计算机,每个账号可以同时运行多个程序。

        多个用户能够同时访问和使用同一台计算机,其中的一个用户具有管理所有这些用户账户和整个计算机的资源的权限,在Windows上,这个具有管理其他用户和计算机资源的用户一般叫administrator。

        个人计算机操作系统早期一般都是单用户操作系统,其主要特点是在某一时间为单个用户服务。早期的DOS操作系统是单用户单任务操作系统,Windows XP则是单用户多任务操作系统。LinuxUNIX是多用户多任务操作系统。现在常用的Windows操作系统都是多用户、多任务的操作系统,使用最广泛的win7win10都是多用户,多任务操作系统。

    老羊快跑,一个纯粹的操作系统公众号,欢迎关注内有老羊的《200分钟PCB设计傻瓜教程》,免费观看

    展开全文
  • 深入理解Android系统多用户

    万次阅读 多人点赞 2019-07-05 14:59:35
    这篇文章以Android v28的源码为参考,介绍Android多用户的特性、使用方式和系统原理。 二、初识Android多用户 2.1 Android多用户简介 从Android 4.0开始,Google就开始在Android上布局多用户,UserManager因此而...

    一、引言

    这篇文章以Android v28的源码为参考,介绍Android多用户的特性、使用方式和系统原理。


    二、初识Android多用户


    2.1 Android多用户简介

    从Android 4.0开始,Google就开始在Android上布局多用户,UserManager因此而诞生,然而此时还没有对应的Binder服务。真正支持多用户是从Android 4.2 开始,即便如此,系统中也依然存在各种Bug和兼容性问题。直到Android 6.0,Android多用户才比较完善,国内外的厂家也纷纷开始针对多用户这个噱头来做各种 “花里胡哨” 的操作,“手机分身”、“分身应用”、“应用双开” 应运而生,不得不说,国内的厂家在多用户这方面定制化到如今已经非常稳定和完善了。

    下图从左到右分别为小米手机的手机分身、应用双开以及华为手机的多用户:


    2.2 基础概念

    要学习多用户,首先我们需要了解一些基础概念:

    1. Uid(用户Id):在Linux上,一个用户Uid标识着一个给定的用户。Android上也沿用了Linux用户的概念,Root用户Uid为0,System Uid为1000,并且,每个应用程序在安装时也被赋予了单独的Uid,这个Uid将伴随着应用从安装到卸载。

    2. Gid(用户组Id):Linux上规定每个应用都应该有一个用户组,对于Android应用程序来说,每个应用的所属用户组与Uid相同。

    3. Gids:应用在安装后所获得权限的Id集合。在Android上,每个权限都可能对应一个或多个group,每个group有个gid name,gids就是通过对每个gid name计算得出的id集合,一个UID可以关联GIDS,表明该UID拥有多种权限。

    对于Android中的每个进程,都有一个单独的Uid、Gid以及Gids集合(这个地方有点疑惑,Android Framework中的权限控制不依赖这个Gids,这个是Linux上有的东西,有待考证),通过这三者,Android系统实现了一套文件和数据访问权限规则系统。如:

    1. 访问某个文件,文件系统规定了该文件在磁盘中的rwx(read/write/excute)和 SELinux 权限:
    root@virgo:/ # ls -lZ /system/xbin/su
    -rwsr-sr-x root     shell             u:object_r:su_exec:s0 su
    
    1. 访问Framework中提供的某个服务功能,Android规定了该功能的访问权限:
    // 网络访问权限,通过Binder.getCallingUid()
    private void enforceInternetPermission() {
        mContext.enforceCallingOrSelfPermission(
                android.Manifest.permission.INTERNET,
                "ConnectivityService");
    }
    

    Android权限知识拓展:

    1. 安装时权限的获取记录存储在:/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. 运行时权限的获取记录存储在:/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>
    

    Uid/Gid/Gids 的知识延伸:

    1. 查看方式:
    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. Gids的真面目及来龙去脉:

    Android系统和应用安装后的权限声明保存在 “/etc/permissions/” 目录下:

    1|root@virgo:/ # ls /etc/permissions/
    ConnectivityExt.xml
    android.hardware.bluetooth_le.xml
    android.hardware.camera.flash-autofocus.xml
    android.hardware.camera.front.xml
    ...
    handheld_core_hardware.xml
    imscm.xml
    micloud-sdk.xml
    platform-miui.xml
    platform.xml
    

    看下最常用的platform权限:

    root@virgo:/ # cat /etc/permissions/platform.xml
    ...
    <permissions>
        <!-- The following tags are associating low-level group IDs with
             permission names.  By specifying such a mapping, you are saying
             that any application process granted the given permission will
             also be running with the given group ID attached to its process,
             so it can perform any filesystem (read, write, execute) operations
             allowed for that group. -->
        <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>
    ...
    

    这里只截取了部分权限,我们发现每个我们常见的权限都可能对应一个或多个group gid,而我们上面说的gids就是由这个group gid生成的集合。具体的权限读取和gids生成流程如下:

    三、多用户的特性


    3.1 独立的userId

    Android在创建每个用户时,都会分配一个整型的userId。对于主用户(正常下的默认用户)来说,userId为0,之后创建的userId将从10开始计算,每增加一个userId加1:

    root@virgo:/ # pm list users
    Users:
            UserInfo{0:机主:13} running
            UserInfo{10:security space:11} running
    

    创建一个名为"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
    

    启动和切换到该用户:

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

    3.2 独立的文件存储

    为了多用户下的数据安全性,在每个新用户创建之初,不管是外部存储(External Storage)还是app data目录,Android都为其准备了独立的文件存储。


    多用户下的/storage分区:

    root@virgo:/ # ls -l /storage/emulated/
    drwxrwx--x root     sdcard_rw          2019-06-21 17:44 0
    drwxrwx--x root     sdcard_rw          2019-06-25 14:04 10
    drwxrwx--x root     sdcard_rw          2019-06-25 17:32 11
    
    root@virgo:/ # ls -l /sdcard
    lrwxrwxrwx root     root              2019-06-21 10:47 sdcard -> /storage/self/primary
    root@virgo:/ # ls -l /storage/self/primary
    lrwxrwxrwx root     root              2019-06-21 10:47 primary -> /mnt/user/0/primary
    root@virgo:/ # ls -l /mnt/user/0/primary
    lrwxrwxrwx root     root              2019-06-21 10:47 primary -> /storage/emulated/0
    

    新用户创建时,Android在 “/storage/emulated” 目录下为每个用户都创建了名为用户id的目录,当我们在代码中使用 “Environment.getExternalStorageDirectory().absolutePath” 获取外部存储路径时,返回的就是当前用户下的对应目录(如:userId = 11, 则返回为 “/storage/emulated/11”)。

    另外,可以看出,我们平常说到的 “/sdcard” 目录其实最终也是软链到了 “/storage/emulated/0”


    多用户下的/data分区:

    root@virgo:/ # ls -l data/user/
    lrwxrwxrwx root     root              2019-05-28 22:15 0 -> /data/data/
    drwxrwx--x system   system            2019-06-24 15:30 10
    drwxrwx--x system   system            2019-06-25 17:30 11
    
    root@virgo:/ # ls -l /data/user/11/com.ulangch.multiuser/
    drwxrwx--x u11_a110 u11_a110          2019-06-25 18:02 cache
    drwxrwx--x u11_a110 u11_a110          2019-06-25 18:30 code_cache
    drwxrwx--x u11_a110 u11_a110          2019-06-25 18:28 files
    

    与External Storage相同,新用户创建时,Android也会在 “data/user” 目录下创建了名为userId的目录,用于存储该用户中所有App的隐私数据,如果在代码中使用 “Context.getFilesDir()” 来获取应用的data目录,不同User下也会有不同。

    另外,也可以看出,平常说到的 “/data/data” 目录其实也是软链到了 “/data/user/0”。


    下图是两个用户下同一个App的获取结果:

    注:在Android中,应用的uid是和当前的用户有关的,同一个应用具有相同的appId,其uid的计算方式为: uid = userId * 1000000 + appId,在主用户中,uid = appId。


    3.3 独立的权限控制

    1. 不同用户具有的权限不同,如:访客用户的默认权限限制就有:
    perseus:/ $ dumpsys user
    ...
      Guest restrictions:
        no_sms                        // 限制发送短信
        no_install_unknown_sources    // 限制安装
        no_config_wifi                // 限制配置WiFi
        no_outgoing_calls             // 限制拨打电话
    

    (注:使用 “adb shell dumpsys user” 可以查看所有的用户信息,如userId、name、restrictions等)

    这些权限可以在创建用户时规定,也可以后期由系统动态设置。


    1. 不同用户下App的应用权限是独立的

    前面说到,uid与userId存在一种计算关系(uid = userId * 1000000 + appId),而在系统中对于权限控制也是根据uid和对应的userId来判定的,因此不同用户下相同应用可以具有不同的权限。


    3.4 App安装的唯一性

    虽然前面说到,App的文件存储和数据目录在不同用户下都是独立的,但是对于App的安装,多个用户下同一个App却保持着同一个安装目录,即:

    1. 普通三方app:/data/app/
    2. 普通系统应用:/system/app/
    3. 特权系统应用:/system/priv-app/
    root@virgo:/ # ls /data/app/com.ulangch.multiuser-1/
    base.apk
    lib
    oat
    

    拓展:权限在声明时安全等级(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" />
    
    <!-- Allows applications to access information about Wi-Fi networks.
             <p>Protection level: normal
    -->
    <permission android:name="android.permission.ACCESS_WIFI_STATE"
                android:description="@string/permdesc_accessWifiState"
                android:label="@string/permlab_accessWifiState"
                android:protectionLevel="normal" />
    
    <!-- @SystemApi Allows applications to set the system time.
        <p>Not for use by third-party applications. -->
    <permission android:name="android.permission.SET_TIME"
                android:protectionLevel="signature|privileged" />
    
    1. normal:普通权限,在AndroidManifest.xml中声明就可以获取的权限,如INTERNET权限
    2. dangerous:敏感权限,需要动态申请告知用户才能获取
    3. signature|privileged:具有系统签名的系统应用才可以获取的权限,对应上方的 “/system/priv-app”

    因此,多用户下的应用其实只安装一次,不同用户下同一个应用的版本和签名都应该相同,不同用户下相同App能够独立运行是因为系统为他们创造了不同的运行环境和权限。


    3.5 kernel及系统进程的不变性

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

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

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

    四、流程分析

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


    3.1 多用户的创建

    adb shell pm create-user [–profileOf USER_ID] [–managed] USER_NAME

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

    private UserInfo createUserInternalUnchecked(String name, int flags, int parentId, String[} {
      final boolean isGuest = (flags & UserInfo.FLAG_GUEST) != 0;
      final boolean isManagedProfile = (flags & UserInfo.FLAG_MANAGED_PROFILE) != 0;
      final boolean isRestricted = (flags & UserInfo.FLAG_RESTRICTED) != 0;
      final boolean isDemo = (flags & UserInfo.FLAG_DEMO) != 0;] disallowedPackages);
      // ... 省略,以下操作在 mPackagesLock 锁中
      // 用户创建条件判断
      // 一个用户只能有一个ManagedProfile
      if (isManagedProfile && !canAddMoreManagedProfiles(parentId, false)) {
        Log.e(LOG_TAG, "Cannot add more managed profiles for user " + parentId);
        return null;
      }
      // 判断是否达到最大用户数,Android 6.0上最大用户数为5,可配置
      if (!isGuest && !isManagedProfile && !isDemo && isUserLimitReached()) {
        return null;
      }
      // 系统中只能有一个访客用户
      if (isGuest && findCurrentGuestUser() != null) {
        return null;
      }
      // ... 省略关于 restricted 和 ephemeral 类型的创建判断
      
      // 获取下一个userId,userId从10开始递增,除非达到"上限",否则前面的userId不会复用
      userId = getNextAvailableId();
      // 创建 "/data/system/users/${userId}"目录
      Environment.getUserSystemDirectory(userId).mkdirs();
      synchronized (mUsersLock) {
        // 构造UserInfo和UserData,后面会进行固化保存
        userInfo = new UserInfo(userId, name, null, flags);
        userInfo.serialNumber = mNextSerialNumber++;
        long now = System.currentTimeMillis();
        userInfo.creationTime = (now > EPOCH_PLUS_30_YEARS) ? now : 0;
        // partial = true表明用户还没有创建完成,为了防止创建用户进程被突然停止,下次系统重启时
        // 需要检查文件中该属性,如果为true则需要继续用户的创建 
        userInfo.partial = true;
        userInfo.lastLoggedInFingerprint = Build.FINGERPRINT;
        if (isManagedProfile && parentId != UserHandle.USER_NULL) {
            userInfo.profileBadge = getFreeProfileBadgeLU(parentId);
        }
        userData = new UserData();
        userData.info = userInfo;
        mUsers.put(userId, userData);
      }
      // 将上述UserData信息固化到 "/data/system/users/${userId}.xml"
      writeUserLP(userData);
      // 将新创建的userId固化到 "/data/system/users/userlist.xml"
      writeUserListLP();
      // ... 省略一部分profile和restricted类型的写文件操作
    
      // 为新用户准备文件系统
      final StorageManager storage = mContext.getSystemService(StorageManager.class);
      // 通过vold对新用户进行文件系统加密(相关:https://www.jianshu.com/p/d25f73805729)
      storage.createUserKey(userId, userInfo.serialNumber, userInfo.isEphemeral());
      // 通过vold创建以下目录,并赋予相关rwx权限:
      // "/data/system/users/${userId}" : 0700
      // "/data/misc/users/${userId}" : 0750
      mUserDataPreparer.prepareUserData(userId, userInfo.serialNumber,
            StorageManager.FLAG_STORAGE_DE | StorageManager.FLAG_STORAGE_CE);
      // 为已安装应用创建"/data/system/user/${userId}/${packageName}"目录
      //       ...
      mPm.createNewUser(userId, disallowedPackages);
      // 用户已经创建完成,固化用户创建状态
      userInfo.partial = false;
      synchronized (mPackagesLock) {
        writeUserLP(userData);
      }
      // 更新所有缓存的用户
      updateUserIds();
      // ...省略guest 和 restrictions
    
      // 为新创建的用户赋予默认权限
      mPm.onNewUserCreated(userId);
      // 向所有用户发送 "ACTION_USER_ADDED" 广播
      Intent addedIntent = new Intent(Intent.ACTION_USER_ADDED);
      addedIntent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
      mContext.sendBroadcastAsUser(addedIntent, UserHandle.ALL,
            android.Manifest.permission.MANAGE_USERS);
    }
    

    从上面的代码分析可以看出,用户创建的过程主要是应用运行环境(文件系统、权限等)的准备过程,主要可以分为以下几个关键的步骤:(忽略访客用户相关的操作)

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

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

    • 构造包含新用户信息的UserData,并固化到 “/data/system/users/${userId}.xml”
    root@virgo:/ # cat data/system/users/10.xml
    <?xml version='1.0' encoding='utf-8' standalone='yes' ?>
    <user id="10" serialNumber="10" flags="17" created="1561361447098" lastLoggedIn="1561460313625">
        <name>security space</name>
        <restrictions no_install_unknown_sources="false" no_usb_file_transfer="false" no_debugging_features="false" />
    </user>
    
    • 将新创建新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>
    

    3. 准备文件系统:

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

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

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

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

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


    3.2 多用户的切换

    adb shell am start-user: start USER_ID in background if it is currently stopped,
    use switch-user if you want to start the user in foreground.


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

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

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

    AMS的startUser方法只是判断了是否展示切换用户的Dialog,最终都会调用到UserController.startUser方法中:

    boolean startUser(final int userId, final boolean foreground) {
        if (mInjector.checkCallingPermission(INTERACT_ACROSS_USERS_FULL)
                != PackageManager.PERMISSION_GRANTED) {
            String msg = "Permission Denial: switchUser() from pid="
                    + Binder.getCallingPid()
                    + ", uid=" + Binder.getCallingUid()
                    + " requires " + INTERACT_ACROSS_USERS_FULL;
            Slog.w(TAG, msg);
            throw new SecurityException(msg);
        }
    
        Slog.i(TAG, "Starting userid:" + userId + " fg:" + foreground);
    
        final long ident = Binder.clearCallingIdentity();
        try {
            synchronized (mLock) {
                final int oldUserId = mCurrentUserId;
                if (oldUserId == userId) {
                    return true;
                }
    
                if (foreground) {
                    mInjector.getActivityStackSupervisor().setLockTaskModeLocked(
                            null, ActivityManager.LOCK_TASK_MODE_NONE, "startUser", false);
                }
    
                final UserInfo userInfo = getUserInfo(userId);
                if (userInfo == null) {
                    Slog.w(TAG, "No user info for user #" + userId);
                    return false;
                }
                if (foreground && userInfo.isManagedProfile()) {
                    Slog.w(TAG, "Cannot switch to User #" + userId + ": not a full user");
                    return false;
                }
    
                if (foreground && mUserSwitchUiEnabled) {
                    // 1. 冻结输入事件
                    // 2. 强制结束所有动画
                    // 3. 截取当前屏幕并展示
                    mInjector.getWindowManager().startFreezingScreen(
                            R.anim.screen_user_exit, R.anim.screen_user_enter);
                }
    
                boolean needStart = false;
    
                // If the user we are switching to is not currently started, then
                // we need to start it now.
                if (mStartedUsers.get(userId) == null) {
                    UserState userState = new UserState(UserHandle.of(userId));
                    mStartedUsers.put(userId, userState);
                    // 初始状态为 STATE_BOOTING
                    mInjector.getUserManagerInternal().setUserState(userId, userState.state);
                    updateStartedUserArrayLocked();
                    needStart = true;
                }
    
                final UserState uss = mStartedUsers.get(userId);
                final Integer userIdInt = userId;
                // 修改当前用户历史
                mUserLru.remove(userIdInt);
                mUserLru.add(userIdInt);
    
                if (foreground) {
                    mCurrentUserId = userId;
                    // 从Setting Provider读取需要切换用户的字体、语言、地区等配置并更新
                    // 如果是初创用户,对于字体则使用默认配置,语言和地区使用当前用户的配置:https://android.googlesource.com/platform/frameworks/base/+/ea906b3%5E!/
                    mInjector.updateUserConfigurationLocked();
                    mTargetUserId = UserHandle.USER_NULL; // reset, mCurrentUserId has caught up
    
                    // 更新当前用户附属的ManageProfile
                    updateCurrentProfileIdsLocked();
                    // 设置当前用户下所有window的可见性
                    // 设置切换用户的屏幕分辨率
                    mInjector.getWindowManager().setCurrentUser(userId, mCurrentProfileIds);
                    // Once the internal notion of the active user has switched, we lock the device
                    // with the option to show the user switcher on the keyguard.
                    if (mUserSwitchUiEnabled) {
                        // 切换过程中关闭Keyguard的指纹监听
                        mInjector.getWindowManager().setSwitchingUser(true);
                        // 设置Keyguard锁屏
                        mInjector.getWindowManager().lockNow(null);
                    }
                } else {
                    final Integer currentUserIdInt = mCurrentUserId;
                    updateCurrentProfileIdsLocked();
                    mInjector.getWindowManager().setCurrentProfileIds(mCurrentProfileIds);
                    mUserLru.remove(currentUserIdInt);
                    mUserLru.add(currentUserIdInt);
                }
    
                // Make sure user is in the started state.  If it is currently
                // stopping, we need to knock that off.
                if (uss.state == UserState.STATE_STOPPING) {
                    // If we are stopping, we haven't sent ACTION_SHUTDOWN,
                    // so we can just fairly silently bring the user back from
                    // the almost-dead.
                    uss.setState(uss.lastState);
                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
                    updateStartedUserArrayLocked();
                    needStart = true;
                } else if (uss.state == UserState.STATE_SHUTDOWN) {
                    // This means ACTION_SHUTDOWN has been sent, so we will
                    // need to treat this as a new boot of the user.
                    uss.setState(UserState.STATE_BOOTING);
                    mInjector.getUserManagerInternal().setUserState(userId, uss.state);
                    updateStartedUserArrayLocked();
                    needStart = true;
                }
    
                if (uss.state == UserState.STATE_BOOTING) {
                    // Give user manager a chance to propagate user restrictions
                    // to other services and prepare app storage
    
                    // 设置新用户的权限,校验或准备新用户app存储
                    mInjector.getUserManager().onBeforeStartUser(userId);
    
                    // Booting up a new user, need to tell system services about it.
                    // Note that this is on the same handler as scheduling of broadcasts,
                    // which is important because it needs to go first.
    
                    // 通知系统所有的服务新用户已经启动 (如:JobSchedulerService会根据Job对应的用户是否启动来确定是否需要再继续维护Job)
                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_START_MSG, userId, 0));
                }
    
                if (foreground) {
                    // 通知系统所有服务用户切换 (如:如果当前用户连接的是私有网络(如隐藏WiFi),则切换到新用户需要断开)
                    mHandler.sendMessage(mHandler.obtainMessage(SYSTEM_USER_CURRENT_MSG, userId,
                            oldUserId));
                    mHandler.removeMessages(REPORT_USER_SWITCH_MSG);
                    mHandler.removeMessages(USER_SWITCH_TIMEOUT_MSG);
                    mHandler.sendMessage(mHandler.obtainMessage(REPORT_USER_SWITCH_MSG,
                            oldUserId, userId, uss));
                    // 设置3s超时,如果3s内没有完成用户切换,则停止切换并解冻屏幕。
                    // 上述任务完成后会取消掉该延迟消息,并最终都会调用到continueUserSwitch方法,最终调用 dispatchUserSwitchComplete
                    mHandler.sendMessageDelayed(mHandler.obtainMessage(USER_SWITCH_TIMEOUT_MSG,
                            oldUserId, userId, uss), USER_SWITCH_TIMEOUT);
                }
    
                if (needStart) {
                    // Send USER_STARTED broadcast
                    // 发送ACTION_USER_STARTED广播
                    Intent intent = new Intent(Intent.ACTION_USER_STARTED);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY
                            | Intent.FLAG_RECEIVER_FOREGROUND);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                    mInjector.broadcastIntentLocked(intent,
                            null, null, 0, null, null, null, AppOpsManager.OP_NONE,
                            null, false, false, MY_PID, SYSTEM_UID, userId);
                }
    
                if (foreground) {
                    // Stop当前用户的Activity,如果待切换用户之前存在前台Activity,则在对应的ActivityStack中将其拉到Top并resume该Activity,否则启动桌面Activity
                    // 在待切换新用户使用Handler并首次调用MessageQueue.next()方法时,会调用AMS的activityIdle方法,进而调用activityIdleInternalLocked方法,此时会检查mStartingUsers列表, 根据待启动用户的userId调用UserController的finishUserSwitch
                    moveUserToForegroundLocked(uss, oldUserId, userId);
                } else {
                    finishUserBoot(uss);
                }
    
                if (needStart) {
                    Intent intent = new Intent(Intent.ACTION_USER_STARTING);
                    intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
                    intent.putExtra(Intent.EXTRA_USER_HANDLE, userId);
                    mInjector.broadcastIntentLocked(intent,
                            null, new IIntentReceiver.Stub() {
                                @Override
                                public void performReceive(Intent intent, int resultCode,
                                        String data, Bundle extras, boolean ordered, boolean sticky,
                                        int sendingUser) throws RemoteException {
                                }
                            }, 0, null, null,
                            new String[] {INTERACT_ACROSS_USERS}, AppOpsManager.OP_NONE,
                            null, true, false, MY_PID, SYSTEM_UID, UserHandle.USER_ALL);
                }
            }
        } finally {
            Binder.restoreCallingIdentity(ident);
        }
    
        return true;
    }
    

    方法很长,涉及到AMS和WMS的方法分支也很多。切换分为前台切换和后台切换,这里从前台切换侧并且对用户未启动的情况总结下关键的切换过程:

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

    • 冻结输入事件
    • 强制结束App动画
    • 截取当前屏幕并显示
    // WindowManagerService.java
    void startFreezingDisplayLocked(boolean inTransaction, int exitAnim, int enterAnim, DisplayContent displayContent) {
        //... 
        mInputMonitor.freezeInputDispatchingLw();
    
        // Clear the last input window -- that is just used for
        // clean transitions between IMEs, and if we are freezing
        // the screen then the whole world is changing behind the scenes.
        mPolicy.setLastInputMethodWindowLw(null, null);
    
        if (mAppTransition.isTransitionSet()) {
            mAppTransition.freeze();
        }
        // ...
        displayContent.updateDisplayInfo();
        screenRotationAnimation = new ScreenRotationAnimation(mContext, displayContent, mFxSession, inTransaction, mPolicy.isDefaultOrientationForced(), isSecure, this);
        mAnimator.setScreenRotationAnimationLocked(mFrozenDisplayId, screenRotationAnimation);
        // ...
    }
    

    上述这个过程在屏幕旋转的过程中也会执行,因此截取屏幕并展示也是采用和横竖屏切换一样的方式(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();
    // ...
    

    Start1:如果是待启动用户,则初始化待启动用户的状态为STATE_BOOTING,


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

    • 从SettingsProvider读取待切换用户的字体、语言、地区等配置并更新到系统。如果是初创用户,则字体使用默认配置,语言和地区使用当前用户的配置
    • 为待切换用户更新资源:如Attributes、Drawable、Color、Animator、StateList等。(有兴趣可以重点看下AMS的updateGlobalConfiguration()方法)
    • 修改当前用户下所有Window的可见性,启动Keyguard,切换过程中关闭Keyguard的指纹监听,并设置锁屏

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

    Start2:如果是待启动用户

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

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

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

    4. 设置切换超时定时器

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

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

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

    ActivityStackSupervisor.switchUserLocked():

    boolean switchUserLocked(int userId, UserState uss) {
        final int focusStackId = mFocusedStack.getStackId();
        // We dismiss the docked stack whenever we switch users.
        moveTasksToFullscreenStackLocked(DOCKED_STACK_ID, focusStackId == DOCKED_STACK_ID);
        // Also dismiss the pinned stack whenever we switch users. Removing the pinned stack will also cause all tasks to be moved to the fullscreen stack at a position that is appropriate.
        // Stop画中画对应ActivityStack中所有的Activity
        removeStackLocked(PINNED_STACK_ID);
    
        mUserStackInFront.put(mCurrentUser, focusStackId);
        final int restoreStackId = mUserStackInFront.get(userId, HOME_STACK_ID);
        mCurrentUser = userId;
    
        // 后续会根据这个List来finishUserSwitch
        mStartingUsers.add(uss);
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
            for (int stackNdx = stacks.size() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = stacks.get(stackNdx);
                // 这里会移动ActivityStack中所有的Task,并将能在待切换用户中运行的Task置于栈顶
                stack.switchUserLocked(userId);
                TaskRecord task = stack.topTask();
                if (task != null) {
                    // 将最顶端的Task对应的Window置顶
                    stack.positionChildWindowContainerAtTop(task);
                }
            }
        }
    
        // 取出待切换用户之前存在的前台应用对应的ActivityStack
        ActivityStack stack = getStack(restoreStackId);
        if (stack == null) {
            stack = mHomeStack;
        }
        final boolean homeInFront = stack.isHomeStack();
        if (stack.isOnHomeDisplay()) {
            // 将之前存在的前台应用置于前台
            stack.moveToFront("switchUserOnHomeDisplay");
        } else {
            // Stack was moved to another display while user was swapped out.
            resumeHomeStackTask(null, "switchUserOnOtherDisplay");
        }
        return homeInFront;
    }
    

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

    • 解冻屏幕和输入
    • 设置Keyguard,如果切换用户设置了指纹,则需要开始监听指纹信息
    • 通知监听者用户已经完成了切换

    UserController.continueUserSwitch()方法执行流程:

    7. 完成切换用户

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

    前台切换情况下finishUserBoot()方法的调用流程:

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

    3.3 多用户的删除

    adb shell pm remove-user ${userId}

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

    3.4 多用户的真面目

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

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


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


    5.1 获取当前用户userId的方式

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

    private fun readUserIdByReflect(): Int {
        var userId = 0
        try {
            val clz = UserHandle::class.java
            val myUserIdMethod = clz.getDeclaredMethod("myUserId")
            userId = myUserIdMethod.invoke(null) as Int
            Log.i("ulangch-r", "userId=$userId")
        } catch (e: Exception) {
        }
        return userId
    }
    

    5.2 跨用户启动Activity

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

    /**
     * Version of {@link #startActivity(Intent)} that allows you to specify the
     * user the activity will be started for.  This is not available to applications
     * that are not pre-installed on the system image.
     * @param intent The description of the activity to start.
     * @param user The UserHandle of the user to start this activity for.
     * @throws ActivityNotFoundException &nbsp;
     * @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.");
    }
    

    只有具有"android.Manifest.permission.INTERACT_ACROSS_USERS_FULL"的系统应用才可以调用该方法,经过反射测试,的确会抛出SecurityException:

    // 反射调用startActivityAsUser会抛出SecurityException
    07-01 20:59:40.445 25832 25832 W System.err: Caused by: java.lang.SecurityException: Permission Denial: startActivityAsUser asks to run as user 0 but is calling from user 13; this requires android.permission.INTERACT_ACROSS_USERS_FULL
    07-01 20:59:40.445 25832 25832 W System.err:         at android.os.Parcel.createException(Parcel.java:1953)
    07-01 20:59:40.445 25832 25832 W System.err:         at android.os.Parcel.readException(Parcel.java:1921)
    07-01 20:59:40.445 25832 25832 W System.err:         at android.os.Parcel.readException(Parcel.java:1871)
    07-01 20:59:40.446 25832 25832 W System.err:         at android.app.IActivityManager$Stub$Proxy.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.IActivityManager$Stub.onTransact$startActivityAsUser$(IActivityManager.java:11005)
    

    AMS中的权限检查抛出异常,具有 “android.permission.INTERACT_ACROSS_USERS_FULL” 和 “android.permission.INTERACT_ACROSS_USERS” 权限的系统应用才可以,以startActivityAsUser为例:

    // ActivityManagerService.java
    @Override
    public final int startActivityAsUser(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions, int userId) {
        enforceNotIsolatedCaller("startActivity");
        userId = mUserController.handleIncomingUser(Binder.getCallingPid(), Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY, "startActivity", null);
        // TODO: Switch to user app stacks here.
        return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent, resolvedType, null, null, resultTo, resultWho, requestCode, startFlags, profilerInfo, null, null, bOptions, false, userId, null, "startActivityAsUser");
    }
    

    最终会使用UserController.handleIncomingUser() 方法来做跨用户的权限检查:

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

    5.3 跨用户启动Service

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

    // 反射调用startServiceAsUser会抛出SecurityException
    07-01 20:56:33.759 25832 25832 W System.err: Caused by: java.lang.SecurityException: Permission Denial: service asks to run as user 0 but is calling from user 13; this requires android.permission.INTERACT_ACROSS_USERS_FULL or android.permission.INTERACT_ACROSS_USERS
    07-01 20:56:33.760 25832 25832 W System.err:         at android.os.Parcel.createException(Parcel.java:1953)
    07-01 20:56:33.760 25832 25832 W System.err:         at android.os.Parcel.readException(Parcel.java:1921)
    07-01 20:56:33.760 25832 25832 W System.err:         at android.os.Parcel.readException(Parcel.java:1871)
    07-01 20:56:33.760 25832 25832 W System.err:         at android.app.IActivityManager$Stub$Proxy.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.IActivityManager$Stub.onTransact$startService$(IActivityManager.java:10318)
    

    5.4 跨用户发送广播

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


    5.5 跨用户Query

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

    // ContentProvider.java
    /** @hide */
    public static Uri maybeAddUserId(Uri uri, int userId) {
        if (uri == null) return null;
        if (userId != UserHandle.USER_CURRENT
                && ContentResolver.SCHEME_CONTENT.equals(uri.getScheme())) {
            if (!uriHasUserId(uri)) {
                //We don't add the user Id if there's already one
                Uri.Builder builder = uri.buildUpon();
                builder.encodedAuthority("" + userId + "@" + uri.getEncodedAuthority());
                return builder.build();
    fe        }
        }
        return uri;
    }
    

    但是对于三方应用来说,仍然不可以通过这种方式实现跨用户共享数据,AMS还是会检查权限:

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

    5.6 /storage目录的跨用户访问

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


    5.7 跨用户的数据共享

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

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

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

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

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

    2. “/storage/emulated/obb” (不可行,Permission denied)

    展开全文
  • 如今,各大电商热衷于造节,电商节越来越多,没有一款成熟且强大的多店铺网上商城系统作为后盾支持,你该如何应对如排山倒海而来的抢购订单?...下面,小编通过当下市场主打的三款多用户商城系统Shopex、商淘B2B2...
  • 如今,各大电商热衷于造节,电商节越来越多,没有一款成熟且强大的多店铺网上商城系统作为后盾支持,你该如何应对如排山倒海而来的抢购订单?...下面,小编通过当下市场主打的三款多用户商城系统Shopex、WSTMar...
  • 所以我想发表一下我的个人看法 在我看来,windows7是多用户多任务操作系统。因为同一台电脑确实可以利用远程登录功能实现多个用户同时使用。你可以尝试一下使用QQ的这个远程协助功能。在别人协助帮你解决问题的同时...
  • Linux是一个多用户多任务的操作系统

    万次阅读 2016-04-24 15:25:01
    Linux是一个多用户多任务的操作系统多用户是指多个用户可以在同一时间使用计算机系统;多任务是指Linux可以同时执行几个任务,它可以在还未执行完一个任务时又执行另一项任务。  操作系统管理多个用户的请求和多...
  • 单用户、多用户、单任务、多任务,这么多种操作系统容易让人迷糊。其实这种初看你会觉得理解了一点,但其实你仔细研究会发现,多用户到底讲的是什么鬼? 多任务比较简单,就是应用程序都要放置到内存上去给CPU调度...
  • 商淘软件开源多用户商城系统已经有配套的PC、H5 、微信公众号商城、安卓app、苹果app、小程序端六端,带上配套的源码IM客服系统,现在又添加一员商家版APP,让这套商城系统如虎添翼。。。 让我们一览这款多用户商城...
  • PHP多用户在线客服系统

    千次阅读 2009-12-14 20:23:00
    PHP多用户在线客服系统,后台有代码镶嵌到你的系统中,感觉很实用,简洁,方便,网址是:http://mibew.org/,这个是开源的,支持多用户同时在线联系客服,客服后台及时性很强,只要用户点击在线客服,后台就有提示,...
  • Windows系统远程桌面的多用户登录

    万次阅读 2018-02-28 15:29:07
    转载自大名鼎鼎的大大,三好学生,转载请注明原文出处原文链接Windows系统远程桌面的多用户登录 0x00 前言在渗透测试中,经常会接触Windows服务器的远程桌面服务,通过界面对服务器进行管理。而对于普通的Windows...
  • linux系统用户与权限

    千次阅读 2019-03-22 19:16:56
    Linux 是一个多用户系统。 Root 是超级用户 根据账号位置: 本地账号 远程账号 LDAP NIS 根据账号的功能: 超级用户: root uid 0 普通用户: 系统用户: uid 1-499 本地用户: uid 500 每个用户都有一个...
  • 多用户博客BLOG系统大全

    千次阅读 2009-09-10 20:17:00
    近期朋友需要用到一个多用户BLOG程序,装了许多多用户BLOG。几天下来黑眼圈都下来了。我相信有人跟他也一样的经历。因为我曾经也“爱过”(开个玩笑)。不过程序这东西,仁者见仁,智者见智!适合自己的,才是最好的...
  • 基于laravel开发的多用户博客系统(-)
  • 系统架构改进--多系统用户整合

    千次阅读 2017-12-12 11:16:31
    单WEB应用登录登出实现原理首先,我们来分析下单Web应用系统登录登出的实现机理。 ...2、授权操作:根据用户在此系统中的权限定义,绑定正确的权限信息,为用户后续正确使用系统功能提供安全保障。
  • 用户系统设计

    万次阅读 2017-04-01 23:42:30
    本文会介绍用户系统的具体落地方案。登陆验证在一般项目账号体系中,一般会要求支持手机、邮箱、账号、QQ、微信、微博实现登陆。后面三种方式都是基于第三方授权后,完成的身份验证。手机、邮箱、账号则是相对传统的...
  • Android下uid与多用户释疑(一)

    千次阅读 2015-12-09 16:51:14
    同事,Android也引进了自己的多用户功能。所以,大量的类似“uid”的概念存在于Android中,让初学者很头痛,有必要整理一下以释疑。1.Linux uid/gid ...Linux是多用户系统,每个用户都拥有一个uid,这个uid
  • 用户多任务操作系统是指:一台计算机同时只能有一个用户使用,但该用户一次可以运行或提交个作业。(如:windows)(window2000和XP都可以有用户,为什么还是单用户呢?因为windows虽然可以设置
  • 租户用户管理系统中,常见的业务场景有以下几种:用户注册用户通过填写手机号码等信息,进行注册操作;该场景这重验证用户手机号码的有效性,一般通过短信验证码进行验证;租户注册用户通过填写租户的相关信息,...
  • 抽奖活动系统用户通过参与活动获得奖券,然后刮开奖券查询是否获奖。项目需求和技术方案: 项目需求是测试抽奖活动的中奖概率: 1、中奖率在定义的概率范围内 2、中奖次数不得大于定义的次数 3、所有奖券的...
  • Linux 系统同时可以支持多个用户,每个用户对自己的文件设备有特殊的权利,能够保证用户之间互不干扰。就像手机开了助手一样,同时登陆多个 qq 账号,当硬件配置非常...多用户是 Linux 优于其他操作系统的一大特点。
  • 推荐系统——用户画像

    千次阅读 多人点赞 2018-10-15 22:21:39
    写在最开始,本人不是专业做推荐系统的,本文只是整理最近培训的所见...目前推荐系统用户画像都是对用户的向量化表示,推荐系统用户画像是给计算机用的,而不是给人看的。用户画像不是推荐系统的目的,而是在构...
  • 安装git,使用yum源在线安装yum install -y git初始化git仓库,在/home/data/git/路径下初始化一个空的仓库gittest```git init --bare /home/data/git/gittest.git```创建用户组gitgroup```groupadd gitgroup```创建...
  • C语言-用户登录系统

    千次阅读 多人点赞 2020-01-10 20:25:53
    运行程序时,指定用户名,不输入,或少输入,或输入错误,都...输入正确:打印欢迎用户! 更资料请点击:我的目录 运行时输入用户名与密码: #include <stdio.h> #include <string.h> int main(in...
  • 用户画像系统概述

    千次阅读 2019-04-27 15:09:30
    1. 用户画像的概念 1.1 什么是用户画像? 用户画像是对现实世界中的用户的数学建模。 1.2 用户标签画像 用户标签画像是用标签标示方法来表示用户。 标签是某一种用户特征的符号表示; 用户画像是一个整体,各个维度...
  • Linux 是一个多用户、多任务的操作系统,我们应该了解单用户多任务和多用户多任务的概念。单用户多任务:比如我们以beinan 登录系统,进入系统后,我要打开gedit 来写文档,但在写文档的过程中,我感觉少点音乐,...
  • 批处理系统,分时系统,实时系统

    千次阅读 2015-10-23 16:36:48
    三大基本操作系统是批处理系统,分时系统,实时系统 概述 What操作系统 ...交互性强的多用户系统 实时系统 事件驱动,较少有人为干预的监督和控制系统   批处理系统特点: 优点:
  • 本文设计的移动互联网用户行为分析引擎通过云计算技术实现分布式并发的大规模计算能力,构建移动互联网端到端的大数据挖掘分析系统,实现对DPI和应用平台用户上网行为的偏好分析,提供个性化推荐服务,打通从数据...
  • 利用Python搭建用户画像系统

    万次阅读 多人点赞 2017-08-30 16:32:50
    用户画像是当下很企业都会提及的概念,多数情况下会和大数据以及营销挂钩。本文将对用户画像的相关知识进行进行简单的介绍,并利用Python去实现一个简单的用户画像系统。 1.什么是用户画像 用户画像可以理解成是...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 3,546,192
精华内容 1,418,476
关键字:

多用户系统