android热修复--阿里热修复使用和源码分析

android热修复–阿里热修复使用和源码分析

AndFix

全称Android hot-fix,是alibaba的Android热修复框架,支持Android 2.3到7.0的版本,支持arm与X86系统架构,支持Dalvik和ART Runtime。

原理

AndFix的原理就是方法的替换,把有bug的方法替换成补丁文件中的方法。

android热修复--阿里热修复使用和源码分析_第1张图片

使用

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

@Override
public 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

@Override
public 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补丁文件

第一步我们就不用看了,我们来看第二步:

android热修复--阿里热修复使用和源码分析_第2张图片

命令格式:

apkpatch -f  -t  -o  -k  -p <***> -a  -e <***>
     -a,--alias      keystore entry alias.
     -e,--epassword <***>   keystore entry password.
     -f,--from         new Apk file path.
     -k,--keystore     keystore path.
     -n,--name        patch name.
     -o,--out          output dir.
     -p,--kpassword <***>   keystore password.
     -t,--to           old Apk file path.

我这里使用的是(在当前目录下执行):

apkpatch -f new.apk -t old.apk -o ./ -k MyTestAppKey.jks -p 123456 -a testapp -e 123456

android热修复--阿里热修复使用和源码分析_第3张图片

android热修复--阿里热修复使用和源码分析_第4张图片

使用很简单,我们就到这里。下面我们来看下apatch补丁文件,解压一下:

这里写图片描述

现在在对calsses.dex反编译:

android热修复--阿里热修复使用和源码分析_第5张图片

我们看到阿里热修复对你改动的方法添加了注解。

我们在看下META-INF/PATCH.MF文件,这个文件很重要,他是对你修改过的类做了一份记录。

Manifest-Version: 1.0
Patch-Name: new
Created-Time: 19 May 2017 06:33:36 GMT
From-File: new.apk
To-File: old.apk
Patch-Classes: github.com.mytestapp.TestActivity_CF
Created-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;

}

因为本人能力有限,只能分析到这个地步,所以到这边就分析完了。

你可能感兴趣的:(android进阶)