大話插件 - 動態加載插件 Activity

ChristianRo 9年前發布 | 27K 次閱讀 Activity Android開發 移動開發

有時候稍不注意, 忘記在 Manifest 文件中注冊 Activity,在運行的時候啟動 Activity 時就會觸發 ActivityNotFoundException 的異常。對于每一個運行的 Activity 都需要進行注冊,這個常識我們都很清楚,但是在插件中這樣的要求就有些難以實現,由于宿主程序在設計的時候,不知道插件的細節,更不用說在宿主程序的 Manifest 里面提前注冊插件 Activity。

在這篇文章中,介紹了幾種可以繞過 Android 對 Activity 需要注冊的限制的實現方式。對這些實現方式的了解,有助于理解 Activity 背后的原理,加深對 ActivityManagerService 等等重要系統服務的認知,是不錯的進階知識。

在正式開始寫之前,我還是想額外地扯扯淡。就我自身看來,插件化技術本身的未來是不明朗的,在后續日趨穩定的類 Reactive Native 技術穩定(國內有 Weex)后,可以幫助我們屏蔽不同版本的兼容性問題,實現動態功能的成本也更低,可能更適合于長遠方向。但我依舊還在學習插件化技術,是在于插件化技術的深入理解需要依托于對 Android Framework 層的透徹了解上,通過對此的學習,對自身內功的修煉很有裨益。Android 技術也日新月異的發展,而背后的 Framework 層則相對穩定,設計理念也是大體相同,對于 Framework 層的理解能幫我們構建出更好的程序。這就是你所不能被其他人替代的地方,因為你的不可替代性,也能贏得更好的機會。

利用接口偽裝

dynamic-load-apk 作為第一個將 Android 插件化開源方案出去的項目,提供了最初的解決方案,

Manifest 注入代理 ProxyActivity

<activity
    android:name="com.ryg.dynamicload.DLProxyActivity"
    android:label="@string/app_name" >
    <intent-filter>
        <action android:name="com.ryg.dynamicload.proxy.activity.VIEW" />

        <category android:name="android.intent.category.DEFAULT" />
    </intent-filter>
</activity>

既然不能提前在 Manifest 里面注冊相應的 Activity ,那么就提前注冊代理 ProxyActivity,這個代理 Activity 在啟動后,會通過靜態代理的方式,再實際調用真實 Activity 的方法。

這里一定要在宿主程序中,聲明 DLProxyActivity,目前還沒有什么方案可以繞過不需要聲明的限制。

啟動插件 Activity

通常啟動 Activity 的時候,代碼是這樣實現的。

Intent intent = new Intent(context, TargetActivity.class);
context.startActivity(intent);

dynamic-load-apk 在實現的時候為了實現自己代理的效果,進行了自己的封裝,將 Intent 封裝成 DLIntent。如下面的代碼所示,DLIntent 將插件包名和對應插件的 Activity 類傳遞進來。

public DLIntent(String pluginPackage, String pluginClass) {
    super();
    this.mPluginPackage = pluginPackage;
    this.mPluginClass = pluginClass;
}

public DLIntent(String pluginPackage, Class<?> clazz) {
    super();
    this.mPluginPackage = pluginPackage;
    this.mPluginClass = clazz.getName();
}

接下來看看 dynamic-load-apk 如何實現啟動 startActivity 的。

public int startPluginActivityForResult(Context context, DLIntent dlIntent, int requestCode) {

    String packageName = dlIntent.getPluginPackage();
    //驗證intent的包名
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    //檢測插件是否加載
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    //要調用的插件Activity的class完整路徑
    final String className = getPluginActivityFullPath(dlIntent, pluginPackage);
    //Class.forName
    Class<?> clazz = loadPluginClass(pluginPackage.classLoader, className);
    if (clazz == null) {
        return START_RESULT_NO_CLASS;
    }
    //獲取代理Activity的class,DLProxyActivity/DLProxyFragmentActivity
    Class<? extends Activity> proxyActivityClass = getProxyActivityClass(clazz);
    if (proxyActivityClass == null) {
        return START_RESULT_TYPE_ERROR;
    }
    //put extra data
    dlIntent.putExtra(DLConstants.EXTRA_CLASS, className);
    dlIntent.putExtra(DLConstants.EXTRA_PACKAGE, packageName);
    dlIntent.setClass(mContext, proxyActivityClass);
    //通過context啟動宿主Activity
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}

首先通過 ClassLoader 的方式(這里有具體介紹 Android ClassLoader 加載機制 ) 加載插件。同樣在 DLIntent 里面將實際的插件包名和插件對象傳遞進去,以便后續代理 Activity 調用。注意這里的 getProxyActivityClass 方法返回的是 DLProxyActivity , 也就是說將要啟動的 Activity 替換為了代理 Activity。

處理插件生命周期

當 ProxyActivity 啟動后,在相應的生命周期時通過反射的方式調用實際 Activity 中生命周期的方法,但反射這種方式存在兩個方法的問題,一是頻繁地調用反射會有不可忽視的性能開銷,另一方面反射的使用會使得代碼難以維護,而且可能存在兼容性問題。就先定義了如下的接口:

public interface DLPlugin {

    public void onCreate(Bundle savedInstanceState);
    public void onStart();
    public void onRestart();
    public void onActivityResult(int requestCode, int resultCode, Intent data);
    public void onResume();
    public void onPause();
    public void onStop();
    public void onDestroy();
    public void attach(Activity proxyActivity, DLPluginPackage pluginPackage);
    public void onSaveInstanceState(Bundle outState);
    public void onNewIntent(Intent intent);
    public void onRestoreInstanceState(Bundle savedInstanceState);
    public boolean onTouchEvent(MotionEvent event);
    public boolean onKeyUp(int keyCode, KeyEvent event);
    public void onWindowAttributesChanged(LayoutParams params);
    public void onWindowFocusChanged(boolean hasFocus);
    public void onBackPressed();
    public boolean onCreateOptionsMenu(Menu menu);
    public boolean onOptionsItemSelected(MenuItem item);

}

代理 Activity 實現了這個接口,并在相應的接口中,去調用插件 Activity 的方法,從而實現偷天換日的功效。下面以 finish 函數為例,說明如何實現的。

public class DLBasePluginActivity extends Activity implements DLPlugin {

  // ...

  @Override
  public void attach(Activity proxyActivity, DLPluginPackage pluginPackage) {
      mProxyActivity = (Activity) proxyActivity;
      that = mProxyActivity;
      mPluginPackage = pluginPackage;
  }

  @Override
  public void finish() {
      if (mFrom == DLConstants.FROM_INTERNAL) {
          super.finish();
      } else {
          mProxyActivity.finish();
      }
  }

  // ...

}

這種方案只差最后,怎么將 TargetActivity 和 ProxyActivity 綁定在一起了? dynamic-load-apk 中 launchTargetActivity 實現了這個功能,在其中的 attach 函數里面,將 ProxyActivity 和 PluginActivity 綁定在一起。

protected void launchTargetActivity() {
    try {
        Class<?> localClass = getClassLoader().loadClass(mClass);
        Constructor<?> localConstructor = localClass.getConstructor(new Class[] {});
        Object instance = localConstructor.newInstance(new Object[] {});
        mPluginActivity = (DLPlugin) instance;
        ((DLAttachable) mProxyActivity).attach(mPluginActivity, mPluginManager);
        // attach the proxy activity and plugin package to the mPluginActivity
        mPluginActivity.attach(mProxyActivity, mPluginPackage);

        Bundle bundle = new Bundle();
        bundle.putInt(DLConstants.FROM, DLConstants.FROM_EXTERNAL);
        mPluginActivity.onCreate(bundle);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

經過上訴的步驟,就可以繞過 Activity 需要注冊的限制了,但這個方案也有一定的限制。不僅要求,插件和宿主都必須同時依賴于一個接口工程,這樣會嚴重制約插件的實現。另一方面,對于 Activity 可以這么實現,但是對于 Service 等等其他組件,也需要進行同樣的接口代理,在代碼的可讀性上不是很好。于是,插件化在發展一段時間后,有了如下的解決方案。

構建 APP 虛擬運行環境

構建虛擬運行環境的方式,提供了一種一勞永逸的方案,這種方案通過反射、動態代理等技術,將 APP 需要運行的系統服務進行了特殊處理,欺上瞞下,使得 APP 能在不安裝的情況下,運行起來。在這種情況下,自然而然就沒有前面方案中,需要額外依賴工程的弊端,也不需要插件為了繼承做什么特殊處理。而實現這種虛擬運行環境,需要大量的工程,對每個系統服務都進行特殊處理,在這里就不展開敘述了,只說明 Activity 如何在這個虛擬環境下跑起來。

攔截 startActivity 請求

為了不讓插件做額外的工作,我們必須對攔截 startActivity, 進行偷天換日的工作。這里用到的技術就是動態代理,關于動態代理如何實現,網上有不少的文章可以參考,不再詳述。 startActivty 眾多簽名的方法中,最后都會進入到 ActivityManager 中去,因而對 ActivityManager 進行代理是不錯的選擇,而實際上 ActivityManager 所做的工作是通過 Binder 機制對 ActivityManagerService 的調用。最后的代理工作,還是要回到 ActivityManagerService 里面來。

if (ActivityManagerNative.gDefault.type() == IActivityManager.class) {
  ActivityManagerNative.gDefault.set(getHookObject().getProxyObject());
} else if (ActivityManagerNative.gDefault.type() == android.util.Singleton.class) {
  Object gDefault = ActivityManagerNative.gDefault.get();
  Singleton.mInstance.set(gDefault, getHookObject().getProxyObject());
}

上面的代碼中,ActivtyManagerNative 是 ActivityManagerService 的基類,對 ActivityManagerNative 的修改作用于 ActivityManagerService。這里針對了不同 SDK 版本做了不同的處理,總之在這個替換后,ActivityManagerService 中的 gDefault 變量已經變成我們 hook 后的對象了, getHookObject().getProxyObject() 。

HookBinder<IActivityManager> hookAMBinder = new HookBinder<IActivityManager>() {
  @Override
  protected IBinder queryBaseBinder() {
    return ServiceManager.getService(Context.ACTIVITY_SERVICE);
  }

  @Override
  protected IActivityManager createInterface(IBinder baseBinder) {
    return getHookObject().getProxyObject();
  }
};
hookAMBinder.injectService(Context.ACTIVITY_SERVICE);

public void injectService(String name) throws Throwable {
  Map<String, IBinder> sCache = mirror.android.os.ServiceManager.sCache.get();
  if (sCache != null) {
    sCache.remove(name);
    sCache.put(name, this);
  } else {
    throw new IllegalStateException("ServiceManager is invisible.");
  }
}

在接下來的這段代碼里面,是替換 SystemServer 中存放的 ActivityManagerService 變量,這樣在上面兩個地方進行代理之后,已經將所有和 ActivityManagerService (以下簡稱 AMS) 相關的入口都進行了處理,這樣當我們調用 startActivity 方法時,就能進入到我們代理的對象中。接下來看看,這個代理對象應該如何實現。

代理所完成的工作是對 AMS 進行各種改動,已達成完成啟動插件 Activity 的目的,這里就只從 startActivity 這個方法入手,其他方法可以逐類旁通。

我們先看看 startActivity 的方法簽名,這個函數在不同版本的簽名也各不相同,下面演示的是基于 SDK-23 源碼。

public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
        String resolvedType, IBinder resultTo, String resultWho, int requestCode,
        int startFlags, ProfilerInfo profilerInfo, Bundle options) throws RemoteException {

        // ...

        }
  1. 其中 intent 參數就是我們傳入的啟動 Intent;

  2. caller 是傳入到 AMS 中的 binder, 當通知 Activity 啟動或者其他事件完成的時候,就可以通過這個 Binder 對象進行通知 Activty 進行生命周期處理了;

  3. resultTo 這個參數是 ActivtyRecord 中的變量,這里用來表征一個 Activity。 resultTo 本事是 Binder 對象,而 Binder 對象可以在跨進程中起到唯一標示的作用。

其余參數就不再敘述了,現在看看 startActivity 具體是怎么攔截的。Java 一般使用 InvocationHandler 來進行動態代理,代理過后會調用到 invoke 方法 , 當 method.getName 是 startActivity 時,我們就可以進行攔截了。

public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  //...
}

獲取必要的參數,并保存下來。

int intentIndex = ArrayUtils.indexOfFirst(args, Intent.class);
int resultToIndex = ArrayUtils.indexOfObject(args, IBinder.class, 2);
String resolvedType = (String) args[intentIndex + 1];

Intent targetIntent = (Intent) args[intentIndex];
targetIntent.setDataAndType(targetIntent.getData(), resolvedType);
IBinder resultTo = resultToIndex != -1 ? (IBinder) args[resultToIndex] : null;
String resultWho = null;
int requestCode = 0;
Bundle options = ArrayUtils.getFirst(args, Bundle.class);
if (resultTo != null) {
  resultWho = (String) args[resultToIndex + 1];
  requestCode = (int) args[resultToIndex + 2];
}
int userId = getUserId(targetIntent);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR2) {
  args[intentIndex - 1] = getHostPkg();
}

解析 startActivity 中的 Intent

在上一篇文章中,講解到如何不經過安裝過程,解析 APK 的信息。在得到這些信息之后,VirtualApp(以下簡稱VA) 會將這些信息組織起來,存放在本地的包服務中。信息的組織形式,與系統的 PackageManagerService 類似,將各大組件、權限等信息保留下來,當調用到 startActivity 時,解析其中的 Intent,查看是否有相應的組件匹配對應的 Intent。

ActivityInfo targetActInfo = VirtualCore.getCore().resolveActivityInfo(targetIntent, userId);
if (targetActInfo == null) {
  return method.invoke(who, args);
}
String packageName = targetActInfo.packageName;
if (!isAppPkg(packageName)) {
  return method.invoke(who, args);
}
Intent resultIntent = VActivityManager.get().startActivity(targetIntent, targetActInfo, resultTo, options, userId);
if (resultIntent == null) {
  if (resultTo != null) {
    VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
  }
  return 0;
}

上面的代碼中獲取到 intent 對應的 targetActInfo, 如果為空,或者沒有在 VirtualApp 里面,就直接調用原有 API 的方法,否則則進入我們的攔截邏輯,看起來 VActivityManager.get().startActivity 是重中之重。

創建應用進程

在我之前的一篇文章里, Android 應用進程啟動流程 , Android 組件的運行都是需要相應的進程的。我們在講如何啟動插件 Activity 的時候,也要處理好這個問題。單獨的進程有助于進行數據隔離,當發生意外情況時,不至于影響主進程。皮之不存,毛將焉附,現在看看創建進程,讓插件 Activity 可以依附。

VirtualApp 會預先在 AndroidManifest 通過 android:process 來預置一些進程,當有需要的時候,會查看這些進程是否存在,利用其未占用的進程給插件使用。下面截取了一段 AndroidManifest 中的代碼,可以注意其中的 android:process 字段。

<activity
    android:name="com.lody.virtual.client.stub.StubActivity$C0"
    android:configChanges="mcc|mnc|locale|...|fontScale"
    android:process=":p0"
    android:taskAffinity="com.lody.virtual.vt"
    android:theme="@style/VATheme">
    <meta-data
        android:name="X-Identity"
        android:value="Stub-User"/>
</activity>

<activity
    android:name="com.lody.virtual.client.stub.StubActivity$C1"
    android:configChanges="mcc|mnc|locale|...|fontScale"
    android:process=":p1"
    android:taskAffinity="com.lody.virtual.vt"
    android:theme="@style/VATheme">
    <meta-data
        android:name="X-Identity"
        android:value="Stub-User"/>
</activity>

VActivityManagerService(以下簡稱VAMS) 在啟動后,會遍歷對應 Manifest 文件中的 Activity 和 Provider 組件,并查看其中的 processName,通過 Map 建立起 processName 和對應 Activity 和 Provider 之間的關系。

PackageManager pm = context.getPackageManager();
PackageInfo packageInfo = null;
try {
  packageInfo = pm.getPackageInfo(context.getPackageName(),
      PackageManager.GET_ACTIVITIES | PackageManager.GET_PROVIDERS
      | PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
  e.printStackTrace();
}

ActivityInfo[] activityInfos = packageInfo.activities;
for (ActivityInfo activityInfo : activityInfos) {
  if (isStubComponent(activityInfo)) {
    String processName = activityInfo.processName;
    stubProcessList.add(processName);
    StubInfo stubInfo = stubInfoMap.get(processName);
    if (stubInfo == null) {
      stubInfo = new StubInfo();
      stubInfo.processName = processName;
      stubInfoMap.put(processName, stubInfo);
    }
    String name = activityInfo.name;
    if (name.endsWith("_")) {
      stubInfo.dialogActivityInfos.add(activityInfo);
    } else {
      stubInfo.standardActivityInfos.add(activityInfo);
    }
  }
}

當需要啟動進程時,就從 stubInfoMap 里面去查看是否有空閑的進程可供使用。如果存在空閑的進程,則通過前面提到的 Map,從進程名得到相應的 Stub 信息。進程的創建是相對重量級的事情,而 VA 只用了幾行代碼,就完成了這個事情,利用的正是 Android Provider 的機制。當 Provider 啟動的時候,可以同步地啟動對應的進程,具體原理可以參看 這篇文章 .

public static Bundle call(String authority, Context context, String methodName, String arg, Bundle bundle) {
  Uri uri = Uri.parse("content://" + authority);
  ContentResolver contentResolver = context.getContentResolver();
  return contentResolver.call(uri, methodName, arg, bundle);
}

進程啟動后,還需要將這個進程和插件具體綁定起來,使得這個進程能夠當做 Application 來運行,這段邏輯也相對復雜,有興趣的可以看看 VClientImpl 的實現。

斗轉星移繞過 Manifest 的限制

在進程創建成功后, startProcessIfNeedLocked 可以得到對應的 ProcessRecord 對象,這個對象中存放著相應的 Activity、Service 等等組件信息,當然也包括用于偷換概念的 Stub 信息。在進程啟動后,將對應的 Stub 放置在 intent 中,并通過 Binder 機制返回給 Client 端。

ProcessRecord processRecord = mService.startProcessIfNeedLocked(info.processName, userId, info.packageName);
if (processRecord == null) {
  return null;
}
StubInfo selectedStub = processRecord.stubInfo;
ActivityInfo stubActInfo = selectedStub.fetchStubActivityInfo(info);
if (stubActInfo == null) {
  return null;
}
newIntent.setClassName(stubActInfo.packageName, stubActInfo.name);
newIntent.putExtra("_VA_|_intent_", intent);
newIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
newIntent.putExtra("_VA_|_target_activity_", info);
newIntent.putExtra("_VA_|_user_id_", userId);
return newIntent;

重點來啦,在上面的 newIntent 中,將 className 設置成為代理 Activity 的信息。在 Client 端,獲取到這個 resultIntent 后,將這個值注入到 startActivity 的 intent 里面去,在 args[intentIndex] = resultIntent 這里進行的替換。而在這里進行替換過后,就可以繞過 AMS 的對 Activity 需要注冊的限制了。

@Override
public Object onHook(Object who, Method method, Object... args) throws Throwable {
  super.onHook(who, method, args);

  // ...

  Intent resultIntent =
    VActivityManager.get().startActivity(
      targetIntent, targetActInfo, resultTo, options, userId);
  if (resultIntent == null) {
    if (resultTo != null) {
      VActivityManager.get().sendActivityResult(resultTo, resultWho, requestCode);
    }
    return 0;
  }
  args[intentIndex] = resultIntent;
  return method.invoke(who, args);
}

給插件 Activity 注入生命

上段代碼中的 method.invoke(who, args) , 執行的是 SDK 中 startActivity 的邏輯。這個邏輯里面執行的是啟動邏輯,在這里篇幅的限制,就不再詳細說明了,大家可以看我的這篇博文, Android Activity 生命周期是如何實現的 , 最終程序會執行到 ActivityThread.mH 中去,對應的消息就是 LAUNCH_ACTIVITY ,其后執行的方法就是下面代碼所描述的這樣。

private boolean handleLaunchActivity(Message msg) {
  Object r = msg.obj;
  // StubIntent
  Intent stubIntent = ActivityThread.ActivityClientRecord.intent.get(r);
  // TargetIntent
  Intent targetIntent = stubIntent.getParcelableExtra("_VA_|_intent_");

  ComponentName component = targetIntent.getComponent();
  String packageName = component.getPackageName();

  AppSetting appSetting = VirtualCore.getCore().findApp(packageName);
  if (appSetting == null) {
    return true;
  }
  // 從 Intent 中獲取的 stub 和 target 的信息
  ActivityInfo stubActInfo = stubIntent.getParcelableExtra("_VA_|_stub_activity_");
  ActivityInfo targetActInfo = stubIntent.getParcelableExtra("_VA_|_target_activity_");

  if (stubActInfo == null || targetActInfo == null) {
    return true;
  }
  String processName = ComponentUtils.getProcessName(targetActInfo);
  // 保證 Process 已經與 Application 綁定起來
  if (!VClientImpl.getClient().isBound()) {
    int targetUser = stubIntent.getIntExtra("_VA_|_user_id_", 0);
    VActivityManager.get().ensureAppBound(processName, appSetting.packageName, targetUser);
    getH().sendMessageDelayed(Message.obtain(msg), 5);
    return false;
  }

  // 設置對應的 classLoader
  ClassLoader appClassLoader = VClientImpl.getClient().getClassLoader(targetActInfo.applicationInfo);
  targetIntent.setExtrasClassLoader(appClassLoader);
  boolean error = false;
  try {
    targetIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
    targetIntent.putExtra("_VA_|_target_activity_", targetActInfo);
  } catch (Throwable e) {
    error = true;
    VLog.w(TAG, "Directly putExtra failed: %s.", e.getMessage());
  }
  if (error && Build.VERSION.SDK_INT <= Build.VERSION_CODES.KITKAT) {
    ClassLoader oldParent = getClass().getClassLoader().getParent();
    mirror.java.lang.ClassLoader.parent.set(getClass().getClassLoader(), appClassLoader);
    try {
      targetIntent.putExtra("_VA_|_stub_activity_", stubActInfo);
      targetIntent.putExtra("_VA_|_target_activity_", targetActInfo);
    } catch (Throwable e) {
      VLog.w(TAG, "Secondly putExtra failed: %s.", e.getMessage());
    }
    mirror.java.lang.ClassLoader.parent.set(getClass().getClassLoader(), oldParent);
  }
  // 反射替換其中的 intent 和 activityInfo, 將 Stub 相關的信息換成 target 相關的信息
  ActivityThread.ActivityClientRecord.intent.set(r, targetIntent);
  ActivityThread.ActivityClientRecord.activityInfo.set(r, targetActInfo);
  return true;
}

同樣也是重點,這里將 ActivityClientRecord 中的相應信息替換為了插件 Activity,從這一步過后,對應的插件 Activity 就能通過這個 H Callback 接收到相應的生命周期回調,從這一刻開始,插件 Activity 就是有血有肉的存在了。

總結 VA 的實現方式

可能大家在看前面的描述過后,如果對 AMS 這一塊比較熟悉的話,就會發現所做的工作其實特別簡單。第一步,就是將 startActivity 中的 intent 參數,替換為插件 Activity 的信息;第二步,是在欺騙完系統后,在 H Callback 的 LAUNCH_ACTIVITY 消息中,將對應 Record 中的信息,還原為插件的 Activity 信息。

讀者也許會問,難道真的就這么簡單,就可以欺騙系統了嗎?我們先通過 adb shell dumpsys activity 的方式,看看在 AMS 這個視角上,運行的是哪個 Activity?

Activity Dump Trace

看來,AMS 還真天真地運行著 StubActivity,在前面欺騙的環節中,傳遞進去的確實是 StubActivity,而為何在實際運行的時候,客戶端還能繼續使用插件 Activity 了?在 Android Activity 生命周期是如何實現的 這篇文章里面講到,在 ActivityThread 中的 performLaunchActivity 方法里面,實際去調用 Activity 的 onCreate 方法,而在這個 performLaunchActivity 里面有這樣一段代碼。

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
  ActivityInfo aInfo = r.activityInfo;

  // other code.

  activity.attach(appContext, this, getInstrumentation(), r.token,
                          r.ident, app, r.intent, r.activityInfo, title, r.parent,
                          r.embeddedID, r.lastNonConfigurationInstances, config,
                          r.referrer, r.voiceInteractor);
}

這里調用方法時,已經在 H Callback 中將 ActivityClientRecord 替換為插件 Activity,而在 attach 的時候,也是將這個 token 作為參數寫入進去。因而后續在 Client 段實際使用的是插件 Activity,盡管系統依然用著 StubActivity。

360 的 DP 方案,采用的也是類似的技術,大家可以最后進行下對比。

 

來自:http://www.jianshu.com/p/51c330241ced

 

 本文由用戶 ChristianRo 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!