*本篇文章已授权微信公众号 guolin_blog (郭霖)独家发布

上一篇唯一插件化Replugin源码及原理深度剖析–初始化之框架核心,我们说了Replugin的整体框架的初始化,但是因为篇幅的缘故还有Hook系统的ClassLoader和插件的加载没有说,那么我们这一篇就来详解的来分析一下Hook这块,本章我们讲从Hook系统ClassLoader的思想和原理进行剖析,如果没有看过上一篇建议先看上一篇

提示:请不要忽略代码注释,由于通畅上下逻辑思维,不太重要的部分跳转代码不会全部进去一行行的看,但是会将注释写出来,所以请务必不要忽略注释,而且最好是跟着文章一起看源码。

概要:
一、关于ClassLoader的知识回顾和Replugin中ClassLoader

二、Hook系统ClassLoader的原理分析

三、Hook系统ClassLoader的思想及总结

一、关于ClassLoader的知识回顾和Replugin中ClassLoader

ClassLoader是什么?

ClassLoader是类加载器,它是用来形容将一个类的二进制流加载到虚拟机中的过程,一个类的唯一性要由它的类加载器和它本身来确定,也就是说一个Class文件如果使用不同的类加载器来加载,那么加载出来的类也是不相等的,而在Java中为了保证一个类的唯一性使用了双亲委派模型,也就是说如果要加载一个类首先会委托给自己的父加载器去完成,父加载器会再向上委托,直到最顶层的类加载器,如果父加载器没有找个要加载的类,子类才会尝试自己去加载,这样就保证了加载的类都是一个类,例如Object都是一个类。

Android中的ClassLoader:

1、BootClassLoader:

它是Android中最顶层的ClassLoader,创建一个ClassLoader需要传入一个parent,而android中所有的ClassLoader的最终parent都是BootClassLoader,它也继承自ClassLoader,但是继承的这个ClassLoader也不同于Java本身的ClassLoader,是android经过修改后的ClassLoader,它是ClassLoader的内部类,可以通过ClassLoader.getSystemClassLoader().getParent()得到。

    //BootClassLoader
class BootClassLoader extends ClassLoader {private static BootClassLoader instance;@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")public static synchronized BootClassLoader getInstance() {if (instance == null) {instance = new BootClassLoader();}return instance;}public BootClassLoader() {super(null, true);}。。。。
}

2、PathClassLoader:

继承自BaseDexClassLoader ,它是我们apk的默认加载器,它是用来加载系统类和主dex文件中的类的,但是系统类是由BootClassLoader加载的,如果apk中有多个dex文件,只会加载主dex

//PathClassLoader
public class PathClassLoader extends BaseDexClassLoader {public PathClassLoader(String dexPath, ClassLoader parent) {super(dexPath, null, null, parent);}public PathClassLoader(String dexPath, String libraryPath,ClassLoader parent) {super(dexPath, null, libraryPath, parent);}
}

3、DexClassLoader:
继承自BaseDexClassLoader ,可以用来加载外置的dex文件或者apk等

//DexClassLoader
public class DexClassLoader extends BaseDexClassLoader {public DexClassLoader(String dexPath, String optimizedDirectory,String libraryPath, ClassLoader parent) {super(dexPath, new File(optimizedDirectory), libraryPath, parent);}
}

Android中主要使用的ClassLoader有PathClassLoader和DexClassLoader,它们都继承自BaseDexClassLoader,BaseDexClassLoader中维护了一个DexPathList,PathClassLoader和DexClassLoader查找类的操作直接调用BaseClassLoader的findClass方法,而BaseClassLoader的findClass中又通过内部维护的DexPathList来查找,DexPathList中又维护这一个Element数组,这个数组中Element元素其实就是Dex文件。

PathClassLoader和DexClassLoader最大的区别就是DexClassLoader可以加载外置dex文件,这是因为PathClassLoader构造方法中像上传递时第二个参数传了null,这个参数代表的是dex优化后的路径,DexPathList在生成Element数组时会判断这个参数是否为null,如果为null就使用系统默认路径/data/dalvik-cache,这也是导致如果要加载外置dex文件只能使用DexClassLoader的原因。

PathClassLoader只会加载apk中的主dex文件,其他的dex文件是使用DexClassloader动态加载进来,然后通过反射获取到PathClassLoader中的DexPathList,然后再拿到DexPathList中的Element数组,最后将后加载进来的dex和反射拿到的数组进行合并后并重新设置回去,这也是Google的MultiDex的做法,在我之前写过的插件化的实现的博客中也采用了这种方式

Replugin中的ClassLoader:
在Replugin中有两个ClassLoader,一个用来代替宿主工作的RePluginClassLoader,一个用来加载插件apk类的PluginDexClassLoader,下面我们分别来看一下这两个类是怎么实现的

RePluginClassLoader:用来代替宿主工作的ClassLoader

源码位置:com.qihoo360.replugin.RePluginClassLoader

public class RePluginClassLoader extends PathClassLoader{。。。。public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {// 由于PathClassLoader在初始化时会做一些Dir的处理,所以这里必须要传一些内容进来// 但我们最终不用它,而是拷贝所有的Fieldssuper("", "", parent);mOrig = orig;// 将原来宿主里的关键字段,拷贝到这个对象上,这样骗系统以为用的还是以前的东西(尤其是DexPathList)// 注意,这里用的是“浅拷贝”// Added by Jiongxuan ZhangcopyFromOriginal(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即可// 以下方法在较慢的手机上用时:1mscopyFieldValue("pathList", orig);}}//重写了ClassLoader的loadClass@Overrideprotected 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的方法@Overrideprotected 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

PluginDexClassLoader:用来加载插件自己的类

源码位置:com.qihoo360.replugin.PluginDexClassLoader

public class PluginDexClassLoader extends DexClassLoader {//构造方法
public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {super(dexPath, optimizedDirectory, librarySearchPath, parent);//处理多dexinstallMultiDexesBeforeLollipop(pi, dexPath, parent);//获取宿主的原始ClassLoadermHostClassLoader = 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" nowcnfException = e;}// 若插件里没有此类,则会从宿主ClassLoader中找,找到了则直接返回// 注意:需要读取isUseHostClassIfNotFound开关。默认为关闭的。可参见该开关的说明if (RePlugin.getConfig().isUseHostClassIfNotFound()) {try {return loadClassFromHost(className, resolve);} catch (ClassNotFoundException e) {// Do not throw "e" nowcnfException = e;}}// At this point we can throw the previous exceptionif (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方法设置。

二、Hook原理剖析

我们也看了Replugin中的两个ClassLoader了,现在看一下Replugin是怎么Hook住系统的ClassLoader的,在这过程当中我们将深入源码去了解为什么Hook住了系统的CLassLoader就可以拦截到类的加载过程。

1、如果看了上一篇的分析,应该知道Replugin的Hook是在初始化的过程中完成的,在PMF的init方法中最后一句代码,我们再来看一下

源码位置: com.qihoo360.loader2.PMF

public static final void init(Application application) {//保持对Application的引用setApplicationContext(application);//这里创建在一个叫Tasks的类中创建了一个主线程的Hanlder,//通过当前进程的名字判断应该将插件分配到哪个进程中,PluginManager.init(application);//PmBase是Replugin中非常重要的对象,它本身和它内部引用的其他对象掌握了Replugin中很多重要的功能,sPluginMgr = new PmBase(application);sPluginMgr.init();//将在PmBase构造中创建的PluginCommImpl赋值给Factory.sPluginManagerFactory.sPluginManager = PMF.getLocal();//将在PmBase构造中创建的PluginLibraryInternalProxy赋值给Factory2.sPLProxyFactory2.sPLProxy = PMF.getInternal();//Replugin唯一hook点 hook系统ClassLoaderPatchClassLoaderUtils.patch(application);
}

2、直接点进去看一下PatchClassLoaderUtils类中的patch方法,这个类也只有这一个方法

源码位置:com.qihoo360.loader.utils.PatchClassLoaderUtils

public static boolean patch(Application application) {try {// 获取Application的BaseContext// 该BaseContext在不同版本中具体的实例不同// 1. ApplicationContext - Android 2.1// 2. ContextImpl - Android 2.2 and higher// 3. AppContextImpl - Android 2.2 and higherContext oBase = application.getBaseContext();if (oBase == null) {return false;}// 获取mBase.mPackageInfo// mPackageInfo的类型主要有两种:mPackageInfo这个对象代表了apk文件在内存中的表现// 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3// 2. android.app.LoadedApk - Android 2.3.3 and higherObject oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");if (oPackageInfo == null) {return false;}// 获取mPackageInfo.mClassLoader,也就是宿主的PathClassLoader对象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;}// 从RePluginCallbacks中获取RePluginClassLoader,通过宿主的父ClassLoader和宿主ClassLoader生成RePluginClassLoaderClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);// 将我们创建的RePluginClassLoader赋值给mPackageInfo.mClassLoader ,来达到代理系统的PathClassLoaderReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);// 设置线程上下文中的ClassLoader为RePluginClassLoader// 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针Thread.currentThread().setContextClassLoader(cl);} catch (Throwable e) {e.printStackTrace();return false;}return true;
}

3、hook的主要代码就这么多,其他的就是反射的工具类中的共用代码,我们先来总结一下,这里只是分析原理,不考虑低版本不同类型的问题,
分析的源码基于android5.1

1) 首先通过宿主Application拿到BaseContext,Context的实现类是ContextImpl

2) 再通过BaseContext拿到它的mPackageInfo字段,他的类型是LoadedApk类型

3)通过mPackageInfo字段获取它的mClassLoader字段,也就是我们想要替换的PathClassLoader

4) 通过反射得到的PathClassLoader,并创建Replugin自己的RePluginClassLoader

5) 将RePluginClassLoader设置给mPackageInfo.mClassLoader字段和Thread中的contextClassLoader

看完了这点代码有没有觉得很惊讶,这么点代码就hook住了系统的ClassLoader,没错,就这么点代码,但是起到了非常nb的作用,下面我们分析一下原理和实现思路。

首先我们通过上面的hook代码可以清楚的知道,ContextImpl中的mPackageInfo是一个LoadedApk类型,而这个LoadedApk类型中保存了系统给我们的PathClassLoader,现在我们从源码来看一下这个PathClassLoader是怎么被创建的并保存在了ContextImpl中的,
来证实一下确实是hook住了系统的ClassLoader。

我们android应用是基于四大组件的,这个毋庸置疑,每一个应用都对应一个Application。应用程序最先被执行的是Application,我们就从这里入手。

接下来先分析第1步,看一下四大组件和Application是否是被PathClassLoader加载出来的,这里涉及了应用程序的启动过程和四大组件的启动过程,这里重要的是分析系统的PathClassLoader,所以不会详细的去分析启动过程的源码。

1.简单描述一下应用启动过程,每个应用程序首先会创建一个属于自己的进程,在进程创建后会调用ActivityThread中的mian方法,在mian方法中会开启消息循环并和AMS绑定,然后AMS会调用ActivityThread中的bindApplication方法,这个方法发送了一个消息到Handler中并调用handleBindApplication方法开始创建Application,也代表了一个应用程序真正的启动了,就从这个方法开始

系统源码路径:frameworks/base/core/java/android/app/ActivityThread.java

   private void handleBindApplication(AppBindData data) {。。。。//创建LoaderApkdata.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);。。。。try {//调用了LoadedApk中的makeApplication方法创建ApplicationApplication app = data.info.makeApplication(data.restrictedBackupMode, null);。。。。} finally {StrictMode.setThreadPolicy(savedPolicy);}
}

2.通过getPackageInfoNoCheck先创建了LoaderApk,然后通过makeApplication方法创建了Application,先来看一下创建LoaderApk的过程,因为它维护了ClassLoader,

系统源码路径:
frameworks/base/core/java/android/app/ActivityThread.java

public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai,CompatibilityInfo compatInfo) {//注意这里传入的nullreturn getPackageInfo(ai, compatInfo, null, false, true, false);
}

3.直接跳转了getPackageInfo方法,注意看传入的第3个参数是null

系统源码路径:frameworks/base/core/java/android/app/ActivityThread.java

//上面传入的第3个参数是null,也就是说这里的ClassLoader是null
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,ClassLoader baseLoader, boolean securityViolation, boolean includeCode,boolean registerPackage) {synchronized (mResourcesManager) {//尝试从缓存中获取WeakReference<LoadedApk> ref;if (includeCode) {ref = mPackages.get(aInfo.packageName);} else {ref = mResourcePackages.get(aInfo.packageName);}           LoadedApk packageInfo = ref != null ? ref.get() : null;//未命中缓存if (packageInfo == null || (packageInfo.mResources != null&& !packageInfo.mResources.getAssets().isUpToDate())) {//直接创建一个LoadedApk,传入了ClassLoader,但是上面传入的是nullpackageInfo =new LoadedApk(this, aInfo, compatInfo, baseLoader,securityViolation, includeCode &&(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != , registerPackage);//如果是系统进程if (mSystemThread && "android".equals(aInfo.packageName)) {packageInfo.installSystemApplicationInfo(aInfo,getSystemContext().mPackageInfo.getClassLoader());}//存入缓存if (includeCode) {mPackages.put(aInfo.packageName,new WeakReference<LoadedApk>(packageInfo));} else {mResourcePackages.put(aInfo.packageName,new WeakReference<LoadedApk>(packageInfo));}}return packageInfo;}
}

4.首先会尝试从缓存中获取LoadedApk,如果没有命中缓存直接new一个,并且传入了ClassLoader,但是第2步中传入的ClassLoader是null,我们再看一下LoadedApk构造方法

系统源码路径:
frameworks/base/core/java/android/app/LoadedApk.java

    public LoadedApk(ActivityThread activityThread, ApplicationInfo aInfo,CompatibilityInfo compatInfo, ClassLoader baseLoader,boolean securityViolation, boolean includeCode, boolean registerPackage) {mActivityThread = activityThread;setApplicationInfo(aInfo);mPackageName = aInfo.packageName;//将传入的ClassLoader赋值给了mBaseClassLoadermBaseClassLoader = baseLoader;mSecurityViolation = securityViolation;mIncludeCode = includeCode;mRegisterPackage = registerPackage;mDisplayAdjustments.setCompatibilityInfo(compatInfo);
}

5.这里只是将传入的null赋值给了mBaseClassLoader,没有其他操作了,我们返回去再看第1步中,将LoadedApk创建后接着使用这个LoadedApk创建了Application

系统源码路径:
frameworks/base/core/java/android/app/LoadedApk.java

    public Application makeApplication(boolean forceDefaultAppClass,Instrumentation instrumentation) {//保证只创建一次Application    if (mApplication != null) {return mApplication;}Application app = null;。。。。try {//获取ClassLoaderjava.lang.ClassLoader cl = getClassLoader();//不是系统包名if (!mPackageName.equals("android")) {Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,"initializeJavaContextClassLoader");//不是系统应用执行了initializeJavaContextClassLoader     initializeJavaContextClassLoader();Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);}//创建Context,这个就是hook时获取的BaseContextContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);//创建Applicationapp = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);appContext.setOuterContext(app);} catch (Exception e) {if (!mActivityThread.mInstrumentation.onException(app, e)) {Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);throw new RuntimeException("Unable to instantiate application " + appClass+ ": " + e.toString(), e);}}。。。。return app;
}

6.这里获取ClassLoader,接着创建BaseContext,最后创建Application,但是上面在创建LoadedApk时传入的ClassLoader是null,怎么去加载Application这个类呢,那么说明这里的getClassLoader()肯定会有对ClassLoader的初始化了,来看一下

系统源码路径:
frameworks/base/core/java/android/app/LoadedApk.java

    public ClassLoader getClassLoader() {synchronized (this) {//如果mClassLoader不为空,直接返回了,这个mClassLoader就是hook过程中反射获取的PathClassLoaderif (mClassLoader != null) {return mClassLoader;}if (mIncludeCode && !mPackageName.equals("android")) {//不是系统应用。。。。//获取ClassLoader对象,这里传入的mBaseClassLoader还是null,因为LoadedApk创建的时候传入的就是nullmClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,mBaseClassLoader);StrictMode.setThreadPolicy(oldPolicy);} else {//是系统应用if (mBaseClassLoader == null) {mClassLoader = ClassLoader.getSystemClassLoader();} else {mClassLoader = mBaseClassLoader;}}return mClassLoader;}
}

7.如果不是系统应用通过ApplicationLoaders获取ClassLoader,如果是系统应用通过ClassLoader.getSystemClassLoader()获取,我们不是系统应用,只分析ApplicationLoaders

系统源码路径:
frameworks/base/core/java/android/app/ApplicationLoaders.java

    public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{//这里获取的是BootClassLoader,文章开头说过这个方法ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();synchronized (mLoaders) {//parent是LoadedApk刚传入的mBaseClassLoader,还是nullif (parent == null) {//设置parent=BootClassLoaderparent = baseParent;}//这里肯定相等if (parent == baseParent) {//尝试获取缓存ClassLoader loader = mLoaders.get(zip);if (loader != null) {return loader;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);//创建PathClassLoader,终于出现了PathClassLoader pathClassloader =new PathClassLoader(zip, libPath, parent);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);//存入缓存mLoaders.put(zip, pathClassloader);return pathClassloader;}Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);PathClassLoader pathClassloader = new PathClassLoader(zip, parent);Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);return pathClassloader;}
}

8.终于出现了我们要找的PathClassLoader,这里LoadedApk中的mClassLoader已经有值了,最开始创建LoadedApk时传入的ClassLoader为null,在创建Application时,通过ApplicationLoaders创建了PathClassLoader,PathClassLoader的parent是BootClassLoader。接着看第5步中获取完了ClassLoader后判定不是系统应用调用了initializeJavaContextClassLoader,看看这个方法干了什么

系统源码路径:
frameworks/base/core/java/android/app/LoadedApk.java

        private void initializeJavaContextClassLoader() {IPackageManager pm = ActivityThread.getPackageManager();android.content.pm.PackageInfo pi;try {pi = pm.getPackageInfo(mPackageName, , UserHandle.myUserId());} catch (RemoteException e) {throw new IllegalStateException("Unable to get package info for "+ mPackageName + "; is system dying?", e);}if (pi == null) {throw new IllegalStateException("Unable to get package info for "+ mPackageName + "; is package not installed?");}boolean sharedUserIdSet = (pi.sharedUserId != null);boolean processNameNotDefault =(pi.applicationInfo != null &&!mPackageName.equals(pi.applicationInfo.processName));boolean sharable = (sharedUserIdSet || processNameNotDefault);ClassLoader contextClassLoader =(sharable)? new WarningContextClassLoader(): mClassLoader;//设置当前线程的ClassLoader    ,还记得Replugin的hook的最后一行代码吗,这就是为什么Thread.currentThread().setContextClassLoader(contextClassLoader);
}

9.这里设置当前线程的ClassLoader,应用能明白Replugin的最后一行代码为什么了,接着看第5步,获取完了ClassLoader并且设置当前线程的ClassLoader后创建ContextImpl,也就是hook时反射获取的BaseContext

系统源码路径:
frameworks/base/core/java/android/app/ContextImpl.java

    static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) {if (packageInfo == null) throw new IllegalArgumentException("packageInfo");//直接new了一个ContextImplreturn new ContextImpl(null, mainThread,packageInfo, null, null, false, null, null);
}

10.直接new了一个ContextImpl,再看ContextImpl的构造

系统源码路径:
frameworks/base/core/java/android/app/ContextImpl.java

    private ContextImpl(ContextImpl container, ActivityThread mainThread,LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted,Display display, Configuration overrideConfiguration) {。。。。//mPackageInfo,将传入的LoadedApk赋值给了mPackageInfo,这就是在Hook代码中反射获取的mPackageInfomPackageInfo = packageInfo;。。。。
}

11.将传入的LoadedApk赋值给了mPackageInfo,也就是在Hook代码中反射获取的mPackageInfo,ContextImpl也创建了,而且内部维护的mPackageInfo也出现了,mPackageInfo的值就是刚刚创建的LoadedApk,LoadedApk中的ClassLoader也初始化了,现在还有一点没有证实,hook时获取mPackageInfo时通过Application.getBaseContext获取的ContextImpl,现在我们继续证实一下这个获取的BaseContext就是刚刚创建的ContextImpl,看第5步最后一步创建Application

系统源码路径:
frameworks/base/core/java/android/app/Instrumentation.java

    public Application newApplication(ClassLoader cl, String className, Context context)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//使用了ClassLoader.loadClass来加载Application类,这个ClassLoader就是上面创建的PathClassLoader,这里传入的context就是上面创建的ContextImpl    return newApplication(cl.loadClass(className), context);
}

12.直接调用了另一个重载的方法,但是传入的参数是先使用上面创建的PathClassLoader加载了Application的Class

系统源码路径:
frameworks/base/core/java/android/app/Instrumentation.java

    static public Application newApplication(Class<?> clazz, Context context)throws InstantiationException, IllegalAccessException, ClassNotFoundException {//创建Application并回调  attach方法Application app = (Application)clazz.newInstance();//调用Application的attach方法,传入的context还是上面创建的ContextImpl   app.attach(context);return app;}

13.使用PathClassLoader加载了Application并实例对象后调用了attach方法,接着看

系统源码路径:
frameworks/base/core/java/android/app/Application.java

final void attach(Context context) {//调用了ContextWrapper的方法,看到这个方法了吧,上面提到过,够早回调的吧,context还是ContextImplattachBaseContext(context);mLoadedApk = ContextImpl.getImpl(context).mPackageInfo;
}

14.Application继承自ContextWrapper,在attach中调用了ContextWrapper中的attachBaseContext方法 ,也证明了这个方法回调够早了

系统源码路径:
frameworks/base/core/java/android/content/ContextWrapper.java

protected void attachBaseContext(Context base) {if (mBase != null) {throw new IllegalStateException("Base context already set");}//mBase出现了,mBase的值就是在创建Application时创建的ContextImplmBase = base;
}

到这里hook系统ClassLoader的原理及源码分析就结束,现在再返回去看hook的几行代码应该能明白为什么了。下面我们总结一下系统源码的思路

一个应用程序被启动后首先会调用ActivityThread中的main方法,在main方法中会开启消息循环并和AMS进行绑定,绑定时会传入ActivityThread中的内部类ApplicationThread,ApplicationThread是一个IApplicationThread类型的Binder对象,然后AMS会通过IApplicationThread中的bindApplication方法,在bindApplication方法中会使用Handler发送一条消息后执行handleBindApplication方法,
在这个方法中首先创建了LoadedApk对象,但是在创建的时候传入的ClassLoader是null,接着调用了LoadedApk中的makeApplication方法,在makeApplication方法中首先初始化了LoadedApk中的mClassLoader,是通过ApplicationLoaders中的getClassLoader方法,在方法中首先获取了最顶层的BootClassLoader,然后将BootClassLoader当做parent创建了PathClassLoader,这个PathClassLoader就是我们应用程序默认的类加载器了,接着下面创建了ContextImpl,也就是BaseContext,在构造中将LoadedApk赋值给了mPackageInfo字段,最后使用PathClassLoader加载Application的Class并实例对象,然后调用attach方法将刚刚创建的ContextImpl
赋值给mBase字段。

三、Hook系统ClassLoader的思想及总结

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都有自己的类加载器。而如果还是使用双亲委派模型的话,那么只要找到限定名完全相同的类就会返回,那么这个返回的类并不能保证就是我们需要的那个。

下一篇:唯一插件化Replugin源码及原理深度剖析–插件的安装、加载原理

唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理相关推荐

  1. 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理

    上一篇 唯一插件化Replugin源码及原理深度剖析–唯一Hook点原理 在Replugin的初始化过程中,我将他们分成了比较重要3个模块,整体框架的初始化.hook系统ClassLoader.插件的 ...

  2. c++插件化 NDD源码的插件机制实现解析

    插件机制是一种框架,允许开发人员简单地在应用程序中添加或扩展功能.它使广泛使用,因为它可以作为模块被重复使用,并使它们更易于维护和扩展,因此它们在应用程序中非常有用.插件机制允许管理员在需要时轻松安装 ...

  3. 布隆过滤器原理深度剖析

    HBase布隆过滤器原理深度剖析 1. 数据结构与原理 1.1 初始化 1.2 变量映射 1.3 变量检索 1.4 总结 2. 过滤器特性 2.1 误判率 2.2 判断特点 3. 案列代码 1970年 ...

  4. 在OpenCV下写的直方图匹配(直方图规定化)C++源码!

    直方图匹配的原理就不多作解释了,我曾经还将MATLAB源码改写成过C源码,详情可见我的博文 根据MATLAB的histeq函数改写的运行在OpenCV下的直方图规定化C源码! 本文已转移到 https ...

  5. 根据MATLAB的histeq函数改写的运行在OpenCV下的直方图规定化C源码

    图像处理开发需求.图像处理接私活挣零花钱,请加微信/QQ 2487872782 图像处理开发资料.图像处理技术交流请加QQ群,群号 271891601 2016-6-8日用C++也实现了直方图规定化, ...

  6. WebRTC源码研究(4)web服务器工作原理和常用协议基础

    文章目录 WebRTC源码研究(4)web服务器工作原理和常用协议基础 前言 做WebRTC 开发为啥要懂服务器开发知识 1. Web 服务器简介 2. Web 服务器的类型 3. Web 服务器的工 ...

  7. 【 卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10)】

    卷积神经网络CNN 数学原理分析与源码详解 深度学习 Pytorch笔记 B站刘二大人(9/10) 本章主要进行卷积神经网络的相关数学原理和pytorch的对应模块进行推导分析 代码也是通过demo实 ...

  8. vue开源项目(各大插件,gitup源码)

    vue开源项目(各大插件,gitup源码) 目录 UI组件 开发框架 实用库 服务端 辅助工具 应用实例 Demo示例 UI组件 element★31142 - 饿了么出品的Vue2的web UI工具 ...

  9. c语言 临时文件作用,c语言函数mktemp()产生唯一临时文件名实例源码介绍

    c语言函数mktemp()产生唯一临时文件名实例源码介绍.有关的函数:tmpfile引入的头文件:#include 定义函数mktemp():char * mktemp(char * template ...

  10. 各种风格404错误页面html模板源码30多套高大尚响应式网站模板html5网页静态模板Bootstrap扁平化网站源码css3手机seo自适响应。

    各种风格404错误页面html模板源码30多套高大尚响应式网站模板html5网页静态模板Bootstrap扁平化网站源码css3手机seo自适响应. 下载链接 各种风格404错误页面html模板源码3 ...

最新文章

  1. 对于一个人工智能项目如何把模型调整为最好
  2. 皮一皮:男女的不同...
  3. 2020年大厂职级薪资一览表
  4. [再学Python] - 面向对象的程序设计- 对象和类
  5. 大整数相乘python fft_Python带你理解用于信号同步的CAZAC序列
  6. LINUX文件图标变化:有时右键菜单刷新可以,有时需要重启机器
  7. 基于机器视觉的苹果大小自动分级方法
  8. 车辆路径问题的基本操作
  9. 32位微型计算机原理接口,32位微机原理及接口技术
  10. plc和变频器通讯接线图详解
  11. QTP版“古城钟楼”---使用QTP完成微博定时发送脚本代码
  12. WINDOWS优化大师揭密
  13. Bluetooth LMP介绍
  14. GTC 2020:开发者谈NS版《巫师3》移植
  15. STM32毕业设计题目大全 选题推荐
  16. 监听元素宽高变化resize
  17. 运行在VMware上的VMware公司
  18. 【体感游戏】没有iPhone也能玩AIWI
  19. java计算机毕业设计个人连锁民宿信息管理系统设计与开发系统(修改)源码+mysql数据库+系统+lw文档+部署
  20. UISearchController自动弹出键盘,调用becomeFirstResponder变为第一响应事件,和resignFirstResponder

热门文章

  1. ios swift5 父子控制器
  2. lvgl v8之Styling the scrollbars
  3. HTML做一个传统节日端午节 带设计报告4500字
  4. Android Merged manifest 错误
  5. sshsecureshell登录Ubuntu出错,server responded “algorithm negotiation failed”
  6. 鱼塘钓鱼(贪心算法)--算法设计
  7. 2018年阿里巴巴重要开源项目汇总
  8. python代码块符号_Python 中代码块是用下列()符号代表的。_学小易找答案
  9. Java如何实现网页截图?
  10. sql 去重 distinct