我們這一節(jié)來分析一下滴滴插件化是如何啟動(dòng)插件的 Activity 的伺通。
一、使用
- 1.配置宿主工程的 Module#build.gradle
- 2.配置插件工程的 Module#build.gradle
3.將插件工程使用
assemblePlugin
任務(wù)打包出一個(gè) apk 文件逢享,放進(jìn)宿主工程的 assets 目錄中4.在宿主的 Application 中進(jìn)行滴滴插件化工具的初始化
- 5.在需要用到插件 apk 的資源的地方進(jìn)行插件加載
- 6.正常調(diào)用插件 apk 的 Activity罐监、Service 等資源
二、插件化加載 Activity 的過程解析
- 1.插件初始化
PluginManager.getInstance(context).init();
我們仔細(xì)看看這里做了什么工作:
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();
//關(guān)鍵操作
this.hookInstrumentationAndHandler();
//關(guān)鍵操作
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 進(jìn) 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 初始化的時(shí)候回保存宿主的 Application context,之后會(huì)反射獲取 ActivityThread 的 Instrumentation 對(duì)象,并且生成一個(gè) VAInstrumentation 對(duì)象侧但,再反射設(shè)置進(jìn) ActivityThread 中矢空。
這一步很重要,因?yàn)?Instrumentation 涉及到加載 Activity 的過程禀横,插件化能對(duì) Android 系統(tǒng)進(jìn)行欺上瞞下以及調(diào)用宿主的 Activity 就是因?yàn)?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());
}
//調(diào)用 PackageParser.parsePackage 解析 apk,得到的信息封裝進(jìn) loadedPlugin 對(duì)象
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 的時(shí)候會(huì)用到
mCallbacks.get(i).onAddedLoadedPlugin(plugin);
}
}
//構(gòu)造插件的 Application,并調(diào)用插件 Application 的 onCreate()
plugin.invokeApplication();
} else {
throw new RuntimeException("Can't load plugin which is invalid: " + apk.getAbsolutePath());
}
}
可以看到,這里創(chuàng)建了一個(gè) LoadedPlugin 對(duì)象柏锄,這個(gè) LoadedPlugin 對(duì)象很重要酿箭,用來解析插件 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)廣播轉(zhuǎn)動(dòng)態(tài)注冊(cè)
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();
//雙親委托機(jī)制加載,parent 為宿主的 ClassLoader缭嫡,這樣可以讓插件模塊調(diào)起宿主工程的 Activity
//DexClassLoader: 可以從包含classes.dex的jar或者apk中缔御,加載類,一般用于執(zhí)行動(dòng)態(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 的路徑傳進(jìn)去妇蛀,適用于插件資源合并到宿主里面去的情況耕突,
// 最后插件通過宿主的 Resources 對(duì)象去訪問宿主的資源
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);
//適用于資源獨(dú)立,返回插件獨(dú)立的 Resources 對(duì)象评架,不與宿主有關(guān)系有勾,無法訪問到宿主的資源
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 負(fù)責(zé)生成 ClassLoader古程、Resources 對(duì)象來加載插件 apk 的Activity 和資源蔼卡。
- 3.正常調(diào)用插件的 Activity
這個(gè)是真正難的地方,這里需要結(jié)合 Android 啟動(dòng)一個(gè) Activity 的過程來看挣磨。
Intent intent = new Intent();
intent.setClassName("com.example.moduleone", "com.example.moduleone.ModuleOneMainActivity");
startActivity(intent);
當(dāng)調(diào)用 startActivity(intent);
時(shí)雇逞,其實(shí)是調(diào)用 startActivityForResult(intent, -1);
然后就走到了:
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
if (mParent == null) {
options = transferSpringboardActivityOptions(options);
//轉(zhuǎn)到了 Instrumentation 啟動(dòng) 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);
}
}
}
可以看到我們之前的重點(diǎn)關(guān)注對(duì)象 Instrumentation 出現(xiàn)了!W氯埂塘砸!
一起看看 Instrument 如何啟動(dòng)一個(gè) Activity。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
//當(dāng) intent.getComponent() 為空時(shí)晤锥,根據(jù) intent 的 action掉蔬,data,category 等
// 去已加載的 plugin 中匹配到確定的 Activity
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
//這句 Log 信息可以判斷 Loadplugin 對(duì)象是否已經(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};
//調(diào)用宿主的 Instrument 對(duì)象使用 ActivityManagerProxy 和 AMS 進(jìn)行跨進(jìn)程通信
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;
}
重點(diǎn)看一下 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();
// 判斷如果啟動(dòng)的是插件中類女轿,則將啟動(dòng)的包名和Activity類名存到了intent中,可以看到這里存儲(chǔ)明顯是為了后面恢復(fù)用的
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替換啟動(dòng)的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 中注冊(cè)蛉迹,所以這里的包名以及類的全名必須
//和 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;
}
可以看到,本來啟動(dòng)未在 Manifest.xml 中注冊(cè)的插件 Activity 應(yīng)該會(huì)報(bào)錯(cuò)的放妈,但是經(jīng)過 VAInstrumentation 這么一個(gè)欺上瞞下北救,就做到了欺騙系統(tǒng)加載插件 Activity。
但是這里只是做到了一半芜抒,因?yàn)閾Q了要啟動(dòng)的 Activity珍策,欺騙過了 AMS 之后,最終要啟動(dòng)的不可能是占坑 Activity宅倒,還應(yīng)該是我們的啟動(dòng)的目標(biāo) Activity 呀攘宙。
接下來的就要看 Activity 的啟動(dòng)過程了。
Instrumentation.execStartActivity() 會(huì)調(diào)用ActivityManagerService.startActivity() 來啟動(dòng) Activity (IPC 過程)。這個(gè)過程 AMS 會(huì)對(duì)要啟動(dòng)的 Activity 進(jìn)行 Activity 棧還有其他的處理模聋。
APP 進(jìn)程客戶端:ActivityManagerProxy =====> Binder驅(qū)動(dòng) =====> ActivityManagerService:AMS 服務(wù)端
在 AMS 處理完啟動(dòng) Activity 后,會(huì)調(diào)用:app.thread.scheduleLaunchActivity() (同樣是 IPC 過程)
唠亚,這里的 thread 對(duì)應(yīng)為 server 端链方,其實(shí)就是我們 APP 進(jìn)程的 ActivityThread 中的 ApplicationThread 對(duì)象,而此時(shí)的 AMS 的 IApplicationThread 作為客戶端灶搜,所以是 AMS 客戶端調(diào)用 APP 進(jìn)程服務(wù)端的 ApplicationThread.scheduleLaunchActivity()
方法祟蚀,這樣啟動(dòng) Activity 的操作又回到了 APP 進(jìn)程。
APP 進(jìn)程服務(wù)端:ApplicationThread <===== Binder驅(qū)動(dòng) <===== ApplicationThreadProxy:AMS 客戶端
這時(shí)會(huì)在 ApplicationThread 內(nèi)部會(huì)調(diào)用 mH 類(類 H 是 ActivityThread 的內(nèi)部類割卖,并繼承了 Handler)的 sendMessage() 方法前酿,傳遞的標(biāo)識(shí)為 H.LAUNCH_ACTIVITY,進(jìn)入調(diào)用到 ActivityThread 的 handleLaunchActivity() 方法 :
ActivityThread.handleLaunchActivity() -> ActivityThread.performLaunchActivity() -> mInstrumentation.newActivity() (通過 ClassLoader 實(shí)例化 Activity 對(duì)象)-> Instrumentation.callActivityOnCreate() 調(diào)用 Activity 的 onCreate()鹏溯。
至此罢维,我們就知道了 Activity 的啟動(dòng)流程,那么應(yīng)用到滴滴的插件化技術(shù)丙挽,我們可以知道肺孵,在 AMS 處理完畢后,會(huì)回調(diào)到 VAInstrumentation.newActivity()颜阐。我們重點(diǎn)看看這個(gè)方法:
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws InstantiationException, IllegalAccessException, ClassNotFoundException {
try {
//這個(gè)是宿主的 ClassLoader,而我們替換之后的Activity是插件 Manifest.xml 中聲明的 Activity平窘,
// 實(shí)際上并沒有這樣的類,所以如果要加載的是插件的 占坑 Activity 一定會(huì)拋異常凳怨,加載宿主的就不會(huì)
cl.loadClass(className);
} catch (ClassNotFoundException e) {
//取出目標(biāo)啟動(dòng) Activity 的包名和類名
ComponentName component = PluginUtil.getComponent(intent);
//根據(jù)包名獲取加載插件的 ClassLoader
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(component);
//取出要啟動(dòng)的插件目標(biāo) Activity 類名
String targetClassName = component.getClassName();
//這里的 className 就是插件占坑的 Activity 名字
Log.i(TAG, String.format("newActivity[%s : %s/%s]", className, component.getPackageName(), targetClassName));
if (plugin != null) {
//這里傳入的是構(gòu)造的插件的 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;
}
}
//如果是啟動(dòng)宿主的 Activity,直接就跳到了這一步肤舞,中間的過程不會(huì)進(jìn)入紫新,intent 的內(nèi)容也不會(huì)被替換
return mBase.newActivity(cl, className, intent);
}
可以看到,滴滴插件化也是通過在 Instrumentation 里面進(jìn)行還原目標(biāo) Activity 的加載李剖。這個(gè)做法侵入性比較小弊琴,所以兼容性好一點(diǎn),感覺也比較優(yōu)雅杖爽。
接下來我們看最后一步:Instrumentation.callActivityOnCreate()敲董。這一步直接關(guān)聯(lián)到了傳說中的 Activity.onCreate()。
@Override
public void callActivityOnCreate(Activity activity, Bundle icicle) {
final Intent intent = activity.getIntent();
//在「欺上瞞下」的時(shí)候進(jìn)行標(biāo)記為 true慰安,否則就是啟動(dòng) 宿主的Activity腋寨,跳過資源替換的步驟
if (PluginUtil.isIntentFromPlugin(intent)) {
//這個(gè) base 是宿主的 Application
Context base = activity.getBaseContext();
try {
LoadedPlugin plugin = this.mPluginManager.getLoadedPlugin(intent);
//由于 ContextWrapper 和 Resources 資源加載相關(guān),所以必須替換為插件的 Context 以及插件的 Resources
//詳細(xì)見 LoadedPlugin#createResources()
ReflectUtil.setField(base.getClass(), base, "mResources", plugin.getResources());
//mBase 就是 pluginContext 對(duì)象化焕,里面包裝有宿主的 Application
ReflectUtil.setField(ContextWrapper.class, activity, "mBase", plugin.getPluginContext());
//之前在 LoadPlugin 對(duì)象的時(shí)候萄窜,就已經(jīng)創(chuàng)建了插件的 Application,進(jìn)行替換
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 的路徑傳進(jìn)去,適用于插件資源合并到宿主里面去的情況查刻,
// 最后插件通過宿主的 Resources 對(duì)象去訪問宿主的資源
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);
//適用于資源獨(dú)立键兜,返回插件獨(dú)立的 Resources 對(duì)象,不與宿主有關(guān)系穗泵,無法訪問到宿主的資源
return new Resources(assetManager, hostResources.getDisplayMetrics(), hostResources.getConfiguration());
//return context.getResources();
}
}
可以看到普气,對(duì)于要啟動(dòng)的插件 Activity,這里修改了插件的 mResources佃延、mBase(Context)现诀、mApplication 對(duì)象,以及設(shè)置一些可動(dòng)態(tài)設(shè)置的屬性履肃,這里僅設(shè)置了屏幕方向仔沿。
注意啦,這里將 mBase 替換為 PluginContext尺棋,可以修改 Resources封锉、AssetManager 以及攔截相當(dāng)多的操作。
原本 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());
}
這樣可以很巧妙地對(duì)資源的加載進(jìn)行攔截膘螟,甚至攔截和替換啟動(dòng)的插件 Service 等烘浦。
到這里,我們的插件 Activity 就可以正常啟動(dòng)啦萍鲸。