Android development: principle analysis of hot fix Tinker

Hot fix

At present, the domestic Android hot repair technology has been developed. It can be said that a hundred flowers bloom. From the implementation method, it can be roughly classified into:

  1. Native layer implementation
  2. Java layer implementation

Someone has briefly analyzed the Andfiximplementation principle of Alibaba's open source (based on the Native layer) before, so I won't say more about it here. You can search for it.

This article briefly analyzes the Java layer to implement the hot repair logic, and simply implements the code hot repair Demo, taking Tinker as an example (of course it Tinkeris supported 代码修复,资源修复,so修复, and interested friends move to the official website by themselves~)

First sort out the ideas:

Java class compilation process

It is the process of compiling the java class into a .class file through javac, and then compiling into a .dex file by dx.bat, without going into details, simply draw a picture~

Introduction to ClassLoader

The java.lang.ClassLoader class in Android is also different from the java.lang.ClassLoader in Java. The ClassLoader type in Android can also be divided into system ClassLoader and custom ClassLoader. The system ClassLoader includes 3 types:

  • BootClassLoader, Android system will use BootClassLoader to pre-load commonly used classes when starting up. Unlike Bootstrap ClassLoader in Java, it is not implemented by C/C++ code, but by Java. BootClassLoader is an internal class of ClassLoader.
  • PathClassLoader, The full name is dalvik/system.PathClassLoader, you can load the installed Apk, which is the apk file under /data/app/package, or you can load the nativeLibrary under /vendor/lib, /system/lib.
  • DexClassLoader, The full name is dalvik/system.DexClassLoader, you can load an uninstalled apk file. PathClassLoader and DexClasLoader are both inherited from dalviksystem.BaseDexClassLoader, and their class loading logic is all written in BaseDexClassLoader. The following figure shows the inheritance system in ClassLoader in Android. Among them, SecureClassLoader and UrlClassLoader are class loaders in Java and cannot be used in Android.

.dex file loading

It is known from the source code that the .dexfile is BaseDexClassLoader类(ClassLoader的子类)loaded through, there is a member variable in this class DexPathList对象, and there is an array in this object that stores the DexElementobject, that is, the file loaded from the .dexfile, the entry point is here

For projects, the general project will be subcontracted (when the number of methods is greater than 64k, and when the number of methods is greater than 65535, the subcontracting strategy provided by Google), if Java code is used to implement hot repair, subcontracting must be done, because it is necessary to ensure that the main The package has no bugs, and the sub-package simply means that the packaged apk generally has multiple .dexfiles

Such as: classes.dex,classes2.dex etc...

So for example classes2.dex, if a method of one of our classes is abnormal, we can create a repair package (repaired classes2.dexfile), and then classes2.dexcopy the repaired file to the private directory through a custom class loader , and then jump in the queue to 系统ClassLoaderof dexPathList对象the dexElementarray, so that the system priority load 修复后的classes2.dexthe file, in order to achieve the purpose of hot fixes, this implementation must perform a repair logic to restart the app in order to achieve the effect ~

After understanding this information, the general idea is there. We need to load and analyze the repaired .dex file, and then jump the old installation and packaging .dex file to do the jump operation, which is equivalent to cheating the Android system, which is roughly as follows:

Implementation principle

Thinking about, we need to fix a bug of .dex file, to jump the queue BaseDexClassLoader类in DexPathList对象the DexElementarray, and sorting to front, so that the system is loaded into .dex file after we fix the bug will not have to load dex files, complete Jump in the queue (instrumentation), there will be a knowledge of the class loading mechanism. This article will not introduce it in detail. A separate summary will be written later~ The general implementation steps are as follows:

Demo implementation

1. Basic configuration-main package configuration

Configure subcontracting. The purpose of configuring subcontracting is mainly to package the apk that will have multiple .dex files. In the actual project application, ensure that the main package does not have bugs. When loading the .dex file in the demo, the main package file is also excluded. classes.dex, as follows: Create BaseApplication, BaseActivity, MainActivityplaced in the main bag, which MainActivityis mainly to sub footprint, only a sub-click to jump in SecondActivitythe logical directory app build.gradleopen sub-support, the androiddefaultConfigincrease configuration, wherein multiDex-config.txtis arranged Keep class files in the main package

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

Add sub-package dependencies:

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

Application opens subcontracting:

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

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

2. Subcontracting configuration

The subcontracting creates an SecondActivity类entry to simulate anomalies and repair anomalies, and a Calculatesimulated anomaly, 10/0the operations done, after the repair is10/1

Note: The repaired classes2.dexfile can be obtained by directly decompressing directly by buildapk, or by dx.batexecuting the command under build-tools

Simply paste the code after SecondActivityclicking the fixbutton:

  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

Create a new Module to handle the related logic of hot repair

There are only five files, the core file code is there FixDexUtils, the others are tool classes, and there FixDexUtilsis a code that defines several constants .

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();
        }
    }
}

The main work is: the above flowchart is to first obtain the .dexfile collection that needs to be hot repaired through operations such as traversal, decompression, etc. , traverse the collection, create a temporary one each time DexClassLoader, and then perform the repair steps. The division is six steps:

The final effect is shown in the figure (the mobile phone used in the Demo is a Huawei 8.0 mobile phone):

Note: In order to make the rendering more intuitive, the app has been restarted once.
Note: The hot repair implemented in this way must restart the App to achieve the repair. This is also determined by the class loading mechanism. After the repair is shown in the following figure, open the loading execution again The repaired classes.dexfile is BaseApplicationcalled the repair method in

At last

Here I also share an Android learning PDF+architecture video+interview document+source notes , advanced architecture technology advanced mind map, Android development interview special materials, advanced advanced architecture materials collected and organized by several big guys .

If you have a need, you can point it to receive

If you like this article, you might as well give me a thumbs-up, leave a message in the comment area or forward and support it~

Guess you like

Origin blog.csdn.net/River_ly/article/details/106816634