我们这一节来分析一下滴滴插件化是如何启动插件的 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 系统进行欺上瞒下以及调用宿主的 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());
}
}
可以看到,这里创建了一个 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);
//创建 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) {
//静态广播转动态注册
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中,加载类,一般用于执行动态加载
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 出现了!!!
一起看看 Instrument 如何启动一个 Activity。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
//当 intent.getComponent() 为空时,根据 intent 的 action,data,category 等
// 去已加载的 plugin 中匹配到确定的 Activity
mPluginManager.getComponentsHandler().transformIntentToExplicitAsNeeded(intent);
// null component is an implicitly intent
if (intent.getComponent() != null) {
//这句 Log 信息可以判断 Loadplugin 对象是否已经加载成功
Log.i(TAG, String.format("execStartActivity[%s : %s]", intent.getComponent().getPackageName(),
intent.getComponent().getClassName()));
//欺上瞒下,根据需要是否替换,替换的话就根据 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);
}
//这里只是为了骗过系统,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 应该会报错的,但是经过 VAInstrumentation 这么一个欺上瞒下,就做到了欺骗系统加载插件 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);
//根据包名获取加载插件的 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 的加载。这个做法侵入性比较小,所以兼容性好一点,感觉也比较优雅。
接下来我们看最后一步:Instrumentation.callActivityOnCreate()。这一步直接关联到了传说中的 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 对象的时候,就已经创建了插件的 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 对象,以及设置一些可动态设置的属性,这里仅设置了屏幕方向。
注意啦,这里将 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 就可以正常启动啦。