当前位置: 首页 > 工具软件 > DroidPlugin > 使用案例 >

DroidPlugin

邹祺然
2023-12-01

360 Android 插件项目 DroidPlugin

这个框架是奇虎360手机助手团队,最近在github上开源出来的Android插件框架。这种精神是很值得鼓励的。

github地址为: https://github.com/Qihoo360/DroidPlugin

好,一下纯属引入了官方的说明:

说明

DroidPlugin是360手机助手在Android系统上实现了一种新的插件机制: 
它可以在无需安装、修改的情况下运行APK文件,此机制对改进大型APP的架构,实现多团队协作开发具有一定的好处。

特点

那么这个插件框架,和我们之前所说的插件框架,有什么特点?它的实现方式是什么样的?这里我们从源码来分析一下。


插件原理——DexClassLoader

之前,我们也讨论过,一个Android的apk插件是个什么形式。主要是三点,我总结如下: 
- DexLoader动态的加载dex文件进入vm 
- AndroidManifest.xml中预注册一些将要使用的四大组件(方法很low,其实有更好的) 
- 针对四大组件,分别进行抽象代理,proxy 
- Apk安装还需要单独处理native的,so文件。 
- 宿主需要处理好Resource,保证R文件的正确使用。

360这个Android插件系统是否和我们之前讨论的类似呢?这里我们从IPluginManagerImpl.java文件中,我们能够看到其真正的安装apk插件的方法——installPackage。具体方法详解如下所示:

    /**
      * @param filepath 插件Apk的路径
      * @param flags Flag
      * 
      */
    @Override
    public int installPackage(String filepath, int flags) throws RemoteException {
        //install plugin
        String apkfile = null;
        try {
            // 验证合法性
            PackageManager pm = mContext.getPackageManager();
            PackageInfo info = pm.getPackageArchiveInfo(filepath, 0);
            if (info == null) {
                return PackageManagerCompat.INSTALL_FAILED_INVALID_APK;
            }

            apkfile = PluginDirHelper.getPluginApkFile(mContext, info.packageName);

            if ((flags & PackageManagerCompat.INSTALL_REPLACE_EXISTING) != 0) {
                // 新安装的插件

                // 停止,删除插件的缓存
                forceStopPackage(info.packageName);
                if (mPluginCache.containsKey(info.packageName)) {
                    deleteApplicationCacheFiles(info.packageName, null);
                }
                new File(apkfile).delete();
                Utils.copyFile(filepath, apkfile);
                PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
                parser.collectCertificates(0);
                PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);

                // 验证申请的permission,宿主中是否存在,不存在不让安装。
                if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
                    for (String requestedPermission : pkgInfo.requestedPermissions) {
                        boolean b = false;
                        try {
                            b = pm.getPermissionInfo(requestedPermission, 0) != null;
                        } catch (NameNotFoundException e) {
                        }
                        if (!mHostRequestedPermission.contains(requestedPermission) && b) {
                            Log.e(TAG, "No Permission %s", requestedPermission);
                            new File(apkfile).delete();
                            return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
                        }
                    }
                }

                // 保存签名
                saveSignatures(pkgInfo);
//                if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
//                    for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
//                        Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
//                    }
//                }

                // 拷贝插件中的native库文件。
                copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
                // opt Dex文件,并使用DexClassLoader动态的载入类代码。
                dexOpt(mContext, apkfile, parser);
                mPluginCache.put(parser.getPackageName(), parser);

                // 回调函数,通知插件安装
                mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
                sendInstalledBroadcast(info.packageName);
                return PackageManagerCompat.INSTALL_SUCCEEDED;
            } else {
                // 以下是插件,覆盖安装的过程。即更新的过程。与新安装类似。不做解释了。

                if (mPluginCache.containsKey(info.packageName)) {
                    return PackageManagerCompat.INSTALL_FAILED_ALREADY_EXISTS;
                } else {
                    forceStopPackage(info.packageName);
                    new File(apkfile).delete();
                    Utils.copyFile(filepath, apkfile);
                    PluginPackageParser parser = new PluginPackageParser(mContext, new File(apkfile));
                    parser.collectCertificates(0);
                    PackageInfo pkgInfo = parser.getPackageInfo(PackageManager.GET_PERMISSIONS | PackageManager.GET_SIGNATURES);
                    if (pkgInfo != null && pkgInfo.requestedPermissions != null && pkgInfo.requestedPermissions.length > 0) {
                        for (String requestedPermission : pkgInfo.requestedPermissions) {
                            boolean b = false;
                            try {
                                b = pm.getPermissionInfo(requestedPermission, 0) != null;
                            } catch (NameNotFoundException e) {
                            }
                            if (!mHostRequestedPermission.contains(requestedPermission) && b) {
                                Log.e(TAG, "No Permission %s", requestedPermission);
                                new File(apkfile).delete();
                                return PluginManager.INSTALL_FAILED_NO_REQUESTEDPERMISSION;
                            }
                        }
                    }
                    saveSignatures(pkgInfo);
//                    if (pkgInfo.reqFeatures != null && pkgInfo.reqFeatures.length > 0) {
//                        for (FeatureInfo reqFeature : pkgInfo.reqFeatures) {
//                            Log.e(TAG, "reqFeature name=%s,flags=%s,glesVersion=%s", reqFeature.name, reqFeature.flags, reqFeature.getGlEsVersion());
//                        }
//                    }

                    copyNativeLibs(mContext, apkfile, parser.getApplicationInfo(0));
                    dexOpt(mContext, apkfile, parser);
                    mPluginCache.put(parser.getPackageName(), parser);
                    mActivityManagerService.onPkgInstalled(mPluginCache, parser, parser.getPackageName());
                    sendInstalledBroadcast(info.packageName);
                    return PackageManagerCompat.INSTALL_SUCCEEDED;
                }
            }
        } catch (Exception e) {
            if (apkfile != null) {
                new File(apkfile).delete();
            }
            handleException(e);
            return PackageManagerCompat.INSTALL_FAILED_INTERNAL_ERROR;
        }
    }

以下是dexOpt方法的具体内容,简单的贴一下,不做说明了。

    private void dexOpt(Context hostContext, String apkfile, PluginPackageParser parser) throws Exception {
        String packageName = parser.getPackageName();
        String optimizedDirectory = PluginDirHelper.getPluginDalvikCacheDir(hostContext, packageName);
        String libraryPath = PluginDirHelper.getPluginNativeLibraryDir(hostContext, packageName);
        ClassLoader classloader = new PluginClassLoader(apkfile, optimizedDirectory, libraryPath, ClassLoader.getSystemClassLoader());
//        DexFile dexFile = DexFile.loadDex(apkfile, PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName()), 0);
//        Log.e(TAG, "dexFile=%s,1=%s,2=%s", dexFile, DexFile.isDexOptNeeded(apkfile), DexFile.isDexOptNeeded(PluginDirHelper.getPluginDalvikCacheFile(mContext, parser.getPackageName())));
    }

相关阅读

相关文章

相关问答