Android動態(tài)替換Application實現(xiàn)
背景
雖然熱更新和Hook技術都被大家聊爛了,但是還是想和大家聊一下這方面的內(nèi)容。最近做一些Android方面的優(yōu)化工作,大家知道Android的ClassLoader在加載dex文件的過程中,而AndroidManifest的Application類就在dex文件中,Application通常會做一些全局的初始化工作,在加載dex之前,我們需要替換原有的Application為ProxyApplication。使其應用啟動時加載ProxyApplication,然后在其中實現(xiàn)加載dex等一些流程處理。而后要替換回原有的Application(以下稱為RealApplication),確保應用正常運行,并且要保持生命周期、初始化順序不變,屏蔽對于應用中getContext,getApplicationContext的影響。
在替換Application的過程中,應該注意以下幾點:
- 創(chuàng)建RealApplication,維護正常的生命周期,并進行回調(diào)。
- 對應用中屏蔽掉ProxyApplication,對于下層無感知。在Activity等調(diào)用getApplicationContext之后,應該返回RealApplication。
- ContentProvider創(chuàng)建時機比較特殊,在滿足正常的初始化順序之后,也要屏蔽ProxyApplication的存在。
方案實現(xiàn)
在AndroidManifest.xml文件中替換Application為ProxyApplication,可以使用自動化方式,或者打包方式,關于實現(xiàn)的具體細節(jié)此處不討論。這里主要敘述創(chuàng)建RealApplication的過程。替換了ProxyApplication之后,對于系統(tǒng)而言ProxyApplication就是應用初始化的入口,所有的回調(diào)均是在ProxyApplication中發(fā)生。我們主要關注attachBaseContext和onCreate的回調(diào)。
創(chuàng)建RealApplication
創(chuàng)建RealApplication,我們可以使用反射的方式newInstance創(chuàng)建對象,然后執(zhí)行回調(diào)attachBaseContext。但是對于不同的系統(tǒng)版本,內(nèi)部執(zhí)行的細節(jié)可能不同,或者有其它相關邏輯的處理,所以我們采用另一種方式進行處理。首先看系統(tǒng)源碼的如何實現(xiàn),這里選擇8.0.0的系統(tǒng)源碼進行分析,其它版本去http://androidxref.com/查看。
我們知道,Android初始化是從android.app.ActivityThread開始的,所以從ActivityThread開始查看,ActivityThread中存在靜態(tài)方法currentActivityThread返回實例??梢詤⒖枷到y(tǒng)的ActivityThread類:
public static ActivityThread currentActivityThread() {
return sCurrentActivityThread;
}
ActivityThread內(nèi)部存在成員變量AppBindData mBoundApplication。AppBindData是一個靜態(tài)內(nèi)部類,其中包含成員變量LoadedApk info。查看android.app.LoadedApk源代碼,發(fā)現(xiàn)創(chuàng)建Application的makeApplication方法。
public Application makeApplication(boolean forceDefaultAppClass,
Instrumentation instrumentation) {
if (mApplication != null) {
return mApplication;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "makeApplication");
Application app = null;
String appClass = mApplicationInfo.className;
if (forceDefaultAppClass || (appClass == null)) {
appClass = "android.app.Application";
}
try {
java.lang.ClassLoader cl = getClassLoader();
if (!mPackageName.equals("android")) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER,
"initializeJavaContextClassLoader");
initializeJavaContextClassLoader();
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
}
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(
cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
mActivityThread.mAllApplications.add(app);
mApplication = app;
if (instrumentation != null) {
try {
instrumentation.callApplicationOnCreate(app);
} catch (Exception e) {
if (!instrumentation.onException(app, e)) {
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
throw new RuntimeException(
"Unable to create application " + app.getClass().getName()
+ ": " + e.toString(), e);
}
}
}
// Rewrite the R 'constants' for all library apks.
SparseArray<String> packageIdentifiers = getAssets(mActivityThread)
.getAssignedPackageIdentifiers();
final int N = packageIdentifiers.size();
for (int i = 0; i < N; i++) {
final int id = packageIdentifiers.keyAt(i);
if (id == 0x01 || id == 0x7f) {
continue;
}
rewriteRValues(getClassLoader(), packageIdentifiers.valueAt(i), id);
}
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return app;
}
通過上面的代碼可以發(fā)現(xiàn),如果緩存mApplication不為空,則直接返回。mApplication為空時,則創(chuàng)建RealApplication,并且執(zhí)行相關的回調(diào),創(chuàng)建RealApplication時,類名是從mApplicationInfo.className中獲取。
添加新創(chuàng)建RealApplication到mActivityThread.mAllApplications。賦值給緩存mApplication。所以我們在調(diào)用makeApplication之前,需要將mApplication置為null,否則會直接返回ProxyApplication的實例。
首先,通過android.app.ActivityThread中靜態(tài)方法獲取ActivityThread實例,然后,通過ActivityThread實例,獲得LoadedApk實例。為了使makeApplication順利執(zhí)行,先設置mApplication為null。移除mAllApplications中ProxyApplication的實例。LoadedApk中mApplicationInfo和AppBindData中appInfo都是ApplicationInfo類型,需要分別替換className字段的值為RealApplication的實際類全名。
之后,反射調(diào)用系統(tǒng)的makeApplication.

這樣,在ProxyApplication.attachBaseContext中,調(diào)用makeApplication創(chuàng)建RealApplication,并且內(nèi)部已經(jīng)完成對于RealApplication的attchBaseContext的回調(diào)。在ProxyApplication.onCreate中只需要回調(diào)RealApplication實例的onCreate,即可完成對于RealApplication的創(chuàng)建,已經(jīng)內(nèi)部替換以及正常的生命周期的回調(diào)。而且在Activity中調(diào)用getApplicationContext返回的值,實際上也是LoadedApk中mApplication的值,同時也保證對于Activity等地方屏蔽ProxyApplication的目的。
ContentProvider中getContext
Application和ContentProvider的初始化順序是:Application.attachBaseContext -> ContentProvider.onCreate -> Application.onCreate。ContentProvider中也存在getContext方法,看ContentProvider的源代碼實現(xiàn):

其中mContext被賦值的有兩個地方,一個在構造方法,一個是attchInfo的時候。繼續(xù)追蹤源代碼中使用構造方法初始化,或者調(diào)用attachInfo的地方,結果在android.app.ActivityThread中找到installProvider方法中存在著調(diào)用關系。
可以看出,使用反射調(diào)用ContentProvider無參構造方法創(chuàng)建實例,然后調(diào)用了attachInfo,傳遞的Context為installProvider方法中的參數(shù),而installProvider的參數(shù)是在installContentProviders內(nèi)部在初始化中傳遞的。

可以明確,installContentProviders中調(diào)用installProvider時傳遞的Context,也是由方法調(diào)用時傳遞的參數(shù)。繼續(xù)向上追蹤發(fā)現(xiàn)ActivityThread.handleBindApplication在初始化ContentProvider時調(diào)用了installContentProviders,最終通過attachInfo設置給ContentProvider中的Context的實際類型是Application。
在App初始化時,系統(tǒng)調(diào)用makeApplication創(chuàng)建了ProxyApplication實例,同時回調(diào)了attachBaseContext(Context context)。所以這個方法返回的就是App初始化時ProxyApplication,調(diào)用發(fā)生ProxyApplication.attachBaseContext之后,ProxyApplication.onCreate之前。所以我們沒有辦法在這兩個方法生命周期內(nèi)進行替換為RealApplication。
這種方案,接入成本比較低,但是新系統(tǒng)出現(xiàn)之后,可能出現(xiàn)兼容性的問題,需要每次發(fā)布新系統(tǒng)之后進行相關的適配。但是這種Hook解決問題的思路,可以借鑒一下。
以上就是本文的全部內(nèi)容,希望對大家的學習有所幫助,也希望大家多多支持腳本之家。
相關文章
Android自定義ViewGroup實現(xiàn)豎向引導界面
這篇文章主要為大家詳細介紹了Andoird自定義ViewGroup實現(xiàn)豎向引導界面,具有一定的參考價值,感興趣的小伙伴們可以參考一下2019-03-03
Android 中啟動自己另一個程序的activity如何實現(xiàn)
這篇文章主要介紹了Android 中啟動自己另一個程序的activity如何實現(xiàn)的相關資料,需要的朋友可以參考下2017-04-04
android 中viewpager+fragment仿微信底部TAG完美漸變
這篇文章主要介紹了android 中viewpager+fragment仿微信底部TAG完美漸變,需要的朋友可以參考下2017-05-05
Android tabLayout+recyclerView實現(xiàn)錨點定位的示例
這篇文章主要介紹了Android tabLayout+recyclerView實現(xiàn)錨點定位的示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-08-08

