Principle of Android Thermal Repair

Keywords: Mobile Android Google Java

This paper is a brief introduction to the principle of thermal repair in the series of "Tamping Foundation in 2018"
Author: Bob

Background

(1) Why is there thermal repair technology?

Everyone is developing, so we should all know that there is one thing we can never avoid. Good, ** Bug! ** It's good that we encounter bugs in the development phase. It's just a straightforward solution. It's no big deal to let the test run one more round. But what if the version we sent out appears online Bug? Most small companies may choose to redistribute a new version to override the installation. This approach is expensive, and users generally hate the frequent need to update versions of APP. For small companies, the impact of online problems is relatively small, after all, fewer users. However, for some giant companies, some problems can not be repaired in time, which is fatal. So, there are some companies with high technology reserve and ability that have pioneered us.

Pay tribute to our ancestors!

For giant companies such as mobile QQ space, Alipay and 360, of course, it is intolerable that online Bug should be solved. So they came up with a technical solution for thermal repair. Most of Android's hot fix technology comes from this.

(3) Brief introduction of principle

Having said so much, what kind of technology is thermal repair?
Before we talk about hot fixes, we need to know that the app running on our mobile phone is actually the Java code we write compiled into class files, and then packaged into dex files to run on the phone. That is to say, dex files are actually running in our mobile phones. So, if our APP has an online problem, 90% of the probability is that the code we write causes a Bug, that is, the dex file running on the phone appears a Bug. So the idea of hot fix is to silently download the new dex file on the server to replace the defective dex file.

II. Realization Principle of Thermal Repair

Replacing dex files is easy to say, but we still need to consider more details when it comes to reality. Next, we will focus on the detailed analysis of the replacement dex file to achieve the steps:

Dex subcontracting

We know that at the beginning (ART hasn't been launched yet), Android used the Dalvik virtual machine to run our application. When the Android project packaged the APK, it packaged all the compiled class files into dex files. And the Dalvik Virtual Machine optimizes the dex file through the DexOpt tool when we install the application. DexOpt has a disadvantage that it retrieves all the method ID s of the classes in the dex when it is executed. There is a linked list, and the length of the linked list defines the type of short, which leads to the total number of methods in the dex file can not exceed the scope of short, that is, it can not exceed the scope of short. Over 65536. Obviously, when our project is relatively large, this method is not enough. So Android later launched ART, which itself supports multiple dex APK.

With all that said, what does DEX subcontracting have to do with the thermal repair we're talking about here? We've already talked about the idea of replacing dex. If there's only one DEX and we don't split it, obviously we can't replace it. If we want to replace the DEX that happened to Bug, we must first have the DEX code that works properly to replace it. Therefore, we will take out a relatively stable master DEX as the code to implement downloading and replacing actions from the server. When other modules appear Bug, the DEX file of the corresponding module is updated. So how do we usually split dex?

Before Android 5.0, we need to introduce multidex.jar support from Google. So we need to add the following configuration in the project build.gradle first.

android {
    defaultConfig {
        multiDexEnabled true
    }
}

dependencies {
    compile 'com.android.support:multidex:1.0.0'
}

Then we use DexKnife, a useful third-party library for configuring subcontracting, to configure how to subcontract. DexKnife is not detailed here. Readers can go to DexKnife. Dexknife's Usage Document See the details of how to use it.

(2) Replace dex files

The logic of replacing dex files is usually that after we enter the main page, we request an interface to query the server whether there is a new dex that needs to be replaced at present, and if there is one, we will replace it after downloading silently in the background. Of course, there are some version, dex name and other parameters to distinguish. How to download files is beyond the scope of this article, I believe readers have done it. This article mainly explains how to replace a dex package when we download it from the server.

Before we talk about replacement, let's first understand how our packaged DEX files are loaded into the virtual machine. Android uses BaseDexClassLoader to load classes in dex, so let's analyze the source code of BaseDexClassLoader.

private final DexPathList pathList;

In this class, we first see a wrapper class DexPathList to store the list of dex files that need to be loaded. We continue to look at the source code of DexPathList and find that:

    /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;

The way to store dex files in an array of dexElements is interesting for Google engineers at that time. It's funny to write in all the reflection calls Facebook would use. Ha-ha, naughty. Then I'll see how he put the directory of dex files into the dexElements array.

    public DexPathList(ClassLoader definingContext, ByteBuffer[] dexFiles) {
    	...ellipsis
        // TODO calls the makePathElements() method here
        this.nativeLibraryPathElements = makePathElements(this.systemNativeLibraryDirectories);
		...ellipsis
       
    }
    
    // Let's continue to see what makePathElements did.
    @SuppressWarnings("unused")
    private static Element[] makePathElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions) {
        // The makeDexElements method is called directly here.
        return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
    }
    
    // Continue tracking the makeDexElements method
     private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
            List<IOException> suppressedExceptions, ClassLoader loader) {
      Element[] elements = new Element[files.size()];
      int elementsPos = 0;
      /*
       * Open all files and load the (direct or contained) dex files up front.
       */
      for (File file : files) {
          if (file.isDirectory()) {
              // We support directories for looking up resources. Looking up resources in
              // directories is useful for running libcore tests.
              elements[elementsPos++] = new Element(file);
          } else if (file.isFile()) {
              String name = file.getName();

              if (name.endsWith(DEX_SUFFIX)) {
                  // Raw dex file (not inside a zip/jar).
                  try {
                      DexFile dex = loadDexFile(file, optimizedDirectory, loader, elements);
                      if (dex != null) {
                          elements[elementsPos++] = new Element(dex, null);
                      }
                  } catch (IOException suppressed) {
                      System.logE("Unable to load dex file: " + file, suppressed);
                      suppressedExceptions.add(suppressed);
                  }
              } else {
                  DexFile dex = null;
                  try {
                      dex = loadDexFile(file, optimizedDirectory, loader, elements);
                  } catch (IOException suppressed) {
                      /*
                       * IOException might get thrown "legitimately" by the DexFile constructor if
                       * the zip file turns out to be resource-only (that is, no classes.dex file
                       * in it).
                       * Let dex == null and hang on to the exception to add to the tea-leaves for
                       * when findClass returns null.
                       */
                      suppressedExceptions.add(suppressed);
                  }

                  if (dex == null) {
                      elements[elementsPos++] = new Element(file);
                  } else {
                      elements[elementsPos++] = new Element(dex, file);
                  }
              }
          } else {
              System.logW("ClassLoader referenced unknown path: " + file);
          }
      }
      if (elementsPos != elements.length) {
          elements = Arrays.copyOf(elements, elementsPos);
      }
      return elements;
    }

We can see that the source code is to encapsulate dex files as Element objects stored in arrays. So, big companies like Facebook or Alibaba or Tencent see the essence of Android loading classes by traversing dex files in an array of dexElements and loading them if they find one. So, our idea is to put our new repaired Bug dex file in the front of dexElements, then when the system loads our wrong classes, it will first load our repaired classes, thus playing the role of repairing Bug.

So, what do you usually do? First, we exemplify a BaseDexClassLoader class to load the DEX file we downloaded from the server into memory. Of course, all this needs reflection to get the dexElements array in the DexPathList class, and then load our DEX file into an Element object. Then, we use reflection to get our new dexElements array in APP itself. Element is put at the front. This allows the new DEX to be loaded prior to the Dex with Bug.

Three, summary

This section is mainly about the context of using thermal repair, and we find out the reason why we can achieve thermal repair through source code analysis. We have also introduced to you what technical points you need to know to use thermal repair and the more successful companies in the industry. In the next section, we will focus on the principles of this section and analyze a framework for simple implementation of hot repair in the form of demo, so that readers can easily implement it themselves.

Welcome to pay attention to forwarding and improve it together.~

Wechat Public Number: Android Tribe in Nanjing

Posted by joking on Thu, 16 May 2019 19:16:00 -0700