唯一插件化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源码及原理深度剖析--初始化之框架核心

Replugin 源码分析------replugin-host-gradle插件源码分析

Replugin 源码分析------replugin-host-gradle插件源码分析