andfix热修复核心代码解析

前言

https://ke.qq.com/webcourse/index.html#course_id=130901&term_id=100146035&taid=1287279008153429&vid=r1417pykrgc
学习了下这个视频的热修复方法。成功实现,但是还是遇到了一些问题。这里记录下,帮助大家学习。

代码分析

首先先简单的代码分析一下。

//问题代码
class Calculator {
    fun calc(): Int {
        return 100 / 0
    }
}

//修复后代码
class Calculator {
    @Replace(clazz = "**.Calculator", method = "calc")
    fun calc(): Int {
        return 100
    }
}

原理:替换Art虚拟机中有问题的类的方法,为修复后的方法。(Android 5.0以上)
视频中已有详细介绍,这里就不多赘述了。

fun loadDex(context: Context, dexName: String) {
        val cacheFile: File
        if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
            cacheFile = context.externalCacheDir
        } else {
            cacheFile = context.cacheDir
        }
        val file = File(cacheFile, "dxtest")
        file.deleteOnExit()
        val dexFile = DexFile(File("/storage/emulated/0/", dexName).absolutePath)

        val iterator = dexFile.entries()
        while (iterator.hasMoreElements()) {
            val clazzName = iterator.nextElement()
            val clazz = Class.forName(clazzName)
            //遍历类方法根据注解替换
            clazz.declaredMethods.forEach { method ->
                val replacObj = method.getAnnotation(Replace::class.java)
                val replaceClazz = Class.forName(replacObj.clazz)
                val replaceMethod = replaceClazz.getDeclaredMethod(replacObj.method, *method.parameterTypes)
                fixMethod(replaceMethod, method)
            }
        }
    }
....
private external fun fixMethod(replaceMethod: Method, method: Method)

先单独把修复后的Calculator.class打包,利用dx命令打包成out.dex,然后放到/storage/emulated/0/目录下。

JNIEXPORT void JNICALL
Java_**_fixMethod(JNIEnv *env, jobject thiz,jobject replaceMethod, jobject method) {
    art::mirror::ArtMethod *artReplaceMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
            replaceMethod);

    art::mirror::ArtMethod *artMethod = (art::mirror::ArtMethod *) env->FromReflectedMethod(
            method);

    artReplaceMethod->access_flags_ = artMethod->access_flags_;
    artReplaceMethod->declaring_class_ = artMethod->declaring_class_;
    artReplaceMethod->dex_code_item_offset_ = artMethod->dex_code_item_offset_;
    artReplaceMethod->dex_method_index_ = artMethod->dex_method_index_;
    artReplaceMethod->hotness_count_ = artMethod->hotness_count_;
    artReplaceMethod->method_index_ = artMethod->method_index_;
    artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_methods_ = artMethod->ptr_sized_fields_.dex_cache_resolved_methods_;
    artReplaceMethod->ptr_sized_fields_.dex_cache_resolved_types_ = artMethod->ptr_sized_fields_.dex_cache_resolved_types_;
    artReplaceMethod->ptr_sized_fields_.entry_point_from_jni_ = artMethod->ptr_sized_fields_.entry_point_from_jni_;
    artReplaceMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_ = artMethod->ptr_sized_fields_.entry_point_from_quick_compiled_code_;
}

按照视频中所说的,把ArtMehod中的变量一一替换。

问题记录

  1. 困扰我最久的是,访问sdcard,6.0以上需要手动申请,这也是自己粗心。视频中貌似也没有详细提到。而且最让我头疼的是logcat中的报错信息。
Caused by: java.io.IOException: No original dex files found for dex location /storage/emulated/0/out.dex
...

于是开始百度,google

《andfix热修复核心代码解析》 image.png

没有讲到权限问题的。
这里值得一提的是曾今在使用VirtualApk插件化技术的时候,也是遇到这种问题,必须要手到申请下权限,不然就会报错。

后面灵光一闪。添加了权限申请就好了。

  1. 视频中提到了需要使用到art_method.h,但是里面的东西又不能全部拿来用,因为会涉及到很多其他的头文件,所以只要ArtMethod的结构体就好了。
    android源码很大,这里我没有去下载,直接访问http://androidxref.com/7.0.0_r1/xref/art/runtime/art_method.h#mirror
    查看自己需要的内容。
/*
 * Copyright (C) 2011 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef ART_RUNTIME_ART_METHOD_H_A
#define ART_RUNTIME_ART_METHOD_H_A


namespace art {

    union JValue;

    class OatQuickMethodHeader;

    class ProfilingInfo;

    class ScopedObjectAccessAlreadyRunnable;

    class StringPiece;

    class ShadowFrame;

    namespace mirror {
        class Array;

        class Class;

        class IfTable;

        class PointerArray;
        
        class ImtConflictTable {
            enum MethodIndex {
                kMethodInterface,
                kMethodImplementation,
                kMethodCount,  // Number of elements in enum.
            };

        private:
            union {
                uint32_t data32_[0];
                uint64_t data64_[0];
            };

        };

        class ArtMethod {
        public:
            uint32_t declaring_class_;
            uint32_t access_flags_;
            uint32_t dex_code_item_offset_;
            uint32_t dex_method_index_;
            uint16_t method_index_;
            uint16_t hotness_count_;
            struct PtrSizedFields {
                ArtMethod **dex_cache_resolved_methods_;
                uint32_t *dex_cache_resolved_types_;
                void *entry_point_from_jni_;
                void *entry_point_from_quick_compiled_code_;
            } ptr_sized_fields_;

        };

    }
} // namespace art

#endif  // ART_RUNTIME_ART_METHOD_H_A

我这里去掉了一些不需要的内容,最重要的是要把ArtMethod结构体中的参数列出来。

  1. 测试,在华为荣耀v9中可以。在内置的android虚拟机中,debug模式下可以,正常运行后无法替换,比较奇怪,后续有发现原因,再补充。
    原文作者:javalong
    原文地址: https://www.jianshu.com/p/3a1c4c89225a
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞