唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理
来源:互联网 发布:做淘宝新手自己做模特 编辑:程序博客网 时间:2024/06/05 23:41
*本篇文章已授权微信公众号 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()得到。
//BootClassLoaderclass 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
//PathClassLoaderpublic 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等
//DexClassLoaderpublic 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 @Overrideprotected 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
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都有自己的类加载器。而如果还是使用双亲委派模型的话,那么只要找到限定名完全相同的类就会返回,那么这个返回的类并不能保证就是我们需要的那个。
下一篇:唯一插件化Replugin源码及原理深度剖析–插件的安装、加载原理
- 唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理
- 唯一插件化Replugin源码及原理深度剖析--插件的安装、加载原理
- 唯一插件化RePlugin源码及原理深度剖析--工程职责
- 唯一插件化Replugin源码及原理深度剖析--初始化之框架核心
- PageHelper分页插件源码及原理剖析
- PageHelper分页插件源码及原理剖析
- 360开源的插件化框架Replugin深度剖析
- 360开源的插件化框架Replugin深度剖析
- 深度剖析fork()的原理及用法
- PureMVC学习系列-从源码深度剖析PureMVC(核心组件工作流程及原理)
- 决策树学习(上)——深度原理剖析及源码实现
- Kinect原理深度剖析
- 深度剖析hdfs原理
- MapReduce 原理深度剖析
- HashSet保证元素唯一性原理图解
- TreeSet保证元素唯一性原理图解
- HashSet保证数据唯一的原理
- HashSet保证元素唯一性的原理
- 谈谈Java利用原始HttpURLConnection发送POST数据
- NOIP模拟(20171031)T2 朋友(BZOJ2143 飞飞侠)
- ceph中的SafeTimer 用法和分析
- Glide-回调与监听(四)
- Spring+SpringMVC+MyBatis+easyUI整合基础篇(七)JDBC url的连接参数
- 唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理
- entrySet和keySet遍历Map的区别
- Kafka 配置参数(非常好的总结)
- ID3算法及C++代码实现
- ubuntu和centos系统的下文件的压缩解压命令
- oracle常用函数(function)
- 为什么发布出去
- recyclerview,滚到指定位置
- 【转】CSS选择器