Detailed explanation of Android memory leakage and optimization scheme

  • 2021-12-19 06:43:45
  • OfStack

Directory 1. Common Memory Leak Application Scenarios? 1. Improper use of singletons
2. Static variables cause memory leakage
3. Non-static inner classes cause memory leakage
4. Memory leak caused by failure to cancel registration or callback
5. Timers Timer and TimerTask cause memory leakage
6. The objects in the collection are not cleaned, resulting in memory leakage
7. The resource is not closed or released, resulting in memory leakage
8. Memory leak caused by animation
9. Memory leak caused by WebView
Summarize

1. Common memory leak application scenarios?

1. Improper use of singletons

Singletons are one of the most common and frequently used design patterns in our development, so if used improperly they can lead to memory leaks. Because of the singleton's static nature, its life cycle is as long as the application's life cycle 1. If an object is useless, but the singleton still holds its reference, the object cannot be recycled normally throughout the application's life cycle, resulting in memory leakage.

Such as:


public class App {
    private static App sInstance;

    private Context mContext;

    private App(Context context) {
        this.mContext = context;
    }

    public static App getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new App(context);
        }
        return sInstance;
    }
}

If the context passed in when calling getInstance (Context context) method is the context of the current active Activity or the current service Service and the current fragment, when they are destroyed, this static singleton sIntance will still hold their references, which leads to the current activity, service, fragment and other objects not being recycled and released, resulting in memory leakage. The use of this context often leads to memory leaks if it is not handled properly, which requires us to pay more attention to coding specifications.

2. Static variables cause memory leakage

Static variables are stored in the method area, and their life cycle begins with class loading and ends with the whole process. 1 Once a static variable is initialized, the references it holds will not be released until the end of the process.

The following code:


public class MainActivity extends AppCompatActivity {
    private static Info sInfo;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (sInfo != null) {
            sInfo = new Info(this);
        }
    }
}

class Info {
    public Info(Activity activity) {
    }
}

Info is a static member of Activity and holds a reference to Activity, but sInfo, as a static variable, definitely has a longer life cycle than Activity. So when Activity exits, sInfo still references Activity, and Activity cannot be recycled, which leads to memory leakage. In the development of Android, static holding may lead to memory leakage because its life cycle is not 1. Therefore, when creating statically held variables, we need to consider the reference relationship between each member under 1, and use statically held variables as little as possible to avoid memory leakage. Of course, we can also reset the static quantity to null at an appropriate time, so that it no longer holds references, which can also avoid memory leakage.

3. Non-static inner classes cause memory leakage

Non-static inner classes (including anonymous inner classes) hold references to external classes by default, resulting in memory leaks when the life cycle of non-static inner class objects is longer than that of external class objects. This kind of memory leak is typical of the use of Handler, so 1 said we should be very familiar with it, we all know how to deal with this kind of memory leak.

Examples of using Handler:


    private void start() {
        Message msg = Message.obtain();
        msg.what = 1;
        mHandler.sendMessage(msg);
    }

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            if (msg.what == 1) {
                // ui Update 
            }
        }
    };

Handler message mechanism, mHandler will be stored as a member variable in the sent message msg, that is, msg holds the reference of mHandler, while mHandler is a non-static internal class instance of Activity, that is, mHandler holds the reference of Activity, so we can understand that msg indirectly holds the reference of Activity. After msg is sent, it is placed in message queue MessageQueue, and then waits for polling processing of Looper (MessageQueue and Looper are both associated with threads, MessageQueue is a member variable referenced by Looper, and Looper is stored in ThreadLocal). Then when Activity exits, msg may still exist in the message pair column MessageQueue unprocessed or being processed, which will cause Activity to be unable to be recycled, resulting in a memory leak of Activity.

How to avoid:
1. Adopt static inner class + weak reference


 private static class MyHandler extends Handler {
        private WeakReference<MainActivity> activityWeakReference;

        public MyHandler(MainActivity activity) {
            activityWeakReference = new WeakReference<>(activity);
        }

        @Override
        public void handleMessage(Message msg) {
            MainActivity activity = activityWeakReference.get();
            if (activity != null) {
                if (msg.what == 1) {
                    //  Make corresponding logic  
                }
            }
        }
    }

mHandler holds Activity by weak reference. When GC performs garbage collection, Activity will reclaim and release the occupied memory units. In this way, memory leaks will not occur. However, it is still possible for msg to exist in message queue MessageQueue.

2. When Activity is destroyed, the callback and sent message of mHandler are removed.


  @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandler.removeCallbacksAndMessages(null);
    }

Memory leaks caused by non-static inner classes are also caused by asynchronous calls using Thread or AsyncTask:
For example:
Thread:


public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }

AsyncTask:


public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new AsyncTask<Void, Void, Void>() {
            @Override
            protected Void doInBackground(Void... params) { 
                // UI Thread processing 
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return null;
            }
        }.execute();
    }
}

The newly created sub-threads Thread and AsyncTask are anonymous internal class objects, which implicitly hold references to external Activity by default, resulting in memory leakage of Activity. To avoid memory leaks, you still need to use static inner class + weak reference as above Handler 1 (as above Hanlder uses static inner class + weak reference).

4. Memory leak caused by failure to cancel registration or callback

For example, if we register the broadcast in Activity, if we don't cancel the registration after Activity is destroyed, then this broadcast will exist directly in the system, and hold the Activity reference like the non-static internal class 1 mentioned above, resulting in memory leakage. Therefore, after registering the broadcast, the registration must be cancelled after Activity is destroyed.


this.unregisterReceiver(mReceiver);

When registering the observation mode, if it is not cancelled in time, it will also cause memory leakage. For example, observer callbacks that use Retrofit+RxJava to register network requests also hold external references as anonymous inner classes, so remember to unregister when you don't use them or destroy them.

5. Timers Timer and TimerTask cause memory leak

When we destroy Activity, it is possible that Timer is still waiting to execute TimerTask, and its reference to Activity cannot be recycled. Therefore, when we destroy Activity, we should immediately destroy Timer and TimerTask by cancel to avoid memory leakage.

6. The objects in the collection are not cleaned, resulting in memory leakage

This is easy to understand. If an object is put into a collection of ArrayList, HashMap, etc., the collection will hold a reference to the object. When we no longer need this object, we don't remove it from the collection, so as long as the collection is still in use (and this object is no longer useful), this object causes a memory leak. And if the collection is statically referenced, those useless objects in the collection will cause memory leakage. Therefore, when using the collection, the unused objects should be removed from the collection remove or clear in time to avoid memory leakage.

7. The resource is not closed or released, resulting in memory leakage

Shut down when using IO, File stream or Sqlite, Cursor and other resources. These resources are usually buffered when reading and writing operations. If they are not closed in time, these buffered objects will be occupied and not released, resulting in memory leakage. So we shut them down when we don't need to use them, so that the buffers can be released in time to avoid memory leaks.

8. Memory leak caused by animation

Animation is also a time-consuming task. For example, the attribute animation (ObjectAnimator) was started in Activity, but the cancle method was not called when it was destroyed. Although we can't see the animation, the animation will continue to play. The animation refers to the control where it is located, and the control where it is located refers to Activity, which causes Activity to be unable to be released normally. Therefore, cancel should also drop the attribute animation when Activity is destroyed to avoid memory leakage.


  @Override
    protected void onDestroy() {
        super.onDestroy();
        mAnimator.cancel();
    }

9. Memory leak caused by WebView

As for the memory leak of WebView, because WebView will occupy memory for a long time after loading web pages and cannot be released, we will call its destory () method to destroy it after Activity is destroyed to release memory.
In addition, when looking up the information about WebView memory leakage, I saw this situation: Callback under Webview holds Activity reference, which makes Webview memory unable to be released, even if methods such as Webview. destory () are called, the problem cannot be solved (after Android 5.1)

The final solution is to remove WebView from the parent container before destroying WebView, and then destroy WebView.


   @Override
    protected void onDestroy() {
        super.onDestroy();
        //  Remove from the parent control first 
        WebView mWebViewContainer.removeView(mWebView);
        mWebView.stopLoading();
        mWebView.getSettings().setJavaScriptEnabled(false);
        mWebView.clearHistory();
        mWebView.removeAllViews();
        mWebView.destroy();
    }

Summarize

Try not to use the reference of Activity when constructing singletons;
Pay attention to the blank application object or use less static reference when using static reference;
Use static inner class + soft reference instead of non-static inner class;
Cancel the registration of broadcasters or observers in time;
Time-consuming task and attribute animation remember cancel when Activity is destroyed;
File flow, Cursor and other resources are closed in time; Removal and destruction of WebView when Activity is destroyed.

The next article continues: Explain the memory optimization strategy of Android in detail

ps: Memory leak is a pain point in development, which requires us to have good coding habits. Ori, here! ! ! ! ! ! ! ! !


Related articles: