Android插件化是一种开发模式,它允许我们动态地加载和卸载APK,从而实现模块化开发,热更新等功能。
一、Android插件化原理
类加载与双亲委托机制
Android插件化的核心原理是基于Java的类加载机制。在Java中,ClassLoader负责加载类。每个ClassLoader都有一个父ClassLoader,当我们尝试加载一个类时,ClassLoader会首先尝试让它的父ClassLoader加载这个类,这就是所谓的双亲委托机制。这种机制可以确保同一个类只会被加载一次。
在Android插件化中,我们可以创建一个新的ClassLoader来加载插件中的类。这个ClassLoader的父ClassLoader是宿主应用的ClassLoader。
注意:这里说的父ClassLoader,不是一个一般类的父类。特指DexClassLoader的父类是BootClassLoader。
下面我们通过代码来加深对这三个基本的类加载器的理解
实验环境android-8.0.0
public void testClassLoader(){
ClassLoader pathClassLoader = MainActivity.class.getClassLoader();
Log.d("xz_aliyun",pathClassLoader.toString());
ClassLoader bootClassLoader = String.class.getClassLoader();
Log.d("xz_aliyun",bootClassLoader.toString());
}
getClassLoader方法会获取当前类的加载器
运行结果
通过运行结果,我们可以知道MainActvity的加载器是PathClassLoader,String类的加载器是BootClassLoader
这说明PathClassLoader加载已安装的APK类,而BootClassLoader加载系统核心的类。
public void testClassLoader(){
ClassLoader classLoader = MainActivity.class.getClassLoader();
Log.d("xz_aliyun",classLoader.toString());
ClassLoader parent = classLoader.getParent();
while(parent != null){
Log.d("xz_aliyun","parent->"+parent.toString());
parent = parent.getParent();
}
}
这个循环会不断地获取父类加载器,并打印其信息,直到没有父类加载器为止。在每次循环中,先打印当前的父类加载器信息,然后继续获取它的父类加载器,从而实现沿着类加载器层次结构向上遍历。
运行结果
由DexClassLoader的构造函数可以知道,要实例化DexClassLoader类需要四个参数分别为dexPath,optimizedDirectory,librarySearchPath,parent
String dexPath 指定了要加载的类所在的 dex 文件的位置
String optimizedDirectory 用于指定优化后的 dex 文件的输出目录路径
String librarySearchPath 指定要搜索的本地库的路径
ClassLoader parent 指定 DexClassLoader 的父类加载器
接下类我们用具体代码实现对DexClassLoader对象的创建方法
public void testClassLoader(Context context,String dexfilepath){
File optfile = context.getDir("opt_dex", Context.MODE_PRIVATE);
File libfile = context.getDir("lib_path", Context.MODE_PRIVATE);
DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath, optfile.getAbsolutePath(), libfile.getAbsolutePath(), MainActivity.class.getClassLoader());
}
双亲委派机制
简单来说,在安卓中,双亲委派机制就是当要加载一个类时,先让父类加载器去尝试加载。如果父类加载器找不到这个类,子类加载器才会去加载。就像你找东西,先问家长有没有,家长没有你再自己去找。这样可以保证核心类优先被系统的类加载器加载,避免重复加载和混乱,让安卓系统更稳定安全。
下面来验证一下双亲委派机制
public void testParentDelegationMechanism(){
ClassLoader classloader = MainActivity.class.getClassLoader();
try {
Class StringClass = classloader.loadClass("java.lang.String");
Log.d("xz_aliyun","load StringClass success!"+classloader.toString());
} catch (ClassNotFoundException e) {
e.printStackTrace();
Log.d("xz_aliyun","load StringClass fail!"+classloader.toString());
}
}
我尝试使用MainActivity的类加载器(即PathClassLoader)去加载java.lang.String类,如果加载成功则打印成功信息及类加载器信息,否则打印失败信息及类加载器信息,以此测试双亲委派机制下该类加载器对核心类的加载情况。
运行结果
所以,我们使用PathClassLoader去加载String.class类,还是可以加载成功,这是因为在双亲委派机制下,当类加载器收到一个类加载请求时,它首先会把这个请求委派给它的父类加载器去尝试加载。而PathClassLoader会交给BootClassLoader去加载。
资源加载
在Android中,资源文件是通过Resources对象来加载的。每个APK都有一个独立的Resources对象,用于加载它自己的资源文件。
在Android插件化中,我们可以创建一个新的Resources对象来加载插件中的资源文件。这个Resources对象的AssetManager是通过反射创建的,可以加载任意路径的APK文件。
四大组件支持
在Android插件化中,四大组件(Activity,Service,BroadcastReceiver,ContentProvider)的支持是一个重要的问题。因为在Android系统中,这些组件都需要在AndroidManifest.xml中进行声明,而插件APK的AndroidManifest.xml是不会被解析的,所以我们需要采取一些特殊的手段来支持这些组件。
Activity:Activity是最常用的组件,也是最需要支持的组件。在插件化中,我们可以通过Proxy Activity代理或者Hook方式来支持Activity。
Service:Service的支持也是通过代理或者Hook方式来实现的。我们可以在宿主中预先声明一些代理Service,然后在运行时将这些代理Service替换为插件中的Service。
BroadcastReceiver:BroadcastReceiver的支持相对比较简单,我们可以在运行时动态注册BroadcastReceiver,或者通过Hook方式来支持静态注册的BroadcastReceiver。
ContentProvider:ContentProvider的支持是最复杂的,因为ContentProvider在应用启动时就会被创建,而且每个ContentProvider都需要一个唯一的authority。我们可以通过Hook方式来支持ContentProvider,或者使用一些特殊的技巧,例如使用同一个authority来支持多个ContentProvider。
二、Android插件化方案
目前,市面上有几种主流的Android插件化方案,包括Shadow,RePlugin,AAB(Android App Bundle),Qigsaw,以及Atlas。
Shadow
Shadow是腾讯开发的一款插件化框架,其设计目标是不修改APK文件的情况下实现插件化。Shadow通过创建一个新的ClassLoader来加载插件,从而实现插件的动态加载和卸载。
- Shadow所指的插件是插件的代码完全是一个正常可安装的App代码,无需引用任何Shadow的库。这样的App代码应用了Shadow之后可以免安装运行在另一个App中。
- Shadow是一个完全无Hack,甚至零反射实现的Android插件框架。
- Shadow是一个全动态实现的插件框架,就是说插件框架的代码跟插件的代码一样都是动态发布的。那么什么是全动态插件框架架构?我们和非动态插件框架架构进行对比看看。
非动态插件框架架构
全动态插件框架架构图
优点:不修改APK,插件之间完全隔离,动态加载和卸载插件,对Android API的支持非常全面。
缺点:实现复杂,文档相对较少,可能会遇到一些特殊的问题,需要进行更多的测试来确保稳定性。
选择建议:如果你需要一个强大且灵活的插件化框架,并且不介意花费更多的时间来理解和使用,那么Shadow可能是一个不错的选择。
RePlugin
RePlugin是360公司开发的一款插件化框架,它通过修改ClassLoader,实现了插件的动态加载和卸载。
优点:插件化和组件化并行,无需修改已有代码,插件间通信机制完善。
缺点:需要对插件进行特殊的打包处理,对于一些复杂的场景,可能需要进行较大的改动,对新的Android版本的支持可能会有延迟。
选择建议:如果需要一个稳定且成熟的插件化框架,并且希望能够快速地进行插件化开发,那么建议选择RePlugin。
AAB (Android App Bundle)
AAB是Google官方提供的一种新的应用发布格式,它允许你将应用的功能模块化,并在需要时动态地下载和安装这些模块。
优点:Google官方支持,与Android系统和Google Play商店的集成度高,可以实现按需下载和安装模块。
缺点:只支持Android 5.0及以上版本的设备,需要通过Google Play商店进行模块的下载和安装。
选择建议:如果应用主要针对的是有Google Play服务的设备或地区,并且希望能够减小应用的初始下载大小,那么建议选择AAB。
Qigsaw
Qigsaw是爱奇艺开发的一款插件化框架,它通过修改ClassLoader,实现了插件的动态加载和卸载。
优点:支持动态加载和卸载插件,支持热更新,支持分包。
缺点:实现相对复杂,需要对插件进行特殊的打包处理,对新的Android版本的支持可能会有延迟。
选择建议:如果需要一个支持热更新和分包的插件化框架,并且愿意花费更多的时间来理解和使用,那么建议选择Qigsaw。
Atlas
Atlas是阿里巴巴开发的一款插件化框架,它通过修改ClassLoader,实现了插件的动态加载和卸载。官方的介绍文章可以阅读Atlas-手淘组件化框架的前世今生和未来的路。
优点:功能强大,支持热更新,可以动态加载和卸载插件,支持插件之间的相互调用,有丰富的文档和社区支持。
缺点:实现复杂,可能需要修改大量的代码才能实现插件化。
选择建议:目前Atlas开源项目处于没有维护的状态,只能作为技术方案参考。
三、插件增量更新
插件维护阶段,大的需求变更较少,很多时候更新插件版本只是为了解决一些用户反馈的小问题。但即使是很小的更新,都需要插件发布新版本,用户更新-下载-安装这些步骤中,都会造成用户的流失。那有没有可能对插件进行热更新呢?只需要下发小的补丁文件即可以达到修复的能力确实更适合我们业务场景。目前比较流行的热更新方案有下面这些。
Sophix
Sophix是阿里巴巴推出的一款热修复方案。Sophix的基本原理是通过AndFix实现的,AndFix是一种在Android Dalvik/ART环境下的热补丁框架,它可以在不需要重启APP的情况下动态修复Class。
优点
支持全量更新,增量更新,热更新,支持So库修复。
提供了详细的接入文档和稳定的服务支持。
有较好的兼容性和稳定性。
缺点
只支持阿里系的应用,对外部应用支持不足。
需要接入阿里的mPaaS移动开发平台,对于一些不希望接入阿里云的项目可能会有影响。
Bugly hotfix
Bugly hotfix是腾讯推出的一款热修复方案。Bugly hotfix的基本原理是通过dex文件替换实现的,它会在运行时替换掉有问题的dex文件,从而实现热修复。
优点
提供了全面的热更新解决方案,包括热修复和热更新。
提供了详细的接入文档和稳定的服务支持。
有较好的兼容性和稳定性。
缺点
需要接入腾讯的Bugly平台,对于一些不希望接入腾讯云的项目可能会有影响。
对于一些复杂的修复场景,可能需要更深入的定制,这可能会增加接入的复杂性。
插桩热更新
插桩热更新是一种基于字节码插桩技术的热更新方案。插桩热更新的基本原理是在编译阶段修改字节码,为方法调用插入额外的逻辑,从而实现在运行时替换方法的功能。
优点
原理简单,实现相对容易。
不依赖于任何第三方平台,可以自由定制。
缺点
需要对字节码插桩技术有一定的了解,对于一些开发者来说可能会有一定的学习成本。
对于一些复杂的修复场景,可能需要更深入的定制,这可能会增加接入的复杂性。
兼容性和稳定性可能不如Sophix和Bugly hotfix。
四、Android插件化实现原理
做到插件化需实现以下要点:
- 反射并执行插件 Apk 中的代码(ClassLoader Injection)
- 让系统能调用插件 Apk 中的组件(Runtime Container)
- 正确识别插件 Apk 中的资源(Resource Injection)
两种支持Activity的方式
所有的插件框架在解决的问题都不是如何动态加载类,而是动态加载的Activity没有在AndroidManifest中注册,该如何能正常运行。如果Android系统没有AndroidManifest的限制,那么所有插件框架都没有存在的必要了。因为Java语言本身就支持动态更新实现的能力。
Proxy Activity代理:这种方式是通过在宿主应用中预先声明一些代理Activity,然后在运行时将这些代理Activity替换为插件中的Activity。这种方式的优点是实现简单,但是有一些限制,例如无法支持插件中的Activity在AndroidManifest.xml中声明的一些属性。
Hook方式:这种方式是通过Hook AMS(Activity Manager Service),替换AMS中的一些方法,从而绕过AMS的检查。这种方式的优点是可以完全支持插件中的Activity,但是实现复杂,需要对Android系统有深入的了解。
Proxy Activity代理方式
ClassLoader Injection
简单来说,插件化场景下,会存在同一进程中多个 ClassLoader 的场景:
宿主 ClassLoader:宿主是安装应用,运行即自动创建
插件 ClassLoader:使用 new DexClassLoader 创建
我们称这个过程叫做 ClassLoader 注入。完成注入后,所有来自宿主的类使用宿主的 ClassLoader 进行加载,所有来自插件 Apk 的类使用插件 ClassLoader 进行加载,而由于 ClassLoader 的双亲委派机制,实际上系统类会不受 ClassLoader 的类隔离机制所影响,这样宿主 Apk 就可以在宿主进程中使用来自于插件的组件类了。
Runtime Container
上面说到只要做到 ClassLoader 注入后,就可以在宿主进程中使用插件 Apk 中的类,但是我们都知道 Android 组件都是由系统调用启动的,未安装的 Apk 中的组件,是未注册到 AMS 和 PMS 的,就好比你直接使用 startActivity 启动一个插件 Apk 中的组件,系统会告诉你无法找到。
我们的解决方案很简单,即运行时容器技术,简单来说就是在宿主 Apk 中预埋一些空的 Android 组件,以 Activity 为例,我预置一个 ContainerActivity extends Activity 在宿主中,并且在 AndroidManifest.xml 中注册它。
它要做的事情很简单,就是帮助我们作为插件 Activity 的容器,它从 Intent 接受几个参数,分别是插件的不同信息,如:
pluginName
pluginApkPath
pluginActivityName
等,其实最重要的就是 pluginApkPath 和 pluginActivityName,当 ContainerActivity 启动时,我们就加载插件的 ClassLoader、Resource,并反射 pluginActivityName 对应的 Activity 类。当完成加载后,ContainerActivity 要做两件事:
- 转发所有来自系统的生命周期回调至插件 Activity
- 接受 Activity 方法的系统调用,并转发回系统
Resource Injection
最后要说的是资源注入,其实这一点相当重要,Android 应用的开发其实崇尚的是逻辑与资源分离的理念,所有资源(layout、values 等)都会被打包到 Apk 中,然后生成一个对应的 R 类,其中包含对所有资源的引用 id。
资源的注入并不容易,好在 Android 系统给我们留了一条后路,最重要的是这两个接口:
PackageManager#getPackageArchiveInfo:根据 Apk 路径解析一个未安装的 Apk 的 PackageInfo
PackageManager#getResourcesForApplication:根据 ApplicationInfo 创建一个 Resources 实例
我们要做的就是在上面 ContainerActivity#onCreate 中加载插件 Apk 的时候,用这两个方法创建出来一份插件资源实例。具体来说就是先用 PackageManager#getPackageArchiveInfo 拿到插件 Apk 的 PackageInfo,有了 PacakgeInfo 之后我们就可以自己组装一份ApplicationInfo,然后通过 PackageManager#getResourcesForApplication 来创建资源实例。
Hook方式
先来了解一下Hook的概念,Hook 中文意思就是 钩子。简单说,它的作用就是改变代码的正常执行流程
比如对象A与对象B互相调用,我们加入一个钩子,这时候A调用B的时候 ,必须要经过Hook层,通过Hook再调用B,同理,B调用A也是一样。就像上面提到的,我们通过Hook去修改AMS中的Activity,验证完成后又通过Hook修改要启动的Activity。这样思路就出来了。
Hook实现方式
那么,通过什么技术来实现Hook呢?有两种方式:
- 反射
- 动态代理
查找Hook点的原则: - 尽量静态变量或者单例对象。
- 尽量 Hook public 的对象和方法。
寻找Hook点
有了思路,下面就开始从源码来寻找Hook点。首先来看Instrumentation的跳转方法
// android.app.Instrumentation
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);
// 调用AMS来启动Activity
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;
}
可以看到最终调用了ActivityManager.getService()来启动Activity,它返回的是一个什么对象呢?继续往下看:
// android.app.ActivityManager
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;
}
};
// android.util.Singleton
public abstract class Singleton<T> {
private T mInstance;
public Singleton() {
}
protected abstract T create();
public T get() {
synchronized(this) {
if (this.mInstance == null) {
this.mInstance = this.create();
}
return this.mInstance;
}
}
}
可以看到,是调用了Singleton的get()方法,返回了一个IActivityManager,那么我们就可以从这里下手,Hook住IActiviyManager的startActivity方法(简单来说就是替换掉Singleton的mInstanse实例)。
代码实现
代理对象
因为要替换IActivityManager中的startActivity方法,所以就代理这个类:
首先获取到IActivityManager的class对象,再通过Proxy.newProxyInstance创建一个代理对象,其实这里就返回了一个新的IActivityManager。可以看到有3个参数:
传一个ClassLoader对象,这里直接使用当前线程的ClassLoader就可以了
要代理的类,这里是一个 数组,我们只需要代理IActivityManager的类就可以了
回调函数,每执行要代理的类中的方法的时候,都会走到这个回调函数,所以我们只需要判断method.getName()如果是startActivity的话,就修改它的参数
// 获取IActivityManager的Class
Class<?> iActivityManagerClass = Class.forName("android.app.IActivityManager");
// 代理IActivityManager
Object newInstance = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{iActivityManagerClass},
new InvocationHandler() {
// 没执行一个方法,都会调用到这里
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e("kangf", "old args === " + args.length);
// 如果是startActivity方法的话
if (method.getName().equals("startActivity")) {
// startActivity方法有多个参数
// 这里是索引,记录Intent参数的索引
int index = 0;
// 遍历所有的参数,获取Intent参数的索引
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 重新创建一个Intent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.kangf.dynamic",
"com.kangf.dynamic.ProxyActivity");
// 并将原来的intent记录下来
proxyIntent.putExtra("oldIntent", (Intent) args[index]);
// 给Intent重新赋值,让它变成我们的代理Activity,这样验证就通过了
args[index] = proxyIntent;
}
Log.e("kangf", "change args === " + args.length);
// 在这里还需要继续执行这个方法
return method.invoke(mInstance, args);
}
});
注意,最后执行方法的时候,有个mInstance对象,说明执行的是mInstance对象里面的方法,为什么这么写呢?
偷天换日,替换原来的Intent
上面我们已经分析了,要替换的是Singleton对象里面的实例,所以这个mInstance其实就是IActivityManager实例,那么继续:
// 获取ActivityManager的Class
Class<?> activityManagerClass = Class.forName("android.app.ActivityManager");
// 根据Class获取IActivityManagerSingleton私有字段
Field iActivityManagerSingletonField = activityManagerClass.getDeclaredField("IActivityManagerSingleton");
iActivityManagerSingletonField.setAccessible(true);
// 根据iActivityManagerSingletonField字段获取Singleton对象
Object singleTon = iActivityManagerSingletonField.get(null);
Class<?> singleTonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singleTonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
// 获取到真正的IActivityManger的实例对象
final Object mInstance = mInstanceField.get(singleTon);
这样就获取到了IActivityManger的实例对象。
ActivityThread
上一步是在AMS检测之前,将原来的Intent替换掉了,让它检测宿主中的Activity,可我们要做的是跳转到插件,这样我们就需要在AMS检测完成之后,再讲Intent替换插件的intent。
在AMS检测完成之后,会走到ActivityStackSupervisor,这个类中的realStartActivityLocked方法。先来看一下API 28的启动时序图:
// com.android.server.am.ActivityStackSupervisor.java
final boolean realStartActivityLocked(ActivityRecord r, ProcessRecord app,
boolean andResume, boolean checkConfig) throws RemoteException {
// 省略不重要的代码 ...
try {
// 省略不重要的代码 ...
try {
// 省略不重要的代码 ...
// Create activity launch transaction.
final ClientTransaction clientTransaction = ClientTransaction.obtain(app.thread,
r.appToken);
clientTransaction.addCallback(LaunchActivityItem.obtain(new Intent(r.intent),
System.identityHashCode(r), r.info,
// TODO: Have this take the merged configuration instead of separate global
// and override configs.
mergedConfiguration.getGlobalConfiguration(),
mergedConfiguration.getOverrideConfiguration(), r.compat,
r.launchedFromPackage, task.voiceInteractor, app.repProcState, r.icicle,
r.persistentState, results, newIntents, mService.isNextTransitionForward(),
profilerInfo));
// Set desired final state.
final ActivityLifecycleItem lifecycleItem;
if (andResume) {
lifecycleItem = ResumeActivityItem.obtain(mService.isNextTransitionForward());
} else {
lifecycleItem = PauseActivityItem.obtain();
}
clientTransaction.setLifecycleStateRequest(lifecycleItem);
// Schedule transaction.
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
// ...
} catch (RemoteException e) {
// ...
throw e;
}
} finally {
endDeferResume();
}
// ...
return true;
}
代码片段太长,把与我们用到的无关的代码省略掉了,有兴趣的可以自己查看源码:com.android.server.am.ActivityStackSupervisor.java
需要注意的是,ClientTransaction加入了一个callback: LaunchActivityItem,记住,这里是重点!
可以看到,最终走到了
mService.getLifecycleManager().scheduleTransaction(clientTransaction);
继续跟踪到ClientLifecycleManager.java的scheduleTransaction方法:
// ClientLifecycleManager.java
void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
final IApplicationThread client = transaction.getClient();
transaction.schedule();
if (!(client instanceof Binder)) {
transaction.recycle();
}
}
发现调用了ClientTransaction的schedule()方法:
// ClientTransaction.java
public void schedule() throws RemoteException {
mClient.scheduleTransaction(this);
}
mClient是IApplicationThread,调用了它的scheduleTransaction,这个IApplicationThread是不是很熟悉?没错,其实就是ActivityThread,继续往下看:
// ActivityThread.java
@Override
public void scheduleTransaction(ClientTransaction transaction) throws RemoteException {
ActivityThread.this.scheduleTransaction(transaction);
}
// ClientTransactionHandler
void scheduleTransaction(ClientTransaction transaction) {
transaction.preExecute(this);
sendMessage(ActivityThread.H.EXECUTE_TRANSACTION, transaction);
}
发送了一个消息:ActivityThread.H.EXECUTE_TRANSACTION,这个消息在ActivityThread中接收:
public static final int EXECUTE_TRANSACTION = 159;
public void handleMessage(Message msg) {
if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
switch (msg.what) {
// ...
case EXECUTE_TRANSACTION:
final ClientTransaction transaction = (ClientTransaction) msg.obj;
mTransactionExecutor.execute(transaction);
if (isSystem()) {
transaction.recycle();
}
break;
// ...
}
// ...
}
现在回到了主线程 ,它又调用了mTransactionExecutor的excute方法,并传入了一个transaction:
// TransactionExecutor
public void execute(ClientTransaction transaction) {
final IBinder token = transaction.getActivityToken();
log("Start resolving transaction for client: " + mTransactionHandler + ", token: " + token);
executeCallbacks(transaction);
executeLifecycleState(transaction);
mPendingActions.clear();
log("End resolving transaction");
}
public void executeCallbacks(ClientTransaction transaction) {
final List<ClientTransactionItem> callbacks = transaction.getCallbacks();
// 。。。
final int size = callbacks.size();
for (int i = 0; i < size; ++i) {
final ClientTransactionItem item = callbacks.get(i);
// 。。。
item.execute(mTransactionHandler, token, mPendingActions);
item.postExecute(mTransactionHandler, token, mPendingActions);
// 。。。
}
}
最后调用了item.execute方法,这个item是ClientTransactionItem对象,ClientTransactionItem又是从callbacks集合中获取的,那这个callbacks又是什么呢?从上面可以看到,是transaction里面拿到的!还记得上面的addCallback吗?其实就是调用了LaunchActivityItem中的execute方法!
@Override
public void execute(ClientTransactionHandler client, IBinder token,
PendingTransactionActions pendingActions) {
Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityStart");
ActivityClientRecord r = new ActivityClientRecord(token, mIntent, mIdent, mInfo,
mOverrideConfig, mCompatInfo, mReferrer, mVoiceInteractor, mState, mPersistentState,
mPendingResults, mPendingNewIntents, mIsForward,
mProfilerInfo, client);
// 调用了handleLaunchActivity
client.handleLaunchActivity(r, pendingActions, null /* customIntent */);
Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
}
到这里一目了然了, 跳转Activity时构造了一个ActivityClientRecord对象,这个对象里面传入了mIntent, 这是个私有的成员变量,是不是思路就出来了?我们只需要拿到这个intent并修改它就可以了!
既然时个私有变量,那Hook的时候,就需要得到LaunchActivityItem的实例对象,怎么获得呢?首先要知道LaunchActivityItem时怎么来的?我们还是从上面的addCallback方法入手, 继续往上跟源码:
// ClientTransaction.java
public void addCallback(ClientTransactionItem activityCallback) {
if (mActivityCallbacks == null) {
mActivityCallbacks = new ArrayList<>();
}
mActivityCallbacks.add(activityCallback);
}
到这里发现其实是把LaunchActivityItem放进了mActivityCallbacks集合里面,那么我们只需要获取到这个集合,遍历如果是LaunchActivityItem就可以了,ClientTransaction是什么就不用多说了吧?不就是ActvityThread中 msg接收的对象吗!
有了思路,接下来就正式撸码~
Hook Handler
还有一步,首先要拦截到ActivityThread中的handleMessage方法,接收到159(public static final int EXECUTE_TRANSACTION = 159;)这个消息才进行替换intent的操作。那我们先来看一下Handler:
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
// .....
}
public void handleMessage(Message msg) {
}
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
这里不难理解,接收消息首先会走dispatchMessage方法,如果callback不为空,就会走mCallback.handleMessage,并返回一个布尔值,如果返回true,那么拦截消息,否则继续走handleMessage方法。这样,我们就可以Hook住ActivityThread中的Handler,给它加上一个callback!
ActivityThread中的Handler怎么拿到呢?
final H mH = new H();
private static volatile ActivityThread sCurrentActivityThread;
H继承自Handler,它直接在全局定义了一个Handler,还有它本身的一个实例,这样拿到它就简单很多了~接下来看一下完整的hook代码!
将代理的intent替换回来
public static void hookHandler() {
try {
// 获取ActivityThread类
final Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
// 拿到ActivityThread对象
Field activityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
final Object activityThread = activityThreadField.get(null);
// 通过activityThread对象获取Handler实例
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Object mH = mHField.get(activityThread);
Class<?> handlerClass = Class.forName("android.os.Handler");
Field mCallbackField = handlerClass.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
// 给它的handler设置一个callback,并返回false,这里要让它继续往下走 ,不然就拦截掉了
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Log.e("kangf", "handling code = " + msg.what);
switch (msg.what) {
// 1. 接收到159的消息
case 159:
try {
// 2. 获取mActivityCallbacks字段
Field mActivityCallbacksField = msg.obj.getClass().getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
// 3. 通过字段获取mActivityCallbacks集合
List<Object> mActivityCallbacks = (List<Object>) mActivityCallbacksField.get(msg.obj);
// 4. 遍历这个集合,如果是LaunchActivityItem的话,进行hook
for (int i = 0; i < mActivityCallbacks.size(); i++) {
Class<?> itemClass = mActivityCallbacks.get(i).getClass();
if (itemClass.getName().equals("android.app.servertransaction.LaunchActivityItem")) {
// 开始hook
// 5. 获取LaunchActivityItem中的intent字段
Field intentField = itemClass.getDeclaredField("mIntent");
intentField.setAccessible(true);
// 6. 根据字段获取到代理的intent
Intent proxyIntent = (Intent) intentField.get(mActivityCallbacks.get(i));
// 7. 拿到保存过的intent
Intent intent = proxyIntent.getParcelableExtra("oldIntent");
// 8. 把原来的替换回来
proxyIntent.setComponent(intent.getComponent());
break;
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
// 这里必须返回false
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
上面每一步都在注释里写得很清楚了,这样就完成了插件Activity的跳转
插件中的Activity这里先亮出来:
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// setContentView(R.layout.activity_main)
Toast.makeText(this, "插件的MainActivity", Toast.LENGTH_SHORT).show()
}
}
加载资源需单独适配,可以参考链接: 从零开始实现一个插件化框架
参考:https://blog.youkuaiyun.com/mba16c35/article/details/136565293
https://cloud.tencent.com/developer/article/1791592
https://blog.youkuaiyun.com/qq_22090073/article/details/103946596 从零开始实现一个插件化框架