从框架层分析如何启动未注册的 Activity

2,401 阅读7分钟
本文关键词:Binder、AMS、ActivityThread、Handler、Java 反射

引言

要解决这个问题首先要明白为什么 Activity 需要在 AndroidManifest.xml 中注册。本文也会带大家大致分析一下 Activity 的启动过程,然后从系统框架的层面找出切入口来完成这件『邪恶』的事情。

Activity 启动过程分析

首先我们尝试启动一个未注册的 Activity,然后通过异常来跟踪方法的调用栈:

从下到上直到第三行实际上都是支持库对 startActivity 的封装,我们可以直接跳过,实际上最重要的一步就是 execStartActivity,这一步是在 Instrumentation 这个类执行的,对于这个类本文不多展开,它主要是为单元测试提供 Mock 的,大家了解一下就好。

我们直接跳入这个方法:

public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {
        IApplicationThread whoThread = (IApplicationThread) contextThread;
        Uri referrer = target != null ? target.onProvideReferrer() : null;
        if (referrer != null) {
            intent.putExtra(Intent.EXTRA_REFERRER, referrer);
        }
        if (mActivityMonitors != null) {
            synchronized (mSync) {
                final int N = mActivityMonitors.size();
                for (int i=0; i<N; i++) {
                    final ActivityMonitor am = mActivityMonitors.get(i);
                    if (am.match(who, null, intent)) {
                        am.mHits++;
                        if (am.isBlocking()) {
                            return requestCode >= 0 ? am.getResult() : null;
                        }
                        break;
                    }
                }
            }
        }
        try {
            intent.migrateExtraStreamToClipData();
            intent.prepareToLeaveProcess(who);
            
            // 在这里开始真正发起启动请求。
            int result = ActivityManagerNative.getDefault()
                .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;
    }

通过 ActivityManagerNative 这个类可以拿到 ActivityManagerService( 一下简称 AMS)的 AIDL 接口,调用它的 startActivity 方法来通知系统一个 Activity 即将被启动。

然而 AMS 在收到请求后肯定会根据应用的 Package 来检查相应的 Activity 是否存在,如果 Manifest 里并没有这个 Activity,那么就会直接返回一个错误代码,然后 checkStartActivityResult 方法就会针对相应的错误代码抛出异常。

所以到这里你应该明白了为什么所有 Activity 需要被注册了吧,因为系统需要知道你的 app 能提供哪些 Activity,毕竟 Android 不像 iOS 那样应用只能打开自己的 ViewController,Android 允许应用之间相互调起 Activity,系统如果不知道这个 app 能不能打开是会很麻烦的。

--- 分割一下 ---

如果一切检查都通过了,AMS 就会开始通知应用:『喂,你,醒醒,给我开个 Activity!』

这很形象,因为 AMS 首先会通过 Binder(ActivityThread 中的 ApplicationThread 暴露给系统的)来以 IPC 的方式告诉 app,你要启动一个 Activity 了。对于 Binder 机制本文也不多解释了,简单说下流程:native 层调用 Binder 类的 execTransact 并传入序列化的数据包,接下来 onTransact 方法被调用,这是 Binder 实现 RPC 解析的地方,AIDL 帮我们实现了很多东西,但是 Android 系统框架有自己的实现方法。

AMS 发出的调用编号是 SCHEDULE_LAUNCH_ACTIVITY_TRANSACTIONonTransact 中会详细处理这个调用:

case SCHEDULE_LAUNCH_ACTIVITY_TRANSACTION:
        {
            data.enforceInterface(IApplicationThread.descriptor);
            Intent intent = Intent.CREATOR.createFromParcel(data);
            IBinder b = data.readStrongBinder();
            int ident = data.readInt();
            ActivityInfo info = ActivityInfo.CREATOR.createFromParcel(data);
            Configuration curConfig = Configuration.CREATOR.createFromParcel(data);
            Configuration overrideConfig = null;
            if (data.readInt() != 0) {
                overrideConfig = Configuration.CREATOR.createFromParcel(data);
            }
            CompatibilityInfo compatInfo = CompatibilityInfo.CREATOR.createFromParcel(data);
            String referrer = data.readString();
            IVoiceInteractor voiceInteractor = IVoiceInteractor.Stub.asInterface(
                    data.readStrongBinder());
            int procState = data.readInt();
            Bundle state = data.readBundle();
            PersistableBundle persistentState = data.readPersistableBundle();
            List<ResultInfo> ri = data.createTypedArrayList(ResultInfo.CREATOR);
            List<ReferrerIntent> pi = data.createTypedArrayList(ReferrerIntent.CREATOR);
            boolean notResumed = data.readInt() != 0;
            boolean isForward = data.readInt() != 0;
            ProfilerInfo profilerInfo = data.readInt() != 0
                    ? ProfilerInfo.CREATOR.createFromParcel(data) : null;
            scheduleLaunchActivity(intent, b, ident, info, curConfig, overrideConfig, compatInfo,
                    referrer, voiceInteractor, procState, state, persistentState, ri, pi,
                    notResumed, isForward, profilerInfo);
            return true;
        }

上面基本就是从 Parcel 中读取出来所需的信息,包括最重要的 Intent,和一些系统提供的 Binder 来进行状态反馈。倒数几行中执行了 scheduleLaunchActivity 这个方法,它的实现存在于 ActivityThreadApplicationThread 内部类,它是 ApplicationThreadNative 的子类,ApplicationThreadNative 通过 onTransact 读出来请求后调用 IApplicationThread 中约定这个方法。让后我们来看它的实现:

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;
            r.referrer = referrer;
            r.voiceInteractor = voiceInteractor;
            r.activityInfo = info;
            r.compatInfo = compatInfo;
            r.state = state;
            r.persistentState = persistentState;

            r.pendingResults = pendingResults;
            r.pendingIntents = pendingNewIntents;

            r.startsNotResumed = notResumed;
            r.isForward = isForward;

            r.profilerInfo = profilerInfo;

            r.overrideConfig = overrideConfig;
            updatePendingConfiguration(curConfig);

            sendMessage(H.LAUNCH_ACTIVITY, r);
        }

这里应该很简单了,它把所有有用的东西打了个包(ActivityClientRecord),然后通过 Handler 把这个请求再派发到主线程,因为 Binder 接收请求的线程并不是主线程,所以需要 Handler 去做这样的一个 transition。

后面的事情我就不领大家一步一步分析了,看一下源码就明白了。但是我们要关注一下 Activity 类的实例化,它是在 Handler 处理消息时做完的:

// handleLaunchActivity -> performLaunchActivity -> Instrumentation#newActivity


public Activity newActivity(ClassLoader cl, String className,
            Intent intent)
            throws InstantiationException, IllegalAccessException,
            ClassNotFoundException {
        return (Activity)cl.loadClass(className).newInstance();
    }
而且经过分析,到这里框架也不会检查 AndroidManifest 了。

Recap 一下整个启动过程:app 调用 startActivity 方法 -> Instrumentation 类通过 ActivityManagerNative 将启动请求发送给 AMS -> AMS 进行一系列检查并将此请求通过 Binder 派发给所属 app -> app 通过 Binder 收到这个启动请求 -> ActivityThread 中的实现将收到的请求进行封装后送入 Handler -> 从 Handler 中取出这个消息,开始 app 本地的 Activity 初始化和启动逻辑。

绕了一大圈经过了系统又回到 app 了,但是不得不称赞这个设计很厉害,其实 Android 可以设计成在 startActivity 时检查一下是不是本地的 Activity 类,如果是的话直接走本地启动逻辑就好了,能提升一下速度。

本文目标的实现思路

经过这样的一个分析,我们应该有了一些思路,说一下我的:

我们可以把 Activity 的启动过程分为两个阶段:

1. 将启动请求告知系统

2. 系统告知应用去启动 Activity

第一个阶段我们肯定不能动了,只要 Intent 有问题立马就挂。但是第二个阶段通过反射我们可以随便改,反正都是应用层的东西。

那么我们可以用一个合法的 Intent 来包裹一个未注册的 Activity 启动请求,先把第一个阶段给过了,然后在第二个阶段的某个时机将系统传下来的 Intent 给掉包,来个『狸猫换太子』,然后我们自己的 app 一看,哦,这个 Activity 能实例化,就正常启动了。

掉包的时机很重要,因为 Java 不像 Objective-C,想怎么 method swizzle 都可以,Java 修改方法实现比较困难,我们可不可以从别处下手呢?

实际上 Handler 的设计给我们提供了机会,不知大家是否记得 Handler 的构造方法中有一个重载,它接受一个 Callback 类型的参数,然后在 dispatchMessage 的时候:

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

如果有 callback,那就先执行一下它,再 handleMessage。而 ActivityThread 的 Handler 实现并没有用到这个 callback,现在我们可以把它利用起来了:强行注入一个 Callback,检查一下是否为 Activity 启动的消息,如果是再检查它是不是有一个需要掉包的 Intent,如果是,就进行掉包,然后继续后面正常的逻辑,大功告成!

好了,思路清晰了以后我们快速把它实现了吧!

示例实现

import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;

import java.lang.reflect.Field;

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private static boolean hooked = false;

    private static final class PatcherCallback implements Handler.Callback {
        @Override
        public boolean handleMessage(Message msg) {
            if (msg.what == 100) { // LAUNCH_ACTIVITY
                try {
                    // Replace the proxy intent with the real one if needed.
                    Object record = msg.obj;
                    Field field_intent = record.getClass().getDeclaredField("intent");
                    field_intent.setAccessible(true);
                    Intent intent = (Intent) field_intent.get(record);
                    if (intent.hasExtra("TARGET_INTENT")) {
                        // Yep, we should do the replacement here!
                        Intent realIntent = intent.getParcelableExtra("TARGET_INTENT");
                        field_intent.set(record, realIntent);
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
            return false;
        }
    }

    public MainActivity() {
        super();

        if (!hooked) {
            synchronized (MainActivity.class) {
                if (hooked) {
                    return;
                }

                hooked = true;

                try {
                    Class<?> clazz_ActivityThread = Class.forName("android.app.ActivityThread");
                    Field field_sCurrentActivityThread = clazz_ActivityThread.getDeclaredField("sCurrentActivityThread");
                    field_sCurrentActivityThread.setAccessible(true);
                    Object sCurrentActivityThread = field_sCurrentActivityThread.get(null);

                    Field field_mH = clazz_ActivityThread.getDeclaredField("mH");
                    field_mH.setAccessible(true);
                    Handler oriHandler = (Handler) field_mH.get(sCurrentActivityThread);
                    Field field_mCallback = Handler.class.getDeclaredField("mCallback");
                    field_mCallback.setAccessible(true);
                    field_mCallback.set(oriHandler, new PatcherCallback());
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    @Override
    public void onClick(View v) {
        if (v.getId() == R.id.button) {
            Intent realIntent = new Intent(this, UnregisteredActivity.class);
            Intent intent = new Intent(this, MainActivity.class);
            
            // Any intent that has this extra is a proxy intent, need replacing.
            intent.putExtra("TARGET_INTENT", realIntent);
            startActivity(intent);
        }
    }
}

上面的实现都是基于框架源码分析得出的,建议大家也不要只 copy-paste,可以自己尝试实现一下,应该还是比较简单的,当然了,前提是要对 Java 的反射机制很熟悉。

Happy Hacking!