在学习插件化之前需要看前面几篇文章:
动态加载技术:
在程序运行时,动态加载一些程序中原本不存在的可执行文件并运行起来,,随着应用技术的发展,动态加载技术逐渐派生出两个分支,热修复和插件化;
- 热修复:用于修复bug
- 插件化:解决应用庞大,功能模块解耦,复用其他apk的代码
插件化思想:
将复用的apk作为插件,插入另一个apk中,比如淘宝中会有咸鱼的页面,用淘宝为咸鱼引流,使用插件化技术,可以直接使用咸鱼apk中的dex文件,这样省去再次开发一套咸鱼页面的成本,并且有效的降低了淘宝apk的耦合度;
Activity插件化原理:
插件化activity的目的是直接使用另一个apk的activity,而activity的启动和生命周期的管理需要经过AMS的处理,另一个apk的activity没有在本项目的manifest注册,肯定是无法通过的,所以我们需要hook startActivity的流程,绕过ams的验证,可以在本项目使用一个占坑activity,在发送给ams前将插件activity换成占坑activity去通过ams的验证,验证好以后在真实的启动时再将插件activity换回来;
步骤:
- 事先在本项目准备好占坑activity
- 使用占坑activity绕过ams验证
- 还原插件activity
1. 准备占坑activity
直接在原项目准备一个空白的activity即可,记得必须在manifest注册,下文叫他SubActivity
2. 使用插件activity替换占坑activity
在交给ams进程验证之前,在用户进程会经过两个类的传递,Instrumentation, iActivityManager,者两个类都可以作为hook点,这里介绍hook iActivityManager的这种方法;
2.1 创建hook点的代理类,iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())){
// 首先找到,原本需要启动的插件activity的原始intent
Intent originIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
originIntent = (Intent) args[i];
index = i;
break;
}
}
// 新建欺骗ams的占坑activity的intent
Intent fakeIntent = new Intent();
fakeIntent.setClass("xxx.xxx.xxx",SubActivity.class);
// 将真实的intent保存在fakeIntent中用于第三步的还原操作
fakeIntent.putExtra("real_intent",originIntent);
// 将fakeIntent写回原来的arges数组中
args[index] = fakeIntent;
}
return method.invoke(realActivityManager,args);
}
}
这里使用的动态代理创建iActivityManager的代理,首先找到原本启动的插件Activity的Intent,然后新建一个启动SubActivity的intent替换它;
2.2替换原本的iActivityManager:
public void hookAMS() throws Exception {
// 获取ActivityManager getService 返回的单例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成动态代理对象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 将代理对象设置到单例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
- 这个方法需要在startActivity前调用
3. 还原插件Activity
绕开ams验证后,我们还需要真实的启动TargetActivity,再学习了Handler机制后,我们知道message的处理顺序是首先会判断当前message.callback有没有逻辑,会首先执行callback;我们可以将Message作为Hook点
3.1 创建自定义CallBack,在handleMessage处理前,将fakeIntent换成真实的intent
class MCallBack implements android.os.Handler.Callback {
@Override
public boolean handleMessage(Message msg) {
try {
Object activityClientRecord = msg.obj;
// 获取fakeIntent
Class acrClazz = activityClientRecord.getClass();
Field intentField = acrClazz.getDeclaredField("intent");
Intent intent = (Intent) intentField.get(activityClientRecord);
// 取出targetActivity的Intent
Intent realIntent = intent.getParcelableExtra("real_intent");
// 将realIntent的内容设置到fakeIntent
intent.setComponent(realIntent.getComponent());
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
msg.getTarget().handleMessage(msg);
return true;
}
}
3.2 hook ActivityThread, 修改主线程的H(Handler)的CallBack属性,原理参考dispatchMessage方法
private void hookActivityThread() throws Exception {
Class activityThreadClass = Class.forName("android.app.ActivityThread");
Field singleInstanceField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
Object activityThreadInstance = singleInstanceField.get(null);
Field mHField = activityThreadClass.getDeclaredField("mH");
Handler handler = (Handler) mHField.get(activityThreadInstance);
// 修改handler 的callback
Class handlerClazz = handler.getClass();
Field callbackField = handlerClazz.getDeclaredField("mCallback");
callbackField.set(handler,new MCallBack());
}
在Handler机制中有两个callback,一个是Handler.mCallback,一个是Message.callback
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
在loop中轮询处理message的时候会调用dispatchMessage;如果Message.callback不会null就处理Runnable的回调然后结束,如果msg.callback为null,则先执行Handler的mCallback,并根据Handler的mCallback.handleMessage的返回值判断是否执行Handler.handleMessage;根据上面的流程,我们可以在ActivityThread的H处理startActivity这个Message的handleMessage前,在H的Callback中插入修改intent的代码,做到真实的开启TargetActivity
3.3 插件Activity的生命周期管理:
上面的操作只做到了开启activity,插件activity的生命周期是如何管理的,AMS通过token来对activity进行识别管理,而插件activity token的绑定是不受影响的,所以插件activity是具有生命周期的;
Service插件化原理
代理分发实现:
当启动插件Service时,就会先启动代理Service,当代理Service运行后,在其onStartCommand中启动插件Service;
步骤:
- 项目中准备好代理Service
- hook iActivityManager 启动代理Service
- 代理分发:
- ProxyService需要长时间对插件Service进行分发,所以需要return START_STICKY ProxyService重新创建
- 创建插件Service,attach,onCreate;
1. 在项目中创建一个ProxyService,在manifest中注册;
2. hook iActivityManager,将要启动的TargetService换成ProxyService
2.1 创建自定义iActivityManagerProxy
public class IActivityManagerProxy implements InvocationHandler {
private Object realActivityManager;
public IActivityManagerProxy(Object realActivityManager) {
this.realActivityManager = realActivityManager;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startService".equals(method.getName())){
Intent targetIntent = null;
int index = 0;
for (int i = 0;i<args.length;i++){
if (args[i] instanceof Intent){
targetIntent = (Intent) args[i];
index = i;
break;
}
}
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.xx.xx","com.xx.xx.ProxyService");
proxyIntent.putExtra("target_intent",targetIntent);
args[index] = proxyIntent;
}
return method.invoke(realActivityManager,args);
}
}
2.2 hook AMS替换原来的IActivityManager 同上
public void hookAMS() throws Exception {
// 获取ActivityManager getService 返回的单例
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
// 通过单例.get()获取iActivityManager, 这两步需要参考源码的iActivityManager的获取
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton,null);
// 生成动态代理对象
Object proxyInstance = Proxy.newProxyInstance(
ActivityManagerClazz.getClassLoader(),
ActivityManagerClazz.getInterfaces(),
new IActivityManagerProxy(iActivityManager));
// 将代理对象设置到单例上
Field mInstanceField = singleClazz.getField("mInstance");
mInstanceField.set(IActivityManagerSingleton,proxyInstance);
}
只要在startService前调用这段代码,就会启动proxyService,下面我们在proxyService中对targetService进行分发;
2.3 在proxyService中启动TargetService:
- 调用attach绑定Context
- 调用onCreate
public class ProxyService extends Service {
@Nullable
@Override
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
try {
// 准备attach方法的参数
Class activityThreadClazz = null;
activityThreadClazz = Class.forName("android.app.ActivityThread");
Method getApplicationMethod = activityThreadClazz.getDeclaredMethod("getApplicationMethod");
Field sCurrentActivityThreadField = activityThreadClazz.getDeclaredField("sCurrentActivityThread");
sCurrentActivityThreadField.setAccessible(true);
// activityThread
Object activityThread = sCurrentActivityThreadField.get(null);
// applicationThread
Object applicationThread = getApplicationMethod.invoke(activityThread, null);
Class iInterFaceClazz = Class.forName("android.os.IInterface");
Method asBinderMethod = iInterFaceClazz.getDeclaredMethod("asBinder");
asBinderMethod.setAccessible(true);
// token
Object token = asBinderMethod.invoke(applicationThread);
// iActivityManager
Class ActivityManagerClazz = ActivityManager.class;
Field IActivityManagerSingletonField = ActivityManagerClazz.getDeclaredField("IActivityManagerSingleton");
Object IActivityManagerSingleton = IActivityManagerSingletonField.get(ActivityManagerClazz);
Class singleClazz = IActivityManagerSingleton.getClass();
Method getMethod = singleClazz.getDeclaredMethod("get");
Object iActivityManager = getMethod.invoke(IActivityManagerSingleton, null);
// targetService
Class serviceClazz = Class.forName("android.app.Service");
Service targetService = (Service) serviceClazz.newInstance();
// attach
Method attachMethod = serviceClazz.getDeclaredMethod("attach");
attachMethod.invoke(targetService, this,
activityThread, intent.getComponent().getClassName(),
token, getApplication(), iActivityManager);
targetService.onCreate();
} catch (Exception e) {
e.printStackTrace();
}
return START_STICKY;
}
}
- 首先准备attach需要的参数,通过反射获取
- 调用targetService的attach方法
- 调用targetService的onCreate方法
资源的插件化
参考这边换肤文章:安卓换肤实现原理
某东的插件化实践
1. 插件化做的事情:
插件化的目的就是在主工程使用插件工程的代码/类
和资源
1.1插件中四大组件的处理:
当使用插件的四大组件类时,比如插件activity的使用必须做特殊的处理,一般的处理方式有:
- hook AMS,绕过AMS对四大组件的验证,手动管理生命周期
- 直接在主工程的manifest注册插件的activity
方式1的特点是实现难度高,灵活性高,但是随着谷歌对于系统非sdk调用的限制,这个方式可能会在未来失效;
方式2的特点是实现简单,但是不够灵活,必须在manifest中写死
某东采用的方式2,直接在manifest中写死插件的四大组件注册
1.2插件中类的加载和使用:
每一个插件都设置一个ClassLoader,目的是整个插件的类都是由一个加载器加载,所有的插件的ClassLoader都在双亲委派中继承了另一个ClassLoader,这个目的是便于主工程的统一管理;
1.3DelegateClassLoader的替换:
替换LoadedAPK的ClassLoader为DelegateClassLoader即可;
1.4插件资源的引用
添加一个path给AssetManager就行,详见上文
2. 插件如何打包进主工程,即主工程如何集成插件包:
- 将插件apk放入asset目录,通过assetManager去加载
- 将插件apk文件修改后缀为.so 放入lib/armeabi目录,主工程apk在安装的时候会自动将这个目录的文件加载到data/data/< package_name >/lib/目录下,可以直接获取;
某东使用的第二种以so的形式放入lib目录自动加载,因为在运行时去使用AssetManager加载asset资源会影响程序的运行时速度
3. 插件和主工程如何通信
主要借鉴的airbnb的DeepLinkDispatch
DeepLinkDispatch类似Android原生的scheme协议,用于跳转到另一个APP的页面,比如在美团中打开高德地图,或者在微信中打开京东,
DeepLinkDispatch的实现思路:首先在插件工程中需要被打开的Activity添加注解,然后在主工程调用DeepLinkDispatch.startActivityDirect()
传入注解中设置好的参数,最后会通过系统的apiContext.startActivity()
开启页面
4. 插件化的未来
随着谷歌对于系统API的限制越来越严格,并且现在已经分成黑名单,深灰名单,浅灰名单留时间开发者调整,插件化应该是没有未来的,我们想想插件化到底是为了什么:
- 独立编译,提高开发效率
- 模块解耦,复用代码
东东的解决方案:
组件化:
传统的组件话是新建一个Android Library,在开发调试和实际引用的时候在Application和Library之间切换,东东的组件化是将每一个组件单独做成一个项目,然后在项目结构中保留Application和Android Library,library用于实现组件的功能,app用于开发调试,在主工程使用时直接通过依赖从云端的maven sync;
来源:oschina
链接:https://my.oschina.net/u/4400968/blog/4720739