Bootstrap

Android 多用户 —— 从入门到应用分身

目录

一、面向企业的 Android 新功能

二、以第三方应用为例做说明

三、以微信为例, 我们测试创建新用户,并尝试双开微信


一、面向企业的 Android 新功能

 ------从Google的设计思路来看,是从多用户入手,看完后,感觉还是国内人爱玩概念

应用分身,国外人才注重原理框架设计,不在一个层面。但实质是从用户的需求出发,有需求才有设计。

参考文档:

     Android 多用户 —— 从入门到应用分身 (上)

     What's new for enterprise in Android 14  |  Android Developers

二、以第三方应用为例做说明

一个应用被安装后,系统给分配唯一的"Application ID", 简称 =="AppId"== . 同时系统中会有多个用户(User),每个用户也有一个唯一的ID值,称为 =="UserId"== 。

Android这里的"UserId"跟Linux的UserId完全不是同一个东西。UserId *10000 + appId 才等于Linux下的UserId, 即进程所属用户的概念, 在Android我们通常记做 =="uid"==,

以下以微信为例作为说明:

// 查看微信进程信息
$ adb shell ps | grep tencent
USER      PID   PPID  VSIZE   RSS    WCHAN                PC  NAME
u0_a110   26250 416   2144140 203204 SyS_epoll_ 00e7b40428 S com.tencent.mm
u0_a110   26338 416   1794628 123868 SyS_epoll_ 00e7b40428 S com.tencent.mm:push

从Android L(5.0)开始引入多用户API。先不论权限问题,我们可以创建多个用户。Android为什么引入这样的多用户概念? 有至少以下2个意图:

  • 实现"访客模式"这样的通常PC端支持的多用户模式。其特点设备可以支持多个人在不同时段登陆, 独享设备。比如上班时登陆A用户,回家给小孩玩,登陆B用户. A/B用户数据完全独立, 互不影响。

  • 单纯为了"应用多开",原生的计划基于多用户做"工作模式"(Android For work, 简"AFW")。所谓工作模式就是可在手机上打开一个应用的多个实例,同时运行与同一个桌面下,而又数据相互独立:一个用于工作, 一个用于生活. 简单的说,基本就是我们要的"应用多开"。

以上2点的区别是, 前一种多用户是必须有一个明显的用户切换过程,一个时段内只有一个用户下的进程可以在前台显示和交互;后一种是在主用户的基础上创建一个依附于主用户的"影子用户"(Managed Profile)。

前一种运行情况可以用我们flyme-设置-访客模式 来体验,进入访客模式就是进入B用户。

后一种影子用户与主用户运行在同一个桌面下 效果如下图:

图2:多任务里, 显示2个qq, 一个是主用户下的qq进程组, 一个是影子用户下的qq进程组。

所谓 =="影子用户"== 是我个人的翻译. 谷歌官方把这种用户一直称为 =="Managed Profile"== , 字面翻译不能表达其真实含义, 所以我将其意译为"影子用户"。

使用Android原生多用户 -- "普通用户"

可以使用adb命令来模拟:

// 第一步,创新新用户:
$ adb shell pm craete-user 'test-user'

// 第二步,得道新用户的userId:
$ adb shell dumpsys user 
  ...
  UserInfo{10:test-user:0} serialNo=1001
  ...
// 得道UserId = 10

// 第三步, 启动新用户:
$ adb shell am start-user 10

// 第四步, 将新用户切到前台来:
$ adb shell am switch-user 10

// 第五步, 校验切换用户成功:
$ adb shell dumpsys activity a | grep 'Hist '
      * Hist #0: ActivityRecord{8636857 u10 com.meizu.flyme.launcher/.Launcher t1100001}

// 看到桌面"com.meizu.flyme.launcher/.Launcher"运行在"u10"即运行在userId=10的用户下, 说明新用户正处与前台.

// ps: 如切回主用户不赘述, 可自行查询 "adb shell pm / am"命令.

1. 原生Android多用户在linux看来是不可见的上次应用行为. Android多用户的多用户与Linux的多用户是完全不同的概念;

2. 原生多用户可以做到2种型式的多开:

  • 一种是显式的切换用户后,应用运行在不同用户下的多开, 跟传统PC上的多用户类似;

  • 另一种是不用切换用户,当前用户下运行N个影子用户,一个主用户跟N个影子用户共同运行于同一桌面下.

3. 上面忽略了一些信息, 如:

  • 原生提供了DevicePolicyManager接口, 用于控制新创建用户的权限,比如可以控制新用户是否可以访问网络等;

  • 创建用户时flags参数可以控制新用户的一些属性, 比如此用户是"访客用户",则该用户退出时完全清理数据。这些细节对我们系统开发人员来说并不重要, 我们很容易可以按需定制, 稍做了解知道有这么回事就好。
     

4. 无论是所谓"普通用户"还是"影子用户", 原生默认情况下都把系统应用安装到这些创建的用户里去:

三、以微信为例, 我们测试创建新用户,并尝试双开微信

第一步,创建一个android:sharedUserId="android.uid.system"的应用, 调用如下代码,创建ManagedProfileUser(影子用户), 并启动该用户。(ps:创建"影子用户必须使用代码, 没有对应的adb命令)虽然创建影子用户跟普通用户走一样的代码, 只是flags的不同.但是adb命令中无法指定这个flash,故而创建影子用户需使用代码调用API创建;

 int FLAG_MANAGED_PROFILE = 0x00000020; // 创建影子用户必要的flag
    private static final String FLYME_PARALLEL_SPACE_USER_NAME = "FlymeParallelSpace";// 指定影子用户UserName
    private UserManager mUserManager;
    private Object mFlymeParallelSpaceUserInfo = null; // multi-open UserInfo

    static int getUserIdFromUserInfo(Object userInfo) {
        int userId = -1;
        try {
            Field field_id = userInfo.getClass().getDeclaredField("id");
            field_id.setAccessible(true);
            userId = (Integer)field_id.get(userInfo);
        } catch (Exception e) {
            Log.d(TAG, "getUserIdFromUserInfo() E:" + e.toString(), e);
        }
        return userId;
    }

    public void openFlymeParallelSpace() {
        mUserManager = (UserManager) mContext.getSystemService(Context.USER_SERVICE);

        // step 1 : call UMS.createProfileForUser() to create Managed Profile User
        UserHandle userHandle = android.os.Process.myUserHandle();
        int getIdentifier = ReflectCache.on(userHandle, "getIdentifier").invoke();
        mFlymeParallelSpaceUserInfo = ReflectCache.on(mUserManager,
                "createProfileForUser",
                String.class, int.class, int.class)
                .invoke(FLYME_PARALLEL_SPACE_USER_NAME,
                        FLAG_MANAGED_PROFILE /*| FLAG_DISABLED*/,
                        getIdentifier);


        // step 2 call AMS.startUserInBackground() to start the new user.
        int userId = getUserIdFromUserInfo(mFlymeParallelSpaceUserInfo);
        Object iActivityManager = ReflectCache.on("android.app.ActivityManagerNative",
                    "getDefault").invoke();
            boolean isOk = ReflectCache.on(iActivityManager, "startUserInBackground",
                    int.class).invoke(userId);
            Log.d(TAG, "startUserInBackground() userId = " + userId + " | isOk = " + isOk);
    }

第二步,安装应用到影子用户, 如果事先已安装了微信,则可是使用如下命令将微信额外安装到影子用户:

// 首先要获取影子用户的userID:
$ adb shell dumpsys user
  ...
  UserInfo{10:FlymeParallelSpace:30} serialNo=10
  ...

// UserInfo{10:FlymeParallelSpace:30}表示:
//  userId=10, 
//  userName="FlymeParallelSpace", 
//  flags=0x30
于是我们得到影子用户的UserId


// 如微信已经安装了, 使用adb命令重安装到影子用户:
$ adb shell pm install -r --user 10 `adb shell pm path com.tencent.mm | awk -F':' '{print $2}'`
// "--user 10" 指定安装userId为10.


// 或者调用API:
//   PMS.installExistingPackageAsUser()
//   PMS.installPackageAsUser()

第三步, 分别启动主用户和影子用户下的微信;

// 首先找到微信的首页Activity:
$ adb shell dumpsys package com.tencent.mm | grep "android.intent.action.MAIN:" -A 5 
      android.intent.action.MAIN:
        8f8990c com.tencent.mm/.ui.LauncherUI filter 3d82835
          Action: "android.intent.action.MAIN"
          Category: "android.intent.category.LAUNCHER"
          Category: "android.intent.category.MULTIWINDOW_LAUNCHER"
          AutoVerify=false
 // 得到首页Activity为"com.tencent.mm/.ui.LauncherUI"


 //于是启动影子用户下的微信为:
 $ adb shell am start --user 10 com.tencent.mm/.ui.LauncherUI
 // "--user 10"为指定userId为10, 不指定则默认为主用户, 即userId=0为默认.
 // ps: 并不是所有可指定userId的命令都这样设定.
 //     如 "am force-stop"命令是默认情况下杀所有用户下进程, 而非仅杀主用户下进程.



// 启动主用户下微信:
$ adb shell am start --user 0 com.tencent.mm/.ui.LauncherUI
 或:
$ adb shell am start com.tencent.mm/.ui.LauncherUI


检查微信进程:
$ adb shell ps | grep com.tencent.mm
u10_a110 19794 11620 2157444 185912 SyS_epoll_ 00eb0ce428 S com.tencent.mm
u10_a110 19882 11620 1832116 121932 SyS_epoll_ 00eb0ce428 S com.tencent.mm:push
u0_a110   19989 11620 2151704 194924 SyS_epoll_ 00eb0ce428 S com.tencent.mm
u0_a110   20072 11620 1830048 122600 SyS_epoll_ 00eb0ce428 S com.tencent.mm:push

可以看到,微信出现两组进程组, 一组在u0_a110用户下, 一组在u10_a110用户下。且观察界面可以看到他们同时运行在同一个桌面下。基于多用户, 我们很容易将创建了任意应用(微信)的分身乃至多开(多创建几个影子用户即可)。

;