360开源的插件化框架Replugin深度剖析

360开源的插件化框架Replugin深度剖析


今日科技快讯

今年7月,高通指控苹果侵犯其多项专利,这些专利与延长智能手机的电池寿命有关。苹果对此指控作出否认,并指称高通的专利是无效的。而近日苹果提出了自己的指控,称高通侵犯了其所拥有的至少八项电池寿命专利,涉及到确保手机芯片的各个组成部分都只消耗最低的电量、在不需要的情况下切断芯片某些部分的供电以及改进睡眠和唤醒功能等。

作者简介

明天就是周六啦,提前祝大家周末愉快!

本篇文章来自 刘镓旗 的投稿。讲解了360开源的插件化框架Replugin的原理,希望大家喜欢!

刘镓旗 的博客地址:

http://blog.csdn.net/yulong0809

前言

首先简单介绍一下 Replugin,Replugin 是360开源的Android插件化框架,它在2017年7月开源后,目前已经成为最火热的插件化框架之一,它的特点是唯一Hook,全工程只有一处Hook了系统类。这无形之中让插件化降低了门槛,以前需要实现插件化需要Hook很多系统类,如果你想Hook住系统类,那么首先你要了解系统类才行,而 Replugin 的唯一Hook点不但降低了插件化的门槛,而且对于稳定性也更强了

我们这一篇就来详解的来分析一下 Hook 这块,本章我们讲从 Hook 系统ClassLoader 的思想和原理进行剖析,如果没有看过上一篇建议先看上一篇:

http://blog.csdn.net/yulong0809/article/details/78423529

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

概要:

一、关于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是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

    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深度剖析

    360开源的插件化框架Replugin深度剖析