唯一插件化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源码及原理深度剖析–插件的安装、加载原理

阅读全文
0 0
原创粉丝点击