preface

  • inAndroidMemory leaks are common in development
  1. Definition of a memory leak: an object that should have been collected cannot be collected and remains in heap memory
  2. Memory leaks occur when an object is no longer in use and should be recycled but cannot be recycled because another object in use holds a reference to it. This leads to a memory leak.
  • This article will detail one of the memory leak scenarios: inHandlerA memory leak occurred in

Android Development: The Handler asynchronous communication mechanism (including Looper and Message Queue)


directory


1. Problem description

  • HandlerCommon usage = newHandlerSubclass (inner class), anonymousHandlerThe inner class
Public class MainActivity extends appActivity {public static final String TAG ="carson:"; private Handler showhandler; @override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); Handler(); MessageQueue showhandler = new FHandler(); // 2. Start child thread 1 newThread() {
                    @Override
                    public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 1; // Message identifier msg.obj ="AA"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); // 3. Start child thread 2 newThread() {
                    @Override
                    public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 2; // Message identifier msg.obj ="BB"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); } // Analysis 1: Class FHandler extends Handler {// To Override handlerMessage() to determine the operation that updates the UI handleMessage(Message msg) { switch (msg.what) {case 1:
                            Log.d(TAG, "Message received from thread 1");
                            break;
                        case 2:
                            Log.d(TAG, "Message received from thread 2");
                            break; }}}} /** * Method 2: Anonymous Handler internal class */ public class MainActivity extends AppCompatActivity {public static final String TAG ="carson:"; private Handler showhandler; @override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); Handler class object instantiated by anonymous inner class // Note: Looper is not specified here, so automatically bind the current thread (main thread) Looper, MessageQueue showhandler = newHandler@override public void handleMessage(Message MSG){switch (MSG. What){case 1:
                                Log.d(TAG, "Message received from thread 1");
                                break;
                            case 2:
                                Log.d(TAG, "Message received from thread 2");
                                break; }}}; // 2. Start child thread 1 newThread() {
                @Override
                public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 1; // Message identifier msg.obj ="AA"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); // 3. Start child thread 2 newThread() {
                @Override
                public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 2; // Message identifier msg.obj ="BB"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); }}Copy the code
  • The test results

  • The above example will run successfully, but the code will have a serious warning:

  1. Reason for warning = thisHandlerClass because it is not set to static, soThis caused a memory leak
  2. The final memory leak occurs whenHandlerExternal class of class:MainActivityclass

So why does this Handler leak memory when it is not set to static?


2. Explain the reasons

2.1 Reserve knowledge

  • The main thread of theLooperObject life cycle = life cycle of the application
  • inJava,Non-static inner class & Anonymous inner classAll hold references to external classes by default

2.2 Description of leakage causes

You can see from the sample code above:

  • The aboveHandlerThe message queue of the instance has two messages (delay, respectively) from thread 1 and thread 21s,6s)
  • inHandlerMessage queue There are unprocessed messages/messages in the message queue while being processedMessageholdHandlerInstance reference
  • Due to theHandler= non-static inner class/anonymous inner class (2 ways to use), so it also holds references to external classes by default (i.eMainActivityExample), the reference relationship is shown in the following figure

The above reference relationship remains until all messages in the Handler message queue have been processed

  • inHandlerThe external class should be destroyed if there are unprocessed messages in the message queue/while messages are being processedMainActivity, but because of the above reference, the garbage collector(GC)Unable to recycleMainActivity, causing a memory leak. The diagram below:

2.3 summarize

  • whenHandlerThere are unprocessed messages in the message queue/there are references to messages being processed:“Unprocessed/processed message ->HandlerInstance -> External Class
  • If appearHandler> the life cycle of the external classnamelyHandlerThe message queue has unprocessed messages/processing messages and the external class needs to be destroyed) will make external classes unavailable to the garbage collector(GC)Reclaim, thus causing a memory leak

3. Solutions

As can be seen from the above, there are two key conditions that cause memory leaks:

  1. Unprocessed/Processed message -> existsHandlerInstance -> External class reference relationship
  2. Handler> life cycle of the external class

That is, the Handler message queue has unprocessed messages/processing messages and the external class needs to be destroyed

Solution idea = make any condition 1 above not true.

Solution 1: Static inner class + weak reference

  • Principle Static inner classes do not hold references to external classes by default, so that the “unprocessed/processed message -> Handler instance -> external class” reference relationship does not exist.

  • The concrete scenario sets the subclasses of Handler to static inner classes

  • At the same time, you can also use WeakReference to hold an Activity instance
  • Reason: Weakly referenced objects have a short lifetime. When the garbage collector thread scans, once it finds an object with only weak references, it reclaims its memory regardless of whether the current memory space is sufficient or not
  • To solve the code
public class MainActivity extends AppCompatActivity {

    public static final String TAG = "carson:"; private Handler showhandler; @override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);setContentView(R.layout.activity_main); //1. Instantiate the custom Handler class object ->> Analyze 1. Looper is not specified here, so automatically bind Looper and MessageQueue of the current thread (main thread). Showhandler = new FHandler(this); showhandler = new FHandler(this); // 2. Start child thread 1 newThread() {
            @Override
            public void run() { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 1; // Message identifier msg.obj ="AA"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); // 3. Start child thread 2 newThread() {
            @Override
            public void run() { try { Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } // a. Define the Message to be sent Message MSG = message.obtain (); msg.what = 2; // Message identifier msg.obj ="BB"; Send showhandler.sendMessage(MSG) to MessageQueue; } }.start(); } // Analysis 1: custom Handler subclass // set to: Private static class FHandler extends Handler{// Define WeakReference<Activity> reference; Public FHandler(Activity Activity) {// use WeakReference WeakReference to hold Activity instance reference = new WeakReference<Activity>(activity); } @override public void handleMessage(Message MSG) {switch (MSG. What) {case 1:
                    Log.d(TAG, "Message received from thread 1");
                    break;
                case 2:
                    Log.d(TAG, "Message received from thread 2");
                    break; }}}}Copy the code

Solution 2: Empty the message queue inside the Handler when the external class ends its life cycle

  • The principle not only eliminates the “unprocessed/processed message -> Handler instance -> external class” reference relationship, but also synchronizes the Handler lifecycle (i.e. the period in which the message exists) with the lifecycle of the external class

  • The specific plan When an external class (here in the Activity, for example) the end of the life cycle (the system will call onDestroy ()), to remove all the messages from the message queue Handler (call removeCallbacksAndMessages (null))

  • Specific code

@Override
    protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); // Emptying the message queue when the external class Activity life cycle ends & ending the Handler life cycle}Copy the code

Use advice

To ensure that all messages in the message queue in Handler can be executed, solution 1 is recommended to solve the memory leak problem, which is static inner class + weak reference


4. To summarize

  • This article mainly explainsHandlerKnowledge of memory leaks: Principles & Solutions
  • And I’m going to continue with thatAndroidDevelopment of knowledge about memory leaks, interested can continue to pay attention toCarson_Ho android Development Notes

Thumb up, please! Because your encouragement is the biggest power that I write!


Welcome to follow Carson_ho on wechat