以前一直很好奇, 启动一个新的 Activity, 为什么非要在清单文件里注册, 到底是哪里地方进行了校验, 整个启动的流程是什么样子的. 如果想实现插件化机制, 启动一个插件中新的 Activity 的话有什么其它方法去做到. 这篇文章本来是想写在 Acytivity 的启动流程分析之后的, 但是里面确实涉及的类, 逻辑很多, 写起来可能会有些漏缺, 而且比较无聊, 所以先写一下 Android 的 hook 技术, 先大概讲一下 Activity 的启动流程, 里面会涉及到一些进程交互, 如果对 Android 中的 Binder 机制不熟悉的朋友可以看我上篇文章 3 分钟带你看懂 Android 的 Binder 机制
2.Activity 大致启动流程
启动一个 Activity 大致会经历一下几个方法:
- Activity.startActivity()
- Activity.startActivityForResult()
- Instrumentation.execStartActivity()
- ActivityManagerService.startActivity()
- ApplicationThread.scheduleLaunchActivity()
- ActivityThread.Handler.handleMessage()
具体方法本文就不详细说了, 免得篇幅太长, 引用一张图来表述整个的交互过程:
从上图我们可以看出整个通信过程是涉及到 2 次 Binder 通信过程的, App 进程和 system_server 进程分别作为了一次 client 和 server 端. App 进程也就是我们自己的应用进程, system_server 进程是系统进程, javaframework 框架的核心载体, 里面运行了大量的系统服务, 比如这里提供 ApplicationThreadProxy),ActivityManagerService, 结合图, 启动流程大致如下:
App 进程入口是 ActivityThread, 初始化时便会创建一个 Binder 线程(具体是指 ApplicationThread,Binder 的服务端, 用于接收系统服务 AMS 发送来的事件),ApplicationThread 主要有两个作用: 1. 作为 Binder 服务端接受 system_server 中的 client 端 ApplicationThreadProxy 发送过来的消息, 对应上面的方法是 ActivityManagerService------>ApplicationThread,2. 作为消息的中转站, 将 msg 通过 handler 传递到 ActivityThread 中, 也就是传递到上述的最后一个方法中.
system_server 进程中的 ActivityManagerService 作为 Binder 的服务端, 接受 ActivityManagerProxy 作为 client 端发送过来的消息, 也就是 Instrumentation.execStartActivity()---->ActivityManagerService.startActivity().
这里主要看下 Instrumentation.execStartActivity 这个方法, 比较关键, 跳过去可能有的朋友比较模糊, 主要代码如下:
- public ActivityResult execStartActivity(
- Context who, IBinder contextThread, IBinder token, Activity target,
- Intent intent, int requestCode, Bundle options) {
- IApplicationThread whoThread = (IApplicationThread) contextThread;
- ....
- try {
- intent.migrateExtraStreamToClipData();
- intent.prepareToLeaveProcess(who);
- 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);
- } catch (RemoteException e) {
- throw new RuntimeException("Failure from system", e);
- }
- return null;
- }
这里的 contextThread 也就是上面讲的 ApplicationThread 对象, 主要看下面 ActivityManager.getService(), 返回的是一个 IActivityManager 接口类型对象, 继续看:
- 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);
- }
这里我用的 API25,API26 及以上, 实现的代码不太一样, 废弃了 ActivityManagerProxy, 改用了 AIDL 来实现通信, 为了让大家伙更理解 Binder, 这里就用之前的 API 了, 逻辑应该很清晰通过 ServiceManager 拿到 IBinder 对象, 再在本地进行查找, 如果不在同一个进程, 就返回 ActivityManagerProxy 代理对象, 所以很清晰, Instrumentation.execStartActivity()实际上最后就调用到了 ActivityManagerProxy 中.
3.Hook 实现
3.1 hook 实现思路
咳咳!! 我们回到正题, 上面只是铺垫, 我们的主题是 hook, 怎么启动一个没注册的 Activity 呢, 先将下思路, 既然最终检查是在 AMS 中, 那我们可以在之前做一些骚操作, 来个狸猫换太子, 具体思路如下:
直接 startActivity()开启一个未注册的 TargetActivity
既然在 AMS 之前, 消息是从 ActivityManagerProxy 中发出去的, 我们可以动态代理生成一个类 (不熟悉动态代理的朋友只能自行 google 了~), 代理 ActivityManagerProxy 对象, 拦截其中的 startActivity() 方法, 拿到其中的 intent 参数对象. 我们把真正的 intent 替换成我们一个已经注册过的 ProxyActivity, 先把 AMS 的 check 这一关给过了, 再把真正的 intent 当做对象存在 ProxyActivity 的 intent 中.
既然 intent 替换了, AMS 是过了, 那肯定得给它搞回来, 要不然不就直接打开了 ProxyActivity? 我们知道最好消息时回到了 ActivityThread.Handler.handleMessage()中的, 嘿嘿, 熟悉 Handler 机制的朋友应该知道, 在这之前是会先走 dispatchMessage 方法的, 不熟悉的可以看下我之前的文章 Android 源码学习之 handler,
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
是分别会先后执行 handleCallback(msg)--->mCallback.handleMessage(msg)--->handleMessage(msg), 而 Activity 中正好是最好一个, 那我们可以 hook 一下这个 mCallback, 让我们在最后消息执行时, 把我们的 intent 给替换回去
3.2 上代码!!
- public class HookActivityUtils {
- private static final String TAG = "HookActivityUtils";
- private volatile static HookActivityUtils sHookActivityUtils;
- public static HookActivityUtils getInstance(){
- if (sHookActivityUtils==null){
- synchronized (HookActivityUtils.class){
- if (sHookActivityUtils==null){
- sHookActivityUtils = new HookActivityUtils();
- }
- }
- }
- return sHookActivityUtils;
- }
- private HookActivityUtils(){
- }
- public void hooks(Context mContext){
- Object object;
- try {
- // 寻找 hook 点, 最好是静态或者单例, 不容易发生改变, 因为是静态, 所以传入 null 即可
- // 因为版本差异, 所以要分开处理
- if (Build.VERSION.SDK_INT>=26){
- Field iActivityManagerSingleton = ActivityManager.class.getDeclaredField("IActivityManagerSingleton");
- iActivityManagerSingleton.setAccessible(true);
- object = iActivityManagerSingleton.get(null);
- }else{
- Field gDefault = Class.forName("android.app.ActivityManagerNative").getDeclaredField("gDefault");
- gDefault.setAccessible(true);
- object = gDefault.get(null);
- }
- // 获取单例对象, 实现 IActivityManager 接口的实现类
- Field mFieldInstance = Class.forName("android.util.Singleton").getDeclaredField("mInstance");
- mFieldInstance.setAccessible(true);
- Object mInstance = mFieldInstance.get(object);
- // 寻找到 hook 点后, 新建一个代理对象
- ActivityManagerDelegate managerDelegate = new ActivityManagerDelegate(mInstance,mContext);
- Class<?> aClass = Class.forName("android.app.IActivityManager");
- Object proxy = Proxy.newProxyInstance(aClass.getClassLoader(), new Class<?>[]{aClass}, managerDelegate);
- // 替换动态代理对象
- mFieldInstance.set(object,proxy);
- } catch (Exception mE) {
- mE.printStackTrace();
- }
- }
- public void hookHanlder(){
- try {
- Class<?> aClass = Class.forName("android.app.ActivityThread");
- Method currentActivityThread = aClass.getDeclaredMethod("currentActivityThread");
- currentActivityThread.setAccessible(true);
- //ActivityThread 本身对象
- Object invoke = currentActivityThread.invoke(null);
- Field mH = aClass.getDeclaredField("mH");
- mH.setAccessible(true);
- // 获取 handler 对象
- Object handler = mH.get(invoke);
- // 获取 handler 中的 mCallback
- Field mCallback = Handler.class.getDeclaredField("mCallback");
- mCallback.setAccessible(true);
- mCallback.set(handler,new HookCallBack((Handler) handler));
- } catch (Exception mE) {
- mE.printStackTrace();
- }
- }
- }
主要也就是对应的两个方法, 一个通过反射拿到实现 IActivityManager 接口的对象, 并生成一个代理此对象的代理对象, 另外一个是反射拿到 ActivityThread 中的 mH Handler 对象, 然后传入一个实现 Handler.callback 接口的对象, 这样 Handler 中的 mcallback 就不为空了, 也就达到了我们的目的
然后是我们的代理对象:
- public class ActivityManagerDelegate implements InvocationHandler {
- private static final String TAG = "ActivityManagerDelegate";
- private Object mObject;
- private Context mContext;
- public ActivityManagerDelegate(Object mObject,Context mContext) {
- this.mObject = mObject;
- this.mContext = mContext;
- }
- @Override
- public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
- if (method.getName().equals("startActivity")){
- // 拦截方法
- Log.e(TAG,"i got you");
- Intent intent =null;
- for (int i = 0; i <args.length; i++) {
- if (args[i] instanceof Intent){
- intent = (Intent) args[i];
- // 找到了 intent 参数
- Intent mIntent = new Intent();
- ComponentName componentName = new ComponentName(mContext,ProxyActivity.class);
- // 将真正的 intent 带上, 后续替换
- mIntent.setComponent(componentName);
- mIntent.putExtra("realObj",intent);
- // 修改为已注册 Activity 的 intent, 先让 AMS 检查通过
- args[i] = mIntent;
- }
- }
- }
- return method.invoke(mObject,args);
- }
- }
我们拦截 startActivity, 然后将 ProxyActivity 的 ComponentName 传递进去, 狸猫换太子, 同时将真正的 intent 带过去, 接下来就是处理消息了:
- public class HookCallBack implements Handler.Callback {
- private static final String TAG = "HookCallBack";
- private Handler mHandler;
- public HookCallBack(Handler mHandler) {
- this.mHandler = mHandler;
- }
- @Override
- public boolean handleMessage(Message msg) {
- if (msg.what==100){
- handleHookMsg(msg);
- }
- mHandler.handleMessage(msg);
- return false;
- }
- private void handleHookMsg(Message mMsg) {
- Object obj = mMsg.obj;
- try {
- Field intent = obj.getClass().getDeclaredField("intent");
- // 这时候拿出之前存进来真正的 intent
- intent.setAccessible(true);
- Intent proxyIntent = (Intent) intent.get(obj);
- Intent realIntent = proxyIntent.getParcelableExtra("realObj");
- proxyIntent.setComponent(realIntent.getComponent());
- } catch (Exception mE) {
- mE.printStackTrace();
- }
- }
- }
什么? 为什么要拦截 msg.what 等于 100 的消息?
- private class H extends Handler {
- ....
- public static final int LAUNCH_ACTIVITY = 100;
- public static final int PAUSE_ACTIVITY = 101;
- ....
- }
这下明白了吧, 拦截到这个消息后, 把事先存进去的 intent 的 Component 再 set 回去就完美了~~
主页面 MainActivity:
- public class MainActivity extends AppCompatActivity {
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
- HookActivityUtils.getInstance().hooks(this);
- HookActivityUtils.getInstance().hookHanlder();
- findViewById(R.id.textView).setOnClickListener(new View.OnClickListener() {
- @Override
- public void onClick(View v) {
- Intent intent = new Intent(MainActivity.this,TargetActivity.class);
- startActivity(intent);
- }
- });
- }
- }
这里我们打开的是 TargetActivity, 但是清单文件中并没有声明:
- <application
- 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=".ProxyActivity"></activity>
- </application>
这样的话, 就实现打开一个未注册的 Activity 了, 是不是也是挺 easy 的, 在实现插件化机制的时候, 要打开插件中的 activity 的话, 因为没有在原宿主中的清单文件注册, 是无法直接调转的, 这时候我们这个代理 activity 就可以起很大的作用了.
有兴趣的朋友可以跟着一起实现一下, 感谢观看, 溜了溜了~~
来源: https://juejin.im/post/5c90a76a5188252d6d2fa6f1