English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Android Message Mechanism and Memory Leak of Handler

Handler

Every beginner in Android development cannot avoid the 'bump' of Handler. Why do we say it's a bump? Firstly, it is one of the essences of Android architecture, and secondly, most people know what it is but not why it is. After seeing the Handler.post method, I decided to go back and look at the source code to sort out the implementation mechanism of Handler.

Asynchronous UI update

Let's start with a must-remember mantra: 'Do not perform time-consuming operations on the main thread, and do not update the UI on a child thread'. This rule should be known to beginners. So how do we solve the problem in the mantra? That's when Handler comes into play (AsyncTask can also be used, but essentially it is a wrapper for Handler), here is a classic and commonly used code snippet (we will ignore memory leak issues and discuss them later):

Firstly, create a new handler in the Activity:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      super.handleMessage(msg);
      switch (msg.what) {
        case 0:
          mTestTV.setText("This is handleMessage");//Update UI
          break;
      }
    }
  ;

Then send a message in the child thread:

new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(1000);//There is a time-consuming operation in the child thread, such as network request
          mHandler.sendEmptyMessage(0);
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();

So far, we have completed the time-consuming operation in the child thread and asynchronously updated the UI in the main thread, but we didn't use the post in the title, let's take a look at the version of post again:

new Thread(new Runnable() {
      @Override
      public void run() {
        try {
          Thread.sleep(1000);//There is a time-consuming operation in the child thread, such as network request
          Handler handler = new Handler();
          handler.post(new Runnable() {
            @Override
            public void run() {
              mTestTV.setText("This is post");//Update UI
            }
          });
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
      }
    }).start();

On the surface, a Runnable is passed to the post method, which seems to have started a child thread, but you can't update the UI in the child thread, so what's the matter? With this doubt, let's take a look at the source code of Handler:

Let's take a look at what the ordinary sendEmptyMessage looks like:

public final boolean sendEmptyMessage(int what)
  {
    return sendEmptyMessageDelayed(what, 0);
  }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
  }

Encapsulated the parameters we passed into a message and then called sendMessageDelayed:

public final boolean sendMessageDelayed(Message msg, long delayMillis)
  {
    if (delayMillis < 0) {}}
      delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis()) + delayMillis);
  }

Then call sendMessageAtTime again:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException;
          this + "sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

Well, let's take a look at post():

public final boolean post(Runnable r)
  {
    return sendMessageDelayed(getPostMessage(r), 0);//The difference between getPostMessage and the other message sending methods
  }

The method only has one line, the internal implementation is the same as the ordinary sendMessage, but there is only one difference, that is, the getPostMessage(r) method:

private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();
    m.callback = r;
    return m;
  }

This method also encapsulates the parameters we pass into a message, but this time it is m.callback = r, and just now it was msg.what=what, and we don't look at these properties of Message

Android message mechanism

Here, we only know that the principles of post and sendMessage are both encapsulated into Message, but we still don't know what the whole mechanism of Handler is like, let's continue to explore.

Just now, I saw that both of these methods ultimately called sendMessageAtTime

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
      RuntimeException e = new RuntimeException;
          this + "sendMessageAtTime() called with no mQueue");
      Log.w("Looper", e.getMessage(), e);
      return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
  }

This method also calls enqueueMessage, which should be the meaning of adding messages to the queue, let's take a look inside:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
      msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
  }

We won't worry about mAsynchronous, which is related to asynchronous, and continue to pass the parameters to the enqueueMessage method of queue. As for the assignment of msg.target, we will see it later. Now let's continue into the enqueueMessage method of the MessageQueue class, the method is quite long, let's look at the key lines:

Message prev;
for (;;) {
  prev = p;
  p = p.next;
  if (p == null || when < p.when) {
    break;
  }
  if (needWake && p.isAsynchronous()) {
    needWake = false;
  }
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;

As the method name suggests, an infinite loop adds messages to the message queue (in the form of a linked list), but there is a give and take, so how do we take this message out?

Looking at the MessageQueue method, we found next(), the code is too long to go into detail, but we know it is used to extract messages. However, where is this method called? It's not in the Handler, we found the key figure Looper, I call him the circular messenger, who is responsible for taking messages out of the message queue, the key code is as follows:

for (;;) {
   Message msg = queue.next(); // might block
   ...
   msg.target.dispatchMessage(msg);
   ...
   msg.recycleUnchecked();
}

Simple and clear, we see the msg.target we mentioned just now, just now in the Handler we assigned msg.target = this, so let's look at the dispatchMessage in the Handler:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
      handleCallback(msg);
    }
      if (mCallback != null) {
        if (mCallback.handleMessage(msg)) {
          return;
        }
      }
      handleMessage(msg);
    }
  }

1.If msg's callback is not empty, call the handleCallback method (message.callback.run())
2.If mCallback is not empty, call mCallback.handleMessage(msg)
3.If all others are empty, execute the Handler's own handleMessage(msg) method
The callback of msg should have thought of what it is, that is, the run method of the Runnable passed through Handler.post(Runnable r), here we need to talk about java basics, calling the run method of the thread directly is equivalent to calling a method in a normal class, or executing in the current thread, and will not open a new thread.

So here, we have resolved the initial confusion, why a Runnable was passed in the post, and it can still update the UI on the main thread.

Continue to see if msg.callback is empty, what is mCallback, let's take a look at the constructor:

1.
public Handler() {
    this(null, false);
  }
2.  
public Handler(Callback callback) {
    this(callback, false);
  }
3.
public Handler(Looper looper) {
    this(looper, null, false);
  }
4.
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
  }
5.
public Handler(boolean async) {
    this(null, async);
  }
6.
public Handler(Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {}}
      final Class<? extends Handler> klass = getClass();
      if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
          (klass.getModifiers() & Modifier.STATIC) == 0) {
        Log.w(TAG, "The following Handler class should be static or leaks might occur: ") +
          klass.getCanonicalName());
      }
    }
    mLooper = Looper.myLooper();
    if (mLooper == null) {
      throw new RuntimeException(
        "Can't create handler inside thread that has not called Looper.prepare()");
    }
    mQueue = mLooper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }
7.
public Handler(Looper looper, Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
  }

The specific implementation is only the last two. Now that we know how mCallback comes from, we can pass it in the constructor.

If both of these callbacks are empty, then execute the Handler's own handleMessage(msg) method, which is the handleMessage method that we are familiar with when creating a new Handler and overriding it.

Looper

Here we have a doubt, that is, when we create a Handler, we did not pass any parameters, nor is there anywhere showing that Looper-related methods were called. Where is the creation and method call of Looper? In fact, Android itself has done these things for us. We can find them in the main method of the program entry ActivityThread:

 public static void main(String[] args) {
  ...
  Looper.prepareMainLooper();
  ...
  Looper.loop();
  ...

Summary

I have roughly sorted out the message mechanism of Handler, as well as the difference between the post method and the commonly used sendMessage method. To summarize, it mainly involves four classes: Handler, Message, MessageQueue, and Looper:

Create a new Handler, send a message through sendMessage or post, and the Handler calls sendMessageAtTime to pass the Message to MessageQueue

The MessageQueue.enqueueMessage method puts the Message into the queue in the form of a linked list

The loop method of Looper calls MessageQueue.next() in a loop to get messages, and then calls Handler's dispatchMessage to process messages

In dispatchMessage, it is judged that msg.callback, mCallback, i.e., the callback of the post method or the constructor passed in, is not empty, and their callbacks are executed. If both are empty, the most commonly used overridden handleMessage is executed.

Finally, let's talk about the memory leak problem of the handler
Let's take a look at the code for creating our new Handler:

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      ...
    }
  ;

When creating a Handler using an inner class (including anonymous class), the Handler object implicitly holds a reference to the Activity.

Handlers usually come with a time-consuming background thread, which sends a message to update the UI after the task is completed. However, if the user closes the Activity during the network request process, normally, the Activity is no longer used and may be recycled during GC checking. But since the thread has not finished executing at this point, and this thread holds a reference to the Handler (otherwise how can it send messages to the Handler?), this Handler also holds a reference to the Activity, causing the Activity to be unable to be recycled (i.e., memory leak) until the network request is finished.

In addition, if the postDelayed() method of Handler is executed, a MessageQueue will be executed before the set delay reaches. -> Message -> Handler -> The chain of Activity leads to your Activity being held by a reference and cannot be recycled.

One solution is to use weak references:

static class MyHandler extends Handler {
  WeakReference<Activity > mActivityReference;
  MyHandler(Activity activity) {
    mActivityReference= new WeakReference<Activity>(activity);
  }
  @Override
  public void handleMessage(Message msg) {
    final Activity activity = mActivityReference.get();
    if (activity != null) {
      mImageView.setImageBitmap(mBitmap);
    }
  }
}

This is the summary of the Android handler message mechanism. We will continue to supplement relevant information in the future, thank you for your support of this site!

Statement: The content of this article is from the Internet, and the copyright belongs to the original author. The content is contributed and uploaded by Internet users spontaneously. This website does not own the copyright, has not been manually edited, and does not assume any relevant legal liability. If you find any content suspected of copyright infringement, please send an email to: notice#oldtoolbag.com (Please replace # with @ when sending an email to report abuse, and provide relevant evidence. Once verified, this site will immediately delete the content suspected of infringement.)

You May Also Like