Android hot patch dynamic Repair Technology (2): Practice! CLASS_ISPREVERIFIED problem!
I. Preface
In the previous blog, we introduced the dex subcontracting principle to introduce Android's hot Patching Technology, and now we will solve two problems.
1. How to package the Fixed Bug class into dex
2. How to insert external dex into ClassLoader
2. Create and test the Demo2.1 directory structure
2.2 source code
Activity_main.xml
MainActivity. class
package com.aitsuki.bugfix;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.view.View;import android.widget.Toast;import com.aitsuki.bugfix.animal.Cat;public class MainActivity extends AppCompatActivity { private Cat mCat; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mCat = new Cat(); } public void click(View view) { Toast.makeText(this, mCat.say(),Toast.LENGTH_SHORT).show(); }}
Cat. class
Package com. aitsuki. bugfix. animal;/*** Created by AItsuki on 2016/3/14. */public class Cat {public String say () {return "Wang! ";}}
2.3 running results
Suppose this is a development project of our company, and a serious bug has just been found on the launch. The cat will scream.
If you want to fix the bug and immediately update it again, it is obviously unfriendly. In this case, hot Patching is useful.
3. Make Patches
Before loading the dex code, we should first create a patch.
1. First, we will fix the Cat class, change Wang to, and re-compile the project. (Just Rebuild it)
2. Save the project and copy the Cat. class file.
3. Create a folder and copy Cat. class to the package name of the Cat. class file,
Character = "here write picture description" src = "http://www.bkjia.com/uploads/allimg/160408/0414554559-4.png" title = "\"/>
Then the test directory is like this.
Patch_dex.jar is the packaged patch. We put it in the sdCard and the patch will be loaded from here.
About how to package patches in such a complicated way:
You can also copy the java file directly, compile it with a package through javac-d, and convert it into a jar file.
But there is a reason for this problem, because you may encounter ParseException using this method, because the jar package version is different from the dx tool version.
It is okay to directly convert the compiled class into jar from the project, because java will be backward compatible, and the jar package and the class version are consistent.
All in all, the dx version must correspond to the class compilation version.
Iv. How to load patch 4.1
In the previous blog, we know that dex is saved in this location.
BaseDexClassLoader-> pathList-> dexElements
The apk classes. dex can be obtained from the DexClassLoader of the application. The dex of path_dex must be loaded by a new DexClassLoader before obtaining it. Retrieve dex files by reflection, merge them into an array, and assign values to the dexElements4.2 code of the ClassLoader.
Load the external dex. We can operate it in the Application.
Create a new HotPatchApplication, configure it in the configuration file, and add the permission to read sdcard, because the patch is saved there.
The HotPatchApplication code is as follows:
Package com. aitsuki. hotpatchdemo; import android. app. application; import android. OS. environment; import android. util. log; import java. io. file; import java. lang. reflect. array; import java. lang. reflect. field; import dalvik. system. dexClassLoader;/*** Created by hp on 2016/4/6. */public class HotPatchApplication extends Application {@ Override public void onCreate () {super. onCreate (); // obtain the patch. If the patch exists, execute the injection operation String dexPath = Environment. getExternalStorageDirectory (). getAbsolutePath (). concat ("/patch_dex.jar"); File file = new File (dexPath); if (file. exists () {inject (dexPath);} else {Log. e ("BugFixApplication", dexPath + "does not exist") ;}/ *** path of the dex to be injected ** @ param path */private void inject (String path) {try {// get the dexElements Class of classes
Cl = Class. forName ("dalvik. system. baseDexClassLoader "); Object pathList = getField (cl," pathList ", getClassLoader (); Object baseElements = getField (pathList. getClass (), "dexElements", pathList); // obtain the dexElements of patch_dex (dex needs to be loaded first) String dexopt = getDir ("dexopt", 0 ). getAbsolutePath (); DexClassLoader dexClassLoader = new DexClassLoader (path, dexopt, dexopt, getClassLoader (); Object obj = getField (cl, "pathList", dexClassLoader ); object dexElements = getField (obj. getClass (), "dexElements", obj); // combine two Elements Object combineElements = combineArray (dexElements, baseElements ); // assign the merged Element array to the classLoader setField (pathList. getClass (), "dexElements", pathList, combineElements ); /// =============whether the test is successful or not ====================== Object object Object = getField (pathList. getClass (), "dexElements", pathList); int length = Array. getLength (object); Log. e ("BugFixApplication", "length =" + length);} catch (ClassNotFoundException e) {e. printStackTrace ();} catch (IllegalAccessException e) {e. printStackTrace ();} catch (NoSuchFieldException e) {e. printStackTrace () ;}}/*** get the Object property value through reflection */private Object getField (Class
Cl, String fieldName, Object object) throws NoSuchFieldException, IllegalAccessException {Field field = cl. getDeclaredField (fieldName); field. setAccessible (true); return field. get (object);}/*** set the object property value through reflection */private void setField (Class
Cl, String fieldName, Object object, Object value) throws NoSuchFieldException, IllegalAccessException {Field field = cl. getDeclaredField (fieldName); field. setAccessible (true); field. set (object, value);}/*** merge two arrays through reflection */private Object combineArray (Object firstArr, Object secondArr) {int firstLength = Array. getLength (firstArr); int secondLength = Array. getLength (secondArr); int length = firstLength + secondLength; Class
ComponentType = firstArr. getClass (). getComponentType (); Object newArr = Array. newInstance (componentType, length); for (int I = 0; I <length; I ++) {if (I <firstLength) {Array. set (newArr, I, Array. get (firstArr, I);} else {Array. set (newArr, I, Array. get (secondArr, I-firstLength) ;}} return newArr ;}}
V. CLASS_ISPREVERIFIED
Run the Demo and report the following error. (AndroidStudio 2.0 may not report errors. errors may occur only when packaging is required. This is caused by Instant run)
The length of dexElements is 2. It seems that our patch_dex has been successfully added.
However, from the log prompt above the yellow box and yellow box, we can see that MainActivity references Cat, but they are found in different Dex.
If you see this, you may ask:
Why didn't this error occur when so many projects used the subcontract solution?
I have summarized a process here. For details about the analysis process, see the original article of the QQ space development team.
During apk installation, You can optimize dex to odex to execute it. In this process, all classes are verified. Verification Method: Assume that Class A directly references Class B in its static, private, constructor, and override methods. If Class A and Class B are in the same dex, Class A will be marked with CLASS_ISPREVERIFIED. The class marked with this identifier cannot reference classes in other dex, otherwise, an error in the figure will be reported. In our Demo, MainActivity and Cat are in the same dex, so MainActivity is marked with CLASS_ISPREVERIFIED. When fixing the bug, We reference the Cat of another dex. class, so the error is reported here, but this error is not returned in the general subcontract solution, because the referenced and referenced classes are not in the same dex at the beginning, therefore, during the verification, it will not be added with CLASS_ISPREVERIFIED to add the second article: if Class A still references A class C, and class C is in another dex, class A is not marked. In other words, as long as other dex classes are directly referenced in the static, constructor, private, and override methods, the class will not be marked with CLASS_ISPREVERIFIED. 5.1 Solutions
According to the sixth article above, we only need to make all classes reference a class in other dex.
The following is the solution provided by QQ controls.
Insert this line of code into constructors of all classes
System.out.println(AntilazyLoad.class);
In this way, when the apk is installed, classes. classes in dex reference an AntilazyLoad class in different dex, which prevents the class from being marked with CLASS_ISPREVERIFIED, as long as the class is not marked with this mark, the patch operation can be performed. Hack. dex must be loaded before the application starts. Otherwise, the AntilazyLoad class will be marked
Does not existEven if hack. dex is loaded later, the AntilazyLoad class will still prompt that it does not exist. If this class cannot be found at one time, it will always be marked as missing. We generally execute the dex injection operation in the Application, so we cannot add
System.out.println(AntilazyLoad.class);
This line of code does not exist because hack. dex is not loaded yet. The constructor is selected because it does not increase the number of methods. A class has an implicit default constructor even if there is no explicit constructor. 5.2 it is not feasible to manually insert code in the source code. hack. dex is not loaded at this time. AntilazyLoad. class does not exist and compilation fails. Therefore, we need to insert the source code into the bytecode after the source code is compiled. There are many frameworks to operate on bytecode, but the most common ones are ASM and javaassist. However, AndroidStudio uses Gradle to build projects, and compilation-packaging is automated. How can we operate it. Please wait for the next blog to be written later.
In fact, the most difficult part of the hot Patching Technology is not the principle, not the injection of dex, but the injection of bytecode.
This requires our team to build the Gradle script. The Groovy language has some knowledge.