概述
本文相關系統知識點在 上文 系統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都有自己的類加載器。而如果還是使用雙親委派模型的話,那么只要找到限定名完全相同的類就會返回,那么這個返回的類并不能保證就是我們需要的那個。