之前在Android 11多用户功能之理论篇(一)中对多用户功能做了一个简单的介绍,这一篇将利用理论基础,探讨如何在原生AOSP上实现国内各大厂商早就有的应用分身功能。
1、启用多用户
从 Android 5.0
开始,多用户功能默认处于停用状态。如需启用这项功能,设备制造商必须定义一个资源叠加层,以替换frameworks/base/core/res/res/values/config.xml中的以下值:
请注意,开发者需要在资源叠加层(即Android SRO机制,后续会单独开一组文章探讨一下framework资源替换的课题)上修改,尽量不要去改动AOSP源码中的内容。
2、应用分身
先创建一个系统级应用用于提供应用分身的交互界面。为了节约点开发成本,笔者只对Bilibli实现了应用分身,该应用的UI如下图所示:
还记得上一篇里介绍的诸多用户类型吗,究竟用哪种用户类型来实现应用分身的功能会比较好呢?
答案是,使用PROFILE_MANAGED类型的用户。现在来创建一个父用户类型为System的PROFILE_MANAGED用户,用户名为"shadow",代码如下:
int userId = -1;
List<UserInfo> userInfoList = userManager.getUsers();
boolean isCreated = false;
for(UserInfo userInfo : userInfoList){
if("shadow".equals(userInfo.name)){
isCreated = true;
userId = userInfo.id;
break;
}
}
if(!isCreated) {
UserHandle userHandle = userManager.createProfile("shadow",
UserManager.USER_TYPE_PROFILE_MANAGED,
new HashSet<String>());
userId = userHandle.getIdentifier();
}
接下来,为App在androidManifest.xml中增加一些特定的权限,如下所示:
<uses-permission android:name="android.permission.MANAGE_USERS"/>
<uses-permission android:name="android.permission.CREATE_USERS"/>
<uses-permission android:name="android.permission.INTERACT_ACROSS_USERS_FULL"/>
<uses-permission android:name="android.permission.INSTALL_EXISTING_PACKAGES"/>
<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>
第三步,在后台启动刚创建的用户:
//后台启动userId
final IActivityManager am = ActivityManager.getService();
try {
am.startUserInBackground(userId);
} catch (RemoteException e) {
Log.*w*("UserManager", "could not start user " + userId, e);
}
第四步,将Bilibli安装到shadow用户下:
//将bilibili安装到shadow用户下
IPackageManager packageManager = ActivityThread.getPackageManager();
try {
PackageInstaller packageInstaller = new PackageInstaller(
packageManager.getPackageInstaller(),getPackageName(), userId);
packageInstaller.installExistingPackage("tv.danmaku.bili",
PackageManager.INSTALL_REASON_USER,null);
}catch (RemoteException e){
e.printStackTrace();
}
通过以上四个步骤,就已经实现Bilibli双开的功能了。通过adb查看可以清楚的看到存在两个不同进程的Bilibli应用:
但是去车载系统的launcher界面查看时,launcher上仍然只有一个图标,这是怎么回事?
为了回答这个问题,得去翻翻源码,看看车载launcher到底是如何实现的。车载系统默认用到的launcher并不是launcher3,而是carlauncher,其源码路径位于 packages/apps/Car/Launcher。
launcher获取所有应用的方法位于AppLauncherUtils类的getLauncherApps方法中,其核心代码如下:
可以看到,在查询应用时,它仅仅只查询了当前进程所属用户下的所有应用,所以我们在界面上只能看到一个图标。
能不能改呢,答案是当然可以,不过由于carlauncher存储应用的数据结构采用的是Map,采用的是ComponentName作为key,无法存储不同用户下的相同ComponentName的应用,得考虑改成List,修改的幅度有点大,主要修改要点如下:
1、将LauncherAppsInfo中的mLaunchables由Map<ComponentName, AppMetaData>类型修改为List类型。
2、获取当前用户及其Profile用户下的应用数据:
注意注释1处的if条件,开发者可以通过应用分身管理app通过Provider提供需要展示分身应用的数据,carlauncher主动去查询过滤出真正需要显示在界面上的应用(ps:创建新用户时会默认复制一份系统应用数据,如果这里不做过滤,界面上会显示多复制出来的系统应用图标)。
3、修改AppLauncherUtils类的launchApp方法,这个方法没有对多用户启动做支持,可以修改为如下式样:
到此为止,我们基本上已经实现了应用分身的功能。但还是有一点不太完美的地方,原应用和分身的应用名称都是一样的,不太好做区分,能不能自定义分身应用的名称呢?
Framework仅仅只为应用增加了右下角标作为不同用户的应用标识,想要通过framework层面修改来实现分身应用重命名有点大动干戈了,所以还是改carlauncher代码比较方便。
同样在AppLauncherUtils中的getLauncherApps方法中做如下修改即可:
还有一个需要注意的小细节,虽然PROFILE_MANAGED类型用户并不会展示在Settings的多用户菜单中,但是它仍然占据了一个用户创建名额,开发者需要根据实际占用的PROFIEL用户数量适当地调整最大用户数量上限。
上面实现了应用双开,能不能做到应用3开、4开甚至5开?调整一下PROFILE_MANAGER用户的创建数量上限的限制就行。
打开
framework/base/services/core/java/com/android/server/pm/UserTypeFactory. java文件,找到getDefaultTypeProfileManaged方法,可以看到它限制的上限数量就是1个:
一般不推荐直接在getDefaultTypeProfileManaged方法中修改,而是通过config_user_types.xml文件去覆盖默认的配置行为:
打包framework-res.apk,替换掉正在运行的系统中的apk,然后重启。重启后就可以创建两个PROFILE_MANAGER用户了: