1 概述
如果要啓動沒有在Manifest中註冊的Activity,應該從startActivity着手。一般啓動Activity的方式有兩種,一種是startActivity,一種是startActivityForResult。其實startActivity最終調用的也是startActivityForResult,如下所示:
//Activity.java
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
if (options != null) {
startActivityForResult(intent, -1, options);
} else {
// Note we want to go through this call for compatibility with
// applications that may have overridden the method.
startActivityForResult(intent, -1);
}
}
當我們啓動一個Activity時,AMS會對這個Activity是否在AndroidManifest中聲明註冊進行檢查,如果沒有註冊,則會報錯。要想啓動沒有在AndroidManifest中註冊的Activity,則需要“欺騙AMS”,讓其以爲我們已經在AndroidManifest中註冊了。
基本思路是:
- 發送要啓動的Activity信息給AMS之前,把這個Activity替換爲一個在AndroidManifest中聲明的StubActivity,這樣就能繞過AMS的檢查,在替換過程中,要把原來的Activity信息保存起來。
- AMS通知App啓動StubActivity時,我們再將保存的Activity替換StubActivity,這樣就能啓動沒有在AndroidManifest中註冊的Activity。
修改Activity啓動流程如圖所示(圖片來自網絡)
2 代碼實現
AndroidManifest.xml
<application
android:name=".MainApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SubActivity"/>
</application>
一共兩個Activity,分別是MainActivity和SubActivity,一個MainApplication,SubActivity是一個殼Activity,用於欺騙AMS的。
MainActivity.java
private View.OnClickListener mListener = new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.setClass(MainActivity.this, PluginTestActivity.class);
startActivity(intent);
}
};
PluginTestActivity並沒有在AndroidManifest中註冊,正常情況下會報錯,所以我們需要欺騙AMS。
欺騙AMS的方式有幾種,這裏我們選擇對ActivityThread的mInstrumentation進行hook。代碼如下:
public class HookHelper {
public static final String PLUGIN_INTENT = "plugin_intent";
public static void hookInstrumentation(Context context,String className) throws Exception{
//獲取ActivityThread的字節碼對象
Class<?> clazz = Class.forName("android.app.ActivityThread");
//獲取ActivityThread中的sCurrentActivityThread字段
Field sCurrentActivityThreadField = ReflectUtils.getField(clazz,"sCurrentActivityThread");
//獲取ActivityThread中的mInstrumentation字段
Field mInstrumentationField = ReflectUtils.getField(clazz,"mInstrumentation");
//獲取ActivityThread中的sCurrentActivityThread的值並賦值給currentActivityThread
Object currentActivityThread = sCurrentActivityThreadField.get(clazz);
//獲取ActivityThread中的mInstrumentation的值並賦值給instrumentation
Instrumentation instrumentation = (Instrumentation) mInstrumentationField.get(currentActivityThread);
//創建一個PackageManager對象packageManager
PackageManager packageManager = context.getPackageManager();
//創建InstrumentationProxy對象,InstrumentationProxy是繼承Instrumentation的類,下面詳細介紹
InstrumentationProxy instrumentationProxy = new InstrumentationProxy(instrumentation,packageManager,className);
//將ActivityThread中的mInstrumentation賦值爲instrumentationProxy
ReflectUtils.setFieldObject(clazz,currentActivityThread,"mInstrumentation",instrumentationProxy);
}
}
InstrumentationProxy類如下
package com.example.plugintest;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.os.Bundle;
import android.os.IBinder;
import android.text.TextUtils;
import java.lang.reflect.Method;
import java.util.List;
/**
* Created by yds
* on 2020/2/14.
*/
public class InstrumentationProxy extends Instrumentation{
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;
private String className;
public InstrumentationProxy(Instrumentation mInstrumentation, PackageManager mPackageManager, String className) {
this.mInstrumentation = mInstrumentation;
this.mPackageManager = mPackageManager;
this.className = className;
}
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> infos = mPackageManager.queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
intent.putExtra(HookHelper.PLUGIN_INTENT,intent.getComponent().getClassName());
intent.setClassName(who,className);
}
try {
Method execMethod = Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class,IBinder.class,IBinder.class,Activity.class,Intent.class,int.class,Bundle.class);
return (ActivityResult) execMethod.invoke(mInstrumentation,who,contextThread,token,target,intent,requestCode,options);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
public Activity newActivity(ClassLoader cl,String className,Intent intent) throws
InstantiationException,IllegalAccessException,ClassNotFoundException{
String intentName = intent.getStringExtra(HookHelper.PLUGIN_INTENT);
if(!TextUtils.isEmpty(intentName)){
return super.newActivity(cl,intentName,intent);
}
return super.newActivity(cl,className,intent);
}
}
只要將InstrumentationProxy賦值給ActivityThread的mInstrumentation,調用startActivity時,最終會調用InstrumentationProxy的execStartActivity和newActivity。我們只要在execStartActivity中將未註冊的Activity保存起來,並使用已註冊的SubActivity,然後在newActivity中將保存起來未註冊的Activity替換SubActivity就可以了。