Android开发经验

VirtualApp 框架解析---安装篇

2019-07-07  本文已影响0人  Kael_Huang

VirtualApp是如何来安装apk,与系统原生通过AppManagerService服务来进行安装会有哪些不同之处呢?
首先,从添加apk的函数入手,来掌握整个安装的脉络。

public void addApp(AppInfoLite info) {
    ...
    InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(info.packageName, 0);
            addResult.justEnableHidden = installedAppInfo != null;
            if (addResult.justEnableHidden) {
                int[] userIds = installedAppInfo.getInstalledUsers();
                int nextUserId = userIds.length;
                /*
                  Input : userIds = {0, 1, 3}
                  Output: nextUserId = 2
                 */
                for (int i = 0; i < userIds.length; i++) {
                    if (userIds[i] != i) {
                        nextUserId = i;
                        break;
                    }
                }
                addResult.userId = nextUserId;
                if (VUserManager.get().getUserInfo(nextUserId) == null) {
                    // user not exist, create it automatically.
                    ...
                }
                boolean success = VirtualCore.get().installPackageAsUser(nextUserId, info.packageName);
                ...
            } else {
                InstallResult res = mRepo.addVirtualApp(info);
                ...
            }
    ...
}

从核心代码来看,整个过程清晰并不复杂,主要的步骤分别是

  1. 根据AppInfoLitepackageName调用getInstalledAppInfo方法去获取已经安装的apk信息
  2. 通过justEnableHidden判断该apk是否是第一次安装(是否有用户已经完成过安装)
  3. 如果已经安装过,则通过现有的用户Id数组创建一个新的userId,同时如果该userId的用户不存在,还需通过VUserManager.get().createUser进行用户创建,最后调用VirtualCore.get().installPackageAsUser进行安装
  4. 如果没有安装过,则直接调用mRepo.addVirtualApp其实就是VirtualCore.get().installPackage进行安装

对上述过程分析后,发现主要的关键点分别是传入AppInfoLite调用getInstalledAppInfo获取apk信息、首次安装调用installPackage以及非首次安装调用installPackageAsUser方法。
其中,AppInfoLite是一个序列化的类对象,是AppInfo的简化版。

public class AppInfoLite implements Parcelable {
    ...
    public String packageName;
    public String path;
    public boolean fastOpen;
    ...
}

public class AppInfo {
    public String packageName;
    public String path;
    public boolean fastOpen;
    public Drawable icon;
    public CharSequence name;
    public int cloneCount;
}

构造数据基本是通过PackageManager来获取,整个构造过程也比较简单,需要注意的就是info.cloneCount,是指通过VirtualApp进行安装过的次数。

List<PackageInfo> pkgList = context.getPackageManager().getInstalledPackages(0)
PackageManager pm = context.getPackageManager();
List<AppInfo> list = new ArrayList<>(pkgList.size());
String hostPkg = VirtualCore.get().getHostPkg();
for (PackageInfo pkg : pkgList) {
    // ignore the host package
    if (hostPkg.equals(pkg.packageName)) {
        continue;
    }
    // ignore the System package
    if (isSystemApplication(pkg)) {
        continue;
    }
    ApplicationInfo ai = pkg.applicationInfo;
    String path = ai.publicSourceDir != null ? ai.publicSourceDir : ai.sourceDir;
    if (path == null) {
        continue;
    }
    AppInfo info = new AppInfo();
    info.packageName = pkg.packageName;
    info.fastOpen = fastOpen;
    info.path = path;
    info.icon = ai.loadIcon(pm);
    info.name = ai.loadLabel(pm);
    InstalledAppInfo installedAppInfo = VirtualCore.get().getInstalledAppInfo(pkg.packageName, 0);
    if (installedAppInfo != null) {
        info.cloneCount = installedAppInfo.getInstalledUsers().length;
    }
    list.add(info);
}

那接下来看下getInstalledAppInfoinstallPackage以及installPackageAsUser,这三个都属于VAppManagerService类的方法。

getInstalledAppInfo

public InstalledAppInfo getInstalledAppInfo(String packageName, int flags) {
        synchronized (PackageCacheManager.class) {
            if (packageName != null) {
                PackageSetting setting = PackageCacheManager.getSetting(packageName);
                if (setting != null) {
                    return setting.getAppInfo();
                }
            }
            return null;
        }
}

从代码来看,就是从PackageCacheManager中获取InstalledAppInfo对象,而PackageCacheManager是在首次安装才会构造对应的app数据并进行存储,所以安装过程中也会根据这个方法获取的对象是否为空来判断是否为首次安装。

installPackageAsUser

这个方法呢,是在非首次安装的时候进行调用,所以处理的事情不多。

if (VUserManagerService.get().exists(userId)) {
    PackageSetting ps = PackageCacheManager.getSetting(packageName);
    if (ps != null) {
        if (!ps.isInstalled(userId)) {
            ps.setInstalled(userId, true);
            notifyAppInstalled(ps, userId);
            ...
        }
    }
}

private void notifyAppInstalled(PackageSetting setting, int userId) {
    final String pkg = setting.packageName;
    int N = mRemoteCallbackList.beginBroadcast();
    while (N-- > 0) {
        try {
            if (userId == -1) {
                sendInstalledBroadcast(pkg);
                mRemoteCallbackList.getBroadcastItem(N).onPackageInstalled(pkg);
                mRemoteCallbackList.getBroadcastItem(N).onPackageInstalledAsUser(0, pkg);
            } else {
                mRemoteCallbackList.getBroadcastItem(N).onPackageInstalledAsUser(userId, pkg);
            }
            ...
        }
    }
        ...
}
...

从代码流程来看,主要干了两件事情,设置对应的PackageSetting安装标志位为已安装,同时通过调用notifyAppInstalled方法来通知其他进程广播已安装消息。

installPackage

而这个方法和installPackageAsUser从字面来看都是安装,但是过程全大不相同,此方法是在首次安装的时候调用,可以理解为是最为核心的方法,涉及处理的流程也较多,接下来顺着几个主要流程来解读下代码。

public synchronized InstallResult installPackage(String path, int flags, boolean notify) {
        ...
        /*
        ** 通过反射调用android.content.pm.PackageParser中的parsePackage方法对apk进行解析,
        ** 获取其中的activity、service、receivers、providers、permissions等信息并赋值给VPackage对象
        */
        VPackage pkg = null;
        try {
            pkg = PackageParserEx.parsePackage(packageFile);
        } catch (Throwable e) {
            e.printStackTrace();
        }
        ...
        /*
        ** 检查是否是更新安装,如果是更新安装,杀死app所在的进程并进行信息更新
        */
        VPackage existOne = PackageCacheManager.get(pkg.packageName);
        PackageSetting existSetting = existOne != null ? (PackageSetting) existOne.mExtras : null;
        if (existOne != null) {
            if ((flags & InstallStrategy.IGNORE_NEW_VERSION) != 0) {
                res.isUpdate = true;
                return res;
            }
            ...
        }
        File appDir = VEnvironment.getDataAppPackageDirectory(pkg.packageName);
        File libDir = new File(appDir, "lib");
        if (res.isUpdate) {
            FileUtils.deleteDir(libDir);
            VEnvironment.getOdexFile(pkg.packageName).delete();
            VActivityManagerService.get().killAppByPkg(pkg.packageName, VUserHandle.USER_ALL);
        }
        ...
        /*
        ** 反射调用com.android.internal.content.NativeLibraryHelper中的copyNativeBinaries方法,
        ** 拷贝对应的so到app对应的私有lib目录下。
        */   
        NativeLibraryHelperCompat.copyNativeBinaries(new File(path), libDir);
        /*
        ** 拷贝apk文件到对应的app私有目录下,并修改起读写权限。
        */  
        if (!dependSystem) {
            ...
            try {
                FileUtils.copyFile(packageFile, privatePackageFile);
            } catch (IOException e) {
                privatePackageFile.delete();
                return InstallResult.makeFailure("Unable to copy the package file.");
            }
            packageFile = privatePackageFile;
        }
        ...
        chmodPackageDictionary(packageFile);
        /*
        ** 构造对应的PackageSetting对象,存储相关信息并缓存到PackageCacheManager。
        */  
        PackageSetting ps;
        ...
        ps.dependSystem = dependSystem;
        ps.apkPath = packageFile.getPath();
        ps.libPath = libDir.getPath();
        ps.packageName = pkg.packageName;
        ps.appId = VUserHandle.getAppId(mUidSystem.getOrCreateUid(pkg));
        ...
        PackageParserEx.savePackageCache(pkg);
        PackageCacheManager.put(pkg, ps);
        ...
        /*
        ** 如果是art虚拟机(5.0以上系统),则先通过dex2oat命令进行dex优化,如果失败则通过DexFile.loadDex方法调用进行dex优化。
        */ 
        if (!dependSystem) {
            boolean runDexOpt = false;
            if (VirtualRuntime.isArt()) {
                try {
                    ArtDexOptimizer.interpretDex2Oat(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath());
                } catch (IOException e) {
                    e.printStackTrace();
                    runDexOpt = true;
                }
            } else {
                runDexOpt = true;
            }
            if (runDexOpt) {
                try {
                    DexFile.loadDex(ps.apkPath, VEnvironment.getOdexFile(ps.packageName).getPath(), 0).close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        /**
        ** 读取apk中manifest配置的静态注册广播信息,以动态注册的形式注册到VirtuapApp进程中,注意不是apk对应的进程;并且广播其他进程已安装信息。
        */
        BroadcastSystem.get().startApp(pkg);
        if (notify) {
            notifyAppInstalled(ps, -1);
        }
        res.isSuccess = true;
        return res;
    }

通过解读核心代码,可以得知主要存在7个步骤(已对应在代码的注释中)。

  1. 通过反射调用android.content.pm.PackageParser中的parsePackage方法对apk进行解析,获取其中的activity、service、receivers、providers、permissions、signatures等信息并赋值给VPackage对象。
private static VPackage buildPackageCache(PackageParser.Package p) {
        VPackage cache = new VPackage();
        cache.activities = new ArrayList<>(p.activities.size());
        cache.services = new ArrayList<>(p.services.size());
        cache.receivers = new ArrayList<>(p.receivers.size());
        cache.providers = new ArrayList<>(p.providers.size());
        cache.instrumentation = new ArrayList<>(p.instrumentation.size());
        cache.permissions = new ArrayList<>(p.permissions.size());
        cache.permissionGroups = new ArrayList<>(p.permissionGroups.size());

        for (PackageParser.Activity activity : p.activities) {
            cache.activities.add(new VPackage.ActivityComponent(activity));
        }
        for (PackageParser.Service service : p.services) {
            cache.services.add(new VPackage.ServiceComponent(service));
        }
        for (PackageParser.Activity receiver : p.receivers) {
            cache.receivers.add(new VPackage.ActivityComponent(receiver));
        }
        for (PackageParser.Provider provider : p.providers) {
            cache.providers.add(new VPackage.ProviderComponent(provider));
        }
        for (PackageParser.Instrumentation instrumentation : p.instrumentation) {
            cache.instrumentation.add(new VPackage.InstrumentationComponent(instrumentation));
        }
        cache.requestedPermissions = new ArrayList<>(p.requestedPermissions.size());
        cache.requestedPermissions.addAll(p.requestedPermissions);
        if (mirror.android.content.pm.PackageParser.Package.protectedBroadcasts != null) {
            List<String> protectedBroadcasts = mirror.android.content.pm.PackageParser.Package.protectedBroadcasts.get(p);
            if (protectedBroadcasts != null) {
                cache.protectedBroadcasts = new ArrayList<>(protectedBroadcasts);
                cache.protectedBroadcasts.addAll(protectedBroadcasts);
            }
        }
        cache.applicationInfo = p.applicationInfo;
        cache.mSignatures = p.mSignatures;
        cache.mAppMetaData = p.mAppMetaData;
        cache.packageName = p.packageName;
        cache.mPreferredOrder = p.mPreferredOrder;
        cache.mVersionName = p.mVersionName;
        cache.mSharedUserId = p.mSharedUserId;
        cache.mSharedUserLabel = p.mSharedUserLabel;
        cache.usesLibraries = p.usesLibraries;
        cache.mVersionCode = p.mVersionCode;
        cache.mAppMetaData = p.mAppMetaData;
        cache.configPreferences = p.configPreferences;
        cache.reqFeatures = p.reqFeatures;
        addOwner(cache);
        return cache;
}
  1. 检查是否是更新安装,如果是则杀死app所在的进程并进行信息更新。
  2. 反射调用com.android.internal.content.NativeLibraryHelper中的copyNativeBinaries方法,拷贝对应的so到app对应的私有lib目录下。
private static int copyNativeBinariesBeforeL(File apkFile, File sharedLibraryDir) {
    try {
        return Reflect.on(NativeLibraryHelper.TYPE).call("copyNativeBinariesIfNeededLI", apkFile, sharedLibraryDir)
                    .get();
    } catch (Throwable e) {
        e.printStackTrace();
    }
    return -1;
}
  1. 拷贝apk文件到对应的app私有目录下,并修改其文件目录读写权限。
  2. 构造对应的PackageSetting对象,存储相关信息并缓存到PackageCacheManager中。
  3. 如果是art虚拟机,则通过dex2oat命令或者DexFile.loadDex方法调用的方式进行dex优化。
  4. 读取apk中AndroidMinifest文件配置的静态注册广播信息,动态注册到VirtuapApp进程中,同时广播其他进程已安装通知。
public void startApp(VPackage p) {
        PackageSetting setting = (PackageSetting) p.mExtras;
        for (VPackage.ActivityComponent receiver : p.receivers) {
            ActivityInfo info = receiver.info;
            List<BroadcastReceiver> receivers = mReceivers.get(p.packageName);
            if (receivers == null) {
                receivers = new ArrayList<>();
                mReceivers.put(p.packageName, receivers);
            }
            String componentAction = String.format("_VA_%s_%s", info.packageName, info.name);
            IntentFilter componentFilter = new IntentFilter(componentAction);
            BroadcastReceiver r = new StaticBroadcastReceiver(setting.appId, info, componentFilter);
            mContext.registerReceiver(r, componentFilter, null, mScheduler);
            receivers.add(r);
            for (VPackage.ActivityIntentInfo ci : receiver.intents) {
                IntentFilter cloneFilter = new IntentFilter(ci.filter);
                SpecialComponentList.protectIntentFilter(cloneFilter);
                r = new StaticBroadcastReceiver(setting.appId, info, cloneFilter);
                mContext.registerReceiver(r, cloneFilter, null, mScheduler);
                receivers.add(r);
            }
        }
}

尾语

VirtualApp整个安装过程大概如此,那到底和系统原生安装主要存在什么差异呢?

根据上述过程,可以认知的几个不同之处在于:

  1. 系统安装是拷贝apk到/data/app/包名目录下,而va是拷贝到自己定义的私有目录下。
  2. 首次安装的时候要自行进行dex优化。
  3. 需要将AndroidMinifest文件中静态注册的广播信息解析出来并且在va进程中进行动态注册。

好了,VirtualApp的安装过程就先到此为止。

上一篇下一篇

猜你喜欢

热点阅读