Replugin源碼解析之replugin-host-library---hook點

概述

本文相關(guān)系統(tǒng)知識點在 上文 系統(tǒng)ClassLoader相關(guān)及Application初始化簡單分析及總結(jié) 中,由以上文章可知:

系統(tǒng)產(chǎn)生出來的PathClassLoader 僅在
1:packageInfo握有其成員變量引用,
2:當前線程的classLoader
故要替換系統(tǒng)的類加載器只需要從這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的實現(xiàn),但一定要基于RePluginClassLoader類
            ClassLoader cl = RePlugin.getConfig().getCallbacks().createClassLoader(oClassLoader.getParent(), oClassLoader);

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

            // 設(shè)置線程上下文中的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方法

//即簡單創(chuàng)建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字段,該字段即為系統(tǒng)創(chuàng)建加載主dex的PathClassLoader,然后以此類加載器為父加載器,創(chuàng)建Replugin自己實現(xiàn)的PathClassLoader子類即RePluginClassLoader,然后 將新創(chuàng)建的PathClassLoader,賦值給上文我們需要替換的2個點 即 packageInfo的成員變量及當前線程的classLoader。到此即成功利用自身的RePluginClassLoader替換宿主的PathClassLoader。
我們看RePluginClassLoader的實現(xiàn).
com.qihoo360.replugin.RePluginClassLoader

public class RePluginClassLoader extends PathClassLoader{

    。。。。

    public RePluginClassLoader(ClassLoader parent, ClassLoader orig) {

        // 由于PathClassLoader在初始化時會做一些Dir的處理,所以這里必須要傳一些內(nèi)容進來
        // 但我們最終不用它,而是拷貝所有的Fields
        super("", "", parent);
        mOrig = orig;

        // 將原來宿主里的關(guān)鍵字段,拷貝到這個對象上,這樣騙系統(tǒng)以為用的還是以前的東西(尤其是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,有一堆字段,需要逐一復(fù)制
            // 以下方法在較慢的手機上用時:8ms左右
            copyFieldValue("libPath", orig);
            copyFieldValue("libraryPathElements", orig);
            copyFieldValue("mDexs", orig);
            copyFieldValue("mFiles", orig);
            copyFieldValue("mPaths", orig);
            copyFieldValue("mZips", orig);
        } else {
            // Android 4.0以上只需要復(fù)制pathList即可
            // 以下方法在較慢的手機上用時:1ms
            copyFieldValue("pathList", orig);
        }
    }

    //重寫了ClassLoader的loadClass
    @Override
    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {

        Class<?> c = null;

        //攔截類的加載過程,判斷要加載的類是否存在對應(yīng)的插件信息,如果有從插件中加載
        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);
    }


    //重寫反射的方法,執(zhí)行的是原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在構(gòu)造方法中將宿主原來ClassLoader中的重要字段拷貝到本對象中,用來欺騙系統(tǒng),接著反射獲取原ClassLoader中的重要方法用來實現(xiàn)自身對應(yīng)這些方法。
最后重寫了loadClass方法,首先會通過要加載的類名來查找是否存在對應(yīng)的插件信息,如果有取出插件信息中的ClassLoader,使用該插件的ClassLoader來加載類,如果沒有找到再使用宿主原來的ClassLoader來加載,插件使用的ClassLoader就是Replugin中的另一個ClassLoader,PluginDexClassLoader。
我們再從文初說的剩下6.0 PMF.callAttach()為入口,看下是怎么利用另一個ClassLoader即PluginDexClassLoader來加載插件的。

com.qihoo360.loader2 .PMF

public class PMF {
   static PmBase sPluginMgr;
//即調(diào)用上文設(shè)值進來的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);
                    }
                }
            }
        }
    }

調(diào)用Plugin的load方法com.qihoo360.loader2.Plugin

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

-----------------------------------
 private boolean loadLocked(int load, boolean useCache) {
  ....
//調(diào)用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環(huán)境里
                    // 需要替換為BootClassLoader才行
                    // Added by yangchao-xy & Jiongxuan Zhang
                    parent = ClassLoader.getSystemClassLoader();
                } else {
                    // 線上環(huán)境保持不變
                    parent = getClass().getClassLoader().getParent(); // TODO: 這里直接用父類加載器
                }
                String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
                mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
      ...
   }
}

調(diào)用RePluginCallbacks的createPluginClassLoader方法創(chuàng)建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(用于加載插件)的類加載器出現(xiàn)。
我們看下其實現(xiàn)

public class PluginDexClassLoader extends DexClassLoader {

//構(gòu)造方法
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開關(guān)。默認為關(guān)閉的??蓞⒁娫撻_關(guān)的說明
    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;
}

//通過在構(gòu)造方法中反射原宿主的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的做法一樣

這里就比較簡單了,因為插件是依賴于宿主生存的,這里只需要將要查找的類找到并返回就可以了,至于其他的操作已經(jīng)由上面的RePluginClassLoader來處理了,這里還處理了如果插件中早不到類,會去宿主中查找,這里會有一個開關(guān),默認是關(guān)閉的,可以通過RePluginConfig的setUseHostClassIfNotFound方法設(shè)置。

總結(jié)

Replugin通過Hook住系統(tǒng)的PathClassLoader并重寫了loadClass方法來實現(xiàn)攔截類的加載過程,并且每一個插件apk都設(shè)置了一個PluginDexClassLoader,在加載類的時候先使用這個PluginDexClassLoader去加載,加載到了直接返回否則再通過持有系統(tǒng)或者說是宿主原有的PathClassLoader去加載,這樣就保證了不管是插件類、宿主類、還是系統(tǒng)類都可以被加載到。

那么說到思想,Replugin這么做的思想是什么?其實我覺得是破壞了ClassLoader的雙親委派模型,或者說叫打破這種模型,為什么這樣說?首先雙親委派模型是層層向上委托的樹形加載,而Replugin在收到類加載請求時直接先使用了插件ClassLoader來嘗試加載,這樣的加載模式應(yīng)該算是網(wǎng)狀加載,所以說Replugin是通過Hook系統(tǒng)ClassLoader來做到破壞了ClassLoader的雙親委派模型,我們再回想一下上一章我們分析過的Replugin框架代碼中,Replugin將所以插件apk封裝成一個Plugin對象統(tǒng)一在插件管理進程中管理,而每一個插件apk都有屬于自己的ClassLoader,在類被加載的時候首先會使用插件自己的ClassLoader去嘗試加載,這樣做的好處是,可以精確的加載到需要的那個類,而如果使用雙親委派只要找到一個同路徑的類就返回,那么這個被返回的類有可能并不是我們需要的那個類。

舉個例子,例如兩個插件apk中有一個路徑和名字完全相同的類,如果使用這種網(wǎng)狀加載可以精確的加載到這個類,因為每一個插件apk都有自己的類加載器。而如果還是使用雙親委派模型的話,那么只要找到限定名完全相同的類就會返回,那么這個返回的類并不能保證就是我們需要的那個。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容