大話插件 - 動(dòng)態(tài)加載插件 Activity

有時(shí)候稍不注意, 忘記在 Manifest 文件中注冊(cè) Activity诬乞,在運(yùn)行的時(shí)候啟動(dòng) Activity 時(shí)就會(huì)觸發(fā) ActivityNotFoundException 的異常。對(duì)于每一個(gè)運(yùn)行的 Activity 都需要進(jìn)行注冊(cè),這個(gè)常識(shí)我們都很清楚,但是在插件中這樣的要求就有些難以實(shí)現(xiàn)部宿,由于宿主程序在設(shè)計(jì)的時(shí)候彤叉,不知道插件的細(xì)節(jié),更不用說(shuō)在宿主程序的 Manifest 里面提前注冊(cè)插件 Activity看铆。

在這篇文章中,介紹了幾種可以繞過(guò) Android 對(duì) Activity 需要注冊(cè)的限制的實(shí)現(xiàn)方式笆搓。對(duì)這些實(shí)現(xiàn)方式的了解性湿,有助于理解 Activity 背后的原理,加深對(duì) ActivityManagerService 等等重要系統(tǒng)服務(wù)的認(rèn)知满败,是不錯(cuò)的進(jìn)階知識(shí)肤频。

在正式開(kāi)始寫之前,我還是想額外地扯扯淡算墨。就我自身看來(lái)宵荒,插件化技術(shù)本身的未來(lái)是不明朗的,在后續(xù)日趨穩(wěn)定的類 Reactive Native 技術(shù)穩(wěn)定(國(guó)內(nèi)有 Weex)后净嘀,可以幫助我們屏蔽不同版本的兼容性問(wèn)題报咳,實(shí)現(xiàn)動(dòng)態(tài)功能的成本也更低,可能更適合于長(zhǎng)遠(yuǎn)方向挖藏。但我依舊還在學(xué)習(xí)插件化技術(shù)暑刃,是在于插件化技術(shù)的深入理解需要依托于對(duì) Android Framework 層的透徹了解上,通過(guò)對(duì)此的學(xué)習(xí)膜眠,對(duì)自身內(nèi)功的修煉很有裨益岩臣。Android 技術(shù)也日新月異的發(fā)展溜嗜,而背后的 Framework 層則相對(duì)穩(wěn)定,設(shè)計(jì)理念也是大體相同架谎,對(duì)于 Framework 層的理解能幫我們構(gòu)建出更好的程序炸宵。這就是你所不能被其他人替代的地方,因?yàn)槟愕牟豢商娲怨瓤郏材苴A得更好的機(jī)會(huì)土全。

利用接口偽裝

dynamic-load-apk 作為第一個(gè)將 Android 插件化開(kāi)源方案出去的項(xiàng)目,提供了最初的解決方案会涎,

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 里面注冊(cè)相應(yīng)的 Activity 裹匙,那么就提前注冊(cè)代理 ProxyActivity,這個(gè)代理 Activity 在啟動(dòng)后在塔,會(huì)通過(guò)靜態(tài)代理的方式幻件,再實(shí)際調(diào)用真實(shí) Activity 的方法。

這里一定要在宿主程序中蛔溃,聲明 DLProxyActivity绰沥,目前還沒(méi)有什么方案可以繞過(guò)不需要聲明的限制。

啟動(dòng)插件 Activity

通常啟動(dòng) Activity 的時(shí)候贺待,代碼是這樣實(shí)現(xiàn)的徽曲。

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

dynamic-load-apk 在實(shí)現(xiàn)的時(shí)候?yàn)榱藢?shí)現(xiàn)自己代理的效果,進(jìn)行了自己的封裝麸塞,將 Intent 封裝成 DLIntent秃臣。如下面的代碼所示,DLIntent 將插件包名和對(duì)應(yīng)插件的 Activity 類傳遞進(jìn)來(lái)哪工。

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();
}

接下來(lái)看看 dynamic-load-apk 如何實(shí)現(xiàn)啟動(dòng) startActivity 的奥此。

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

    String packageName = dlIntent.getPluginPackage();
    //驗(yàn)證intent的包名
    if (TextUtils.isEmpty(packageName)) {
        throw new NullPointerException("disallow null packageName.");
    }
    //檢測(cè)插件是否加載
    DLPluginPackage pluginPackage = mPackagesHolder.get(packageName);
    if (pluginPackage == null) {
        return START_RESULT_NO_PKG;
    }
    //要調(diào)用的插件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);
    //通過(guò)context啟動(dòng)宿主Activity
    performStartActivityForResult(context, dlIntent, requestCode);
    return START_RESULT_SUCCESS;
}

首先通過(guò) ClassLoader 的方式(這里有具體介紹 Android ClassLoader 加載機(jī)制) 加載插件雁比。同樣在 DLIntent 里面將實(shí)際的插件包名和插件對(duì)象傳遞進(jìn)去稚虎,以便后續(xù)代理 Activity 調(diào)用。注意這里的 getProxyActivityClass 方法返回的是 DLProxyActivity, 也就是說(shuō)將要啟動(dòng)的 Activity 替換為了代理 Activity偎捎。

處理插件生命周期

當(dāng) ProxyActivity 啟動(dòng)后蠢终,在相應(yīng)的生命周期時(shí)通過(guò)反射的方式調(diào)用實(shí)際 Activity 中生命周期的方法,但反射這種方式存在兩個(gè)方法的問(wèn)題茴她,一是頻繁地調(diào)用反射會(huì)有不可忽視的性能開(kāi)銷寻拂,另一方面反射的使用會(huì)使得代碼難以維護(hù),而且可能存在兼容性問(wèn)題丈牢。就先定義了如下的接口:

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 實(shí)現(xiàn)了這個(gè)接口祭钉,并在相應(yīng)的接口中,去調(diào)用插件 Activity 的方法己沛,從而實(shí)現(xiàn)偷天換日的功效朴皆。下面以 finish 函數(shù)為例帕识,說(shuō)明如何實(shí)現(xiàn)的泛粹。

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 實(shí)現(xiàn)了這個(gè)功能,在其中的 attach 函數(shù)里面晶姊,將 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();
    }
}

經(jīng)過(guò)上訴的步驟,就可以繞過(guò) Activity 需要注冊(cè)的限制了们衙,但這個(gè)方案也有一定的限制钾怔。不僅要求,插件和宿主都必須同時(shí)依賴于一個(gè)接口工程蒙挑,這樣會(huì)嚴(yán)重制約插件的實(shí)現(xiàn)宗侦。另一方面,對(duì)于 Activity 可以這么實(shí)現(xiàn)忆蚀,但是對(duì)于 Service 等等其他組件矾利,也需要進(jìn)行同樣的接口代理,在代碼的可讀性上不是很好馋袜。于是男旗,插件化在發(fā)展一段時(shí)間后,有了如下的解決方案欣鳖。


構(gòu)建 APP 虛擬運(yùn)行環(huán)境

構(gòu)建虛擬運(yùn)行環(huán)境的方式察皇,提供了一種一勞永逸的方案,這種方案通過(guò)反射泽台、動(dòng)態(tài)代理等技術(shù)什荣,將 APP 需要運(yùn)行的系統(tǒng)服務(wù)進(jìn)行了特殊處理,欺上瞞下怀酷,使得 APP 能在不安裝的情況下稻爬,運(yùn)行起來(lái)。在這種情況下胰坟,自然而然就沒(méi)有前面方案中因篇,需要額外依賴工程的弊端,也不需要插件為了繼承做什么特殊處理笔横。而實(shí)現(xiàn)這種虛擬運(yùn)行環(huán)境竞滓,需要大量的工程,對(duì)每個(gè)系統(tǒng)服務(wù)都進(jìn)行特殊處理吹缔,在這里就不展開(kāi)敘述了商佑,只說(shuō)明 Activity 如何在這個(gè)虛擬環(huán)境下跑起來(lái)。

攔截 startActivity 請(qǐng)求

為了不讓插件做額外的工作厢塘,我們必須對(duì)攔截 startActivity, 進(jìn)行偷天換日的工作茶没。這里用到的技術(shù)就是動(dòng)態(tài)代理肌幽,關(guān)于動(dòng)態(tài)代理如何實(shí)現(xiàn),網(wǎng)上有不少的文章可以參考抓半,不再詳述喂急。 startActivty 眾多簽名的方法中,最后都會(huì)進(jìn)入到 ActivityManager 中去笛求,因而對(duì) ActivityManager 進(jìn)行代理是不錯(cuò)的選擇廊移,而實(shí)際上 ActivityManager 所做的工作是通過(guò) Binder 機(jī)制對(duì) ActivityManagerService 的調(diào)用。最后的代理工作探入,還是要回到 ActivityManagerService 里面來(lái)壳炎。

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 的基類绿淋,對(duì) ActivityManagerNative 的修改作用于 ActivityManagerService博其。這里針對(duì)了不同 SDK 版本做了不同的處理,總之在這個(gè)替換后植旧,ActivityManagerService 中的 gDefault 變量已經(jīng)變成我們 hook 后的對(duì)象了辱揭,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.");
  }
}

在接下來(lái)的這段代碼里面隆嗅,是替換 SystemServer 中存放的 ActivityManagerService 變量界阁,這樣在上面兩個(gè)地方進(jìn)行代理之后,已經(jīng)將所有和 ActivityManagerService (以下簡(jiǎn)稱 AMS) 相關(guān)的入口都進(jìn)行了處理胖喳,這樣當(dāng)我們調(diào)用 startActivity 方法時(shí)泡躯,就能進(jìn)入到我們代理的對(duì)象中。接下來(lái)看看丽焊,這個(gè)代理對(duì)象應(yīng)該如何實(shí)現(xiàn)较剃。

代理所完成的工作是對(duì) AMS 進(jìn)行各種改動(dòng),已達(dá)成完成啟動(dòng)插件 Activity 的目的技健,這里就只從 startActivity 這個(gè)方法入手写穴,其他方法可以逐類旁通。

我們先看看 startActivity 的方法簽名雌贱,這個(gè)函數(shù)在不同版本的簽名也各不相同啊送,下面演示的是基于 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 參數(shù)就是我們傳入的啟動(dòng) Intent欣孤;

  2. caller 是傳入到 AMS 中的 binder, 當(dāng)通知 Activity 啟動(dòng)或者其他事件完成的時(shí)候馋没,就可以通過(guò)這個(gè) Binder 對(duì)象進(jìn)行通知 Activty 進(jìn)行生命周期處理了;

  3. resultTo 這個(gè)參數(shù)是 ActivtyRecord 中的變量降传,這里用來(lái)表征一個(gè) Activity篷朵。 resultTo 本事是 Binder 對(duì)象,而 Binder 對(duì)象可以在跨進(jìn)程中起到唯一標(biāo)示的作用。

其余參數(shù)就不再敘述了声旺,現(xiàn)在看看 startActivity 具體是怎么攔截的笔链。Java 一般使用 InvocationHandler 來(lái)進(jìn)行動(dòng)態(tài)代理,代理過(guò)后會(huì)調(diào)用到 invoke方法 , 當(dāng) method.getName 是 startActivity 時(shí)腮猖,我們就可以進(jìn)行攔截了鉴扫。

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

獲取必要的參數(shù),并保存下來(lái)缚够。

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

在上一篇文章中幔妨,講解到如何不經(jīng)過(guò)安裝過(guò)程,解析 APK 的信息谍椅。在得到這些信息之后,VirtualApp(以下簡(jiǎn)稱VA) 會(huì)將這些信息組織起來(lái)古话,存放在本地的包服務(wù)中雏吭。信息的組織形式,與系統(tǒng)的 PackageManagerService 類似陪踩,將各大組件杖们、權(quán)限等信息保留下來(lái),當(dāng)調(diào)用到 startActivity 時(shí)肩狂,解析其中的 Intent摘完,查看是否有相應(yīng)的組件匹配對(duì)應(yīng)的 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 對(duì)應(yīng)的 targetActInfo, 如果為空傻谁,或者沒(méi)有在 VirtualApp 里面孝治,就直接調(diào)用原有 API 的方法,否則則進(jìn)入我們的攔截邏輯审磁,看起來(lái) VActivityManager.get().startActivity 是重中之重谈飒。

創(chuàng)建應(yīng)用進(jìn)程

在我之前的一篇文章里,Android 應(yīng)用進(jìn)程啟動(dòng)流程, Android 組件的運(yùn)行都是需要相應(yīng)的進(jìn)程的态蒂。我們?cè)谥v如何啟動(dòng)插件 Activity 的時(shí)候杭措,也要處理好這個(gè)問(wèn)題。單獨(dú)的進(jìn)程有助于進(jìn)行數(shù)據(jù)隔離钾恢,當(dāng)發(fā)生意外情況時(shí)手素,不至于影響主進(jìn)程。皮之不存瘩蚪,毛將焉附泉懦,現(xiàn)在看看創(chuàng)建進(jìn)程,讓插件 Activity 可以依附募舟。

VirtualApp 會(huì)預(yù)先在 AndroidManifest 通過(guò) android:process 來(lái)預(yù)置一些進(jìn)程祠斧,當(dāng)有需要的時(shí)候,會(huì)查看這些進(jìn)程是否存在拱礁,利用其未占用的進(jìn)程給插件使用琢锋。下面截取了一段 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(以下簡(jiǎn)稱VAMS) 在啟動(dòng)后吴超,會(huì)遍歷對(duì)應(yīng) Manifest 文件中的 Activity 和 Provider 組件钉嘹,并查看其中的 processName,通過(guò) Map 建立起 processName 和對(duì)應(yīng) Activity 和 Provider 之間的關(guān)系鲸阻。

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);
    }
  }
}

當(dāng)需要啟動(dòng)進(jìn)程時(shí)跋涣,就從 stubInfoMap 里面去查看是否有空閑的進(jìn)程可供使用。如果存在空閑的進(jìn)程鸟悴,則通過(guò)前面提到的 Map陈辱,從進(jìn)程名得到相應(yīng)的 Stub 信息。進(jìn)程的創(chuàng)建是相對(duì)重量級(jí)的事情细诸,而 VA 只用了幾行代碼沛贪,就完成了這個(gè)事情,利用的正是 Android Provider 的機(jī)制震贵。當(dāng) Provider 啟動(dòng)的時(shí)候利赋,可以同步地啟動(dòng)對(duì)應(yīng)的進(jìn)程,具體原理可以參看 這篇文章.

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);
}

進(jìn)程啟動(dòng)后猩系,還需要將這個(gè)進(jìn)程和插件具體綁定起來(lái)媚送,使得這個(gè)進(jìn)程能夠當(dāng)做 Application 來(lái)運(yùn)行,這段邏輯也相對(duì)復(fù)雜寇甸,有興趣的可以看看 VClientImpl 的實(shí)現(xiàn)塘偎。

斗轉(zhuǎn)星移繞過(guò) Manifest 的限制

在進(jìn)程創(chuàng)建成功后,startProcessIfNeedLocked 可以得到對(duì)應(yīng)的 ProcessRecord 對(duì)象幽纷,這個(gè)對(duì)象中存放著相應(yīng)的 Activity式塌、Service 等等組件信息,當(dāng)然也包括用于偷換概念的 Stub 信息友浸。在進(jìn)程啟動(dòng)后峰尝,將對(duì)應(yīng)的 Stub 放置在 intent 中,并通過(guò) Binder 機(jī)制返回給 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;

重點(diǎn)來(lái)啦武学,在上面的 newIntent 中,將 className 設(shè)置成為代理 Activity 的信息伦意。在 Client 端火窒,獲取到這個(gè) resultIntent 后,將這個(gè)值注入到 startActivity 的 intent 里面去驮肉,在 args[intentIndex] = resultIntent 這里進(jìn)行的替換熏矿。而在這里進(jìn)行替換過(guò)后,就可以繞過(guò) AMS 的對(duì) Activity 需要注冊(cè)的限制了。

@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), 執(zhí)行的是 SDK 中 startActivity 的邏輯票编。這個(gè)邏輯里面執(zhí)行的是啟動(dòng)邏輯褪储,在這里篇幅的限制,就不再詳細(xì)說(shuō)明了慧域,大家可以看我的這篇博文鲤竹,Android Activity 生命周期是如何實(shí)現(xiàn)的, 最終程序會(huì)執(zhí)行到 ActivityThread.mH 中去,對(duì)應(yīng)的消息就是 LAUNCH_ACTIVITY昔榴,其后執(zhí)行的方法就是下面代碼所描述的這樣辛藻。

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 已經(jīng)與 Application 綁定起來(lái)
  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;
  }

  // 設(shè)置對(duì)應(yīng)的 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 相關(guān)的信息換成 target 相關(guān)的信息
  ActivityThread.ActivityClientRecord.intent.set(r, targetIntent);
  ActivityThread.ActivityClientRecord.activityInfo.set(r, targetActInfo);
  return true;
}

同樣也是重點(diǎn),這里將 ActivityClientRecord 中的相應(yīng)信息替換為了插件 Activity互订,從這一步過(guò)后吱肌,對(duì)應(yīng)的插件 Activity 就能通過(guò)這個(gè) H Callback 接收到相應(yīng)的生命周期回調(diào),從這一刻開(kāi)始屁奏,插件 Activity 就是有血有肉的存在了岩榆。

總結(jié) VA 的實(shí)現(xiàn)方式

可能大家在看前面的描述過(guò)后,如果對(duì) AMS 這一塊比較熟悉的話坟瓢,就會(huì)發(fā)現(xiàn)所做的工作其實(shí)特別簡(jiǎn)單。第一步犹撒,就是將 startActivity 中的 intent 參數(shù)折联,替換為插件 Activity 的信息;第二步识颊,是在欺騙完系統(tǒng)后诚镰,在 H Callback 的 LAUNCH_ACTIVITY 消息中,將對(duì)應(yīng) Record 中的信息祥款,還原為插件的 Activity 信息清笨。

讀者也許會(huì)問(wèn),難道真的就這么簡(jiǎn)單刃跛,就可以欺騙系統(tǒng)了嗎抠艾?我們先通過(guò) adb shell dumpsys activity 的方式,看看在 AMS 這個(gè)視角上桨昙,運(yùn)行的是哪個(gè) Activity检号?

Activity Dump Trace

看來(lái),AMS 還真天真地運(yùn)行著 StubActivity蛙酪,在前面欺騙的環(huán)節(jié)中齐苛,傳遞進(jìn)去的確實(shí)是 StubActivity,而為何在實(shí)際運(yùn)行的時(shí)候桂塞,客戶端還能繼續(xù)使用插件 Activity 了凹蜂?在Android Activity 生命周期是如何實(shí)現(xiàn)的 這篇文章里面講到,在 ActivityThread 中的 performLaunchActivity 方法里面,實(shí)際去調(diào)用 Activity 的 onCreate 方法玛痊,而在這個(gè) 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);
}

這里調(diào)用方法時(shí),已經(jīng)在 H Callback 中將 ActivityClientRecord 替換為插件 Activity卿啡,而在 attach 的時(shí)候吟吝,也是將這個(gè) token 作為參數(shù)寫入進(jìn)去。因而后續(xù)在 Client 段實(shí)際使用的是插件 Activity颈娜,盡管系統(tǒng)依然用著 StubActivity剑逃。

360 的 DP 方案,采用的也是類似的技術(shù)官辽,大家可以最后進(jìn)行下對(duì)比蛹磺。


文檔信息


最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市同仆,隨后出現(xiàn)的幾起案子萤捆,更是在濱河造成了極大的恐慌,老刑警劉巖俗批,帶你破解...
    沈念sama閱讀 222,681評(píng)論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件俗或,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡岁忘,警方通過(guò)查閱死者的電腦和手機(jī)辛慰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,205評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)干像,“玉大人帅腌,你說(shuō)我怎么就攤上這事÷樘” “怎么了速客?”我有些...
    開(kāi)封第一講書人閱讀 169,421評(píng)論 0 362
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)五鲫。 經(jīng)常有香客問(wèn)我溺职,道長(zhǎng),這世上最難降的妖魔是什么臣镣? 我笑而不...
    開(kāi)封第一講書人閱讀 60,114評(píng)論 1 300
  • 正文 為了忘掉前任辅愿,我火速辦了婚禮,結(jié)果婚禮上忆某,老公的妹妹穿的比我還像新娘点待。我一直安慰自己,他們只是感情好弃舒,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,116評(píng)論 6 398
  • 文/花漫 我一把揭開(kāi)白布癞埠。 她就那樣靜靜地躺著状原,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苗踪。 梳的紋絲不亂的頭發(fā)上颠区,一...
    開(kāi)封第一講書人閱讀 52,713評(píng)論 1 312
  • 那天,我揣著相機(jī)與錄音通铲,去河邊找鬼毕莱。 笑死,一個(gè)胖子當(dāng)著我的面吹牛颅夺,可吹牛的內(nèi)容都是我干的朋截。 我是一名探鬼主播,決...
    沈念sama閱讀 41,170評(píng)論 3 422
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼吧黄,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼部服!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起拗慨,我...
    開(kāi)封第一講書人閱讀 40,116評(píng)論 0 277
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤廓八,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后赵抢,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體剧蹂,經(jīng)...
    沈念sama閱讀 46,651評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,714評(píng)論 3 342
  • 正文 我和宋清朗相戀三年烦却,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了国夜。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,865評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡短绸,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出筹裕,到底是詐尸還是另有隱情醋闭,我是刑警寧澤,帶...
    沈念sama閱讀 36,527評(píng)論 5 351
  • 正文 年R本政府宣布朝卒,位于F島的核電站证逻,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏抗斤。R本人自食惡果不足惜囚企,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,211評(píng)論 3 336
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望瑞眼。 院中可真熱鬧龙宏,春花似錦、人聲如沸伤疙。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 32,699評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至黍特,卻和暖如春蛙讥,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背灭衷。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,814評(píng)論 1 274
  • 我被黑心中介騙來(lái)泰國(guó)打工次慢, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人翔曲。 一個(gè)月前我還...
    沈念sama閱讀 49,299評(píng)論 3 379
  • 正文 我出身青樓迫像,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親部默。 傳聞我的和親對(duì)象是個(gè)殘疾皇子侵蒙,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,870評(píng)論 2 361

推薦閱讀更多精彩內(nèi)容