Android Hook-Activity的启动流程

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

源码基于 SDK 28 ~ Android 9.0

方式1:使用Activity自带的startActivity

代码示例

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

程序执行走向图

代码追踪:Activity.java

1
2
3
4
@Override
public void startActivity(Intent intent) {
this.startActivity(intent, null);
}
1
2
3
4
5
6
7
8
9
10
@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);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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去找这个方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
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中,可以找到关键代码:

1
2
3
4
5
6
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进行检测;

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/** @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分支:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
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

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

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

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
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对象

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

(4)完整辅助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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中可以发现下面的日志.

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

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

第二种启动方式的hook方案

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

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

锁定ActivityThreadmInstrumentation成员

1
2
3
4
5
6
7
8
9
10
//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)创建代理对象,和上面的代理类一模一样,就不重复贴代码了

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

(3)替换掉原对象

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

(4)完整辅助类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
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()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
}
}

效果

1
2
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实例)的代码:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法,获得真正的实例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
/**
* 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
2
3
4
5
//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进行创建代理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//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

1
2
3
4
5
6
7
8
9
//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中看到日志:

1
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实例的方式完全不同

1
2
3
4
5
6
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继续找:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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方法,修改为下面这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2020 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :