First, code repair

1. Class loading scheme

(1) Dex subcontracting principle

The number of methods in a Dex file cannot exceed 65536 methods.

(1) Reason: Because Android will retrieve the method ID of each class, there is a linked list structure. However, the length of the list is stored with a short type, which takes up two bytes (save -2 ^ 15 to 2 ^ 15 -1, i.e. -32768~32767), and the maximum number of saves is 65536.

(2) Solutions:

  • Reduce the number of methods and remove unused classes, methods, and third-party libraries.
  • Use ProGuard to remove some unused code
  • Some modules adopt local plug-in mode.
  • Segmentation Dex

The Dex subcontracting scheme mainly divides the application code into multiple Dex during packaging, and puts the classes that must be used when the application is started and the directly referenced classes of these classes into the main Dex, and other codes into the secondary Dex. The primary Dex is loaded when the application is started, and the secondary Dex is dynamically loaded after the application is started.

(2) Class loading repair scheme

If an exception exists in the key. Class file, repair the Class file and add it to the Patch package of patch. dex. (1) Solution 1: Get the DexPathList in the PathClassLoader by reflection, and then get the Element array in the DexPathList. Place patch. dex in the first Element of Element array dexElements. Finally, merge the arrays and set them back. During Class loading, due to the parent delegation mechanism of the ClassLoader, the Class is loaded only once, that is, the key. Class in patch. dex is loaded.


(3) Limitations of class loading schemes

Solution a:

  • Since classes cannot be unloaded, the App needs to be restarted if the class needs to be reloaded, so the class loading fix is not immediate.
  • In ART mode, if a class changes its structure, it will be out of memory. To solve this problem, all relevant calling classes, parent classes and subclasses must be loaded into patch.dex, resulting in a large patch package and serious time consuming.

Scheme 2:

  • Next startup repair
  • Dex merging memory consumption may cause OOM, and dex merging fails

2. Low-level replacement scheme

(1) Basic scheme

It is mainly to replace the original method in Native layer. ArtMethod structure contains all information of Java method, including execution entry, access permission, class and code execution address, etc. Replacing a field in the ArtMethod structure, or replacing the entire ArtMethod structure, is the underlying substitution. Because the method is replaced directly, it takes effect immediately and does not require a restart.

(2) Advantages and disadvantages

(1) Disadvantages

  • Methods and fields of the original class cannot be added or subtracted. If we increase the number of methods, the index of the method will also increase, so that the correct method cannot be found through the index when accessing the method.
  • Platform compatibility issues. If the vendor makes changes to the ArtMethod structure, the replacement mechanism is problematic.

(2) Advantages

  • The immediacy of Bug fixes
  • The generated PATCH has small volume and low performance impact

Second, resource restoration

1, Instant Run

The core code: the runtime/MonkeyPatcher. Java

#MonkeyPatcherpublic static void monkeyPatchExistingResources(@Nullable Context context, @Nullable String externalResourceFile, @Nullable Collection<Activity> activities) { ...... try { // Create a new AssetManager instance and point it to the resources installed under // (1) Create a newAssetManager by reflection, Call addAssetPath add the bundle on the sdcard AssetManager newAssetManager = AssetManager. Class. GetConstructor (). The newInstance (); Method mAddAssetPath = AssetManager.class.getDeclaredMethod("addAssetPath", String.class);
        mAddAssetPath.setAccessible(true);
        if (((Integer) mAddAssetPath.invoke(newAssetManager, externalResourceFile)) == 0) {
            throw new IllegalStateException("Could not create new AssetManager");
        }
        // Kitkat needs this method call, Lollipop doesn't. However, it doesn't seem to cause any harm
        // in L, so we do it unconditionally.
        Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod("ensureStringBlocks");
        mEnsureStringBlocks.setAccessible(true);
        mEnsureStringBlocks.invoke(newAssetManager);
        if(activities ! = null) {//(2) Reflection gets a reference to the Activity's AssetManager and replaces it with the newly created newAssetManagerfor (Activity activity : activities) {
                Resources resources = activity.getResources();
                try {
                    Field mAssets = Resources.class.getDeclaredField("mAssets");
                    mAssets.setAccessible(true);
                    mAssets.set(resources, newAssetManager);
                } catch (Throwable ignore) {
                    Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
                    mResourcesImpl.setAccessible(true);
                    Object resourceImpl = mResourcesImpl.get(resources);
                    Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
                    implAssets.setAccessible(true);
                    implAssets.set(resourceImpl, newAssetManager);
                }
                Resources.Theme theme = activity.getTheme();
                try {
                    try {
                        Field ma = Resources.Theme.class.getDeclaredField("mAssets");
                        ma.setAccessible(true);
                        ma.set(theme, newAssetManager);
                    } catch (NoSuchFieldException ignore) {
                        Field themeField = Resources.Theme.class.getDeclaredField("mThemeImpl");
                        themeField.setAccessible(true);
                        Object impl = themeField.get(theme);
                        Field ma = impl.getClass().getDeclaredField("mAssets");
                        ma.setAccessible(true); ma.set(impl, newAssetManager); }... } //(3) iterate over the collection of Resource weak references and replace AssetManager with newAssetManagerfor (WeakReference<Resources> wr : references) {
            Resources resources = wr.get();
            if(resources ! = null) { // Set the AssetManager of the Resources instance to our brand new one try { Field mAssets = Resources.class.getDeclaredField("mAssets");
                    mAssets.setAccessible(true);
                    mAssets.set(resources, newAssetManager);
                } catch (Throwable ignore) {
                    Field mResourcesImpl = Resources.class.getDeclaredField("mResourcesImpl");
                    mResourcesImpl.setAccessible(true);
                    Object resourceImpl = mResourcesImpl.get(resources);
                    Field implAssets = resourceImpl.getClass().getDeclaredField("mAssets");
                    implAssets.setAccessible(true); implAssets.set(resourceImpl, newAssetManager); } resources.updateConfiguration(resources.getConfiguration(), resources.getDisplayMetrics()); } } } catch (Throwable e) { throw new IllegalStateException(e); }}Copy the code
  • Reflection builds the new AssetManager, and reflection calls addAssertPath to load the new resource bundle in sdcard, resulting in an AssetManager with all the new resources
  • Replace the original reference to the AssetManager with the new AssetManager by reflection

2. Resource Pack Replacement (Sophix)

The default APK compiled by the Android SDK has a package ID of 0x7f. The resource package ID of framework-res.jar is 0x01

  • Construct a resource bundle with package ID 0x66 (not 0x7f and 0x01) that contains only the resource items that have changed.
  • Since it does not conflict with Ox7f already loaded, the package can be loaded through the addAssetPath of the original AssetManager.

3. SO library repair

The essence is the repair and replacement of native methods

1. Load the SO library

(1) Load so library through the following method

#Systempublic static void loadLibrary(String libname) { Runtime.getRuntime().loadLibrary0(VMStack.getCallingClassLoader(), libname); } parameter is so library name, Public static void load(String filename) {runtime.geTruntime ().load0(vmstack.getStackclass1 (), filename); } load external custom so library file, parameter is the full path of so library on diskCopy the code
private static native String nativeLoad(String filename, ClassLoader loader, String librarySearchPath);
Copy the code

Finally, the native method nativeLoad is called, and fileName is the complete path name of SO in the disk

(2) Traverse the nativeLibraryDirectories

#DexPathList
public String findLibrary(String libraryName) {
    String fileName = System.mapLibraryName(libraryName);
    for (File directory : nativeLibraryDirectories) {
        File file = new File(directory, fileName);
        if (file.exists() && file.isFile() && file.canRead()) {
            returnfile.getPath(); }}return null;
}
Copy the code

Similar to the findClass method of class loading, each element in the array corresponds to a so library, and ultimately returns the path to so. If you add the so patch to the top of the array, the path to the so patch is returned when the method is called to load the so library.

2. SO repair scheme

(1) Interface replacement

Provides methods to replace the System.loadLibrary method

  • If the patch SO exists, load the patch so library instead of the SO library in the APK installation directory
  • If the patch so does not exist, call System.loadLibrary to load the so library in the install APK directory

(2) Reflection injection

Because loading the so library will traverse nativeLibraryDirectories

  • Use reflection to insert the patch so library path at the top of the nativeLibraryDirectories array
  • As the nativeLibraryDirectories are traversed, the patch SO library is returned and loaded for repair purposes

Four, thermal repair frame analysis

  • Underlying replacement scheme: AndFix, HotFix of Ali
  • Class loading solutions: QQ space patch technology, wechat Tinker solution, Ele. me Amigo
  • A combination of both: Sophix

References:

  • Analysis of mainstream thermal repair schemes
  • Android hotfix technology, which would you choose?
  • Android Advanced Decryption
  • Deep Dive into the Principles of Android HotFix Technology