Android optimization (1) Java code optimization

Android optimization (1) Java code optimization

General outline of performance optimization:

It will take about a month to produce 7-8 topics to share the android performance optimization experience accumulated in work and study .

I hope you will continue to pay attention.

Now is the first topic: java code optimization

But this is just to provide you with some ideas and a more comprehensive summary. It is not a big deal . I hope that there are errors or problems in the comments below.

After the final conclusion, the mind map and optimization framework will be sorted out, please look forward to it.

Inscription:

How to ensure high performance of Java applications on Android devices? The first thing to do: know how Android executes the code, and then experience the so-called optimization techniques, as well as some techniques to improve the response speed of the application and use the database efficiently.

However, you should be aware that code optimization is not the primary task of application developers. Provide a good user experience and focus on code maintainability. is our primary task. In fact, code optimization should be done last. If your program feels to an acceptable level, code optimization is not even needed.

1. Let's first take a look at how Android executes code

  • Android Java code Java bytecode Dalvik bytecode Dalvik virtual machine (before 4.4)

  • Android Java code Java bytecode machine code (after 5.0)

i: In Android 4.4, Google provides developers with two compilation modes, one is the default Dalvik mode, and the other is the ART mode. Dalvik is abandoned in 5.0.

ii: The native code is directly executed by the CPU, and does not need to be interpreted and executed by the virtual machine; the native code can be optimized for a specific architecture.

iii: From the user's point of view, if the calculation can be completed in 100ms or less, it is an instantaneous calculation.

So let's take a look at several Android compilation modes JIT and Dalvik ART and AOT

JIT and Dalvik

JIT is the abbreviation of "Just In Time Compiler", which is "Just In Time Compiler Technology", which is related to the Dalvik virtual machine.

How to understand this sentence? This starts with some features of Android.

JIT was proposed in version 2.2, the purpose is to improve the running speed of Android, has survived to version 4.4, because in the ROM after 4.4, there is no Dalvik virtual machine.

We use Java to develop android. When compiling and packaging APK files, we will go through the following process

  • The Java compiler compiles all Java files in the application into class files

  • The dx tool converts the class file output by application compilation into Dalvik bytecode, that is, dex file

  • After signing, alignment and other operations, it becomes an APK file.

The Dalvik virtual machine can be regarded as a Java VM. It is responsible for interpreting the dex file as machine code. If we do not process it, every time we execute the code, Dalvik needs to translate the dex code into microprocessor instructions, and then hand it over to the system for processing. , This is not efficient.

In order to solve this problem, Google added a JIT compiler in version 2.2. When the App is running, whenever a new class is encountered, the JIT compiler will compile this class, and the compiled code will be optimized to be equivalent Simplified native script (ie native code), so that the next time the same logic is executed, the speed will be faster.

Of course, using JIT does not necessarily speed up the execution. If most of the code is executed very few times, then the time spent compiling is not necessarily less than the time to execute dex. Of course Google also knows this, so JIT does not compile all dex codes, but only compiles the dex that has been executed more frequently as local machine code.

One thing to note is that the translation of dex bytecode to local machine code occurs during the running process of the application, and every time the application is rerun, the translation has to be redone, so this work is not Not once and for all, every time you reopen the App, JIT compilation is required.

In addition, the Dalvik virtual machine has survived from the birth of Android to version 4.4, and JIT did not exist when Android was first released, and was added to Dalvik after 2.2.

ART and AOT

AOT is the abbreviation of "Ahead Of Time", which refers to the operation mode of ART (Anroid RunTime).

As mentioned earlier, JIT is run-time compilation, which can compile and optimize the frequently executed dex code and reduce the translation time in future use. Although it can speed up the running speed of Dalvik, there are still drawbacks. That is to translate dex into Local machine code also takes time, so Google launched ART after 4.4 to replace Dalvik.

In version 4.4, the two runtime environments coexist and can be switched with each other, but in 5.0+, the Dalvik virtual machine is completely discarded and all uses ART.

ART's strategy is different from Dalvik. In the ART environment, when the application is first installed, the bytecode will be pre-compiled into machine code, making it a true local application. When the App is opened later, no additional translation work is required, and the local machine code is used to run directly, so the running speed is improved.

Of course, ART still has shortcomings compared with Dalvik.

  • ART needs to convert the program code into machine language when the application is installed, so this will consume more storage space, but the increase in the consumed space usually does not exceed 20% of the application code package size

  • Due to a transcoding process, the application installation time will inevitably be extended

But these are not worth mentioning compared to the smoother Android experience.

Through the introduction of the previous background knowledge, I can finally introduce the relationship between these four nouns more simply:

  • JIT stands for runtime compilation strategy and can also be understood as a runtime compiler. It is a technical solution proposed to speed up the Dalvik virtual machine's interpretation of dex to cache frequently used local machine code
    .

  • Both ART and Dalvik are considered as an Android runtime environment, or virtual machine, used to interpret dex type files. But ART is interpreted during installation, and Dalvik is interpreted during runtime
    .

  • AOT can be understood as a compiling strategy, that is, compiling before running. The main feature of the ART virtual machine is AOT

2. some optimization techniques

Optimization idea one:

Minor optimization: when n is equal to 0 or 1, return n directly, instead of checking whether n is equal to 0 or 1 in another if statement.

public class Fibonacci{
     public static long computeRecursively(int n){
          if(n>1) return computeRecursivelv(n-2) + computeRecursivelv(n-1);
          return n;
     }
} 

Optimization idea 2: Take the optimization of the Fibonacci sequence as an example, and briefly talk about the idea

1. The first optimization is to eliminate a method call

public class Fibonacci{
     public static long computeRecursively(int n){
          if(n>1) {
               long result = 1;
                   do {
                        result += computeRecursivelyWithLoop(n-2);
                        n--;
                       }while (n>1)
                        return result;
                       }
                       return n;
                      }
            }
 

2. The second optimization will be replaced by iterative implementation: especially when there is not much memory, recursive algorithms often consume a lot of stack space, which may cause stack overflow and crash the application.

public class Fibonacci{
     public static long computeRecursively(int n){
          if(n>1) {
               long a= 0,b = 1;
               do {
                long tmp = b;
                b += a;
                a = amp;

               }while (--n>1)
                return b;
           }
   return n;
  }
} 

3. Up to three times with slight modifications, each iteration calculates two items, and the total number of iterations is reduced by half. Since the long type has only 64 bits, there will be an overflow in the 92nd term of the Fibonacci sequence, resulting in an error in the result, and the 93rd term will become negative.

    public class Fibonacci{
         public static long computeRecursively(int n){
          if(n>1) {
               long a= 0,b = 1;
           n--;
           a = n & 1;
           n/= 2;


           while (n-->0){
            a += b;
            b += a;

           }
           return b;
          }
          return n;
} 

4. The fourth time BigInteger is used to ensure that it will not overflow, but the speed drops again: 1. BigInteger is immutable 2. BigInteger uses BigInt and local code to achieve 3. The larger the number, the cost of the addition operation The longer the time

public class Fibonacci{
 public static BigInteger computeIterativelvFasterUsingBigInteger(int n){
  if(n>1) {
   BigInteger a,b = BigInteger.ONE;
   n--;
   a = BigInteger.valueOf(n & 1);
   n/= 2;


   while (n-->0){
    a=a.add(0);
    b=b.add(a);

   }
   return b;
  }
  return n==0?BigInteger.ZERO : BigInteger.ONE;
} 

5. Improve the algorithm for the fifth time to reduce the number of allocations. Based on the Fibonacci Q-matrix, we will have an algorithm formula to speed it up.

6. The sixth use of BigInteger and the fast recursive implementation of the basic type Long

             n>92 BigInteger 20 
 

7. The seventh time to use BigInteger and pre-calculated results to recursively realize quickly...

>  ( ) 

result = cache.get(n);

if(result = null){

    

    result = computeResult(n);

    cache.put(n,result);

}

return result;

 

8. Considering that the calculation cost is too high, it is best to cache the results. The SparseArray class defined by Android is more efficient than HashMap (the difference between Integer and int)

public class Fibonacci{
 public static BigInteger computeRecursivelyWith Cache(int n){
  SparseArray cache = new SparseArray();
  return computeRecursivelyWithCache(n,cache);
    }


    private static BigInteger computeRecursivelyWithCache(int n,SparseArray cache){
     if(n>92) {
   BigInteger fN = cache.get(n);
    if(fN == null){
     int m = (n/2) +(n&1);
     BigIntger fM = computeRecursiveWithCache(m,cache);
     BigIntger fM_1 = computeRecursiveWithCache(m - 1,cache);
     if((n&1)==1){
     fN = fm.pow(2).add(fM_1.pow(2));
     }else{
      fN = fM_1.shiftLeft(1).add(fM).multiply(fM);
     }
     cache.put(n,fN);
    }
    return fN;


   }
   return BigInteger.valueOf(iterativeFaster(n));
  }
  private static long iterativeFaster(int n){
   ...
  }
    } 
} 

Another thing worth mentioning is the LRUCache algorithm, which also corresponds to a MRUCache algorithm

This class was introduced by Android 3.1, and the length of the cache can be customized when it is created. In addition, the method of calculating the size of each cache entry can be changed by overwriting the sizeof() method.

  • LRU (Least Recently Used) cache county discards the least recently used items, but in some sub-scenarios we may also use MRUcache to discard the most recently used items. These two algorithms are not discussed in depth here, and will be discussed in detail when there is a chance to share the data structure in the future.

Finally, we come to a conclusion that we often have many ways to optimize a scene, but a certain realization is generally not the best solution. The best result is to combine a variety of different technologies, rather than relying on them 1. for example, faster implementations can use pre-calculation, caching mechanisms, or even different mathematical formulas.

3. API

Generally, we should use elements in the manifest to formulate the following three important information

  • Minimum API level (mainSdkVersion)

  • Expected API level (targetSdkVersion)

  • The highest API level (maxSdkVersion)

One thing to note: expired ones cannot be used

Also note: you can use the new API to get the best performance, or it can run normally on the old platform

E.g:

android6.0 permission problem

Compatible attribute animations below android3.0, etc.

The use of sparseArray:

if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){

    sparseArray.removeAt(1)

} else {

    int key = sparseArray.keyAt(1)

     sparseArray.remove(key)

}


 

If you don't want to use the above method to detect the version number, you can also use reflection to confirm whether there is a specific method, but there is one thing, where performance is critical, you should try to avoid using reflection . The alternative is to call Class.forName() and Class.getMethod() in the static initialization code block to confirm whether the specified method exists, and only call Method.invoke() in places with high performance requirements.

4. data structure

The realization of the above Fibonacci sequence proves that a good algorithm and data structure is the key to rapid application. The java.util package has defined many tools that we can use at hand, such as various collections. Android also defines some classes to solve performance problems:

  • LruCache

  • SparseArray

  • SparseBooleanArray

  • SparseIntArray

  • Pair

The data structure is still the same as above and will be discussed later when there is a chance.

5. Responsiveness

Let users really feel fast, such as lazy loading technology .

Usually our approach is to perform all initialization in the onCreate() method of the component. Although this is feasible, it means that onCreate() takes a long time to end.

This is particularly important for the ACtivity of the application, onStart() will not be called until after the onCreate() method (similarly, onResume() will only be called after onStart() is completed).

Any delay will cause the application to take a long time to start, and the user may eventually feel unbearable.

  • Let your main thread only do the following things:

Let s briefly talk about the user s feelings: when the user feels that your app is stuck, the favorability will decrease, and when it reaches a certain critical point, goodbye.

So, when do you feel the freeze? Generally, when the image frame rate seen by the human eye is 60fps , it will feel smoother, converted to time is 0.16s/frame , if your application is at a certain point, then 0.16s If the rendering is not completed, it will cause the so-called lag . From the perspective of optimization, in addition to changing the GPU , what we can do is to reduce the nesting of the layout and the ViewStub to postpone the object creation . Of course, you can use the view tree to detect, that is not within the scope of code optimization, so it's good to know.

Android uses android.view.ViewStub to defer initialization, which can expand resources at runtime. When the View-Stub needs to be displayed, it is expanded and replaced by the corresponding resource, and it becomes an object to be garbage collected.

Since memory allocation takes time, it is also a good choice to wait until the object is really needed to allocate it. When an object is not going to be used immediately, postponing the creation of the object has obvious benefits. The following code is an example of exit initialization: In order to avoid always checking whether the object is empty, consider using the factory method pattern.

int n = 100;
if(cache == null){
 
 cache = createCache();
}
BigInteger fN = cache.get(n);
if(fN == null){
 fN = Fibonacci.computeRecurivelyWithCache(n);
 cache.put(n,fN);
} 

6. SQLite:

Most applications will not be heavy users of SQLite, so don't worry too much about the performance when dealing with the database (for extensive use of the database, please refer to my other article "Solving Performance Problems in 3.Aspects" ). but. When optimizing SQLite-related code in an application, you need to understand several concepts:

1 SQLite 

2 

3  
 

Because ordinary SQL statements are simple strings, they need to be interpreted or compiled before they can be executed. When you execute SQL statements, for example:

SQLiteDatabase db = SQLiteDatabase.create(null);//
db.execSQL("CREATE TABLE cheese(name TEXT,origin TEXT)");
db.execSQL("INSERT INTO cheeese VALUES ('Roquefort','Roquefort-sur-Solulzon')");
db.close();// 

It turns out that the execution of SQLite statements may take a long time. In addition to compiling, the statement itself needs to be created. Now we only care about the performance of INSERT. After all, the table will only be created once, but it will be added, modified, and deleted many times.

 String sql = "INSERT INTO cheese VALUES(\"" +name +"\",\"" + origin +"\")" 
  • It took 393ms for such 650 pieces of data to be stored in the memory database, with an average of 0.6ms.

Then the first optimization solution is to replace "+" with StringBuilder or String.format()

builder.appended(name).append("\",\"").addped(origin).append("\")");




String sql = String.format("INSERT INTO cheese VALUES(\"%s\",\"%s\")")",name .origin);



 

The second solution: We found that all the statements are very similar, so we can use a statement to make a part of it compiled only once outside the loop:

public void populateWithCompileStatement(){
 SQLiteStatement stmt = db.compileStatement("INSERT INTO cheese VALUES(?,?)");
 int i = 0;
     for(String name : sCheeseNames){
          String origin = sCheeseOrigins[i++];
          stmt.clearBingings();
          stmt.bindString(1,name);//name
          stmt.bingString(2,origin);//origin
          stmt.executeInsert();
     }
}
 
  • Because the statement is compiled only once instead of 650 times, and the binding value is a lighter operation than the compilation, this method is significantly faster and takes 269ms in total.

Transaction: The above example does not show the creation of any transaction, but it will automatically create a transaction for each insert operation and commit it immediately after each insert. Show that the creation of a transaction has the following two basic characteristics:

 

 
 

Putting aside the pursuit of performance, the first feature is very important. Atomic commit means that all changes to the database are completed or not done. The transaction will not only commit partial changes. Like the above code, after adding the transaction

try{

db.beginTransaction();

SQLiteStatement stmt = db.compileStatement("INSERT INTO cheese VALUES(?,?)");
 int i = 0;
     for(String name : sCheeseNames){
          String origin = sCheeseOrigins[i++];
          stmt.clearBingings();
          stmt.bindString(1,name);//name
          stmt.bingString(2,origin);//origin
          stmt.executeInsert();
     }

db.setTransactionSuccessful();//

} catch(e..){

//

}finally{

db.endTransaction();//finally 

}

 

Query: We can speed up the query speed by restricting the access to the database, especially for the database in storage. The database query will only return a cursor (cursor) object, and then use it to traverse the results.

When querying, try to read only the data you need. For example, suppose our table has two columns, name and origin:

db.query("cheese,null,null,null,null,null,null");(1)

db.query("cheese",new String[]{"name"},null,null,null,null,null);(2) 

The two methods of querying a certain amount of data take 61ms and 23ms respectively

Therefore, it is certain that only reading the required data is the best choice.

7. summary:

A few years ago, Java was widely criticized for performance issues, but now the situation has changed drastically.

Every time a new version of Android is released, the performance of the Dalvik virtual machine (including its JIT compiler) will improve.

The code can be compiled into native code to take advantage of the latest CPU architecture without having to recompile.

Although implementation is very important, the most important thing is to choose data structures and algorithms carefully.

Good algorithms can make up for poor implementations, and even make applications run smoothly without optimization; and bad algorithms, no matter how much effort you spend on implementation, the result will still be terrible.

Finally, smooth response is the key to success.