Preface
This series of articles mainly summarizes the technical articles of the big guys, which belongs to the basic part of Android . As a qualified Android development engineer, we must be proficient in java and android. Let’s talk about these in this issue~
[Non-commercial use, if there is any infringement, please let me know, I will delete]
DD: Android advanced development various documents, you can also follow the official account Android Hardship Boat to get it.
1. The essential basic skills of Android senior development engineers 2. Core knowledge notes for Android performance optimization 3. Sprint collection of Android + advanced audio and video development interview questions 4. Introduction to Android audio and video development to practical learning manual 5. Android Framework refined kernel analysis 6. Flutter practical advanced technical manual 7. Nearly 100 Android video recording and broadcasting + audio and video dome....
Android virtual machine instructions
1. instruction set interpretation
1.1 JVM Cross-language and bytecode
JVM is a cross-language platform. Many languages can be compiled into bytecode that complies with the specifications. These bytecodes can be run on the JAVA virtual machine. Java virtual machine does not care whether the bytecode comes from the Java program . It only requires each language to provide its own compiler. The bytecode follows the bytecode specification. For example, the beginning of the bytecode is CAFEBABY.
compiles various languages into compilers that are called front-end compilers. There are also compilers in the Java virtual machine, such as the instant compiler, which is called the backend compiler here.
Java virtual machine must be cross-language, and it should be the most powerful virtual machine at present. But it was not the first to be cross-language.
1.1.1 What is the benefit of a cross-language platform?
has a cross-language platform, multi-language hybrid programming is more convenient, and it solves specific domain problems through specific domain languages.
For example, parallel processing is written in Clojure language, the display layer is used in JRuby/Rails, and the intermediate layer is written in Java. Each application layer can be written in a different language, and the interface is transparent to developers. Different languages can call each other, just like calling the API native to their own language. They all run on the same virtual machine.
1.1.2 What is bytecode?
bytecode is narrowly compiled from java language, but since JVM supports bytecode compiled in multiple languages, and bytecode is a standard specification, because we should call it JVM bytecode.
Different compilers can compile the same bytecode file, and the bytecode file can also be run in different JVM on different operating systems.
Therefore, the Java virtual machine is actually not a forced association relationship with the Java language. The virtual machine is only strongly associated with the secondary file (Class file).
1.2 class byte code interpretation
1.2.1 Class class file structure
class file is a set of binary streams based on 8 bytes. Each data item is arranged in the file in strict order in order, and no separator is added in the middle, which makes almost all the content stored in the entire class file be necessary data of the program. When encountering data items that need to occupy more than 8 bytes of space, they will be divided into several 8 bytes in the way of high positions for storage. There are only two data types in the
Class file format: "unsigned number" and "table".
- unsigned number: belongs to the basic data type, and represents 1 byte, 2 bytes, 4 bytes and 8 bytes respectively with u1, u2, u4, and u8. Unsigned numbers can be used to describe numbers, index references, quantity values, or string values composed of utf-8 encoding.The
- table is a composite data type composed of multiple unsigned numbers or other tables as data items. In order to facilitate distinction, the naming of all tables habitually ends with "_info". Tables are used to describe data that describes hierarchical relationships. The entire class file can essentially be a table, arranged in strict order.
is the following figure, which is the class structure:

- magic number and the version of class file: The first 4 bytes of each class file are called magic number, and its only function is to determine whether this file is a class file that can be accepted by the virtual machine. The four bytes of the magic number store the version number of the class file: the 5th and 6th bytes are the minor version number, and the 7th and 8th bytes are the major version number. The Java version number starts from No. 45.
- constant pool, followed by the primary and secondary version numbers is the entrance to the constant pool. The constant pool can be compared to the source repository in the class file. It is the data that is most associated with other projects in the class file structure, and is usually one of the data items that occupy the largest space of the class file. In addition, it is also the first table-type data item in the class file that appears in the class file. The entrance of the constant pool needs to place a u2 type data, representing the constant pool capacity count value. This capacity count starts at 1, not at 0. There are two major categories of constants in the constant pool: literal and symbolic reference. Literals are closer to Java-level constant concepts, such as text strings, constant values declared as final, etc. Symbol references include the following types of constants:
- fully qualified name fields of package classes and interfaces exported by modules or open, and the name of descriptor method and descriptor method handle and method type Dynamic call point and dynamic constant
Constant pool is a table. As of jdk3, there are 17 different types of constants in the constant table respectively.
- access flag (access_flag): After the constant pool ends, the next 2 bytes represent the access flag. This indicates whether it is access information at some class or interface level, including: is this class class or interface; whether it is defined as public; whether it is defined as abstract type, etc. There are 16 types of flags that can be used in access_flag. Currently, only 9 flags are defined, and all flags that are not used are 0.
- class index (this_class), parent class index (super_class) and interface index set (interfaces); class index and parent class index are both a data set of u2 type, and the interface index set is a set of data sets of u2 type. These three data in the class file determine the inheritance relationship of this type. Class index is used to determine the fully qualified name of this class, and parent class index is used to determine the fully qualified name of the parent class of this class. The interface index set is used to describe which interfaces are implemented in this class. These implemented interfaces will be arranged from left to right in the interface order after the implements keyword.
- field table (field_class) is used to describe variables declared in interfaces or classes. Includes class-level variables and instance-level variables, but do not include local variables declared inside the method. The modifiers that a field can include the scope of the field (public, protect), instance variable or class variable (static tml4), variability (final), concurrent visibility (volatile, whether it is read and written from main memory), whether it can be serialized (transient), and field data type (base type, object, array). The above modifiers are either or not, which are very suitable for use with flag bits. Fields and field types can only be described by reference to constants in the constant pool. Following the access_flag flag are two index values: name_index and description_index. They are references to constant pools, representing the simple name of the field and the descriptor of the field and method, respectively.Fully qualified name: similar to: org/test/testclass; simple name refers to a method or field name without type and parameter modification: similar to inc() inc, field m m; method and field descriptor are more complex




































- method table description; the description of the method in the class file storage format uses almost the same method as the description of the field. The structure of the method table is like the field table, including access flags, name index, descriptor index, and attribute table collection in turn. If the parent class method is not overridden in the child class, method information from the parent class will not appear in the method table collection. It is possible that the compiler's own method appears.
- attribute table: class files, field tables, and method tables can all carry their own attribute table collections to describe special information for certain scenarios. The following is some attribute table information.
1.2.2 Bytecode and data type
Java virtual machine instructions are composed of one byte length data representing the meaning of a certain operation (called an opcode) and followed by zero to multiple parameters (called operands) that represent the required operation (called operands). Since the Java virtual machine adopts an architecture that is oriented towards operand stack rather than register , most instructions do not include operands, only one opcode, and the instruction parameters are placed in the operand stack. The Java virtual machine's opcode is one byte (0-255), which means that the total number of opcodes in the instruction set cannot exceed 256. The class file format abandons operand alignment of compiled code, which means that when the virtual machine processes data that exceeds one byte, it has to reconstruct the specific data structure from the bytes at runtime.
is as follows: the data types supported by the Java virtual machine instruction set.
- Loading and storage instructions: used to transfer data back and forth between local variables in the stack frame and operand stack. For example: iload (load a local variable to the operand stack), istore (store a value from the operand stack to the local variable table), bipush (load a constant to the operand stack)
- operation instruction: used to perform a certain operation on the values on the two operand stacks and restort the result to the top of the operand stack. For example: iadd, isub, imul, idiv, irem, ineg.
- type conversion instruction: two different numeric types can be converted to each other.
- object creation and access instructions: Although class instances and arrays are objects, the Java virtual machine uses different bytecode instructions for the creation and operation of class instances and arrays.After the object is created, you can use the object access instructions to obtain the field or array element of the object instance
- to create class instructions: new; create array instructions: newarray, anewarray, multianewarray
- to access class fields and instance fields: getfield, putfield, getstatic, putstatic
- to load an array element into the operand stack: baload, calod, etc.
- to store elements of an operand stack to array elements: bastore, castor, etc.
- to take the array length: arraylength; to check the type of class instance: inst anceof, checkcast;
- operand stack instructions: pop, swap,
- control transfer instructions: ifeq, iflt, etc.
- method call and return instructions; invokevirtual (call object instance method, allocate according to the actual type of the object), invokeinterface (call interface method, find an object that implements this interface method at runtime), invokespeccoal (special processing instance method, similar to private method, parent class method, initialization method), invokestatic (class static method ), invokedynamic (a method referenced by the call point qualifier at runtime). Its allocation logic is set by the guidance method set by the user. Return instruction: ireturn
- Exception handling instruction: Exception handling table is used in Java virtual machine.
- synchronization instruction: The Java virtual machine supports synchronization of method level and a sequence of instruction within the method. Both of these are implemented using monitorro. Synchronized instruction sequence is usually represented by synchronized statement blocks in the Java language. The instructions in the Java virtual machine have monitorenter and monitorexit to support the semantics of synchronized.
1.3 Hotspot dalvik ART relationship comparison
1.3.1 Introduction to Dalvik




1.3.2 The difference between Dalvik and JVM



1.3.3 ART (Android Runtime)


Dalvik and ART is that Dalvik is compiled on the fly, and it is compiled before each run; while ART adopts precompilation.
ART Advantages and Disadvantages
Advantages:




Disadvantages:


1.3.4 Dex
Dex file is Dalvik's executable file . Dalvik is a Java virtual machine designed for embedded devices, so there is a big difference in structure between Dex files and Class files. In order to better utilize the resources of your device in embedded , after Dalvik compiles the Java program, it also needs to use the dx tool to integrate the compiled several Class files into a Dex file. In this way, each class can share data, reduce redundancy, and make the file structure more compact.
Before executing a Dex file, a device needs to optimize the Dex file and generate the corresponding Odex file, and then the Odex file is executed by Dalvik.The Odex file is essentially a Dex file, but it only makes related optimizations for the target platform, including a series of processing of internal bytecode, mainly bytecode verification, replacement optimization and empty method elimination.
1.3.5 Dalvik and Art are different.
Android can run multiple apps, corresponding to multiple dalvik instances. Each application has an independent linux process. The independent process can prevent virtual machine crashes and cause all programs to be closed. Just like the lights on a light bulb are in parallel, if one light bulb is broken, the other light bulbs will not be affected, and if one program crashes, the other programs will not be affected.
- Art compiles once, and it will benefit lifelong, improve the loading speed of the app, run speed, and save power; however, the installation time is a little longer, which occupies a slightly larger Rom volume
- Dalvik takes up a small Rom volume and installs slightly faster, but the loading time of the app is long, slower, and more power-consuming.
1.4 Storage structure and operating principle of the stack
1.4.1 What is stored in the stack?

1.4.2 The principle of running the stack

stack: As shown in the figure below, there are four methods, method 1 calls method 2, 2 calls 3, 3 calls 4. At this time, there will be 4 stack frames in the stack. The current stack frame is the stack frame corresponding to method 4, located at the top of the stack. The method execution is completed and will be released in turn. The order of stacking is 4, 3, 2, 1.

6. When the current method returns, the current stack frame will return the execution result of this method to the previous stack frame. Then the virtual machine discards the current stack frame, making the previous stack frame become the current stack frame again.
7. There are two ways to return Java function, use return or throw exceptions. Either way, it will cause the stack frame to be popped up.
1.5 Internal structure of stack frame





uses methods as the most basic execution unit in JAVA virtual machine, and "stack frame" is a data structure used to support virtual machine method calls and execution. It is also a stack element in the stack in the data area of the virtual machine runtime.
From the perspective of JAVA programs, at the same time, in the same thread, all methods on the call stack are in the execution state at the same time. However, for the execution engine, in the active thread, only the method on the top of the stack is running, that is, only the method on the top of the stack is effective, which is called the "current stack frame", and the method associated with this stack frame is called the "current method". All bytecode instructions run by the execution engine are only operated for the current stack frame. The local variable table of methods, operand stack, dynamic connection and method return address are stored in the
stack frame. The following are the introductions to these parts one by one.
1.5.1 Local variable table
Local variables represent the storage space of a set of variable values, which is used to store method parameters and local variables defined internally by the method.The capacity of the local variable table is the minimum unit of variable slots. One variable slot occupies 32-bit memory space, that is, among the 8 types of data in the stack, except for double and long that need to occupy two variable slots, the rest occupy one variable slot.
It should be noted that the local variable table is built on the thread's stack, that is, the thread-private data, that is, the read and write of the variable slot is thread-safe.
In addition, variable slot 0 in the local variable table usually contains this object reference, and other data is stored from variable slot 1 and stored in the local variable table through the bytecode instruction store. When it needs to be called, it can be retrieved through the load command. At the same time, in order to save the memory space occupied by stack frames, the variable slots of the local variable table can be reused, and their scope may not necessarily cover the entire method body. If the PC counter of the current bytecode has exceeded the scope of a certain variable, then this variable slot can be handed over to other variables for reusing.
can refer to the following code:
publicvoid method1(){int a = 0;int b = 2;int c = a+b;}publicvoid method2(){int d = 0;int e = 2;int f = d+e;}
public void method1();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=4, args_size=1 0: iconst_0 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: istore_3 8: returnLineNumberTable:line 9: 0line 10: 2line 11: 4line 12: 8 public void method2();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=4, args_size=1 0: iconst_0 1: istore_1 2: iconst_2 3: istore_2 4: iload_1 5: iload_2 6: iadd 7: istore_3 8: returnLineNumberTable:line 14: 0line 15: 2line 16: 4line 17: 8
You can see that in two different methods, the d, e, and f variables of method2 multiplex the variable slots corresponding to a, b, and c in method1.
This can save overhead, but it will also bring certain problems. Please refer to the following code:
public static void main(String[] args) {{byte[] b = new byte[64*1024*1024];}System.gc();}
[GC (System.gc())68813K-66384K(123904K), 0.0017888 secs][Full GC (System.gc())66384K-66225K(123904K), 0.0074844 secs]
You can see that the array b that should have been recycled was not recycled. This is mainly because the reference to b is still stored in the variable slot of the local variable table (although the scope has been out, the variable slot has not been reused, so the reference relationship remains), making it impossible to be garbage collected. You can multiplex the corresponding variable slot by inserting int a = 0 below the code block, breaking the reference relationship, or setting b to null. Both methods can realize the recycling of b.
In addition, the objects in the local variable table must be assigned values. The default value cannot be assigned by the system like class variable
public class A{int a;//System assignment a = 0public void method(){int b;//Error, value must be assigned}}
1.5.2 operand stack
operand is mainly used for operations between variables in the method. The main principle is that when encountering operation-related bytecode instructions (such as iadd), the two elements closest to the top of the stack will pop up for operation. The specific workflow of the operand stack can be referred to as the following code:
publicvoid method1(){int a = 0;int b = 2;int c = a+b;}
In addition, in the virtual machine stack, two stack frames will overlap part of it, that is, part of the operands of the following stack frame overlap with part of the local variable table of the above stack frame. This not only saves space, but also directly shares part of the data when calling the method, without copying and passing additional parameters.
1.5.3 Dynamic connection
Each stack frame contains a reference to the method to which the stack frame belongs in the constant pool at runtime. This reference is held to support dynamic connections during method invocation, that is, symbolic references of methods in the constant pool must be dynamically converted into direct references during each run.
1.5.4 method return address
method has two ways to exit this method. First, the execution engine encounters the bytecode instruction (return) returned by any method.Second, an exception occurred during the execution of the method, and the corresponding exception handler was not found in the exception table of the method. After the method exits, it must return to the location where the initial method was called before the program can continue to execute. The value of the PC counter of the main tone method can be used as the return address, and the value of the counter will be stored in the stack frame.
1.6 Jclasslib and HSDB tool application analysis
1.6.1 jclasslib application analysis
What I want to grandly introduce below is a visual bytecode viewing plug-in: jclasslib.
can be installed directly in Idea plug-in management (installation steps are omitted).
Usage method :
- Open the class you want to study in IDEA.
- compiles this class or directly compiles the entire project (if the class you want to study is in the jar package, this step can be skipped).
- Open the "view" menu and select the "Show Bytecode With jclasslib" option.
- After selecting the above menu item, a jclasslib tool window will pop up in IDEA.
So there is a powerful disassembly tool that comes with. Is it still necessary to use this plug-in for Javap? The power of of the plug-in
is that is:
- does not require a command to be typed, it is simple and direct, and it is convenient to compare and learn with the source code on the right.
- bytecode command supports hyperlinks. clicks on the virtual machine command in it to jump to jvms related chapter , which is super convenient.
This plug-in is of great help to us to learn virtual machine instructions.
1.6.2HSDB usage
HSDB full name is HotSpotDebugger, HotSpot virtual machine debugging tool. When using it, the program needs to be in a paused state. You can directly use Idea's debug tool. Using HSDB you can see the relevant content in the stack.
starts HSDB
no matter which way you start, you need to know the current java program process number first. We use the jps command, as shown in the figure below:
and then we use the command jhsdb hsdb --pid=87854 Start HSDB, as shown in the figure below:
Use HSDB to view JVM virtual machine stack information
We know that when creating a thread, there will be a jvm stack for it. As shown in the figure above, we can see that there are 5 threads in java Threads. We select the main thread, and then click the icon to view stack information above, as shown in the figure below:

Stack Memory for main There are three parts of the main body of the panel, as shown in the figure above




You can see Young com/platform/tools/jvm/Main$TestObject This object we define, remember this address x00000001161d11e0 This object is referenced in the stack
Use HSDB to view heap information
Most of our objects are in the heap. We can use HSDB to see how many instance objects there are in the heap. As shown in the figure below



, the third one is the member variable in the method we see in the stack.
For the other two, we need to query through a reverse pointer, see which class references this instance, and see which variable
HSDB uses revptrs to see the instance reference
. For the above two addresses, we cannot determine what object it is, so we can use pointer back-check to see what they are referenced, as shown in the figure below:
As shown in the figure above, we can see that one is referenced by Class object, is a class static variable, and the other is referenced by jvm/Main, that is, our Main class, is a class member variable. Through this we can also summarize, Static variables are actually also present in the heap. The relationship between
Class, static and Klass
. Some hsdb versions of
do not support, such as mem, whatis, etc., so those who want to learn in depth can use jdk1.8 hsdb to try the above two commands
. More than one Java object (Java Object, in the heap) corresponds to the same Klass (in MetaSpace) corresponds to the same Class object (in the heap). The static variable addresses of the class are all behind the Class object (so they are also in the heap).
2. Deep in Android memory management
Android Runtime (ART) virtual machines and Dalvik virtual machines both use paging (paging) and ** memory mapping (Memory-mapped file) to manage memory . This means that any memory modified by , whether the modification method is allocating a new object or touching the memory-mapped page , will always reside in RAM, and cannot swap out . To release memory from application , you can only release object reserved by reference , so that memory can be recycled by garbage collector . There is an exception to in this case : for any memory mapped file that has not been modified (for example: code) **, if system wants to use its memory in other locations , it can be swapped out of RAM.
1.1 The underlying difference between Android virtual machines and JVM
virtual machines: The function of JVM is to translate the bytecode in platform-independent .class into platform-related machine code to achieve cross-platform.Dalvik and Art (virtual machines used after Android 5.0) are virtual machines used in Android. What is a
virtual machine? The difference between Jvm, Dalvik (DVM) and Art
1.1.1 The difference between JVM and Android virtual machine
The difference 1: dvm executes a .dex format file. jvm executes a .class file. After the Android program is compiled, the .class file will be produced. Then, the dex tool will process the .class file into a .dex file, and then package the resource file and .dex file into a .apk file. apk means android package. jvm executes a .class file. difference 2: dvm is a register-based virtual machine, while jvm execution is a virtual machine based on virtual stack. The register access speed is much faster than the stack. DVM can achieve maximum optimization based on hardware, which is more suitable for mobile devices. The difference between is three: .class files have a lot of redundant information. The dex tool will remove redundant information and integrate all .class files into .dex files. Reduce I/O operations and improve the search speed of classes
Summary: JVM uses Class as the execution unit, Android virtual machine uses Dex execution unit, and the compilation process JVM can be loaded directly through Javac. Android virtual machines need to be compiled into dex first, and then compiled into apk. Finally, when the Android Art virtual machine is installed, dex caches local machine code. The installation is slow and consumes storage space. The Android Dalvik virtual machine is translated during the program running. Save space and consume CPU time. What is the structural difference between dex and class, which exchanges space for time?
dex divides the file into three areas, which store the information of all java files in the entire project, so the advantages of dex are revealed when there are more and more classes. It only has one dex file, and many areas can be reused, reducing the size of the dex file.
is essentially the same. Dex evolved from the class file, but there is a lot of information about the sunshine in cals. Dex removes the sunshine info and integrates the concept of
1.1.3 stack and registers. Have you understood it in depth before?
summary: Java virtual machines are all stack-based structures, while Dalvik virtual machines are register-based. Stack-based instructions are very compact, and the instructions used by Java virtual machines only occupy one byte, so they are called byte code. Register-based instructions need to specify the source and destination addresses, so they need to occupy more instruction space. Some instructions for the Dalvik virtual machine need to take up two bytes. Stack-based and register-based instruction sets have their own advantages and disadvantages. Generally speaking, when performing the same function, stack-based requires more instructions (mainly load and store instructions), while register-based requires more instruction space. The stack requires more instructions to occupy more CPU time, and the register requires more instruction space to mean that the data buffering (d-cache) is more prone to failure.
1.2 Garbage collection
Android Runtime (ART) virtual machine or Dalvik virtual machine managed memory environment tracks every memory allocation . Once determines that the program no longer uses a certain piece of memory, it will re-free the memory in the heap without any intervention from the programmer. This mechanism of reclaiming unused memory in managed memory environments is called garbage collection . garbage collection has two goals: find data objects that cannot be accessed in the program in the future, and recycle the resources used by these objects .
Android's heap is generation , which means will track different allocated storage partitions according to the life expectancy and size of the allocated object. For example: 's recently allocated object belongs to the new generation. When an object remains active for a long enough time, it can be promoted to an older generation, and then the permanent generation .
heap each generation of memory that can be occupied by the corresponding object has its own dedicated upper limit . Whenever the generation starts to fill with , the system will execute garbage collection event to release memory to . The duration of garbage collection depends on which generation of objects it recycles and how many active objects there are in each generation .
Although garbage collection speed is very fast , it will still affect the performance of applications . Generally speaking, we cannot control when the garbage collection event occurs from the code. The system has a set of standards specifically to determine when to execute garbage collection . When meets the conditions, the system will stop the execution process and start garbage collection . If garbage collection occurs in intensive processing loop process such as animation or music playback , may increase the processing time , which may cause the code execution in the application to exceed the recommended 16ms threshold, and it is impossible to achieve efficient and smooth efficient and smooth .
In addition, the various tasks performed by our code stream may force garbage collection events to occur more frequently or cause its duration to exceed the normal range . For example: during each frame of Alpha hybrid animation , we allocate multiple objects in innermost layer of for , then it may create a large number of objects in heap . In this case, garbage collector will execute multiple garbage collection events , and may reduce the performance of the application .
1.3 Memory Issue
1.3.1 Shared Memory
To accommodate everything you need in RAM, Android will try to share RAM page across processes, which can be implemented in the following ways:
- Each application process forks (fork) from an existing process named Zygote. When the system starts and loads the common framework code and resources (for example: Activity theme background) , the Zygote process is started. To start the new application process , the system will fork (fork) Zygote process , and then load in the new process in the new process and run application code in . This method can allow framework (Framework) code and resources to allocate most RAM pages of to share among all application processes**.
- Most static data is memory mapped into a process, which allows the data to be shared not only among processes, but also swapped out when needed. static data examples include: Dalvik code (direct memory mapped by putting it into a pre-linked .odex file) , application resources (by designing the resource table as a memory mapped structure and by aligning the APK's zip entries) and ** Traditional project elements (for example: native code in .so files) **.
- In many places, Android uses explicitly allocated shared memory areas (via ashmem or gralloc) to share the same dynamic RAM between processes. For example: window surface uses memory shared between application and screen synthesizer , while cursor buffer uses memory shared between content provider and client .
1.3.2 Allocate and recycle application memory
Dalvik heap is limited to the single virtual memory range of each application process . This defines the logical heap size . The size can grow as needed, but cannot exceed the upper limit defined by the system for for each application . The logical size of
heap is different from the physical memory used by the heap . When checking the application heap , Android will calculate the memory size (PSS) value of that is allocated according to the ratio. This value takes into account both the dirty page and the clean page shared with other processes , but the number of is proportional to the number of applications that share the RAM . This (PSS) total is the system considers the physical memory usage of .
Dalvik heap does not compress the heap's logical size, which means Android will not defragment the heap to reduce the space . Only when has unused space in at the end of heap, can Android reduce the logical heap size , but the system can still reduce the physical memory used by by .After garbage collects , Dalvik traverses heap and finds unused pages , and then uses madvise to return these pages to kernel . Therefore, pairing allocation and unallocated should make All (or almost all) of the physical memory used by ml6 are recycled , but the efficiency of recycled from is much lower than , because pages for for are used for smaller allocation may still be sharing with other data blocks that have not yet released .
1.3.3 Limit application memory
In order to maintain the normal operation of in multitasking environment , Android will set the hard upper limit of for heap size for for each application . The exact heap size upper limit of for different devices depends on the overall RAM size of devices . If applies to try to allocate more memory after reaching heap capacity upper limit , you may receive OutOfMemory exception.
In some cases of , under , for example: In order to determine how much data is saved in cache , is safer , we can query the system by calling getMemoryClass() method ** to determine the exact available heap space size on the current device . This method returns an integer , and represents the number of available megabytes of the application heap **.
1.3.4 Switch application
When the user switches between applications, Android will retain non-foreground application in cache . non-foreground application refers to the ** application that cannot see the foreground service (for example: music playback) of . For example: When a user starts an application for the first time, the system will create a process for its . However, when user leaves this application , the process of will not exit . The system will keep the process in the cache . If user returns to the application later, the system will reuse the process , and will speed up the application switching speed**.
If the application has a cached process and retains the currently unwanted resource , then even if the user does not use the application , it will affect the overall performance of the system . When the system resources (for example: memory) are insufficient , it will terminate the cached process , and the system will also consider to terminate the process that consumes the most memory to free RAM.
It should be noted that when the application of is in cache, the less memory it consumes, the more likely it is to be exempted from being terminated and recover quickly. However, the system may also terminate at any time without considering the resource usage of the cache process according to current needs.
1.3.5 Memory allocation between processes
Android platform When running won't waste available memory , it will keep trying to utilize all available memory . For example: the system will keep in memory after application closes , so that users can quickly switch back to these applications . Therefore, usually, Android device has almost no memory available when running , so to correctly allocate between important system process and many user application , memory management is crucial.
will explain the basics of how Android allocates memory for system and user applications and operating systems to cope with low memory .
1.3.6 Memory type
Android device contains three different types of memory : RAM, zRAM and memory , as shown in the figure below:
It should be noted that CPU and GPU access the same RAM in .
- RAM is the fastest memory type , but its size is usually limited . high-end device usually has RAM capacity of maximum .
- zRAM is an RAM partition for swap space .All data will be compressed by when put into zRAM, and then will be decompressed by when copying from zRAM to . This part of RAM will increase or reduce as page enters and exits zRAM, and increases or reduces . device manufacturer can set zRAM size upper limit .
- Memory contains all persistence data (for example: file system, etc.) and ** Object code added for all applications, libraries and platforms . memory is much larger than the other two memory . On Android, memory is not used in swap space like other Linux implementations, because frequent writing to will cause corruption of in this memory , and shortens the service life of the storage medium**.
1.3.7 Memory page
Random access memory (RAM) is divided into ** multiple pages . Usually, each page is memory of 4KB **.
system will treat page as available or has used . The available pages for are unused RAM. The pages used by are currently using RAM that the system is currently using. They can be divided into the following categories:
- cache pages:
- has memory supported by files in memory (for example: code or memory mapped files). There are two types of cache memory:
- Private page: owned by a process and not shared.
- clean page : Unmodified file copy in memory, which can be deleted by kernel swap daemon (kswapd) to increase the available memory**.
- dirty page : The modified file copy in memory can be moved to ** zRAM by the kernel swap daemon (kswapd) or compressed in zRAM to increase the available memory**.
- Shared page: used by multiple processes.
- clean page : memory unmodified file copy , which can be deleted by kernel swap daemon (kswapd) to increase the available memory**.
- dirty page : The modified file copy in memory allows the exchange of daemon (kswapd) through the kernel or by explicitly using ** msync() or munmap() to write changes back to file in memory to increase memory space**.
- anonymous page: no memory supported by files in memory (for example: allocated by mmap() with MAP_ANONYMOUS tag set).
- dirty page : can be moved to **zRAM by the kernel swap daemon (kswapd) or compressed in zRAM to increase the available memory **.
It should be noted that clean page contains the ** exact copy of that exists in memory . If clean page no longer contains the exact copy of the file (for example, due to application operations), it will become dirty page . clean pages can delete , because they can always be regenerated using data in memory ; dirty pages can not delete , otherwise data will be lost **.
memory insufficient management
Android has two main mechanisms for to handle memory insufficient : kernel exchange daemon and low memory terminating daemon .
Kernel Swapd Daemon (kswapd)
Kernel Swapd Daemon (kswapd) is part of ** Linux kernel , which is used to convert used memory to available memory . When the available memory of on the device is insufficient , the daemon will become active . Linux kernel has an available memory upper and lower threshold . When the available memory drops below the lower limit threshold, kswapd starts to recycle memory ; When the available memory reaches the upper limit threshold, kswapd stops recycle memory**.
kswapd can delete clean pages to recycle them, because these pages are supported by in memory and has not been modified . If a certain process tries to process clean page of that has deleted , the system will copy the page from memory to RAM, and this operation becomes request paging .
The figure below shows that the clean page supported by is deleted by memory :
kswapd can move private dirty pages and anonymous dirty pages that caches to zRAM for compression , so that can free the available memory (available page) in RAM . If a certain process tries to process dirty page in zRAM, the page will be decompressed by and moved back to RAM. If the process associated with the compressed page is terminated by , the page will delete from zRAM. If the available memory amount of is below the specific threshold of , the system will start to terminate the process .
The figure below shows that the dirty page is moved to zRAM and compressed :
1.3.8 Low memory termination daemon (LMK)
Many times, the kernel swap daemon (kswapd) cannot free enough memory for the system . In this case, the system will use the onTrimMemory() method ** to notify the application that there is insufficient memory , and should reduce its allocation . If that's not enough, Linux kernel starts killing process frees memory with , it uses low memory termination daemon (LMK)** to do this.
LMK uses a out of memory score called oom_adj_score to determine the priority of the running process , thereby determining the process to terminate . The process with the highest score in was first terminated by . The background application was first terminated, and the system process was finally terminated .
The following figure lists the LMK score category of from high to low . The category with the highest score, that is, the items in the first row will be terminated first :
- Background apps (Background apps) : application that has been running before and is not active. LMK will first terminate the background process from application with highest oom_adj_score.
- Previous app (Previous app) : recently used background app . application has higher priority (lower score) than background application , because compared with a certain background application , users are more likely to switch to , an application on .
- Home Screen Application (Home app) : This is the launcher application . terminates the app to cause the wallpaper to disappear .
- Services (Services) : The service is started by the application, for example: synchronizes or uploads to the cloud .
- perceptible apps : The user can detect non-foreground application in some way, for example: runs a small interface search or listens to music .
- Foreground app (Foreground app) : The application is currently in use . terminates the foreground application , it looks like the application crashes , and may prompt the user that there is a problem with the device .
- persistence (service) (Persisient) : These are the core services of devices , such as: phone and WLAN.
- system (System) : system process . After these processes are terminated by , the phone may look like will restart soon.
- Native (Native) : The system uses very low-level process , for example: kernel interactively terminates daemon thread (kswapd) .
It should be noted that device manufacturers can change the behavior of LMK .
1.3.9 Calculate memory usage
kernel will track all memory pages in system .
The following figure shows the page used by different processes of :
When determining the amount of memory used by the application, the system must consider the page shared by . Accessing the same service or library shared memory page , for example: Google Play services and A certain game application may share location information service , so it is difficult to determine the amount of memory belonging to and lbs. The figure below shows the page shared by by two applications (intermediate) :
If you need to determine the memory usage of application , you can use any of the following indicators :
- Resident Memory Size (RSS) : The number of shared and non-shared pages used by applications .
- proportionally allocated memory size (PSS) : The number of non-shared pages used by the application plus the evenly allocated number of shared pages (for example: if three processes share 3MB, the PSS for each process is 1MB) .
- Exclusive Memory Size (USS) : The number of non-shared pages used by the application (excluding shared pages) .
If operating system wants to know how much memory is used by all processes , then the memory size (PSS) that is allocated proportionally (PSS) is very useful because the ** page only counts once, but it takes a long time to calculate , because system needs to determine the shared page and the number of processes that share pages . resident memory size (RSS) does not distinguish between shared and non-shared page , so is faster to calculate , and is more suitable for tracking changes in memory allocation**.
1.3.10 Manage application memory
Random access memory (RAM) is a valuable resource in any software development environment. Especially in mobile operating system . Since physical memory is usually limited , RAM is more valuable . Although both Android Runtime (ART) virtual machines and Dalvik virtual machines perform routine garbage collection tasks, this does not mean that we can ignore the application allocating and freeing memory locations and times . We still need to avoid the introduction of memory leak issue (usually caused by retaining object references in static member variables) and release all Reference objects at the appropriate time (e.g. lifecycle callback)**.
1.3.11 Monitoring the available memory and memory usage
We need to first find the usage problem of memory in the application, and then can fix the problem . You can use the memory performance profiler (Memory Profiler) in Android Studio to help us ** Find and diagnose memory problems**:
- Learn how our application allocates memory in for a period of time. Memory Profiler can display real-time chart , including: memory usage of applications , the number of Java objects allocated by , and the time when garbage collection events occur.
- initiates the garbage collection event and takes a snapshot of Java heap when the application is running.
- records the application's memory allocation , and then checks the allocated objects , checks the stack track of each allocated , and jumps to the corresponding code in Android Studio Editor .
1.3.12 Free memory to respond to events
As mentioned above, Android can recycle memory from the application through in various ways or completely terminate the application when requires , so releases memory to perform critical tasks.In order to further help balance system memory and avoid the system need to terminate our application process , we can implement the ComponentCallback2 interface in the Activity class and rewrite the onTrimMemory() method. can listen to memory-related events when in the foreground or backend , and then releases the object in response to the application life cycle event or system event that indicates that the system needs to recycle memory. The sample code is as follows:
/** * Created by TanJiaJun on 2020/7/7. */class MainActivity: AppCompatActivity(), ComponentCallbacks2 {/** * Release memory when the UI is hidden or the system resources are insufficient. * @param level Memory-related events raised */override fun onTrimMemory(level: Int) {super.onTrimMemory(level)when (level) {ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN - {/** * Free all UI objects currently holding memory. * * The user interface has been moved to the background. */}ComponentCallbacks2.TRIM_MEMORY_RUNNING_MODERATE,ComponentCallbacks2.TRIM_MEMORY_RUNNING_LOW,ComponentCallbacks2.TRIM_MEMORY_RUNNING_CRITICAL - {/** * Free the memory that the application does not need to run. * * The device is insufficient memory when the application is running. * The raised event indicates the severity of the memory-related event. * If the event is TRIM_MEMORY_RUNNING_CRITICAL, the system will start killing the background process. */}ComponentCallbacks2.TRIM_MEMORY_BACKGROUND,ComponentCallbacks2.TRIM_MEMORY_MODERATE,ComponentCallbacks2.TRIM_MEMORY_COMPLETE - {/** * Free as much memory as possible that the process can free. * * The application is in the LRU list and the system is out of memory at the same time. * The raised event indicates the location of the application in the LRU list. * If the event is TRIM_MEMORY_COMPLETE, the process will be the first to be terminated. */}else - {/** * Publish any non-critical data structure. * * The application receives an unrecognized memory level value from the system, and we can treat this message as a normal low memory message. */}}}}}}} }
It should be noted that the onTrimMemory() method was added only by Android 4.0. For the early versions of , we can use the onLowMemory() method. This callback method is roughly equivalent to the TRIM_MEMORY_COMPLETE** event.
1.3.13 Check how much memory should be used
In order to allow multiple processes of to run at the same time, Android sets a hard limit for the heap size allocated for each application. This limit varies depending on the overall amount of RAM available on the device . If our application has reached the heap capacity limit and tries to allocate more memory , the system will throw an OutOfMemory exception.
In order to avoid running out of memory , we can query the system to determine the heap space available for on the current device. You can query the system for this value by calling getMemoryInfo() method. This method will return ** ActivityManager.MemoryInfo object, which will provide information related to the current memory state of the device , for example: available memory , total memory and memory threshold (if this memory level is reached, the system will start to terminate the process)**. ActivityManager.MemoryInfo object will also provide a boolean value lowMemory. We can determine whether the device has insufficient memory based on this value . The sample code is as follows:
fun doSomethingMemoryIntensive() {// Check whether the device is in a low memory state before executing logic that requires a lot of memory if (!getAvailableMemory().lowMemory) {// Execute logic that requires a lot of memory}}// Get the MemoryInfo object private fun getAvailableMemory(): ActivityManager.MemoryInfo =ActivityManager.MemoryInfo().also {(getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager).getMemoryInfo(it)}
1.3.14 Using the code structure with higher memory efficiency
We can choose the -efficient solution in the code to minimize the memory usage of the as much as possible.
1.3.15 Use Service (Service)
with caution. If our application requires a certain service (Service) to perform work in the ** background , please do not keep it running . Unless it really needs to run the job, should stop running after the service completes the task, otherwise it may cause memory leak**.
After we start a certain service , the system prefers to keep the process of this service running state . This behavior will cause the service process to cost very high , because once service uses a certain part of RAM, then this part of RAM will no longer be used by other processes for . This will reduce the number of cache processes that the system can retain in LRU cache , so that reduces the application switching efficiency of . When memory is tight and the system cannot maintain enough processes to host the currently running service , it may cause memory jitter .
should usually avoid using persistence service , because they will make persistence requirements for memory available , we can use JobScheduler to schedule background process .
If we have to use a certain service , the best way for to limit the life cycle of this service is to use IntentService, which will end itself immediately after processing the start of its intent.
1.3.16 Some classes provided by the
programming language using the optimized data container programming language are not optimized for mobile devices. For example, the memory efficiency implemented by conventional HashMap may be very low in , because each mapping of needs to correspond to a separate entry object . Android framework contains several optimized data containers , such as: SparseArray, SparseBooleanArray and LongSparseArray. Taking SparseArray as an example, is more efficient because it can avoid the system need to automatically box keys (sometimes also value) (this will create 1 to 2 objects for each entry) . , as much as possible, use streamlined data structure , for example: array . Developers often regard abstract as a good programming practice , because abstraction can improve code flexibility and maintenance , but abstract is very expensive . usually requires more code to execute , requires more time and more RAM to map code to memory . Therefore, if abstract does not bring significant benefits , we should avoid using abstract . protocol buffer (Protocol Buffers) for serialized data. is a 1.3.17 Careful for code abstraction
1.3.18 Use the streamlined version of Protobuf
1.4 Avoid memory jitter
As mentioned earlier, garbage collection event usually does not affect the application performance . However, if occurs many garbage collection events in a short time, may quickly run out of frame time . The more time the system spends on garbage collection on , the less time it can be spent on rendering interface or streaming audio .
Usually, memory jitter may cause a large number of garbage collection events in . In fact, memory jitter can indicate the number of allocated temporary objects that appear in a given time . For example: we allocate multiple temporary objects in for in for or create ** Paint objects or onDraw() method of View. In these two cases, the application will quickly create a large number of objects in . These operations can quickly consume all available memory in the new generation (young generation) area, so forces garbage collection events to happen **.
We can use memory performance profiler (Memory Profiler) in to find the higher position of memory jitter . After determining the problem area in the code, tries to reduce the number of allocated in the area that is crucial to performance. You can consider to move some code logic out of the internal loop or to use factory method mode**.
removes resources that will occupy a lot of memory and library
. Some resources and library may swallow memory without us knowing it. The overall size of APK (including third-party libraries or embedded resources) may affect the application's memory consumption . We can reduce the application's memory consumption by removing any redundant , unnecessary or bloated components , resources or library from the code, , resources or library , , library , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , , html
Reduce the overall APK size
We can reduce the overall size of the application by to significantly reduce the memory usage of the application . bitmap size , resource , animation frame number and third-party library will all affect the size of APK. Android Studio and Android SDK provide various tools to help us reduce resources and external dependency size . These tools can reduce code , such as: R8 compile .
When we build a project using Android Gradle plugin 3.4.0 and later , the plugin no longer uses ProGuard to perform compile-time code optimization , but works in conjunction with R8 compiler to handle the following compile-time tasks:
- Code reduction (i.e. Tree Shaking) : detects and safely removes unused classes, fields, methods and properties from the application and its library dependencies (which makes it a very useful tool for circumventing 64K reference restrictions) . For example: If we only use a few API for a certain library dependency of , the reduction function can identify the library code that is not used by the application, and removes this part of the code from the application.
- Resource Reduction : removes unused resources from the encapsulated application. includes unused resources in the application library dependencies. This function can be used in conjunction with the code reduction function. In this way, after removing the code that does not use , all resources that no longer references can be safely removed.
- obfuscation processing : shortens the name of class and member , thereby reducing the size of DEX file .
- Optimization : check and rewrite the code , to further reduce the size of the applied DEX file . For example: If R8 detects that has never used the code of the else branch of , will remove the code of the else branch.
Use Android App Bundle to upload applications (only Google Play)
When publishing to Google Play, you need to reduce the application size immediately. The easiest way is to publish the application as Android App Bundle, which is a brand new upload format , which contains all the compiled code and resources of the application. Google Play is responsible for handling APK generation and signature work .
Google Play's new application service mode Dynamic Delivery will use the App Bundle provided by us to generate and provide optimized APK for each user's device configuration. Therefore, they only need to download the code and resources required to run our application. We do not need to compile , sign and manage and multiple APK to support different devices . Users can also get smaller and more optimized download file package .
It should be noted that Google Play stipulates that the compressed download size of signed APK we uploaded is limited to no more than 100MB, while the compressed download size of applied to App Bundle is limited to 150MB for applications published using App Bundle.
Use Android Size Analyzer
Android Size Analyzer tool to easily discover and implement a variety of strategies to reduce the size of . It can be used as Android Studio plug-in or standalone JAR.
Use Android Size Analyzer
in Android Studio. We can use plug-in market in Android Studio to download Android Size Analyzer plug-in. You can follow the following steps:
- Select Android StudioPreferences in turn. If it is Windows, select FileSettings in turn.
- Select the Plugins section in the left panel.
- Click on the Marketplace tag.
- Search for Android Size Analyzer plugin.
- Click the Install button of analyzer plugin .
as shown in the figure below:
After installing plug-in , select AnalyzeAnalyzeApp from menu bar Size, run application size analysis for the current project. After analyzing the project, the system will display an tool window , where contains suggestions on how to reduce the application size , as shown in the figure below:
Use analyzer
through the command line
We can download the latest version of version Android Size Analyer from GitHub in the form of TAR or ZIP files. After decompressing the file, use the following command to Android project or Android App Bundle runs size-analyzer script (on Linux or MacOS) or ** size-analyzer.bat script (on Windows) **:
./size-analyzer check-bundle path-to-aab./size-analyzer check-project path-to-project-directory
1.4.1 Understand the APK structure
Before discussing how to reduce the size of the application , it is necessary to understand the structure of APK. APK file consists of an Zip compressed file , which contains all the files of the application , these files include Java class file , resource file , and file containing compiled resources.
APK contains the following folder :
- META-INF/: contains CERT.SF and CERT.RSA signature files, as well as MANIFEST.MF manifest files.
- assets/: contains the resource of the application. These resources can be retrieved using the AssetManager object.
- res/: Contains the resource that is not compiled into resources.arsc.
- lib/: Contains compiled code specific to processor software layer . This directory contains subdirectories for each platform type , such as: armeabi, armeabi-v7a, armeabi-v7a, arm64-v8a, x86, x86_64 and mips.
APK also contains the following files , in which only AndroidManifest.xml is required for :
- resources.arsc: Contains compiled resource , this file contains ** XML content in all configurations of the res/values/ folder. packaging tool will extract this XML content, compile it into binary file form , and compress the content , which includes language strings and styles , and the path to content (for example, layout files and pictures) that is not directly included in the resources.arsc file**.
- classes.dex: Contains class that is compiled in DEX file format understandable by Android Runtime (ART) virtual machines and Dalvik virtual machines.
- AndroidManifest.xml: Contains Android manifest file . This file lists the name of application, version , access permissions and library file . It uses Android's binary XML format .
1.4.2 Reducing the number of resources and size
APK size will affect the loading speed of applications , the amount of memory used by and the power consumed by . A simple way to reduce the size of APK is to reduce the number of resources and sizes it contains. Specifically, we can remove the resource that the application no longer uses, and can replace the image file with a scalable Drawable object.
1.4.3 Remove unused resources
lint tool is the static code analyzer included in Android Studio. You can detect the resource that is not referenced by the code in the res/ folder. When lint tool finds that there is a resource that may not be used in the project, a message will be displayed **, and the message is as follows:
res/layout/preferences.xml: Warning: The resource R.layout.preferences appears to be unused [UnusedResources]
It should be noted that lint tool will not scan assets/folder , resources and referenced by reflection have been linked to the application's library file . In addition, will not remove resource , but will only remind us of their existence .
If we enable shrinkResource in the application's build.gradle file, then Gradle can help us automatically remove unused resources . The sample code is as follows:
android {buildTypes {release {minifyEnabled trueshrinkResources trueproguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'}}}
To use shrinkResource, we must enable code reduction function. In compilation process , R8 will first remove unused code , and then Android Gradle plug-in will remove unused resources .
In Android Gradle plug-in version 0.7 and later , we can declare the configuration supported by the application . Gradle will use resConfig and resConfigs variant and defaultConfig options to pass this information to compilation system . Subsequently, compilation system will prevent other unsupported configurations from appearing in APK , and reduce the size of APK .
It should be noted that code reduction can clean up some unnecessary code of the library , but may not be able to remove large internal dependencies .
1.4.4 Minimize the resource usage in the library
When developing Android application , we usually need to use external library to improve the usability and versatility of the application . For example: we can use Glide to realize the image loading function.
If library is designed for server or desktop device , it may contain many objects and methods that are not needed by applications. If library license allows us to modify library , we can edit the library file to remove unwanted part of , and we can also use library suitable for mobile devices.
1.4.5 only supports specific density
Android supports a variety of devices, covering various screen density .In Android 4.4 (API level 19) and later versions of , the framework supports various density : ldpi, mdpi, tvdpi, hdpi, xhdpi, xxhdpi, xxhdpi, and xxxhdpi. Although Android supports all these density , we do not need to export rasterized resource to per density .
If we do not add the resource for a specific screen density, Android will automatically scale the resource for other screen density designs. It is recommended that each application of contains at least one xxhdpi image variant .
1.4.6 Use drawable objects
Some pictures does not require static image resource . The framework can dynamically draw pictures at runtime. We can use Drawable object (shape element in XML) to dynamically draw the picture , which will only occupy a small amount of space in the APK . In addition, Drawable object of XML can generate monochrome pictures that meet the Material Design guidelines**.
1.4.7 Reuse resource
We can add separate resource for the variant of picture, for example: same picture shade setting or rotate version. It is recommended that reuse the same set of resources , and customize it as needed at runtime.
On Android5.0 (API level 21) and later versions of , the android:tint and android:tintMode properties can change the color of the resource . For platforms with lower versions of , the ColorFilter class is used.
We can omit the resource that is only the rotation equivalent of another resource. The following example shows that rotates 180 degrees around the center position of the image, and turns thumb up into thumb down . The example code is as follows:
?xml version="1.0" encoding="utf-8"?rotate xmlns:android="http://schemas.android.com/apk/res/android"android:drawable="@drawable/ic_thumb_up"android:fromDegrees="180"android:pivotX="50%"android:pivotY="50%" /
1.4.8 Rendering
from the code, we can reduce the APK size of by rendering pictures according to a certain program, so that can free up a lot of space , because does not need to store image files in the APK.
1.4.9 Compress the PNG file
aapt tool can optimize the image resource placed in res/drawable/ in the compilation process through lossless compression of . For example: aapt tool can convert true color PNG that does not require more than 256 colors to 6 68-bit PNG through palette . This will generate images with the same quality but smaller memory usage.
It should be noted that the aapt tool has the following limitations: the
- aapt tool will not reduce the PNG file contained in the asset/folder.
- image file requires 256 or less colors to be used for aapt tool to optimize .
- aapt tool may expand the compressed PNG file . To prevent this, we can use cruncherEnabled to mark PNG file to disable this process. The sample code is as follows:
- aaptOptions { cruncherEnabled = false }
compresses PNG and JPEG files
We can use pngcrush, pngquant or zopflipng and other tools reduce the size of the PNG file , and at the same time does not lose the image quality . All these tools can reduce the size of PNG files in , while keeps the image quality perceived by the naked eye unchanged .
pngcrush tool is the most effective for : the tool will iterate over the PNG filter and zlib (Deflate) parameters , and use each combination of filter of parameters to compress the image , and then it will select the configuration that can produce the minimum compressed output.
To compress JPEG file , we can use tools such as packJPG and guetzli.
uses WebP file format
If Android3.2 (API level 13) and higher version is the target (target) , we can use image in the WebP file format instead of PNG file or JPEG file . WebP format provides lossy compression (for example: JPEG) and ** transparency (for example: PNG) **, but compared with PNG or JPEG, this format can provide better compression effect .
We can use Android Studio to convert the existing BMP, JPG, PNG or static GIF picture into WebP format.
It should be noted that Google Play only accepts the launcher icon in PNG format.
Using vector graphics
We can use vector graphics to create resolution-independent icons and other scalable media , which can greatly reduce the space occupied by APK . vector image is represented in Android as an VectorDrawable object. The 100-byte file can generate a clear picture with the same screen size.
It should be noted that it takes a lot of time to render each VectorDrawable object in system to render each VectorDrawable object. It takes longer to render the larger picture of to display on the screen. Therefore, it is recommended that use VectorDrawable object only when displaying small pictures.
Use vector graphics for animation pictures
Do not use AnimationDrawable to create frame-by-frame animation , because doing this requires adding separate bitmap (bitmap) file for each frame . This will greatly increase the size of APK . It should be used instead to create animation vector drawing resource using AnimatedVectorDrawableCompat.
1.5 Reduce native (Native) and Java code
We can use multiple methods to reduce native (Native) and ** Java code base size **.
1.5.1 Remove unnecessary generation code
ensures that you understand the space taken for automatic generation of any code. For example: Many protocol buffer tools will generate too many classes and methods , which may double or double the size of the application .
1.5.2 Avoid using enumeration
Single enumeration will increase the size of the applied classes.dex file by about 1.0 to 1.4KB. The increased size of these will quickly accumulate , resulting in complex systems or shared library . If possible, please consider using @IntDef annotation and code reduction to remove enumerations and convert them into integer . This type conversion can retain enumeration various security advantages of .
1.5.3 Reduce the size of native binary files
If our application uses native code and Android NDK, we can also use to optimize code to reduce the size of the publishing application . removes debugging symbols and to avoid decompressing the native library is two very practical technologies for .
Remove debugging symbol
If the application is under development and still requires to debug , it is very suitable to use debugging symbol . We can use arm-eabi-strip tool provided in Android NDK to remove unnecessary debugging symbol from native library . After that, we can compile and release version by .
avoids decompressing native library
When building the release version of of the application in 6, we can package the uncompressed .so file in APK by setting android:extractNativeLibs="false" in application list in APK. Deactivating this tag prevents PackageManager from copying .so files from APK to file system during installation, and has the added benefit of reducing application updates . When building an application using Android Gradle plugin 3.6.0 and later , the plugin will set this property to false by default.
1.6 Maintain multiple streamlined APK
APK may contain content that users download but never use , such as other language or resources for specific screen density . To ensure that the user is provided with the smallest download file for , we should use Android App Bundle to upload the app to Google Play. By uploading App Bundle, Google Play can configure for each user's device to generate and provide optimized APK. Therefore, users only need to download the code and resources required to run our application. We no longer need to compile , sign and manage and multiple APK to support different devices . Users can also obtain smaller and more optimized download file package .
If we do not plan to to publish the application to Google Play, we can subdivide the application into multiple APK and distinguish it according to factors such as screen size or GPU texture support .
When the user downloads our application, our device will set to receive the correct APK of according to the functions of the device, so that the device will not receive the functions and resources that devices do not have. For example: If the user has hdpi devices, does not need resources provided by xxxhdpi for the higher density display .
1.7 Dependency injection using Dagger2
Dependency injection framework can simplify the code we wrote , and provides an adaptive environment for us to perform tests and other configuration changes .
If we plan to use dependency injection framework in our application, consider using Dagger2. Dagger2 does not use reflection to scan the application's code . Its static compile-time implementation of means it can be used in Android application , and will not bring unnecessary running cost or memory consumption .
Other dependency injection frameworks that use reflection tend to initialize process by scanning comments in code. This process may require more CPU cycles and RAM, and may cause significant delays when the application starts .
1.8 Careful use of external libraries
External library code is usually not written for mobile environment . Running on mobile client may be inefficient in . If we decide to use the external library , we may need to optimize the library for mobile device . Before deciding to use the library , please plan in advance and analyze the library in terms of code size and RAM consumption .
Even some libraries optimized for mobile devices may cause problems due to different implementation methods of . For example, one library may use simplified version Protobuf, while another library uses Micro Protobuf, which leads to two different implementations of in our application. logging , analysis , image loading framework , and many other functions that we unexpectedly implement may cause this situation.
Although ProGuard can remove API and resource with appropriate tags, cannot remove the library's large internal dependency .The functions we need in libraries may require dependency for lower level . This is particularly prone to problems if the following situations exist: we use the Activity subclass in (often there are a lot of dependencies) , libraries to use reflection (this is very common, meaning we need to spend a lot of time manually adjusting ProGuard to make it run) , etc.
In addition, please avoid using the shared library for one or two of the dozens of functions. This will cause a lot of code and overhead for that we cannot even use at all. When considering whether to use this library , please find an implementation of that is very consistent with our needs. Otherwise, can decide to create and implement by ourselves.
3. Class loading mechanism
3.1 Life cycle of class
3.1.1 Loading stage
Loading stage can be subdivided into the binary stream of the loading stage
- loading class
- data structure conversion, converting the static storage structure represented by the binary stream into the runtime data structure of the method area
- to generate a java.lang.Class object, as the access portal for various data of this class
loading class binary stream method
- read from the zip package. Our common JAR and AAR dependencies on
- to generate dynamically when running. Our common dynamic proxy technology is used in java.reflect.Proxy to generate a proxy binary stream for specific interfaces
3.1.2 Verification
Verification is the first step in the connection stage. The purpose of this stage is to ensure that the information contained in the byte stream of the Class file meets the requirements of the current virtual machine and does not endanger the security of the virtual machine itself.
- file format verification: such as whether it starts with magic number 0xCAFEBABE, whether the primary and secondary version numbers are within the current virtual machine processing range, and constant rationality verification, etc. This stage ensures that the input byte stream can be correctly parsed and stored in the method area, and the format meets the requirements of describing a Java type information.
- metadata verification: whether there is a parent class, whether the inheritance chain of the parent class is correct, whether the abstract class implements all the methods required to be implemented in its parent class or interface, whether the fields and methods conflict with the parent class, etc. The second stage is to ensure that there is no metadata information that does not comply with Java language specifications.
- bytecode verification: Through data flow and control flow analysis, it is determined that the program semantics are legal and logical. For example, ensure that the jump instruction will not jump to bytecode instructions other than the method body.
- Symbol Reference Verification: Occurs during the parsing phase, ensuring that symbol references can be converted into direct references.
You can consider using the -Xverify:none parameter to close most class verification measures to shorten the load time of virtual machine class.
3.1.3 Prepare
to allocate memory for class variable and set the initial value of the class variable. The memory used by these variables will be allocated in the method area.
3.1.4 The process of parsing
virtual machine replaces symbol references in the constant pool with direct references. The parsing action mainly focuses on class or interface, fields, class methods, interface methods, method types, method handles and call point qualifiers. 7 class symbol references perform
3.1.5 Initialize
to the initialization stage, and only then do the Java program code defined in the class are actually started to execute. This stage is the process of executing the clinit() method.
3.1.6 Time for class loading
Virtual Machine Specification stipulates that there are and only 5 cases where the class must be "initialized" immediately (and loading, verification, and preparation naturally need to start before this).
- When encountering the four bytecode instructions of new, getstatic, putstatic or invokestatic, if the class has not been initialized, its initialization needs to be triggered first. The corresponding scenarios are: instantiating an object using new, reading or setting a static field of a class (except for static fields that have been placed in a constant pool during the compilation period), and calling a static method of a class.
- When performing reflection calls on a class, if the class has not been initialized, it needs to be triggered first.
- When the parent class of the initialization class has not been initialized, the initialization of its parent class needs to be triggered first. (An interface does not require all its parent interfaces to be initialized when initializing.) When the
- virtual machine is started, the user needs to specify a main class to be executed (the class containing the main() method), and the virtual machine will initialize the main class first.
- When using the dynamic language support of JDK 1.7, if the last parsing result of a java.lang.invoke.MethodHandle instance is the method handle of REF_getStatic, REF_putStatic, REF_invokeStatic, and the class corresponding to this method handle has not been initialized, it needs to be triggered first.
Note:
- references the static field of the parent class through the subclass, which will not cause the subclass to be initialized.
- references classes through array definitions and does not trigger initialization of such classes. MyClass[] cs = new MyClass[10];
- constants will be stored in the constant pool of the calling class during the compilation stage. In essence, they do not directly refer to the class that defines the constant, so the initialization of the class that defines the constant will not be triggered.
3.2 Class loader
calls the code module that implements the action of "geting a binary byte stream describing this class through the fully qualified name of a class" in the class loading stage called the "class loader".
puts the binary data of the class file into the method area, and then creates a java.lang.Class object in the heap. The Class object encapsulates the data structure of the class in the method area and provides developers with an interface to access the data structure in the method area.
3.3 Uniqueness of class
For any class, the class loader that loads it needs to establish its uniqueness in the Java virtual machine together with the class itself.
Even if the two classes originate from the same Class file and are loaded by the same virtual machine, as long as the class loaders loaded are different, the two classes are not equal. The "equal" referred to here includes the equals() method of the Class object representing the class, isAssignableFrom() method, isInstance() method, and isInstance() method, as well as the use of the instanceof keyword to determine the object's belonging relationship, etc.
3.4 Parent delegation mechanism
If a class loader receives a class loading request, it will first not try to load the class itself, but delegate the request to the parent class loader to complete. This is true for each level of class loader, so all load requests should eventually be sent to the top-level startup class loader. Only when the parent loader feedbacks that it cannot complete the load request (the required class is not found in its search scope), the child loader will try to load it itself.
protected Class? loadClass(String name, boolean resolve)throws ClassNotFoundException{// First, check if the class has already been loaded//First add this class from the cache without loading Class? c = findLoadedClass(name); if (c == null) {try {if (parent != null) {//Load c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// ClassNotFoundException thrown if class not found// from the non-null parent class loader}//If it cannot be loaded, load it yourself if (c == null) {// If still not found, then invoke findClass in order// to find the class.c = findClass(name);}}return c;}
benefits
- Avoid repeated loading. When the parent loader has loaded the class, there is no need for the child ClassLoader to load it again.
- security considerations prevent the core API library from being tampered with at will.
3.5 ClassLoader
- ClassLoader is an abstract class that defines the main function of ClassLoader
- BootClassLoader is a subclass of ClassLoader (note that it is not an internal class, some materials say it is an internal class, which is wrong). It is used to load some classes required at the Framework level of the system. It is all ClassLoa on the Android platform. The final parent
- SecureClassLoader of der extends the ClassLoader class, adds permissions, and enhances security
- URLClassLoader inherits SecureClassLoader, which is used to load classes and resources from jar files and folders through URI paths. It is basically impossible to use
- BaseDexClassLoader in Android. It implements Android Most of the functions of ClassLoader
- PathClassLoader loads the application's class, and will load the dex file in the /data/app directory, as well as the apk file or java file containing the dex (some materials say it will also load the system class, but I haven't found it, there is doubt here).
- DexClassLoader can load custom dex files and apk file or jar file containing the dex, and supports loading from the SD card. When we use plug-in technology, we will use
- InMemoryDexClassLoader to load dex files in memory
3.6 ClassLoader loading process source code analysis
- ClassLoader.java class
protected Class? loadClass(String name, boolean resolve) throws ClassNotFoundException {synchronized(this.getClassLoadingLock(name)) {//First find out whether the class has been loaded, if it has been loaded, directly return Class? c = this.findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();try {if (this.parent != null) { //Delegate to the parent loader for loading ClassLoader parent;c = this.parent.loadClass(name, false);} else { //When executing to the top-level class loader, parent = nullc = this.findBootstrapClassOrNull(name);}} catch (ClassNotFoundException var10) {}if (c == null) {long t1 = System.nanoTime();c = this.findClass(name);PerfCounter.getParentDelegationTime().addTime(t1 - t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);//If not found in the parent loader, PerfCounter.getFindClasses().increment();}}if (resolve) {this.resolveClass(c);}return c;}}
is implemented by subclass
protected Class? findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);}
BaseDexClassLoader class findClass method
protected Class? findClass(String name) throws ClassNotFoundException {ListThrowable suppressedExceptions = new ArrayListThrowable();// pathList is a DexPathList, which is the specific place to store the code.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;}public Class? findClass(String name, ListThrowable 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;}public Class? findClass(String name, ClassLoader definingContext,ListThrowable suppressed) {return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed) : null;}public Class loadClassBinaryName(String name, ClassLoader loader, ListThrowable suppressed) {return defineClass(name, loader, mCookie, this, suppressed);}private static Class defineClass(String name, ClassLoader loader, Object cookie, DexFile dexFile, ListThrowable suppressed) {Class result = null;try {result = defineClassNative(name, loader, cookie, dexFile);} catch (NoClassDefFoundError e) {if (suppressed != null) {suppressed.add(e);}} catch (ClassNotFoundException e) {if (suppressed != null) {suppressed.add(e);}} return result;}// Call Native layer code private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)
3.7 Hot repair technology
3.7.1 Introduction to hot repair technology
- re-release version is expensive, high, untimely, and poor user experience. There are several solutions to this:
- Hybird: native + H5 hybrid development, the disadvantage is that the labor cost is done, and the user experience is not as good as the pure native solution;
- plug-in: high porting cost, the transformation of old code is time-consuming and labor-intensive, and cannot be modified dynamically;
- hot repair technology, upload patches to the cloud, the app can Directly download the patch from the cloud and apply it directly;
- hot repair technology is a relatively practical function for domestic developers, which can solve the following problems:
- releases a new version with high cost, and the user downloads and installation costs are high; the efficiency of the update of
- version takes a long time to complete the version coverage; the upgrade rate of
- version update, users who do not upgrade the version cannot get fixed, which is even more powerful and more violent.
- 's small and important functions need to be completed in a short time to cover the version, such as festivals.Advantages of
- hot repair: no release version, no user perception, high repair success rate and short time;
The hot repair framework of the hundreds of schools of thought
- mobile Taobao Dexposed: Open source, underlying replacement solution, based on Xposed, Java Method for Dalvik runtime Hook technology, but it is too dependent on Dalvik's underlying layer and cannot continue to be compatible with ART after Android 5.0, so it is given up;
- Alipay's Andfix: open source, underlying replacement solution, with the help of Dexposed idea, Dalvik and ART environments are compatible with full versions, but its underlying fixed structure replacement solution is not stable, and there are many restrictions on the scope of use. Moreover, for resource and so repair, it failed to implement
- Alibaba Baichuan's Hotfix: open source, underlying replacement solution, relying on Andfix and decoupling of business logic, with good security and ease of use, but there are still the disadvantages of Andfix;
- Qzone super patch: Unopen source, class loading solution, will invade the packaging process
- Meituan's Robust: open source, Instant Run solution,
- Dianping's Nuwa: open source, class loading solution,
- Ele.me's Amigo: open source, class loading solution
- WeChat's Tinker: open source, class loading solution
- Taobao's Sophix: unopen source
3.7.2 Principle of hot repair technology
- hot repair framework has three main core technologies, namely code repair, resource repair and dynamic link library repair
code repair:
- code repair has three main solutions, namely the underlying replacement solution, class loading solution and Instant Run solution
1. Class loading solution
- class loading solution requires restarting the App and let ClassLoader reload the new class, because the class cannot be uninstalled. If you want to reload the new class, you need to restart the App. Therefore, the hot repair framework using the class loading solution cannot take effect immediately.
Advantages:
- does not require too many adaptations;
- implementation is simple and has no many restrictions;
Disadvantages
- requires APP restart to take effect (cold startup repair);
- dex Instrumentation: The performance loss caused by instrumentation on the Dalvik platform, and the problem that the patch package may be too large due to address offset problems on the Art platform;
- dex replacement: Dex merge memory consumes in vm On the head, it may OOM, resulting in the merge failure of
- virtual machine to class with the CLASS_ISPREVERIFIED flag during installation. This is to improve performance. Forced prevention of the class being marked with the flag will affect performance;
Dex subcontracting
- class loading scheme is based on the Dex subcontracting scheme, and the Dex subcontracting scheme is mainly to solve the 65536 limit and LinearAlloc limit:
- 65536 limit: The method call instruction invoke-kind index of the DVM instruction set is 16 bits, and it can be referenced at most. 65535 methods;
- LinearAlloc limit: LinearAlloc in DVM is a fixed cache area. When too many methods exceed the size of the cache area, INSTALL_FAILED_DEXOPT is prompted during installation;
- Dex subcontracting scheme: When packaging, divide the application code into multiple Dex, put the classes that must be used when the application starts and the directly referenced classes of these classes into the main Dex, and put other code into the secondary Dex. When the application starts, the main Dex is loaded first, and then the second Dex is loaded dynamically after the application starts. There are two main solutions, namely Google's official solution, Dex automatic unpacking and dynamic loading solution.
Several different implementations:
- Place the patch package in the first element of the Element array and get priority loaded (super patch and Nuwa in QQ space)
- takes out the Element corresponding to each dex in the patch package, and then forms a new Element array, and replaces the existing Element with the new Element array through reflection at runtime. Array (Amigo from Ele.me);
- diffs the new and old apks, get patch.dex, and then merge patch.dex with the classes.dex of the apk in the mobile phone to generate new classes.dex, and then reflect the classes.dex at runtime to place the first element of the Element array (WeChat Tinker)
- Sophix: The granularity of dex is in the dimension of the class, and the order of dex in the package is reorganized, classes.dex, classes2.dex.., can be regarded as a class instrumentation scheme at the dex file level, breaking the order of dex in the old package, reorganizing the order of dex in the old package
2. The underlying replacement solution
- comes from the Xposed framework, which perfectly interprets AOP programming, directly modifying the original class in the Native layer (no need to restart the APP). Since there are many restrictions on modification in the original class, the methods and fields of the original class cannot be added or decreased, because this destroys the structure of the original class (causing index changes). Although there are many restrictions, it is time-consuming and easy to load, and it takes effect immediately;
advantages
- takes effect in real time, and does not require restart. Loading
disadvantages
- has poor compatibility. Since the implementation of each version of the Android system is different, a lot of compatibility needs to be done.
- development requires mastering jni related knowledge, and native exception troubleshooting is more difficult
- . Since new methods and fields cannot be added, it is impossible to achieve the function release level
. Several different implementations are:
- uses fields in the ArtMethod structure, which will have compatibility problems, because the modifications of mobile phone manufacturers and the iteration of the Android version may lead to differences in the underlying ArtMethod structure, resulting in failure of method replacement; (AndFix)
- uses both class loading and underlying replacement solutions. For small modifications, the underlying replacement solutions are restricted. In the surrounding area, we will also determine whether the running model supports the underlying replacement solution. We will use the underlying replacement (replace the entire ArtMethod structure so there will be no compatibility problems), otherwise we will use class load replacement; (Sophix)
3. The principle of the new feature of Instant Run is that after the code changes, we will conduct incremental construction, that is, only the changed code is built, and this part of the code is deployed incrementally on the device in the form of a patch, and then the code is hot replaced, so as to observe the effect of code replacement. In fact, Instant Run and Hot Repair are essentially the same.
Instant Run Packaging Logic
- After connecting to Instant Run, compared with the traditional method, there will be the following four different points when packaging: InstantRun will generate its own application, and then register this application in the manifest configuration file, so that a series of preparations can be done in it, and then the business code can be run;
- stant Run code is placed in the main dex: After manifest injection, the code of Instant Run will be placed in the first loaded dex file of the Android virtual machine, including classes.dex and classes2.dex. These two dex files are stored in Instant Run itself framework code, without any business layer code.
- engineering code insert—IncretmentalChange; this insert will involve specific IncretmentalChange class.
- project code is put into instantrun.zip; the logic here is to unzip the specific project code in this package after the entire app is running and run the entire business logic.
- Instant Run When building apk for the first time, ASM used ASM to inject code similar to the following into each method (ASM is a Java bytecode control framework. It can be used to dynamically generate classes or enhance the functions of existing classes)
//$change implements the IncrementalChange abstract interface.//When clicking InstantRun, if the method does not change, $change is null, return is called without any processing. //If the method changes, a replacement class will be generated. Assuming that the onCreate method of MainActivity is modified, the replacement class MainActivity$override will be generated. //This class implements the IncrementalChange interface, and will also generate an AppPatchesLoaderImpl class. The getPatchedClasses method of this class will return a list of the modified class (including MainActivity). According to the list, the $change of MainActivity will be set to MainActivity$override// Therefore, localIncrementalChange is satisfied!= null will execute the access$dispatch method of MainActivity$override. //The access$dispatch method will execute the onCreate method of MainActivity$override based on the parameter "onCreate.(Landroid/os/Bundle;)V", and thus the onCreate method is modified. IncrementalChange localIncrementalChange = $change;if (localIncrementalChange != null) {//2localIncrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[] { this,paramBundle });return;}
A significant change in the abandoned Instant Run
Android Studio 3.5 is the introduction of Apply Changes, which replaces the old Instant Run. Instant Run is to make small changes to your applications more easily and test them, but it creates some problems. To solve this problem, Google has completely removed Instant Run and fundamentally built Apply Changes, no longer modifying the APK during the build process, but instead dynamically redefines the class using runtime tools, which should be more reliable and faster than running right away.
Advantages
- takes effect in real time, and there is no need to restart
- supports adding methods and classes
- supports method-level repair, including static method
- . Each function of each product code is automatically inserted into the compilation and packaging stage. The insertion process is completely transparent to business development
Disadvantages
- code is invasive, and the relevant code will be added to the original class
- will increase the volume of the apk
4. Resource repair
- Currently, most resource hot repair solutions on the market basically refer to the implementation of Instant Run. It is mainly divided into two steps:
- creates a new AssetManager, and calls addAssetPath through reflection to load the complete new resource package;
- finds all the places that were previously referenced to the original AssetManager, and replaces the reference with the new AssetManager through reflection;
- The specific principles here can be found in the chapter to explore the Android open source framework - 10. The resource loading part in the plug-in principle;
- Sophix: Constructs a resource package with package id 0x66 (the original resource package is 0x7f). This package only contains the changed resource items, and then directly in the original AssetManager addAssetPath package is enough. It does not modify the reference of AssetManager, and replace it faster and safer.
5. The so library repairs
- mainly to update so, that is, reload so, mainly using the System load and loadLibrary methods
- System.load(""): Pass the complete path of so on disk, used to load the specified path so
@CallerSensitivepublic static void load(String filename) {Runtime.getRuntime().load0(Reflection.getCallerClass(), filename);}
- System.loadLibrary(""): Pass in the so name, which is used to automatically copy from the apk package after loading the app installation to so
@CallerSensitivepublic static void loadLibrary(String libname) {Runtime.getRuntime().loadLibrary0(Reflection.getCallerClass(), libname);}
- will eventually call LoadNativeLibrary(), which mainly does the following work:
- determines whether the so file has been loaded. If it has been loaded, determine whether it is the same as class_Loader, avoiding so repeated loading;
- If the so file is not loaded, open so and get the so handle. If the so handle is failed to obtain, it returns false. A new SharedLibrary is common. If the library corresponding to the incoming path is a null pointer, the SharedLib will be created. Rary assigns value to library and stores library in libraries_;
- looks for the function pointer of JNI_OnLoad, sets the value of was_successful according to different situations, and finally returns the was_successful;
two solutions:
- inserts the so patch into the front of the NativeLibraryElement array, so that the path of so patch is returned and loaded first;
- calls the System.load method to take over the loading portal of so;