Android热修复:Qfix方案的gradle实践

一、Android热修复方案的发展

Android热修复技术现在主流分为NativeHook,ClassLoader以及新出现的Instant-Run方案,在工作中因为团队需要为团队引入了QZone的ClassLoader插桩方案,当初开发的时候(15年底)市面上还只有AndFix和ClassLoader两种方案,而那时的AndFix兼容性也还比较差,就选择了ClassLoader方案,基于网上开源的Nuwa方案修改而来。

2016年的时候Android热修复方案如雨后春笋,ClassLoader方案新起之秀有Tinker,QFix等。对比几种新出的方案,调研之后认为QFix与团队之前使用的ClassLoader方案最为接近,同时无需插桩,避免了插桩性能损失问题,在16年下半年时候将团队的热修复方案升级到了QFix方案。

由于团队的热修复方案需要对原来的方案做不少兼容,所以会有不少雍余的代码。17年年初就想着自己写个QFix的开源gradle实现,现项目已完成发布到Github,地址:https://github.com/alexclin0188/QFixPatch。 这篇文章主要是对开发过程的一个记录,发出来供有兴趣的同学参考和自己以后复习使用。

二、QFix方案原理

QFix方案原理介绍原文。有兴趣的同学可以仔细研读下这篇原理介绍,这里就不再详细讲解,补丁修复的基本原来和ClassLoader类同,反射在DexPahList类中dex数组前插入补丁dex,区别在于QFix使用在native层调用dvmResolveClass方法来解决pre-verify的问题,不再需要插桩。

GitHub上有一个开源的简单的QFix方案Eclipse工程demo:https://github.com/lizhangqu/QFix, QFixPatch项目即是基于这个demo扩展而来的一个gradle实践。

三、Gradle插件开发

gradle插件生成补丁逻辑和Nuwa类似,只是增加了dexdump分析补丁类class-id的操作,简单的流程示意图如下:

Android热修复:Qfix方案的gradle实践_第1张图片 qfix_gradle_proto.png

首先是buildBase,构建基础apk和保存基础信息,在dexTask之前增加hook,将原始apk中的类的sha值保存到hash.txt中,以便生成补丁时使用。然后在buildPatch执行时比较找出修改的类(补丁类),并通过dexdump分析这些补丁类在基础apk的dex中的class-id,将分析得到的class-id信息和补丁类通过dex工具打包成一个补丁apk

3.1 创建插件工程

在一个AS工程内,如果有buildSrc目录,这个目录就会被当做gradle插件源码目录编译,并可以直接在工程模块中直接使用编写的gradle插件,下面是QFixPatch工程gradle插件源码目录截图

Android热修复:Qfix方案的gradle实践_第2张图片 qfix-gradle-source-code.png

和Android模块不同的是其中有resources目录,下面有个META-INF.gradle-plugins文件夹,内有propertites文件,而这个文件是用来注册插件入口类的,下面是alexclin.qfix.properties的内容

//alexclin.qfix.properties文件,properties文件名称即为gradle插件名implementation-class=alexclin.qfix.QFixPlugin

我们gradle插件的使用方式是在模块的build.gradle中增加apply,如下

//build.gradle文件......apply plugin: 'alexclin.qfix'......

而这里的apply plugin的作用最终就是让项目在执行gradle编译时调用我们的实现类alexclin.qfix.QFixPlugin的apply函数,下面让我们看QFixPlugin类的代码

//QFixPlugin类public class QFixPlugin implements Plugin {    static final String CLASS_ID_TXT = "class-ids.txt"    static final String PATCH_NAME = "patch"    static final String DEBUG = "debug"    static final String PLUGIN_NAME = "qfix"    def debugOn    @Override    void apply(Project project) {        if (project.getPlugins().hasPlugin(AppPlugin)) {            project.extensions.create(PLUGIN_NAME, QFixExtension, project)            applyPlugin(project)        }    }    //真正的applyplugin逻辑代码    private void applyPlugin(Project project) {        project.afterEvaluate {            def extension = project.extensions.findByName(PLUGIN_NAME) as QFixExtension            Creator creator = new Creator(project, extension);            debugOn = extension.debugOn            project.android.applicationVariants.each { variant ->                if (variant.name.contains(DEBUG) && !debugOn)                    return;                configTasks(project, variant, creator);            }            //添加总的buildPatch Task            def releasePatch = project.tasks.findByName("assembleReleasePatch")            def debugPatch = project.tasks.findByName("assembleDebugPatch")            if (releasePatch != null || debugPatch != null) {                def buildPatchTask = project.task("buildPatch")                if (debugPatch) buildPatchTask.dependsOn debugPatch                if (releasePatch) buildPatchTask.dependsOn releasePatch            }            def assembleDebugBase = project.tasks.findByName("assembleDebugBase")            def assembleReleaseBase = project.tasks.findByName("assembleReleaseBase")            if (assembleDebugBase != null || assembleReleaseBase != null) {                def assembleBase = project.task("buildBase");                if (assembleDebugBase) assembleBase.dependsOn assembleDebugBase                if (assembleReleaseBase) assembleBase.dependsOn assembleReleaseBase            }        }    }        .... }

可以看到上面这段代码里面,主要是在真正的applyplugin函数对项目进行了Task设置,并对buildBase,buildPatch,assembleDebugBase,assembleDebugPacth,assembleReleaseBase,assembleReleasePacth的依赖关系做了设置。

实际上只有assembleBase,assemblePatch的区别,前者是构建基础apk包和保存基础信息,后者是构建补丁

3.2 插件设置QFixExtension

gradle插件支持属性设置,如我们的QFix插件在build.gradle中属性设置,如下

//qfix插件在app模块build.gradle中的属性设置qfix{      debugOn true //debug模式是否开启补丁构建task,可选    outputDir 'qfix_output'//补丁基础信息默认输入目录和构建输出目录,必须    excludeClass = ["App.class"]//需要排除的类,会自动排除Application及其父类,此处只是示例,可选    //includePackage = []//需要包含的package,可选    strictMode true //严格模式,用于解决ART下dex激进内联问题,详情参看README,可选}

而上面这个设置实际对应的是一个Extension类,在qfix-gradle插件中对应的就是QFixExtension类

package alexclin.qfiximport org.gradle.api.Projectclass QFixExtension {    HashSet includePackage = []    HashSet excludeClass = []    //补丁构建在debug版是否开启,默认开启    boolean debugOn = false    //补丁基础信息保存目录和补丁输出目录    String outputDir    //严格模式,启用则打包所有引用补丁类的class到补丁中,应对ART激进内联引起的问题    boolean strictMode = false;    QFixExtension(Project project) {    }}

3.3 设置Task和hook dexTask

设置Task和hook dexTask的逻辑主要都在QFixPlugin的configTasks函数中,以下是简化的函数代码。

    static void configTasks(Project project, BaseVariant variant, Creator creator) {        Map hashMap        //获取dexTask和proguardTask        def dexTask = ...        def proguardTask = ...        if (creator.patchTaskEnable()) {            //创建Task,寻找被修改的类,只有assemblePatch时才会被调用            def diffClassBeforeDex = "diffClassBeforeDex${variant.name.capitalize()}"            def diffClassBeforeDexTask = ...            diffClassBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(dexTask)            //创建Task,将改变的类打成一个dex            def hotfixPatch = "assemble${variant.name.capitalize()}Patch"            def hotfixPatchTask = ...            hotfixPatchTask.dependsOn diffClassBeforeDexTask        }        //创建Hook Task, 在dexTask执行之前 保存所有类的sha值到对应目录的hash.txt        def shaClassBeforeDex = "shaClassBeforeDex${variant.name.capitalize()}"        def shaClassBeforeDexTask = ...            //备份构建过程中的mapping.txt,如果有            if (proguardTask) {                def mapFile = new File("${project.buildDir}/outputs/mapping/${variant.dirName}/mapping.txt")                def newMapFile = creator.getMappingOutFile(variant);                Utils.copyFile(mapFile, newMapFile)            }        }        shaClassBeforeDexTask.dependsOn dexTask.taskDependencies.getDependencies(dexTask)        //对assembleRelease或assembleDebug添加Hook,保存apk        def assembleTaskName = "assemble${variant.name.capitalize()}Base";        def assembleTask = project.task(assembleTaskName);        Closure saveAssembleClosure = ...                //设置依赖关系,保证assembleXXXBase在android自身的assembleDebug/assembleRelease之后执行        assembleTask.doLast(saveAssembleClosure)        assembleTask.dependsOn shaClassBeforeDexTask        assembleTask.dependsOn project.tasks["assemble${variant.name.capitalize()}"]    }

上面创建的几个task中间最重要的是shaClassBeforeDexTask和diffClassBeforeDexTask,前者是保存基础apk中所有类的sha信息,后者则是对比基础apk和修改后的代码类的信息,找出改变的类。先来看shaClassBeforeDexTask

//shaClassBeforeDexTask 保存基础apk中所有类的sha信息def shaClassBeforeDexTask = project.task(shaClassBeforeDex) << {            //准备工作...            ...                        //保存所有类的sha值到hashFile中            Set inputFiles = AndroidUtils.getDexTaskInputFiles(project, variant, dexTask)            if (proguardTask) {                inputFiles.each {                    inputFile ->                        if (inputFile.path.endsWith(".jar")) {                            shaJarInfo(inputFile, creator.patchSetting, hashFile)                        }                }            } else if (AndroidUtils.compareVersionName(Version.ANDROID_GRADLE_PLUGIN_VERSION, "2.2.3") > -1) {                //没有混淆在2.2.3及以后插件上inputFiles不包含当前模块类                //合并所有jar包                Set jarAndDir = new HashSet<>(inputFiles)                jarAndDir.add(new File(project.buildDir, "intermediates/classes/${variant.dirName}"))                File combinedJar = combineJarAndDir(project, jarAndDir)                shaJarInfo(combinedJar, creator.patchSetting, hashFile)            }            //备份构建过程中的mapping.txt            ......        }

diffClassBeforeDexTask的作用是找出改变的类并dump分析改变的类在基础apk的dex中的class-id,简化的代码如下:

//diffClassBeforeDexTask 找出改变的类并dump分析改变的类在基础apk的dex中的class-iddef diffClassBeforeDexTask = project.task(diffClassBeforeDex) << {                //补丁准备工作, 读取原来apk的dex目录,proguard-mapping,hash.txt,以及初始化补丁输出目录                def baseDexDir = ...                File mappingFile = ...                hashMap = ...                File patchOutDir = ...                //获取dexTask的输入jar报参数                //比较所有类与之前保存的sha值是否有差异,有差异则保存到patchClassDir                Set inputFiles = AndroidUtils.getDexTaskInputFiles(project, variant, dexTask)                if (proguardTask) {                    inputFiles.each {                        inputFile ->                            if (inputFile.path.endsWith(".jar")) {                                diffJar(inputFile, hashMap, creator, variant, finder)                            }                    }                } else if (AndroidUtils.compareVersionName(Version.ANDROID_GRADLE_PLUGIN_VERSION, "2.2.3") > -1) {                    //没有混淆在2.2.3及以后插件上inputFiles不包含当前模块类                    //合并所有jar包                    Set jarAndDir = new HashSet<>(inputFiles)                    jarAndDir.add(new File(project.buildDir, "intermediates/classes/${variant.dirName}"))                    File combinedJar = combineJarAndDir(project, jarAndDir)                    diffJar(combinedJar, hashMap, creator, variant, finder)                }                def allRefPatchClasses = ...                def appName = ...                //增加dexDump处理,分析补丁类在基础apk的dex中class-id并保存                def dumpCmdPath = AndroidUtils.getDexDumpPath(project, creator.sdkDir);                File patchClassDir = creator.getClassOutDir(variant);                File classIdsFile = new File(patchClassDir, CLASS_ID_TXT);                DexClassIdResolve.dumpDexClassIds(dumpCmdPath, baseDexDir, patchClassDir, classIdsFile,appName,allRefPatchClasses)            }

可以看到重要的逻辑就是比较sha值和dexDump分析class-id, diffJar函数主要是读取jar包中的类并保存sha信息,代码如下

static void diffJar(File jarFile, HashMap hashMap, Creator builder, BaseVariant variant, SubClsFinder finder) {        File basePatchClassDir = builder.getClassOutDir(variant);        if (!basePatchClassDir.exists()) basePatchClassDir.mkdirs();        if (jarFile && jarFile.isFile()) {            def file = new JarFile(jarFile);            builder.patchSetting.addApplicationAndSuper(file, variant);            Enumeration enumeration = file.entries();            while (enumeration.hasMoreElements()) {                JarEntry jarEntry = (JarEntry) enumeration.nextElement();                String entryName = jarEntry.getName();                InputStream inputStream = file.getInputStream(jarEntry);                if (!builder.patchSetting.isExcluded(entryName)) {                    def bytes = Utils.readAllBytesAndClose(inputStream);                    def hash = DigestUtils.shaHex(bytes)                    if (Utils.notSame(hashMap, entryName, hash)) {                        finder.addAbsPatchClass(bytes, entryName)                        Utils.copyBytesToFile(bytes, Utils.touchFile(basePatchClassDir, entryName))                    } else {                        finder.addOutPatchClass(bytes, entryName)                    }                }            }            Collection collections = finder.getRefClasses();            for (String entryName : collections) {                ZipEntry zipEntry = file.getJarEntry(entryName);                InputStream inputStream = file.getInputStream(zipEntry);                def bytes = Utils.readAllBytesAndClose(inputStream);                Utils.copyBytesToFile(bytes, Utils.touchFile(basePatchClassDir, entryName))                inputStream.close();            }            file.close();        }    }

3.4 调用dexDump分析补丁类在原始apk的dex中class-id

dexDump的分析逻辑在alexclin.qfix.DexClassIdResolve类中,主要代码是readClassIdMap函数,该函数根据传入的dex和补丁类信息,结合dexdump工具的输出,将补丁类class-id保存下来

private static String readClassIdMap(InputStream mInputStream, int mDexIndex, HashSet mPatchSet,Set patchRefSet,ArrayList mClassIdMapList) {        InputStreamReader isReader = null;        BufferedReader reader = null;        String entrance = null;        try {            isReader = new InputStreamReader(mInputStream);            reader = new BufferedReader(isReader);            boolean findHead = false;            boolean findClass = false;            int classIndex = -1;            long classIdx = -1;            def line            while ((line = reader.readLine()) != null) {                if (line.startsWith("Class #") && line.endsWith(" header:") && !findHead && classIndex < 0) {                    findHead = true;                    classIndex = Integer.parseInt(line.substring("Class #".length(), line.indexOf(" header:")));                } else if (line.startsWith("class_idx") && findHead && classIndex >= 0 && classIdx < 0) {                    classIdx = Integer.parseInt(line.substring(line.indexOf(": ") + 2));                } else if (line.startsWith("Class #") && findHead && classIndex >= 0                        && line.contains(String.valueOf(classIndex)) && classIdx > 0) {                    findClass = true;                } else if (line.startsWith("  Class descriptor") && findHead && findClass && classIndex >= 0 && classIdx > 0) {                    String className = line.substring(line.indexOf("'L") + 2, line.indexOf(";'"));                    if (mPatchSet.contains(className)) {                        ClassIdMap item = new ClassIdMap(className, mDexIndex, classIdx);                        mClassIdMapList.add(item);                    }else if(!patchRefSet.contains(className)&&entrance==null){                        entrance = className;                    }                    System.out.println("className:"+className)                    findHead = false;                    findClass = false;                    classIndex = -1;                    classIdx = -1;                }            }        } catch (Exception e) {            //异常处理和关闭流            ......        }        return entrance;    }

因为在APP注入补丁时调用dvmResolveClass需要传入一个引用类,这里使用当前dex中和补丁类无调用关系的一个类作为该dex中补丁类的入口类

3.5 生成补丁

经过上面的流程,已经有所有改变的类(补丁类)和补丁类在基础apk的Dex中的class-ids信息(class-ids.txt),生成补丁就直接调用dx工具类即可

def hotfixPatchTask = project.task(hotfixPatch) << {                    //调用dx工具生成apk                ......                AndroidUtils.dex(project, creator.getClassOutDir(variant), creator.sdkDir, pathFilePath)                //签名补丁                SigningConfig signingConfig = variant.signingConfig;                if(signingConfig!=null){                    ......                    if (AndroidUtils.signApk(patchFile, patchSignedFile, signingConfig,compatible))                        patchFile.delete();                }            }

四、补丁客户端应用代码开发

4.1 使用Application代理方式

因为Application类被调起之后我们才有机会去加载我们自己的补丁,而如果将Application调用了某些其他类,这些类的class也可能在我们加载补丁前就已经被加载到ClassLoader中,这样这些类就没有机会被替换了。

为了解决如上这个问题,参考了Tinker的方式,也才用Application代理来解决,将真正的Application逻辑写在代理类中。demo中的Application类如下

public class App extends PatchApplication {    private static final String DELEGATE_NAME = "alexclin.qfix.qfixgradle.AppDelegate";    public App() {        super(DELEGATE_NAME);    }    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        MultiDex.install(base);        File debugPatch = new File(Environment.getExternalStorageDirectory(),"debugPatch.apk");        PatchTool.installPatch(this,debugPatch);    }}

下面是PatchApplication的代码,主要是反射调起Application代理类的对应方法

public abstract class PatchApplication extends Application {    private String delegateClassName;    private ApplicationLifeCycle delegate;    public PatchApplication(String delegateClassName) {        this.delegateClassName = delegateClassName;    }    @Override    public final void onCreate() {        super.onCreate();        ensureDelegate();        delegate.onCreate();    }    @Override    public final void onTerminate() {        super.onTerminate();        if(delegate!=null) delegate.onTerminate();    }    @Override    public final void onConfigurationChanged(Configuration newConfig) {        super.onConfigurationChanged(newConfig);        if(delegate!=null) delegate.onConfigurationChanged(newConfig);    }    @Override    public final void onLowMemory() {        super.onLowMemory();        if(delegate!=null) delegate.onLowMemory();    }    @Override    public final void onTrimMemory(int level) {        super.onTrimMemory(level);        if(delegate!=null) delegate.onTrimMemory(level);    }    private void ensureDelegate(){        if(delegate==null){            try {                Class<?> delegateClass = Class.forName(delegateClassName,false,getClassLoader());                Constructor<?> constructor = delegateClass.getConstructor(Application.class);                delegate = (ApplicationLifeCycle) constructor.newInstance(this);            } catch (Exception e) {                throw new IllegalArgumentException("create app delegate failed",e);            }        }    }}

4.1 反射注入补丁dex到ClassLoader中的数组前面

反射注入补丁dex到ClassLoader中的数组前面的方式和其它ClassLoader方案的注入方式差别不大,入口函数是PatchTool.installPatch(Application application,File patchFile),在此函数中先通过InjectUtil提供的函数注入补丁dex,再使用解析出来的class-id信息调用native函数

注入函数这里只简单列出

   //InjectUtil类代码   //注入补丁Dex    static boolean injectDex(Application context, File patchFile) {        ArrayList files = new ArrayList();        files.add(patchFile);        try {            checkApkFiles(files);            if(isAliyunOs()){   //阿里云OS的注入                for(File file:files){                    injectLexFile(context,file);                }                return true;            }else if(isAndroid()){  //普通android-os的注入                installDex(context,InjectUtil.class.getClassLoader(),context.getDir("dex", 0),files);                return true;            }            throw new IllegalStateException("Current system is not support");        } catch (Exception e) {            e.printStackTrace();            return false;        }    }

4.2 解析补丁apk中的补丁class-id信息

解析补丁apk中class-id信息的逻辑在PatchTool.readPatchClassIds函数中,代码如下

//class-ids.txt中的格式如下 第一行是入口类信息,以:分割,格式:Dex1入口类:Dex2入口类//之后是class-id信息,格式:classname-dexIndex-classIdprivate static List> readPatchClassIds(File patchFile, String defaultEntranceClass){        List> classIds = new ArrayList>();        InputStream inputStream = null;        BufferedReader reader = null;        SparseArray dexEntrances = new SparseArray();        try {            JarFile jarFile = new JarFile(patchFile);            ZipEntry entry = jarFile.getEntry(CLASS_ID_TXT);            inputStream = jarFile.getInputStream(entry);            reader = new BufferedReader(new InputStreamReader(inputStream));            String line;            boolean isFirst = true;            while ((line=reader.readLine())!=null){                if(isFirst){                    isFirst = false;                    if(line.contains(":")){ //解析入口                        String[] entrances = line.split(":");                        for(int i=0;i

然后使用解析出来的class-id信息调用PatchTool.resolvePatchClass函数

private static boolean resolvePatchClass(Application app, String[] referrerClassList, long[] classIdxList, int size) {        if (!sIsLibLoaded) {            sIsLibLoaded = loadPatchToolLib();        }        if (!sIsLibLoaded) {            boolean unloadResult = InjectUtil.unloadPatchElement(app, 0);            Log.e(TAG, "load lib failed, unload patch result=" + unloadResult);            return false;        } else {            int resolveResult = nativeResolvePatchClass(referrerClassList, classIdxList, size);            if (resolveResult != CODE_RESOLVE_PATCH_ALL_SUCCESS) {                //resolve不成功从dex数组中卸载对应dex                boolean unloadResult = InjectUtil.unloadPatchElement(app, 0);                Log.e(TAG, String.format(Locale.ENGLISH,"resolve patch class failed, unload patch result= %b,refClass1:%s",                        unloadResult,referrerClassList[0]));                return false;            } else {                Log.d(TAG, "resolve patch class success");                return true;            }        }    }

4.3 调用nativeResolveClass函数

上一节中最终会调用到nativeResolvePatchClass方法,这个方法是native方法,实现在qfixlib/src/main/jni/ResolvePatch.c中,这个C文件也是直接使用https://github.com/lizhangqu/QFix的中的C文件

jint Java_alexclin_patch_qfix_tool_PatchTool_nativeResolvePatchClass(JNIEnv* env,        jobject thiz, jobjectArray referrerClassList, jlongArray classIdxList, jint size) {    LOGI("enter nativeResolvePatchClass");    int referrerClassSize = (*env)->GetArrayLength(env, referrerClassList);    int classIdxSize = (*env)->GetArrayLength(env, classIdxList);    if (size <= 0 || referrerClassSize != size || classIdxSize != size) {        LOGE("CODE_NATIVE_INIT_PARAMETER_ERROR");        return CODE_NATIVE_INIT_PARAMETER_ERROR;    }    jlong* jClassIdxArray = (*env)->GetLongArrayElements(env, classIdxList, 0);    if (jClassIdxArray == 0) {        LOGE("CODE_NATIVE_INIT_PARAMETER_ERROR");        return CODE_NATIVE_INIT_PARAMETER_ERROR;    }    void* handle = 0;    handle = dlopen("/system/lib/libdvm.so", RTLD_LAZY);    if (handle) {        void* findFunc = 0;        int i = 0;        while(i < ARRAY_SIZE_FIND_CLASS) {            findFunc = dlsym(handle, ARRAY_SYMBOL_FIND_LOADED_CLASS[i]);            if (findFunc) {                break;            }            i++;        }        if (findFunc) {            g_pDvmFindLoadedClass_Addr = findFunc;            void* resolveFunc = 0;            i = 0;            while(i < ARRAY_SIZE_RESOLVE_CLASS) {                resolveFunc = dlsym(handle, ARRAY_SYMBOL_RESOLVE_CLASS[i]);                if (resolveFunc) {                    break;                }                i++;            }            if (resolveFunc) {                g_pDvmResolveClass_Addr = resolveFunc;                i = 0;                while(i < size) {                    jstring jClassItem = (jstring)((*env)->GetObjectArrayElement(env, referrerClassList, i));                    const char* classItem = (*env)->GetStringUTFChars(env, jClassItem, 0);                    if (classItem == 0) {                        (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);                        LOGE("CODE_NATIVE_ITEM_PARAMETER_ERROR=%d", i);                        return NUM_FACTOR_PATCH * i + CODE_NATIVE_ITEM_PARAMETER_ERROR;                    }                    if (strlen(classItem) < 5 || jClassIdxArray[i] < 0) {                        (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);                        (*env)->ReleaseStringUTFChars(env, jClassItem, classItem);                        LOGE("CODE_NATIVE_ITEM_PARAMETER_ERROR=%d", i);                        return NUM_FACTOR_PATCH * i + CODE_NATIVE_ITEM_PARAMETER_ERROR;                    }                    void* referrerClassObj = g_pDvmFindLoadedClass_Addr(classItem);                    if (referrerClassObj) {                        void* resClassObj = g_pDvmResolveClass_Addr(referrerClassObj, (unsigned int)jClassIdxArray[i], 1);                        if (!resClassObj) {                            (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);                            (*env)->ReleaseStringUTFChars(env, jClassItem, classItem);                            LOGE("CODE_PATCH_CLASS_OBJECT_ERROR=%d", i);                            return NUM_FACTOR_PATCH * i + CODE_PATCH_CLASS_OBJECT_ERROR;                        }                    } else {                        (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);                        (*env)->ReleaseStringUTFChars(env, jClassItem, classItem);                        LOGE("CODE_REFERRER_CLASS_OBJECT_ERROR=%d", i);                        return NUM_FACTOR_PATCH * i + CODE_REFERRER_CLASS_OBJECT_ERROR;                    }                    (*env)->ReleaseStringUTFChars(env, jClassItem, classItem);                    i++;                }            } else {                (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);                LOGE("CODE_RESOLVE_CLASS_ERROR");                return CODE_RESOLVE_CLASS_ERROR;            }        } else {            (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);            LOGE("CODE_FIND_LOADED_CLASS_ERROR");            return CODE_FIND_LOADED_CLASS_ERROR;        }    } else {        (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);        LOGE("CODE_LOAD_DALVIK_SO_ERROR");        return CODE_LOAD_DALVIK_SO_ERROR;    }    (*env)->ReleaseLongArrayElements(env, classIdxList, jClassIdxArray, 0);    LOGI("CODE_RESOLVE_PATCH_ALL_SUCCESS");    return CODE_RESOLVE_PATCH_ALL_SUCCESS;}

到这里加载流程就结束了。

五、总结

QFix方案原理和实现相对Tinker方案来说都简单不少,比较轻量级,不过Tinker实现比较重的同时可以实现功能级更新,而QFix方案更多是应对bug的修复。

QFixPatch这个项目算是QFix方案的一个gradle实践,实际开发过程中原创东西不多,主要工作还是在gradle插件。对于Android热修复我也是因为工作中有需求所以了解多一些。项目中有不完善的地方也欢迎各位同学来github提issue.

热修复目前主流是NativeHook,ClassLoader,InstantRun三种方案,NativeHook,ClassLoader在android7.0版本上都会有一些兼容性问题,相对来说InstantRun方案兼容性会更好,目前我也在研究学习Robust和Aceso的路上,欢迎有兴趣的同学一起来探讨。

本人Github:https://github.com/alexclin0188 欢迎关注

Stashed changes

更多相关文章

  1. 一种粗暴快速的Android全屏幕适配方案
  2. Android 使用adb shell 和android studio插件Database Navigator
  3. Android 动画方案
  4. ubuntu下安装android ADT 插件
  5. android组件化方案、二维码扫码、Kotlin新闻客户端、动画特效等
  6. 【Android】编译CM10遇到的错误解决方案
  7. android bitmap溢出问题完美解决方案
  8. Android Studio中Android Parcelable code generator插件的使用
  9. android和java平台统一的DES加密解决方案,解决加密不一样的问题

随机推荐

  1. Android(安卓)Suspend
  2. android中的事件总线
  3. android push机制-C2DM
  4. android 联系人数据库
  5. Android系统应用调用Aidl
  6. Android(安卓)安全机制
  7. CyanogenMod源码下载和编译(Android(安卓
  8. Android关于ThreadLocal的思考和总结
  9. 【翻译】Android多线程下安全访问数据库
  10. OpenGL ES之GLSurfaceView学习四:Android(