概要:

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

二、Hook 系统ClassLoader 的原理分析

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

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() 得到。

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

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等

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的处理,所以这里必须要传一些内容进来 // 但我们最终不用它,而是拷贝所有的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

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); //处理多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 方法设置。

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.sPluginManager Factory.sPluginManager = PMF.getLocal(); //将在PmBase构造中创建的PluginLibraryInternalProxy赋值给Factory2.sPLProxy Factory2.sPLProxy = PMF.getInternal(); //Replugin唯一hook点 hook系统ClassLoader PatchClassLoaderUtils.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 higher Context 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 higher Object 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生成RePluginClassLoader ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader); // 将我们创建的RePluginClassLoader赋值给mPackageInfo.mClassLoader ,来达到代理系统的PathClassLoader ReflectUtils.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) { ... //创建LoaderApk data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo); ... try { //调用了LoadedApk中的makeApplication方法创建Application Application 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) { //注意这里传入的null return getPackageInfo(ai, compatInfo, null, false, true, false);
}

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

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

//上面传入的第3个参数是null,也就是说这里的ClassLoader是nullprivate 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,但是上面传入的是null packageInfo = 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赋值给了mBaseClassLoader mBaseClassLoader = 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 { //获取ClassLoader java.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时获取的BaseContext ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this); //创建Application app = 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过程中反射获取的PathClassLoader if (mClassLoader != null) { return mClassLoader; } if (mIncludeCode && !mPackageName.equals("android")) { //不是系统应用 。。。。 //获取ClassLoader对象,这里传入的mBaseClassLoader还是null,因为LoadedApk创建的时候传入的就是null mClassLoader = 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,还是null if (parent == null) { //设置parent=BootClassLoader parent = 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了一个ContextImpl return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, null);
}

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

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

static ContextImpl createAppContext(ActivityThread mainThread, LoadedApk packageInfo) { if (packageInfo == null) throw new IllegalArgumentException("packageInfo"); //直接new了一个ContextImpl return new ContextImpl(null, mainThread, packageInfo, null, null, false, null, null);
} private ContextImpl(ContextImpl container, ActivityThread mainThread, LoadedApk packageInfo, IBinder activityToken, UserHandle user, boolean restricted, Display display, Configuration overrideConfiguration) { ... //mPackageInfo,将传入的LoadedApk赋值给了mPackageInfo,这就是在Hook代码中反射获取的mPackageInfo mPackageInfo = 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还是ContextImpl attachBaseContext(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时创建的ContextImpl mBase = 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 都有自己的类加载器。而如果还是使用双亲委派模型的话,那么只要找到限定名完全相同的类就会返回,那么这个返回的类并不能保证就是我们需要的那个。

360开源的插件化框架Replugin深度剖析相关推荐

  1. 滴滴开源Android插件化框架VirtualAPK原理分析

    概述 滴滴出行公司的首个对外开源项目 - VirtualAPK.地址:github.com/didi/Virtua- 滴滴自行研发了这款插件化框架,功能全面.兼容性好,还能够适用于有耦合的业务插件,这 ...

  2. [Android]用架构师角度看插件化(1)-Replugin入门剖析

    多谢一直以来的支持,组件化的内容,应该会有一段时间不再更新,一些非常关键的技术将会在我将要出版的组件化书籍中提及. 组件化模块化的开发适合于中小型企业的业务叠加,和代码重用.而插件化的开发将组件化和模 ...

  3. 360手机卫士插件化RePlugin今日开源,官方全面解读

    作者:张炅轩,360手机卫士·客户端技术专家 写在前面 "RePlugin将在6月底开源,这将是我们献给安卓世界最好的礼物."当我们宣布这一消息时,心中的激动,无以言表.是的,三年 ...

  4. Shadow插件化框架设计——replugin原理(架构师进阶之旅)

    DroidPlugin原理解析 从系统设计的角度,组件和窗口的逻辑实体都存在于系统服务,比如Activity创建后,其逻辑控制主体在AMS,对于窗口,其逻辑控制主体在WMS android将逻辑主体放 ...

  5. 从零开始实现一个插件化框架

    /   今日科技快讯   / 网易科技讯 7月15日消息,据外媒报道,最新泄露的内部文件显示,电动汽车制造商特斯拉位于美国加州弗里蒙特汽车组装工厂的员工感染新冠病毒的危险激增.到目前为止,已有130多 ...

  6. [Android]用架构师角度看插件化(2)-Replugin 唯一hook点

    Replugin,为何我选择要研究这个的插件呢?很大的原因是因为它的介绍中说明,他只会有一个hook点. 一.Hook hook点是什么? 我们入门Android的时候,一定会看到过这个图,但是你确定 ...

  7. 爱奇艺开源轻量级插件化方案 Neptune

    爱奇艺近日开源了其轻量级插件化方案 Neptune,项目地址:https://github.com/iqiyi/Neptune 插件化框架可以在主程序不重新安装的情况下,针对单个业务模块进行动态加载达 ...

  8. java 轻量级插件化框架_轻量级插件化框架——Small

    photo-1441716844725-09cedc13a4e7.jpg 前言 世界那么大,组件那么小.Small,做最轻巧的跨平台插件化框架. --Galenlin 这是Small作者,林光亮老师, ...

  9. 插件化篇 - 插件化框架对比

    来看看现有插件化框架的对比. 目录: MulitDex 引起的问题 插件化需要解决的问题与方案 插件化实现方案分析对比 1. MulitDex 引起的问题 在应用安装到手机上的时候 dex 文件的安装 ...

  10. android中的插件开发框架,设计并开发一个 Android 的插件化框架

    结合动态加载系列文章的分析,现在开始设计并开发一个 Android 的插件化框架,命名为 Frontia.Frontia 有 "前端" 的意思,寓意着 Android 插件能像前端 ...

最新文章

  1. 关于Fast Terrain Rendering Using Geometrical MipMapp
  2. background-position 使用方法具体介绍
  3. 基于计算机网络的可持续发展信息共享情况调查
  4. 性能测试:服务器配置清单分析
  5. 动态规划 1.背包问题
  6. Eclipse怎样连接并打开oracle等数据库?
  7. mysql 读取oracle数据_Python中Pandas通过read_sql方法从Mysql或Oracle数据库中读取数据帧(DataFrame)...
  8. 每日算法系列【LeetCode 827】最大人工岛
  9. Extjs grid增加或删除列后记住滚动条的位置
  10. 如何理解二元函数的可导与可微?
  11. 计算机vfp考试笔试试题,关于计算机的二级VFP笔试试题
  12. AR1021x USB网卡驱动学习笔记
  13. java计算机毕业设计基于安卓Android微信小程序的共享单车租赁系统uniApp
  14. CSS复合选择器:后代选择器
  15. 爱了爱了,这样的文字动画让你爱不释手
  16. R语言学习 文本处理
  17. 基于RFID技术下的化工厂定位系统,包含化工厂人员定位解决方案-新导智能
  18. Error while Launching activity
  19. 管家婆软件库存周转率的解释及计算方式
  20. js实现文字滚动效果

热门文章

  1. pic16 hex 逆向c语言,PIC单片机的十六进制文件格式:Hex文件
  2. 本科计算机er发篇论文,其实没有那么难
  3. Elasticsearch——Keyword字段类型
  4. Es的mapping映射
  5. 论文学习——多元时间序列相似性度量方法
  6. 不良事件总结怎么写_不良事件总结
  7. 三年经验前端开发面试总结
  8. 数据结构之什么是数组?
  9. python全栈测试开发工程师_Python测试开发全栈核心课程 互联网测试工程师必修课...
  10. python抓取谷歌app市场的icon