RePlugin 原理

作者: 风风风筝 | 来源:发表于2017-08-17 15:51 被阅读246次
    1. 预埋坑位

    利用 gradle 插件,在编译的时候往 AndroidManifest.xml 预埋坑位
    launchMode, theme, taskAffinity, process...

    <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS0' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.Translucent.NoTitleBar' />
    <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityN1NRTS1' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.Translucent.NoTitleBar' />
    <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityN1STPTS0' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.Translucent.NoTitleBar' android:launchMode='singleTop' />
    ...
    <service android:name='com.qihoo360.replugin.component.service.server.PluginPitServiceP0' android:process=':p0' android:exported='false' />
    ...
    <activity android:name='com.qihoo360.replugin.sample.host.loader.a.ActivityP2TA1STNTS2' android:configChanges='keyboard|keyboardHidden|orientation|screenSize' android:exported='false' android:screenOrientation='portrait' android:theme='@android:style/Theme.NoTitleBar' android:launchMode='singleTask' android:process=':p2' android:taskAffinity=':t1' />
    <provider android:name='com.qihoo360.replugin.component.provider.PluginPitProviderP2' android:authorities='com.qihoo360.replugin.sample.host.Plugin.NP.2' android:process=':p2' android:exported='false' />
    <provider android:name='com.qihoo360.replugin.component.process.ProcessPitProviderP2' android:authorities='com.qihoo360.replugin.sample.host.loader.p.mainN98' android:process=':p2' android:exported='false' />
    <service android:name='com.qihoo360.replugin.component.service.server.PluginPitServiceP2' android:process=':p2' android:exported='false' />
    
    2. 替换 ClassLoader

    在 Application 的 attachBaseContext 中把 ClassLoader 替换为 RePluginClassLoader

    RePlugin.App.attachBaseContext(this);
    

    最终会调用

    PatchClassLoaderUtils.patch(application);
    

    伪代码

    Context oBase = application.getBaseContext();
    ClassLoader oClassLoader = oBase.mPackageInfo.mClassLoader;
    oBase.mPackageInfo.mClassLoader = new RePluginClassLoader(oClassLoader.getParent(), oClassLoader);
    
    3. 插件 Activity 替换为预埋的坑位 Activity

    RePlugin.startActivity(Context context, Intent intent) 把 intent 的目标插件 Activity 替换为预埋的坑位 Activity

    4. 预埋的坑位 Activity 替换回插件 Activity

    server 进程返回后,在 ActivityThread 的 handleLaunchActivity → performLaunchActivity 中

    Activity activity = null;
    try {
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
        activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
    ...
    

    [Instrumentation.java]

    public Activity newActivity(ClassLoader cl, String className, Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }
    

    这里的 ClassLoader 就是 RePluginClassLoader,在 RePluginClassLoader 的 loadClass 中把坑位 Activity 的类名替换回原本的目标插件 Activity

    5. 加载插件 dex

    在 RePlugin.startActivity 中判断如果插件的 dex 还未被加载就 new PluginDexClassLoader

    public PluginDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) 
    

    PluginDexClassLoader 没有什么特别的逻辑,就是普通的 DexClassLoader
    这个步骤中还会动态注册插件中静态声明的 receiver 到常驻进程

    6. 资源

    在 RePlugin.startActivity 中判断如果插件的 dex 还未被加载就为 Plugin 对象的 Loader 对象
    构造 PackageInfo mPackageInfo
    构造 Resources mPkgResources
    构造 Application——PluginApplicationClient
    构造 PluginApplicationClient 的 Context mBase——PluginContext

    [Loader.java]

    mPackageInfo = pm.getPackageArchiveInfo(mPath,
                            PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
    ...
    mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
    ...
    mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
    

    mPath 就是插件 APK 的路径
    接着调用插件的 Application 的 onCreate

    [Plugin.java]

    private void callAppLocked() {
        // 获取并调用Application的几个核心方法
        if (!mDummyPlugin) {
            // NOTE 不排除A的Application中调到了B,B又调回到A,这时需要isLoaded防止循环加载
            mApplicationClient = PluginApplicationClient.getOrCreate(
                    mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);
    
            if (mApplicationClient != null && !mApplicationClient.isLoaded()) {
                mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
                mApplicationClient.callOnCreate();
            }
        } else {
            if (LOGR) {
                LogRelease.e(PLUGIN_TAG, "p.cal dm " + mInfo.getName());
            }
        }
    }
    

    PluginContext 是怎么跟插件的 Activity 关联起来的


    [Loader.java]

    final Context createBaseContext(Context newBase) {
        return new PluginContext(newBase, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
    }
    

    [PluginActivity.java]

    public abstract class PluginActivity extends Activity {
    
        @Override
        protected void attachBaseContext(Context newBase) {
            newBase = RePluginInternal.createActivityContext(this, newBase);
            super.attachBaseContext(newBase);
        }
    

    插件的 Activity 全部都是直接或间接继承于 PluginActivity,虽然开发的时候没有强制,但是编译的时候 gradle 插件会处理

    7. 题外话

    为每个插件创建一个 Resource 这种方式在插件化领域早期就有了,优点就是实现简单、兼容性好(较少的反射),缺点就是占用内存较大

    相关文章

      网友评论

        本文标题:RePlugin 原理

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