Android 热修补方案(AndFix)
来源:互联网 发布:淘宝历史最低价怎么看 编辑:程序博客网 时间:2024/05/01 08:18
AndFix介绍
AndFix是一个Android App的在线热补丁框架。使用此框架,我们能够在不重复发版的情况下,在线修改App中的Bug。AndFix就是 “Android Hot-Fix”的缩写。
AndFix支持Android 2.3到6.0版本,并且支持arm 与 X86系统架构的设备。完美支持Dalvik与ART的Runtime。
AndFix 的补丁文件是以 .apatch 结尾的文件。
AndFix是阿里的开源项目。https://github.com/alibaba/AndFix
小例子:
下载demo APK http://120.55.185.35:8080/old.apk
demo 下载地址,或者扫描下面二维码,安装好apk后运行AndFixDemo,点击“提示信息”按钮,跳出“未修复的toast”,点击“开始修复”,app会到远程服务端下载补丁包更新,大概会持续几秒钟,等待几秒后再次点击“提示信息”按钮,会弹出修复好的内容
实际运行中则不需要点击“开始修复”按钮,在demo中为了对比效果,所以加了按钮去控制
实际运行中检测需要打补丁的方案:
- 类似检测升级,在打开app或者某个页面去检测
- 服务端推送(需要和服务端定义更多实现细节)
AndFix的优点很明显
- 补丁包很小(上面的例子,补丁包才几K),打补丁的速度很快
- 打好补丁后,后续都不用再打了
使用方法:
1. 添加依赖
2. 在自定义Application中初始化,并在AndroidManifest.xml中注册该Application
3.在activity 中打补丁,(这里省略了检测步骤,默认就是直接下载补丁并更新,实际开发中需要对补丁的版本进行检测,更新好后删除补丁包,动态获取补丁路径等等)
4.到这里已经完成了配置工作,接下来用正式的key的打包,就生成了old.apk, 把old.apk放到了服务器上,项目就上线了
5.项目上线后,会有各种突发情况,(比如文案修改,紧急bug,造成app crash等麻烦,正常情况只有进行版本升级),这里看下AndFix热修补的步骤,以修改showToast方法为例子
6.现在把showToast方法修改,并打包,命名为fix.apk
7.下载apkpatch工具
8.制作补丁包,cd 到目录下,运行apkpatch.bat -f fix.apk -t old.apk -o output1 -k demo.jks -p 123456 -a demo -e 123456
(这里-t 为老的apk -f为修复过的apk,-o 为输出目录 -k 为打包的key -p -e 为密码 -a 为别名)
9.屏幕输出了增加了修改了toast的方法 ,同时目录下新增了output1 目录,点进去查看,其中.apatch为真正的补丁包
10. 用dex2Jar工具把diff文件转成jar文件,在用jd.gui查看
其实这个工具就是比对两个dex文件,分析出修改过的地方,然后生成补丁包
11. 把.apatch命名为app.apatch上传至服务器,坐等客户端打补丁
源码分析:
1首先来看下在application中的初始化
@Override public void onCreate() { super.onCreate(); // 初始化patch管理类 mPatchManager = new PatchManager(this); // 初始化patch版本 mPatchManager.init("2.0"); // 加载已经添加到PatchManager中的patch mPatchManager.loadPatch(); }
2 构造了PatchManager对象,来看下代码
public PatchManager(Context context) { mContext = context; mAndFixManager = new AndFixManager(mContext); //初始化AndFixManager,等会再介绍 //getFileDir 获取的是/data/data/<application package>/files //在这里是 /data/data/<application package>/files/apatch mPatchDir = new File(mContext.getFilesDir(), DIR); // 支持并发访问的有序的补丁集合 mPatchs = new ConcurrentSkipListSet<Patch>(); // ClassLoader的集合,同样也是基于线程安全的 mLoaders = new ConcurrentHashMap<String, ClassLoader>(); }
3 初始化patch版本,代码
public void init(String appVersion) { //再次检测patch存放路径 if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail Log.e(TAG, "patch dir create error."); return; } else if (!mPatchDir.isDirectory()) {// not directory mPatchDir.delete(); return; } //用SharedPreferences 获取path的版本 SharedPreferences sp = mContext.getSharedPreferences(SP_NAME, Context.MODE_PRIVATE); String ver = sp.getString(SP_VERSION, null); if (ver == null || !ver.equalsIgnoreCase(appVersion)) { //如果是第一次使用或者版本不一致,则删除所有Patch ,这里equalsIgnoreCase是忽略大小写的equals cleanPatch(); // 存放版本 sp.edit().putString(SP_VERSION, appVersion).commit(); } else { // 加载有所的Patch initPatchs(); } } private void initPatchs() { File[] files = mPatchDir.listFiles(); for (File file : files) { addPatch(file); } }
4 加载已经添加到PatchManager中的patch,代码
/** * load patch,call when application start * 这里写的也很清楚了,在程序启动的时候调用 */ public void loadPatch() { //首先加载了通用的类加载器 mLoaders.put("*", mContext.getClassLoader());// wildcard Set<String> patchNames; List<String> classes; //遍历每个补丁包 Patch 的结构HashMap<String, List<String>>() //实际中,只会有一个key,就是你修改后apk的名字,list中存放修改的className //fix----[cv.cocoa.com.andfixdemo.MainActivity_CF] for (Patch patch : mPatchs) { patchNames = patch.getPatchNames(); for (String patchName : patchNames) { classes = patch.getClasses(patchName); //更新补丁 mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(), classes); } } }
5.在Application中的初始化代码就完成了,其中还有一个AndFixManager没讲,AndFixManager在构造前,会做一个兼容性的检测,放在了Compat 类中,代码
public class Compat { public static boolean isChecked = false; public static boolean isSupport = false; /** * whether support on the device * 需要对兼容性进行检测,检测的判断是不能是YunOs的手机,sdk的版本必须是在2.3-6.0之间 * @return true if the device support AndFix */ public static synchronized boolean isSupport() { if (isChecked) return isSupport; isChecked = true; // AndFix.setup()判断是Dalvik还是Art虚拟机,来注册Native方法 if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) { isSupport = true; } if (inBlackList()) { isSupport = false; } return isSupport; }}
/** * initialize * * @return true if initialize success * * 判断是Dalvik还是Art虚拟机,并初始化native的方法, * * 官方文档中说到https://developer.android.com/guide/practices/verifying-apps-art.html * You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property's value is "2.0.0" or higher. * */ public static boolean setup() { try { final String vmVersion = System.getProperty("java.vm.version"); boolean isArt = vmVersion != null && vmVersion.startsWith("2"); int apilevel = Build.VERSION.SDK_INT; return setup(isArt, apilevel); } catch (Exception e) { Log.e(TAG, "setup", e); return false; } }
6.然后再来看下AndFixManager 的代码
//最关键的就是这里,获取到Class对象后,用反射获取修改的方法private void fixClass(Class<?> clazz, ClassLoader classLoader) { //java反射获取Class的方法 Method[] methods = clazz.getDeclaredMethods(); MethodReplace methodReplace; String clz; String meth; for (Method method : methods) { // 找到MethodReplace的注解 methodReplace = method.getAnnotation(MethodReplace.class); if (methodReplace == null) continue; //对照上面的demo,clz就是cv.cocoa.com.andfixdemo.MainActivity clz = methodReplace.clazz(); //对照上面的demo,meth 就是showToast meth = methodReplace.method(); if (!isEmpty(clz) && !isEmpty(meth)) { //替换方法 replaceMethod(classLoader, clz, meth, method); } } } /** * replace method * * @param classLoader classloader * @param clz class * @param meth name of target method * @param method source method */ private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) { try { String key = clz + "@" + classLoader.toString(); Class<?> clazz = mFixedClass.get(key); if (clazz == null) {// class not load Class<?> clzz = classLoader.loadClass(clz); // initialize target class clazz = AndFix.initTargetClass(clzz); } if (clazz != null) {// initialize class OK mFixedClass.put(key, clazz); Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes()); //调用jni的方法 AndFix.addReplaceMethod(src, method); } } catch (Exception e) { Log.e(TAG, "replaceMethod", e); } }
7.总的来说,java 层的代码就是做兼容性的检测,对apatch补丁文件进行检测,然后对补丁文件中的注解进行查找,最后传递给jni层。(可能java层有写还没讲到,比如补丁安全性的验证等,后续再补上),这里先来讲下jni层的东西。
AndFix 支持 ART 和Dalvik , 同是art 又区分为5.0,5.1,6.0, 这里主要使用 AndFix类中 setup()的方法,然后再调用andfix.cpp 中的方法
//jni层的setup//isart //apilevelstatic jboolean setup(JNIEnv* env, jclass clazz, jboolean isart, jint apilevel) { isArt = isart; LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"), (int )apilevel); //判断是否art if (isArt) { return art_setup(env, (int) apilevel); } else { return dalvik_setup(env, (int) apilevel); }}/* * 注册jni */static JNINativeMethod gMethods[] = {/* name, signature, funcPtr */{ "setup", "(ZI)Z", (void*) setup }, { "replaceMethod", "(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V", (void*) replaceMethod }, { "setFieldFlag", "(Ljava/lang/reflect/Field;)V", (void*) setFieldFlag }, };
8. 判断好虚拟机后,分别调用不同的实现,这里以dalvik为例
参考 :http://blog.csdn.net/com360/article/details/46671315
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup( JNIEnv* env, int apilevel) { //Davik虚拟机实现 是在libdvm.so中 //dlopen()函数以指定模式打开指定的动态链接库文件,并返回dalvik的句柄 void* dvm_hand = dlopen("libdvm.so", RTLD_NOW); if (dvm_hand) {//调用dvm_dlsym 获取dvmDecodeIndirectRef 的函数指针 dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand, apilevel > 10 ? "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" : "dvmDecodeIndirectRef"); if (!dvmDecodeIndirectRef_fnPtr) { return JNI_FALSE; } dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand, apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf"); if (!dvmThreadSelf_fnPtr) { return JNI_FALSE; } jclass clazz = env->FindClass("java/lang/reflect/Method"); jClassMethod = env->GetMethodID(clazz, "getDeclaringClass", "()Ljava/lang/Class;"); return JNI_TRUE; } else { return JNI_FALSE; }}extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod( JNIEnv* env, jobject src, jobject dest) { jobject clazz = env->CallObjectMethod(dest, jClassMethod); ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr( dvmThreadSelf_fnPtr(), clazz); clz->status = CLASS_INITIALIZED; //FromReflectedMethod转换一个java.lang.reflect.Method或java.lang.reflect.Constructor对象到一个方法ID。 Method* meth = (Method*) env->FromReflectedMethod(src); Method* target = (Method*) env->FromReflectedMethod(dest); LOGD("dalvikMethod: %s", meth->name); meth->clazz = target->clazz; meth->accessFlags |= ACC_PUBLIC; meth->methodIndex = target->methodIndex; meth->jniArgInfo = target->jniArgInfo; meth->registersSize = target->registersSize; meth->outsSize = target->outsSize; meth->insSize = target->insSize; meth->prototype = target->prototype; meth->insns = target->insns; meth->nativeFunc = target->nativeFunc;}
1 0
- Android 热修补方案(AndFix)
- android 热修补AndFix
- android 热修补之andfix实践
- andFix进行热修补
- Android热修复方案—AndFix
- Android 热修复以及阿里AndFix方案使用
- Android 热修复-AndFix
- Android热修复---AndFix
- Android 热修复 AndFix
- Android 热修复AndFix
- Android热更新AndFix
- Android AndFix热更新
- android AndFix--热补丁框架
- Android 热修复之AndFix
- Android AndFix 热修复框架
- Android热修复之AndFix
- Android AndFix 热修复 使用
- Android AndFix 热修复框架
- Windows7安装和配置MySql5.7.11
- APP代码规范
- 编写线程安全的代码
- 自己对团队开发项目的意见以及项目需求分析
- nodejs错误 : request entity too large
- Android 热修补方案(AndFix)
- GPU处理图像 Shader的入门
- Win7下 gulp搭建 + gulp实时监测demo
- git push到github报:fatal: HTTP request failed 的可能原因
- POI操作Excel、
- 数据库的乱码问题
- java中的数据类型
- arduino随笔(1)
- linux环境变量