preface

(Nonsense) Recently I found a problem, some programmers who usually write a lot of blog, but in their daily work, they write mediocre business. They are only good at explaining theories, and even teach others how to find a job and how to do architecture, but they don’t understand it themselves. But layering of audience, led to the exporter, the teacher who teaches students off north doesn’t necessarily come from better schools, therefore, for the output of the blog, one is as a record of his own learning, very careful combing can very convenient, let’s pick up when needed, and even if the knowledge now need not, unable to go further, May be forgotten more quickly, and its details may forget, but the core thought we still have the impression, and stand in the perspective of the reader, because of the difference of readers of our blog on the premise of guarantee correct, must be can help to many students, in line with these principles, the output of a report on Monday, hope you can stick to it.

(Topic) Recently, I have been doing some research on hotfix, and then I will write some articles on this blog. Today, I will analyze the simplest Nvwa in ClassLoader solution. This article will start from the class search process to the implementation of Nvwa, and what problems are solved during implementation. Step by step.

Based on using

Initialize the

Nuwa.init(this);
Copy the code

Loading the patch pack

Nuwa.loadPatch(this,patchFile)
Copy the code

Source code analysis

Type of lookup

For class loading, when loading through DexClassLoader, load through DexPathList, which maintains an array of elements. When searching for a class, it will search through the number group to find the class, and return if found. The code for array traversal lookup is shown below.

public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if(dex ! = null) { Class clazz = dex.loadClassBinaryName(name, definingContext);if(clazz ! = null) {returnclazz; }}}return null;
}
Copy the code
  • Initialization of Nvwa
public static void init(Context context) { File dexDir = new File(context.getFilesDir(), DEX_DIR); dexDir.mkdir(); String dexPath = null; Try {// copy hack. apk from the Asset directory to the specified path dexPath = assetutils. copyAsset(context, HACK_DEX, dexDir); } catch (IOException e) { e.printStackTrace(); } // Load apK loadPatch(context, dexPath) from the specified path after the copy; }Copy the code

What you do in nvWAw’s init method is copy a hack.apk from asset and load it as a patch.

  • Patch loading
public static void loadPatch(Context context, String dexPath) {

    if (context == null) {
        return;
    }
    if(! new File(dexPath).exists()) {return; } File dexOptDir = new File(context.getFilesDir(), DEX_OPT_DIR); dexOptDir.mkdir(); try { DexUtils.injectDexAtFirst(dexPath, dexOptDir.getAbsolutePath()); } catch (Exception e) { e.printStackTrace(); }}Copy the code

From the above code, we can see that the core implementation of the DexUtils injectDexAtFirst call.

public static void injectDexAtFirst(String dexPath, String defaultDexOptPath) throws NoSuchFieldException, IllegalAccessException, ClassNotFoundException {
    DexClassLoader dexClassLoader = new DexClassLoader(dexPath, defaultDexOptPath, dexPath, getPathClassLoader());
    Object baseDexElements = getDexElements(getPathList(getPathClassLoader()));
    Object newDexElements = getDexElements(getPathList(dexClassLoader));
    Object allDexElements = combineArray(newDexElements, baseDexElements);
    Object pathList = getPathList(getPathClassLoader());
    ReflectionUtils.setField(pathList, pathList.getClass(), "dexElements", allDexElements);
}
Copy the code

Incorporating two Dex, Dex will patch in front of the array, and then set up by means of reflection, in this way, according to the above class loading logic, can know, for a class of loaded from an array of the most began to find the location of the load, the current the Dex lookup to the corresponding class, will stop at the back of the lookup, so, The classes we replace through the patch will take effect.

private static Object combineArray(Object firstArray, Object secondArray) { Class<? >localClass = firstArray.getClass().getComponentType();
    int firstArrayLength = Array.getLength(firstArray);
    int allLength = firstArrayLength + Array.getLength(secondArray);
    Object result = Array.newInstance(localClass, allLength);
    for (int k = 0; k < allLength; ++k) {
        if (k < firstArrayLength) {
            Array.set(result, k, Array.get(firstArray, k));
        } else{ Array.set(result, k, Array.get(secondArray, k - firstArrayLength)); }}return result;
}
Copy the code

There is a problem

In this way, we put the differential patch in a separate package and deliver it so that our class can be fixed. However, in this implementation, there is a problem, which is an optimization of dex in Dalvik VIRTUAL machine. When Dalvik vm is started, it will have many startup parameters, one of which is verify. When Verify is turned on, the doVerify variable is true, and the class is verified (dvmVerifyClass method call). If the verification is successful, the class is marked CLASS_ISPREVERIFIED.

This is a security solution to prevent external DEX injection, that is, to ensure that the DEX relationship between the runtime Class and its direct reference Class is the same as at installation time, and to prevent Class tampering to verify the validity of the Class. During the installation of the Dalvik VM, CLASS_ISPREVERIFIED for the Class is to improve the performance. The verification operation is not required the next time the Dalvik VM is used to improve the access efficiency. When the DVM loads the Class during runtime, it verifies the reference Class in its memory. If the Class does not have the same dex as the reference Class, an error “pre-verification” is reported, and the Class cannot be loaded.

Due to this limitation, our patch package cannot raise an exception when it is called. Therefore, we need to make sure that our patch package is not verified by CLASS_ISPREVERIFIED this time, so that our patch package will not raise an exception when it is loaded.

The nvWA approach is to peg each class in a separate dex and reference it to the hack.class in hack.apk loaded at initialization, so that our class is not tagged with this label. This allows you to continue loading classes in other Dex.

What’s the problem with piling? Each class is loaded with validation tags because there is no validation tag.

Wechat in the inserted and uninserted pile test. In the continuous loading of 700 50-line classes, and the statistics of the application startup time data, 700 classes: unplugged: 84ms, plugged: 685ms. Startup time: 4934ms, 7240ms.

conclusion

Every Monday, due to the recent business needs, the update speed is obviously much slower, so this analysis is also a very simple framework. Next, we will step in and analyze some of the more complex thermal repair solution frameworks.

The resources

PreVerify problem in Dalvik