天天看點

Android動态替換Application實作

背景

雖然熱更新和Hook技術都被大家聊爛了,但是還是想和大家聊一下這方面的内容。最近做一些Android方面的優化工作,大家知道Android的ClassLoader在加載dex檔案的過程中,而AndroidManifest的Application類就在dex檔案中,Application通常會做一些全局的初始化工作,在加載dex之前,我們需要替換原有的Application為ProxyApplication。使其應用啟動時加載ProxyApplication,然後在其中實作加載dex等一些流程處理。而後要替換回原有的Application(以下稱為RealApplication),確定應用正常運作,并且要保持生命周期、初始化順序不變,屏蔽對于應用中getContext,getApplicationContext的影響。

在替換Application的過程中,應該注意以下幾點:

  1. 建立RealApplication,維護正常的生命周期,并進行回調。
  2. 對應用中屏蔽掉ProxyApplication,對于下層無感覺。在Activity等調用getApplicationContext之後,應該傳回RealApplication。
  3. ContentProvider建立時機比較特殊,在滿足正常的初始化順序之後,也要屏蔽ProxyApplication的存在。

方案實作

在AndroidManifest.xml檔案中替換Application為ProxyApplication,可以使用自動化方式,或者打包方式,關于實作的具體細節此處不讨論。這裡主要叙述建立RealApplication的過程。替換了ProxyApplication之後,對于系統而言ProxyApplication就是應用初始化的入口,所有的回調均是在ProxyApplication中發生。我們主要關注attachBaseContext和onCreate的回調。

建立RealApplication

建立RealApplication,我們可以使用反射的方式newInstance建立對象,然後執行回調attachBaseContext。但是對于不同的系統版本,内部執行的細節可能不同,或者有其它相關邏輯的處理,是以我們采用另一種方式進行處理。首先看系統源碼的如何實作,這裡選擇8.0.0的系統源碼進行分析,其它版本去http://androidxref.com/檢視。

我們知道,Android初始化是從android.app.ActivityThread開始的,是以從ActivityThread開始檢視,ActivityThread中存在靜态方法currentActivityThread傳回執行個體。可以參考系統的ActivityThread類:

public static ActivityThread currentActivityThread() {
  return sCurrentActivityThread;
 }           

複制

ActivityThread内部存在成員變量AppBindData mBoundApplication。AppBindData是一個靜态内部類,其中包含成員變量LoadedApk info。檢視android.app.LoadedApk源代碼,發現建立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;
 }           

複制

通過上面的代碼可以發現,如果緩存mApplication不為空,則直接傳回。mApplication為空時,則建立RealApplication,并且執行相關的回調,建立RealApplication時,類名是從mApplicationInfo.className中擷取。

添加新建立RealApplication到mActivityThread.mAllApplications。指派給緩存mApplication。是以我們在調用makeApplication之前,需要将mApplication置為null,否則會直接傳回ProxyApplication的執行個體。

首先,通過android.app.ActivityThread中靜态方法擷取ActivityThread執行個體,然後,通過ActivityThread執行個體,獲得LoadedApk執行個體。為了使makeApplication順利執行,先設定mApplication為null。移除mAllApplications中ProxyApplication的執行個體。LoadedApk中mApplicationInfo和AppBindData中appInfo都是ApplicationInfo類型,需要分别替換className字段的值為RealApplication的實際類全名。

Android動态替換Application實作

之後,反射調用系統的makeApplication.

Android動态替換Application實作

這樣,在ProxyApplication.attachBaseContext中,調用makeApplication建立RealApplication,并且内部已經完成對于RealApplication的attchBaseContext的回調。在ProxyApplication.onCreate中隻需要回調RealApplication執行個體的onCreate,即可完成對于RealApplication的建立,已經内部替換以及正常的生命周期的回調。而且在Activity中調用getApplicationContext傳回的值,實際上也是LoadedApk中mApplication的值,同時也保證對于Activity等地方屏蔽ProxyApplication的目的。

ContentProvider中getContext

Application和ContentProvider的初始化順序是:Application.attachBaseContext – ContentProvider.onCreate – Application.onCreate。ContentProvider中也存在getContext方法,看ContentProvider的源代碼實作:

Android動态替換Application實作

其中mContext被指派的有兩個地方,一個在構造方法,一個是attchInfo的時候。繼續追蹤源代碼中使用構造方法初始化,或者調用attachInfo的地方,結果在android.app.ActivityThread中找到installProvider方法中存在着調用關系。

Android動态替換Application實作

可以看出,使用反射調用ContentProvider無參構造方法建立執行個體,然後調用了attachInfo,傳遞的Context為installProvider方法中的參數,而installProvider的參數是在installContentProviders内部在初始化中傳遞的。

Android動态替換Application實作

可以明确,installContentProviders中調用installProvider時傳遞的Context,也是由方法調用時傳遞的參數。繼續向上追蹤發現ActivityThread.handleBindApplication在初始化ContentProvider時調用了installContentProviders,最終通過attachInfo設定給ContentProvider中的Context的實際類型是Application。

在App初始化時,系統調用makeApplication建立了ProxyApplication執行個體,同時回調了attachBaseContext(Context context)。是以這個方法傳回的就是App初始化時ProxyApplication,調用發生ProxyApplication.attachBaseContext之後,ProxyApplication.onCreate之前。是以我們沒有辦法在這兩個方法生命周期内進行替換為RealApplication。

這種方案,接入成本比較低,但是新系統出現之後,可能出現相容性的問題,需要每次釋出新系統之後進行相關的适配。但是這種Hook解決問題的思路,可以借鑒一下。

以上就是本文的全部内容,希望對大家的學習有所幫助。