美文网首页
RePlugin 记录(二)插件启动过程

RePlugin 记录(二)插件启动过程

作者: arstao | 来源:发表于2018-12-28 21:17 被阅读0次

接着第一篇文章,我们继续分析插件加载的方法:Plugin.load

final boolean load(int load, boolean useCache) {
        PluginInfo info = mInfo;
        //继续去看loadLocked这个方法
        boolean rc = loadLocked(load, useCache);
        // 尝试在此处调用Application.onCreate方法
        // Added by Jiongxuan Zhang
        if (load == LOAD_APP && rc) {
            callApp();
        }
        return rc;
    }
//只加载Service/Activity/ProviderInfo信息(包含ComponentList)
static final int LOAD_INFO = 0;
//加载插件信息和资源
static final int LOAD_RESOURCES = 1;
//加载插件信息、资源和Dex
static final int LOAD_DEX = 2;
//加载插件信息、资源、Dex,并运行Entry类
static final int LOAD_APP = 3;

private boolean loadLocked(int load, boolean useCache) {
         // 这里只看新建的情况即useCache = false且load = LOAD_APP的情况,省略缓存的情况。
        
        // 若插件被“禁用”,则即便上次加载过(且进程一直活着),这次也不能再次使用了
        int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
        if (status < PluginStatusController.STATUS_OK) {
            return false;
        }
        
        Context context = mContext;
        ClassLoader parent = mParent;
        PluginCommImpl manager = mPluginManager;

        ProcessLocker lock = new ProcessLocker(context, lockFileName);
       
        if (!lock.tryLockTimeWait(5000, 10)) {
            // 此处仅仅打印错误
            if (LOGR) {
                LogRelease.w(PLUGIN_TAG, logTag + ": failed to lock: can't wait plugin ready");
            }
        }
       
        boolean rc = doLoad(logTag, context, parent, manager, load);
        lock.unlock();
        
        if (rc) {
            
            try {
                // 至此,该插件已开始运行
                PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
            } catch (Throwable e) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "p.u.1: " + e.getMessage(), e);
                }
            }

            return true;
        }

        // 删除一些文件,重试上面的流程。。。
      
    }
private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
        if (mLoader == null) {
            // 试图释放文件
            // 根据PluginInfo提供的路径提取文件到指定插件目录,其中so库的提取要根据Abi来获取对应的文件
            //这里释放文件可以类比PMS安装文件的过程,加上下面loadDex方法的loadInfo过程
            //参考 https://blog.csdn.net/Innost/article/details/47253179  中4.4节APK Installation分析
            PluginInfo info = null;
            if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
                File dir = context.getDir(Constant.LOCAL_PLUGIN_SUB_DIR, 0);
                File dexdir = mInfo.getDexParentDir();
                String dstName = mInfo.getApkFile().getName();
                boolean rc = AssetsUtils.quickExtractTo(context, mInfo, dir.getAbsolutePath(), dstName, 
                                                        dexdir.getAbsolutePath());
                if (!rc) {
                    return false;
                }
                File file = new File(dir, dstName);
                info = (PluginInfo) mInfo.clone();
                info.setPath(file.getPath());

                // FIXME 不应该是P-N,即便目录相同,未来会优化这里
                info.setType(PluginInfo.TYPE_PN_INSTALLED);

            } else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
                // 外置插件,多了对插件的校验,通过文件头的一些字段,校验长度是否一致
                // 插件版本,MD5校验,证书校验
            } else {
                //
            }

            mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
            if (!mLoader.loadDex(parent, load)) {
                return false;
            }

            // 设置插件为“使用过的”
            // 注意,需要重新获取当前的PluginInfo对象,而非使用“可能是新插件”的mInfo
            PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
           
            // 若需要加载Dex,则还同时需要初始化插件里的Entry对象
            if (load == LOAD_APP) {
                // NOTE Entry对象是可以在任何线程中被调用到
                //调用插件Library的entry方法,传递在loadDex创建好的PluginContext给插件
               // 并初始化插件的环境(初始化一些反射的方法,然后调用Factory2的方法)。
               // 并且通过将插件中的Activity通过gradle 在编译期改成继承了PluginActivity形成了一个插件环境。
                //https://mp.weixin.qq.com/s/Vuh6SxFnTgiUsdX-ofeb4g
                // PluginActivity在获取context改成通过Factory2方法调用获取插件的context(PluginContext)
                // 重写启动组件的方法改成Replugin提供的启动方法(比如启动Activity通过把目标Activity改成
                //mainfest中的坑位达到欺骗系统的效果)
                if (!loadEntryLocked(manager)) {
                    return false;
                }
            }
        }


    }

解析四大组件,并保存到对应的数据结构,后面启动插件中的组件时使用

 final boolean loadDex(ClassLoader parent, int load) {
        try {
            PackageManager pm = mContext.getPackageManager();

            mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
            if (mPackageInfo == null) {
                // PackageInfo 根据apk里面的mainfest解析xml获取四大组件的信息
                mPackageInfo = pm.getPackageArchiveInfo(mPath,
                        PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | 
                        PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS |
                        PackageManager.GET_META_DATA);
              
                mPackageInfo.applicationInfo.sourceDir = mPath;
                mPackageInfo.applicationInfo.publicSourceDir = mPath;

                if (TextUtils.isEmpty(mPackageInfo.applicationInfo.processName)) {
                    mPackageInfo.applicationInfo.processName = mPackageInfo.applicationInfo.packageName;
                }

                // 添加针对SO库的加载
                // 此属性最终用于ApplicationLoaders.getClassLoader,在创建PathClassLoader时成为其参数
                // 这样findLibrary可不用覆写,即可直接实现SO的加载
                // Added by Jiongxuan Zhang
                PluginInfo pi = mPluginObj.mInfo;
                File ld = pi.getNativeLibsDir();
                mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
            }

            // 创建或获取ComponentList表
            // Added by Jiongxuan Zhang
            mComponents = Plugin.queryCachedComponentList(mPath);
            if (mComponents == null) {
                // ComponentList
                mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
                // 动态注册插件中声明的 receiver
                regReceivers();
                /* 只调整一次 */
                // 调整插件中组件的进程名称
                adjustPluginProcess(mPackageInfo.applicationInfo);
                // 调整插件中 Activity 的 TaskAffinity
                adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
            }

            if (load == Plugin.LOAD_INFO) {
                return isPackageInfoLoaded();
            }

            mPkgResources = Plugin.queryCachedResources(mPath);
            // LOAD_RESOURCES和LOAD_ALL都会获取资源,但LOAD_INFO不可以(只允许获取PackageInfo)
            if (mPkgResources == null) {
                //调用ActivityThread.getTopLevelResources 获取资源,AssetManager通过路径去加载
               // resource.asrc获取资源的索引信息。之后就可以通过资源索引来加载资源了
                mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
                // 缓存表: Resources
                synchronized (Plugin.FILENAME_2_RESOURCES) {
                    Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
                }
            }
            if (load == Plugin.LOAD_RESOURCES) {
                return isResourcesLoaded();
            }

            mClassLoader = Plugin.queryCachedClassLoader(mPath);
            if (mClassLoader == null) {
                //获取Dex(优化后)生成时所在的目录
                String out = mPluginObj.mInfo.getDexParentDir().getPath();
                parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
                String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
                long begin = 0;
                boolean isDexExist = false;
                //创建PluginDexClassLoader,持有宿主ClassLoader,可以使用只有宿主才有的类
                //ClassLoader借助DexPathList对传入路径(dex路径、so路径)解析
                //然后在ClassLoader的findClass中通过DexPathList将dex文件加载进内存
                //详细过程可以看 https://www.cnblogs.com/lanrenxinxin/p/4712224.html
                mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, 
                                  mPath, out, soDir, parent);
            }
            if (load == Plugin.LOAD_DEX) {
                return isDexLoaded();
            }
            // Context
            mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources,
                                              mPluginName, this);
        } catch (Throwable e) {
            return false;
        }

        return true;
    }

参考:
Gradle插件在RePlugin中的应用:https://mp.weixin.qq.com/s/Vuh6SxFnTgiUsdX-ofeb4g
浅析dex文件加载机制:https://www.cnblogs.com/lanrenxinxin/p/4712224.html
[深入理解Android卷二 全文-第四章]深入理解:PackageManagerServicehttps://blog.csdn.net/Innost/article/details/47253179

相关文章

网友评论

      本文标题:RePlugin 记录(二)插件启动过程

      本文链接:https://www.haomeiwen.com/subject/vkfrkqtx.html