Android performance optimization: How to avoid creating unnecessary objects in Android

Android performance optimization: How to avoid creating unnecessary objects in Android

[[169562]]

In programming development, memory usage is a reality we often have to face. The usual direction of memory tuning is to minimize memory usage. Avoiding the creation of unnecessary objects is an important aspect of this.

Android devices do not have as much memory as PCs, and the memory occupied by a single App is actually relatively small. Therefore, it is particularly important to avoid creating unnecessary objects for Android development.

This article will introduce some common scenarios and methods for avoiding object creation. Some of them are micro-optimizations, some are coding techniques, and of course there are methods that can really have a significant effect.

Using Singleton

Singleton is a common design pattern. Using this pattern, we can only provide one object for global call. Therefore, singleton is a way to avoid creating unnecessary objects.

The singleton pattern is easy to use, but you need to pay attention to many issues. The most important thing is to ensure the uniqueness of the singleton in the case of multi-threaded concurrency. Of course, there are many ways, such as the hungry man style and lazy man style double-check. Here is a very geeky way to write a singleton.

  1. public   static class SingleInstance {
  2. private SingleInstance() {
  3. }
  4.  
  5. public   static SingleInstance getInstance() {
  6. return SingleInstanceHolder.sInstance;
  7. }
  8.  
  9. private static class SingleInstanceHolder {
  10. private static SingleInstance sInstance = new SingleInstance();
  11. }
  12. }

In Java, static initialization of a class is triggered when the class is loaded. By using this principle, we can take advantage of this feature and combine it with inner classes to implement the above code and create instances in a lazy way.

Avoid implicit boxing

Automatic boxing is a feature introduced in Java 5, which automatically converts primitive data into corresponding reference types, such as converting int to Integer.

This feature greatly reduces the tedious work of coding, but if you are not careful, you may create unnecessary objects. For example, the following code

  1. Integer   sum = 0;
  2. for ( int i = 1000; i < 5000; i++) {
  3. sum += i;
  4. }

The code sum+=i above can be seen as sum = sum + i, but the + operator is not applicable to Integer objects. First, sum performs an automatic unboxing operation to add the values, and finally an automatic boxing operation occurs to convert it into an Integer object. The internal changes are as follows

  1. int result = sum .intValue() + i;
  2. Integer   sum = new Integer (result);

Since the sum we declare here is of Integer type, nearly 4,000 useless Integer objects will be created in the above loop. In such a large loop, the performance of the program will be reduced and the workload of garbage collection will be increased. Therefore, when we program, we need to pay attention to this point and declare the variable type correctly to avoid performance problems caused by automatic boxing.

In addition, when adding primitive data types to a collection, automatic boxing will also occur, so objects are created in this process. If you need to avoid this, you can choose containers such as SparseArray, SparseBooleanArray, SparseLongArray, etc.

Choose your container carefully

Java and Android provide many editable container collections to organize objects, such as ArrayList, ContentValues, HashMap, etc.

However, although such containers are convenient to use, they also have some problems, that is, they will automatically expand, which does not create a new object, but creates a larger container object. This means that this will take up more memory space.

Taking HashMap as an example, when we put key and value, it will detect whether it needs to be expanded. If necessary, it will double the capacity.

  1. @Override
  2. public V put(K key , V value) {
  3. if ( key == null ) {
  4. return putValueForNullKey(value);
  5. }
  6. // some code here
  7.  
  8. // No entry for (non- null ) key   is present; create one
  9. modCount++;
  10. if ( size ++ > threshold) {
  11. tab = doubleCapacity();
  12. index = hash & (tab.length - 1);
  13. }
  14. addNewEntry( key , value, hash, index );
  15. return   null ;
  16. }

Regarding the issue of capacity expansion, there are usually the following methods

  • Estimate a larger capacity value to avoid multiple expansions
  • Find alternative data structures to ensure a balance between time and space

Make good use of LaunchMode

The LaunchMode is definitely related to the Activity. Normally, we declare the Activity in the manifest, and if we don't set the LaunchMode, the default standard mode is used.

Once set to standard, a new instance of Activity will be created every time an Intent is requested. For example, if there are 10 intents to compose emails, 10 instances of ComposeMailActivity will be created to handle these intents. As a result, it is obvious that this mode will create multiple instances of an Activity.

If you are looking for an Activity with a search function, you only need to keep one Activity instance. Using the standard mode will cause too many Activity instances to be created, which is not a good idea.

Ensure that LaunchMode is used reasonably and in accordance with common sense to reduce the creation of Activities.

Activity handles onConfigurationChanged

This is another one about creating Activity objects, because the cost of creating Activity is much higher than other objects.

By default, when we rotate the screen, the original Activity will be destroyed and a new Activity will be created. This is done to handle layout adaptation. Of course, this is the system's default approach. In the case of controllable development, we can avoid recreating the Activity.

Taking screen switching as an example, when declaring Activity, add

  1. <activity
  2. android: name = ".MainActivity"  
  3. android:configChanges= "orientation"  
  4. android:label= "@string/app_name"  
  5. android:theme= "@style/AppTheme.NoActionBar" />

Then override the onConfigurationChanged method of Activity

  1. @Override
  2. public void onConfigurationChanged(Configuration newConfig) {
  3. super.onConfigurationChanged(newConfig);
  4. if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT) {
  5. setContentView(R.layout.portrait_layout);
  6. } else if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) {
  7. setContentView(R.layout.landscape_layout);
  8. }
  9. }

Note the string concatenation

Strings are perhaps the least noticeable item. Here we mainly talk about string concatenation

  1. Log.i(LOGTAG, "onCreate bundle=" + savedInstanceState);

This should be our most common way of logging. However, string concatenation actually generates a StringBuilder object, and then appends them one by one until the toString method is finally called.

The following is a code loop, which is obviously very bad because it creates many StringBuilder objects.

  1. public void implicitUseStringBuilder(String[] values ) {
  2. String result = "" ;
  3. for ( int i = 0; i < values ​​.length; i++) {
  4. result += values ​​[i];
  5. }
  6. System. out .println(result);
  7. }

Ways to reduce string concatenation include

  • Replace using String.format
  • If it is a loop concatenation, it is recommended to explicitly create a StringBuilder outside the loop.

Reduce layout levels

Too many layout levels not only make the inflation process time-consuming, but also create redundant auxiliary layouts. Therefore, it is necessary to reduce auxiliary layouts. You can try other layout methods or customize views to solve such problems.

Check in advance to reduce unnecessary exceptions

Exceptions are common in programs, and the code for exceptions is actually very high because it needs to collect the on-site data stacktrace. However, there are still some measures to avoid exceptions, that is, to do some advance checks.

For example, if we want to print each line of a string in a file, the unchecked code is as follows, and there is a possibility that FileNotFoundException will be thrown.

  1. private void printFileByLine(String filePath) {
  2. try {
  3. FileInputStream inputStream = new FileInputStream( "textfile.txt" );
  4. BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
  5. String strLine;
  6. // Read File Line By Line
  7. while ((strLine = br.readLine()) != null ) {
  8. //Print the content on the console
  9. System. out .println(strLine);
  10. }
  11. br.close ();
  12. } catch (FileNotFoundException e) {
  13. e.printStackTrace();
  14. } catch (IOException e) {
  15. e.printStackTrace();
  16. }
  17. }

If we check whether the file exists, the probability of throwing FileNotFoundException will be much reduced.

  1. private void printFileByLine(String filePath) {
  2. if (!new File(filePath).exists()) {
  3. return ;
  4. }
  5. try {
  6. FileInputStream inputStream = new FileInputStream( "anonymous.txt" );
  7. BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
  8. String strLine;
  9. // Read File Line By Line
  10. while ((strLine = br.readLine()) != null ) {
  11. //Print the content on the console
  12. System. out .println(strLine);
  13. }
  14. br.close ();
  15. } catch (FileNotFoundException e) {
  16. e.printStackTrace();
  17. } catch (IOException e) {
  18. e.printStackTrace();
  19. }
  20. }

The above check is a good coding technique and is recommended to be adopted.

Don't create too many threads

In Android, we should try to avoid performing time-consuming operations in the main thread, so we need to use other threads.

  1. private void testThread() {
  2. new Thread() {
  3. @Override
  4. public void run() {
  5. super.run();
  6. //do some io work  
  7. }
  8. }.start();
  9. }

Although these work, the cost of creating a thread is much higher than that of a normal object. It is recommended to use HandlerThread or ThreadPool as a replacement.

Use annotations instead of enumerations

Enumeration is a method we often use to limit values. It is more reliable to use enumeration than simple constant convention. However, the essence of enumeration is to create objects. Fortunately, Android provides relevant annotations, which allows value limitation to be performed at compile time, thereby reducing the pressure at runtime. The relevant annotations are IntDef and StringDef.

The following uses IntDef as an example to introduce how to use

In a file declare

  1. public class AppConstants {
  2. public   static final int STATE_OPEN = 0;
  3. public   static final int STATE_CLOSE = 1;
  4. public   static final int STATE_BROKEN = 2;
  5.  
  6. @IntDef({STATE_OPEN, STATE_CLOSE, STATE_BROKEN})
  7. public @interface DoorState {
  8. }
  9. }

Then set up a method that writes something like this

  1. private void setDoorState(@AppConstants.DoorState int state) {
  2. // some code
  3. }

Only STATE_OPEN, STATE_CLOSE, and STATE_BROKEN can be used when calling methods. Using other values ​​will result in compiler reminders and warnings.

Select object pool

There are many pool concepts in Android, such as thread pool, connection pool, including Handler.Message, which we have been using for a long time, which uses the pool technology.

For example, if we want to use Handler to send a message, we can use Message msg = new Message() or Message msg = handler.obtainMessage(). Using a pool does not create a new object every time, but rather takes objects from the pool first.

There are a few things to note when using object pools

  • Put the object back into the pool, and be sure to initialize the object's data to prevent dirty data.
  • Reasonably control the growth of the pool to avoid it being too large, which causes many objects to be idle

Initialize Application Carefully

Android applications can support opening multiple processes. The usual practice is as follows

  1. <service
  2. android: name = ".NetworkService"  
  3. android:process= ":network" />

Usually we will do a lot of initialization operations in the onCreate method of Application, but each process startup needs to execute this onCreate method. In order to avoid unnecessary initialization, it is recommended to initialize according to the process (by judging the current process name).

  1. public class MyApplication extends Application {
  2. private static final String LOGTAG = "MyApplication" ;
  3.  
  4. @Override
  5. public void onCreate() {
  6. String currentProcessName = getCurrentProcessName();
  7. Log.i(LOGTAG, "onCreate currentProcessName=" + currentProcessName);
  8. super.onCreate();
  9. if (getPackageName().equals(currentProcessName)) {
  10. //init for   default process
  11. } else if (currentProcessName.endsWith( ":network" )) {
  12. //init for netowrk process
  13. }
  14. }
  15.  
  16. private String getCurrentProcessName() {
  17. String currentProcessName = "" ;
  18. int pid = android.os.Process.myPid();
  19. ActivityManager manager = (ActivityManager) this.getSystemService(Context.ACTIVITY_SERVICE);
  20. for (ActivityManager.RunningAppProcessInfo processInfo : manager.getRunningAppProcesses()) {
  21. if (processInfo.pid == pid) {
  22. currentProcessName = processInfo.processName;
  23. break;
  24. }
  25. }
  26. return currentProcessName;
  27. }
  28. }

The above knowledge is a summary of how to avoid creating redundant objects in Android. You are welcome to put forward your opinions and views and make progress together.

<<:  Losing both hardware and software: Why is Apple's licensing of operating systems a "dirty move"?

>>:  What are the shortcomings of Android compared to iOS?

Recommend

Stop worrying about Google Glass

In recent days, the Google Glass incident has att...

This formula teaches you how to create popular screen-sweeping cases!

On the Internet, we should regard advertising con...

Use 3 tricks to easily promote your product

For a large, mature company, promoting a new prod...

Good copywriting comes from user research!

I often hear that there are four stages of learni...

Hello everyone! I am the "King of Snow Mountain"

recent A six-month-old snow leopard refused to le...

Why does my voice sound so bad in the voice chat? Here’s the reason!

After sending a voice message on WeChat, will you...

Who stole your users?

When we are doing product or brand marketing , it...

Procreate Drawing Lesson

Procreate painting course resources introduction:...

Solid info! 5 major customer acquisition models for APP promotion!

I used to think about this question often when I ...