我們這一節(jié)來分析一下滴滴插件化是如何啟動插件的 Activity 的。
一、使用
- 1.配置宿主工程的 Module#build.gradle

- 2.配置插件工程的 Module#build.gradle

3.將插件工程使用
assemblePlugin任務打包出一個 apk 文件,放進宿主工程的 assets 目錄中4.在宿主的 Application 中進行滴滴插件化工具的初始化

- 5.在需要用到插件 apk 的資源的地方進行插件加載

- 6.正常調用插件 apk 的 Activity、Service 等資源

二、插件化加載 Activity 的過程解析
- 1.插件初始化
PluginManager.getInstance(context).init();
我們仔細看看這里做了什么工作:
public static PluginManager getInstance(Context base) {
//宿主的 context
if (sInstance == null) {
synchronized (PluginManager.class) {
if (sInstance == null)
sInstance = new PluginManager(base);
}
}
return sInstance;
}
private PluginManager(Context context) {
Context app = context.getApplicationContext();
if (app == null) {
this.mContext = context;
} else {
this.mContext = ((Application)app).getBaseContext();
}
prepare();
}
private void prepare() {
Systems.sHostContext = getHostContext();
//關鍵操作
this.hookInstrumentationAndHandler();
//關鍵操作
this.hookSystemServices();
}
public void init() {
mComponentsHandler = new ComponentsHandler(this);
RunUtil.getThreadPool().execute(new Runnable() {
@Override
public void run() {
doInWorkThread();
}
});
}
private void hookInstrumentationAndHandler() {
try {
Instrumentation baseInstrumentation = ReflectUtil.getInstrumentation(this.mContext);
if (baseInstrumentation.getClass().getName().contains("lbe")) {
// reject executing in paralell space, for example, lbe.
System.exit(0);
}
//hook Instrumentation,set 進 ActivityThread 中
final VAInstrumentation instrumentation = new VAInstrumentation(this, baseInstrumentation);
Object activityThread = ReflectUtil.getActivityThread(this.mContext);
ReflectUtil.setInstrumentation(activityThread, instrumentation);
ReflectUtil.setHandlerCallback(this.mContext, instrumentation);
this.mInstrumentation = instrumentation;
} catch (Exception e) {
e.printStackTrace();
}
}
可以看到,PluginManager 初始化的時候回保存宿主的 Application context,之后會反射獲取 ActivityThread 的 Instrumentation 對象,并且生成一個 VAInstrumentation 對象,再反射設置進 ActivityThread 中。
這一步很重要,因為 Instrumentation 涉及到加載 Activity 的過程,插件化能對 Android 系統(tǒng)進行欺上瞞下以及調用宿主的 Activity 就是因為 hook 了 Instrumentation。
- 2.加載插件 apk
PluginManager.getInstance(mContext).loadPlugin(apk);
/**
* load a plugin into memory, then invoke it's Application.
* @param apk the file of plugin, should end with .apk
* @throws Exception
*/
public void loadPlugin(File apk) throws Exception {
if (null == apk) {
throw new IllegalArgumentException("error : apk is null.");
}
if (!apk.exists()) {
throw new FileNotFoundException(apk.getAbsolutePath());
}
//調用 PackageParser.parsePackage 解析 apk,得到的信息封裝進 loadedPlugin 對象
LoadedPlugin plugin = LoadedPlugin.create(this, this.mContext, apk);
if (null != plugin) {
//加載過的插件緩存起來
this.mPlugins.put(plugin.getPackageName(), plugin);
synchronized (mCallbacks) {
for (int i = 0; i < mCallbacks.size(); i++) {
//hookDataBindUtil 的時候會用到
mCallbacks.get(i).onAddedLoadedPlugin(plugin);
}
}
//構造插件的 Application,并調用插件 Application 的 onCreate()
plugin.invokeApplication();
} else {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
}
可以看到,這里創(chuàng)建了一個 LoadedPlugin 對象,這個 LoadedPlugin 對象很重要,用來解析插件 apk 的資源。
/**
* 資源加載
* Created by renyugang on 16/8/9.
*/
public final class LoadedPlugin {
public static LoadedPlugin create(PluginManager pluginManager, Context host, File apk) throws Exception {
//需要注意 context 是宿主的 Context
//apk 指的是插件的路徑
return new LoadedPlugin(pluginManager, host, apk);
}
......
LoadedPlugin(PluginManager pluginManager, Context context, File apk) throws Exception {
this.mPluginManager = pluginManager;
this.mHostContext = context;
this.mLocation = apk.getAbsolutePath();
this.mPackage = PackageParserCompat.parsePackage(context, apk, /*PackageParser.PARSE_MUST_BE_APK*/0);
this.mPackage.applicationInfo.metaData = this.mPackage.mAppMetaData;
this.mPackageInfo = new PackageInfo();
this.mPackageInfo.applicationInfo = this.mPackage.applicationInfo;
this.mPackageInfo.applicationInfo.sourceDir = apk.getAbsolutePath();
if (Build.VERSION.SDK_INT >= 28
|| (Build.VERSION.SDK_INT == 27 && Build.VERSION.PREVIEW_SDK_INT != 0)) { // Android P Preview
try {
this.mPackageInfo.signatures = this.mPackage.mSigningDetails.signatures;
} catch (Throwable e) {
PackageInfo info = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
this.mPackageInfo.signatures = info.signatures;
}
} else {
this.mPackageInfo.signatures = this.mPackage.mSignatures;
}
this.mPackageInfo.packageName = this.mPackage.packageName;
if (pluginManager.getLoadedPlugin(mPackageInfo.packageName) != null) {
throw new RuntimeException("plugin has already been loaded : " + mPackageInfo.packageName);
}
this.mPackageInfo.versionCode = this.mPackage.mVersionCode;
this.mPackageInfo.versionName = this.mPackage.mVersionName;
this.mPackageInfo.permissions = new PermissionInfo[0];
this.mPackageManager = new PluginPackageManager();
this.mPluginContext = new PluginContext(this);
this.mNativeLibDir = context.getDir(Constants.NATIVE_DIR, Context.MODE_PRIVATE);
//通過 COMBINE_RESOURCES 決定是否將插件資源加載到宿主中
this.mResources = createResources(context, apk);
//創(chuàng)建 ClassLoader
this.mClassLoader = createClassLoader(context, apk, this.mNativeLibDir, context.getClassLoader());
tryToCopyNativeLib(apk);
// Cache instrumentations
Map<ComponentName, InstrumentationInfo> instrumentations = new HashMap<ComponentName, InstrumentationInfo>();
for (PackageParser.Instrumentation instrumentation : this.mPackage.instrumentation) {
instrumentations.put(instrumentation.getComponentName(), instrumentation.info);
}
this.mInstrumentationInfos = Collections.unmodifiableMap(instrumentations);
this.mPackageInfo.instrumentation = instrumentations.values().toArray(new InstrumentationInfo[instrumentations.size()]);
// Cache activities
Map<ComponentName, ActivityInfo> activityInfos = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity activity : this.mPackage.activities) {
activityInfos.put(activity.getComponentName(), activity.info);
}
this.mActivityInfos = Collections.unmodifiableMap(activityInfos);
this.mPackageInfo.activities = activityInfos.values().toArray(new ActivityInfo[activityInfos.size()]);
// Cache services
Map<ComponentName, ServiceInfo> serviceInfos = new HashMap<ComponentName, ServiceInfo>();
for (PackageParser.Service service : this.mPackage.services) {
serviceInfos.put(service.getComponentName(), service.info);
}
this.mServiceInfos = Collections.unmodifiableMap(serviceInfos);
this.mPackageInfo.services = serviceInfos.values().toArray(new ServiceInfo[serviceInfos.size()]);
// Cache providers
Map<String, ProviderInfo> providers = new HashMap<String, ProviderInfo>();
Map<ComponentName, ProviderInfo> providerInfos = new HashMap<ComponentName, ProviderInfo>();
for (PackageParser.Provider provider : this.mPackage.providers) {
providers.put(provider.info.authority, provider.info);
providerInfos.put(provider.getComponentName(), provider.info);
}
this.mProviders = Collections.unmodifiableMap(providers);
this.mProviderInfos = Collections.unmodifiableMap(providerInfos);
this.mPackageInfo.providers = providerInfos.values().toArray(new ProviderInfo[providerInfos.size()]);
// Register broadcast receivers dynamically
Map<ComponentName, ActivityInfo> receivers = new HashMap<ComponentName, ActivityInfo>();
for (PackageParser.Activity receiver : this.mPackage.receivers) {
receivers.put(receiver.getComponentName(), receiver.info);
BroadcastReceiver br = BroadcastReceiver.class.cast(getClassLoader().loadClass(receiver.getComponentName().getClassName()).newInstance());
for (PackageParser.ActivityIntentInfo aii : receiver.intents) {
//靜態(tài)廣播轉動態(tài)注冊
this.mHostContext.registerReceiver(br, aii);
}
}
this.mReceiverInfos = Collections.unmodifiableMap(receivers);
this.mPackageInfo.receivers = receivers.values().toArray(new ActivityInfo[receivers.size()]);
}
private static ClassLoader createClassLoader(Context context, File apk, File libsDir, ClassLoader parent) {
File dexOutputDir = context.getDir(Constants.OPTIMIZE_DIR, Context.MODE_PRIVATE);
String dexOutputPath = dexOutputDir.getAbsolutePath();
//雙親委托機制加載,parent 為宿主的 ClassLoader,這樣可以讓插件模塊調起宿主工程的 Activity
//DexClassLoader: 可以從包含classes.dex的jar或者apk中,加載類,一般用于執(zhí)行動態(tài)加載
DexClassLoader loader = new DexClassLoader(apk.getAbsolutePath(), dexOutputPath, libsDir.getAbsolutePath(), parent);
if (Constants.COMBINE_CLASSLOADER) {
try {
DexUtil.insertDex(loader);
} catch (Exception e) {
e.printStackTrace();
}
}
return loader;
}
private static AssetManager createAssetManager(Context context, File apk) {
try {
AssetManager am = AssetManager.class.newInstance();
ReflectUtil.invoke(AssetManager.class, am, "addAssetPath", apk.getAbsolutePath());
return am;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private static Resources createResources(Context context, File apk) {
if (Constants.COMBINE_RESOURCES) {
//將插件 Assetmanager 的路徑傳進去,適用于插件資源合并到宿主里面去的情況,
// 最后插件通過宿主的 Resources 對象去訪問宿主的資源
Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
//這里是宿主的 context
ResourcesManager.hookResources(context, resources);
return resources;
} else {
Resources hostResources = context.getResources();
//獲取插件 apk 的 AssetManager
AssetManager assetManager = createAssetManager(context, apk);
//適用于資源獨立,返回插件獨立的 Resources 對象,不與宿主有關系,無法訪問到宿主的資源
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
//return context.getResources();
}
}
private static ResolveInfo chooseBestActivity(Intent intent, String s, int flags, List<ResolveInfo> query) {
return query.get(0);
}
可以看到,LoadedPlugin 負責生成 ClassLoader、Resources 對象來加載插件 apk 的Activity 和資源。
- 3.正常調用插件的 Activity
這個是真正難的地方,這里需要結合 Android 啟動一個 Activity 的過程來看。
Intent intent = new Intent();
intent.setClassName("com.example.moduleone", "com.example.moduleone.ModuleOneMainActivity");
startActivity(intent);
當調用 startActivity(intent); 時,其實是調用 startActivityForResult(intent, -1);然后就走到了:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
//轉到了 Instrumentation 啟動 Activity
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
可以看到我們之前的重點關注對象 Instrumentation 出現(xiàn)了!??!
一起看看 Instrument 如何啟動一個 Activity。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
//當 intent.getComponent() 為空時,根據(jù) intent 的 action,data,category 等
// 去已加載的 plugin 中匹配到確定的 Activity
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
//這句 Log 信息可以判斷 Loadplugin 對象是否已經(jīng)加載成功
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
//欺上瞞下,根據(jù)需要是否替換,替換的話就根據(jù) launchode 替換插件的 Activity 為插件 Manifest.xml 占坑的 Activity
this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
}
ActivityResult result = realExecStartActivity(who, contextThread, token, target,
intent, requestCode, options);
return result;
}
private ActivityResult realExecStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
ActivityResult result = null;
try {
Class[] parameterTypes = {Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class,
int.class, Bundle.class};
//調用宿主的 Instrument 對象使用 ActivityManagerProxy 和 AMS 進行跨進程通信
result = (ActivityResult)ReflectUtil.invoke(Instrumentation.class, mBase,
"execStartActivity", parameterTypes,
who, contextThread, token, target, intent, requestCode, options);
} catch (Exception e) {
if (e.getCause() instanceof ActivityNotFoundException) {
throw (ActivityNotFoundException) e.getCause();
}
e.printStackTrace();
}
return result;
}
重點看一下 this.mPluginManager.getComponentsHandler().markIntentIfNeeded(intent);
public void markIntentIfNeeded(Intent intent) {
if (intent.getComponent() == null) {
return;
}
String targetPackageName = intent.getComponent().getPackageName();
String targetClassName = intent.getComponent().getClassName();
// 判斷如果啟動的是插件中類,則將啟動的包名和Activity類名存到了intent中,可以看到這里存儲明顯是為了后面恢復用的
if (!targetPackageName.equals(mContext.getPackageName()) && mPluginManager.getLoadedPlugin(targetPackageName) != null) {
intent.putExtra(Constants.KEY_IS_PLUGIN, true);
intent.putExtra(Constants.KEY_TARGET_PACKAGE, targetPackageName);
intent.putExtra(Constants.KEY_TARGET_ACTIVITY, targetClassName);
dispatchStubActivity(intent);
}
}
private void dispatchStubActivity(Intent intent) {
ComponentName component = intent.getComponent();
String targetClassName = intent.getComponent().getClassName();
LoadedPlugin loadedPlugin = mPluginManager.getLoadedPlugin(intent);
ActivityInfo info = loadedPlugin.getActivityInfo(component);
if (info == null) {
throw new RuntimeException("can not find " + component);
}
int launchMode = info.launchMode;
Resources.Theme themeObj = loadedPlugin.getResources().newTheme();
themeObj.applyStyle(info.theme, true);
String stubActivity = mStubActivityInfo.getStubActivity(targetClassName, launchMode, themeObj);
Log.i(TAG, String.format("dispatchStubActivity,[%s -> %s]", targetClassName, stubActivity));
//intent通過setClassName替換啟動的Activity為占坑Activity
intent.setClassName(mContext, stubActivity);
}
//Manifest.xml 寫好的占坑 Activity
public static final String STUB_ACTIVITY_STANDARD = "%s.A$%d";
public static final String STUB_ACTIVITY_SINGLETOP = "%s.B$%d";
public static final String STUB_ACTIVITY_SINGLETASK = "%s.C$%d";
public static final String STUB_ACTIVITY_SINGLEINSTANCE = "%s.D$%d";
public String getStubActivity(String className, int launchMode, Theme theme) {
String stubActivity= mCachedStubActivity.get(className);
if (stubActivity != null) {
return stubActivity;
}
TypedArray array = theme.obtainStyledAttributes(new int[]{
android.R.attr.windowIsTranslucent,
android.R.attr.windowBackground
});
boolean windowIsTranslucent = array.getBoolean(0, false);
array.recycle();
if (Constants.DEBUG) {
Log.d("LogUtils_StubActivity", "getStubActivity, is transparent theme ? " + windowIsTranslucent);
}
//這里只是為了騙過系統(tǒng),Activity 已在 Manifest.xml 中注冊,所以這里的包名以及類的全名必須
//和 Manifest.xml 占坑的相同
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
switch (launchMode) {
case ActivityInfo.LAUNCH_MULTIPLE: {
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, usedStandardStubActivity);
if (windowIsTranslucent) {
stubActivity = String.format(STUB_ACTIVITY_STANDARD, corePackage, 2);
}
break;
}
case ActivityInfo.LAUNCH_SINGLE_TOP: {
usedSingleTopStubActivity = usedSingleTopStubActivity % MAX_COUNT_SINGLETOP + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETOP, corePackage, usedSingleTopStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_TASK: {
usedSingleTaskStubActivity = usedSingleTaskStubActivity % MAX_COUNT_SINGLETASK + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLETASK, corePackage, usedSingleTaskStubActivity);
break;
}
case ActivityInfo.LAUNCH_SINGLE_INSTANCE: {
usedSingleInstanceStubActivity = usedSingleInstanceStubActivity % MAX_COUNT_SINGLEINSTANCE + 1;
stubActivity = String.format(STUB_ACTIVITY_SINGLEINSTANCE, corePackage, usedSingleInstanceStubActivity);
break;
}
default:break;
}
mCachedStubActivity.put(className, stubActivity);
return stubActivity;
}

可以看到,本來啟動未在 Manifest.xml 中注冊的插件 Activity 應該會報錯的,但是經(jīng)過 VAInstrumentation 這么一個欺上瞞下,就做到了欺騙系統(tǒng)加載插件 Activity。
但是這里只是做到了一半,因為換了要啟動的 Activity,欺騙過了 AMS 之后,最終要啟動的不可能是占坑 Activity,還應該是我們的啟動的目標 Activity 呀。
接下來的就要看 Activity 的啟動過程了。
Instrumentation.execStartActivity() 會調用ActivityManagerService.startActivity() 來啟動 Activity (IPC 過程)。這個過程 AMS 會對要啟動的 Activity 進行 Activity 棧還有其他的處理。
APP 進程客戶端:ActivityManagerProxy =====> Binder驅動 =====> ActivityManagerService:AMS 服務端
在 AMS 處理完啟動 Activity 后,會調用:app.thread.scheduleLaunchActivity() (同樣是 IPC 過程),這里的 thread 對應為 server 端,其實就是我們 APP 進程的 ActivityThread 中的 ApplicationThread 對象,而此時的 AMS 的 IApplicationThread 作為客戶端,所以是 AMS 客戶端調用 APP 進程服務端的 ApplicationThread.scheduleLaunchActivity() 方法,這樣啟動 Activity 的操作又回到了 APP 進程。
APP 進程服務端:ApplicationThread <===== Binder驅動 <===== ApplicationThreadProxy:AMS 客戶端
這時會在 ApplicationThread 內部會調用 mH 類(類 H 是 ActivityThread 的內部類,并繼承了 Handler)的 sendMessage() 方法,傳遞的標識為 H.LAUNCH_ACTIVITY,進入調用到 ActivityThread 的 handleLaunchActivity() 方法 :
ActivityThread.handleLaunchActivity() -> ActivityThread.performLaunchActivity() -> mInstrumentation.newActivity() (通過 ClassLoader 實例化 Activity 對象)-> Instrumentation.callActivityOnCreate() 調用 Activity 的 onCreate()。
至此,我們就知道了 Activity 的啟動流程,那么應用到滴滴的插件化技術,我們可以知道,在 AMS 處理完畢后,會回調到 VAInstrumentation.newActivity()。我們重點看看這個方法:
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
//這個是宿主的 ClassLoader,而我們替換之后的Activity是插件 Manifest.xml 中聲明的 Activity,
// 實際上并沒有這樣的類,所以如果要加載的是插件的 占坑 Activity 一定會拋異常,加載宿主的就不會
cl.loadClass(className);
} catch (ClassNotFoundException e) {
//取出目標啟動 Activity 的包名和類名
ComponentName component = PluginUtil.getComponent(intent);
//根據(jù)包名獲取加載插件的 ClassLoader
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
//取出要啟動的插件目標 Activity 類名
String targetClassName = component.getClassName();
//這里的 className 就是插件占坑的 Activity 名字
Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));
if (plugin != null) {
//這里傳入的是構造的插件的 ClassLoader,所以能加載插件的 Activity
Activity activity = mBase.newActivity(plugin.getClassLoader(), targetClassName, intent);
activity.setIntent(intent);
try {
// for 4.1+
ReflectUtil.setField(ContextThemeWrapper.class, activity, "mResources", plugin.getResources());
} catch (Exception ignored) {
// ignored.
}
return activity;
}
}
//如果是啟動宿主的 Activity,直接就跳到了這一步,中間的過程不會進入,intent 的內容也不會被替換
return mBase.newActivity(cl, className, intent);
}
可以看到,滴滴插件化也是通過在 Instrumentation 里面進行還原目標 Activity 的加載。這個做法侵入性比較小,所以兼容性好一點,感覺也比較優(yōu)雅。
接下來我們看最后一步:Instrumentation.callActivityOnCreate()。這一步直接關聯(lián)到了傳說中的 Activity.onCreate()。
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
final Intent intent = activity.getIntent();
//在「欺上瞞下」的時候進行標記為 true,否則就是啟動 宿主的Activity,跳過資源替換的步驟
if (PluginUtil.isIntentFromPlugin(intent)) {
//這個 base 是宿主的 Application
Context base = activity.getBaseContext();
try {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
//由于 ContextWrapper 和 Resources 資源加載相關,所以必須替換為插件的 Context 以及插件的 Resources
//詳細見 LoadedPlugin#createResources()
ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
//mBase 就是 pluginContext 對象,里面包裝有宿主的 Application
ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
//之前在 LoadPlugin 對象的時候,就已經(jīng)創(chuàng)建了插件的 Application,進行替換
ReflectUtil.setField(Activity.class, activity, "mApplication", plugin.getApplication());
ReflectUtil.setFieldNoException(ContextThemeWrapper.class, activity, "mBase", plugin.getPluginContext());
// set screenOrientation
ActivityInfo activityInfo = plugin.getActivityInfo(PluginUtil.getComponent(intent));
if (activityInfo.screenOrientation != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED) {
activity.setRequestedOrientation(activityInfo.screenOrientation);
}
} catch (Exception e) {
e.printStackTrace();
}
int theme = PluginUtil.getTheme(mPluginManager.getHostContext(), intent);
if (theme != 0) {
activity.setTheme(theme);
}
}
mBase.callActivityOnCreate(activity, icicle);
}
//LoadPlugin#createResources()
private static Resources createResources(Context context, File apk) {
if (Constants.COMBINE_RESOURCES) {
//將插件 Assetmanager 的路徑傳進去,適用于插件資源合并到宿主里面去的情況,
// 最后插件通過宿主的 Resources 對象去訪問宿主的資源
Resources resources = ResourcesManager.createResources(context, apk.getAbsolutePath());
//這里是宿主的 context
ResourcesManager.hookResources(context, resources);
return resources;
} else {
Resources hostResources = context.getResources();
//獲取插件 apk 的 AssetManager
AssetManager assetManager = createAssetManager(context, apk);
//適用于資源獨立,返回插件獨立的 Resources 對象,不與宿主有關系,無法訪問到宿主的資源
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
//return context.getResources();
}
}
可以看到,對于要啟動的插件 Activity,這里修改了插件的 mResources、mBase(Context)、mApplication 對象,以及設置一些可動態(tài)設置的屬性,這里僅設置了屏幕方向。
注意啦,這里將 mBase 替換為 PluginContext,可以修改 Resources、AssetManager 以及攔截相當多的操作。
原本 Activity 的部分 get 操作
# ContextWrapper
@Override
public AssetManager getAssets() {
return mBase.getAssets();
}
@Override
public Resources getResources()
{
return mBase.getResources();
}
@Override
public PackageManager getPackageManager() {
return mBase.getPackageManager();
}
@Override
public ContentResolver getContentResolver() {
return mBase.getContentResolver();
}
替換之后:
# PluginContext
@Override
public Resources getResources() {
return this.mPlugin.getResources();
}
@Override
public AssetManager getAssets() {
return this.mPlugin.getAssets();
}
@Override
public ContentResolver getContentResolver() {
return new PluginContentResolver(getHostContext());
}
這樣可以很巧妙地對資源的加載進行攔截,甚至攔截和替換啟動的插件 Service 等。
到這里,我們的插件 Activity 就可以正常啟動啦。