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)
的最终执行者是:ActivityThread
的mInstrumentation
成员,持有者是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
的对象和该对象的持有者
锁定ActivityThread
的mInstrumentation
成员
//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;
}
}
}
那么,我们从这个单例中,就可以获得系统当前的AMS
实例,将它取出来,然后保存。OK,确认:
hook
对象:ActivityManager
的IActivityManagerSingleton
成员变量内的单例mInstance
hook
对象的持有者:ActivityManager
的IActivityManagerSingleton
成员变量
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);
它是使用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();
}
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
开发,是学习源码思想,改变源码执行流程。所以,在多个版本的设备上运行,很容易发生不兼容的情况
解决方案:找到不兼容的设备版本,根据报的异常,参照源码的版本变迁做出相应的兼容性改动
版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/03/29/startup-process-for-android-hook-activity/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。
共有 0 条评论