Detailed implementation principle of Handler in Android

  • 2021-06-28 14:05:17
  • OfStack

In Android, only the main thread can operate UI, but the main thread cannot perform time-consuming operations, otherwise it will block the thread and cause ANR exceptions, so time-consuming operations are often placed on other sub-threads.If the UI needs to be updated in a child thread, 1 normally sends the message through Handler, and the main thread accepts the message and performs the appropriate logical processing.In addition to using Handler directly, UI can also be updated through post methods of View and runOnUiThread methods of Activity, which also make use of Handler internally.In the previous article, Android AsyncTask Source Analysis also mentioned that it uses Handler internally to return the processing results of a task to the UI thread.

This paper gives an in-depth analysis of the message processing mechanism of Android to understand how Handler works.

Handler
Start with an example to see the usage of Handler.


public class MainActivity extends AppCompatActivity {
 private static final int MESSAGE_TEXT_VIEW = 0;
 
 private TextView mTextView;
 private Handler mHandler = new Handler() {
  @Override
  public void handleMessage(Message msg) {
   switch (msg.what) {
    case MESSAGE_TEXT_VIEW:
     mTextView.setText("UI Successful Update ");
    default:
     super.handleMessage(msg);
   }
  }
 };

 @Override
 protected void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setContentView(R.layout.activity_main);
  Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
  setSupportActionBar(toolbar);

  mTextView = (TextView) findViewById(R.id.text_view);

  new Thread(new Runnable() {
   @Override
   public void run() {
    try {
     Thread.sleep(3000);
    } catch (InterruptedException e) {
     e.printStackTrace();
    }
    mHandler.obtainMessage(MESSAGE_TEXT_VIEW).sendToTarget();
   }
  }).start();

 }
}

The code above first creates a new instance of Handler and overrides the handleMessage method, in which the corresponding UI update is made based on the type of message received.So look at the source code for the construction method of Handler:


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;
}

In the construction method, the Looper object is obtained by calling Looper.myLooper().If mLooper is empty, an exception is thrown: "Can't create handler inside thread that has not called Looper.prepare()", meaning that handler cannot be created on a thread that does not call Looper.prepare().The example above does not call this method, but it does not throw an exception.It is because the main thread has already called for us at startup, so we can create Handler directly.Creating Handler directly on other sub-threads can cause the application to crash.

After you get Handler, you get its internal variable, mQueue, which is the MessageQueue object, the message queue, used to hold the messages sent by Handler.

So far, all three important roles of the Android messaging mechanism have appeared, Handler, Looper and MessageQueue.1 In general, Handler is the one that we have more contact with in the code, but Looper and MessageQueue are indispensable to the Handler runtime.

Looper
The previous section analyzed the structure of Handler, which calls the Looper.myLooper() method. Here is its source code:

static final ThreadLocal < Looper > sThreadLocal = new ThreadLocal < Looper > ();


public static @Nullable Looper myLooper() {
 return sThreadLocal.get();
}

The code for this method is simply to get the Looper object from sThreadLocal.sThreadLocal is an ThreadLocal object, which indicates that Looper is thread independent.

In the construction of Handler, you can see from the exception thrown that each thread needs to call the prepare() method to continue looking at its code in order to get Looper:


private static void prepare(boolean quitAllowed) {
 if (sThreadLocal.get() != null) {
  throw new RuntimeException("Only one Looper may be created per thread");
 }
 sThreadLocal.set(new Looper(quitAllowed));
}

Simply set up an Looper for sThreadLocal.Note, however, that if sThreadLocal is already set, an exception will be thrown, meaning that only one Looper will be thrown for a thread.When Looper is created, a message queue is created internally:


private Looper(boolean quitAllowed) {
 mQueue = new MessageQueue(quitAllowed);
 mThread = Thread.currentThread();
}

The question now is, what exactly does Looper look like when it's important?
Answer: Looper opens the message loop system and constantly removes messages from the message queue MessageQueue for processing by Handler.

Why do you say that? Look at the loop method of Looper:


public static void loop() {
 final Looper me = myLooper();
 if (me == null) {
  throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
 }
 final MessageQueue queue = me.mQueue;

 // Make sure the identity of this thread is that of the local process,
 // and keep track of what that identity token actually is.
 Binder.clearCallingIdentity();
 final long ident = Binder.clearCallingIdentity();
 
 // Infinite loop 
 for (;;) {
  Message msg = queue.next(); // might block
  if (msg == null) {
   // No message indicates that the message queue is quitting.
   return;
  }

  // This must be in a local variable, in case a UI event sets the logger
  Printer logging = me.mLogging;
  if (logging != null) {
   logging.println(">>>>> Dispatching to " + msg.target + " " +
     msg.callback + ": " + msg.what);
  }

  msg.target.dispatchMessage(msg);

  if (logging != null) {
   logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
  }

  // Make sure that during the course of dispatching the
  // identity of the thread wasn't corrupted.
  final long newIdent = Binder.clearCallingIdentity();
  if (ident != newIdent) {
   Log.wtf(TAG, "Thread identity changed from 0x"+ Long.toHexString(ident) + " to 0x"+ Long.toHexString(newIdent) + " while dispatching to "+ msg.target.getClass().getName() + " "+ msg.callback + " what=" + msg.what);
  }

  msg.recycleUnchecked();
 }
}

The code for this method is a bit long. It doesn't go into details, it just looks at the overall logic.You can see that there is a dead loop inside this method, in which the next message is obtained by next () method of MessageQueue, and no one gets blocked.If a new message is obtained successfully, msg.target.dispatchMessage (msg) is called, msg.target is the Handler object (as you will see in the next section), dispatchMessage is the distribution message (which is now running on the UI thread), and the sending and processing of the message is analyzed below.

Message Sending and Processing
When a subthread sends a message, it calls a series of methods, such as sendMessage, sendMessageDelayed, and sendMessageAtTime, and eventually it calls sendMessageAtTime (Message msg, long uptimeMillis) repeatedly, code as follows:


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);
}

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

This method is to call enqueueMessage to insert a message into the message queue. In the total of enqueueMessage, msg.target is set to the current Handler object.

After the message is inserted into the message queue, Looper takes care of removing it from the queue and then calls the dispatchMessage method of Handler.Next, let's see how this works with messages:


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

First, if the callback of the message is not empty, the handleCallback process is called.Otherwise, determine if mCallback of Handler is null or call its handleMessage method if it is not.If it is still empty, call Handler's own handleMessage, which is the method we override when we create Handler.

If you call the post (Runnable r) method of Handler when sending a message, you encapsulate Runnable into the callback of the message object, then call sendMessageDelayed with the following code:


public final boolean post(Runnable r)
{
 return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
 Message m = Message.obtain();
 m.callback = r;
 return m;
}

handleCallback is then called in dispatchMessage for processing:


 private static void handleCallback(Message message) {
 message.callback.run();
}

You can see that the run method was called directly to process the message.

If an Callback object is provided directly when an Handler is created, the message is handed over to the handleMessage method of that object.Callback is an interface within Handler:


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;
}

0

This is the process of sending and processing messages, sent in a subthread, but processed in a main thread with the dispatchMessage method.

summary
At this point, the analysis of the principles of the Android message processing mechanism is complete.It is now known that message processing is done through Handler, Looper, and MessageQueue.Handler is responsible for sending and processing messages. Looper creates a message queue and constantly removes messages from the queue to Handler. MessageQueue is used to save messages.


Related articles: