插件化技术和热修复技术都属于动态加载,从普及率的角度来看,插件化技术还没有热修复的普及率高,主要原因是占大多数的中小型应用很少也没有必要去采用插件化技术。
Android P preview(Android 9)开始限制调用隐藏 API,也出现了一些绕过限制的方案。无论采用何种方案,插件化的原理还是需要理解的。
1 动态加载技术
动态加载技术是插件化的前身。
在 Android 传统开发中,一旦应用的代码被打包成 APK 并被上传到各个渠道市场,就不能修改应用的源码了。只能通过服务器来控制应用中预留的分支代码。但是很多时候我们无法提前预知需求和突发的情况,也就不能提前在应用代码中预留分支代码,这时就需要采用动态加载技术。
在应用程序运行时,动态加载一些程序中原本不存在的可执行文件并运行这些文件里的代码逻辑。可执行文件总的来说分为两种,一种是动态链接库 so,另一种是 dex 相关文件(dex 以及包含 dex 的 jar/apk 文件)。
随着应用的发展,动态加载技术派生出两个技术,分别是热修复技术和插件化技术:
其中热修复技术主要用来修复 Bug,插件化技术则主要用于解决应用越来越庞大以及功能模块的解耦,围绕着两个技术出现了很多的热修复框架和插件化框架。 需要注意的是,动态加载技术本身并没有被官方认可,并且是一个非常规的技术,在国外这门技术关注度并不高,它的产生更多的是国内的业务需求和产品驱动。
2 插件化的产生
在 Android 开发早起很少用到动态加载技术,因为这个时候业务需求和应用开发的复杂度不是很大高,但随着互联网的发展会出现以下几种情况:
1 业务复杂,模块耦合
随着业务越来越复杂越来越多,导致应用体积越来越大,应用程序的工程和功能模块数量越来越多,一个应用可能是由几十、几百人协同开发的,很多工程和功能模块都是由一个小组进行开发维护的,如果功能模块间的耦合度比较高,修改一个功能模块会影响其他的功能模块,势必会极大地增加沟通成本。
2 应用间的接入
一个应用不再是单独的应用,它可能需要接入其他的应用。拿手机淘宝来说,它的流量非常大,其他的淘宝应用或者业务比如:聚划算、菜鸟、淘票票、饿了么等都希望接入到淘宝客户端,这样既能获取到流量,同时也可以将用于引流到自己的应用中,如果使用常规的技术手段,会产生两个问题:
- 比如饿了么需要接入到淘宝客户端中,那么饿了么团队可能需要维护两个版本,一个是自身版本,另一个是淘宝客户端版本,这样维护成功和沟通成本会比较高。况且饿了么不止是接入淘宝客户端,它还可以接入到其他应用中,比如支付宝应用,那么饿了么团队维护的就不仅仅是两个版本了;
- 比如淘宝客户端接入很多其他的应用,势必会使应用的体积急剧变大,编译时间会变得非常长,一个 Bug 和功能就会由组内的开发协作变为组与组之间甚至是部门之间的开发协作,极大地增加了开发测试成本和沟通成本,新功能的添加会牵扯的越多,版本发布的时间变得不可控;
3 65536 限制,内存占用大
随着应用功能越来越复杂,代码量不断的增大,引入的库也越来越多,可能会在编译时提示如下异常:
com.android.dex.DexIndexOverflowException: method ID not in [0, 0xffff]: 65536
这说明应用引用的方法超过了最大数 65536 个,产生这一问题的原因就是系统的 65536 限制,65536 限制的主要原因是 DVM Bytecode 的限制,DVM 指令集的方法调用指令 invoke-kind 索引为 16bits,最多能引用 65535 个方法。
应用代码量的增加同时也导致应用占用大量的内存。
3 插件化思想
Android 系统本身并没有提供太多的功能,内置的应用数量和整体功能也很有限,它像一个为人类服务的机器人,只能满足基本的需求。如下所示:
可以看到初始的机器人只有相机、天气、地图、浏览器、计算器等功能,这显然是很乏味的,我们可以给这个机器人安装很多其他的应用,使它提供更多的功能,如下所示:
我们给这个设备安装了很多应用,这些应用不仅覆盖了人的衣食住行,还提供了娱乐功能,我们可以玩游戏、听音乐和购物等,手机的功能也得到了极大提升,能够为人类提供更多的服务。这些安装的应用可以理解为插件,这些插件可以自由地进行插拔,比如我们需要听音乐时可以安装“酷狗音乐”,如果不好用就把它卸载掉。这么说来其实 Android、iOS、Mac 等操作系统采用的都是这种思想,也就是插件化思想。
4 插件化定义
第 2 节所提出的问题就可以采用插件化的思想来解决,如果没有采用插件化,那么手机淘宝客户端的框架可以缩略为下图所示的样子:
上图中采用常规技术的淘宝客户端分为两大部分,一个是自身的业务模块,比如购物、逛逛、消息和搜索等,另一个是要外接的其他的应用业务,比如聚划算、菜鸟、淘票票和饿了么等。如果采用这种常规的技术方案,那么就会产生第二节的各种问题,为了解决这些问题,我们可以采用插件化的思想来对淘宝主客户端的框架进行改造,如图所示:
插件化的客户端由宿主和插件两个部分组成。宿主就是指被安装到手机中的 APK,插件一般是指经过处理的 APK、so 和 dex 等文件,插件可以被宿主进行加载,有的插件也可以作为 APK 独立运行。
可以看出采用插件化的淘宝客户端分为两大部分,一部分是宿主,也就是淘宝客户端;另一部分是插件部分,不仅包括了外接的其他应用业务,比如聚划算和菜鸟,同时也包括了淘宝自身的业务模块,比如消息和搜索。需要注意的是,这里的举例更过是为了便于理解,只是淘宝客户端演讲过程中的一个非常缩略的框架,和真实的淘宝客户端有非常大的区别。
插件化的定义:将一个应用按照插件的方式进行改造的过程就叫做插件化。
采用了插件化的淘宝客户端就能避免出现第 2 节出现的各种问题,在协作方面,插件可以由一个人或者一个小组来进行开发,这样各个插件之间,以及插件和宿主之间的耦合度会降低。应用间的接入和维护也变的便捷,每个应用团队只需要负责自己的那一部分就可以了。应用以及主 dex 的体积也会相应变小,间接地避免了 65536 限制。第一次加载到内存的只有淘宝主客户端,当使用到其他插件时才会加载相应的插件到内存,这样就减少了内存的占用。
5 插件化框架对比
为了方便地将应用插件化,出现了很多插件化的框架。虽然插件化湿近两年才受到广泛的关注,但是其实在 2012 年大众点评的屠毅就推出了 AndroidDynamicLoader 框架,这是最早的插件化框架。插件化技术发展到现在,已经产生了众多插件化框架,如下图所示:
目前主流的插件化方案对比,如下所示:
如果加载的插件不需要和宿主有任何耦合,也无须和宿主进行通信,比如加载第三方 APP,那么推荐使用 RePlugin,其他的情况推荐使用 VirtualApk。
6 Activity 插件化
Activity 插件化主要有 3 种实现方式,分别是反射实现、接口实现和 Hook 技术实现。 反射实现会对性能有所影响,主流的插件化框架没有采用此方式。目前 Hook 技术实现是主流,因此着重介绍 Hook 技术的实现。
Hook 技术实现主要有两种解决方案,一种是通过 Hook IActivityManager 来实现,另一种是 Hook Instrumentation 实现。
6.1 Activity 的启动流程回顾
Activity 的启动分为两种,一种是根 Activity 的启动,另一种是普通 Activity 的启动。
对于根 Activity 的启动,首先是 Launcher 进程向 system_server 进程的 ATMS 请求创建 Activity,如果所需要的应用程序进程不存在,就会向 Zygote 进程请求创建新的应用程序进程,应用程序进程启动后,ATMS 会请求应用程序进程创建并启动根 Activity。以下是根 Activity 的启动流程:
对于普通的 Activity 的启动,不会涉及到应用进程的创建,和 Launcher 进程也没有关系。应用进程中的 Activity 向 system_server 进程中的 ATMS 请求创建 Activity,ATMS 会对这个 Activity 的生命周期和栈进行管理,校验 Activity 等。如果 Activity 满足 ATMS 的校验,ATMS 就会请求应用程序中的 ActivityThread 创建并启动 Activity。
以下是普通 Activity 的启动流程:
6.2 Hook IActivityTaskManager 方案实现
ATMS 存在于 system_server 进程中,无法直接修改,只能在应用程序进程中做文章,可以采用预先占位的方式来解决没有在 AndroidManifest.xml 中显示声明的问题。
具体做法就是在 Activity 访问 ATMS 之前使用一个在 AndroidManifest.xml 中注册的 Activity 来进行占位,用来通过 ATMS 的校验,接着在 ATMS 向 ApplicationThread 发送请求时用插件 Activity 替换占位的 Activity。
6.2.1 注册 Activity 进行占位
创建一个 TargetActivity 代表已经加载进来的插件 Activity,接着再创建一个 SubActivity 来进行占位。在 AndroidManifest.xml 中注册 SubActivity,如下所示:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.myapplication">
<application
... >
<activity
android:name=".MainActivity"
android:exported="true"
android:label="@string/app_name"
android:theme="@style/Theme.androidcenter.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".SubActivity" />
</application>
</manifest>
TargetActivity 用来代表已经被加载进来的插件 Activity,因此不需要在 AndroidManifest.xml 中进行注册。如果直接在 MainActivity 中启动 TargetActivity 肯定会报错(android.content.ActivityNotFoundException):
Intent intent = new Intent(MainActivity.this, TargetActivity.class);
startActivity(intent);
运行,出现以下问题:
这是 TargetActivity 没有在清单文件中进行注册的原因。那么如何避开清单文件的检查,正常的调用起 TargetActivity 呢?可以通过 Hook 技术,避开清单文件的校验,从而实现跳转到未注册的 TargetActivity 中。
6.2.2 使用占位 Activity 通过 ATMS 验证
为了防止报错,需要将启动的 TargetActivity 替换为 SubActivity,用 SubActivity 来通过 ATMS 的验证。
以下是 Android 7.0 的相关源码:
// /frameworks/base/core/java/android/app/Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options); // 1
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
// frameworks/base/core/java/android/app/ActivityManagerNative.java
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;
}
};
class ActivityManagerProxy implements IActivityManager {
public int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo,
String resultWho, int requestCode, int startFlags,
ProfilerInfo profilerInfo, Bundle options)
throws RemoteException {
Parcel data = Parcel.obtain();
Parcel reply = Parcel.obtain();
data.writeInterfaceToken(IActivityManager.descriptor);
data.writeStrongBinder(caller != null ? caller.asBinder() : null);
data.writeString(callingPackage);
intent.writeToParcel(data, 0);
data.writeString(resolvedType);
data.writeStrongBinder(resultTo);
data.writeString(resultWho);
data.writeInt(requestCode);
data.writeInt(startFlags);
if (profilerInfo != null) {
data.writeInt(1);
profilerInfo.writeToParcel(data, Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
} else {
data.writeInt(0);
}
if (options != null) {
data.writeInt(1);
options.writeToParcel(data, 0);
} else {
data.writeInt(0);
}
mRemote.transact(START_ACTIVITY_TRANSACTION, data, reply, 0);
reply.readException();
int result = reply.readInt();
reply.recycle();
data.recycle();
return result;
}
}
以下是 Android 8.0 的相关源码:
// frameworks/base/core/java/android/app/Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String 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, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
// /frameworks/base/core/java/android/app/ActivityManager.java
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
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;
}
};
// frameworks/base/services/core/java/com/android/server/am/ActivityManagerService.java
public class ActivityManagerService extends IActivityManager.Stub
implements Watchdog.Monitor, BatteryStatsImpl.BatteryCallback {
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
@Override
public final int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions,
int userId) {
enforceNotIsolatedCaller("startActivity");
userId = mUserController.handleIncomingUser(Binder.getCallingPid(),
Binder.getCallingUid(), userId, false, ALLOW_FULL_ONLY,
"startActivity", null);
// TODO: Switch to user app stacks here.
return mActivityStarter.startActivityMayWait(caller, -1, callingPackage, intent,
resolvedType, null, null, resultTo, resultWho, requestCode, startFlags,
profilerInfo, null, null, bOptions, false, userId, null, null,
"startActivityAsUser");
}
}
以下是 Android 10.0 的相关源码:
// frameworks/base/core/java/android/app/Instrumentation.java
@UnsupportedAppUsage
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, String target,
Intent intent, int requestCode, Bundle options) {
IApplicationThread whoThread = (IApplicationThread) contextThread;
...
try {
intent.migrateExtraStreamToClipData();
intent.prepareToLeaveProcess(who);
int result = ActivityTaskManager.getService()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target, requestCode, 0, null, options);
checkStartActivityResult(result, intent);
} catch (RemoteException e) {
throw new RuntimeException("Failure from system", e);
}
return null;
}
// /frameworks/base/core/java/android/app/ActivityTaskManager.java
public static IActivityTaskManager getService() {
return IActivityTaskManagerSingleton.get();
}
@UnsupportedAppUsage(trackingBug = 129726065)
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);
}
};
// frameworks/base/services/core/java/com/android/server/wm/ActivityTaskManagerService.java
@Override
public final int startActivity(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions,
UserHandle.getCallingUserId());
}
@Override
public int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions,
int userId) {
return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo,
resultWho, requestCode, startFlags, profilerInfo, bOptions, userId,
true /*validateIncomingUser*/);
}
int startActivityAsUser(IApplicationThread caller, String callingPackage,
Intent intent, String resolvedType, IBinder resultTo, String resultWho,
int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions,
int userId, boolean validateIncomingUser) {
enforceNotIsolatedCaller("startActivityAsUser");
userId = getActivityStartController().checkTargetUser(userId, validateIncomingUser,
Binder.getCallingPid(), Binder.getCallingUid(), "startActivityAsUser");
// TODO: Switch to user app stacks here.
return getActivityStartController().obtainStarter(intent, "startActivityAsUser")
.setCaller(caller)
.setCallingPackage(callingPackage)
.setResolvedType(resolvedType)
.setResultTo(resultTo)
.setResultWho(resultWho)
.setRequestCode(requestCode)
.setStartFlags(startFlags)
.setProfilerInfo(profilerInfo)
.setActivityOptions(bOptions)
.setMayWait(userId)
.execute();
}
在 Android 7.0 中 AMS 的代理是 ActivityManagerProxy,Activity 的启动会调用 ActivityManagerNative.getDefault() 方法,getDefault() 方法返回了 IActivityManager 类型的对象,IActivityManager 借助了 Singleton 类来实现单例,而且 gDefault 是静态的,因此是一个比较好的 Hook 点。
Android 8.0 的 Activity 的启动会调用 ActivityManager.getService() 方法, AMS 的代理是 IActivityManager,直接采用 AIDL 来进行进程间通信。ActivityManager.getService() 方法返回了 IActivityManager 类型的对象,并且 IActivityManager 借助了 Singleton 类来实现单例,同样的,IActivityManagerSingleton 是静态的,是一个比较好的 Hook 点。
Android 10 使用的是 ATMS 启动的 Activity,ATMS 的代理是 IActivityTaskManager,采用的也是 AIDL 来进行进程间通信。ActivityTaskManager.getService() 返回的是 IActivityTaskManager 类型的对象,并且 IActivityTaskManager 借助了 Singleton 类来实现单例,同样的,IActivityTaskManagerSingleton 是静态的,是一个比较好的 Hook 点。
因此,无论是 Android 7.0、Android 8.0 还是 Android 10,IActivityManager/IActivityTaskManager 都是比较好的 Hook 点。Singleton 类如下所示:
// frameworks/base/core/java/android/util/Singleton.java
public abstract class Singleton<T> {
@UnsupportedAppUsage
private T mInstance;
protected abstract T create();
@UnsupportedAppUsage
public final T get() {
synchronized (this) {
if (mInstance == null) {
mInstance = create();
}
return mInstance;
}
}
}
由于 Hook 需要多次对字段进行反射操作,先实现一个工具类 FieldUtil:
public class FieldUtil {
public static Object getField(Class clazz, Object target, String name)
throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field.get(target);
}
public static Field getField(Class clazz, String name) throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
return field;
}
public static void setField(Class clazz, Object target, String name, Object value)
throws Exception {
Field field = clazz.getDeclaredField(name);
field.setAccessible(true);
field.set(target, value);
}
}
接着定义替换 IActivityManager/IActivityTaskManager 的代理类 IActivityTaskManagerProxy,如下所示:
public class IActivityTaskManagerProxy implements InvocationHandler {
private static final String TAG = "IActivityTaskManagerProxy";
private Object mActivityTaskManager;
public IActivityTaskManagerProxy(Object activityTaskManager) {
this.mActivityTaskManager = activityTaskManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) { // 1
Intent intent = null;
int index = 0;
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
intent = (Intent) args[index];
Intent subIntent = new Intent(); // 2
String packageName = "com.example.myapplication";
subIntent.setClassName(packageName, packageName + ".SubActivity"); // 3
subIntent.putExtra(HookHelper.TARGET_INTENT, intent); // 4
args[index] = subIntent; // 5
}
return method.invoke(mActivityTaskManager, args);
}
}
Hook 点 IActivityManager/IActivityTaskManager 是一个接口,建议采用动态代理。在注释 1 处拦截 startActivity 方法,接着获取参数 args 中第一个 Intent 对象,它原本要启动插件 TargetActivity 的 Intent。在注释 2、3 出新建一个 stubIntent 用来启动 StubActivity,在注释 4 处将这个 TargetActivity 的 Intent 保存到 stubIntent 中,便于以后还原 TargetActivity。在注释 5 处用 stubIntent 赋值给参数 args,这样启动的目标就变成了 SubActivity,用来通过 AMS/ATMS 的校验。最后用代理类 IActivityTaskManagerProxy 来替换 IActivityManager/IActivityTaskManager,如下所示:
public class HookHelper {
public static final String TARGET_INTENT = "target_intent";
public static void hookActivityTaskManager() {
if (Build.VERSION.SDK_INT > Build.VERSION_CODES.P) { // 1 > Android 9
hookATMS();
} else {
hookAMS();
}
}
public static void hookATMS() {
try {
Field iActivityTaskManagerFiled = null;
Class<?> activityTaskManagerClazz =
Class.forName("android.app.ActivityTaskManager");
iActivityTaskManagerFiled =
activityTaskManagerClazz.getDeclaredField("IActivityTaskManagerSingleton");
iActivityTaskManagerFiled.setAccessible(true);
Object singleton = iActivityTaskManagerFiled.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance"); // 2
mInstanceField.setAccessible(true);
final Object iActivityTaskManager = mInstanceField.get(singleton); // 3
Object proxy =
Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{Class.forName("android.app.IActivityTaskManager")},
new IActivityTaskManagerProxy(iActivityTaskManager));
mInstanceField.set(singleton, proxy);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static void hookAMS() {
try {
Field singletonField = null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
Class<?> activityManagerClazz =
Class.forName("android.app.ActivityManager");
// 获取 activityManager 中的 IActivityManagerSingleton 字段
singletonField =
activityManagerClazz.getDeclaredField("IActivityManagerSingleton");
} else {
Class<?> activityManagerNativeClazz =
Class.forName("android.app.ActivityManagerNative");
// 获取 ActivityManagerNative 中的 gDefault 字段
singletonField = activityManagerNativeClazz.getDeclaredField("gDefault");
}
singletonField.setAccessible(true); // 可访问 gDefault 字段
Object singleton = singletonField.get(null);
Class<?> activityManagerCls = Class.forName("android.util.Singleton");
Field mInstanceField = activityManagerCls.getDeclaredField("mInstance"); // 2
mInstanceField.setAccessible(true);
final Object iActivityManager = mInstanceField.get(singleton); // 3
Class<?> iActivityManagerClazz = Class.forName("android.app.IActivityManager");
Object proxy =
Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[]{iActivityManagerClazz},
new IActivityTaskManagerProxy(iActivityManager));
mInstanceField.set(singleton, proxy);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
首先在注释 1 处对系统版本进行区分,最终获取的是 Singleton<IActivityTaskManager>/Singleton<IActivityManager>
类型的 IActivityTaskManagerService/IActivityManagerSigleton/gDefault 字段。在注释 2 处获取 Singleton 类中的 mInstance 字段。
从前面 Singleton 类的代码可以得知 mInstance 字段的类型为 T,在注释 3 处得到 IActivityTaskManagerSingleton/IActivityManagerSingle/gDefault 字段中的 T 的类型,T 的类型为 IActivityTaskManager/IActivityManager。最后后台创建代理类 IActivityTaskManagerProxy,用 IActivityTaskManagerProxy 来替换 IActivityTaskManager/IActivityManager。
在 MainActivity 中启动 TargetActivity,如下所示:
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HookHelper.hookActivityTaskManager();
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, TargetActivity.class);
startActivity(intent);
}
});
}
}
点击 Button 时,启动的并不是 TargetActivity 而是 SubActivity,说明已经成功用 SubActivity 通过了 ATMS/AMS 的校验。
6.2.3 还原插件 Activity
前面用占位的 Activity 通过了 ATMS/AMS 的校验,但是我们要启动的插件是 TargetActivity,还需要用插件 TargetActivity 来替换占位的 SubActivity,替换时机就在 ApplicationThread 向 ActivityThread 发送消息之后。以下是 ActivityThread 启动 Activity 的过程:
ActivityThread 会通过 H 类将代码的逻辑切换到主线程中,H 类是 ActivityThread 的内部类并继承自 Handler,如下所示:
// frameworks/base/core/java/android/app/ActivityThread.java
class H extends Handler {
public void handleMessage(Message msg) {
...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
...
}
}
H 类中重写了 handleMessage 方法会对 EXECUTE_TRANSACTION 类型的消息进行处理,最终会调用 Activity 的 onCreate 方法,那么在哪里进行替换呢?接着来看 Handler.dispatchMessage 方法:
// frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Handler 的 dispatchMessage 用于处理消息,看到如果 Handler 的 Callback 类型的 mCallback 不为 null,就会执行 mCallback 的 handleMessage 方法。因此,mCallback 可以作为 Hook 点,我们可以用自定义的 Callback 来替换 mCallback,自定义的 Callback 如下所示:
public class HCallback implements Handler.Callback {
public static final int EXECUTE_TRANSACTION = 159;
Handler mHandler;
public HCallback(Handler handler) {
mHandler = handler;
}
@Override
public boolean handleMessage(@NonNull Message msg) {
if (msg.what == EXECUTE_TRANSACTION) {
try {
Object r = msg.obj;
// 获取到 ClientTransaction 列表中的 mActivityCallbacks
Field mActivityCallbacksField =
r.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List<Object> mActivityCallbacks = (List<Object>)
mActivityCallbacksField.get(r);
String itemName = "android.app.servertransaction.LaunchActivityItem";
for (Object obj : mActivityCallbacks) {
if (obj.getClass().getCanonicalName().equals(itemName)) {
Field mIntentField = obj.getClass().getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent sugerBullet = (Intent) mIntentField.get(obj);
Intent realIntent = sugerBullet.getParcelableExtra(TARGET_INTENT);
// 把 TargetActivity 信息写回去
sugerBullet.setComponent(realIntent.getComponent());
break;
}
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
mHandler.handleMessage(msg);
return true;
}
}
HCallback 实现了 Handler.Callback,并重写了 handleMessage 方法,当收到消息的类型为 EXECUTE_TRANSACTION 时,将启动 StubActivity 的 Intent 替换为启动 TargetActivity 的 Intent。接着在 HookHelper 中定义一个 hookHandler 方法,如下所示:
// HookHelper
public static void hookHandler() {
try {
Class<?> activityThread = Class.forName("android.app.ActivityThread");
Field sCurrentActivityThreadField =
activityThread.getDeclaredField("sCurrentActivityThread"); // 1
sCurrentActivityThreadField.setAccessible(true);
Object sCurrentActivityThread = sCurrentActivityThreadField.get(null);
Field mHField = activityThread.getDeclaredField("mH"); // 2
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(sCurrentActivityThread); // 3
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new HCallback(mH));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
ActivityThread 类中有一个静态变量 sCurrentActivityThread,用于表示当前的 ActivityThread 对象,因此在注释 1 处获取 ActivityThread 中定义的 sCurrentActivityThread 对象。注释 2 处获取 ActivityThread 类的 mH 字段,接着在注释 3 处获取当前 ActivityThread 对象中的 mH 对象,最后用 HCallback 来替换 mH 中的 mCallback。在 Activity.onCreate 方法中调用 HookHelpler.hookHandler() 方法,运行程序,当我们单击“启动插件”时,发现启动的是插件 TargetActivity。
public class MainActivity extends AppCompatActivity {
private Button button;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
HookHelper.hookActivityTaskManager();
HookHelper.hookHandler();
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(MainActivity.this, TargetActivity.class);
startActivity(intent);
}
});
}
}
6.2.4 插件 Activity 的生命周期
插件 TargetActivity 确实启动了,但是它有生命周期吗?
从源码的角度来进行分析,Activity.finish 方法可以触发 Activity 的生命周期变化,和 Activity 的启动过程类似。以下是 Activity.finish 的相关时序图:
总体来说是,Activity 请求 ATMS,ATMS 通过 ApplicationThread 调用 ActivityThread,ActivityThread 向 H 类发送消息执行 handleDestroyActivity,接着调用 performDestroyActivity 方法。
// frameworks/base/core/java/android/app/servertransaction/TransactionExecutor.java
@VisibleForTesting
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
if (callbacks == null || callbacks.isEmpty()) {
// No callbacks to execute, return early.
return;
}
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callbacks in transaction");
final IBinder token = transaction.getActivityToken(); // 1
ActivityClientRecord r = mTransactionHandler.getActivityClient(token);
final ActivityLifecycleItem finalStateRequest = transaction.getLifecycleStateRequest();
final int finalState = finalStateRequest != null ? finalStateRequest.getTargetState()
: UNDEFINED;
final int lastCallbackRequestingState = lastCallbackRequestingState(transaction);
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
if (DEBUG_RESOLVER) Slog.d(TAG, tId(transaction) + "Resolving callback: " + item);
final int postExecutionState = item.getPostExecutionState();
if (item.shouldHaveDefinedPreExecutionState()) {
final int closestPreExecutionState = mHelper.getClosestPreExecutionState(r,
item.getPostExecutionState());
if (closestPreExecutionState != UNDEFINED) {
cycleToPath(r, closestPreExecutionState, transaction);
}
}
item.execute(mTransactionHandler, token, mPendingActions); // 2
item.postExecute(mTransactionHandler, token, mPendingActions);
if (r == null) {
// Launch activity request will create an activity record.
r = mTransactionHandler.getActivityClient(token);
}
if (postExecutionState != UNDEFINED && r != null) {
final boolean shouldExcludeLastTransition =
i == lastCallbackRequestingState && finalState == postExecutionState;
cycleToPath(r, postExecutionState, shouldExcludeLastTransition, transaction);
}
}
}
// frameworks/base/core/java/android/app/servertransaction/DestroyActivityItem.java
public class DestroyActivityItem extends ActivityLifecycleItem {
@Override
public void execute(ClientTransactionHandler client, ActivityClientRecord r,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");
client.handleDestroyActivity(r, mFinished, mConfigChanges,
false /* getNonConfigInstance */, "DestroyActivityItem");
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
}
注释 1 处通过 IBinder 类型的 token 来获取 ActivityClientRecord,ActivityClientRecord 用于描述应用进程中的 Activity:
// frameworks/base/core/java/android/app/ActivityThread.java
/** Activity client record, used for bookkeeping for the real {@link Activity} instance. */
public static final class ActivityClientRecord {
}
在 ActivityThread.performDestroyActivity 方法中调用 Instrumentation.callActivityOnDestroy 方法并传入 r.activity,继而调用 Activity.performDestroy() 方法。
// frameworks/base/core/java/android/app/ActivityThread.java
@Override
public void handleDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance,
String reason) {
performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason); // 1
...
}
/** Core implementation of activity destroy call. */
void performDestroyActivity(ActivityClientRecord r, boolean finishing,
int configChanges, boolean getNonConfigInstance, String reason) {
Class<? extends Activity> activityClass;
if (localLOGV) Slog.v(TAG, "Performing finish of " + r);
activityClass = r.activity.getClass();
r.activity.mConfigChangeFlags |= configChanges;
if (finishing) {
r.activity.mFinished = true;
}
performPauseActivityIfNeeded(r, "destroy");
if (!r.stopped) {
callActivityOnStop(r, false /* saveState */, "destroy");
}
...
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnDestroy(r.activity); // 2
...
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
r.setState(ON_DESTROY);
...
}
// frameworks/base/core/java/android/app/Instrumentation.java
public void callActivityOnDestroy(Activity activity) {
activity.performDestroy();
}
Activity 的销毁流程
前面用 SubActivity 替换了 TargetActivity 并通过了 ATMS 的校验,这样 ATMS 就只知道 SubActivity 的存在,那么 ATMS 是如何控制 TargetActivity 的生命周期的回调呢?接下来看 Activity.performLaunchActivity 方法:
// frameworks/base/core/java/android/app/Activity.java
final ArrayMap<IBinder, ActivityClientRecord> mActivities = new ArrayMap<>();
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent); // 1
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
appContext.getAttributionSource());
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
if (!mInstrumentation.onException(activity, e)) {
throw new RuntimeException(
"Unable to instantiate activity " + component
+ ": " + e.toString(), e);
}
}
try {
Application app = r.packageInfo.makeApplicationInner(false, mInstrumentation);
...
// updatePendingActivityConfiguration() reads from mActivities to update
// ActivityClientRecord which runs in a different thread. Protect modifications to
// mActivities to avoid race.
synchronized (mResourcesManager) {
mActivities.put(r.token, r); // 2
}
if (activity != null) {
...
appContext.setOuterContext(activity);
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.activityConfigCallback,
r.assistToken, r.shareableActivityToken);
...
}
r.setState(ON_CREATE);
} catch (SuperNotCalledException e) {
throw e;
} catch (Exception e) {
...
}
return activity;
}
注释 1 处根据 Activity 的类名用 ClassLoader 加载 Activity,接着调用 Activity.attach 方法,将 r.token 赋值给 Activity 的成员变量 mToken。在注释 2 处将 ActivityClientRecord 根据 r.token 保存在 mActivities 中,mActivities 类型为 ArrayMap<IBinder, ActivityClientRecord>。
// frameworks/base/core/java/android/app/Activity.java
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
IBinder shareableActivityToken) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(mWindowControllerCallback);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token; // 1
mAssistToken = assistToken;
mShareableActivityToken = shareableActivityToken;
mIdent = ident;
mApplication = application;
mIntent = intent;
mReferrer = referrer;
mComponent = intent.getComponent();
mActivityInfo = info;
mTitle = title;
mParent = parent;
mEmbeddedID = id;
mLastNonConfigurationInstances = lastNonConfigurationInstances;
if (voiceInteractor != null) {
if (lastNonConfigurationInstances != null) {
mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
} else {
mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
Looper.myLooper());
}
}
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
mWindow.setColorMode(info.colorMode);
mWindow.setPreferMinimalPostProcessing(
(info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);
getAutofillClientController().onActivityAttached(application);
setContentCaptureOptions(application.getContentCaptureOptions());
}
因此得出结论:ATMS 和 ActivityThread 之间的通信采用了 token 来对 Activity 进行标识,并且此后的 Activity 的生命周期处理也是根据 token 来对 Activity 进行标识的。
在上面的例子中,在 Activity 启动时用插件 TargetActivity 替换占位置的 SubActivity,这一过程在 Activity.performLaunchActivity 方法调用之前,因此 r.token 指向的是 TargetActivity,在 performLaunchActivity 方法中获取的就是代表 TargetActivity 的 ActivityClientRecord,可见,TargetActivity 是有生命周期的。
6.3 Hook Instrumentation 方案实现
Hook Instrumentation 实现要比 Hook IActivityManager 实现简单一些。Hook Instrumentation 实现同样也需要用到占位的 Activity,与 Hook IActivityManager 实现不同的是,用占位 Activity 替换插件 Activity 以及还原插件 Activity 的地方不同。Activity.startActivity 方法调用时许图如下所示:
在 Activity 通过 ATMS 校验之前,会调用 Activity 的 startActivityForResult 方法:
// frameworks/base/core/java/android/app/Activity.java
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); // 1
if (ar != null) {
mMainThread.sendActivityResult(
mToken, mEmbeddedID, requestCode, ar.getResultCode(),
ar.getResultData());
}
if (requestCode >= 0) {
mStartedActivity = true;
}
cancelInputsAndStartExitTransition(options);
} else {
if (options != null) {
mParent.startActivityFromChild(this, intent, requestCode, options);
} else {
mParent.startActivityFromChild(this, intent, requestCode);
}
}
}
在注释 1 处调用了 Instrumentation.execStartActivity 方法来激活 Activity 的生命周期。
对于 ActivityThread.performLaunchActivity 方法,如下所示:
// frameworks/base/core/java/android/app/ActivityThread.java
/** Core implementation of activity launch. */
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
...
// 创建要启动的 Activity 的上下文环境
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 用类加载器来创建 Activity 的实例
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent); // 1
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess(isProtectedComponent(r.activityInfo),
appContext.getAttributionSource());
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
...
}
...
return activity;
}
注释 1 处调用了 Instrumentation.newActivity 方法,其内部会用类加载器来创建 Activity 的实例。看到这里我们可以得到方案,就是在 Instrumentation.execStartActivity 方法中使用占位 SubActivity 来通过 ATMS 的验证,在 Instrumentation.newActivity 方法中还原 TargetActivity,这两步操作都和 Instrumentation 有关,因此我们可以用自定义的 Instrumentation 来替换掉 mInstrumentation。首先我们自定义一个 Instrumentation,在 execStartActivity 方法中将启动的 TargetActivity 替换为 SubActivity,如下所示:
public class InstrumentationProxy extends Instrumentation {
private Instrumentation mInstrumentation;
private PackageManager mPackageManager;
public InstrumentationProxy(Instrumentation instrumentation,
PackageManager packageManager) {
mInstrumentation = instrumentation;
mPackageManager = packageManager;
}
public ActivityResult execStartActivity(Context who, IBinder contextThread,
IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
List<ResolveInfo> infos = mPackageManager.
queryIntentActivities(intent, PackageManager.MATCH_ALL);
if (infos == null || infos.size() == 0) {
intent.putExtra(HookHelper.TARGET_INTENT,
intent.getComponent().getClassName()); // 1
intent.setClassName(who, "com.example.myapplication.SubActivity"); // 2
}
try {
Method execMethod =
Instrumentation.class.getDeclaredMethod("execStartActivity",
Context.class, IBinder.class, IBinder.class, Activity.class,
Intent.class, int.class, Bundle.class);
return (ActivityResult) execMethod.invoke(mInstrumentation, who, contextThread,
token, target, intent, requestCode, options);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
首先查找要启动的 Activity 是否已经在 AndroidManifest.xml 中注册了,如果没有注册就在注释 1 处将要启动的 Activity(TargetActivity)的 ClassName 保存起来用于后面还原 TargetActivity,接着在注释 2 处替换要启动的 Activity 为 SubActivity,最后通过反射调用 execStartActivity 方法,这要就可以用 SubActivity 通过 ATMS 的验证。在 InstrumentationProxy 的 newActivity 方法中还原 TargetActivity,如下所示:
// InstrumentationProxy
@Override
public Activity newActivity(ClassLoader cl, String className, Intent intent) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
String intentName = intent.getStringExtra(HookHelper.TARGET_INTENT);
if (!TextUtils.isEmpty(intentName)) {
return super.newActivity(cl, intentName, intent);
}
return super.newActivity(cl, className, intent);
}
在 newActivity 方法中创建了此前保存的 TargetActivity,完成了还原 TargetActivity。编写 hookInstrumentation 方法,用 InstrumentationProxy 替换 mInstrumentation:
public class App extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
hookInstrumentation();
}
private void hookInstrumentation() {
try {
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field activityThreadField =
activityThreadClass.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
// 1 获取 ActivityThread 对象 sCurrentActivityThread
Object activityThread = activityThreadField.get(null);
Field instrumentationField =
activityThreadClass.getDeclaredField("mInstrumentation");
instrumentationField.setAccessible(true);
// 2 从 sCurrentActivityThread 中获取成员变量 mInstrumentation
Instrumentation instrumentation =
(Instrumentation) instrumentationField.get(activityThread);
// 创建代理对象 InstrumentationProxy
InstrumentationProxy proxy =
new InstrumentationProxy(instrumentation, getPackageManager());
// 3 将 sCurrentActivityThread 中成员变量 mInstrumentation 替换成代理类 InstrumentationProxy
instrumentationField.set(activityThread, proxy);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
}
在注释 1 处获取 ActivityThread 对象 sCurrentActivityThread。在注释 2 处获取 ActivityThread 类中的 mInstrumentation 字段,最后用 InstrumentationProxy 来替换 mInstrumentation。在 MyApplication 的 attachBaseContext 方法中调用 hookInstrumentation 方法,运行程序后,单击“启动插件”按钮时,发现启动的是插件 TargetActivity。
参考
Hook技术
Android Hook技术分析
黑科技之 - hook 欺骗 startActivity 清单文件校验规则
Hook技术–Activity的启动过程的拦截
Android中插件化实现的原理,hook Activity(二)