android热修复--阿里热修复使用和源码分析
android热修复–阿里热修复使用和源码分析
AndFix
全称Android hot-fix,是alibaba的Android热修复框架,支持Android 2.3到7.0的版本,支持arm与X86系统架构,支持Dalvik和ART Runtime。
原理
AndFix的原理就是方法的替换,把有bug的方法替换成补丁文件中的方法。
使用
1.添加依赖和混肴
maven:
com.alipay.euler andfix 0.5.0 aar
gradle:
dependencies { compile 'com.alipay.euler:andfix:0.5.0@aar'}
混肴
-keep class * extends java.lang.annotation.Annotation-keepclasseswithmembernames class * { native ;}
2.在Application.onCreate()中初始化PatchManager
@Overridepublic void onCreate() { super.onCreate(); try { //初始化PatchManager mPatchManager = new PatchManager(this); //初始化版本号 PackageInfo info = getPackageManager().getPackageInfo(getPackageName(), 0); mPatchManager.init(info.versionName); // 加载已经添加到PatchManager中的patch mPatchManager.loadPatch(); } catch (Exception e) { e.printStackTrace(); Log.e(TAG,"修复失败!"); }}
2.在Application.onCreate()中初始化PatchManager
@Overridepublic void onClick(View v){ if(v.getId()==R.id.btn_ali){ fixAliBug(); }}private void fixAliBug() { Log.e(TAG, "阿里开始修复。。。。"); try { File dexDir = this.getDir("tem",MODE_PRIVATE); String dexPath=dexDir.getAbsolutePath()+"fix.apatch"; //拷贝文件到本项目下 FileUtil.copyAssetsTo(this,"fix.apatch",dexPath); File file=new File(dexPath); if (file.exists()) { //添加修复文件 PatchManager mPatchManager=new PatchManager(this); mPatchManager.addPatch(dexPath); Log.e(TAG, "修复成功。。。。"); }else { Log.e(TAG,"没有修复文件!"); } }catch (Exception ex){ ex.printStackTrace(); Log.e(TAG, "修复失败:"+ex); } Log.e(TAG, "阿里修复结束。。。。");}
我们注意到上面的fix.apatch文件,这个就是补丁,那么这个补丁从那边来呢?阿里提供了生成的工具apkpatch
点击下载: apkpatch
apkpatch使用
1.将修改前和修改后的项目打包
2.使用apkpatch工具生成apatch补丁文件
第一步我们就不用看了,我们来看第二步:
命令格式:
apkpatch -f -t -o
我这里使用的是(在当前目录下执行):
apkpatch -f new.apk -t old.apk -o ./ -k MyTestAppKey.jks -p 123456 -a testapp -e 123456
使用很简单,我们就到这里。下面我们来看下apatch补丁文件,解压一下:
现在在对calsses.dex反编译:
我们看到阿里热修复对你改动的方法添加了注解。
我们在看下META-INF/PATCH.MF文件,这个文件很重要,他是对你修改过的类做了一份记录。
Manifest-Version: 1.0Patch-Name: newCreated-Time: 19 May 2017 06:33:36 GMTFrom-File: new.apkTo-File: old.apkPatch-Classes: github.com.mytestapp.TestActivity_CFCreated-By: 1.0 (ApkPatch)
我们现在来看下源码。
源码分析
我们就从mPatchManager.addPatch(dexPath)开始分析:
public void addPatch(String path) throws IOException { File src = new File(path); File dest = new File(mPatchDir, src.getName()); if(!src.exists()){ throw new FileNotFoundException(path); } if (dest.exists()) { Log.d(TAG, "patch [" + path + "] has be loaded."); return; } //拷贝文件 FileUtil.copyFile(src, dest);// copy to patch's directory Patch patch = addPatch(dest); if (patch != null) { //加载补丁 loadPatch(patch); }}private void loadPatch(Patch patch) { //从MANIFEST.MF中得到Class-Name Set patchNames = patch.getPatchNames(); ClassLoader cl; List classes; for (String patchName : patchNames) { if (mLoaders.containsKey("*")) { cl = mContext.getClassLoader(); } else { cl = mLoaders.get(patchName); } if (cl != null) { //得到dex中class列表 classes = patch.getClasses(patchName); //修复 mAndFixManager.fix(patch.getFile(), cl, classes); } }}//开始修复public synchronized void fix(File file, ClassLoader classLoader, List classes) { //使用系统的DexFile来操作dex文件 final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(), optfile.getAbsolutePath(), Context.MODE_PRIVATE); //读取dex并且将带有(com.alipay.euler.andfix)注解的class生成类加载器 ClassLoader patchClassLoader = new ClassLoader(classLoader) { @Override protected Class<?> findClass(String className) throws ClassNotFoundException { Class<?> clazz = dexFile.loadClass(className, this); if (clazz == null && className.startsWith("com.alipay.euler.andfix")) { return Class.forName(className); } if (clazz == null) { throw new ClassNotFoundException(className); } return clazz; } }; //使用生成的类加载器加载带有注解的类,并且进行修复 Enumeration entrys = dexFile.entries(); Class<?> clazz = null; while (entrys.hasMoreElements()) { String entry = entrys.nextElement(); if (classes != null && !classes.contains(entry)) { continue;// skip, not need fix } clazz = dexFile.loadClass(entry, patchClassLoader); if (clazz != null) { //开始修复 fixClass(clazz, classLoader); } }}
我们从fixClass再来看:
private void fixClass(Class<?> clazz, ClassLoader classLoader) { Method[] methods = clazz.getDeclaredMethods(); MethodReplace methodReplace; String clz; String meth; for (Method method : methods) { //获取注解 methodReplace = method.getAnnotation(MethodReplace.class); if (methodReplace == null) continue; clz = methodReplace.clazz(); meth = methodReplace.method(); if (!isEmpty(clz) && !isEmpty(meth)) { //最主要的地方: replaceMethod(classLoader, clz, meth, method); } }}
这个方法主要是获取类中带有阿里注解的方法,然后把带有注解的方法新的类和方法以及被修改的类和方法名传递给replaceMethod方法。下面我们重点研究replaceMethod方法:
private void replaceMethod(ClassLoader classLoader, String clz, String meth, Method method) { ... Class<?> clazz = mFixedClass.get(key); //初始化clazz(其实就是获得目标类) ... if (clazz != null) { ... //获得目标类中的方法 Method src = clazz.getDeclaredMethod(meth, method.getParameterTypes()); //将目标类方法和新的方法传给addReplaceMethod AndFix.addReplaceMethod(src, method); }}public static void addReplaceMethod(Method src, Method dest) { try { //在底层替换目标方法指针指向的位置 replaceMethod(src, dest); } catch (Throwable e) { Log.e(TAG, "addReplaceMethod", e);
下面我们一起来分析下native层代码:
AndFix-master\AndFix-master\jni\andfix.cpp
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest) { if (isArt) { art_replaceMethod(env, src, dest); } else { dalvik_replaceMethod(env, src, dest); }}
由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式.我们只分析Dalvik部分。
AndFix-master\AndFix-master\jni\dalvikdalvik_method_replace.cpp
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup( JNIEnv* env, int apilevel) { //libdvm.so 加载系统的so库 void* dvm_hand = dlopen("libdvm.so", RTLD_NOW); if (dvm_hand) { 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; } //获取Method的Class jclass clazz = env->FindClass("java/lang/reflect/Method"); //获取Method的getDeclaringClass的Id 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) { //调用getDeclaringClass jobject clazz = env->CallObjectMethod(dest, jClassMethod); ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr( dvmThreadSelf_fnPtr(), clazz); clz->status = CLASS_INITIALIZED; //得到原方法的反射 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;}
因为本人能力有限,只能分析到这个地步,所以到这边就分析完了。
更多相关文章
- Binder框架 -- android AIDL 的使用
- Android开发之WebView的使用(1)
- Android(安卓)应用保存状态
- Android(安卓)NDK学习笔记4-Android.mk篇
- Android使用addView动态添加组件
- Android(安卓)View如何获取焦点
- Android输入系统(三):加载按键映射
- Android(安卓)中ListView setOnItemClickListener点击无效原因分
- Android向服务器的数据库MySQL传输数据:经过修正的 Android(安卓)