在前一篇文章Activity启动过程分析中,通过源码分析的方式介绍了Activity的大致启动过程。今天就来实战一下,一个是加深对Activity启动过程的理解,另外一个就是让我们知道阅读源码有助于拓宽视野,提升开发能力。
首先先抛出需求:
我们想启动一个Activity A页面,但是想要进入这个A页面必须是已经登录过的,如果没有登录的话就启动登录页面B,并且在B页面登录成功之后需要跳转到页面A
提升一下难度,Activity页面A、B均没有在清单文件中注册,但是要完成正常的跳转(这是为插件化的研究做准备)
在阅读本文之前,可以先clone一份 apk-plugin-technology,参考此项目的
binder-hook
模块。运行一下Demo,让你有个更感性的认识
Hook技术
Hook,就是钩子,也就是说可以干预某些代码的正常执行流程。关于Hook的详细介绍,可以自行搜索相关文章。
完成Hook过程,需要注意3点,也可以说是3个步骤:
寻找Hook点,Hook点一般要选择类的静态成员变量,因为静态成员变量一般不容易发生变化,只需要Hook一次就好了。如果想要Hook的成员变量不是静态的,那么可以找这个变量所持有的引用中是否有静态的成员变量。而且最好是public的,public的一般不容易发生变化。如果是非public的,就要考虑适配问题了。
选择合适的代理方式,一般来说我们不可能Hook一个对象的所有方法,所以就要通过代理的方式来Hook,如果是想要Hook的方法,就要走我们自己的逻辑,如果是不需要Hook的方法,还是要调用原对象的方法。
用代理对象替换原始对象
这个过程可能还有点不是很清晰,没关系,继续往下看就明白了。
Activity启动过程寻找Hook点
我们知道,启动一个Activity,可以通过Activity本身的startActivity方法,也可以调用Context的startActivity方法,虽然Activity也是继承自Context,但是Activity重写了相关的启动方法,这是因为Activity本身有Activity任务栈,而其它的Context,比如Service中并没有任务栈,所以启动的时候需要加上一个flag Intent.FLAG_ACTIVITY_NEW_TASK
,Context是一个抽象类,其启动Activity的方法,实际上调用的是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
标记的话会抛出异常。
另外,从这个方法结合我们之前Activity启动过程分析中所分析的,不管是通过Activity调用还是通过Context调用,最终调用的均是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中会调用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 是一个静态变量,它是Singleton的一个匿名类,Singleton类其实就是用于获取单例对象的,gDefault的get方法获取的是IActivityManager的一个实现类。知道Binder的应该知道,这个获取的实际上是ActivityManagerProxy对象,如果不明白的建议先去看看 Binder学习概要这篇文章。
ActivityManagerProxy对应的server端就是ActivityManagerService,也就是真正负责管理启动Activity的地方。我们启动一个Activity就是调用的ActivityManagerService的startActivity方法。
那么我们想一想,是否可以在gDefault这个方法做点文章呢。我们推理一下这个逻辑:
- hook 的是Activity的启动过程
- ActivityManagerService负责管理启动,但是它在Server端,我们拿不到,但是通过gDefault我们可以拿到它在本地的代理对象ActivityManagerProxy对象。
- 我们需要为这个ActivityManagerProxy对象创建一个代理对象,当它调用startActivity方法的时候,需要做一些处理,比如按照需求1,判断被跳转的页面是否需要登录,如果需要登录的话就更改这个Intent,跳转到登录页面。当调用其它的方法的时候,直接使用原始的ActivityManagerProxy对象去处理。
讲到这,其实我们就可以解决需求1了。但是我想把需求2一起解决了,这样的话上面的逻辑就有点不够完善,毕竟我们所要启动的Activity A和登录页面B都是没有在AndroidManifest.xml中声明的,启动一个未声明的Activity肯定会报一个ActivityNotFoundException。
再来回想一下Activity的启动过程:
调用startActivity方法启动一个目标Activity的时候,实际上会通过Instrumentation进行启动,再通过ActivityManagerService的本地代理对象调用ActivityManagerService的方法来启动一个Activity,这是一个IPC过程,在ActivityManagerService中会校验被启动的Activity的合法性,如果合法,会通过IPC过程调用ApplicationThread的方法,进而调用ActivityThread的handleLaunchActivity方法创建Activity,并执行Activity的生命周期方法。
看到没,Activity的启动过程是分两步的
- ActivityManagerService去校验被启动Activity合法性,并做好启动Activity的必要准备
- 在ActivityThread中真正的创建Activity,并完成Activity的启动阶段的生命周期回调。
既然没法办通过AMS去启动一个未注册的Activity,那么我们换一个思路来:
- 我们找一个在AndroidManifest.xml中声明一个代理页面ProxyActivity,当发起请求A页面的时候,我们hook ActivityManagerProxy的startActivity方法,把A页面的Intent替换为请求启动ProxyActivity页面的Intent,这样的话至少可以通过ActivityManagerService校验这一关。
- 当调用回我们ActivityThread的内部中的时候,做一下处理,把代理页面ProxyActivity对应的Intent替换成我们想要启动的Activity A对应的Intent,当然了,在这一过程还需要判断是否需要登录,如果需要登录的话,就需要替换成B页面。示例代码如下:
private void hookActivityManagerApi25() {
try {
// 反射获取ActivityManagerNative的静态成员变量gDefault, 注意,在8.0的时候这个已经更改了
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);
// 需要动态代理IActivityManager,把Singleton的成员变量mInstance的值设置为我们的这个动态代理对象
// 但是有一点,我们不可能完全重写一个IActivityManager的实现类
// 所以还是需要用到原始的IActivityManager对象,只是在调用某些方法的时候做一些手脚
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;
// 加入目标Activity没有在清单文件中注册,我们就欺骗ActivityManagerService,启动一个代理页面
// 真正启动页面,会开始回调ActivityThread的handleLaunchActivity方法
// 调用这个方法前可以做点文章,启动我们想要启动的页面
newIntent = new Intent();
ComponentName componentName = new ComponentName(context, ProxyActivity.class);
newIntent.setComponent(componentName);
//把原始的跳转信息当作参数携带给代理类
newIntent.putExtra(EXTRA_REAL_WANTED_INTENT, wantedIntent);
index = i;
}
}
args[index] = newIntent;
}
return method.invoke(originalObject, args);
}
}
那么在ActivityThread中怎么找Hook点呢?
首先要明确一点,我们找找个Hook点是要为了替换之前代理ProxyActivity的Intent的,有了找个思路,我们就可以有目的的去寻找了。
AMS启动一个Activity,会调用ApplicationThread的scheduleLaunchActivity方法,这个方法应该是在Activity启动过程中我们的App最先被AMS调用的了,在这个方法中第一个参数就是Intent,这个Intent就是我们发起请求启动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;
// 注意这个参数
r.intent = intent;
...
sendMessage(H.LAUNCH_ACTIVITY, r);
}
ApplicationThread继承自ApplicationThreadNative,而ApplicationThreadNative继承自Binder并实现了IApplicationThread接口。我们考虑一下,能否像Hook ActivityManagerProxy那样,采用一个动态代理的方式,创建IApplicationThread的代理类,当调用IApplicationThread的scheduleLaunchActivity方法的时候,我们更改这个方法的Intent参数,变为我们想要的那个Intent,然后就可以按照我们的需求来跳转了。
private class ApplicationThread extends ApplicationThreadNative
public abstract class ApplicationThreadNative extends Binder
implements IApplicationThread
想法是很好的,但是很遗憾,我们做不到,至于为什么,请接着往下看。
我们想要Hook ApplicationThread的scheduleLaunchActivity,那么我们先看一下这个ApplicationThread对象是什么时候创建的。ApplicationThread是ActivityThread的非静态内部类,在ActivityThread中,它的创建时机是在ActivityThread对象初始化的时候,
final ApplicationThread mAppThread = new ApplicationThread();
由于它没有采用多态的方式来创建ApplicationThread,我们创建的动态代理对象实际上是没有办法赋值给mAppThread这个变量的,也就是说实际上这个点我们是没有办法hook的。
那么我们接着这个方法来看,在scheduleLaunchActivity方法中,通过ActivityThread的一个H类型的成员mH来发送一个类型为 H.LAUNCH_ACTIVITY (int 型,值为100)的消息,这个H是ActivityThread的非静态内部类,实际上是继承自Handler的。
private class H extends Handler
至于为什么需要用Handler来切换,在Binder学习概要
已经介绍过,因为scheduleLaunchActivity是在Binder线程池中被调用的,需要用Hander来切换到主线程。H.LAUNCH_ACTIVITY类型的消息发送之后,H的handleMessage方法会被调用,在这里就会根据msg.what的来处理,对应LAUNCH_ACTIVITY类型的,会调用ActivityThread的handleLaunchActivity来创建Activity并完成Activity启动过程的生命周期回调。
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);
// 调用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;
应用的启动入口是ActivityThread的main方法,在这个方法中会创建ActivityThread对象,接着又会调用它的attach方法,在这个方法中,把ActivityThread对象赋值给其类的静态成员变量sCurrentActivityThread。静态成员变量就很好说了,通过反射就可以获取这个对象。
public static void main(String[] args) {
...
ActivityThread thread = new ActivityThread();
thread.attach(false);
...
}
private void attach(boolean system) {
sCurrentActivityThread = this;
...
}
虽然获取了这个ActivityThread对象,但是我们怎么准备一个代理对象来代理ActivityThread对象呢?
由于ActivityThread没有继承或实现任何类或接口,好像为它准备代理对象有点难度。
public final class ActivityThread
难道没有办法了吗?
当然不是,否则我还写这篇文章干嘛?
想一想Handler那块,从发送消息到处理消息,实际上中间是有一个消息分发过程的,也就是Handler的dispatchMessage方法会被调用,在这个方法中实际上Handler本身的handleMessage方法是最后才可能会被调用到的。msg.callback
这块我们没办法处理,因为消息创建是我们控制不了,而在else中,mCallback != null
这块,我们似乎可以给Hander设置一个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内部的一个接口。
public interface Callback {
public boolean handleMessage(Message msg);
}
那我们赶紧去找找怎么设置Callback,不过很遗憾的是设置Callback只有一种方式就是作为Handler构造方法参数传递,但我们的mH对象已经创建了。既然正常的路径没办法了,那只要采用反射的方式来设置成员变量了。
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);
//真正想要跳转的 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)) {
//如果需要登录并且没有登录,跳转登录页面
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了,并且可以根据是否需要登录来跳转到不同的页面。
继承AppCompatActivity会遇到的问题
如果你所有的Activity均继承的是Activity,上面的代码逻辑已经是没有问题了。但是,如果你的Activity类继承的是AppCompatActivity,是会报一个异常:
android.content.pm.PackageManager$NameNotFoundException
报这个异常的原因是在AppCompatActivity的onCreate方法中经过层层调用,会调用到NavUtils的getParentActivityName方法。在这个方法中会调用到PackageManager的getActivityInfo方法,返回的ActivityInfo对象是Activity在AndroidManifest.xml中注册信息对应的一个JavaBean对象,调用这个方法实际上会再检查一次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()实际上会调用ContextImpl的getPackageManager方法,而这个实际上返回的是ApplicationPackageManager对象,这个类是把IPackageManager进行了包装,实际上的功能还是由PackageManagerService调用。
# 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 调用getActivityInfo实际上调用的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());
}
这个过程就是经过一个IPC过程调用PackageManagerService的getActivityInfo方法。
为了不报NameNotFoundException异常,我们需要Hook这个IPackageManager,当调用PackageManager的getActivityInfo的时候,不让它进行IPC调用,而是直接返回一个不为null的ActivityInfo对象,这样就可以解决问题了。
Hook PMS
我们要去Hook PMS,还是遵循之前讲的3个步骤,那么就先来找这个Hook点,上面我们在贴出的代码中也看到了,ContextImpl的getPackageManager方法中首先会获取调用ActivityThread的静态方法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;
}
在这里有一个静态变量sPackageManager,如果不为空的话直接就返回了,这个静态变量的类型是接口类型,那么这个Hook点就很好,静态的很好获取对象,而接口类型更容易使用代理。
static volatile IPackageManager sPackageManager;
前面讲了那么多,怎么去Hook应该也知道 了,我们目前只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进行通信
虽然我们启动了Activity A或者B,但是AMS实际上还是以为我们启动的是ProxyActivity。不信的话,可以使用命令行查看。
adb shell dumpsys activity activities | grep mFocusedActivity
结果如下,可以看到,在AMS端记录的Activity实际上是ProxyActivity。
mFocusedActivity: ActivityRecord{4ff4194 u0 com.sososeen09.binder.hook/.ProxyActivity t5057}
那么通过这种Hook方式启动的Activity ,还具有完整的生命周期吗?
答案是肯定的。
我们知道Activity是不具有跨进程通讯的能力的,那么AMS是如何管理Activity,控制Activity的声明周期的呢?答案就是一个通过一个Ibinder类型的token变量来控制。ASM通过这个token来与ApplicationThread进行通讯,进行控制Activity的声明周期。在AMS那边,它以为token表示的是ProxyActivity,但是在客户端这边,token实际上指的是Activity A或者B。
这个token是在AMS在回到IApplicationThread的scheduleLaunchActivity方法中传递过来的第二个参数。
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和对应的ActivityClientRecord会以键值对的形式存储在ActivityThread的变量mActivities中。后面再对Activity进行生命周期方法调用的时候,均可以通过AMS端传过来的token来获取正确的Activity。
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
总结
前面讲了一大堆,我们现在来总结概括一下这个过程和原理。
再来看一下需求:
我们想启动一个Activity A页面,但是想要进入这个A页面必须是已经登录过的,如果没有登录的话就启动登录页面B,并且在B页面登录成功之后需要跳转到页面A
Activity页面A、B均没有在清单文件中注册,但是要完成正常的跳转
为什么我们可以跳转到一个未在AndroidManifest.xml中声明的Activity中,而且可以根据不同的逻辑跳转到不同的页面呢?
调用startActivity方法启动一个目标Activity的时候,实际上会通过Instrumentation进行启动,再通过ActivityManagerService的本地代理对象调用ActivityManagerService的方法来启动一个Activity,这是一个IPC过程,在ActivityManagerService中会校验被启动的Activity的合法性,如果合法,会通过IPC过程调用ApplicationThread的方法,ApplicationThread是一个Binder对象,它的方法运行是在Binder线程池中的,所以需要采用一个Handler把方法调用切换到主线程,ApplicationThread通过发送消息,进而调用ActivityThread的handleLaunchActivity方法创建Activity,并执行Activity的生命周期方法。
传递到ActivityManagerService的被启动的Activity信息必须是声明过的,而如果我们想要启动一个没有在AndroidManifest.xml中声明的Activity,可以通过欺上瞒下的方法,hook ActivityManagerService在本地的代理对象,如果调用的是ActivityManagerProxy的startActivity方法,那么就更改这个Intent,替换成启动一个声明过的ProxyActivity,当ActivityManagerService校验完启动的合法性之后,会通过ApplicationThread调用到ActivityThread的一个叫做mH的Handler中来。当Handler收到消息的时候,会有一个消息分发的过程,如果给Handler设置了一个Callback,这个Callback的handleMessage方法就会先于Handler本身的handleMessage方法调用。所以可以想办法给这个叫做mH的Handler对象设置Callback,并且在Callback的handleMessage方法中从Message上面拿到相关的Intent信息,此时的Intent还是跳转到代理页面,可以根据当前是否登录,是否需要重定向到登录页面等对这个Intent进行相应的处理,比如设置为跳转到登录页或者真正想要跳转的页面,并由后续的mH的handleMessage来调用。
更多细节,请查看项目 apk-plugin-technology