Android 插件化框架 Replugin 源码解读(二)hook系统ClassLoader

        在上一章节中我们讲到了Replugin初始化的时候创建了插件管理进程作为服务端。其他工作进程作为客户端,然后分别在各自的进程中初始化自己要做的事情。创建了多个Binder对象用来完成客户端和服务端的信息交互。在完成PmBase 的初始化后。在PMF.init()中还有剩下的部分代码

com.qihoo360.loader2.PMF


 public static final void init(Application application) {
        setApplicationContext(application);

        PluginManager.init(application);

        sPluginMgr = new PmBase(application);
        sPluginMgr.init();

        Factory.sPluginManager = PMF.getLocal();//1
        Factory2.sPLProxy = PMF.getInternal();//2

        PatchClassLoaderUtils.patch(application);//3
    }

"1"中PMF.getLocal()就是在PmBase 构造函数中 创建的PluginCommImpl。这个类负责宿主与插件、插件间的互通,因此这个类中的方法使用的频率会比较高。而Factory类是一个包装类,缓存了PluginCommImpl对象,方便以后的调用。同样的"2"中的PMF.getInternal() 就是在PmBase 中创建的PluginLibraryInternalProxy。同样的这个类中的方法也会经常使用到,比如说startActivity这个方法。

"3"PatchClassLoaderUtils.patch(application)这里的逻辑就涉及到这个框架的核心唯一hook 的点,hook系统的ClassLoader

Replugin唯一hook点 hook系统ClassLoader

Replugin是如何hook 系统的ClassLoader。

//com.qihoo360.loader.utils.PatchClassLoaderUtils

public class PatchClassLoaderUtils {

    

    public static boolean patch(Application application) {
        try {
            // 获取Application的BaseContext (来自ContextWrapper)
            Context oBase = application.getBaseContext();
            if (oBase == null) {
                
                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) {
                
                return false;
            }

            // mPackageInfo的类型主要有两种:
            // 1. android.app.ActivityThread$PackageInfo - Android 2.1 - 2.3
            // 2. android.app.LoadedApk - Android 2.3.3 and higher
            

            // 获取mPackageInfo.mClassLoader
            ClassLoader oClassLoader = (ClassLoader) ReflectUtils.readField(oPackageInfo, "mClassLoader");
            if (oClassLoader == null) {
              
                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);

            
        } catch (Throwable e) {
            e.printStackTrace();
            return false;
        }
        return true;
    }
}

  这里利用了反射工具,是代码看上去非常简洁。主要做了这么几件事情

1. application.getBaseContext() 获取APP的BaseContext

2.利用反射获取BaseContext 中的mPackageInfo字段,他的类型是LoadedApk类型 

3.利用反射获取mPackageInfo 中的mClassLoader字段。

4.通过宿主的父ClassLoader和宿主ClassLoader利用RePluginCallbacks的createClassLoader方法生成RePluginClassLoader

5.替换mPackageInfo.mClassLoader中的系统ClassLoader为我们新生成的RepluginClassLoader.

6.替换线程中的ClassLoader为我们新生成的RepluginClassLoader.

简简单单几步我们就成功的将系统的ClassLoader替换成了我们自己的RePluginClassLoader。这里我们就完成了hook 的工作。那么为什么hook住了ClassLoader我们就可以利用插件化来实现打开插件中的组件呢。我们来看看ClassLoader是如何工作的

ClassLoader如何工作

Android 中的ClassLoader

ClassLoader

ClassLoader 是一个抽象类。主要功能就是 loadClass 和 findClass 

//源码地址:libcore/ojluni/src/main/java/java/lang/Classloader


protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先判断这个class是否已经被加载
            Class<?> c = findLoadedClass(name);
            
            if (c == null) {
                try {
                    //判断父加载器是否存在
                    if (parent != null) {
                        //用父加载器加载这个类
                        c = parent.loadClass(name, false);
                    } else {
                        //这个方法直接返回null
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    //子类实现findClass
                    c = findClass(name);
                }
            }
            return c;
    }

在Java中为了保证一个类的唯一性使用了双亲委派模型,也就是说如果要加载一个类首先会委托给自己的父加载器去完成,父加载器会再向上委托,直到加载器加载过这个类,或者如果没有找到父加载器(到达最顶层),子类才会尝试自己去加载,这样就保证了加载的类都是一个类。一个类的唯一性要由它的类加载器和它本身来确定,也就是说一个Class文件如果使用不同的类加载器来加载,那么加载出来的类也是不相等的。在这里Android 的 ClassLoader 也是使用了双亲委托模式。首先判断这个类是否被加载,如果已经被加载了就返回,没有记载先判断父加载器是都存在,如果存在就用父加载器加载这个类,这样就能够一层层向上委托。如果到最顶层加载器也没加载过,子类才尝试加载。

BootClassLoader

BootClassLoader是ClassLoader的内部类。

//默认修饰
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);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }

    。。。
}

Android 系统启动的时候会使用BootClassLoader来预加载常用类。是ClassLoader的内部类,是一个单例类。类是默认修饰。外部无法访问。所以我们在应用中也无法直接调用。

BaseDexClassLoader

BaseDexClassLoader是ClassLoader的具体实现类,在其构造方法中创建了DexPathList 

public BaseDexClassLoader(String dexPath,
            String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
            boolean isTrusted) {

        super(parent);
        ...
        //创建DexPathList
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null,         isTrusted);

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }


   @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        ...
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        //调用了DexPathList 的findClass 
        Class c = pathList.findClass(name, suppressedExceptions);

        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException(
                    "Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }



BaseDexClassLoader加载类的时候,就是通过调用DexPathList.findClass进行加载。在DexPathList中有 Element[] dexElements 数据 。 而Element 元素其实就是Dex文件。(这可能是一个普通的dex文件(在这种情况下是dexZipPath应该是null),或者一个jar(在这种情况下,dexZipPath应该表示zip文件)。


public Element(DexFile dexFile, File dexZipPath) {

            if (dexFile == null && dexZipPath == null) {
                throw new NullPointerException("Either dexFile or path must be non-null");
            }

            this.dexFile = dexFile;
            this.path = dexZipPath;
          
            this.pathIsDirectory = (path == null) ? null : path.isDirectory();
        }

PathClassLoader和DexClassLoader

public class DexClassLoader extends BaseDexClassLoader {
    
    //android 8.0 及之后的构造函数
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }

    //android 8.0 前的构造函数
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }

}



public class PathClassLoader extends BaseDexClassLoader {
    
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }

   
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }

    

PathClassLoader 和DexClassLoader 都继承自BaseDexClassLoader。而且类中只有构造方法,具体的实现都是在BaseDexClassLoader中完成的,而他两的区别就是 在DexClassLoader的构造函数中 有optimizedDirectory参数。而PathClassLoader中没有,

optmizedDirectory 不为空时,使用用户定义的目录作为 DEX 文件优化后产物 .odex 的存储目录,为空时,会使用默认的 /data/dalvik-cache/ 目录。PathClassLoader 其实并不是只能加载安装后的 APK,也可以加载其他 DEX/JAR/APK 文件,只不过生成的 .odex 文件只能存储在系统默认路径下。不过在Android 8.0 之后optmizedDirectory字段已经弃用,PathClassLoader和DexClassLoader已经没有区别了。

Replugin中的ClassLoader

1.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);

        initMethods(orig);
    }

    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);
    }

    private void copyFromOriginal(ClassLoader orig) {
        

        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);
        }
    }

    private void copyFieldValue(String field, ClassLoader orig) {
        try {
            Field f = ReflectUtils.getField(orig.getClass(), field);
            if (f == null) {
               
                return;
            }

            // 删除final修饰符
            ReflectUtils.removeFieldFinalModifier(f);

            // 复制Field中的值到this里
            Object o = ReflectUtils.readField(f, orig);
            ReflectUtils.writeField(f, this, o);

            
        } catch (IllegalAccessException e) {
            
        }
    }

    @Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //
        Class<?> c = null;
        //先通过类名找找看有没有插件中对应的类,找到了直接用插件的ClassLoader加载
        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);
    }
//还有一些重写的方法。可以去源码中查看
。。。。
}

首先RepluginClassLoader为了兼容Android 7.0以上的LoadedApk.updateApplicationInfo中,对addDexPath方法的依赖, 特将继承关系调整到PathClassLoader,以前是ClassLoader。

RePluginClassLoader在构造方法中将宿主原来ClassLoader中的重要字段拷贝到本对象中,用来欺骗系统,接着反射获取原ClassLoader中的重要方法用来重写这些方法,最后重写了loadClass方法,首先会通过要加载的类名来查找是否存在对应的插件信息,如果有取出插件信息中的ClassLoader,使用该插件的ClassLoader来加载类,如果没有找到再使用宿主原来的ClassLoader来加载

2.PluginDexClassLoader

//com.qihoo360.replugin.PluginDexClassLoader

public class PluginDexClassLoader extends DexClassLoader {

   。。。

    //初始化插件的DexClassLoader的构造函数。插件化框架会调用此函数。
     
    public PluginDexClassLoader(PluginInfo pi, String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super(dexPath, optimizedDirectory, librarySearchPath, parent);

        mPluginName = pi.getName();

        installMultiDexesBeforeLollipop(pi, dexPath, parent);

        mHostClassLoader = RePluginInternal.getAppClassLoader();

        initMethods(mHostClassLoader);
    }

    private static void initMethods(ClassLoader cl) {
        Class<?> clz = cl.getClass();
        if (sLoadClassMethod == null) {
            sLoadClassMethod = ReflectUtils.getMethod(clz, "loadClass", String.class, Boolean.TYPE);
            if (sLoadClassMethod == null) {
                throw new NoSuchMethodError("loadClass");
            }
        }
    }

    @Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        // 插件自己的Class。从自己开始一直到BootClassLoader,采用正常的双亲委派模型流程,读到了就直接返回
        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;

            if (PluginDexClassLoaderPatch.need2LoadFromHost(className)) {
                try {
                    return loadClassFromHost(className, resolve);
                } catch (ClassNotFoundException e1) {
                    // Do not throw "e1" now
                    cnfException = e1;

                   
                }
            } else {
                
            }
        }

        // 若插件里没有此类,则会从宿主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;
    }

。。。。
}

RepluginDexClassLoader 继承自DexClassLoader 。用来做一些“更高级”的特性,在RePluginConfig中可直接配置。原本只需要DexClassLoader即可,但为了要支持一些高级特性(如可自由使用宿主的Class),实现了RepluginDexClassLoader这个类。这个类的工作就是去插件中通过类名寻找相应的类。如果没有找到就去宿主中找,如果要在宿主中找,要把isUseHostClassIfNotFound这个开关打开,默认是关闭的。

结合之前Replugin hook 了系统的PathClassLoader 替换成了自己的RepluginClassLoader。重写了loadClass方法来实现拦截类的加载过程,并且每一个插件apk都设置了一个PluginDexClassLoader,在加载类的时候先使用这个PluginDexClassLoader去加载,加载到了直接返回否则再通过持有系统或者说是宿主原有的PathClassLoader去加载,这样就保证了不管是插件类、宿主类、还是系统类都可以被加载到。这样就实现了插件化加载插件中的类的整个过程。

PMF.callAttach();

最后在attachBaseContext方法中还剩下PMF.callAttach(); 



//com.qihoo360.loader2.PmBase

 final void callAttach() {
        //获取ClassLoader
        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 (rc) {
                        mDefaultPlugin = p;
                        mClient.init(p);
                    }
                }
            }
        }
    }

首先获取ClassLoader 这里是宿主的PathClassLoader。 然后把Context  , PathClassLoader , PluginCommImpl 挂在到插件对象上。最后如果当前进程是插件进程就加载默认插件。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章