Android Thermal Repair Technology: Analysis of QQ Space Patch Scheme (1)

Keywords: Android Google jvm

In the traditional app development mode, online bugs occur, which can only be repaired after users manually update the new version. As app's business becomes more and more complex, the amount of code explosively grows, and the probability of bugs increases. If the online bugs are repaired only by distribution, the longer coverage period of the new version will undoubtedly cause great harm to the business, not to mention that large-scale app development usually involves multi-team collaboration, and distribution scheduling must be coordinated.
So is there a solution that can fix online bug s without releasing them? Yes! And more than one, all the big factories in the industry have come up with their own solutions to this problem. The more famous ones are Tinker of Tencent and Andfix of Ali and QQ space patch. There are many introductory articles on the above scheme, but most of them are not comprehensive and many details are omitted. The author also encounters many difficulties in the process of learning. Therefore, the author will introduce the above two schemes through the next several blogs, trying not to let go of every detail. First, let's look at the QQ space patch scheme.

1. Dex subcontracting mechanism

As you all know, the code we developed will be packaged into an dex file after it is compiled into a class file. However, the dex file has a limitation, because the method id is a short type, so a dex file can only store 65536 methods at most. With the increasingly complex development of App today, the number of methods has already exceeded this limit. To solve this problem, Google proposed a multidex solution, that is, an apk file can contain multiple dex files.
It is noteworthy, however, that except for the first dex file, all other dex files are loaded as resources, in other words, injected into the ClassLoader of the system in the Application.onCreate() method. This also provides a possibility for hot repair: the repaired code will be reached patch package, and then sent to the client, the client can load the corresponding dex file at the start-up time to the specified path.
According to the class loading mechanism of the Android virtual machine, the same class will only be loaded once, so to replace the original class with the repaired class, the class of the patch package must be loaded first. Next, look at the class loading mechanism of the Android virtual machine.

2. Class loading mechanism

Android's class loading mechanism is similar to that of jvm, which is accomplished through ClassLoader, but the specific classes are different:

The Android system loads system classes and classes in the main dex through PathClassLoader. DexClassLoader is used to load classes in other dex files. Both of the above classes are inherited from BaseDexClassLoader. The specific loading method is findClass:

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;

    /**
     * Constructs an instance.
     *
     * @param dexPath the list of jar/apk files containing classes and
     * resources, delimited by {@code File.pathSeparator}, which
     * defaults to {@code ":"} on Android
     * @param optimizedDirectory directory where optimized dex files
     * should be written; may be {@code null}
     * @param libraryPath the list of directories containing native
     * libraries, delimited by {@code File.pathSeparator}; may be
     * {@code null}
     * @param parent the parent class loader
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }

    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
            for (Throwable t : suppressedExceptions) {
                cnfe.addSuppressed(t);
            }
            throw cnfe;
        }
        return c;
    }
}

From the code, you can see that the work of loading classes has shifted to pathList, which is a DexPathList type. From the variable name and type name, you can see that it is a container for maintaining Dex:

/*package*/ final class DexPathList {
    private static final String DEX_SUFFIX = ".dex";
    private static final String JAR_SUFFIX = ".jar";
    private static final String ZIP_SUFFIX = ".zip";
    private static final String APK_SUFFIX = ".apk";

    /** class definition context */
    private final ClassLoader definingContext;

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

    /**
     * Finds the named class in one of the dex files pointed at by
     * this instance. This will find the one in the earliest listed
     * path element. If the class is found but has not yet been
     * defined, then this method will define it in the defining
     * context that this instance was constructed with.
     *
     * @param name of class to find
     * @param suppressed exceptions encountered whilst finding the class
     * @return the named class or {@code null} if the class is not
     * found in any of the dex files
     */
    public Class findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            DexFile dex = element.dexFile;

            if (dex != null) {
                Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
                if (clazz != null) {
                    return clazz;
                }
            }
        }
        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }
}

DexPathList's findClass is also simple. dexElements are arrays that maintain DEX files. Each item corresponds to an DEX file. DexPathList traverses dexElements, finds the target class from each DEX file, returns and stops traversing once it finds it. Therefore, in order to achieve the goal of thermal repair, patch DEX must be placed in dexElements before the original dex:

This is the basic idea of QQ space patch scheme. The next blog will give a practical example to illustrate the process of hot repair of QQ space patch.

Posted by buzzed_man on Sat, 06 Jul 2019 11:59:05 -0700