美文网首页
Replugin源码解析之replugin-host-libra

Replugin源码解析之replugin-host-libra

作者: PeytonWu | 来源:发表于2017-12-22 16:22 被阅读0次

概述

本文相关系统知识点在 上文 系统ClassLoader相关及Application初始化简单分析及总结 中,由以上文章可知:

系统产生出来的PathClassLoader 仅在
1:packageInfo握有其成员变量引用,
2:当前线程的classLoader
故要替换系统的类加载器只需要从这2个方面入手即可**

接下来就接着 Replugin源码解析之replugin-host-library---多进程初始化及通信 ,由以上文章可知:

RePlugin分别定义了RePluginClassLoader及PluginDexClassLoader,来替代主进程中的classloder及用于加载插件apk。

承接上文剩下未分析的 5.3 PMF.init中最后的PatchClassLoaderUtils.patch(application)及
6.0 PMF.callAttach()为入口分析 ,看replugin 是不是只从以上2点入手 及如何用新的classloader加载插件。

源码分析

com.qihoo360.loader.utils.PatchClassLoaderUtils

public class PatchClassLoaderUtils {

    private static final String TAG = "PatchClassLoaderUtils";

    public static boolean patch(Application application) {
        try {
            // 获取Application的BaseContext (来自ContextWrapper)
            Context oBase = application.getBaseContext();
            if (oBase == null) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "pclu.p: nf mb. ap cl=" + application.getClass());
                }
                return false;
            }

            // 获取mBase.mPackageInfo
            // 1. ApplicationContext - Android 2.1
            // 2. ContextImpl - Android 2.2 and higher
            // 3. AppContextImpl - Android 2.2 and higher
            Object oPackageInfo = ReflectUtils.readField(oBase, "mPackageInfo");
            if (oPackageInfo == null) {
                if (LOGR) {
                    LogRelease.e(PLUGIN_TAG, "pclu.p: nf mpi. mb cl=" + oBase.getClass());
                }
                return false;
            }

            // mPackageInfo的类型主要有两种:
            // 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
            // 2. android.app.LoadedApk - Android 2.3.3 and higher
            if (LOG) {
                Log.d(TAG, "patch: mBase cl=" + oBase.getClass() + "; mPackageInfo cl=" + oPackageInfo.getClass());
            }

            // 获取mPackageInfo.mClassLoader
            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;
            }

            // 外界可自定义ClassLoader的实现,但一定要基于RePluginClassLoader类
            ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);

            // 将新的ClassLoader写入mPackageInfo.mClassLoader
            ReflectUtils.writeField(oPackageInfo, "mClassLoader", cl);

            // 设置线程上下文中的ClassLoader为RePluginClassLoader
            // 防止在个别Java库用到了Thread.currentThread().getContextClassLoader()时,“用了原来的PathClassLoader”,或为空指针
            Thread.currentThread().setContextClassLoader(cl);

            if (LOG) {
                Log.d(TAG, "patch: patch mClassLoader ok");
            }
        } catch (Throwable e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

简单看下其中的com.qihoo360.replugin.RePluginCallbacks.createClassLoader方法

//即简单创建RePluginClassLoader实例
public RePluginClassLoader createClassLoader(ClassLoader parent, ClassLoader original) {
        return new RePluginClassLoader(parent, original);
    }

先获取Application,再反射获取其中的mPackageInfo,(mPackageInfo的类型主要有两种:a. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3;
b. android.app.LoadedApk - Android 2.3.3 and higher),再获取其mClassLoader字段,该字段即为系统创建加载主dex的PathClassLoader,然后以此类加载器为父加载器,创建Replugin自己实现的PathClassLoader子类即RePluginClassLoader,然后 将新创建的PathClassLoader,赋值给上文我们需要替换的2个点 即 packageInfo的成员变量及当前线程的classLoader。到此即成功利用自身的RePluginClassLoader替换宿主的PathClassLoader。
我们看RePluginClassLoader的实现.
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。
我们再从文初说的剩下6.0 PMF.callAttach()为入口,看下是怎么利用另一个ClassLoader即PluginDexClassLoader来加载插件的。

com.qihoo360.loader2 .PMF

public class PMF {
   static PmBase sPluginMgr;
//即调用上文设值进来的PmBase 的callAttach方法
  public static final void callAttach() {
        sPluginMgr.callAttach();
    }
}

接下来看PmBase.callAttach方法
com.qihoo360.loader2.PmBase

    final void callAttach() {
        //
        mClassLoader = PmBase.class.getClassLoader();

        // 挂载
        for (Plugin p : mPlugins.values()) {
            p.attach(mContext, mClassLoader, mLocal);
        }

        // 加载默认插件
        if (PluginManager.isPluginProcess()) {
            if (!TextUtils.isEmpty(mDefaultPluginName)) {
                //
                Plugin p = mPlugins.get(mDefaultPluginName);
                if (p != null) {
                    boolean rc = p.load(Plugin.LOAD_APP, true);
                    if (!rc) {
                        if (LOG) {
                            LogDebug.d(PLUGIN_TAG, "failed to load default plugin=" + mDefaultPluginName);
                        }
                    }
                    if (rc) {
                        mDefaultPlugin = p;
                        mClient.init(p);
                    }
                }
            }
        }
    }

调用Plugin的load方法com.qihoo360.loader2.Plugin

class Plugin {
        final boolean load(int load, boolean useCache) {
        PluginInfo info = mInfo;
      //调用loadLocked方法
        boolean rc = loadLocked(load, useCache);
      ....
}

-----------------------------------
 private boolean loadLocked(int load, boolean useCache) {
  ....
//调用doload方法
boolean rc = doLoad(logTag, context, parent, manager, load);
...


}
------------------------------------------------------
  private final boolean doLoad(String tag, Context context, ClassLoader parent, 
   PluginCommImpl manager, int load) {
        if (mLoader == null) {
       ...
      mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
            if (!mLoader.loadDex(parent, load)) {
                return false;
            }
       ...
     }
  }
}

看下Loader的loadDex方法
com.qihoo360.loader2.Loader

class Loader {
    final boolean loadDex(ClassLoader parent, int load) {
      ...
      if (BuildConfig.DEBUG) {
                    // 因为Instant Run会替换parent为IncrementalClassLoader,所以在DEBUG环境里
                    // 需要替换为BootClassLoader才行
                    // Added by yangchao-xy & Jiongxuan Zhang
                    parent = ClassLoader.getSystemClassLoader();
                } else {
                    // 线上环境保持不变
                    parent = getClass().getClassLoader().getParent(); // TODO: 这里直接用父类加载器
                }
                String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
                mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
      ...
   }
}

调用RePluginCallbacks的createPluginClassLoader方法创建classloader
com.qihoo360.replugin.RePluginCallbacks

 public PluginDexClassLoader createPluginClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        return new PluginDexClassLoader(pi, dexPath, optimizedDirectory, librarySearchPath, parent);
    }

至此第二个classloader即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方法设置。

总结

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-host-libra

      本文链接:https://www.haomeiwen.com/subject/tewwwxtx.html