Android Hook-Activity的启动流程

两种启动Activity的方式源码追踪

源码基于 SDK 28 ~ Android 9.0

方式1:使用Activity自带的startActivity

代码示例

private void startActivityByActivity() {
    Intent i = new Intent(MainActivity.this, Main2Activity.class);
    startActivity(i);
}

程序执行走向图

代码追踪:Activity.java

@Override
public void startActivity(Intent intent) {
    this.startActivity(intent, null);
}
@Override
public void startActivity(Intent intent, @Nullable Bundle options) {
    if (options != null) {
        startActivityForResult(intent, -1, options);
    } else {
        // Note we want to go through this call for compatibility with
        // applications that may have overridden the method.
        startActivityForResult(intent, -1);
    }
}
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
        @Nullable Bundle options) {
    if (mParent == null) {
        options = transferSpringboardActivityOptions(options);
        Instrumentation.ActivityResult ar =
            mInstrumentation.execStartActivity(
                this, mMainThread.getApplicationThread(), mToken, this,
                intent, requestCode, options);
        if (ar != null) {
            mMainThread.sendActivityResult(
                mToken, mEmbeddedID, requestCode, ar.getResultCode(),
                ar.getResultData());
        }
        if (requestCode >= 0) {
            // If this start is requesting a result, we can avoid making
            // the activity visible until the result is received.  Setting
            // this code during onCreate(Bundle savedInstanceState) or onResume() will keep the
            // activity hidden during this time, to avoid flickering.
            // This can only be done when a result is requested because
            // that guarantees we will get information back when the
            // activity is finished, no matter what happens to it.
            mStartedActivity = true;
        }

        cancelInputsAndStartExitTransition(options);
        // TODO Consider clearing/flushing other event sources and events for child windows.
    } else {
        if (options != null) {
            mParent.startActivityFromChild(this, intent, requestCode, options);
        } else {
            // Note we want to go through this method for compatibility with
            // existing applications that may have overridden it.
            mParent.startActivityFromChild(this, intent, requestCode);
        }
    }
}

这里有个if (mParent == null)判定,先看true分支,发现一个坑,mInstrumentation.execStartActivity这里居然不能继续往下索引?很奇怪,不过不重要,我们直接进入Instrumentation.java去找这个方法:

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);
                ActivityResult result = null;
                if (am.ignoreMatchingSpecificIntents()) {
                    result = am.onStartActivity(intent);
                }
                if (result != null) {
                    am.mHits++;
                    return result;
                } else 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 = 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;
}

在这个execStartActivity中,可以找到关键代码:

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);

通过这种方式启动Activity,最终的执行权被交给了ActivityManager.getService()(即AMS),它的作用是启动一个Activity并且返回result,然后checkStartActivityResult(result, intent);这句话,对当前的跳转意图intent进行检测;

/** @hide */
public static void checkStartActivityResult(int res, Object intent) {
    if (!ActivityManager.isStartResultFatalError(res)) {
        return;
    }

    switch (res) {
        case ActivityManager.START_INTENT_NOT_RESOLVED:
        case ActivityManager.START_CLASS_NOT_FOUND:
            if (intent instanceof Intent && ((Intent)intent).getComponent() != null)
                throw new ActivityNotFoundException(
                        "Unable to find explicit activity class "
                        + ((Intent)intent).getComponent().toShortString()
                        + "; have you declared this activity in your AndroidManifest.xml?");
            throw new ActivityNotFoundException(
                    "No Activity found to handle " + intent);
        case ActivityManager.START_PERMISSION_DENIED:
            throw new SecurityException("Not allowed to start activity "
                    + intent);
        case ActivityManager.START_FORWARD_AND_REQUEST_CONFLICT:
            throw new AndroidRuntimeException(
                    "FORWARD_RESULT_FLAG used while also requesting a result");
        case ActivityManager.START_NOT_ACTIVITY:
            throw new IllegalArgumentException(
                    "PendingIntent is not an activity");
        case ActivityManager.START_NOT_VOICE_COMPATIBLE:
            throw new SecurityException(
                    "Starting under voice control not allowed for: " + intent);
        case ActivityManager.START_VOICE_NOT_ACTIVE_SESSION:
            throw new IllegalStateException(
                    "Session calling startVoiceActivity does not match active session");
        case ActivityManager.START_VOICE_HIDDEN_SESSION:
            throw new IllegalStateException(
                    "Cannot start voice activity on a hidden session");
        case ActivityManager.START_ASSISTANT_NOT_ACTIVE_SESSION:
            throw new IllegalStateException(
                    "Session calling startAssistantActivity does not match active session");
        case ActivityManager.START_ASSISTANT_HIDDEN_SESSION:
            throw new IllegalStateException(
                    "Cannot start assistant activity on a hidden session");
        case ActivityManager.START_CANCELED:
            throw new AndroidRuntimeException("Activity could not be started for "
                    + intent);
        default:
            throw new AndroidRuntimeException("Unknown error code "
                    + res + " when starting " + intent);
    }
}

have you declared this activity in your AndroidManifest.xml这句异常应该很熟悉了吧?启动一个没有注册的Activity的报错

再看个if (mParent == null)false分支:

public void startActivityFromChild(@NonNull Activity child, @RequiresPermission Intent intent,
        int requestCode, @Nullable Bundle options) {
    options = transferSpringboardActivityOptions(options);
    Instrumentation.ActivityResult ar =
        mInstrumentation.execStartActivity(
            this, mMainThread.getApplicationThread(), mToken, child,
            intent, requestCode, options);
    if (ar != null) {
        mMainThread.sendActivityResult(
            mToken, child.mEmbeddedID, requestCode,
            ar.getResultCode(), ar.getResultData());
    }
    cancelInputsAndStartExitTransition(options);
}

控制权依然是交给了mInstrumentation.execStartActivity(),剩余的代码索引和上面的一样

所以,代码索引的结论,按照一张图来表示就是:

方式2:使用applictonContext的startActivity

private void startActivityByApplicationContext() {
    Intent i = new Intent(MainActivity.this, Main2Activity.class);
    i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
    getApplicationContext().startActivity(i);
}

在方式1中已经展示了源码索引的方式,所以这里不再赘述贴图。直接给出代码索引结论图:

结论

两张图对比,我们很容易得出一个结论:

  • 启动Activity的最终执行权,都被交给了Instrumentation.java
  • 方式1:Activity.startActivity的最终执行者是它的mInstrumentation成员,mInstrumentation的持有者是Activity自身
  • 方式2:getApplicationContext().startActivity(i)的最终执行者是:ActivityThreadmInstrumentation成员,持有者是ActivityThread主线程

两种方式都可以把mInstrumentation当作hook切入点,将它从它的持有者中"偷梁换柱"

第一种启动方式的hook方案

创建一个HookActivityHelper.java,然后三步走:

(1)找到hook点,以及hook对象的持有者,上文中已经说明:hook点是Activity的mInstrumentation成员,持有者就是Activity

Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);

base是系统原来的执行逻辑,存起来后面用得着.

(2)创建Instrumentation代理类,继承Instrumentation,然后重写execStartActivity方法,加入自己的逻辑,然后再执行系统的逻辑

private static class ProxyInstrumentation extends Instrumentation {
    public ProxyInstrumentation(Instrumentation base) {
        this.base = base;
    }

    Instrumentation base;

    public ActivityResult execStartActivity(
            Context who, IBinder contextThread, IBinder token, Activity target,
            Intent intent, int requestCode, Bundle options) {

        Log.d("ProxyInstrumentation", "我们自己的逻辑");

        //这里还要执行系统的原本逻辑,但是突然发现,这个execStartActivity居然是hide的,只能反射咯
        try {
            Class<?> InstrumentationClz = Class.forName("android.app.Instrumentation");
            Method execStartActivity = InstrumentationClz.getDeclaredMethod("execStartActivity",
                    Context.class, IBinder.class, IBinder.class, Activity.class,
                    Intent.class, int.class, Bundle.class);
            return (ActivityResult) execStartActivity.invoke(base, 
                        who, contextThread, token, target, intent, requestCode, options);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return null;
    }
}

(3)用代理类对象替换hook对象

ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);
mInstrumentationField.set(activity, proxyInstrumentation);

(4)完整辅助类

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ActivityHookHelper {

    public static void hook(Activity activity) {

        //目标:Activity的mInstrumentation成员
        try {
            //1.拿到要hook的对象:Activity的mInstrumentation成员
            Field mInstrumentationField = Activity.class.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            Instrumentation base = (Instrumentation) mInstrumentationField.get(activity);

            //2.构建自己的代理对象,这里Instrumentation是一个class,而不是接口,所以只能用创建内部类的方式来做
            ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);

            //3.替换
            mInstrumentationField.set(activity, proxyInstrumentation);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private static class ProxyInstrumentation extends Instrumentation {
        public ProxyInstrumentation(Instrumentation base) {
            this.base = base;
        }

        Instrumentation base;

        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {

            Log.d("ActivityHook", "我们自己的逻辑");

            //这里还要执行系统的原本逻辑,但是突然发现,这个execStartActivity居然是hide的,只能反射咯
            try {
                Class<?> InstrumentationClz = Class.forName("android.app.Instrumentation");
                Method execStartActivity = InstrumentationClz.getDeclaredMethod("execStartActivity",
                        Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
                return (ActivityResult) execStartActivity.invoke(base, who, contextThread, token, target, intent, requestCode, options);
            } catch (Exception e) {
                e.printStackTrace();
            }

            return null;
        }

    }
}

(5)如何使用: 在MainActivity的onCreate中加入一行ActivityHookHelper.hook(this)

public class MainActivity extends AppCompatActivity {

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

        ActivityHookHelper.hook(this);
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivityByActivity();
            }
        });

    }

    private void startActivityByActivity() {
        Intent i = new Intent(MainActivity.this, Main2Activity.class);
        startActivity(i);
    }

}

效果:跳转依然正常,并且logcat中可以发现下面的日志.

cn.appblog.activityhookdemo D/ActivityHook: 我们自己的逻辑

OK,插入自己的逻辑,成功

第二种启动方式的hook方案

创建ApplicationContextHookHelper.java,然后同样是三步走:

(1)确定hook的对象和该对象的持有者

锁定ActivityThreadmInstrumentation成员

//1.主线程ActivityThread内部的mInstrumentation对象,先把他拿出来
Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");
//再拿到sCurrentActivityThread
Field sCurrentActivityThreadField = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
Object activityThreadObj = sCurrentActivityThreadField.get(null); //静态变量的属性get不需要参数,传null即可
//再去拿它的mInstrumentation
Field mInstrumentationField = ActivityThreadClz.getDeclaredField("mInstrumentation");
mInstrumentationField.setAccessible(true);
Instrumentation base = (Instrumentation) mInstrumentationField.get(activityThreadObj); //OK,拿到

(2)创建代理对象,和上面的代理类一模一样,就不重复贴代码了

//2.构建自己的代理对象,这里Instrumentation是一个class,而不是接口,所以只能用创建内部类的方式来做
ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);

(3)替换掉原对象

//3.偷梁换柱
mInstrumentationField.set(activityThreadObj, proxyInstrumentation);

(4)完整辅助类

import android.app.Activity;
import android.app.Instrumentation;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.Method;

public class ApplicationContextHookHelper {
    public static void hook() {
        // 如何取得ActivityThread好像是单例的吧。那就简单了,
        try {
            //1.主线程ActivityThread内部的mInstrumentation对象,先把他拿出来
            Class<?> ActivityThreadClz = Class.forName("android.app.ActivityThread");
            //再拿到sCurrentActivityThread
            Field sCurrentActivityThreadField = ActivityThreadClz.getDeclaredField("sCurrentActivityThread");
            sCurrentActivityThreadField.setAccessible(true);
            Object activityThreadObj = sCurrentActivityThreadField.get(null);//静态变量的属性get不需要参数,传null即可.
            //再去拿它的mInstrumentation
            Field mInstrumentationField = ActivityThreadClz.getDeclaredField("mInstrumentation");
            mInstrumentationField.setAccessible(true);
            Instrumentation base = (Instrumentation) mInstrumentationField.get(activityThreadObj);// OK,拿到

            //2.构建自己的代理对象,这里Instrumentation是一个class,而不是接口,所以只能用创建内部类的方式来做
            ProxyInstrumentation proxyInstrumentation = new ProxyInstrumentation(base);

            //3.偷梁换柱
            mInstrumentationField.set(activityThreadObj, proxyInstrumentation);

        } catch (Exception e) {
            e.printStackTrace();
        }

    }

    private static class ProxyInstrumentation extends Instrumentation {
        public ProxyInstrumentation(Instrumentation base) {
            this.base = base;
        }

        Instrumentation base;

        public ActivityResult execStartActivity(
                Context who, IBinder contextThread, IBinder token, Activity target,
                Intent intent, int requestCode, Bundle options) {

            Log.d("ApplicationContextHook", "我们自己的逻辑");

            //这里还要执行系统的原本逻辑,但是突然发现,这个execStartActivity居然是hide的,只能反射咯
            try {
                Class<?> InstrumentationClz = Class.forName("android.app.Instrumentation");
                Method execStartActivity = InstrumentationClz.getDeclaredMethod("execStartActivity",
                        Context.class, IBinder.class, IBinder.class, Activity.class, Intent.class, int.class, Bundle.class);
                return (ActivityResult) execStartActivity.invoke(base, who, contextThread, token, target, intent, requestCode, options);
            } catch (Exception e) {
                e.printStackTrace();
            }

            return null;
        }

    }
}

(5)如何使用: 在MainActivity的onCreate中加入一行ApplicationContextHookHelper.hook()

public class MainActivity extends AppCompatActivity {

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

        ApplicationContextHookHelper.hook();
        findViewById(R.id.btn).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startActivityByApplicationContext();
            }
        });
    }

    private void startActivityByApplicationContext() {
        Intent i = new Intent(MainActivity.this, Main2Activity.class);
        i.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
        getApplicationContext().startActivity(i);
    }
}

效果

cn.appblog.activityhookdemo D/ApplicationContextHook: 我们自己的逻辑
cn.appblog.activityhookdemo D/ApplicationContextHook: 我们自己的逻辑

OK,第二种启动方式,我们也可以加入自己的逻辑了,hook成功!

目前方案弊端分析

  • 启动方式1的hook:只是在针对单个Activity类,来进行hook,多个Activity则需要写多次,或者写在BaseActivity里面
  • 启动方式2的hook:可以针对全局进行hook,无论多少个Activity,只需要调用一次ApplicationContextHookHelper.hook()函数即可,但是,它只能针对getApplicationContext().startActivity(i)普通的Activity.startActivity(i)则不能起作用

那么有没有一种完整的解决方案:能够在全局起作用,并且可以在两种启动方式下都能hook。回顾之前的两张代码索引结论图,会发现,两种启动Activity的方式,最终都被执行到了AMS内部,下一步,尝试hook AMS

最终解决方案

源码索引

代码索引:基于SDK 28 ~ SDK 29

取得AMS(ActivityManagerService实例)的代码:

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);

如果可以在系统接收到AMS实例之前,把他截了,是不是就可以达到我们的目的?进去看看getService的代码:

SDK 28 ~ Android 9.0: ActivityManager.java

private static final Singleton<IActivityManager> IActivityManagerSingleton =
        new Singleton<IActivityManager>() {
            @Override
            protected IActivityManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
                final IActivityManager am = IActivityManager.Stub.asInterface(b);
                return am;
            }
        };

/** @hide */
public static IActivityManager getService() {
    return IActivityManagerSingleton.get();
}

SDK 29 ~ Android 10.0: ActivityTaskManager.java

private static final Singleton<IActivityTaskManager> IActivityTaskManagerSingleton =
        new Singleton<IActivityTaskManager>() {
            @Override
            protected IActivityTaskManager create() {
                final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
                return IActivityTaskManager.Stub.asInterface(b);
            }
        };

/** @hide */
public static IActivityTaskManager getService() {
    return IActivityTaskManagerSingleton.get();
}

真正的AMS实例来自一个Singleton单例辅助类的create()方法,并且这个Singleton单例类,提供get方法,获得真正的实例

/**
 * Singleton helper class for lazily initialization.
 *
 * Modeled after frameworks/base/include/utils/Singleton.h
 *
 * @hide
 */
public abstract class Singleton<T> {
    private T mInstance;

    protected abstract T create();

    public final T get() {
        synchronized (this) {
            if (mInstance == null) {
                mInstance = create();
            }
            return mInstance;
        }
    }
}

参考:https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/util/Singleton.java

那么,我们从这个单例中,就可以获得系统当前的AMS实例,将它取出来,然后保存。OK,确认:

  • hook对象:ActivityManagerIActivityManagerSingleton成员变量内的单例mInstance
  • hook对象的持有者:ActivityManagerIActivityManagerSingleton成员变量

hook实现

(1)找到hook对象,并且存起来

//1.把hook的对象取出来保存
//矮油,静态的耶,开心
Class<?> ActivityManagerClz = Class.forName("android.app.ActivityManager");
Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");
final Object IActivityManagerObj = getServiceMethod.invoke(null); //OK,已经取得这个系统自己的AMS实例

(2)创建自己的代理类对象,IActivityManager是一个AIDL生成的动态接口类,所以在编译时,androidStudio会找不到这个类,所以,先反射,然后用Proxy进行创建代理。

//2.现在创建我们的AMS实例
//由于IActivityManager是一个接口,那么我们可以使用Proxy类来进行代理对象的创建
//结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");
Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), 
    new Class[]{IActivityManagerClz}, new InvocationHandler() {
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            //proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
            if (method.getName().equals("startActivity")) {
                Log.d("GlobalActivityHook", "全局hook 到了 startActivity");
            }
            return method.invoke(IActivityManagerObj, args);
        }
    }
);

(3)偷梁换柱:这次有点复杂,不再是简单的field.set,因为这次的hook对象被包裹在了一个Singleton

//3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面
Field IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");
IActivityManagerSingletonField.setAccessible(true);
Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
//反射创建一个Singleton的class
Class<?> SingletonClz = Class.forName("android.util.Singleton");
Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

使用方法:老样子,在Activity onCreate里面加入GlobalActivityHookHelper.hook()
运行起来,预期结果应该是:能够在logcat中看到日志:

GlobalActivityHook - 全局hook 到了 startActivity

如果没有看到这个日志,那么原因就是:我们写的hook代码并没有兼容性,需要适配Android版本

适配解决

以 SDK 23 ~ Android 6.0 为例

(1)找到SDK 23的源码

注意,前方有坑,如果直接把Android Studio combileSDK改成23,会出现很多未知问题,所以不建议这么做。但是我们一定要看SDK 23的源码,怎么办?

在线查看源码 - https://www.androidos.net.cn/sourcecode
从谷歌官网下载SDK 23的源码,然后用SourceInsight查看)

(2)查看getService方法不存在的原因,两个版本28 和 23,在这一块代码上有什么不同

(3)改造GlobalActivityHookHelper.java,判定当前设备的系统版本号,让它可以兼容所有版本

按照上面的步骤:

发现SDK 23里面:Instrumentation类的execStartActivitiesAsUser(Context who, IBinder contextThread, IBinder token, Activity target, Intent[] intents, Bundle options, int userId)方法里,获取AMS实例的方式完全不同

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);

参考:https://www.androidos.net.cn/android/6.0.1_r16/xref/frameworks/base/core/java/android/app/Instrumentation.java

它是使用ActivityManagerNative.getDefault()来获得的,进去ActivityManagerNative继续找:

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;
    }
};

/**
 * Retrieve the system's default/global activity manager.
 */
static public IActivityManager getDefault() {
    return gDefault.get();
}

参考:https://www.androidos.net.cn/android/6.0.1_r16/xref/frameworks/base/core/java/android/app/ActivityManagerNative.java

OK,找到了区别,确定结论:SDK 28和23在这块代码上的区别就是获得AMS实例的类名和方法名都不同。另外,这个变化是在SDK 26版本修改的,所以26和26以后是通过ActivityManager.getService()来获取,26以前使用ActivityManagerNative.getDefault()来获得
调整当前的hook方法,修改为下面这样:

import android.os.Build;
import android.util.Log;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class GlobalActivityHookHelper {

    //设备系统版本是不是大于等于26
    private static boolean ifSdkOverIncluding26() {
        int SDK_INT = Build.VERSION.SDK_INT;
        if (SDK_INT > 26 || SDK_INT == 26) {
            return true;
        } else {
            return false;
        }
    }

    public static void hook() {

        try {
            Class<?> ActivityManagerClz;
            final Object IActivityManagerObj;
            if (ifSdkOverIncluding26()) {
                ActivityManagerClz = Class.forName("android.app.ActivityManager");
                Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getService");
                IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
            } else {
                ActivityManagerClz = Class.forName("android.app.ActivityManagerNative");
                Method getServiceMethod = ActivityManagerClz.getDeclaredMethod("getDefault");
                IActivityManagerObj = getServiceMethod.invoke(null);//OK,已经取得这个系统自己的AMS实例
            }

            //2.现在创建我们的AMS实例
            //由于IActivityManager是一个接口,那么其实我们可以使用Proxy类来进行代理对象的创建
            // 结果被摆了一道,IActivityManager这玩意居然还是个AIDL,动态生成的类,编译器还不认识这个类,怎么办?反射咯
            Class<?> IActivityManagerClz = Class.forName("android.app.IActivityManager");
            Object proxyIActivityManager = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(), new Class[]{IActivityManagerClz}, new InvocationHandler() {
                @Override
                public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                    //proxy是创建出来的代理类,method是接口中的方法,args是接口执行时的实参
                    if (method.getName().equals("startActivity")) {
                        Log.d("GlobalActivityHook", "全局hook 到了 startActivity");
                    }
                    return method.invoke(IActivityManagerObj, args);
                }
            });

            //3.偷梁换柱,这里有点纠结,这个实例居然被藏在了一个单例辅助类里面
            Field IActivityManagerSingletonField;
            if (ifSdkOverIncluding26()) {
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("IActivityManagerSingleton");
            } else {
                IActivityManagerSingletonField = ActivityManagerClz.getDeclaredField("gDefault");
            }

            IActivityManagerSingletonField.setAccessible(true);
            Object IActivityManagerSingletonObj = IActivityManagerSingletonField.get(null);
            Class<?> SingletonClz = Class.forName("android.util.Singleton");//反射创建一个Singleton的class
            Field mInstanceField = SingletonClz.getDeclaredField("mInstance");
            mInstanceField.setAccessible(true);
            mInstanceField.set(IActivityManagerSingletonObj, proxyIActivityManager);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

成功,实现了全局范围内的startActivity动作的hook

hook开发可能的坑

(1)Android Studio阅读源码很多类无法索引,这是因为有一些类是@hide的,无法Ctrl点进去

解决方案:Ctrl+shift+R输入类名,手动进入

(2)Android Studio阅读源码直接报红:或者一些是AIDL动态生成的接口,无法直接查看,比IActivityManager

解决方案:这种接口不用管它,如果非要用到它,那就使用本类的包名+IActivityManager作为全限定名,去反射创建它

(3)hook开发,是学习源码思想,改变源码执行流程。所以,在多个版本的设备上运行,很容易发生不兼容的情况

解决方案:找到不兼容的设备版本,根据报的异常,参照源码的版本变迁做出相应的兼容性改动

本文转载至:https://www.jianshu.com/p/efce746836f5

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/29/startup-process-for-android-hook-activity/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
Android Hook-Activity的启动流程
两种启动Activity的方式源码追踪 源码基于 SDK 28 ~ Android 9.0 方式1:使用Activity自带的startActivity 代码示例 private void startActivityByActivity()……
<<上一篇
下一篇>>
文章目录
关闭
目 录