啟動(dòng)沒(méi)有在AndroidManifest中注冊(cè)的Activity是安卓插件化中一個(gè)很重要的知識(shí)點(diǎn),只有這樣你才能把Activity中分離出來(lái),放到插件中.
啟動(dòng)沒(méi)有在AndroidManifest中注冊(cè)的Activity,會(huì)涉及到Activity啟動(dòng)流程、反射、動(dòng)態(tài)代理的知識(shí),我覺(jué)得就算不學(xué)插件化,掌握這些知識(shí)也是很有用的.
Activity的啟動(dòng)流程
為了達(dá)到啟動(dòng)沒(méi)有在AndroidManifest中注冊(cè)的Activity的目的,我們先來(lái)分析下Activity的啟動(dòng)流程,看看有沒(méi)有什么突破口.
這部分的知識(shí)我在《從源碼看Activity生命周期》這篇博客里面其實(shí)也有講過(guò),這里只做大概的講解,然后做一些補(bǔ)充,感興趣的同學(xué)可以將兩篇博客結(jié)合起來(lái)看看.
拋出ActivityNotFoundException的原因
如果使用startActivity去啟動(dòng)一個(gè)沒(méi)有在AndroidManifest中注冊(cè)的Activity,正常情況下是會(huì)拋出ActivityNotFoundException的,那這個(gè)異常是怎么拋出來(lái)的呢?
我們知道調(diào)用Activity.startActivity方法,實(shí)際上最后是調(diào)用了Instrumentation.execStartActivity:
public class Instrumentation {
...
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, null, options);
checkStartActivityResult(result, intent);
...
}
...
public static void checkStartActivityResult(int res, Object intent) {
...
switch (res) {
case ActivityManager.START_INTENT_NOT_RESOLVED:
case ActivityManager.START_CLASS_NOT_FOUND:
if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
throw new ActivityNotFoundException(
"Unable to find explicit activity class "
+ ((Intent)intent).getComponent().toShortString()
+ "; have you declared this activity in your AndroidManifest.xml?");
throw new ActivityNotFoundException(
"No Activity found to handle " + intent);
...
}
...
}
...
}
可以看到Instrumentation又是通過(guò)ActivityManagerNative.getDefault()拿到一個(gè)IActivityManager去調(diào)用其startActivity來(lái)啟動(dòng)Activity的.
這個(gè)IActivityManager內(nèi)部實(shí)際是通過(guò)Binder機(jī)制將處理轉(zhuǎn)發(fā)給ActivityManagerService:
public abstract class ActivityManagerNative extends Binder implements IActivityManager
...
static public IActivityManager getDefault() {
return gDefault.get();
}
...
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
//實(shí)際上是用Binder機(jī)制與AMS進(jìn)行交互
IBinder b = ServiceManager.getService("activity");
IActivityManager am = asInterface(b);
return am;
}
};
...
}
所以可以看到通過(guò)ActivityManagerService去startActivity之后會(huì)有個(gè)返回值.
ActivityManagerService內(nèi)部會(huì)使用PackageManagerService查詢這個(gè)Activity是否在AndroidManifest中注冊(cè).如果沒(méi)有,就會(huì)返回START_CLASS_NOT_FOUND或者START_INTENT_NOT_RESOLVED,這個(gè)時(shí)候Instrumentation就會(huì)拋出ActivityNotFoundException.
所以ActivityNotFoundException就是這樣被拋出的.
Activity是怎樣被創(chuàng)建的
我們都知道兩個(gè)不同的進(jìn)程直接是不能直接訪問(wèn)內(nèi)存的,所以處于應(yīng)用進(jìn)程的Activity肯定還是應(yīng)用進(jìn)程去創(chuàng)建,而不是被AMS創(chuàng)建的.
這塊的代碼在ActivityThread中實(shí)現(xiàn):
public final class ActivityThread {
...
final H mH = new H();
...
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
...
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
...
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
...
}
...
}
...
}
AMS會(huì)調(diào)用ActivityThread的scheduleLaunchActivity,在這個(gè)方法中會(huì)使用一個(gè)Hander同步到主線程中再去創(chuàng)建Activity.
Activity啟動(dòng)的原理圖

怎樣欺騙ActivityManagerService
從上面的Activity啟動(dòng)的原理圖可以看到大概的流程是:
應(yīng)用將要啟動(dòng)的Activity告訴AMS->AMS檢查Activity是否注冊(cè)->AMS讓ActivityThread去創(chuàng)建Activity.
那是不是可以這樣呢?
- 新建一個(gè)StubActivity并且在AndroidManifest中注冊(cè)
- 將想要啟動(dòng)的Activity換成StubActivity,而將真正想要啟動(dòng)的Activity保存到Extra中
- 騙過(guò)AMS
- 在ActivityThread中拿出真正想要?jiǎng)?chuàng)建的Activity換回來(lái)去創(chuàng)建
修改后的原理如下:

將要啟動(dòng)的Activity替換成StubActivity
第一步是將要啟動(dòng)的Activity替換成StubActivity,我們回顧下上一節(jié)看到的ActivityManagerNative代碼:
public abstract class ActivityManagerNative extends Binder implements IActivityManager
...
static public IActivityManager getDefault() {
return gDefault.get();
}
...
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
//實(shí)際上是用Binder機(jī)制與AMS進(jìn)行交互
IBinder b = ServiceManager.getService("activity");
IActivityManager am = asInterface(b);
return am;
}
};
...
}
可以看到這個(gè)gDefault其實(shí)是個(gè)靜態(tài)的私有成員變量.
那我們是不是可以通過(guò)反射,將它替換成我們寫(xiě)的Singleton<IActivityManager>,然后保存好原來(lái)的gDefault,在替換的代碼里面先將要啟動(dòng)的Activity替換成StubActivity,然后再將Intent傳給原來(lái)的gDefault?
大概的做法如下:
class MyActivityManager implements IActivityManager {
private IActivityManager mOrigin;
public MyActivityManager(IActivityManager origin) {
mOrigin = origin;
}
public int startActivity(IApplicationThread caller, String callingPackage, Intent intent,
String resolvedType, IBinder resultTo, String resultWho, int requestCode, int flags,
ProfilerInfo profilerInfo, Bundle options) throws RemoteException {
// TODO 將要啟動(dòng)的activity替換成StubActivity
return mOrigin. startActivity(caller, callingPackage, intent,
resolvedType, resultTo, resultWho, requestCode, flags,
profilerInfo, options);
}
...
}
Class c = Class.forName("android.app.ActivityManagerNative");
final Field field = c.getDeclaredField("gDefault");
field.setAccessible(true);
Singleton<IActivityManager> proxy = new Singleton<IActivityManager>() {
protected IActivityManager create() {
return new MyActivityManager(field.get(null));
}
};
field.set(null, proxy);
但是這個(gè)做法問(wèn)題很大,首先我們要將IActivityManager的所有方法都實(shí)現(xiàn)一遍轉(zhuǎn)發(fā)給mOrigin。而且最大的問(wèn)題是IActivityManager和Singleton被隱藏了,我們?cè)趹?yīng)用層是找不到定義的!
那怎么辦呢?別急,我們先來(lái)看看Singleton的實(shí)現(xiàn):
public abstract class Singleton<T> {
private T mInstance;
protected abstract T create();
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
其實(shí)最終的IActivityManager是保存在mInstance這個(gè)變量里面的,我們只需要替換這個(gè)變量就好,于是就繞過(guò)了Singleton沒(méi)有定義的問(wèn)題。但是還有這個(gè)IActivityManager的定義問(wèn)題擺在我們面前。
怎么辦呢?答案就是我們可以用動(dòng)態(tài)代理的方法去創(chuàng)建IActivityManager。關(guān)于動(dòng)態(tài)代理我之前寫(xiě)過(guò)一篇博客 《Java自定義注解和動(dòng)態(tài)代理》 ,大家感興趣的話可以去看看。這里就直接把代碼貼上了:
// 獲取gDefault
Class activityManagerClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefault = gDefaultField.get(null);
// 獲取mIntance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object mInstance = mInstanceField.get(gDefault);
// 替換mIntance
Object proxy = Proxy.newProxyInstance(
mInstance.getClass().getClassLoader(),
new Class[]{Class.forName("android.app.IActivityManager")},
new IActivityManagerHandler(mInstance));
mInstanceField.set(gDefault, proxy);
public static class IActivityManagerHandler implements InvocationHandler {
private Object mOrigin;
IActivityManagerHandler(Object origin) {
mOrigin = origin;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
Intent raw = (Intent) args[index];
Intent intent = new Intent();
intent.setClassName(raw.getComponent().getPackageName(), StubActivity.class.getName());
intent.putExtra("RawIntent", raw);
args[index] = intent;
}
return method.invoke(mOrigin, args);
}
}
上面的代碼的功能就是創(chuàng)建一個(gè)IActivityManager的代理,代理startActivity方法,將啟動(dòng)的Activity的Intent換成啟動(dòng)StubActivity的Intent,并且將原來(lái)的Intent保存起來(lái)放到RawIntent這個(gè)Extra里。
然后用它去替換ActivityManagerNative.gDefault的mInstance成員變量。
將StubActivity替換會(huì)要啟動(dòng)的Activity
在上面我們已經(jīng)將要啟動(dòng)的Activity替換成了已經(jīng)注冊(cè)了的StubActivity,這樣在AMS檢查的時(shí)候就能在AndroidManifest查到,不會(huì)報(bào)ActivityNotFoundException了.
然后AMS會(huì)讓ActivityThread去創(chuàng)建Activity,這個(gè)時(shí)候就要將StubActivity替換會(huì)真正要啟動(dòng)的Activity了.
再回顧下這部分的代碼:
public final class ActivityThread {
...
final H mH = new H();
...
@Override
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
...
private class H extends Handler {
public static final int LAUNCH_ACTIVITY = 100;
...
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
final ActivityClientRecord r = (ActivityClientRecord) msg.obj;
r.packageInfo = getPackageInfoNoCheck(
r.activityInfo.applicationInfo, r.compatInfo);
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
...
}
...
}
...
}
ActivityThread的scheduleLaunchActivity方法會(huì)被調(diào)到,然后會(huì)向mH發(fā)送LAUNCH_ACTIVITY消息.
所以關(guān)鍵點(diǎn)就是將這個(gè)mH變量替換成我們的代理對(duì)象,將Intent替換回之前保存的RawIntent.
但是這里有個(gè)問(wèn)題,H是個(gè)內(nèi)部類,我們是沒(méi)有辦法用動(dòng)態(tài)代理的方式創(chuàng)建內(nèi)部類的,也就是說(shuō)我們沒(méi)有辦法替換掉mH這個(gè)對(duì)象.
于是只好繼續(xù)挖一挖Handler內(nèi)部有沒(méi)有機(jī)會(huì)了,其實(shí)在Handler.dispatchMessage里面是會(huì)先判斷mCallback是不是有賦值的,如果有就會(huì)將消息交給它去處理.
public class Handler {
...
final Callback mCallback;
...
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
...
}
所以我們可以從這個(gè)mCallback入手,將mH的mCallback設(shè)置成我們的代理對(duì)象:
// 獲取ActivityThread實(shí)例
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field threadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
threadField.setAccessible(true);
Object sCurrentActivityThread = threadField.get(null);
// 獲取mH變量
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(sCurrentActivityThread);
// 設(shè)置mCallback變量
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
Handler.Callback callback = new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
if (msg.what == 100) {
try {
Field intentField = msg.obj.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
Intent intent = (Intent) intentField.get(msg.obj);
Intent raw = intent.getParcelableExtra("RawIntent");
intent.setComponent(raw.getComponent());
} catch (Exception e) {
Log.e("hook", "get intent err", e);
}
}
return false;
}
};
mCallbackField.set(mH, callback);
ActivityThread的實(shí)例保存在sCurrentActivityThread這個(gè)靜態(tài)成員變量里,代碼我就不貼了,然后我們?cè)趍Callback這里將要啟動(dòng)的Activity設(shè)置回來(lái).
處理Android 8.0的情況
上面的代碼運(yùn)行在8.0的系統(tǒng)上會(huì)崩潰,原因是8.0對(duì)Activity的啟動(dòng)這塊做了些改動(dòng),不再使用ActivityManagerNative.getDefault()了,改成了ActivityManager.getService():
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
int result = ActivityManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
checkStartActivityResult(result, intent);
...
}
ActivityManager其實(shí)和ActivityManagerNative很像:
public class ActivityManager {
...
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
...
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
...
}
所以我們類似的去替換IActivityManagerSingleton就好了:
// 獲取IActivityManagerSingleton
Class activityManagerClass = Class.forName("android.app.ActivityManager");
Field singletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
singletonField.setAccessible(true);
Object gDefault = singletonField.get(null);
// 獲取mIntance
Class singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object mInstance = mInstanceField.get(gDefault);
// 替換mIntance
Object proxy = Proxy.newProxyInstance(
mInstance.getClass().getClassLoader(),
new Class[]{Class.forName("android.app.IActivityManager")},
new IActivityManagerHandler(mInstance));
mInstanceField.set(gDefault, proxy);
處理AppCompatActivity的情況
到目前為止,我們已經(jīng)可以正常啟動(dòng)沒(méi)有注冊(cè)的Activity了,但是其實(shí)還有一個(gè)BUG:如果啟動(dòng)的是沒(méi)有注冊(cè)的AppCompatActivity就會(huì)崩潰。
10-25 19:32:30.867 8754 8754 E AndroidRuntime: Caused by: java.lang.IllegalArgumentException: android.content.pm.PackageManager$NameNotFoundException: ComponentInfo{me.linjw.plugindemo/me.linjw.plugindemo.HideActivity}
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.support.v4.app.NavUtils.getParentActivityName(NavUtils.java:285)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.support.v7.app.AppCompatDelegateImplV9.onCreate(AppCompatDelegateImplV9.java:158)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.support.v7.app.AppCompatDelegateImplV14.onCreate(AppCompatDelegateImplV14.java:58)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.support.v7.app.AppCompatActivity.onCreate(AppCompatActivity.java:72)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at com.cvte.tv.speech.TestActivity.onCreate(TestActivity.java:14)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.app.Activity.performCreate(Activity.java:6664)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1118)
10-25 19:32:30.867 8754 8754 E AndroidRuntime: at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2599)
網(wǎng)上很多講啟動(dòng)未注冊(cè)的Activity的文章要不就沒(méi)有講這個(gè),要不就沒(méi)有詳細(xì)講如何處理,直接一筆帶過(guò)了.這里我手把手帶大家解BUG.
遇到問(wèn)題先不要慌,先看看打印找到崩潰的代碼在哪:
@Nullable
public static String getParentActivityName(Activity sourceActivity) {
try {
return getParentActivityName(sourceActivity, sourceActivity.getComponentName());
} catch (NameNotFoundException e) {
// Component name of supplied activity does not exist...?
throw new IllegalArgumentException(e);
}
}
@Nullable
public static String getParentActivityName(Context context, ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
String parentActivity = IMPL.getParentActivityName(context, info);
return parentActivity;
}
很明顯是PackageManager.getActivityInfo在AndroidManifest里面找不到Activity拋出了NameNotFoundException.
所以我們看看有沒(méi)有辦法替換一下這個(gè)Context.getPackageManager()拿到的PackageManager:
class ContextImpl extends Context {
...
@Override
public PackageManager getPackageManager() {
if (mPackageManager != null) {
return mPackageManager;
}
IPackageManager pm = ActivityThread.getPackageManager();
if (pm != null) {
// Doesn't matter if we make more than one instance.
return (mPackageManager = new ApplicationPackageManager(this, pm));
}
return null;
}
...
}
ContextImpl會(huì)從ActivityThread.getPackageManager獲取IPackageManager,讓我們繼續(xù)挖:
public final class ActivityThread {
...
static volatile IPackageManager sPackageManager;
...
public static IPackageManager getPackageManager() {
if (sPackageManager != null) {
//Slog.v("PackageManager", "returning cur default = " + sPackageManager);
return sPackageManager;
}
IBinder b = ServiceManager.getService("package");
//Slog.v("PackageManager", "default service binder = " + b);
sPackageManager = IPackageManager.Stub.asInterface(b);
//Slog.v("PackageManager", "default service = " + sPackageManager);
return sPackageManager;
}
...
}
所以sPackageManager就是我們的突破點(diǎn),讓我們來(lái)把它換掉:
try {
//要先獲取一下,保證它初始化
context.getPackageManager();
Class activityThread = Class.forName("android.app.ActivityThread");
Field pmField = activityThread.getDeclaredField("sPackageManager");
pmField.setAccessible(true);
final Object origin = pmField.get(null);
Object handler = Proxy.newProxyInstance(activityThread.getClassLoader(),
new Class[]{Class.forName("android.content.pm.IPackageManager")},
new PackageManagerHandler(context, origin));
pmField.set(null, handler);
} catch (Exception e) {
Log.e("hook", "hook IPackageManager err", e);
}
static class PackageManagerHandler implements InvocationHandler {
private Context mContext;
private Object mOrigin;
PackageManagerHandler(Context context, Object origin) {
mContext = context;
mOrigin = origin;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (!method.getName().equals("getActivityInfo")) {
return method.invoke(mOrigin, args);
}
//如果沒(méi)有注冊(cè),并不會(huì)拋出異常,而是會(huì)直接返回null
Object ret = method.invoke(mOrigin, args);
if (ret == null) {
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof ComponentName) {
ComponentName componentName = (ComponentName) args[i];
componentName.getClassName();
args[i] = new ComponentName(
mContext.getPackageName(),
StubActivity.class.getName()
);
return method.invoke(mOrigin, args);
}
}
}
return ret;
}
}
在IPackageManager.getActivityInfo方法拋出異常的時(shí)候invoke會(huì)返回null,就代表這個(gè)Activity沒(méi)有注冊(cè),我們直接將他換成StubActivity就好。
大功告成!
完整Demo
完整Demo見(jiàn)我的Github