1. 預(yù)埋坑位
利用 gradle 插件,在編譯的時(shí)候往 AndroidManifest.xml 預(yù)埋坑位
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);
最終會(huì)調(diào)用
PatchClassLoaderUtils.patch(application);
偽代碼
Context oBase = application.getBaseContext();
ClassLoader oClassLoader = oBase.mPackageInfo.mClassLoader;
oBase.mPackageInfo.mClassLoader = new RePluginClassLoader(oClassLoader.getParent(), oClassLoader);
3. 插件 Activity 替換為預(yù)埋的坑位 Activity
RePlugin.startActivity(Context context, Intent intent) 把 intent 的目標(biāo)插件 Activity 替換為預(yù)埋的坑位 Activity
4. 預(yù)埋的坑位 Activity 替換回插件 Activity
server 進(jìn)程返回后葵诈,在 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 的類名替換回原本的目標(biāo)插件 Activity
5. 加載插件 dex
在 RePlugin.startActivity 中判斷如果插件的 dex 還未被加載就 new PluginDexClassLoader
public PluginDexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent)
PluginDexClassLoader 沒(méi)有什么特別的邏輯毅待,就是普通的 DexClassLoader
這個(gè)步驟中還會(huì)動(dòng)態(tài)注冊(cè)插件中靜態(tài)聲明的 receiver 到常駐進(jìn)程
6. 資源
在 RePlugin.startActivity 中判斷如果插件的 dex 還未被加載就為 Plugin 對(duì)象的 Loader 對(duì)象
構(gòu)造 PackageInfo mPackageInfo
構(gòu)造 Resources mPkgResources
構(gòu)造 Application——PluginApplicationClient
構(gòu)造 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 的路徑
接著調(diào)用插件的 Application 的 onCreate
[Plugin.java]
private void callAppLocked() {
// 獲取并調(diào)用Application的幾個(gè)核心方法
if (!mDummyPlugin) {
// NOTE 不排除A的Application中調(diào)到了B炼蛤,B又調(diào)回到A颈畸,這時(shí)需要isLoaded防止循環(huán)加載
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 關(guān)聯(lián)起來(lái)的
[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,雖然開(kāi)發(fā)的時(shí)候沒(méi)有強(qiáng)制掂骏,但是編譯的時(shí)候 gradle 插件會(huì)處理
7. 題外話
為每個(gè)插件創(chuàng)建一個(gè) Resource 這種方式在插件化領(lǐng)域早期就有了,優(yōu)點(diǎn)就是實(shí)現(xiàn)簡(jiǎn)單厚掷、兼容性好(較少的反射)弟灼,缺點(diǎn)就是占用內(nèi)存較大