在前一篇文章Activity啟動過程分析中葛峻,通過源碼分析的方式介紹了Activity的大致啟動過程膨报。今天就來實(shí)戰(zhàn)一下此蜈,一個是加深對Activity啟動過程的理解,另外一個就是讓我們知道閱讀源碼有助于拓寬視野袄秩,提升開發(fā)能力阵翎。
首先先拋出需求:
我們想啟動一個Activity A頁面逢并,但是想要進(jìn)入這個A頁面必須是已經(jīng)登錄過的,如果沒有登錄的話就啟動登錄頁面B郭卫,并且在B頁面登錄成功之后需要跳轉(zhuǎn)到頁面A
提升一下難度砍聊,Activity頁面A、B均沒有在清單文件中注冊贰军,但是要完成正常的跳轉(zhuǎn)(這是為插件化的研究做準(zhǔn)備)
在閱讀本文之前玻蝌,可以先clone一份 apk-plugin-technology,參考此項(xiàng)目的
binder-hook
模塊词疼。運(yùn)行一下Demo俯树,讓你有個更感性的認(rèn)識
Hook技術(shù)
Hook,就是鉤子贰盗,也就是說可以干預(yù)某些代碼的正常執(zhí)行流程许饿。關(guān)于Hook的詳細(xì)介紹,可以自行搜索相關(guān)文章舵盈。
完成Hook過程陋率,需要注意3點(diǎn),也可以說是3個步驟:
尋找Hook點(diǎn)秽晚,Hook點(diǎn)一般要選擇類的靜態(tài)成員變量瓦糟,因?yàn)殪o態(tài)成員變量一般不容易發(fā)生變化,只需要Hook一次就好了赴蝇。如果想要Hook的成員變量不是靜態(tài)的菩浙,那么可以找這個變量所持有的引用中是否有靜態(tài)的成員變量。而且最好是public的扯再,public的一般不容易發(fā)生變化芍耘。如果是非public的,就要考慮適配問題了熄阻。
選擇合適的代理方式,一般來說我們不可能Hook一個對象的所有方法倔约,所以就要通過代理的方式來Hook秃殉,如果是想要Hook的方法,就要走我們自己的邏輯浸剩,如果是不需要Hook的方法钾军,還是要調(diào)用原對象的方法。
用代理對象替換原始對象
這個過程可能還有點(diǎn)不是很清晰绢要,沒關(guān)系吏恭,繼續(xù)往下看就明白了。
Activity啟動過程尋找Hook點(diǎn)
我們知道重罪,啟動一個Activity樱哼,可以通過Activity本身的startActivity方法哀九,也可以調(diào)用Context的startActivity方法,雖然Activity也是繼承自Context搅幅,但是Activity重寫了相關(guān)的啟動方法阅束,這是因?yàn)锳ctivity本身有Activity任務(wù)棧,而其它的Context茄唐,比如Service中并沒有任務(wù)棧息裸,所以啟動的時候需要加上一個flag Intent.FLAG_ACTIVITY_NEW_TASK
,Context是一個抽象類沪编,其啟動Activity的方法呼盆,實(shí)際上調(diào)用的是ContextImpl的startActivity方法,例如:
@Override
public void startActivity(Intent intent, Bundle options) {
warnIfCallingFromSystemProcess();
// Calling start activity from outside an activity without FLAG_ACTIVITY_NEW_TASK is
// generally not allowed, except if the caller specifies the task id the activity should
// be launched in.
if ((intent.getFlags()&Intent.FLAG_ACTIVITY_NEW_TASK) == 0
&& options != null && ActivityOptions.fromBundle(options).getLaunchTaskId() == -1) {
throw new AndroidRuntimeException(
"Calling startActivity() from outside of an Activity "
+ " context requires the FLAG_ACTIVITY_NEW_TASK flag."
+ " Is this really what you want?");
}
mMainThread.getInstrumentation().execStartActivity(
getOuterContext(), mMainThread.getApplicationThread(), null,
(Activity) null, intent, -1, options);
}
可以看到蚁廓,如果不加FLAG_ACTIVITY_NEW_TASK
標(biāo)記的話會拋出異常宿亡。
另外,從這個方法結(jié)合我們之前Activity啟動過程分析中所分析的纳令,不管是通過Activity調(diào)用還是通過Context調(diào)用挽荠,最終調(diào)用的均是Instrumentation的execStartActivity方法。
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
...
try {
...
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
...
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
execStartActivity中會調(diào)用ActivityManagerNative.getDefault()方法平绩,
static public IActivityManager getDefault() {
return gDefault.get();
}
private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = ServiceManager.getService("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
static public IActivityManager asInterface(IBinder obj) {
if (obj == null) {
return null;
}
IActivityManager in =
(IActivityManager)obj.queryLocalInterface(descriptor);
if (in != null) {
return in;
}
return new ActivityManagerProxy(obj);
}
ActivityManagerNative的gDefault 是一個靜態(tài)變量圈匆,它是Singleton的一個匿名類,Singleton類其實(shí)就是用于獲取單例對象的捏雌,gDefault的get方法獲取的是IActivityManager的一個實(shí)現(xiàn)類跃赚。知道Binder的應(yīng)該知道,這個獲取的實(shí)際上是ActivityManagerProxy對象性湿,如果不明白的建議先去看看 Binder學(xué)習(xí)概要這篇文章纬傲。
ActivityManagerProxy對應(yīng)的server端就是ActivityManagerService,也就是真正負(fù)責(zé)管理啟動Activity的地方肤频。我們啟動一個Activity就是調(diào)用的ActivityManagerService的startActivity方法叹括。
那么我們想一想,是否可以在gDefault這個方法做點(diǎn)文章呢宵荒。我們推理一下這個邏輯:
- hook 的是Activity的啟動過程
- ActivityManagerService負(fù)責(zé)管理啟動汁雷,但是它在Server端,我們拿不到报咳,但是通過gDefault我們可以拿到它在本地的代理對象ActivityManagerProxy對象侠讯。
- 我們需要為這個ActivityManagerProxy對象創(chuàng)建一個代理對象,當(dāng)它調(diào)用startActivity方法的時候暑刃,需要做一些處理厢漩,比如按照需求1,判斷被跳轉(zhuǎn)的頁面是否需要登錄岩臣,如果需要登錄的話就更改這個Intent溜嗜,跳轉(zhuǎn)到登錄頁面宵膨。當(dāng)調(diào)用其它的方法的時候,直接使用原始的ActivityManagerProxy對象去處理粱胜。
講到這柄驻,其實(shí)我們就可以解決需求1了。但是我想把需求2一起解決了焙压,這樣的話上面的邏輯就有點(diǎn)不夠完善鸿脓,畢竟我們所要啟動的Activity A和登錄頁面B都是沒有在AndroidManifest.xml中聲明的,啟動一個未聲明的Activity肯定會報一個ActivityNotFoundException涯曲。
再來回想一下Activity的啟動過程:
調(diào)用startActivity方法啟動一個目標(biāo)Activity的時候野哭,實(shí)際上會通過Instrumentation進(jìn)行啟動,再通過ActivityManagerService的本地代理對象調(diào)用ActivityManagerService的方法來啟動一個Activity幻件,這是一個IPC過程拨黔,在ActivityManagerService中會校驗(yàn)被啟動的Activity的合法性,如果合法绰沥,會通過IPC過程調(diào)用ApplicationThread的方法篱蝇,進(jìn)而調(diào)用ActivityThread的handleLaunchActivity方法創(chuàng)建Activity,并執(zhí)行Activity的生命周期方法徽曲。
看到?jīng)]零截,Activity的啟動過程是分兩步的
- ActivityManagerService去校驗(yàn)被啟動Activity合法性,并做好啟動Activity的必要準(zhǔn)備
- 在ActivityThread中真正的創(chuàng)建Activity秃臣,并完成Activity的啟動階段的生命周期回調(diào)涧衙。
既然沒法辦通過AMS去啟動一個未注冊的Activity,那么我們換一個思路來:
- 我們找一個在AndroidManifest.xml中聲明一個代理頁面ProxyActivity奥此,當(dāng)發(fā)起請求A頁面的時候弧哎,我們hook ActivityManagerProxy的startActivity方法,把A頁面的Intent替換為請求啟動ProxyActivity頁面的Intent稚虎,這樣的話至少可以通過ActivityManagerService校驗(yàn)這一關(guān)撤嫩。
- 當(dāng)調(diào)用回我們ActivityThread的內(nèi)部中的時候,做一下處理祥绞,把代理頁面ProxyActivity對應(yīng)的Intent替換成我們想要啟動的Activity A對應(yīng)的Intent非洲,當(dāng)然了,在這一過程還需要判斷是否需要登錄蜕径,如果需要登錄的話,就需要替換成B頁面败京。示例代碼如下:
private void hookActivityManagerApi25() {
try {
// 反射獲取ActivityManagerNative的靜態(tài)成員變量gDefault, 注意兜喻,在8.0的時候這個已經(jīng)更改了
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
Field gDefaultField = activityManagerNativeClass.getDeclaredField("gDefault");
gDefaultField.setAccessible(true);
Object gDefaultObj = gDefaultField.get(null);
// 我們在這里拿到的instanceObj對象一定不為空,如果為空的話就沒辦法使用
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Object instanceObj = mInstanceField.get(gDefaultObj);
// 需要動態(tài)代理IActivityManager赡麦,把Singleton的成員變量mInstance的值設(shè)置為我們的這個動態(tài)代理對象
// 但是有一點(diǎn)朴皆,我們不可能完全重寫一個IActivityManager的實(shí)現(xiàn)類
// 所以還是需要用到原始的IActivityManager對象帕识,只是在調(diào)用某些方法的時候做一些手腳
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
InterceptInvocationHandler interceptInvocationHandler = new InterceptInvocationHandler(instanceObj);
Object iActivityManagerObj = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{iActivityManagerClass}, interceptInvocationHandler);
mInstanceField.set(gDefaultObj, iActivityManagerObj);
} catch (Exception e) {
e.printStackTrace();
}
}
class InterceptInvocationHandler implements InvocationHandler {
Object originalObject;
public InterceptInvocationHandler(Object originalObject) {
this.originalObject = originalObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LogUtils.d("method:" + method.getName() + " called with args:" + Arrays.toString(args));
//如果是startActivity方法,需要做一些手腳
if (METHOD_START_ACTIVITY.equals(method.getName())) {
Intent newIntent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
Object arg = args[i];
if (arg instanceof Intent) {
Intent wantedIntent = (Intent) arg;
// 加入目標(biāo)Activity沒有在清單文件中注冊遂铡,我們就欺騙ActivityManagerService肮疗,啟動一個代理頁面
// 真正啟動頁面,會開始回調(diào)ActivityThread的handleLaunchActivity方法
// 調(diào)用這個方法前可以做點(diǎn)文章扒接,啟動我們想要啟動的頁面
newIntent = new Intent();
ComponentName componentName = new ComponentName(context, ProxyActivity.class);
newIntent.setComponent(componentName);
//把原始的跳轉(zhuǎn)信息當(dāng)作參數(shù)攜帶給代理類
newIntent.putExtra(EXTRA_REAL_WANTED_INTENT, wantedIntent);
index = i;
}
}
args[index] = newIntent;
}
return method.invoke(originalObject, args);
}
}
那么在ActivityThread中怎么找Hook點(diǎn)呢伪货?
首先要明確一點(diǎn),我們找找個Hook點(diǎn)是要為了替換之前代理ProxyActivity的Intent的钾怔,有了找個思路碱呼,我們就可以有目的的去尋找了。
AMS啟動一個Activity宗侦,會調(diào)用ApplicationThread的scheduleLaunchActivity方法愚臀,這個方法應(yīng)該是在Activity啟動過程中我們的App最先被AMS調(diào)用的了,在這個方法中第一個參數(shù)就是Intent矾利,這個Intent就是我們發(fā)起請求啟動ProxyActivity的Intent姑裂。
@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) {
updateProcessState(procState, false);
ActivityClientRecord r = new ActivityClientRecord();
r.token = token;
r.ident = ident;
// 注意這個參數(shù)
r.intent = intent;
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
ApplicationThread繼承自ApplicationThreadNative,而ApplicationThreadNative繼承自Binder并實(shí)現(xiàn)了IApplicationThread接口男旗。我們考慮一下舶斧,能否像Hook ActivityManagerProxy那樣,采用一個動態(tài)代理的方式剑肯,創(chuàng)建IApplicationThread的代理類捧毛,當(dāng)調(diào)用IApplicationThread的scheduleLaunchActivity方法的時候,我們更改這個方法的Intent參數(shù)让网,變?yōu)槲覀兿胍哪莻€Intent呀忧,然后就可以按照我們的需求來跳轉(zhuǎn)了。
private class ApplicationThread extends ApplicationThreadNative
public abstract class ApplicationThreadNative extends Binder
implements IApplicationThread
想法是很好的溃睹,但是很遺憾而账,我們做不到,至于為什么因篇,請接著往下看泞辐。
我們想要Hook ApplicationThread的scheduleLaunchActivity,那么我們先看一下這個ApplicationThread對象是什么時候創(chuàng)建的竞滓。ApplicationThread是ActivityThread的非靜態(tài)內(nèi)部類咐吼,在ActivityThread中,它的創(chuàng)建時機(jī)是在ActivityThread對象初始化的時候商佑,
final ApplicationThread mAppThread = new ApplicationThread();
由于它沒有采用多態(tài)的方式來創(chuàng)建ApplicationThread锯茄,我們創(chuàng)建的動態(tài)代理對象實(shí)際上是沒有辦法賦值給mAppThread這個變量的,也就是說實(shí)際上這個點(diǎn)我們是沒有辦法hook的。
那么我們接著這個方法來看肌幽,在scheduleLaunchActivity方法中晚碾,通過ActivityThread的一個H類型的成員mH來發(fā)送一個類型為 H.LAUNCH_ACTIVITY (int 型,值為100)的消息喂急,這個H是ActivityThread的非靜態(tài)內(nèi)部類格嘁,實(shí)際上是繼承自Handler的。
private class H extends Handler
至于為什么需要用Handler來切換廊移,在Binder學(xué)習(xí)概要
已經(jīng)介紹過糕簿,因?yàn)閟cheduleLaunchActivity是在Binder線程池中被調(diào)用的,需要用Hander來切換到主線程画机。H.LAUNCH_ACTIVITY類型的消息發(fā)送之后冶伞,H的handleMessage方法會被調(diào)用,在這里就會根據(jù)msg.what的來處理步氏,對應(yīng)LAUNCH_ACTIVITY類型的响禽,會調(diào)用ActivityThread的handleLaunchActivity來創(chuàng)建Activity并完成Activity啟動過程的生命周期回調(diào)榛丢。
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
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);
// 調(diào)用ActivityThread的方法來啟動Activity
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
} break;
....
}
...
}
再往下就要走到ActivityThread的handleLaunchActivity方法中了肪康,難道我們要去Hook ActivityThread的handleLaunchActivity方法?
首先错忱,獲取這個ActivityThread對象是沒有難度的界阁,ActivityThread對象可以在它的類成員變量sCurrentActivityThread獲取侯繁。
private static volatile ActivityThread sCurrentActivityThread;
應(yīng)用的啟動入口是ActivityThread的main方法,在這個方法中會創(chuàng)建ActivityThread對象泡躯,接著又會調(diào)用它的attach方法贮竟,在這個方法中,把ActivityThread對象賦值給其類的靜態(tài)成員變量sCurrentActivityThread较剃。靜態(tài)成員變量就很好說了咕别,通過反射就可以獲取這個對象。
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
}
private void attach(boolean system) {
sCurrentActivityThread = this;
...
}
雖然獲取了這個ActivityThread對象写穴,但是我們怎么準(zhǔn)備一個代理對象來代理ActivityThread對象呢惰拱?
由于ActivityThread沒有繼承或?qū)崿F(xiàn)任何類或接口,好像為它準(zhǔn)備代理對象有點(diǎn)難度啊送。
public final class ActivityThread
難道沒有辦法了嗎偿短?
當(dāng)然不是,否則我還寫這篇文章干嘛馋没?
想一想Handler那塊昔逗,從發(fā)送消息到處理消息,實(shí)際上中間是有一個消息分發(fā)過程的篷朵,也就是Handler的dispatchMessage方法會被調(diào)用纤子,在這個方法中實(shí)際上Handler本身的handleMessage方法是最后才可能會被調(diào)用到的。msg.callback
這塊我們沒辦法處理款票,因?yàn)橄?chuàng)建是我們控制不了控硼,而在else中,mCallback != null
這塊艾少,我們似乎可以給Hander設(shè)置一個mCallback卡乾,由這個Callback先一步處理消息,替換Intent缚够。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
我們看一下mCallBack的類型幔妨,它是Handler內(nèi)部的一個接口。
public interface Callback {
public boolean handleMessage(Message msg);
}
那我們趕緊去找找怎么設(shè)置Callback谍椅,不過很遺憾的是設(shè)置Callback只有一種方式就是作為Handler構(gòu)造方法參數(shù)傳遞误堡,但我們的mH對象已經(jīng)創(chuàng)建了。既然正常的路徑?jīng)]辦法了雏吭,那只要采用反射的方式來設(shè)置成員變量了锁施。
private void hookActivityThreadHandler() {
//需要hook ActivityThread
try {
//獲取ActivityThread的成員變量 sCurrentActivityThread
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThread = activityThreadClass.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThread.setAccessible(true);
Object activityThreadObj = sCurrentActivityThread.get(null);
//獲取ActivityThread的成員變量 mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mHObj = (Handler) mHField.get(activityThreadObj);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mHObj, new ActivityCallback(mHObj));
} catch (Exception e) {
e.printStackTrace();
}
}
private class ActivityCallback implements Handler.Callback {
private Handler mH;
public ActivityCallback(Handler mH) {
this.mH = mH;
}
@Override
public boolean handleMessage(Message msg) {
if (msg.what == LAUNCH_ACTIVITY) {
handleLaunchActivity(msg);
}
return false;
}
private void handleLaunchActivity(Message msg) {
//替換我們真正想要的intent
try {
Object activityClientRecord = msg.obj;
Field intentField = activityClientRecord.getClass().getDeclaredField("intent");
intentField.setAccessible(true);
//這個是代理ProxyActivity
Intent interceptedIntent = (Intent) intentField.get(activityClientRecord);
//真正想要跳轉(zhuǎn)的 SecondActivity
Intent realWanted = interceptedIntent.getParcelableExtra(EXTRA_REAL_WANTED_INTENT);
if (realWanted != null) {
//如果不需要登錄
Class<?> real = Class.forName(realWanted.getComponent().getClassName());
NeedLogin annotation = real.getAnnotation(NeedLogin.class);
if (annotation != null && !SPHelper.getBoolean("login", false)) {
//如果需要登錄并且沒有登錄,跳轉(zhuǎn)登錄頁面
Intent loginIntent = new Intent(context, LoginActivity.class);
loginIntent.putExtra(EXTRA_REAL_WANTED_INTENT, realWanted);
interceptedIntent.setComponent(loginIntent.getComponent());
} else {
interceptedIntent.setComponent(realWanted.getComponent());
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
到此杖们,就可以通過Hook的方式來啟動一個未在AndroidManifest.xml聲明的Activity了悉抵,并且可以根據(jù)是否需要登錄來跳轉(zhuǎn)到不同的頁面。
繼承AppCompatActivity會遇到的問題
如果你所有的Activity均繼承的是Activity摘完,上面的代碼邏輯已經(jīng)是沒有問題了姥饰。但是,如果你的Activity類繼承的是AppCompatActivity孝治,是會報一個異常:
android.content.pm.PackageManager$NameNotFoundException
報這個異常的原因是在AppCompatActivity的onCreate方法中經(jīng)過層層調(diào)用列粪,會調(diào)用到NavUtils的getParentActivityName方法。在這個方法中會調(diào)用到PackageManager的getActivityInfo方法谈飒,返回的ActivityInfo對象是Activity在AndroidManifest.xml中注冊信息對應(yīng)的一個JavaBean對象岂座,調(diào)用這個方法實(shí)際上會再檢查一次Activity的合法性。
# android.support.v4.app.NavUtils
public static String getParentActivityName(Context context, ComponentName componentName)
throws NameNotFoundException {
PackageManager pm = context.getPackageManager();
ActivityInfo info = pm.getActivityInfo(componentName, PackageManager.GET_META_DATA);
if (Build.VERSION.SDK_INT >= 16) {
String result = info.parentActivityName;
if (result != null) {
return result;
}
}
if (info.metaData == null) {
return null;
}
String parentActivity = info.metaData.getString(PARENT_ACTIVITY);
if (parentActivity == null) {
return null;
}
if (parentActivity.charAt(0) == '.') {
parentActivity = context.getPackageName() + parentActivity;
}
return parentActivity;
}
在上面方法中步绸,context.getPackageManager()實(shí)際上會調(diào)用ContextImpl的getPackageManager方法掺逼,而這個實(shí)際上返回的是ApplicationPackageManager對象,這個類是把IPackageManager進(jìn)行了包裝瓤介,實(shí)際上的功能還是由PackageManagerService調(diào)用吕喘。
# android.app.ContextImpl
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;
}
而ApplicationPackageManager 調(diào)用getActivityInfo實(shí)際上調(diào)用的IPackageManagerd的getActivityInfo方法
# android.app.ApplicationPackageManager
@Override
public ActivityInfo getActivityInfo(ComponentName className, int flags)
throws NameNotFoundException {
try {
ActivityInfo ai = mPM.getActivityInfo(className, flags, mContext.getUserId());
if (ai != null) {
return ai;
}
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
throw new NameNotFoundException(className.toString());
}
這個過程就是經(jīng)過一個IPC過程調(diào)用PackageManagerService的getActivityInfo方法。
為了不報NameNotFoundException異常刑桑,我們需要Hook這個IPackageManager氯质,當(dāng)調(diào)用PackageManager的getActivityInfo的時候,不讓它進(jìn)行IPC調(diào)用祠斧,而是直接返回一個不為null的ActivityInfo對象闻察,這樣就可以解決問題了。
Hook PMS
我們要去Hook PMS,還是遵循之前講的3個步驟辕漂,那么就先來找這個Hook點(diǎn)呢灶,上面我們在貼出的代碼中也看到了,ContextImpl的getPackageManager方法中首先會獲取調(diào)用ActivityThread的靜態(tài)方法getPackageManager來獲取一個IPackageManager對象钉嘹。我們來看一下這個方法鸯乃。
# android.app.ActivityThread
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;
}
在這里有一個靜態(tài)變量sPackageManager,如果不為空的話直接就返回了跋涣,這個靜態(tài)變量的類型是接口類型缨睡,那么這個Hook點(diǎn)就很好,靜態(tài)的很好獲取對象陈辱,而接口類型更容易使用代理奖年。
static volatile IPackageManager sPackageManager;
前面講了那么多,怎么去Hook應(yīng)該也知道 了沛贪,我們目前只Hook IPackageManager的getActivityInfo方法陋守,廢話也不多說了,直接貼代碼鹏浅,更直觀嗅义。
private void HookPackageManager() {
//需要hook ActivityThread
try {
//獲取ActivityThread的成員變量 sCurrentActivityThread
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field sPackageManagerField = activityThreadClass.getDeclaredField("sPackageManager");
sPackageManagerField.setAccessible(true);
Object iPackageManagerObj = sPackageManagerField.get(null);
Class<?> iPackageManagerClass = Class.forName("android.content.pm.IPackageManager");
InterceptPackageManagerHandler interceptInvocationHandler = new InterceptPackageManagerHandler(iPackageManagerObj);
Object iPackageManagerObjProxy = Proxy.newProxyInstance(context.getClassLoader(), new Class[]{iPackageManagerClass}, interceptInvocationHandler);
sPackageManagerField.set(null, iPackageManagerObjProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
private class InterceptPackageManagerHandler implements InvocationHandler {
Object originalObject;
public InterceptPackageManagerHandler(Object originalObject) {
this.originalObject = originalObject;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
LogUtils.d("method:" + method.getName() + " called with args:" + Arrays.toString(args));
if (METHOD_GET_ACTIVITY_INFO.equals(method.getName())) {
return new ActivityInfo();
}
return method.invoke(originalObject, args);
}
}
AMS與ActivityThread通過token進(jìn)行通信
雖然我們啟動了Activity A或者B,但是AMS實(shí)際上還是以為我們啟動的是ProxyActivity隐砸。不信的話之碗,可以使用命令行查看。
adb shell dumpsys activity activities | grep mFocusedActivity
結(jié)果如下季希,可以看到褪那,在AMS端記錄的Activity實(shí)際上是ProxyActivity。
mFocusedActivity: ActivityRecord{4ff4194 u0 com.sososeen09.binder.hook/.ProxyActivity t5057}
那么通過這種Hook方式啟動的Activity 式塌,還具有完整的生命周期嗎博敬?
答案是肯定的。
我們知道Activity是不具有跨進(jìn)程通訊的能力的峰尝,那么AMS是如何管理Activity偏窝,控制Activity的聲明周期的呢?答案就是一個通過一個Ibinder類型的token變量來控制武学。ASM通過這個token來與ApplicationThread進(jìn)行通訊祭往,進(jìn)行控制Activity的聲明周期。在AMS那邊火窒,它以為token表示的是ProxyActivity硼补,但是在客戶端這邊,token實(shí)際上指的是Activity A或者B熏矿。
這個token是在AMS在回到IApplicationThread的scheduleLaunchActivity方法中傳遞過來的第二個參數(shù)已骇。
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) {
這個會存在ActivityClientRecord中离钝。這個token和對應(yīng)的ActivityClientRecord會以鍵值對的形式存儲在ActivityThread的變量mActivities中。后面再對Activity進(jìn)行生命周期方法調(diào)用的時候褪储,均可以通過AMS端傳過來的token來獲取正確的Activity卵渴。
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
總結(jié)
前面講了一大堆,我們現(xiàn)在來總結(jié)概括一下這個過程和原理乱豆。
再來看一下需求:
我們想啟動一個Activity A頁面奖恰,但是想要進(jìn)入這個A頁面必須是已經(jīng)登錄過的,如果沒有登錄的話就啟動登錄頁面B宛裕,并且在B頁面登錄成功之后需要跳轉(zhuǎn)到頁面A
Activity頁面A、B均沒有在清單文件中注冊论泛,但是要完成正常的跳轉(zhuǎn)
為什么我們可以跳轉(zhuǎn)到一個未在AndroidManifest.xml中聲明的Activity中揩尸,而且可以根據(jù)不同的邏輯跳轉(zhuǎn)到不同的頁面呢?
調(diào)用startActivity方法啟動一個目標(biāo)Activity的時候屁奏,實(shí)際上會通過Instrumentation進(jìn)行啟動岩榆,再通過ActivityManagerService的本地代理對象調(diào)用ActivityManagerService的方法來啟動一個Activity,這是一個IPC過程坟瓢,在ActivityManagerService中會校驗(yàn)被啟動的Activity的合法性勇边,如果合法,會通過IPC過程調(diào)用ApplicationThread的方法折联,ApplicationThread是一個Binder對象粒褒,它的方法運(yùn)行是在Binder線程池中的,所以需要采用一個Handler把方法調(diào)用切換到主線程诚镰,ApplicationThread通過發(fā)送消息奕坟,進(jìn)而調(diào)用ActivityThread的handleLaunchActivity方法創(chuàng)建Activity,并執(zhí)行Activity的生命周期方法清笨。
傳遞到ActivityManagerService的被啟動的Activity信息必須是聲明過的月杉,而如果我們想要啟動一個沒有在AndroidManifest.xml中聲明的Activity,可以通過欺上瞞下的方法抠艾,hook ActivityManagerService在本地的代理對象苛萎,如果調(diào)用的是ActivityManagerProxy的startActivity方法,那么就更改這個Intent检号,替換成啟動一個聲明過的ProxyActivity腌歉,當(dāng)ActivityManagerService校驗(yàn)完啟動的合法性之后,會通過ApplicationThread調(diào)用到ActivityThread的一個叫做mH的Handler中來谨敛。當(dāng)Handler收到消息的時候究履,會有一個消息分發(fā)的過程,如果給Handler設(shè)置了一個Callback脸狸,這個Callback的handleMessage方法就會先于Handler本身的handleMessage方法調(diào)用最仑。所以可以想辦法給這個叫做mH的Handler對象設(shè)置Callback藐俺,并且在Callback的handleMessage方法中從Message上面拿到相關(guān)的Intent信息,此時的Intent還是跳轉(zhuǎn)到代理頁面泥彤,可以根據(jù)當(dāng)前是否登錄欲芹,是否需要重定向到登錄頁面等對這個Intent進(jìn)行相應(yīng)的處理,比如設(shè)置為跳轉(zhuǎn)到登錄頁或者真正想要跳轉(zhuǎn)的頁面吟吝,并由后續(xù)的mH的handleMessage來調(diào)用菱父。
更多細(xì)節(jié),請查看項(xiàng)目 apk-plugin-technology