动态加载、插件化、热部署、热修复(更新)知识汇总

阅读: 评论:0

动态加载插件化、热部署、热修复(更新)知识汇总
开发中经常能听到动态加载,插件化,热部署等词,动态加载到底是何⽅神物,它能实现什么功能,实现原理⼜如何?动态加载和插件化、热部署⼜有着什么样的联系呢?下⾯我们⼀起来学习吧。
1. 基本知识
1.1 动态加载
动态加载,是指在应⽤运⾏时,动态加载某个模块,达到新增或是改变某⼀部分功能/⾏为。
1.1.1 Java的动态加载
通过类加载器ClassLoader(⽤来加载类的)实现动态加载Jar⽂件,当⼀个Class被加载时,这个Class所引⽤的所有Class也会被加载。ClassLoader是双亲委派,即总是先请求⽗ClassLoader查⾃⾝,依次类推,不存在则⽤本类的类加载器加载。
1.1.2 Android中动态加载
由于Android的虚拟机(Dalvik VM)是不能识别Java打出jar的byte code,需要通过dx⼯具来优化转换成Da
lvik byte code才⾏。所以Android动态加载是指应⽤在运⾏的时候,使⽤类加载器加载相应的apk\dex\jar(必须含有dex⽂件),再过通反射获取apk\dex\jar内部资源(class、图⽚、color等),提供给宿主app使⽤。
Android动态加载的类型:so库, dex/jar/apk⽂件。
1.2 Android插件化
动态加载是插件化实现的基础,Android中可以将⼀个应⽤分成多个不同的部分,每⼀部分可看成⼀个插件,再利⽤动态加载技术实现插件化动态加载。
Android类加载器相关知识可以参考我前⾯写的⼀篇博⽂:
简单的Android动态加载实践可参考这篇⽂章,讲解了Android动态加载dex的实现:
1.3 热部署
是⼀个独⽴的apk, 也是程序运⾏动态加载,但加载完了后与宿主apk没有很⼤联系。
1.4 插件化和热部署的区别
独⽴运⾏的插件APK叫热部署:如在⽤户使⽤的时候才加载插件,插件⼀旦运⾏后,与主项⽬没有任何逻辑,只有在主项⽬启动插件时才触发⼀次调⽤插件的⾏为。
需要依赖主项⽬的环境运⾏的播件apk叫插件化:⼀启动项⽬就加载插件,主项⽬提供⼀个启动⼊⼝及从服务器下载最新插件更新逻辑。或是插件需要使⽤主项⽬中的功能时,如插件apk和主项⽬中都有ImageLoader,如果两个项⽬都引⼊,⽆疑造成代码冗余,所以我们可以把ImageLoader相关代码抽离成⼀个AndroidLibrary项⽬,主项⽬以Compile模式引⽤这个Library,⽽插件以Provider模式引⼊这个Library(编译出来的jar),这样丙者之间就能交互了。
1.5 热更新、热修复
上线的应⽤,如果发现紧急bug,⼜不想重机关报发版,可以通过服务器向⽤户发送修复补丁包,使⽤户不需要重新下载,安装,⽽修复取决于。
2. 动态加载的作⽤
利⽤动态加载技术,实现插件化动态加载,可以解决以下问题:
2.1 ⽅法数不得超过65535
问题:⼀个dex⽂件的⽅法数最⼤不得超过65535个,且android在加载dex时会对其进⾏优化成optDex⽂件,早期的optDex⽂件要求不能⼤于5M,后期提升到8M,早期的⼿机可能⽅法数没有超过65535限制,但超过了LinearAllocHdr的分配空间,也会导致安装失败问题。
解决办法:可利⽤插件化拆分多个dex并动态加载,从⽽解决android端代码⽅法数不得超过65535;
2.2 减⼩初始安装包的体积
问题:像淘宝⼀个apk可能包含多个第三⽅应⽤,如聚划算,天猫商城等,如果把所有第三⽅apk也打包进同⼀个apk包,会导致初始apk体积过⼤。⽤户可能在下载时会有顾虑。
解决办法:利⽤动态加载来实现模块加载,应⽤在运⾏时按需动态加载,减少apk包的体积;
2.3 动态更新
问题:应⽤上线后如果发些bug了,传统⽅法是必须重新打个修复包,⽤户需要再次升级更新,⽤户体验上很不好;
解决办法:将需要修复的代码打成⼀个插件,通过服务器下发,应⽤运⾏时,动态加载更新;
2.4 动态换肤
实现原理同2.3,这样⽤户可以实时在线更新⽪肤。
2.5 加快应⽤启动速度
问题:应⽤⽐较⼤时,被拆成多个dex打进初始apk包中,⾸次启动时速度很慢;
解决办法:利⽤动态加载技术,使⽤懒加载机制,在需要时才初始化,提⾼应⽤的启动速度;
2.6 降低耦合,提⾼开发速度
问题:⼤型项⽬,代码量⼀般⽐较⼤,所有代码全放在⼀个module⾥,编译速度慢,且复⽤低,耦合度⾼;
解决办法:分割插件模块,做到项⽬级别的代码分离,⼤⼤降低模块之间的耦合度,同⼀个项⽬能分割出不同模块在多个开发团队之间并⾏开发,若出现bug也容易定位问题;
2.6 过hook系统做⼀些想改变系统操作
3. 动态加载过程
1) 获取到要加载的插件(.so/apk/dex/jar),可直接copy或是从⽹络下载)并放在⼿机本地;
2) 加载可执⾏⽂件;
3) 调⽤具体的⽅法执⾏业务逻辑。
4. 动态加载插件apk⾥的类和资源问题
使⽤ClassLoader动态加载外部的dex⽂件⾮常简单,但它只能⽤来加载类,⽽插件中的apk⾥的资源、XML⽂件等却⽆法加载,同时由于⽆法更改本地的AndroidMainfest清单⽂件,所以⽆法启动新的Activity等组件。
4.1 ⼀种简单的解决办法
先把要⽤到的全部res资源都放到主APK⾥⾯,同时把所有需要的Activity先全部写进l⽂件⾥,只通过动态加载更新代码,不更新res资源,如果需要改动UI界⾯,可以通过使⽤纯Java代码创建布局的⽅式绕开XML布局。同时也可以使⽤Fragment代替Activity,这样可以最⼤限度得避开“⽆法注册新组件的限制”。
4.2 插件Activity⽣命周期管理
Android中动态加载技术虽然能把类加载进来,可是Activity\ Service等组件是有⽣命周期的,合⽤Clas
sLoader可以从插件中创建Activity对象,但是⽆法负责其⽣命周期。所以我们需要把加载进来的Activity等组件交给系统管理,让AMS赋予组件⽣命周期。同时组件必须在l中显⽰注册,否则会报错,⽽插件的组件并没有在宿主apk中注册。
⽹上有三种⽅法:
4.2.1 反射⽅法
通过Java的反射去获取Activity的各种⽣命周期⽅法,再在代理Activity中去调⽤插件对应的⽣命周期⽅法。这种⽅法⽐较复杂,且反射有⼀定的性能开销。
4.2.2 接⼝⽅式
将Activity的⽣命周期⽅法提取出来作为⼀个接⼝,再通过代理Activity去调⽤插件Activity的⽣命周期⽅法,这样就完成了插件Activity的⽣命周期管理。砭石祛斑泥
曼越橘具体要参考:
4.2.3 使⽤傀儡类
使⽤傀儡类Activity⽤于代理执⾏插件APK的Activity的⽣命周期。
(1) 实现⽅法:在宿主apk的l注册⼀个代理ProxyActivity,ProxyActivity是⼀个傀儡类,⾃⾝没有什么业务逻辑。让ProxyActivity进⼊AMS进程接受检验,再在适当时候替换成真正要启动的Activity。
(2) 实现思想: Activity的启动过程,通过IPC调⽤进⼊系统进程system_server,完成Activity管理及⼀些校验⼯作,最后回到App进程中完成真正的Activity对象的创建。
当app进程调⽤startActivity启动插件类PluginActivity时,在进⼊AMS进程的⼊⼝使⽤hook代理提前在启动插件apk时使⽤ProxyActivity去系统层校验,等校验完毕,会调⽤Handler的dispatchMessage⽅法,在此时换回真正的PluginActivity,从⽽达到使⽤傀儡类欺骗系统成功校验。
(3) 实现步骤:
1) 代理系统启动Activity的⽅法,将要启动的Activity替换成我们占坑的Activity,欺骗系统去检查的⽬的。需要拦截startActivity,系统启动Activity最终会调⽤:Default().startActivity
int result = Default()
.startActivity(whoThread, BasePackageName(), intent,
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
//⽽Default()的⽅法是:
static public IActivityManager getDefault() {
();
}
孕妇袜//gDefault是⼀个单例对象,Singleton是系统提供的单例辅助类,由于AMS需要频繁的与我们的应⽤通信,故采⽤单例把这个AMS的代理对象保存起来private static final Singleton<IActivityManager> gDefault = new Singleton<IActivityManager>() {
protected IActivityManager create() {
IBinder b = Service("activity");
if (false) {
Log.v("ActivityManager", "default service binder = " + b);
}
IActivityManager am = asInterface(b);
if (false) {
Log.v("ActivityManager", "default service = " + am);
}
return am;
}
};
我们可以通过hook这个单例,改变代理Default()的返回值,就可以实现AMS的代理对象
/**
* Hook AMS
* 主要完成的操作是  "把真正要启动的Activity临时替换为在l中声明的替⾝Activity"
* 进⽽骗过AMS
*/
public static void hookActivityManagerNative() Exception {汽车脚垫制造设备
//获取ActivityManagerNative的类
Class<?> activityManagerNativeClass = Class.forName("android.app.ActivityManagerNative");
l型匹配
//拿到gDefault字段
Field gDefaultField = DeclaredField("gDefault");
gDefaultField.setAccessible(true);
/
/从gDefault字段中取出这个对象的值
Object gDefault = (null);
// gDefault是⼀个 android.util.Singleton对象; 我们取出这个单例⾥⾯的字段
Class<?> singleton = Class.forName("android.util.Singleton");
//这个gDefault是⼀个Singleton类型的,我们需要从Singleton中再取出这个单例的AMS代理        Field mInstanceField = DeclaredField("mInstance");
mInstanceField.setAccessible(true);
//ams的代理对象
Object rawIActivityManager = (gDefault);
}
现在我们已经拿到了这个AMS的代理对象,现在需要创建⼀个⾃⼰的代理对象去拦截原AMS中的⽅法
class IActivityManagerHandler implements InvocationHandler {
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".Name())) {
Log.e("Main","startActivity⽅法拦截了");
// 到参数⾥⾯的第⼀个Intent 对象
Intent raw;
int index = 0;
防辐射面罩for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
raw = (Intent) args[index];
//创建⼀个要被掉包的Intent
Intent newIntent = new Intent();
// 替⾝Activity的包名, 也就是我们⾃⼰的"包名"
String stubPackage = Context().getPackageName();
// 这⾥我们把启动的Activity临时替换为 ZhanKengActivitiy
ComponentName componentName = new ComponentName(stubPackage, Name());
newIntent.setComponent(componentName);
// 把我们原始要启动的TargetActivity先存起来
newIntent.putExtra(AMSHookHelper.EXTRA_TARGET_INTENT, raw);
// 替换掉Intent, 达到欺骗AMS的⽬的
args[index] = newIntent;
Log.e("Main","startActivity⽅法 hook 成功");
Log.e("Main","args[index] hook = " + args[index]);
return method.invoke(mBase, args);
}
return method.invoke(mBase, args);
}
}
使⽤动态代理去代理上⾯获取的AMS
// 创建⼀个这个对象的代理对象, 然后替换这个字段, 让我们的代理对象帮忙⼲活,这⾥我们使⽤动态代理
//动态代理依赖接⼝
Class<?> iActivityManagerInterface = Class.forName("android.app.IActivityManager");
//返回代理对象,IActivityManagerHandler是我们⾃⼰的代理对象
Object proxy = wProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class<?>[] { iActivityManagerInterface }, new IActivityManagerHandler(rawIActivityManager));
//将我们的代理设值给singleton的单例
mInstanceField.set(gDefault, proxy);
等系统检查完后,再次代拦截系统创建Activity的⽅法,将原来我们替换的Activity再替换回来,达到启动不在AndroidMainfest注册的⽬的
系统检查合法性后,会回调ActivityThread⾥的scheduleLaunchActivity⽅法,在这个⽅法⾥发送⼀个消息到ActivityThread的内部类H 中

本文发布于:2023-05-15 17:58:29,感谢您对本站的认可!

本文链接:https://patent.en369.cn/patent/2/100621.html

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系,我们将在24小时内删除。

标签:加载   插件   动态   实现   代理   启动
留言与评论(共有 0 条评论)
   
验证码:
Copyright ©2019-2022 Comsenz Inc.Powered by © 369专利查询检索平台 豫ICP备2021025688号-20 网站地图