唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理
Posted 刘镓旗
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了唯一插件化Replugin源码及原理深度剖析--唯一Hook点原理相关的知识,希望对你有一定的参考价值。
*本篇文章已授权微信公众号 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的处理,所以这里必须要传一些内容进来
// 但我们最终不用它,而是拷贝所有的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是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,但是上面传入的是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源码及原理深度剖析--工程职责及大纲
唯一插件化Replugin源码及原理深度剖析--初始化之框架核心