修改Android源码实现多开

1 前言

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

2 特征

  • 独立的userid Android在创建每个用户时,都会分配一个整型的userId。对于主用户(正常下的默认用户)来说,userId为0,之后创建的userId将从10开始计算,每增加一个userId加1。
  • 独立的文件存储 为了多用户下的数据安全性,在每个新用户创建之初,不管是外部存储(External Storage)还是app data目录,Android都为其准备了独立的文件存储。
  • App安装的唯一性 虽然前面说到,App的文件存储和数据目录在不同用户下都是独立的,但是对于App的安装,多个用户下同一个App却保持着同一个安装目录,换言之,不同用户可以使用同一份APK文件,但是在不同账户下都创建了属于自己账户的data文件。
  • 独立的权限控制 不同用户具有的权限不同,比如访客用户和主用户有不同的权限,不同用户下App的应用权限是独立的,不同用户下相同应用可以具有不同的权限。

3 修改创建用户限制

从Android5.0开始,Android支持创建Profile。默认情况下,系统只允许创建一个新的多开用户,也就是只能双开,但是修改源码可以达到创建多个用户。

修改frameworks/base/services/core/java/com/android/server/pm/UserManagerService.java的MAX_MANAGED_PROFILES字段,改成自己想要创建的最大用户数,它的默认值是1。

4 任务列表中

默认情况下,最近任务列表是不会出现多开应用的。在ActivityManagerService.java的getRecentTasks方法中,有一段校验,可以将这段校验注释掉。

// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
for (int i = 0; i < recentsCount && maxNum > 0; i++) {
    TaskRecord tr = mRecentTasks.get(i);
    //....
    if (!tr.mUserSetupComplete) {
         // Don't include task launched while user is not done setting-up.
        if (DEBUG_RECENTS) Slog.d(TAG_RECENTS,
                     "Skipping, user setup not complete: " + tr);
                continue;
        }
    //....
    res.add(rti);
    //....
}

5 配置系统应用不安装到子用户

默认情况下,在创建一个新用户的时候,系统会给新用户复制一份系统应用,但是在子用户中我们并不需要系统应用,所以我们要在子用户中取消安装这些系统应用。

系统应用可以不安装到子用户,但是系统服务一定要安装到子用户,否则,运行在子用户的app可能无法正常运行。

修改frameworks/base/services/core/java/com/android/server/pm/Settings.java中的createNewUserLI方法,对系统应用和系统服务是否安装到子用户进行配置。

private final String[] excludeLiStrings={
    "android",
    "android.ext.services",
    "android.ext.shared",
    "com.android.bluetooth",
    "com.android.htmlviewer",
    "com.android.inputdevices",
    "com.android.shell",
    "com.android.certinstaller",
    "com.android.externalstorage",
    "com.android.providers.contacts",
    "com.android.providers.downloads",
    "com.android.providers.media",
    "com.android.providers.settings",
    "com.android.providers.userdictionary",
    "com.android.server.telecom",
    "com.android.packageinstaller",
    "com.android.settings",
    "com.android.providers.telephony",
    "com.android.mms.service",
    "com.android.webview",
    "com.android.location.fused",
    "com.android.cts.priv.ctsshim",
    "com.android.statementservice",
    "com.android.defcontainer",
    "com.android.keychain",
    "com.android.proxyhandler",
    "com.android.dreams.basic",
    "com.android.printspooler",
    "com.android.pacprocessor",
    "com.android.providers.downloads.ui"
};
private boolean isInExcludeList(String pkg){
    for(String excludePkg:excludeLiStrings){
        if(excludePkg.equals(pkg)){
            return true;
        }
    }
    return false;
}
void createNewUserLI(@NonNull PackageManagerService service, @NonNull Installer installer,
        int userHandle) {
    String[] volumeUuids;
    String[] names;
    int[] appIds;
    String[] seinfos;
    int[] targetSdkVersions;
    int packagesCount;
    synchronized (mPackages) {
        Collection<PackageSetting> packages = mPackages.values();
        packagesCount = packages.size();
        volumeUuids = new String[packagesCount];
        names = new String[packagesCount];
        appIds = new int[packagesCount];
        seinfos = new String[packagesCount];
        targetSdkVersions = new int[packagesCount];
        Iterator<PackageSetting> packagesIterator = packages.iterator();
        for (int i = 0; i < packagesCount; i++) {
            PackageSetting ps = packagesIterator.next();
            if (ps.pkg == null || ps.pkg.applicationInfo == null) {
                continue;
            }
            // Only system apps are initially installed.
            //Slog.w(TAG, "User handle:"+userHandle+",pkg name:"+ps.name);
            //修改的地方,在列表外的应用不安装到子用户
            if(userHandle > 0 && !isInExcludeList(ps.name)){
                ps.setInstalled(false, userHandle);
            }
            else{
                 ps.setInstalled(ps.isSystem(), userHandle);
            }
            // Need to create a data directory for all apps under this user. Accumulate all
            // required args and call the installer after mPackages lock has been released
            volumeUuids[i] = ps.volumeUuid;
            names[i] = ps.name;
            appIds[i] = ps.appId;
            seinfos[i] = ps.pkg.applicationInfo.seinfo;
            targetSdkVersions[i] = ps.pkg.applicationInfo.targetSdkVersion;
        }
    }
    for (int i = 0; i < packagesCount; i++) {
        if (names[i] == null) {
            continue;
        }
        // TODO: triage flags!
        final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
        try {
            installer.createAppData(volumeUuids[i], names[i], userHandle, flags, appIds[i],
                    seinfos[i], targetSdkVersions[i]);
        } catch (InstallerException e) {
            Slog.w(TAG, "Failed to prepare app data", e);
        }
    }
    synchronized (mPackages) {
        applyDefaultPreferredAppsLPw(service, userHandle);
    }
}

6 设置App默认只安装到主用户

开启子用户后,如果调用adb install或者pm install来安装apk,会把apk安装所有用户。这不是我们想要的,所以,我们修改成执行这些命令时,只把app安装到主用户。

  • 针对用pm install命令安装apk的方式

      // frameworks/base/cmds/pm/src/com/android/commands/pm/Pm.jav\a
      private static class InstallParams {
        SessionParams sessionParams;
        String installerPackageName;
        //int userId = UserHandle.USER_ALL;
        int userId = UserHandle.USER_SYSTEM;
    }
    

7 相关命令

  • 创建(删除)用户

    pm create-user username
    pm remove-user userId
    
  • 列举用户

    pm list users
    
  • 启动或者激活用户

    am start-user userid
    
  • 在指定用户下安装(卸载)

    pm install -t -r --user userId apkPath
    pm uninstall --user userId pkgName