在上一章節中我們講到了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 掛在到插件對象上。最後如果當前進程是插件進程就加載默認插件。