目录
一、面向企业的 Android 新功能
------从Google的设计思路来看,是从多用户入手,看完后,感觉还是国内人爱玩概念,
应用分身,国外人才注重原理框架设计,不在一个层面。但实质是从用户的需求出发,有需求才有设计。
参考文档:
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用户下。且观察界面可以看到他们同时运行在同一个桌面下。基于多用户, 我们很容易将创建了任意应用(微信)的分身乃至多开(多创建几个影子用户即可)。