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 掛在到插件對象上。最後如果當前進程是插件進程就加載默認插件。

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