Hot Fix principles and basic examples

principle

ClassLoader entrusted with parents

Hot Fix is ​​the basis for the establishment of the ClassLoader loading mechanism.

Android in class ClassLoader is loaded into the virtual machine, specifically how to load it? Look at the code:

ClassLoader

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            //1.首先查找是否已经被加载
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    //2.如果没有被加载,且父ClassLoader不为空,则优先交给父ClassLoader加载
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }

                if (c == null) {
                    //3.父ClassLoader没有找到,则交给本ClassLoader查找
                    c = findClass(name);
                }
            }
            return c;
    }

This is the famous parents commissioned , Android is not caused by a ClassLoader class loads, but more ClassLoader a collaborative effort. Code parent does not refer to the current ClassLoader parent class, but rather refers to a current ClassLoader delegate object, because the principle of parents commissioned roughly commissioned up, look down, so here named parent. As shown below:

6103019-b118a8c59764f120.png
Parents entrust .png

When we load a Java class, commissioned by the PathClassLoader first to BootClassLoader to find, BootClassLoader will also be entrusted to its "parent", until the top is not ClassLoader delegate object, and began to look down: when the top ClassLoader does not find the target , then handed it to find the next level ClassLoader until PathClassLoader.

BootClassLoader often used to pre-load some commonly used Java classes, PathClassLoader commonly used classes and class loading system applications (that is, your apk, it can resolve a class object from your apk in), in addition to DexClassLoader you can load external files and included dex dex compressed file (apk and jar).

There are many parents and commissioned by the principles relating to ClassLoader more detailed information online, not repeat them here.

Hot Fix principle

There are many solutions to repair of heat, which is given here of the principle of a solution, after the specific code will be realized according to this principle.

Speaking of parents above commission, which is important: When Class has been loaded, then quit to find . This Class through the entire loading process: whether it is commissioned to find between, single or multiple ClassLoader ClassLoader internal findClass apply.

Here is a look at how a single internal ClassLoader loaded findClass of:

To PathClassLoader example, its approach is to achieve a findClass parent class: BaseDexClassLoader:

   @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;
   }

The actual call is visible pathList.findClasshere pathList is DexPathList type, which internally holds the load address a series of dex files, including app's apk address. Read on DexPathList.findClass:

    public Class<?> findClass(String name, List<Throwable> suppressed) {
       for (Element element : dexElements) {
           Class<?> clazz = element.findClass(name, definingContext, suppressed);
           if (clazz != null) {
               return clazz;
           }
       }

       if (dexElementsSuppressedExceptions != null) {
           suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
       }
       return null;
   }

Find a loop through the Element class here, dexElementsis Element array, Element is a simple inner class that contains DexFile object. DexFile internal memory contains the address Dex files.

To sum up, that is, as long as we Element array, advance to join a elementA, elementA we have to fix that contains the Class, then load the principle according to the class, then the class in question will not be loaded.

FIG general process:

6103019-90778fb0e7bbe669.png
One hot fixes principle .png

Source Address:

BaseDexClassLoader
DexPathList
DexFile

example

Know the principle will be the practical operation. First, a new project, create a simple Test.java:

public class Test {

    public void say(Context context) {
        Toast.makeText(context, "yes", Toast.LENGTH_SHORT).show();
    }
}

Then call in MainActivity in:

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        Test test = new Test();
        test.say(this);
}

Now simulate hot fixes, without updating App, the output will be changed to yes Toast no.

Because it is analog, so we do not consider how to download, and for convenience, directly modify the project after no packaged into apk, and apk push to sd card root directory.

The following code portion begins:

1. In a custom Application of attachBaseContextthe method, the code is added to the hot fix. The reason is that the method to load faster than onCreate.

2. Analyzing there is no local update package, no skip, there is the next step. Attention must be granted permission to sd card reader!

File hotfixFile = new File(HOTFIX_APK_PATH);
if (!hotfixFile.exists()) {
    return;
}

3. start hot fixes.

<1. Using Reflected Element acquisition array.

//获取加载APK的PathDexClassLoader
ClassLoader classLoader = getClassLoader(); //dalvik.system.PathClassLoader
//获取BaseDexClassLoader的DexPathList
Field pathList = classLoader.getClass().getSuperclass().getDeclaredField("pathList");
pathList.setAccessible(true);
Object dexPathList = pathList.get(classLoader);

//获取DexPathList的Element[]
Field dexElements = dexPathList.getClass().getDeclaredField("dexElements");
dexElements.setAccessible(true);
Object[] dexElementsList = (Object[]) dexElements.get(dexPathList);

Because loading and loading Application Test.java use the same PathClassLoader (see Activity reason to start the process), so directly modify its Application ClassLoader is no problem.

<2. Create a DexFile contains fixes for the apk.

//获取element类型
Class<?> elementClass = dexElementsList.getClass().getComponentType();

//PathDexClassLoader也是可以加载外部Dex的,只要把DexFile传入即可
//创建DexFile
DexFile dexFile = new DexFile(hotfixFile);
//创建Element对象
Constructor<?> constructor = elementClass.getConstructor(DexFile.class, File.class);
constructor.setAccessible(true);
Object elementHot = constructor.newInstance(dexFile, hotfixFile);

<3. Alternatively an array element.

//替换element[],将自定义dex加入到数组前位
Object[] array = (Object[]) Array.newInstance(elementClass, dexElementsList.length + 1);
array[0] = elementHot;
for (int i = 0; i < dexElementsList.length; i++) {
    array[i + 1] = dexElementsList[i];
}
dexElements.set(dexPathList, array);

Run again, you can see the application in the absence of an updated Toast output becomes a yes from no.

Code is small, seemingly simple, practical summary of some of the pit encountered:

1. Initially I tried to replace PathDexClassLoader use proxy mode, ultimately failed.
2.DexFile initialization, still told after I passed my apk file is not available, I later found not to grant permission to read app sd card ...
3.PathClassLoader While loading a Class App itself, but as long as the incoming correspondence DexFile, also can be loaded Class of external sd card.

The complete code

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
    }

    @Override
    protected void attachBaseContext(Context base) {
        super.attachBaseContext(base);
        hotfix();
    }

    private static final String HOTFIX_APK_PATH = "/storage/emulated/0/test.apk";

    private void hotfix() {
        File hotfixFile = new File(HOTFIX_APK_PATH);
        if (!hotfixFile.exists()) {
            return;
        }
        try {
            //获取加载APK的PathDexClassLoader
            ClassLoader classLoader = getClassLoader(); //dalvik.system.PathClassLoader
            //获取BaseDexClassLoader的DexPathList
            Field pathList = classLoader.getClass().getSuperclass().getDeclaredField("pathList");
            pathList.setAccessible(true);
            Object dexPathList = pathList.get(classLoader);

            //获取DexPathList的Element[]
            Field dexElements = dexPathList.getClass().getDeclaredField("dexElements");
            dexElements.setAccessible(true);
            Object[] dexElementsList = (Object[]) dexElements.get(dexPathList);

            //log 输出element[]
            for (int i = 0; i < dexElementsList.length; i++) {
                Log.e("hotfix_demo", dexElementsList[i].toString());
            }

            //获取element类型
            Class<?> elementClass = dexElementsList.getClass().getComponentType();

            //PathDexClassLoader也是可以加载外部Dex的,只要把DexFile传入即可,注意读写权限!!!
            //创建DexFile
            DexFile dexFile = new DexFile(hotfixFile);
            //创建Element对象
            Constructor<?> constructor = elementClass.getConstructor(DexFile.class, File.class);
            constructor.setAccessible(true);
            Object elementHot = constructor.newInstance(dexFile, hotfixFile);

            //替换element[],将自定义dex加入到数组前位
            Object[] array = (Object[]) Array.newInstance(elementClass, dexElementsList.length + 1);
            array[0] = elementHot;
            for (int i = 0; i < dexElementsList.length; i++) {
                array[i + 1] = dexElementsList[i];
            }
            dexElements.set(dexPathList, array);

        } catch (NoSuchFieldException | IllegalAccessException | NoSuchMethodException | InstantiationException | InvocationTargetException | IOException e) {
            e.printStackTrace();
            Log.e("hotfix_demo", "error " + e.toString());
        }
    }
}

Reproduced in: https: //www.jianshu.com/p/8e81df5d4520

Guess you like

Origin blog.csdn.net/weixin_33725722/article/details/91220507