上一篇分析中我们分析了Replugin框架Host端的一些核心概念,还梳理了Activity
启动的流程,但是有两个重要部分没有提及或者详细讲述,那就是Plugin的加载过程,Plugin端的初始化,所以本篇会重点看看这两个方面的内容。
目录
- Plugin加载的详细过程
- Plugin端初始化
- Plugin中启动
Activity
Plugin加载的详细过程
要讲解这个过程,我们得从Plugin.doLoad
函数开始,在上一篇分析中其实已经提到过,只是当时并没有分析它的细节。这个函数做了三件事情:
- 释放插件文件到相应的目录,比如so库,dex文件
- 加载dex文件,比如组件信息,组件属性,资源等
- 要运行Plugin,还需要初始化Plugin的运行环境
释放文件这一步很简单,就是将APK包打开以后将相应的文件放到不同的目录中,有兴趣的同学可以取跟以下代码,这里就不赘述了。
来看看dex文件的加载,这是通过Loader.loadDex
函数实现的。这个方法比较长,我们拆开来看。
第一步,获取PackageInfo
并缓存插件相关的信息,比如组件,组件属性,资源等,下次就可以直接从缓存中读取。
if (mPackageInfo == null) {
// 通过PackageManager获取PackageInfo
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
......
// 添加针对SO库的加载
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// 缓存表: pkgName -> pluginName
synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
}
// 缓存表: pluginName -> fileName
synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
}
// 缓存表: fileName -> PackageInfo
synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
}
}
第二步,解析组件信息,注册Plugin的Manifest中声明的BroadcastReceiver。
if (mComponents == null) {
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 动态注册插件中声明的 receiver
regReceivers();
// 缓存表Components
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
......
}
这一步用到了ComponentList
类,在它的构造函数中完成了对Plugin的Manifest文件的解析,调用的是Minifestparser
类。有兴趣的同学可以跟一跟这里的代码,并不复杂。读取到Manifest文件以后,使用ManifestParser.INS.parse
函数调用XmlHandler
解析,把解析以后的信息缓存到ManifestParser
中。
public void parse(PluginInfo pli, String manifestStr) {
XmlHandler handler = parseManifest(manifestStr);
Map<String, List<IntentFilter>> activityFilterMap = new HashMap<>();
putToMap(mPluginActivityInfoMap, activityFilterMap, pli);
parseComponent(pli.getName(), activityFilterMap, handler.getActivities(), mActivityActionPluginsMap);
Map<String, List<IntentFilter>> serviceFilterMap = new HashMap<>();
putToMap(mPluginServiceInfoMap, serviceFilterMap, pli);
parseComponent(pli.getName(), serviceFilterMap, handler.getServices(), mServiceActionPluginsMap);
Map<String, List<IntentFilter>> receiverFilterMap = new HashMap<>();
putToMap(mPluginReceiverInfoMap, receiverFilterMap, pli);
parseComponent(pli.getName(), receiverFilterMap, handler.getReceivers(), null);
}
第三步,获取Plugin的资源,先查找缓存,如果找不到就通过PackageManager
创建Resources
对象,但这里做了一个多余的赋值操作是为了修复一个BUG,不必在意。最后将资源对象缓存下来。
mPkgResources = Plugin.queryCachedResources(mPath);
if (mPkgResources == null) {
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的话,防止与Instant Run冲突,资源重新New一个
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
return false;
}
// 缓存Resources
synchronized (Plugin.FILENAME_2_RESOURCES) {
Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
}
}
第四步,注意咯,这里就是创建Plugin的PluginDexClassLoader
的地方,将它缓存起来,不必每次都创建一个新的。
if (mClassLoader == null) {
String out = mPluginObj.mInfo.getDexParentDir().getPath();
......
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
// 创建PluginDexClassLoader对象
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPath, out, soDir, parent);
......
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader)); // 缓存ClassLoader
}
}
第五步, 为Plugin创建一个全局的PluginContext
,并用上面创建的ClassLoader
以及Resources
作为参数。而这个PluginContext
对象会被赋值给Plugin的Application
对象(后面会讲到)。其实每一个Plugin的Activity
都会创建一个PluginContext
对象,并使用相同的ClassLoader
和Resources
,因此在Plugin中就可以加载相关的类和使用资源了,跟原生程序一样。
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
到这里,Plugin的加载就算是全部完成了!
Plugin端初始化
Dex文件加载完成以后,要运行Plugin还需要初始化Plugin的运行环境相关的类,在Plugin.doLoad
的最后阶段调用了loadEntryLocked
函数,这个函数负责初始化Plugin的运行环境。
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
......
} else {
......
} else if (mLoader.loadEntryMethod3()) { // 通过反射拿到Entry的create函数
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
return false;
}
}
return true;
}
Loader.loadEntryMethod3
通过反射将Plugin中的Entry
类的create
函数对象得到并保存在mCreateMethod2
中。注意这里的mClassLoader
是插件的PluginDexClassLoader
,所以才能得到插件中的类。
final boolean loadEntryMethod3() {
try {
String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class<?> c = mClassLoader.loadClass(className);
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
}
return mCreateMethod2 != null;
}
接着Loader.invoke2
函数会用反射的方式调用上面拿到的create
函数。
final boolean invoke2(PluginCommImpl x) {
try {
IBinder manager = null;
final ClassLoader classLoader = getClass().getClassLoader();
IBinder b = (IBinder) mCreateMethod2.invoke(null, mPkgContext, classLoader, manager); // 调用create函数
......
mBinderPlugin = new ProxyPlugin(b);
mPlugin = mBinderPlugin;
} catch (Throwable e) {
return false;
}
return true;
}
注意,这里我们将会进入到Plugin端的代码中,来看看Entry.create
做了什么。这里有一个参数是ClassLoader
,这个ClassLoader
是Host中的RepluginClassLoader
,有了它,Plugin才能找到Host当中的类并调用这些类的方法。
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
RePluginFramework.init(cl); // 初始化一些类,这些类用于调用Host中类的方法
RePluginEnv.init(context, cl, manager);
return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}
这里RepluginFramework.init
初始化了一些代理类,这些代理类可以在Plugin中调用Host中的函数。
private static boolean initLocked(ClassLoader cl) {
......
try {
RePluginInternal.ProxyRePluginInternalVar.initLocked(cl);
RePlugin.ProxyRePluginVar.initLocked(cl);
PluginLocalBroadcastManager.ProxyLocalBroadcastManagerVar.initLocked(cl);
PluginProviderClient.ProxyRePluginProviderClientVar.initLocked(cl);
PluginServiceClient.ProxyRePluginServiceClientVar.initLocked(cl);
IPC.ProxyIPCVar.initLocked(cl);
mHostInitialized = true;
} catch (final Throwable e) {
}
return mHostInitialized;
}
我们拿RePlugin.ProxyRePluginVar.initLocked
作为例子来讲解。这里还是通过反射,在Plugin中去获取Host中Replugin
的相关方法并保存在一系列的MethodInvoker
对象中,比如Replugin.install
,Replugin.preload
,Replugin.startActivity
等。
static void initLocked(final ClassLoader classLoader) {
// 初始化Replugin的相关方法
final String rePlugin = "com.qihoo360.replugin.RePlugin";
install = new MethodInvoker(classLoader, rePlugin, "install", new Class<?>[]{String.class});
preload = new MethodInvoker(classLoader, rePlugin, "preload", new Class<?>[]{String.class});
......
startActivity = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class});
startActivity2 = new MethodInvoker(classLoader, rePlugin, "startActivity", new Class<?>[]{Context.class, Intent.class, String.class, String.class});
......
}
}
当我们在Plugin中使用Replugin.startActivity
时,实际上是通过MethodInvoker.call
函数去执行Host中Replugin
对应的startActivity
函数。所以Plugin中的Replugin类的实现就只是一个空壳代理而已,你可以看到它的实现如下:
public static boolean startActivity(Context context, Intent intent) {
if (!RePluginFramework.mHostInitialized) {
return false;
}
try { // 用反射的方式调用Host中Replugin的startActivity函数来启动Activity
Object obj = ProxyRePluginVar.startActivity.call(null, context, intent);
if (obj != null) {
return (Boolean) obj;
}
} catch (Exception e) {
if (LogDebug.LOG) {
e.printStackTrace();
}
}
return false;
}
看到这里,你就跟上一篇中 插件Activity启动流程 这一节连起来啦!
插件中启动Activity
Android中Activity
和Context
都有startActivity
函数,为了在插件中能正常启动Activity
,我们要将这些函数都屏蔽掉,转而使用Replugin
提供的startActivity
。
为什么一定要用Replugin提供startActivity方法呢?因为我们插件的组件在Host的Manifest中是没有声明的,
只能通过坑位来启动,而启动坑位的动作只能在Host中完成。
Replugin
是怎么做的呢?来看看吧。
将官方Demo中的插件APK反编译出来,会发现所有继承了Activity
的类都被强制修改成继承PluginActivity
,这个工作是由replugin-plugin-gradle
在编译阶段完成的。
实际上在
replugin-plugin-lib
中的com.qihoo360.replugin.loader.a
包中,你可以找到更多的Activity关的类。
首先PluginActivity
重写了startActivity
函数,并在其中通过反射调用Host中的Replugin.startActivity
函数,这样就能做到在插件中启动Activity
了。这里的反射调用指的就是调用前面讲过的RePlugin.ProxyRePluginVar
类在初始化时得到的函数。
public void startActivity(Intent intent) {
if (RePluginInternal.startActivity(this, intent)) {
return;
}
super.startActivity(intent);
}
然后,PluginActivity
重写了attachBaseContext
函数,同样在代理类中通过反射调用Host中Factory2.createActivityContext
函数创建一个PluginContext
对象,并用这个对象替换掉原生的Context
对象。
protected void attachBaseContext(Context newBase) {
newBase = RePluginInternal.createActivityContext(this, newBase); //创建PluginContext对象
super.attachBaseContext(newBase); //替换原生的Context
}
那么这个PluginContext
又做了什么呢?原来PluginContext
也重写了startActivity
函数,并且在其中调用了Factory2.startActivity
函数,接着又会调用PluginLibraryInternalProxy.startActivity
。后面的事情如果读过第一篇分析文章,你应该都知道啦!
@Override
public void startActivity(Intent intent) {
if (!Factory2.startActivity(this, intent)) {
if (mContextInjector != null) {
mContextInjector.startActivityBefore(intent);
}
super.startActivity(intent);
if (mContextInjector != null) {
mContextInjector.startActivityAfter(intent);
}
}
}
到这里,你无论是使用getContext().startActivity
还是直接在Activity
中使用startActivity
都会走Replugin的启动流程。但是事情并没有完,还有一个地方需要修改,那就是Application
中的Context
。
PluginApplicationClient
为插件创建了Application
对象,在PluginApplicationClient.callAttachBaseContext
函数中通过反射调用Applicaiton.attach
函数,用一个PluginContext
对象替换掉原来的Context
对象。
这里的PluginContext对象就是在前面讲到的Dex加载过程中创建的,作为Plugin的全局Context。
public void callAttachBaseContext(Context c) {
try {
sAttachBaseContextMethod.setAccessible(true);
sAttachBaseContextMethod.invoke(mApplication, c); // 将PluginContext对象传递给Application对象
} catch (Throwable e) {
if (BuildConfig.DEBUG) {
e.printStackTrace();
}
}
}
OK,到这里我们在插件中就能正常的使用Replugin的启动流程在插件中启动Activity了!!!
注意
实际上在
Plugin
中调用getApplication
获取到的是Host的Application
对象,因为别忘了我们是利用坑位原理运行插件组件的,所以在插件中我们用到的都是Host的Application。但是万一Plugin的Application中有客户定制的任何动作要完成呢?所以在加载 Plugin之后,Host也会通过反射创建Plugin的Application对象,并反射调用它的attach
和create
函数。Plugin.load
final boolean load(int load, boolean useCache) { PluginInfo info = mInfo; boolean rc = loadLocked(load, useCache); //Plugin加载完成 if (load == LOAD_APP && rc) { callApp(); // 关于Plugin的Application的操作都在这里了 } ...... return rc; }
Plugin.callAppLocked
private void callAppLocked() { // 获取并调用Application的几个核心方法 if (!mDummyPlugin) { ...... mApplicationClient = PluginApplicationClient.getOrCreate( mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo); // 创建Plugin的Appication对象 if (mApplicationClient != null) { // 调用Application.attach mApplicationClient.callAttachBaseContext(mLoader.mPkgContext); mApplicationClient.callOnCreate(); //调用Application.onCreate } } else { } }
总结:
通过这两篇文章,我们总结一下一个插件Activity
启动的流程大致是:
-
加载目标
Activity
信息开始查找
Activity
信息 —> 找到对应的Pugin
信息 —> 解压APK文件 —> 加载Dex文件内容 —> 创建Application
对象 —> 创建Entry
对象初始化Plugin环境 -
寻找坑位
查找坑位 —> 将目标
Activity
与找到的坑位以ActivityState
的形式缓存在PluginContainer
s中 —> 启动坑位Activity
—> 系统调用RepluginClassLoader
—> 调用PluginDexClassLoader
加载坑位Activity
类 —> 通过PluginContainers
找到坑位对应的目标Activity
类 —> 系统调用PluginActivity
的attachBaseContext
函数 —> 创建PluginContext
对象并替换 —> 目标Activity
的正常启动流程(onCreate
,onStart
,onResume
.....)
如果你已经读完了这两篇分析文章,但还没有将Replugin的里面如此多的类的关系理清楚,那么下面这张图也许可以帮到你。不过这张图目前还不完整,只针对目前已经讲到的部分,后面会逐渐补全。
绿色的部分是replugin-plugin-lib
,蓝色和红色部分都是是replugin-host-lib
包中的类,但是蓝色部分是运行在UI进程中,而红色部分是运行在Persistent进程中。
绿色部分和蓝色部分之间的调用都是通过反射来实现的,所以用类虚线箭头,同样蓝色部分和红色部分是通过Binder进程间通信机制来调用的,也用虚线箭头表示。
下一篇Replugin 全面解析 (4)我们会讲解Service以及进程相关内容!