程序员

RePlugin 原理

2017-08-17  本文已影响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 这种方式在插件化领域早期就有了,优点就是实现简单、兼容性好(较少的反射),缺点就是占用内存较大

上一篇下一篇

猜你喜欢

热点阅读