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