Desarrollo de Android: análisis del principio de Hotfix Tinker

Arreglo caliente

En la actualidad, se ha desarrollado la tecnología doméstica de reparación en caliente de Android. Se puede decir que florecen cien flores. Desde el método de implementación, se puede clasificar aproximadamente en:

  1. Implementación de capa nativa
  2. Implementación de la capa de Java

Alguien ha analizado brevemente el Andfixprincipio de implementación del código abierto de Alibaba (basado en la capa nativa) antes, así que no hablaré más sobre esto aquí. Puedes buscarlo.

Este artículo analiza brevemente la capa de Java para implementar la lógica de reparación en caliente, y simplemente implementa el código de demostración de reparación en caliente, tomando a Tinker como ejemplo (por supuesto, Tinkeres compatible 代码修复,资源修复,so修复, y los amigos interesados ​​se trasladan al sitio web oficial por sí mismos ~)

Primero clasifique las ideas:

Proceso de compilación de la clase Java

Es el proceso de compilar la clase java en un archivo .class a través de javac, y luego compilarlo en un archivo .dex mediante dx.bat, sin entrar en detalles, simplemente dibuje una imagen ~

Introducción a ClassLoader

La clase java.lang.ClassLoader en Android también es diferente de la clase java.lang.ClassLoader en Java. El tipo ClassLoader en Android también se puede dividir en ClassLoader del sistema y ClassLoader personalizado. El sistema ClassLoader incluye 3 tipos:

  • BootClassLoader, El sistema Android usará BootClassLoader para precargar las clases de uso común al iniciarse. A diferencia de Bootstrap ClassLoader en Java, no se implementa mediante código C / C ++, sino mediante Java. BootClassLoader es una clase interna de ClassLoader.
  • PathClassLoader, El nombre completo es dalvik / system.PathClassLoader, puede cargar el Apk instalado, que es el archivo apk en / data / app / package, o puede cargar nativeLibrary en / vendor / lib, / system / lib.
  • DexClassLoader, El nombre completo es dalvik / system.DexClassLoader, puede cargar un archivo apk desinstalado. PathClassLoader y DexClasLoader se heredan de dalviksystem.BaseDexClassLoader, y su lógica de carga de clases está escrita en BaseDexClassLoader. La siguiente figura muestra el sistema de herencia en ClassLoader en Android. Entre ellos, SecureClassLoader y UrlClassLoader son cargadores de clases en Java y no se pueden usar en Android.

carga de archivo .dex

Se sabe por el código fuente que el .dexarchivo se BaseDexClassLoader类(ClassLoader的子类)carga a través de, hay una variable miembro en esta clase DexPathList对象y hay una matriz en este objeto que almacena el DexElementobjeto, es decir, el archivo cargado desde el .dexarchivo, el punto de entrada es aquí

Para los proyectos, se subcontratará el proyecto general (cuando el número de métodos sea superior a 64k y cuando el número de métodos sea superior a 65535, la estrategia de subcontratación proporcionada por Google), si se utiliza código Java para implementar la reparación en caliente, subcontratación debe hacerse, porque es necesario asegurarse de que el paquete principal no tenga errores, y el subpaquete simplemente significa que el apk empaquetado generalmente tiene varios .dexarchivos

Tales como: classes.dex, classes2.dex, etc.

Entonces, por ejemplo classes2.dex, si un método de una de nuestras clases es anormal, podemos crear un paquete de reparación ( classes2.dexarchivo reparado ) y luego classes2.dexcopiar el archivo reparado al directorio privado a través de un cargador de clases personalizado , y luego saltar a la cola 系统ClassLoaderde dexPathList对象la dexElementmatriz, de modo que la prioridad del sistema cargue 修复后的classes2.dexel archivo, para lograr el propósito de las correcciones urgentes, esta implementación debe realizar una lógica de reparación para reiniciar la aplicación para lograr el efecto ~

Después de comprender esta información, la idea general está ahí. Necesitamos cargar y analizar el archivo .dex reparado, y luego saltar el archivo .dex de instalación y empaquetado anterior para realizar la operación de salto, lo que equivale a engañar al sistema Android, que es aproximadamente como sigue:

Principio de implementación

Pensando en ello, necesitamos corregir un error del archivo .dex, saltar la cola BaseDexClassLoader类en DexPathList对象la DexElementmatriz y ordenar al frente, de modo que el sistema se cargue en el archivo .dex después de que arreglemos el error no tendrá que cargar archivos dex, Complete Jump in the queue (instrumentación), habrá un conocimiento del mecanismo de carga de clases. Este artículo no lo presentará en detalle, y escribiré un resumen más adelante ~ Los pasos generales de implementación son los siguientes:

Implementación de demostración

1. Configuración básica: configuración del paquete principal

Configurar la subcontratación. El propósito de configurar la subcontratación es principalmente empaquetar el apk que tendrá varios archivos .dex. En la aplicación del proyecto real, asegúrese de que el paquete principal no tenga errores. Al cargar el archivo .dex en la demostración, el también se excluye archivo de paquete. classes.dex, de la siguiente manera: Crear BaseApplication, BaseActivity, MainActivitycolocadas en la bolsa principal, que MainActivityes principalmente sub huella, solamente un sub-haz click para saltar en SecondActivityla aplicación directorio lógico build.gradleabierta sub-soporte, el androiddefaultConfigconfiguración de aumento, en el que multiDex-config.txtse arreglado Mantenga los archivos de clase en el paquete principal

 //开启分包
        multiDexEnabled true
        //分包的配置,将配置文件中的放置在主包
        multiDexKeepFile file("multiDex-config.txt")

Agregue dependencias de subpaquetes:

  //multidex分包依赖
    implementation 'com.android.support:multidex:1.0.3'

Aplicación abre subcontratación:

public class BaseApplication extends MultiDexApplication {
    
    
    @Override
    public void onCreate() {
    
    
        super.onCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
    
    
        super.attachBaseContext(base);
        //安装分包配置
        MultiDex.install(this);
    }
}

2. Configuración de subcontratación

La subcontratación crea una SecondActivity类entrada para simular anomalías y reparar anomalías, y una Calculateanomalía simulada, 10/0las operaciones realizadas, una vez finalizada la reparación.10/1

Nota: El classes2.dexarchivo reparado se puede obtener descomprimiéndolo directamente por buildapk, o dx.batejecutando el comando en build-tools

Simplemente pegue el código después de SecondActivityhacer clic en el fixbotón:

  private void update() {
    
    
        //将下载的修复包,复制到私有目录,解压从.dex文件中取到对应的.class文件
        //从sd卡取修复包
        File sourceFile = new File(Environment.getExternalStorageDirectory(), Constants.DEX_NAME);
        //目标文件
        File targetFile = new File(getDir(Constants.DEX_DIR, Context.MODE_PRIVATE).getAbsolutePath() + File.separator + Constants.DEX_NAME);
        if (targetFile.exists()) {
    
    
            targetFile.delete();
            Log.e("update","删除原有dex文件(已使用的)");
        }
        //将SD卡中的修复包copy到私有目录
        FileUtils.copyFile(sourceFile,targetFile);
        Log.e("update","copy完成");
        FixDexUtils.loadDexFile(this);
    }

3 、 FixModule

Cree un nuevo módulo para manejar la lógica relacionada de reparación en caliente

Solo hay cinco archivos, el código del archivo principal está allí FixDexUtils, los otros son clases de herramientas y hay FixDexUtilsun código que define varias constantes .

public class FixDexUtils {
    
    

    //修复文件可能有多个
    private static HashSet<File> loadedDex = new HashSet<>();

    //不建议这么写,demo演示用
    static {
    
    
        loadedDex.clear();
    }

    public static void loadDexFile(Context context) {
    
    
        //获取私有目录
        File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
        //遍历筛选私有目录中的.dex文件
        File[] listFiles = fileDir.listFiles();

        for (int i = 0; i < listFiles.length; i++) {
    
    
            //文件名以.dex结尾,且不是主包.dex文件
            if (listFiles[i].getName().endsWith(Constants.DEX_SUFFIX) && !"classes.dex".equalsIgnoreCase(listFiles[i].getName())) {
    
    
                loadedDex.add(listFiles[i]);
            }
        }
        //创建自定义的类加载器
        createDexClassLoader(context ,fileDir);
    }

    /**
     * @param context
     * @param fileDir
     * 创建自己的类加载器,加载私有目录的.dex文件,上面已经将修复包中的dex文件copy到私有目录
     */
    private static void createDexClassLoader(Context context, File fileDir) {
    
    
        //解压目录
       String optimizedDir = fileDir.getAbsolutePath()+File.separator+"opt_dex";
        File fileOpt = new File(optimizedDir);
        if (!fileOpt.exists()) {
    
    
            fileOpt.mkdirs();
        }

        for (File dex : loadedDex) {
    
    
            //创建自己的类加载器,临时的
            DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), optimizedDir, null, context.getClassLoader());
            //有一个修复文件,就插装一次
            hotFix(classLoader,context);
        }
    }

    private static void hotFix(DexClassLoader classLoader, Context context) {
    
    
        try {
    
    
            //获取系统的PathClassLoader类加载器
            PathClassLoader pathClassLoader = (PathClassLoader)context.getClassLoader();
            //获取自己的dexElements数组

            Object myElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));
            //获取系统的dexElements数组
            Object systemElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathClassLoader));
            //合并数组,并排序,生成一个新的数组
            Object dexElements=ArrayUtils.combineArray(myElements,systemElements);
            //通过反射获取系统的pathList属性
            Object systemPathList = ReflectUtils.getPathList(pathClassLoader);
            //通过反射,将合并后新的dexElements赋值给系统的pathList
            ReflectUtils.setFieldValue(systemPathList,"dexElements",dexElements);
        }catch (Exception e){
    
    
            e.printStackTrace();
        }
    }
}

El trabajo principal es: el diagrama de flujo anterior es obtener primero la .dexcolección de archivos que necesita ser reparada en caliente a través de operaciones como recorrido, descompresión, etc. , atravesar la colección, crear una temporal cada vez DexClassLoadery luego realizar los pasos de reparación. La división es de seis pasos:

El efecto final se muestra en la figura (el teléfono móvil utilizado en la demostración es un teléfono móvil Huawei 8.0):

Nota: Para que el renderizado sea más intuitivo, la aplicación se ha reiniciado una vez.
Nota: La reparación en caliente implementada de esta manera debe reiniciar la aplicación para lograr la reparación. Esto también está determinado por el mecanismo de carga de clases. Después de la reparación es como se muestra en la siguiente figura, abra la ejecución de carga nuevamente El classes.dexarchivo reparado se BaseApplicationllama método de reparación en

Al final

Aquí también comparto un PDF de aprendizaje de Android + video de arquitectura + documento de entrevista + notas de origen , mapa mental avanzado de tecnología de arquitectura avanzada, materiales especiales de entrevistas de desarrollo de Android, materiales de arquitectura avanzada avanzada recopilados y organizados por varios grandes .

Si tiene una necesidad, puede señalarlo para recibir

Si te gusta este artículo, también puedes darme un pequeño me gusta, dejar un mensaje en el área de comentarios o reenviarlo y apoyarlo ~

Supongo que te gusta

Origin blog.csdn.net/River_ly/article/details/106816634
Recomendado
Clasificación