//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);
}
}
}
}
}
上一篇中我們hook住系統的ClassLoader之後就調用callAttach()方法加載默認插件。首先通過設置的插件名字從插件表中獲取插件。是一個Plugin 對象,然後通過Plugin.load 實現加載。
Plugin.load
//com.qihoo360.loader2.Plugin
final boolean load(int load, boolean useCache) {
PluginInfo info = mInfo;
//加載插件
boolean rc = loadLocked(load, useCache);
// 嘗試在此處調用Application.onCreate方法
if (load == LOAD_APP && rc) {
callApp();
}
// 如果info改了,通知一下常駐
// 只針對P-n的Type轉化來處理,一定要通知,這樣Framework_Version也會得到更新
if (rc && mInfo != info) {
UpdateInfoTask task = new UpdateInfoTask((PluginInfo) mInfo.clone());
Tasks.post2Thread(task);
}
return rc;
}
1.這裏通過調用loadLocked 方法來加載插件
//com.qihoo360.loader2.Plugin
private boolean loadLocked(int load, boolean useCache) {
//判斷插件是否禁用
int status = PluginStatusController.getStatus(mInfo.getName(), mInfo.getVersion());
if (status < PluginStatusController.STATUS_OK) {
return false;
}
//判斷是否加載過,加載過就直接返回
if (mInitialized) {
if (mLoader == null) {
return false;
}
if (load == LOAD_INFO) {
boolean rl = mLoader.isPackageInfoLoaded();
return rl;
}
if (load == LOAD_RESOURCES) {
boolean rl = mLoader.isResourcesLoaded();
return rl;
}
if (load == LOAD_DEX) {
boolean rl = mLoader.isDexLoaded();
return rl;
}
boolean il = mLoader.isAppLoaded();
return il;
}
mInitialized = true;
。。。
// 這裏先處理一下,如果cache命中,省了後面插件提取(如釋放Jar包等)操作
if (useCache) {
boolean result = loadByCache(load);
// 如果緩存命中,則直接返回
if (result) {
return true;
}
}
Context context = mContext;
ClassLoader parent = mParent;
PluginCommImpl manager = mPluginManager;
String lockFileName = String.format(Constant.LOAD_PLUGIN_LOCK, mInfo.getApkFile().getName());
//創建進程鎖
ProcessLocker lock = new ProcessLocker(context, lockFileName);
long t1 = System.currentTimeMillis();
//加載插件
boolean rc = doLoad(logTag, context, parent, manager, load);
//解鎖
lock.unlock();
if (!rc) {
}
if (rc) {
try {
// 至此,該插件已開始運行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
}
return true;
}
再鎖一次
lock = new ProcessLocker(context, lockFileName);
// 刪除優化dex文件
File odex = mInfo.getDexFile();
if (odex.exists()) {
odex.delete();
}
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
// support for multidex below LOLLIPOP:delete Extra odex,if need
try {
FileUtils.forceDelete(mInfo.getExtraOdexDir());
} catch (IOException e) {
e.printStackTrace();
} catch (IllegalArgumentException e2) {
e2.printStackTrace();
}
}
t1 = System.currentTimeMillis();
// 嘗試再次加載該插件
rc = tryLoadAgain(logTag, context, parent, manager, load);
lock.unlock();
if (!rc) {
return false;
}
try {
// 至此,該插件已開始運行
PluginManagerProxy.addToRunningPluginsNoThrows(mInfo.getName());
} catch (Throwable e) {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.u.2: " + e.getMessage(), e);
}
}
return true;
}
2.在loadLocked方法中先判斷插件是否被禁用,然後判斷是否被加載過,加載過就返回,再判斷緩存中有沒有,有的話也返回,都沒有就去加載,先創建進程鎖,鎖住進程加載插件過程,調用doLoad 方法加載,加載成功就調用PluginManagerProxy.addToRunningPluginsNoThrows方法添加插件到"當前進程的正在運行插件列表",並同步到Server端。如果失敗就再加載一次插件。
//com.qihoo360.loader2.Plugin
private final boolean doLoad(String tag, Context context, ClassLoader parent, PluginCommImpl manager, int load) {
if (mLoader == null) {
// 試圖釋放文件
PluginInfo info = null;
//內置插件
if (mInfo.getType() == PluginInfo.TYPE_BUILTIN) {
//判斷是否爲內置插件,如果是內置插件,在檢查是否已經釋放了so庫到指定位置,如果沒有先釋放到指定位置,並重新構造PluginInfo
。。。。。。
//p-n 插件
} else if (mInfo.getType() == PluginInfo.TYPE_PN_JAR) {
//判斷是否爲p-n類型並且未執行安裝插件步驟,如果是p-n類型並且未安裝,先執行插件的安裝操作,並重新構造PluginInfo
。。。。。。。
} else {
//
}
//
if (info != null) {
// 替換
mInfo = info;
}
//插件加載類
mLoader = new Loader(context, mInfo.getName(), mInfo.getPath(), this);
//加載插件數據
if (!mLoader.loadDex(parent, load)) {
return false;
}
// 設置插件爲“使用過的”
// 注意,需要重新獲取當前的PluginInfo對象,而非使用“可能是新插件”的mInfo
try {
PluginManagerProxy.updateUsedIfNeeded(mInfo.getName(), true);
} catch (RemoteException e) {
// 同步出現問題,但仍繼續進行
if (LOGR) {
e.printStackTrace();
}
}
// 若需要加載Dex,則還同時需要初始化插件裏的Entry對象
if (load == LOAD_APP) {
// NOTE Entry對象是可以在任何線程中被調用到
if (!loadEntryLocked(manager)) {
return false;
}
// NOTE 在此處調用則必須Post到UI,但此時有可能Activity已被加載
// 會出現Activity.onCreate比Application更早的情況,故應放在load外面立即調用
// callApp();
}
}
if (load == LOAD_INFO) {
return mLoader.isPackageInfoLoaded();
} else if (load == LOAD_RESOURCES) {
return mLoader.isResourcesLoaded();
} else if (load == LOAD_DEX) {
return mLoader.isDexLoaded();
} else {
return mLoader.isAppLoaded();
}
}
3.這裏如果沒有加載過,先判斷插件類型,如果是內置插件判斷so庫等是否已經釋放。沒有的話就釋放到指定的位置。如果是p-n插件然後創建加載類Loader 通過loadDex方法加載插件。加載成功後設置插件信息爲 "使用過"。然後他通過callApp啓動插件
//com.qihoo360.loader2.Loader;
final boolean loadDex(ClassLoader parent, int load) {
try {
//獲取PackageManager
PackageManager pm = mContext.getPackageManager();
//查看緩存
mPackageInfo = Plugin.queryCachedPackageInfo(mPath);
if (mPackageInfo == null) {
// 緩存沒有PackageInfo 創建一個
mPackageInfo = pm.getPackageArchiveInfo(mPath,
PackageManager.GET_ACTIVITIES | PackageManager.GET_SERVICES | PackageManager.GET_PROVIDERS | PackageManager.GET_RECEIVERS | PackageManager.GET_META_DATA);
//插件不存在返回
if (mPackageInfo == null || mPackageInfo.applicationInfo == null) {
mPackageInfo = null;
return false;
}
//指定插件資源路徑
mPackageInfo.applicationInfo.sourceDir = mPath;
mPackageInfo.applicationInfo.publicSourceDir = mPath;
// 添加針對SO庫的加載
// 此屬性最終用於ApplicationLoaders.getClassLoader,在創建PathClassLoader時成爲其參數
// 這樣findLibrary可不用覆寫,即可直接實現SO的加載
PluginInfo pi = mPluginObj.mInfo;
File ld = pi.getNativeLibsDir();
mPackageInfo.applicationInfo.nativeLibraryDir = ld.getAbsolutePath();
// 緩存表: pkgName -> pluginName
synchronized (Plugin.PKG_NAME_2_PLUGIN_NAME) {
Plugin.PKG_NAME_2_PLUGIN_NAME.put(mPackageInfo.packageName, mPluginName);
}
// 緩存表: pluginName -> fileName
synchronized (Plugin.PLUGIN_NAME_2_FILENAME) {
Plugin.PLUGIN_NAME_2_FILENAME.put(mPluginName, mPath);
}
// 緩存表: fileName -> PackageInfo
synchronized (Plugin.FILENAME_2_PACKAGE_INFO) {
Plugin.FILENAME_2_PACKAGE_INFO.put(mPath, new WeakReference<PackageInfo>(mPackageInfo));
}
}
。。。。。。
// 創建或獲取ComponentList表
mComponents = Plugin.queryCachedComponentList(mPath);
if (mComponents == null) {
// ComponentList
mComponents = new ComponentList(mPackageInfo, mPath, mPluginObj.mInfo);
// 動態註冊插件中聲明的 receiver
regReceivers();
// 緩存表:ComponentList
synchronized (Plugin.FILENAME_2_COMPONENT_LIST) {
Plugin.FILENAME_2_COMPONENT_LIST.put(mPath, new WeakReference<>(mComponents));
}
/* 只調整一次 */
// 調整插件中組件的進程名稱
adjustPluginProcess(mPackageInfo.applicationInfo);
// 調整插件中 Activity 的 TaskAffinity
adjustPluginTaskAffinity(mPluginName, mPackageInfo.applicationInfo);
}
//如果是加載插件中組件信息返回
if (load == Plugin.LOAD_INFO) {
return isPackageInfoLoaded();
}
//獲取緩存中插件中的資源
mPkgResources = Plugin.queryCachedResources(mPath);
// LOAD_RESOURCES和LOAD_ALL都會獲取資源,但LOAD_INFO不可以(只允許獲取PackageInfo)
//沒有命中緩存獲取插件中的資源
if (mPkgResources == null) {
// Resources
try {
if (BuildConfig.DEBUG) {
// 如果是Debug模式的話,防止與Instant Run衝突,資源重新New一個
Resources r = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
mPkgResources = new Resources(r.getAssets(), r.getDisplayMetrics(), r.getConfiguration());
} else {
mPkgResources = pm.getResourcesForApplication(mPackageInfo.applicationInfo);
}
} catch (NameNotFoundException e) {
return false;
}
if (mPkgResources == null) {
return false;
}
// 緩存表: Resources
synchronized (Plugin.FILENAME_2_RESOURCES) {
Plugin.FILENAME_2_RESOURCES.put(mPath, new WeakReference<>(mPkgResources));
}
}
//資源加載在這裏返回
if (load == Plugin.LOAD_RESOURCES) {
return isResourcesLoaded();
}
//獲取緩存中插件的ClassLoader
mClassLoader = Plugin.queryCachedClassLoader(mPath);
//沒有命中緩存
if (mClassLoader == null) {
// 先獲取父類加載器
String out = mPluginObj.mInfo.getDexParentDir().getPath();
if (BuildConfig.DEBUG) {
parent = ClassLoader.getSystemClassLoader();
} else {
// 線上環境保持不變
parent = getClass().getClassLoader().getParent(); // TODO: 這裏直接用父類加載器
}
//設置so 文件路徑
String soDir = mPackageInfo.applicationInfo.nativeLibraryDir;
//創建插件自己的PluginDexClassLoader
mClassLoader = RePlugin.getConfig().getCallbacks().createPluginClassLoader(mPluginObj.mInfo, mPath, out, soDir, parent);
if (mClassLoader == null) {
return false;
}
// 緩存表:ClassLoader
synchronized (Plugin.FILENAME_2_DEX) {
Plugin.FILENAME_2_DEX.put(mPath, new WeakReference<>(mClassLoader));
}
}
//加載dex在這裏返回
if (load == Plugin.LOAD_DEX) {
return isDexLoaded();
}
//創建插件apk使用的Context對象
mPkgContext = new PluginContext(mContext, android.R.style.Theme, mClassLoader, mPkgResources, mPluginName, this);
} catch (Throwable e) {
return false;
}
return true;
}
4.在loadDex方法中首先獲取插件的mPackageInfo判斷插件是否存在,並且指定插件apk的資源路徑,包括so庫的路徑最後把pluginName mPath mPackageInfo 緩存起來。
然後創建ComponentList對象 這個對象用來快速獲取四大組件和Application的系統Info的List 每個Plugin對象維護一份ComponentList,且在第一次加載PackageInfo時被生成。
/**
* Class類名 - Activity的Map表
*/
final HashMap<String, ActivityInfo> mActivities = new HashMap<>();
/**
* Class類名 - Provider的Map表
*/
final HashMap<String, ProviderInfo> mProvidersByName = new HashMap<>();
/**
* Authority - Provider的Map表
*/
final HashMap<String, ProviderInfo> mProvidersByAuthority = new HashMap<>();
/**
* Class類名 - Service的Map表
*/
final HashMap<String, ServiceInfo> mServices = new HashMap<>();
/**
* Application對象
*/
ApplicationInfo mApplication = null;
/**
* Class類名 - BroadcastReceiver的Map表
* 注意:是的,你沒有看錯,系統緩存Receiver就是用的ActivityInfo
*/
final HashMap<String, ActivityInfo> mReceivers = new HashMap<>();
ComponentList在創建的時候解析了AndroidManifest.xml文件獲取四大組件信息生成組件與 IntentFilter 的對應關係並將這些信息和四大組件生成對應表關係並緩存,緩存插件apk的Application信息對象,接着動態註冊插件apk中在AndroidManifest中聲明的receiver,調整插件apk中四大組件的自定義進程名稱爲宿主坑位進程名稱和Activity的TaskAffinity
之後先通過pm.getResourcesForApplication創建插件apk的要使用的Resources對象 然後創建插件自己的ClassLoader對象PathClassLoader。這裏是PluginDexClassLoader 緩存這個ClassLoader對象
最後創建插件用的Context對象 。到這裏加載插件的準備工作都已經做好了,該有的數據都有了,現在再看看第3步中loadEntryLocked方法
//com.qihoo360.loader2.Plugin;
private boolean loadEntryLocked(PluginCommImpl manager) {
if (mDummyPlugin) {
mLoader.mPlugin = new IPlugin() {
@Override
public IModule query(Class<? extends IModule> c) {
return null;
}
};
} else {
//嘗試反射調用插件工程中Entry的create方法
if (mLoader.loadEntryMethod2()) {
//執行Entry的create方法
if (!mLoader.invoke2(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod(false)) {
if (!mLoader.invoke(manager)) {
return false;
}
} else if (mLoader.loadEntryMethod3()) {
if (!mLoader.invoke2(manager)) {
return false;
}
} else {
if (LOGR) {
LogRelease.e(PLUGIN_TAG, "p.lel f " + mInfo.getName());
}
return false;
}
}
return true;
}
//com.qihoo360.loader2.Loader
final boolean loadEntryMethod3() {
try {
// PLUGIN_ENTRY_PACKAGE_PREFIX = "com.qihoo360.plugin";
//PLUGIN_ENTRY_CLASS_NAME = "Entry";
String className = Factory.REPLUGIN_LIBRARY_ENTRY_PACKAGE_PREFIX + "." + Factory.PLUGIN_ENTRY_CLASS_NAME;
Class<?> c = mClassLoader.loadClass(className);
//PLUGIN_ENTRY_EXPORT_METHOD_NAME = "create"
mCreateMethod2 = c.getDeclaredMethod(Factory.PLUGIN_ENTRY_EXPORT_METHOD_NAME, Factory.PLUGIN_ENTRY_EXPORT_METHOD2_PARAMS);
} catch (Throwable e) {
}
return mCreateMethod2 != null;
}
//com.qihoo360.replugin.Entry
public static final IBinder create(Context context, ClassLoader cl, IBinder manager) {
// 初始化插件框架,就是反射主工程框架中的類或者方法,方便插件工程之後直接調用
RePluginFramework.init(cl);
// 初始化主工程傳遞過來的Context和ClassLoader
RePluginEnv.init(context, cl, manager);
//返回插件工程中的服務管理Binder對象
return new IPlugin.Stub() {
@Override
public IBinder query(String name) throws RemoteException {
return RePluginServiceManager.getInstance().getService(name);
}
};
}
5.這邊通過反射調用插件工程中的Entry 的create 方法 並執行create 方法。這裏create方法反射了主工程框架中的一些方法,讓插件可以使用宿主的功能,接着就是緩存持有主工程傳遞過來的插件Context對象,這個Context是插件工程自己用的,還通過這個Context獲取了宿主的Context對象,這個對象主要是爲了用來獲取一些宿主中的資源,反射類等一些信息的,還緩存了宿主的ClassLoader對象,也是用來方便反射宿主中的一些類的,最後返回了插件工程中管理服務的Binder對象。具體插件初始化過程會在之後詳細介紹。
// 確保在UI線程中調用
private void callApp() {
if (Looper.myLooper() == Looper.getMainLooper()) {
callAppLocked();
} else {
// 確保一定在UI的最早消息處調用
mMainH.postAtFrontOfQueue(new Runnable() {
@Override
public void run() {
callAppLocked();
}
});
}
}
6.在load完成之後調用callApp方法創建插件apk 的application 在UI線程中調用callAppLocked方法
private void callAppLocked() {
// 獲取並調用Application的幾個核心方法
if (!mDummyPlugin) {
// NOTE 不排除A的Application中調到了B,B又調回到A,或在同一插件內的onCreate開啓Service/Activity,而內部邏輯又調用fetchContext並再次走到這裏
// NOTE 因此需要對mApplicationClient做判斷,確保永遠只執行一次,無論是否成功
if (mApplicationClient != null) {
// 已經初始化過,無需再次處理
return;
}
//創建插件的application幷包裝成PluginApplicationClient返回
mApplicationClient = PluginApplicationClient.getOrCreate(
mInfo.getName(), mLoader.mClassLoader, mLoader.mComponents, mLoader.mPluginObj.mInfo);
if (mApplicationClient != null) {
//調用Application的AttachBaseContext方法
mApplicationClient.callAttachBaseContext(mLoader.mPkgContext);
//調用Application的OnCreate方法
mApplicationClient.callOnCreate();
}
} else {
}
}
7 創建了插件的appliication 幷包裝成PluginApplicationClient 。然後執行插件application 的 attach和onCreate方法。到這裏插件就加載完畢了,此時雖然沒有插件的組件調用,但是插件的application已經啓動了,也就是說其實插件已經啓動了。
總結
到這裏我們講解了Replugin框架初始化的內容。包含的內容我總結起來畫了一個簡單的圖。