Summary of Android hot Patching dynamic repair framework

Source: Internet
Author: User

Summary of Android hot Patching dynamic repair framework

 

I. Overview

Many hot Patching dynamic repair frameworks are open-source on the latest github, which are roughly as follows:

Based on the description of the above three frameworks, the principles are from: Introduction to dynamic hotfix Technology for Android apps and Android dex subcontracting solutions. Therefore, we must take a look at these two frameworks. Here we will not make too many comparisons between the three frameworks. Because the principles are consistent, the Implementation Code may not be very different.

If you are interested, read this article and add the source code of the above framework. Of course, this blog post will also provide an analysis of the above framework source code and the technology used throughout the implementation process.

Ii. hotfix Principle

For the principles of Hotfix, if you read the above two articles, I believe you have understood it. It is important to know that the ClassLoader System of Android generally usesPathClassLoaderAndDexClassLoaderFirst, let's take a look at the differences between the two classes:

ForPathClassLoaderFrom the notes in the document:

Provides a simple {@ link ClassLoader} implementation that operates
On a list of files and directories in the local file system,
Does not attempt to load classes from the network. Android uses
This class for its system class loader and for its application
Class loader (s ).

Android uses this class as its system class and application class loader. In addition, this class can only load apk files that have been installed in the Android system.

ForDexClassLoader, Still read the notes:

A class loader that loads classes from {@ code. jar} and
{@ Code. apk} files containing a {@ code classes. dex} entry.
This can be used to execute code not installed as part of an application.

You can see that this category can be used to load the classes. dex file from within a file of the .jar.apk type. It can be used to execute non-installed program code.

OK. If you know something about plug-ins, you must be familiar with this class. Plug-ins generally provide an apk (plug-in) file and load the apk in the program, so how to load the class in apk? Actually, this DexClassLoader is used to describe the specific code.

You just need to load the classes. dex file in the kernel file.

As we have already mentioned above, Android uses PathClassLoader as its class loader. What is the principle of hotfix?

OK. For the loading class, it is nothing more than giving a classname and then going to findClass. Let's take a look at the source code.
PathClassLoaderAndDexClassLoaderAre inherited fromBaseDexClassLoader. The following source code is available in BaseDexClassLoader:

#BaseDexClassLoader@Overrideprotected Class
   findClass(String name) throws ClassNotFoundException {    Class clazz = pathList.findClass(name);    if (clazz == null) {        throw new ClassNotFoundException(name);    }    return clazz;}#DexPathListpublic Class findClass(String name) {    for (Element element : dexElements) {        DexFile dex = element.dexFile;        if (dex != null) {            Class clazz = dex.loadClassBinaryName(name, definingContext);            if (clazz != null) {                return clazz;            }        }    }    return null;}#DexFilepublic Class loadClassBinaryName(String name, ClassLoader loader) {    return defineClass(name, loader, mCookie);}private native static Class defineClass(String name, ClassLoader loader, int cookie);

We can see that there is a pathList object in BaseDexClassLoader. pathList contains a set of dexElements for DexFile. For Class loading, it is to traverse this set and search for it through DexFile.

OK:

A ClassLoader can contain multiple dex files, each of which is an Element. Multiple dex files are arranged into an ordered array dexElements. when looking for a class, the dex file will be traversed in order, and the class will be searched from the dex file currently traversed. If the class is searched, the system will return. If the class is not found, the system will continue searching from the next dex file. (From: Android App hot patch dynamic Repair Technology Introduction)

In this case, we can do something in dexElements. For example, place our patch in the first element of this array. jar, which contains the repaired classes. In this way, when we traverse findClass, the repaired classes will be found to replace the classes with bugs.

Speaking of this, you may already have a smile. The original hotfix principle is so simple. However, there is anotherCLASS_ISPREVERIFIEDFor details about this problem, see: Android App hot Patching dynamic repair technology.

OK,CLASS_ISPREVERIFIED, Let's take a look:

According to the above article, when the verify option is enabled during Virtual Machine startup, if the static method, private method, constructor, and so on are directly referenced (first-layer relationship) all the classes are in the same dex file.CLASS_ISPREVERIFIEDFlag.

So what we need to do is to prevent the class from being labeledCLASS_ISPREVERIFIED.

Note that it is a class that prevents the quoter. That is to say, assume that your app has a class calledLoadBugClassAnd its internal referencesBugClass. Found during releaseBugClassIf a writing error occurs, you want to publish a newBugClassClass, you have to stopLoadBugClassThis class is labeledCLASS_ISPREVERIFIED.

That is to say, before you generate an apk, You need to prevent the related classes from playingCLASS_ISPREVERIFIED. For how to block it, the above article makes it clear thatLoadBugClassIn the constructor, reference other dex files, such as a class in hack. dex.

OK. Summary:

There are actually two things: 1. dynamically changing the dexElements indirectly referenced by the BaseDexClassLoader object; 2. Preventing related classes from being tagged during app PackagingCLASS_ISPREVERIFIEDFlag.

If you do not understand it, it's okay. Read it several times. The code below will also be used to explain it.

3. Prevent related classes from being tagged CLASS_ISPREVERIFIEDFlag

OK. The following code will be explained through the Code defined by https://github.com/dodola/hotfix.

Here we take the specific class as an example:

The general process is: Before the dx tool is executedLoadBugClass.classFile, modify it, and add it in its structureSystem.out.println(dodola.hackdex.AntilazyLoad.class)And then proceed with the packaging process. Note:AntilazyLoad.classThis class is independent of hack. dex.

OK. You may have two questions:

How can I modify the class file of a class? How can I modify the class file of a class before dx? 1. How can I modify the class file of a class?

Here we use javassist for operations, which is very simple:

OK. First, we create several classes:

package dodola.hackdex;public class AntilazyLoad{}package dodola.hotfix;public class BugClass{    public String bug()    {        return bug class;    }}package dodola.hotfix;public class LoadBugClass{    public String getBugString()    {        BugClass bugClass = new BugClass();        return bugClass.bug();    }}

Note that the package here is to generate a class file after the above classes are compiled normally. For example: LoadBugClass. class, we add a row in the structure of LoadBugClass. class:

System.out.println(dodola.hackdex.AntilazyLoad.class)

The operation class is as follows:

Package test; import ipvsist. classPool; import policsist. ctClass; import into sist. ctConstructor; public class InjectHack {public static void main (String [] args) {try {String path =/Users/zhy/develop_work/eclipse_android/imooc/sharesisttest /; classPool classes = ClassPool. getDefault (); classes. appendClassPath (path + bin); // The bin directory of the project can be CtClass c = classes. get (dodola. hotfix. loadBugClass); CtConstructor ctConstructor = c. getConstructors () [0]; ctConstructor. insertAfter (System. out. println (dodola. hackdex. antilazyLoad. class); c. writeFile (path +/output);} catch (Exception e) {e. printStackTrace ();}}}

OK. Click run. Note that the javassist-*. jar package is imported to the project.

First obtain the ClassPool object, and then add the classpath. If you have multiple classpath, you can call it multiple times. Find LoadBugClass from classpath, get its constructor, and insert a line of code at the end of classpath. OK. The code is very understandable.

OK. Let's decompile and look at the class file we generated:

OK. For more information about javassist, refer to the following articles:

Http://www.ibm.com/developerworks/cn/java/j-dyn0916/ http://zhxing.iteye.com/blog/1703305 (2) how to perform (1) operations before dx

OK. Use the source code of https://github.com/dodola/hotfix.

After importing the source code, open app/build. gradle.

Apply plugin: 'com. android. application 'Task ('processslist') <{String classPath = file ('build/intermediates/classes/debug') // dodola directory where the project compilation class is located. patch. patchClass. process (classPath, project (': hackdex '). buildDir. absolutePath + '/intermediates/classes/debug') // The second parameter is the directory of the hackdex class} android {applicationVariants. all {variant-> variant. dex. dependsOn <processwithpolicsist // enter the code into the class before executing the dx command }}

You will find that the processwithpolicsist task is executed before dx is executed. The role of this task is the same as the code above. The source code is also provided. Let's take a look at it.

OK. Now you can click run. OK. If you are interested, you can decompile it.dodola.hotfix.LoadBugClassWhether the modified Code has been added to the constructor of this class.

On The Decompilation usage, tools, etc., refer to: http://blog.csdn.net/lmj623565791/article/details/23564065

OK. Now we can install and run the apk normally. However, patch-related code is not involved yet.

4. dynamically change the dexElements indirectly referenced by the BaseDexClassLoader object

Okay, it's quite simple here. It can be reflected by dynamically changing a reference of an object.

However, it should be noted that, as we mentioned earlier, the search for class is to traverse dexElements; then ourAntilazyLoad.classActually, it is not included in the classes. dex of the apk. According to the requirements described above, we needAntilazyLoad.classThis class is compiled into an independent hack_dex.jar. Note that it is not a common jar and must be converted using the dx tool.

Specific Practices:

jar cvf hack.jar dodola/hackdex/*dx  --dex --output hack_dex.jar hack.jar 

If you have no way to build that class file into a jar file, go to Baidu...

OK. Now we have hack_dex.jar. Why?

We should remember that our app's central category referencesAntilazyLoad.classWhen the application is started, drop hack_dex.jar to dexElements. Otherwise, an accident will occur.

In this case, the onCreate method of the Application is suitable for this task. We put hack_dex.jar In the assets Directory.

The following describes the hotfix source code:

/* * Copyright (C) 2015 Baidu, Inc. All Rights Reserved. */package dodola.hotfix;import android.app.Application;import android.content.Context;import java.io.File;import dodola.hotfixlib.HotFix;/** * Created by sunpengfei on 15/11/4. */public class HotfixApplication extends Application{    @Override    public void onCreate()    {        super.onCreate();        File dexPath = new File(getDir(dex, Context.MODE_PRIVATE), hackdex_dex.jar);        Utils.prepareDex(this.getApplicationContext(), dexPath, hackdex_dex.jar);        HotFix.patch(this, dexPath.getAbsolutePath(), dodola.hackdex.AntilazyLoad);        try        {            this.getClassLoader().loadClass(dodola.hackdex.AntilazyLoad);        } catch (ClassNotFoundException e)        {            e.printStackTrace();        }    }}

OK, create a file in the private directory of the app, and then call Utils. prepareDex to write hackdex_dex.jar in assets to the file.
Next, HotFix. patch is used to modify dexElements through reflection. Let's take a closer look at the source code:

/* * Copyright (C) 2015 Baidu, Inc. All Rights Reserved. */package dodola.hotfix;/** * Created by sunpengfei on 15/11/4. */public class Utils {    private static final int BUF_SIZE = 2048;    public static boolean prepareDex(Context context, File dexInternalStoragePath, String dex_file) {        BufferedInputStream bis = null;        OutputStream dexWriter = null;        bis = new BufferedInputStream(context.getAssets().open(dex_file));        dexWriter = new BufferedOutputStream(new FileOutputStream(dexInternalStoragePath));        byte[] buf = new byte[BUF_SIZE];        int len;        while ((len = bis.read(buf, 0, BUF_SIZE)) > 0) {            dexWriter.write(buf, 0, len);        }        dexWriter.close();        bis.close();        return true;}

OK is actually a file read/write. It writes the files in the assets Directory to the files in the app's private directory.

The following describes the patch method.

/* * Copyright (C) 2015 Baidu, Inc. All Rights Reserved. */package dodola.hotfixlib;import android.annotation.TargetApi;import android.content.Context;import java.io.File;import java.lang.reflect.Array;import java.lang.reflect.Field;import java.lang.reflect.InvocationTargetException;import dalvik.system.DexClassLoader;import dalvik.system.PathClassLoader;/* compiled from: ProGuard */public final class HotFix{    public static void patch(Context context, String patchDexFile, String patchClassName)    {        if (patchDexFile != null && new File(patchDexFile).exists())        {            try            {                if (hasLexClassLoader())                {                    injectInAliyunOs(context, patchDexFile, patchClassName);                } else if (hasDexClassLoader())                {                    injectAboveEqualApiLevel14(context, patchDexFile, patchClassName);                } else                {                    injectBelowApiLevel14(context, patchDexFile, patchClassName);                }            } catch (Throwable th)            {            }        }    } }

The ClassLoader type in the system is used to determine the principles of reflection.hasDexClassLoader();

private static boolean hasDexClassLoader(){    try    {        Class.forName(dalvik.system.BaseDexClassLoader);        return true;    } catch (ClassNotFoundException e)    {        return false;    }} private static void injectAboveEqualApiLevel14(Context context, String str, String str2)            throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException{    PathClassLoader pathClassLoader = (PathClassLoader) context.getClassLoader();    Object a = combineArray(getDexElements(getPathList(pathClassLoader)),            getDexElements(getPathList(                    new DexClassLoader(str, context.getDir(dex, 0).getAbsolutePath(), str, context.getClassLoader()))));    Object a2 = getPathList(pathClassLoader);    setField(a2, a2.getClass(), dexElements, a);    pathClassLoader.loadClass(str2);}

First, find the classdalvik.system.BaseDexClassLoaderIf it is found, it enters the if body.

In injectAboveEqualApiLevel14, get the PathClassLoader according to the context, get the pathList object in the pathClassLoader through getPathList (PathClassLoader), and call getDexElements to get the dexElements object through pathList.

OK. How can we convert hack_dex.jar TO THE dexElements object?

We can see from the source code that a DexClassLoader object is initialized first. We have mentioned that the parent class of DexClassLoader is also BaseDexClassLoader, so we can obtain dexElements in the same way as PathClassLoader.

OK. Now we have obtained the indirect reference dexElements OF THE PathClassLoader object in the system, and dexElements in hack_dex.jar. Then we will merge these two arrays.

The above Code uses the combineArray method.

After merging, set the new array to pathList through reflection.

Next, let's take a look at the reflection details:

private static Object getPathList(Object obj) throws ClassNotFoundException, NoSuchFieldException,            IllegalAccessException{    return getField(obj, Class.forName(dalvik.system.BaseDexClassLoader), pathList);}private static Object getDexElements(Object obj) throws NoSuchFieldException, IllegalAccessException{    return getField(obj, obj.getClass(), dexElements);}private static Object getField(Object obj, Class cls, String str)            throws NoSuchFieldException, IllegalAccessException{    Field declaredField = cls.getDeclaredField(str);    declaredField.setAccessible(true);    return declaredField.get(obj);}

In fact, it is a process of getting member variables and it should be easy to understand ~~

private static Object combineArray(Object obj, Object obj2){    Class componentType = obj2.getClass().getComponentType();    int length = Array.getLength(obj2);    int length2 = Array.getLength(obj) + length;    Object newInstance = Array.newInstance(componentType, length2);    for (int i = 0; i < length2; i++)    {        if (i < length)        {            Array.set(newInstance, i, Array.get(obj2, i));        } else        {            Array.set(newInstance, i, Array.get(obj, i - length));        }    }    return newInstance;}

OK. Here, the two arrays are merged. You only need to pay attention to one thing. Put dexElements in hack_dex.jar in front of the new array.

At this point, the DexFile contained in hack_dex.jar is dynamically injected into the dexElements of ClassLoader when the application is started. In this way, the AntilazyLoad class will not be found.

Okay, so here we still haven't seen how we patch it. Actually, we have already said that the patching process is consistent with the hack_dex.jar injection process.

Run the HotFix app project and click test in the menu:

Will pop up:Call the Test method: bug class

Next, let's take a look at how to complete the hotfix.

5. Complete hotfix

Okay, so we assume that the BugClass class has an error and needs to be fixed:

package dodola.hotfix;public class BugClass{    public String bug()    {        return fixed class;    }}

We can see that the string has changed: bug class-> fixed class.

Then, compile and convert the class-> jar-> dex of this class. The steps are the same as above.

 jar cvf path.jar dodola/hotfix/BugClass.class  dx  --dex --output path_dex.jar path.jar 

Get the path_dex.jar file.

Under normal circumstances, this item should be downloaded. Of course, we will introduce the principle and you can directly place it on the sdcard.

Then read the data in onCreate of the Application. We put the data in the assets Directory for convenience, and then add the code in onCreate of the Application:

public class HotfixApplication extends Application{    @Override    public void onCreate()    {        super.onCreate();        File dexPath = new File(getDir(dex, Context.MODE_PRIVATE), hackdex_dex.jar);        Utils.prepareDex(this.getApplicationContext(), dexPath, hack_dex.jar);        HotFix.patch(this, dexPath.getAbsolutePath(), dodola.hackdex.AntilazyLoad);        try        {            this.getClassLoader().loadClass(dodola.hackdex.AntilazyLoad);        } catch (ClassNotFoundException e)        {            e.printStackTrace();        }        dexPath = new File(getDir(dex, Context.MODE_PRIVATE), path_dex.jar);        Utils.prepareDex(this.getApplicationContext(), dexPath, path_dex.jar);        HotFix.patch(this, dexPath.getAbsolutePath(), dodola.hotfix.BugClass);    }}

In fact, we have added the following three lines. here we need to explain that the first line is still copied to the private directory. If you are using sdcard, the operations are basically the same, here, don't ask: How can I deal with sdcard or network problems ~

OK, then run our app again.

OK. Let's talk about it. There is a patch button in the project. In the menu, you can also not add the last three lines in the Application.

After you run the app, clickPatchAnd then clickTestYou can also find that the repair is successful.

If you clickTestAnd then clickPatch, AgainTestIt will not change, because once the class is loaded, it will not be reloaded again.

OK. At this point, our hotfix principle has been solved, and I believe it has been described in detail. If you have enough patience, you can implement it. During the process of patch creation, we have a lot of trouble. For automation, please refer to https://github.com/jasonros/nuwa.

Finally, I would like to thank the QQ space team and the Open Source author ~~

 

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.