0%

(05)2021.12.19-如何启动一个没注册的Activity---leo老师

一、前情回顾

init 操作主要是解析 init.rc 文件,它是用户空间的鼻祖。

zygote 是 Java 进程的鼻祖,系统启动的过程在 zygote 之前都是运行在 c/c++ ,初始化运行环境 Android RunTime ,zygote 开始才进入 Java。

zygote 通过 fork 创建 SystemServer 进程,这里面有各种 Android 的服务,比如 AMS 等

Activity 启动通信过程中需要注意一点:Launcher 进程获取通信代理对象(下图第1步)在 10.0 之前 是 AMS ,在 10.0 之后获取到的是 ATMS ,老师说在面试的时候要说下,整个流程如下图所示:

Activity启动通信图

1.1 Activity 的管理

在哪里对 Activity 的栈进行管理,Activity 信息的管理?

答案是在 ActivityStarter 这个类当中(com.android.server.wm 这个包名下的)。在 AMS里面会执行 ActivityStarter.execute() 方法,在 execute 方法里面,会调用 ActivityStarter 的内部类 Request 的 resolveActivity ,在这里面首先获取到 ResolveInfo 数据,之后根据 ResolveInfo 数据获取到 activityInfo 数据**,至此 Activity 信息就获取到了:

1
activityInfo = supervisor.resolveActivity(intent, resolveInfo, startFlags, profilerInfo);

App 层面的一个 Activity 对应在 AMS 中的 一个 ActivityRecord 对象。 还有个细节,在 ActivityStarter 类里面,会通过 computeLaunchingTaskFlags 方法计算 Activity 的启动模式。还有个关键点,ActivityStarter 中会通过 recycleTask 方法清除要启动的 Activity 上面的那些 Activity (比如 SingleTask 模式下)。

1.2 任务栈

如果你指定了 taskAffinity ,想让某个 Activity 运行在指定的任务栈,但是如果没有在 Intent 里面指定 FLAG_ACTIVITY_NEW_TASK 这个 flag 的话,还是会在默认的栈里面(与启动这个 Activity 的 Activity 是同一个栈),不会在指定的栈里面。其他的诸如两个 App 之间通过隐式启动另一个 App 中的 Activity 时,最好也使用 FLAG_ACTIVITY_NEW_TASK,不然还是在当前 App 的任务栈中。

小知识,如果项目里面有多个任务栈,那么在按任务键 的时候,就能看到有多个缩略图(自己可以写demo试试),另外如果需要对比添加 FLAG_ACTIVITY_NEW_TASK 和不添加 FLAG_ACTIVITY_NEW_TASK 的情形的效果,需要卸载App 重新安装,课程里面就出现了这样的小插曲,不生效。

单独任务栈有什么作用?就比如你的图库,需要在单独的任务栈中,这样与普通业务不影响,我个人理解的是在诸如 singleTask 等模式下,假如需要清理掉栈顶那些 Activity 时,不会被清除掉。

任务栈在数据层面表现就是 ActivityStack ,它管理 ActivityRecord 。

1.3 FLAG_ACTIVITY_FORWARD_RESULT

FLAG_ACTIVITY_FORWARD_RESULT 的作用是当前 Activity 忽略 ActivityResult ,这样就会将结果传给上一级。举个例子,Activity A启动 B ,B 启动 C ,我们想在 C 结束的时候,将结果跳过 B 传递给 A ,此时我们只需要在 B 启动 C 的时候,在intent 中添加这个flag 就行(表示忽略那个结果):

1
2
3
Intent intent = new Intent(BBActivity.this, CCActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(intent);

注意最后一行的 startActivity ,这里不能用 startActivityForResult 的,FLAG_ACTIVITY_FORWARD_RESULT 本来是忽略结果,startActivityForResult 是要结果,如果同时使用会报错。自己写的测试代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//Activity A
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_a);

mButton = (Button) findViewById(R.id.btn);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(AAActivity.this, BBActivity.class);
startActivityForResult(intent, 100);
}
});
}

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
Log.e("forwardresult","AAActivity requestCode:" + requestCode + ", resultCode:" + resultCode + "data = " + data.getStringExtra("haha"));
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_b);

mButton = (Button) findViewById(R.id.btn);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(BBActivity.this, CCActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_FORWARD_RESULT);
startActivity(intent);
}
});
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_c);

mButton = findViewById(R.id.btn);
mButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
intent.putExtra("haha", "我是c的数据");
setResult(RESULT_OK, intent);

finish();
}
});
}

这样,当 Activity B 结束的时候,A 就能收到 C 关闭时候的数据 “我是c的数据” 了。注意:C 结束后,就显示出 B 了,只有手动关闭 B 页面后,数据才会传给 A 。

关于这个标志位,更多内容可以参考 FLAG_ACTIVITY_FORWARD_RESULT的使用 - 简书 (jianshu.com)

二、启动未注册的 Activity

启动未注册的 Activity 会报错,那么这个报错是在哪个地方报出来的?我们看到 Instrumentation 这个类,看到其中的 execStartActivity 这个方法,在里面会有启动 Activity 并对启动结果进行检查的逻辑:

1
2
3
4
5
int result = ActivityTaskManager.getService().startActivity(whoThread,
who.getBasePackageName(), who.getAttributionTag(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()), token,
target != null ? target.mEmbeddedID : null, requestCode, 0, null, options);
checkStartActivityResult(result, intent);

在 ActivityStarter 类中,执行 executeRequest 方法时,如果有错误,就会将错误结果体现到上述的 result 里面。最后,在 checkStartActivityResult 中通过 switch-case 去处理各种异常和正常结果。

Hook 目的就是为了改变原有流程。不管你是通过反射还是插桩都行。

2.1 思路

实现未注册的 Activity 的启动的思路如下图所示:

实现未注册的Activity 的启动思路

找Hook点的原则(尽量):

  • 找静态变量或者单例,这种可以直接用反射,还不轻易改变

  • public 修饰的,也不容易改变

一个原则: 在 startActivity 之后,AMS 检测之前将 目标Activity 替换成占位 Activity。在 AMS 检测之后,Activity 创建出来之前(感觉老师说的Activity生命周期之前是有问题的),将占位 Activity 替换回 目标 Activity。

2.2 实践

通过 AMS 启动 Activity 的时候,getService 获取 AMS 的代理的时候,是静态的的,并且 IActivityManager 是一个接口,所以可以使用动态代理。

前面说的 10 之前是 AMS ,10之后是 ATMS ,所以要注意适配。

还有个点需要注意,使用动态代理的时候,需要传入 ClassLoader

,可以使用当前线程的 ClassLoader :

1
Thread.currentThread().getContextClassLoader()

ActivityThread 中 H 类handler 本身的 callback 是 null ,又 Handler 中 dispatchMessage 的时候,msg 的callback 存在的话,就执行 msg 的callback ,其他的就不执行了;如果 msg.callback 是空的话,则先执行 Handler 本身的 callback.handlerMessage (msg) ,只有当这个返回true 的时候,才不会执行我们自定义的 handleMessage;false 的话,还是会继续执行 handleMessage 的,所以我们可以强行new 出来一个 Handler 的 callback ,只不过 callbac.handleMessage 返回false 即可。之后将这个 callback 通过 Hook 设置给 H 类即可。

这样 Hook 是没有风险的,因为流程并没有改变,,Handler 的callback 本身就是空的,我们 Hook 之后,handleMessage 还是会执行

谢谢你的鼓励