概述
本文相关系统知识点在 上文 系统ClassLoader相关及Application初始化简单分析及总结 中,由以上文章可知:
系统产生出来的PathClassLoader 仅在
1:packageInfo握有其成员变量引用,
2:当前线程的classLoader
故要替换系统的类加载器只需要从这2个方面入手即可**
接下来就接着 Replugin源码解析之replugin-host-library---多进程初始化及通信 ,由以上文章可知:
RePlugin分别定义了RePluginClassLoader及PluginDexClassLoader,来替代主进程中的classloder及用于加载插件apk。
承接上文剩下未分析的 5.3 PMF.init中最后的PatchClassLoaderUtils.patch(application)及
6.0 PMF.callAttach()为入口分析 ,看replugin 是不是只从以上2点入手 及如何用新的classloader加载插件。
源码分析
com.qihoo360.loader.utils.PatchClassLoaderUtils
public class PatchClassLoaderUtils {
private static final String TAG = "PatchClassLoaderUtils";
public static boolean patch(Application application) {
try {
// 获取Application的BaseContext (来自ContextWrapper)
Context oBase = application.getBaseContext();
if (oBase == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass());
}
return false;
}
// 获取mBase.mPackageInfo
// 1. ApplicationContext - Android 2.1
// 2. ContextImpl - Android 2.2 and higher
// 3. AppContextImpl - Android 2.2 and higher
Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
if (oPackageInfo == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass());
}
return false;
}
// mPackageInfo的类型主要有两种:
// 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
// 2. android.app.LoadedApk - Android 2.3.3 and higher
if (LOG) {
Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass());
}
// 获取mPackageInfo.mClassLoader
ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
if (oClassLoader == null) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass() + "; mpi cl=" + oPackageInfo.getClass());
}
return false;
}
// 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);
// 将新的ClassLoader写入mPackageInfo.mClassLoader
ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);
// 设置线程上下文中的ClassLoader为RePluginClassLoader
// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
Thread.currentThread().setContextClassLoader(cl);
if (LOG) {
Log.d(TAG, "patch: patch mClassLoader ok");
}
} catch (Throwable e) {
e.printStackTrace();
return false;
}
return true;
}
}
简单看下其中的com.qihoo360.replugin.RePluginCallbacks.createClassLoader方法
//即简单创建RePluginClassLoader实例
public RePluginClassLoader createClassLoader(ClassLoader parent, ClassLoader original) {
return new RePluginClassLoader(parent, original);
}
先获取Application,再反射获取其中的mPackageInfo
,(mPackageInfo的类型主要有两种:a. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3;
b. android.app.LoadedApk - Android 2.3.3 and higher),再获取其mClassLoader
字段,该字段即为系统创建加载主dex的PathClassLoader
,然后以此类加载器为父加载器,创建Replugin自己实现的PathClassLoader子类即RePluginClassLoader,然后 将新创建的PathClassLoader,赋值给上文我们需要替换的2个点 即 packageInfo的成员变量及当前线程的classLoader。到此即成功利用自身的RePluginClassLoader替换宿主的PathClassLoader。
我们看RePluginClassLoader
的实现.
com.qihoo360.replugin.RePluginClassLoader
public class RePluginClassLoader extends PathClassLoader{
。。。。
public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {
// 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来
// 但我们最终不用它,而是拷贝所有的Fields
super("", "", parent);
mOrig = orig;
// 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList)
// 注意,这里用的是“浅拷贝”
// Added by Jiongxuan Zhang
copyFromOriginal(orig);
//反射获取原ClassLoader中的重要方法用来重写这些方法
initMethods(orig);
}
//反射获取原ClassLoader中的方法
private void initMethods(ClassLoader cl) {
Class> c = cl.getClass();
findResourceMethod = ReflectUtils.getMethod(c, "findResource", String.class);
findResourceMethod.setAccessible(true);
findResourcesMethod = ReflectUtils.getMethod(c, "findResources", String.class);
findResourcesMethod.setAccessible(true);
findLibraryMethod = ReflectUtils.getMethod(c, "findLibrary", String.class);
findLibraryMethod.setAccessible(true);
getPackageMethod = ReflectUtils.getMethod(c, "getPackage", String.class);
getPackageMethod.setAccessible(true);
}
//拷贝原ClassLoader中的字段到本对象中
private void copyFromOriginal(ClassLoader orig) {
if (LOG && IPC.isPersistentProcess()) {
LogDebug.d(TAG, "copyFromOriginal: Fields=" + StringUtils.toStringWithLines(ReflectUtils.getAllFieldsList(orig.getClass())));
}
if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1) {
// Android 2.2 - 2.3.7,有一堆字段,需要逐一复制
// 以下方法在较慢的手机上用时:8ms左右
copyFieldValue("libPath", orig);
copyFieldValue("libraryPathElements", orig);
copyFieldValue("mDexs", orig);
copyFieldValue("mFiles", orig);
copyFieldValue("mPaths", orig);
copyFieldValue("mZips", orig);
} else {
// Android 4.0以上只需要复制pathList即可
// 以下方法在较慢的手机上用时:1ms
copyFieldValue("pathList", orig);
}
}
//重写了ClassLoader的loadClass
@Override
protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
Class> c = null;
//拦截类的加载过程,判断要加载的类是否存在对应的插件信息,如果有从插件中加载
c = PMF.loadClass(className, resolve);
if (c != null) {
return c;
}
try {
//如果没有在插件中找到该类,使用宿主原来的ClassLoader加载
c = mOrig.loadClass(className);
return c;
} catch (Throwable e) {
}
return super.loadClass(className, resolve);
}
//重写反射的方法,执行的是原ClassLoader的方法
@Override
protected URL findResource(String resName) {
try {
return (URL) findResourceMethod.invoke(mOrig, resName);
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return super.findResource(resName);
}
//省略反射重写的其他方法,都是一样的
。。。。
}
RePluginClassLoader在构造方法中将宿主原来ClassLoader中的重要字段拷贝到本对象中,用来欺骗系统,接着反射获取原ClassLoader中的重要方法用来实现自身对应这些方法。
最后重写了loadClass方法,首先会通过要加载的类名来查找是否存在对应的插件信息,如果有取出插件信息中的ClassLoader,使用该插件的ClassLoader来加载类,如果没有找到再使用宿主原来的ClassLoader来加载,插件使用的ClassLoader就是Replugin中的另一个ClassLoader,PluginDexClassLoader。
我们再从文初说的剩下6.0 PMF.callAttach()为入口,看下是怎么利用另一个ClassLoader即PluginDexClassLoader来加载插件的。
com.qihoo360.loader2 .PMF
public class PMF {
static PmBase sPluginMgr;
//即调用上文设值进来的PmBase 的callAttach方法
public static final void callAttach() {
sPluginMgr.callAttach();
}
}
接下来看PmBase.callAttach
方法
com.qihoo360.loader2.PmBase
final void callAttach() {
//
mClassLoader = PmBase.class.getClassLoader();
// 挂载
for (Plugin p : mPlugins.values()) {
p.attach(mContext, mClassLoader, mLocal);
}
// 加载默认插件
if (PluginManager.isPluginProcess()) {
if (!TextUtils.isEmpty(mDefaultPluginName)) {
//
Plugin p = mPlugins.get(mDefaultPluginName);
if (p != null) {
boolean rc = p.load(Plugin.LOAD_APP, true);
if (!rc) {
if (LOG) {
LogDebug.d(PLUGIN_TAG, "failed to load default plugin=" + mDefaultPluginName);
}
}
if (rc) {
mDefaultPlugin = p;
mClient.init(p);
}
}
}
}
}
调用Plugin
的load方法com.qihoo360.loader2.Plugin
class Plugin {
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
//调用loadLocked方法
boolean rc = loadLocked(load, useCache);
....
}
-----------------------------------
private boolean loadLocked(int load, boolean useCache) {
....
//调用doload方法
boolean rc = doLoad(logTag, context, parent, manager, load);
...
}
------------------------------------------------------
private final boolean doLoad(String tag, Context context, ClassLoader parent,
PluginCommImpl manager, int load) {
if (mLoader == null) {
...
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
if (!mLoader.loadDex(parent, load)) {
return false;
}
...
}
}
}
看下Loader
的loadDex方法
com.qihoo360.loader2.Loader
class Loader {
final boolean loadDex(ClassLoader parent, int load) {
...
if (BuildConfig.DEBUG) {
// 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里
// 需要替换为BootClassLoader才行
// Added by yangchao-xy & Jiongxuan Zhang
parent = ClassLoader.getSystemClassLoader();
} else {
// 线上环境保持不变
parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
}
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
...
}
}
调用RePluginCallbacks的createPluginClassLoader方法创建classloader
com.qihoo360.replugin.RePluginCallbacks
public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
return new PluginDexClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent);
}
至此第二个classloader即PluginDexClassLoader(用于加载插件)的类加载器出现。
我们看下其实现
public class PluginDexClassLoader extends DexClassLoader {
//构造方法
public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
super(dexPath, optimizedDirectory, librarySearchPath, parent);
//处理多dex
installMultiDexesBeforeLollipop(pi, dexPath, parent);
//获取宿主的原始ClassLoader
mHostClassLoader = RePluginInternal.getAppClassLoader();
//反射获取原ClassLoader中的loadClass方法
initMethods(mHostClassLoader);
}
//重写了ClassLoader的loadClass
@Override
protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException {
// 插件自己的Class。采用正常的双亲委派模型流程,读到了就直接返回
Class> pc = null;
ClassNotFoundException cnfException = null;
try {
pc = super.loadClass(className, resolve);
if (pc != null) {
return pc;
}
} catch (ClassNotFoundException e) {
// Do not throw "e" now
cnfException = e;
}
// 若插件里没有此类,则会从宿主ClassLoader中找,找到了则直接返回
// 注意:需要读取isUseHostClassIfNotFound开关。默认为关闭的。可参见该开关的说明
if (RePlugin.getConfig().isUseHostClassIfNotFound()) {
try {
return loadClassFromHost(className, resolve);
} catch (ClassNotFoundException e) {
// Do not throw "e" now
cnfException = e;
}
}
// At this point we can throw the previous exception
if (cnfException != null) {
throw cnfException;
}
return null;
}
//通过在构造方法中反射原宿主的ClassLoader中的loadClass方法去从宿主中查找
private Class> loadClassFromHost(String className, boolean resolve) throws ClassNotFoundException {
Class> c;
try {
c = (Class>) sLoadClassMethod.invoke(mHostClassLoader, className, resolve);
} catch (IllegalAccessException e) {
throw new ClassNotFoundException("Calling the loadClass method failed (IllegalAccessException)", e);
} catch (InvocationTargetException e) {
throw new ClassNotFoundException("Calling the loadClass method failed (InvocationTargetException)", e);
}
return c;
}
//。。。省略处理多dex文件的代码,原理和上文描述Google的MultiDex的做法一样
这里就比较简单了,因为插件是依赖于宿主生存的,这里只需要将要查找的类找到并返回就可以了,至于其他的操作已经由上面的RePluginClassLoader来处理了,这里还处理了如果插件中早不到类,会去宿主中查找,这里会有一个开关,默认是关闭的,可以通过RePluginConfig的setUseHostClassIfNotFound方法设置。
总结
Replugin通过Hook住系统的PathClassLoader并重写了loadClass方法来实现拦截类的加载过程,并且每一个插件apk都设置了一个PluginDexClassLoader,在加载类的时候先使用这个PluginDexClassLoader去加载,加载到了直接返回否则再通过持有系统或者说是宿主原有的PathClassLoader去加载,这样就保证了不管是插件类、宿主类、还是系统类都可以被加载到。
那么说到思想,Replugin这么做的思想是什么?其实我觉得是破坏了ClassLoader的双亲委派模型,或者说叫打破这种模型,为什么这样说?首先双亲委派模型是层层向上委托的树形加载,而Replugin在收到类加载请求时直接先使用了插件ClassLoader来尝试加载,这样的加载模式应该算是网状加载,所以说Replugin是通过Hook系统ClassLoader来做到破坏了ClassLoader的双亲委派模型,我们再回想一下上一章我们分析过的Replugin框架代码中,Replugin将所以插件apk封装成一个Plugin对象统一在插件管理进程中管理,而每一个插件apk都有属于自己的ClassLoader,在类被加载的时候首先会使用插件自己的ClassLoader去尝试加载,这样做的好处是,可以精确的加载到需要的那个类,而如果使用双亲委派只要找到一个同路径的类就返回,那么这个被返回的类有可能并不是我们需要的那个类。
举个例子,例如两个插件apk中有一个路径和名字完全相同的类,如果使用这种网状加载可以精确的加载到这个类,因为每一个插件apk都有自己的类加载器。而如果还是使用双亲委派模型的话,那么只要找到限定名完全相同的类就会返回,那么这个返回的类并不能保证就是我们需要的那个。