1. Compile mode

1.1 concept

In earlier versions of Android, the Dalvik VIRTUAL machine was used to run applications. However, In later versions (around 4.x), Android’s operating environment was switched to Android Runtime, which handles application execution In a completely different way than Dalvik, which relies on a just-in-time (JIT) compiler to interpret bytecode.

However, in Dalvik, the compiled application code needs to run on the user’s device through an interpreter. This mechanism is not efficient, but it makes it easier for the application to run on different hardware and architectures. ART changes this completely by precompiling the bytecode into the machine language at application installation, a mechanism called Ahead-of-time (AOT) compilation. By removing the process of interpreting code, applications execute more efficiently and start faster.

1.2 advantages of AOT

Here are some advantages of AOT compilation:

1.2.1 Precompilation

ART introduces a pre-compilation mechanism to improve application performance. ART also has more stringent installation-time validation than Dalvik. At installation time, ART uses the dex2OAT tool that comes with the device to compile the application. The utility takes a DEX file as input and generates a compiled application executable for the target device, which can compile all valid DEX files smoothly.

1.2.2 Optimization of garbage collection

Garbage collection (GC) can compromise application performance, resulting in unstable displays, slow interface response times, and other issues. The ART pattern optimizes garbage collection strategies in the following ways:

  • There is only one GC pause, not two
  • Parallel processing while the GC remains paused
  • In the special case of cleaning up recently allocated short-duration objects, the collector’s total GC time is shorter
  • Optimized garbage collection ergonomics for more timely parallel garbage collection, making GC_FOR_ALLOC events extremely rare in typical use cases
  • Compress GC to reduce background memory usage and fragmentation

1.2.3 Optimization in development and debugging

Support for sampling profilers For a long time, developers have used the Traceview tool, which tracks application execution, as a profiler. Although Traceview can provide useful information, the overhead of each method call can skew the results of Dalvik analysis, and the use of the tool obviously affects runtime performance. ART adds support for dedicated sampling analyzers without these limitations to provide a more accurate picture of application execution. Without significantly slowing down. Supported versions starting with KitKat (4.4) add sampling support to Dalvik’s Traceview.

ART supports many new debugging options, especially those related to monitoring and garbage collection. For example, see which locks are held in the stack trace and then jump to the thread that holds the lock; Asks for the number of currently active instances of a given class, requests to see instances, and references to keep objects in a valid state; Filter instance specific events, such as breakpoints.

Optimized diagnostic details in exception and crash reports ART gives you as much context and detail as possible when a runtime exception occurs. ART provides a Java. Lang. ClassCastException, Java, lang. ClassNotFoundException and Java. Lang. NullPointerException more abnormal details (higher version Dalvik will provide Java. Lang. ArrayIndexOutOfBoundsException and Java. Lang. ArrayStoreException exceptionally detailed information more, these information now including the array size and offset seams; ART also provides this information).

1.3 Garbage Collection

ART provides several different GC schemes that run different garbage collectors. The default GC scheme is CMS (Concurrent mark sweep), which mainly uses sticky CMS and partial CMS. Sticky CMS is ART’s non-moving generational garbage collector. It scans only the parts of the heap that have been modified since the last GC and can only reclaim objects allocated since the last GC. With the exception of the CMS scheme, ART performs heap compression when the application changes the process state to a state where lag is not detected (for example, background or cache).

In addition to the new garbage collector, ART also introduced a new bitmap-based memory allocator called RosAlloc (slot run Allocator). This new allocator outperforms the DlMalloc (memory allocator) by having a sharding lock and adding a thread’s local buffer when the allocation size is small.

For details about memory allocator, see: Memory allocator

Meanwhile, compared with Dalvik, ART’s CMS garbage collection also brings other improvements as follows:

  • Compared to Dalvik, the number of pauses was reduced from 2 to 1. Dalvik’s first pause is mainly for root marking, that is, concurrent marking in the ART, allowing the thread to mark its own root and then immediately resuming running.

  • Like Dalvik, the ART GC pauses once before the cleanup process begins. The main difference between the two is that during this pause, some Dalvik segments are concurrent in the ART. These include java.lang.ref.Reference processing, system weak cleanup (for example, JNI weak global, etc.), re-marking of non-threaded roots, and card pre-cleanup. The stages that continue during ART pauses include scanning for dirty cards and relabeling thread roots, which help shorten pause times.

  • A final improvement in ART GC over Dalvik is that the sticky CMS collector increases GC throughput. Unlike normal generational GC, viscous CMS does not move. Young objects are stored in an allocation stack (basically a java.lang.Object array) rather than having a dedicated region for them. This avoids moving the objects needed to keep the pause times low, but has the disadvantage of making the stack longer by adding a large number of complex object images.

Another major difference between the ART GC and Dalvik is that the ART GC introduces a mobile garbage collector. The purpose of using mobile GC is to reduce the memory used by background applications through heap compression. Currently, the event that triggers heap compression is a change in the ActivityManager process state. When the application goes into the background, it notifies ART that it has entered a process state that no longer “senses” lag. At this point, the ART does something (for example, compression and monitor compression) that causes the application thread to pause for a long time.

Currently, the two mobile GCS being used by Android ART are isomorphic space compression and half-space compression. The differences are as follows:

  • Half-space compression: Moves an object between two tightly packed collision pointer Spaces. This kind of mobile GC is suitable for small memory devices because it can save slightly more memory than isomorphic space compression. The extra space saved is mainly from tightly packed objects, thus avoiding the RosAlloc/DlMalloc allocator overhead.
  • Isomorphic space compression is achieved by copying objects from one RosAlloc space to another. This helps reduce memory usage by reducing heap fragmentation. This is currently the default compression mode for non-low memory devices. The main advantage of isomorphic space compression over half-space compression is that there is no heap conversion required to switch applications from background to foreground.

Class loaders

2.1 Class loader classification

At present, Android class loaders are mainly divided into BootstrapClassLoader (root class loader), ExtensionClassLoader (ExtensionClassLoader) and AppClassLoader (application class loader) from bottom to top.

  • Root class loader: This loader has no parent. It is responsible for loading the core libraries of the virtual machine, such as java.lang.*. For example, java.lang.Object is loaded by the root class loader. The root classloader loads the class libraries from the directory specified in the system property sun.boot.class.path. The implementation of the root ClassLoader is dependent on the underlying operating system and is part of the implementation of the virtual machine. It does not inherit the java.lang.ClassLoader class.
  • Extension class loader: Its parent is the root class loader. It loads the libraries from the directory specified by the java.ext.dirs system property, or from the JRE /lib/ext subdirectory (extension directory) of the JDK installation directory, where user-created JAR files are automatically loaded by the extension classloader. The extension ClassLoader is a pure Java class that is a subclass of the java.lang.ClassLoader class.
  • System class loader: Also known as application class loader, its parent is extension class loader. It loads classes from the directory specified by the environment variable classpath or the system property java.class.path, and is the default parent of a user-defined class loader. The system ClassLoader is a pure Java class that is a subclass of the java.lang.ClassLoader class.

The parent-child loader is not inherited, which means that the child loader does not necessarily inherit from the parent loader.

2.2 Parental delegation mode

The so-called parent delegation mode refers to that when a specific class loader receives a request to load a class, it first delegates the loading task to the parent class loader, and then recurses in turn. If the parent class loader can complete the class loading task, it will return successfully. Only if the parent class loader is unable to complete the load task, do the load itself.

Because this avoids double loading, there is no need for the child ClassLoader to load the class again when the parent has already loaded the class. Without this delegate pattern, we can use custom classes to dynamically replace some core classes at any time, which is a huge security risk.

For example, the java.lang.String class is not actually loaded by our custom classloader, but by bootstrap classLoader. Why is that? In fact, this is the reason for the parent delegate mode, because before any custom ClassLoader loads a class, it delegates the load to its parent ClassLoader and only loads it if the parent ClassLoader fails.

2.3 Android class loader

Here is a model of the Android class loader:Look at the DexClassLoader. The DexClassLoader overloads the findClass method and calls its internal DexPathList to load the class. DexPathList is generated during the construction of DexClassLoader, which contains DexFile. The source code involved is as follows.

...public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;
        if(dex ! = null) { Class clazz = dex.loadClassBinaryName(name, definingContext);
            if(clazz ! = null) {returnclazz; }}}returnnull; }...Copy the code

For more on class loaders, see android Class loaders parent Delegate mode

3, the Android Hook

The so-called Hook is to intercept a certain piece of information in the process of program execution, as shown in the diagram below.

The general process of Android Hook can be divided into the following steps: 1, according to the need to determine the object to hook 2, find the owner of the object to hook, get the object to hook 3, define the “object to hook” proxy class, and create the object to hook 4, using the previous step created objects, replace the object to hook

Below is a simple Hook example code, using Java’s reflection mechanism.

@SuppressLint({"DiscouragedPrivateApi"."PrivateApi"})
public static void hook(Context context, final View view) {//
    try {
        Reflection executes the View class getListenerInfo() method to get v's mListenerInfo object, which is the holder of the click event
        Method method = View.class.getDeclaredMethod("getListenerInfo");
        method.setAccessible(true);Since the getListenerInfo() method is not public, this code is added to ensure access
        Object mListenerInfo = method.invoke(view);// Here is the mListenerInfo object, which is the holder of the click event

        // Get the current click event object from hereClass<? > listenerInfoClz = Class.forName("android.view.View$ListenerInfo");// This is the inner class representation
        Field field = listenerInfoClz.getDeclaredField("mOnClickListener");
        final View.OnClickListener onClickListenerInstance = (View.OnClickListener) field.get(mListenerInfo);// Get the real mOnClickListener object

        // 2. Create our own click-event proxy class
        // Method 1: Create your own proxy class
        // ProxyOnClickListener proxyOnClickListener = new ProxyOnClickListener(onClickListenerInstance);
        // Method 2: Since view. OnClickListener is an interface, you can use dynamic proxy mode directly
        // proxy. newProxyInstance
        // Local class loader;
        // The interface that the object of the proxy Class inherits (represented by the Class array, supporting multiple interfaces)
        // The actual logic of the proxy class is encapsulated in the New InvocationHandler
        Object proxyOnClickListener = Proxy.newProxyInstance(context.getClass().getClassLoader(), new Class[]{View.OnClickListener.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Log.d("HookSetOnClickListener"."Click event was hooked");// Add your own logic
                return method.invoke(onClickListenerInstance, args);// Execute the proxied object's logic}});// 3. Use our own click-event proxy class, set to holder
        field.set(mListenerInfo, proxyOnClickListener);
    } catch (Exception e) {
        e.printStackTrace();
    }
}

// Customize the proxy class
static class ProxyOnClickListener implements View.OnClickListener {
    View.OnClickListener oriLis;

    public ProxyOnClickListener(View.OnClickListener oriLis) {
        this.oriLis = oriLis;
    }

    @Override
    public void onClick(View v) {
        Log.d("HookSetOnClickListener"."Click event was hooked");
        if(oriLis ! = null) { oriLis.onClick(v); }}}Copy the code

And in Android development, want to achieve Hook, certainly is not so simple, we need to use some Hook framework, such as Xposed, Cydia Substrate, Legend, etc..

Resources: The Android Hook mechanism

4. Code confusion

4.1 Proguard

Java code is notoriously easy to decompile, and to better protect Java source code, we tend to obfuscate the compiled Class files. ProGuard is an open source project that obfuscates code. Its main function is obfuscation, and of course it can reduce the size of bytecode, optimize it, etc., but those are minor functions for us.

Specifically, ProGuard provides the following functions:

  • Shrink: Detects and deletes unused classes, fields, methods, and features.
  • Optimize: Analyze and Optimize Java bytecode.
  • Obfuscate: Rename classes, fields, and methods using short, meaningless names.

In Android development, enabling obfuscation requires setting the minifyEnabled property in the app/build.gradle file to true, as shown below.

minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
Copy the code

Proguard-android. TXT is the default obfuscation configuration file provided by Android. The obfuscation rules we need are put in this file.

4.2 Confusion Rules

Confused command

  • Keep: Keeps the class and its members from being confused or removed
  • Keepnames: Keeps classes and members of classes from being confused. Members that are not referenced are removed
  • Keepclassmembers: Keeps only members of a class from being confused or removed
  • Keepclassmembernames: Keeps only members of a class to prevent confusion. Members without references are removed
  • Keepclasseswithmembers: KeepclasseswithMembers: KeepclasseswithMembers: KeepclasseswithMembers: KeepclassesWithMembers: KeepclassesWithMembers: KeepclassesWithMembers: KeepclassesWithMembers: KeepclassesWithMembers
  • Keepclasseswithmembernames: keep members of the class and class, prevent be confused, reserves the specified member, member no reference will be removed

Obfuscate wildcards

  • <field>: Matches all fields in the class
  • <method>: matches all methods in a class
  • <init>: matches all constructors in a class
  • *: Matches any length of characters, excluding the package name delimiter (.).
  • 六四屠杀: Matches any length of characters, including the package name delimiter (.)
  • * * *: Matches any parameter type

The format of the keep rule is as follows:

[keep command] [class] {[member]}Copy the code

4.3 Confusing templates

There are some common templates in ProGuard that can be reused, such as compression ratio, case mixing, and some system-provided activities and services that cannot be confused.

# code confusion compression ratio, in0~7The default value is5, optimizationPasses are generally not modified5# do not use case mixing when mixing, Blend to class called lowercase - dontusemixedcaseclassnames # specified not to ignore the public library classes - dontskipnonpubliclibraryclasses # this sentence can make us generate mapping file # after the confusion of the project Contains the name of the class - > confusion after the mapping relationship between the name of the class - verbose # specified not to ignore the class members of the public library - dontskipnonpubliclibraryclassmembers # don't do the divverify, preverify is one of the four steps of proguard, Android does not require PreVerify, and removing this step can speed up obfuscation. - DontPreVerify # Preserve annotations without obfuscating - KeepAttributes *Annotation*,InnerClasses # Avoid obfuscating generics - KeepAttributes Signature # Throw an exception when keep code line number - keepattributes SourceFile, LineNumberTable # specified confusion is to use an algorithm, the parameter is a filter behind the # this filter is recommended by the Google algorithm, generally do not change - optimizations. code/simplification/cast,! field/ *! Class/done / * # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # Android development in some of the need to keep the part # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # to retain the four major components, we use -keep public class * extends Android.app. Activity -keep public class * extends android.app.Appliction -keep public class * extends android.app.Service -keep public class * extends android.content.BroadcastReceiver -keep public class * extends android.content.ContentProvider -keep public class * extends android.app.backup.BackupAgentHelper -keep public class * extends android.preference.Preference -keep public Class * extends android. View. The view - keep public class com. Android. Vending. Licensing. ILicensingService # support Keep class android.support.** {*; ** -keep public class * extends Android.support.v7.** -keep Public class * extends android. Support. The annotation. * * # retain R the following resources - keep class * * * R ${*; } # retain local native method is not be confused - keepclasseswithmembernames class * {native < the methods >; } # Keep the method arguments in the Activity as view methods, Keepclassmembers class extends Android.app.activity {public void keepClassMembers extends Android.app.activity {public void keepClassMembers extends Android.app.activity {public void keepClassMembers extends Android.app.activity {public void keepClassMembers extends Android.app.activity {public void keepClassMembers extends Android.app.activity *(android.view.View); {public static **[] values();} # keepClassMembers enum * {public static **[] values(); public static ** valueOf(java.lang.String); } # public class extends android.view.View {*** get*(); void set*(***); public 
      
       (android.content.Context); public 
       
        (android.content.Context, android.util.AttributeSet); public 
        
         (android.content.Context, android.util.AttributeSet, int); } # keep class * implements android.os.Parcelable {public static final android.os.Parcelable$Creator *; } # keep Serializable classes from being confused-keepNames * implements java.io.Serializable -keepclassmembers * implements java.io.Serializable { static final long serialVersionUID; private static final java.io.ObjectStreamField[] serialPersistentFields; ! static ! transient 
         
          ; ! private 
          
           ; ! private 
           
            ; private void writeObject(java.io.ObjectOutputStream); private void readObject(java.io.ObjectInputStream); java.lang.Object writeReplace(); java.lang.Object readResolve(); Keepclassmembers class * {void *(**On*Event); keepclassMembers class * {void *(**On*Event); void *(**On*Listener); } # webView handling, project did not use to the webView can be ignored - keepclassmembers class FQCN. Of the javascript. Interface. For. WebView {public *; } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.WebView, java.lang.String, android.graphics.Bitmap); public boolean *(android.webkit.WebView, java.lang.String); } -keepclassmembers class * extends android.webkit.webViewClient { public void *(android.webkit.webView, java.lang.String); } # js -keepattributes JavascriptInterface -keep class android.webkit.JavascriptInterface { *; } -keepclassmembers class * { @android.webkit.JavascriptInterface 
            
             ; } # @Keep -keep,allowobfuscation @interface android.support.annotation.Keep -keep @android.support.annotation.Keep class * -keepclassmembers class * { @android.support.annotation.Keep *; }
            
           
          
         
        
       
      Copy the code

For aar, you can add the following obfuscated configuration to aar’s build.gralde.

Android {··· defaultConfig {··· · consumerProguardFile'proguard-rules.pro'}...}Copy the code

5, the NDK

When it comes to advanced Android development, the NDK is definitely a must-ask. The NDK, which stands for Native Development Kit, is a set of tools that allow developers to use C/C++ in Android applications. In general, NDK can be used in the following scenarios:

  • Get better performance from devices for computationally intensive applications, such as games or physics simulations.
  • Reuse your own or other developers’ C/C++ libraries for cross-platform convenience.
  • The NDK integrates specific implementations of API specifications such as OpenSL and Vulkan to achieve functions that cannot be achieved in the Java layer, such as audio and video development and rendering.
  • Increased decompilation difficulty.

5.1, JNI basis

JNI is the Java Native Interface through which Java and native code interact.

5.1.1 JNI access Java object methods

Suppose you have a Java class with the following code.

package com.xzh.jni;

public class MyJob {
    public static String JOB_STRING = "my_job";
    private int jobId;

    public MyJob(int jobId) {
        this.jobId = jobId;
    }

    public int getJobId(a) {
        returnjobId; }}Copy the code

Then, in the CPP directory, create native_lib. CPP and add the corresponding native implementation.

#include <jni.h>

extern "C"
JNIEXPORT jint JNICALL
Java_com_xzh_jni_MainActivity_getJobId(JNIEnv *env, jobject thiz, jobject job) {

    // Get the class object based on the instance
    jclass jobClz = env->GetObjectClass(job);
    // Get the class object based on the class name
    jclass jobClz = env->FindClass("com/xzh/jni/MyJob");

    // Get the attribute id
    jfieldID fieldId = env->GetFieldID(jobClz, "jobId"."I");
    // Get the static attribute ID
    jfieldID sFieldId = env->GetStaticFieldID(jobClz, "JOB_STRING"."Ljava/lang/String;");

    // Get the method ID
    jmethodID methodId = env->GetMethodID(jobClz, "getJobId"."()I");
    // Get the constructor id
    jmethodID  initMethodId = env->GetMethodID(jobClz, "<init>"."(I)V");

    // Get the attribute value based on the object attribute ID
    jint id = env->GetIntField(job, fieldId);
    // Call the method based on the object method ID
    jint id = env->CallIntMethod(job, methodId);

    // Create a new object
    jobject newJob = env->NewObject(jobClz, initMethodId, 10);
    return id;
}
Copy the code

5.2 the NDK development

5.2.1 Basic process

First, declare the Native method in your Java code, as shown below.

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        Log.d("MainActivity".stringFromJNI());
    }
    private native String stringFromJNI(a);
}
Copy the code

Then, create a CPP directory and create a CPP file named native-lib. CPP to implement the relevant methods.

#include <jni.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_xzh_jni_MainActivity_stringFromJNI(
        JNIEnv *env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
Copy the code

CPP files follow the following rules:

  • The format of function names is as follows: Java_package name_class name_method name.
  • Extern “C” specifies that C naming styles are used to compile; otherwise, functions cannot be found when linking due to C and C++ naming styles
  • JNIEnv* : represents a pointer to the JNI environment through which to access the interface methods provided by JNI
  • Jobject: represents this in a Java object
  • JNIEXPORT and JNICALL: Macros defined by JNI can be found in the jni.h header file

The code for system.loadLibrary () is in the Java /lang/ system.java file and the source code is as follows:

@CallerSensitive
public static void load(String filename) {
    Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);
}
Copy the code

5.3 CMake Builds NDK

CMake is an open source cross-platform tool for building, testing, and packaging software. Starting with Android Studio 2.2, Android Sudio uses CMake by default to build native libraries in conjunction with Gradle. Specifically, we can use Gradle to compile C \ C++ code into a native library, and then package the code into our application. The Java code can then call functions in our native library through the Java native interface (JNI).

To develop NDK projects using CMake, download the following kits:

  • Android Native Development Kit (NDK) : This set of tools allows us to develop Android code using C and C++, and provides numerous platform libraries that allow us to manage native activities and access physical device components such as sensors and touch input.
  • CMake: An external build tool that can be used with Gradle to build native libraries. This component is not required if you only plan to use the NdK-build.
  • LLDB: A debugger that Android Studio uses to debug native code.

We can open Android Studio, choose [Tools] > [Android] > [SDK Manager] > [SDK Tools] select LLDB, CMake and NDK.

To enable CMake, add the following code to app/build.gradle.

Android {··· defaultConfig {··· externalNativeBuild {cmake {cppFlags""
            }
        }

        ndk {
            abiFilters 'arm64-v8a'.'armeabi-v7a'ExternalNativeBuild {cmake {path"CMakeLists.txt"}}}Copy the code

Then, create a new cmakelists. TXT file in the corresponding directory and add the code.

# define the minimum version of CMake requiredcmake_minimum_required(VERSION 3.41.)

# add_libraryThe () command is used to add a library# native-lib corresponds to the name of the generated library# SHARED stands for SHARED library# SRC /main/ CPP /native-lib. CPP specifies the path to the source file.
add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).src /main/ CPP /native-lib.cpp) # find_library was added to the CMake build script to locate the NDK library and store its path as a variable. You can use this variable to reference the NDK library in other parts of the build scriptfind_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.Log) # The pre-built NDK library already exists on the Android platform, so there is no need to build or package it into APK. Since the NDK library is already part of CMake's search path, you just need to provide CMake with the name of the library you want to use and associate it with your native librarytarget_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.The ${log - lib})...Copy the code

Reference: Android NDK development basics

6. Dynamic loading

6.1 Basic Concepts

Dynamic loading technology is very common in the Web. For Android projects, the purpose of dynamic loading is to enable users to upgrade application functions without reinstalling APK. The main application scenarios are plug-in and hot repair.

First of all, it should be made clear that plug-in and hot fix are not the same concept, although from the point of view of technical implementation, they are both from the point of view of the system loader, whether using hook, proxy or other underlying implementation. Both cheat the Android system to allow the host to load and run the content of the plug-in (patch) normally; But the starting point is different.

Plug-in, in essence, is to extract the modules or functions to be implemented as an independent function, reduce the scale of the host, and load the corresponding modules when the corresponding functions need to be used. Hot fixes, on the other hand, tend to focus on fixing known bugs without having to install the application again.

For the sake of illustration, let’s clarify a few concepts:

  • Host: currently running APP.
  • Plugins: As opposed to plugins, you load running APK class files.
  • Patch: In contrast to the hot repair technology, it is to load and run a series of files such as. Patch,. Dex,*. Apk that contain the repair content of dex.

The following figure shows the overall architecture of the Android Dynamic development framework.

6.2 the plugin,

As for plug-in technology, it can be traced back to the AndroidDynamicLoader in 2012, whose principle is to dynamically load different fragments to achieve UI replacement. However, with better schemes in 15 or 16 years, this scheme has been gradually phased out. Then came the dynamic-load-APK scheme of Ren Yugang, and began to have the standard scheme of plug-in. The following schemes are mostly based on Hook and dynamic proxy.

At present, plug-in development and no official plug-in scheme, it is a domestic proposed technology implementation, the use of virtual machine class loading mechanism to achieve a technical means, often need hook some system API, and Google from Android9.0 began to limit the use of system private API, Also caused the compatibility of plug-ins, now several popular plug-in technology framework, are based on their own needs, open source, such as Didi VirtualAPK, 360 RePlugin, etc., we can understand the implementation principle of technology according to need.

6.3 hot repair

6.3.1 Principles of Hot Repair

When it comes to hot fixes, classes are loaded by a ClassLoader, similar to a normal JVM. Specifically, it is PathClassLoader and DexClassLoader, two Special class loaders for Android. The differences between these two classes are as follows.

  • PathClassLoader: loads only apK files (/data/app directory) that have been installed in the Android operating system. It is the default class loader used by Android.
  • DexClassLoader: can load dex/jar/ APk /zip files in any directory, which is the patch we mentioned at the beginning.

Both classes inherit from BaseDexClassLoader, whose constructors are as follows.

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
Copy the code

The only thing this constructor does is initialize a DexPathList object with the related arguments passed in. The DexPathList constructor encapsulates the program files (that is, the patch files) passed in as Elements and adds those objects to an Element array called dexElements.

As mentioned earlier, the class loader is used to load a specific class into memory. This is done by the virtual machine. For the developer, it is only a matter of finding the class that needs to be loaded.

On Android, finding a class named Name requires two steps:

  1. The class is obtained by a DexPathList object called findClass() in the DexClassLoader’s findClass method.
  2. The findClass method of DexPathList iterates through the previously constructed dexElements array. If it finds a class with the same class name as name, it returns the class directly, and if it does not find a class with the same name, it returns null.

Therefore, based on the above theory, we can think of the simplest thermal repair solution. Let’s say one of the classes in our code has a Bug. After fixing the Bug, we can package these classes into a patch file, which then encapsulates an Element object and inserts the Element object into the front of the original dexElements array. Thus, when the DexClassLoader loads a class, the inserted Element will be loaded first because of the parental loading mechanism, and the defective Element will not have a chance to be loaded again. In fact, QQ’s early hot fix was just that.

6.3.2 Qzone Super patch solution

The QZone patch solution solves the CLASS_ISPREVERIFIED problem by using Javaassist staking. The steps involved are as follows:

  • During apK installation, the system will optimize the dex file into the Odex file, and the optimization process will involve a pre-verification process.
  • If a class’s static, private, override, and constructors reference other classes and all of them belong to the same dex file, the class is marked CLASS_ISPREVERIFIED.
  • If the class labeled CLASS_ISPREVERIFIED at run time references the classes of other dex, an error is reported.
  • A normal subcontracting scheme ensures that related classes are fed into the same DEX file.
  • In order for patch to be loaded normally, it is necessary to ensure that the class is not marked with CLASS_ISPREVERIFIED. To do this, you must embed references to classes in other DEX files in the unpacked class.

6.3.3 Tinker

Qzone super patch solution is very time-consuming when the patch files are very large, because when a large folder is loaded into memory to build an Element object, it takes time to insert it into the front of the array, which greatly affects the startup speed of the application. Based on these problems, wechat proposed Tinker solution.

The patch.dex file will be merged with the original class. Dex file to create a new file fix_class. Replace the original dexPathList with the new fix_class.dex, and fundamentally fix the Bug.

Compared with the qzone super patch solution, Tinker provides a more efficient idea. For those of you interested in Tinker’s thermal fix, check it outTinker source code analysis of DexDiff/DexPatch

6.3.4 HotFix

Although the strategies of the two methods mentioned above are different, they are generally from the perspective of the upper ClassLoader. Due to the characteristics of ClassLoader, if you want the new patch file to take effect again, you need to restart the application to load the new DexPathList, no matter whether you insert the pile or merge in advance. So as to realize the Bug repair.

AndFix provides a way to modify the Filed pointer in Native during runtime to realize the replacement of the method, so as to achieve immediate effect without restart and no performance consumption for the application. However, the compatibility of AndFix is not very good because Android has become Android in China and major handset manufacturers have customized their ROM, so there are many differences in the underlying implementation.

6.3.5 Sophix

Sophix uses a class-like repair reflection injection, inserting the path of the patched SO library at the top of the nativeLibraryDirectories array so that it will load the patched SO library instead of the original so library.

While fixing the class code, Sophix broke and reorganized the classes.dex order between the old and patched packages so that the system could recognize the order naturally for class coverage purposes.

To fix the resource defect, Sophix constructs a resource package with package ID 0x66. This package contains only the changed resource item, and then directly addAssetPath to the original AssetManager without changing the reference to the AssetManager object.

In addition to these programs, thermal restoration programs include Meituan Robust, Ele. me Amigo, etc. However, it’s hard to come up with a perfect solution for Android hotfixes. For example, in Android development, the four major components need to be declared in AndroidManifest in advance before they are used, and if you need to use the way of hot repair, whether it is early pit or dynamic modification, will bring strong invasion. At the same time, the fragmentation of Android, the adaptation of the hot fix is also a big test.

Explore the principles of Android hotfix technology in depth