Android message mechanism Handler parsing (source code + Demo)
Handler is one of the most common problems for developers During the interview. This article will fully explain Handler, including the source code layer and usage methods.
If you have any questions after reading the article, you are welcome to discuss it in the comments.
The basic content includes:
After reading the article, you can use this picture for review.
I. What is HandlerHandler? It is a mechanism provided by Android to update the UI and a message processing mechanism. It can be used to send or receive messages. Ii. Why is HandlerAndroid designed to encapsulate a set of message creation, transmission, and processing mechanisms? If this mechanism is not followed, the UI information cannot be updated, exception thrown. 3. Handler usage. 1. postdelayed () delayed sending. Run the subthread text polling Demo (update Textview every second)
Public class MainActivity extends AppCompatActivity {private TextView mTextView; private Handler mHandler = new Handler () {@ Override public void handleMessage (Message msg) {super. handleMessage (msg) ;}}; private String [] str = new String [] {"arrogant", "biased", "zombie"}; private int index = 0; myRunnable myRunnable = new MyRunnable (); private class MyRunnable implements Runnable {@ Override public void run () {index = index % 3; mTextView. setText (str [index]); index ++; mHandler. postDelayed (myRunnable, 1000) ;}@ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); mHandler. postDelayed (myRunnable, 1000 );}}
2. sendMessage () callback handleMessage () transfer message Demo: Get the information in the Child thread, send it to the main thread, and update the content of textview
public class MainActivity extends AppCompatActivity { private TextView mTextView; Handler mHandler = new Handler(){ @Override public void handleMessage(Message msg) { mTextView.setText(msg.obj+""+ "arg1="+msg.arg1 + " arg2="+msg.arg2); super.handleMessage(msg); } }; private class Person{ String name; int age; @Override public String toString() { return "name="+name+" age="+age; } } @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mTextView = (TextView) findViewById(R.id.tv); new Thread(){ @Override public void run() { Message msg = new Message(); msg.arg1 = 1; msg.arg2 = 2; Person person = new Person(); person.name = "pig"; person.age = 10; msg.obj = person; mHandler.sendMessage(msg); } }.start(); }}
3. The principle of sendToTarget () message transmission is consistent with that of the second method.
Public class MainActivity extends AppCompatActivity {private TextView mTextView; Handler mHandler = new Handler () {@ Override public void handleMessage (Message msg) {mTextView. setText (msg. obj + "" + "arg1 =" + msg. arg1 + "arg2 =" + msg. arg2); super. handleMessage (msg) ;}}; private class Person {String name; int age; @ Override public String toString () {return "name =" + name + "age =" + age ;}@override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); new Thread () {@ Override public void run () {Message msg = mHandler. obtainMessage (); // You can also obtain the Message object msg. arg1 = 1; msg. arg2 = 2; Person person = new Person (); person. name = "pig"; person. age = 10; msg. obj = person; msg. sendToTarget ();}}. start ();}}
4. Use CallBack to intercept Handler messages
Public class MainActivity extends AppCompatActivity {private TextView mTextView; Handler mHandler = new Handler (new Handler. callback () {// input the CallBack object. If the returned value of the overload is bollean's handleMessage () // The returned value is false, this method will be executed first, then execute the handleMessage () method whose return value is void // the return value is true. Only execute this method @ Override public boolean handleMessage (Message msg) {Toast. makeText (MainActivity. this, "intercept message", Toast. LENGTH_SHORT ). show (); return false ;}}) {public void handleMessage (Message msg) {Toast. makeText (MainActivity. this, "Send message", Toast. LENGTH_SHORT ). show () ;}}; private class Person {String name; int age; @ Override public String toString () {return "name =" + name + "age =" + age ;}@override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); Button btn = (Button) findViewById (R. id. btn); btn. setOnClickListener (new View. onClickListener () {@ Override public void onClick (View v) {mHandler. sendEmptyMessage (0 );}});}}
4. Why is the Handler mechanism used in Android to intelligently update the UI in the main thread? The most fundamental is to solve the problem of multi-thread concurrency. If there are multiple threads updating the UI simultaneously in the same Activity without locking, what will happen? UI updates are messy. What about locking? This may cause performance degradation. Using the Handler mechanism, we do not need to consider multithreading. All UI update operations are processed by polling in the message queue of the main thread. 5. Handler mechanism Principle 1. Handler encapsulates the message sending (mainly including who the message is sent to) logoff (1) contains a message queue, that is, MessageQueue, all messages sent by Handler will enter this queue (2) logoff. the loop method is an endless loop that constantly extracts messages from MessageQueue. messages are processed if there are messages, and messages are blocked if there are no messages. 2. MessageQueue, a message queue, you can add messages, message Processing 3. the Handler will be associated with the Looper internally. That is to say, you can find the Looper in the Handler, find the loue, find the MessageQueue, and send the message in the Handler, actually, it is to send a Message to the Message. Conclusion: Handler is responsible for sending the Message, logoff is responsible for receiving the Message, and returning the Message to Handler. MessageQueue is a container for storing the Message. Source code layer: Android applications are created through ActivityThread. By default, ActivityThread creates a Main thread and a logoff thread, and all UI update threads are created through the Main thread. Check the source code of logoff. loop and find that it is indeed an endless loop.
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(); 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 msg.tar get. dispatchMessage () method is used to process the message and view its source code.
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}
It can be seen from the source code that when there is a CallBack, the message will be intercepted. Otherwise, handleMessage () will be called back to process the message. For SendMessage () series methods, too much parsing will not be done here, however, the source code shows that the message is finally passed into the message queue. 6. Create a thread-related Handler to create a Handler in the Child thread. You need to use logoff. prepare () to obtain the logoff and call the logoff. loop () method to round-robin the Message in the Message queue.
Public class MainActivity extends AppCompatActivity {private TextView mTextView; public Handler mHandler = new Handler () {// Handler @ Override public void handleMessage (Message msg) {Log. d ("CurrentThread", Thread. currentThread () + ""); // print the Thread ID}; class MyThread extends Thread {private Handler handler; // Handler @ Override public void run () in the sub-Thread () {logoff. prepare (); // get logoff handler = new Handler () {@ Override public void handleMessage (Message msg) {Log. d ("CurrentThread", Thread. currentThread () + "") ;}}; Looper. loop (); // round robin Message Queue }}@ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); MyThread thread = new MyThread (); thread. start (); try {thread. sleep (500);} catch (InterruptedException e) {e. printStackTrace ();} thread. handler. sendEmptyMessage (1); mHandler. sendEmptyMessage (1 );}}
Output result 03-31 20:56:06. 498 1804-1816 /? D/CurrentThread: Thread [Thread-113, 5, main] 03-31 20:56:06. 578 1804-1804/com. lian. handlerdemo D/CurrentThread: Thread [main, 5, main] 7. HandlerThread is essentially a Thread. The difference is that after run (), he creates a logoff containing a message queue, in this way, when creating a Handler in a child thread, we only need to specify the logoff in HandlerThread, and no longer need to call logoff. prepare (), logoff. loop (), which simplifies the operation. The logoff used by Handler provided by Android is bound to the Message Queue of the UI thread by default, so we cannot perform time-consuming operations in Handler. For non-UI threads, if you want to use the message mechanism, logoff within HandlerThread is the most suitable, and it does not block the UI thread.
Public class MainActivity extends AppCompatActivity {private TextView mTextView; public HandlerThread mHandlerThread; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mHandlerThread = new HandlerThread ("handler thread"); mHandlerThread. start (); Handler handler = new Handler (mHandlerThread. getLooper () {// get Looper @ Override public void handleMessage (Message msg) {Log through getLooper. d ("current thread", "" + Thread. currentThread () ;}}; handler. sendEmptyMessage (1 );}}
Result: 03-31 21:36:42. 770 7225-7237 /? D/currentthread: Thread [handler thread, 5, main] 8. information interaction between the main Thread and the sub-thread the Handler in the main Thread sends messages to each other in the sub-thread, you only need to call the sendMessage () of the other party.
Public class MainActivity extends AppCompatActivity {public Handler mHandler = new Handler () {@ Override public void handleMessage (Message msg) {Log. d ("current thread", "" + Thread. currentThread (); Message message = new Message (); message. what = 1; handler. sendMessageDelayed (message, 1000); // send a message to the Handler of the subthread}; public HandlerThread mHandlerThread; public Handler handler; private Button btn1, btn2; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); btn1 = (Button) findViewById (R. id. btn); btn2 = (Button) findViewById (R. id. btn2); mHandlerThread = new HandlerThread ("handler thread"); // specify the name of HandlerThread mHandlerThread. start (); handler = new Handler (mHandlerThread. getLooper () {// get Looper @ Override public void handleMessage (Message msg) {Log through getLooper. d ("current thread", "" + Thread. currentThread (); Message message = new Message (); mHandler. sendMessageDelayed (message, 1000); // send a message to the Handler in the main thread}; btn1.setOnClickListener (new View. onClickListener () {@ Override public void onClick (View v) {handler. sendEmptyMessage (1); // start to send the message}); btn2.setOnClickListener (new View. onClickListener () {@ Override public void onClick (View v) {handler. removeMessages (1); // stop sending a message }});}}
Result: 22:21:11, 03-31. 422 16748-16760/com. lian. handlerdemo D/currentthread: Thread [handler thread, 5, main] 03-31 22:21:12. 422 16748-16748/com. lian. handlerdemo D/currentthread: Thread [main, 5, main] 03-31 22:21:13. 422 16748-16760/com. lian. handlerdemo D/currentthread: Thread [handler thread, 5, main] 03-31 22:21:14. 422 16748-16748/com. lian. handlerdemo D/currentthread: Thread [main, 5, main] 03-31 22:21:15. 426 16748-16760/com. lian. handlerdemo D/currentthread: Thread [handler thread, 5, main] 03-31 22:21:16. 426 16748-16748/com. lian. handlerdemo D/currentthread: Thread [main, 5, main] 03-31 22:21:20. 414 16748-16760/com. lian. handlerdemo D/currentthread: Thread [handler thread, 5, main] 03-31 22:21:21. 414 16748-16748/com. lian. handlerdemo D/currentthread: Thread [main, 5, main] 03-31 22:21:22. 414 16748-16760/com. lian. handlerdemo D/currentthread: Thread [handler thread, 5, main] 03-31 22:21:23. 418 16748-16748/com. lian. handlerdemo D/currentthread: Thread [main, 5, main]
9. Four UI update Methods 1. Handler. post (); 2. Handler. sendMessage (); in fact, there is no essential difference between the first two methods. They all update the UI in the UI thread by sending messages. We have already demonstrated this before and will not go into details. 3. How to Use runOnUIThread:
Public class MainActivity extends AppCompatActivity {TextView mTextView; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); new Thread () {@ Override public void run () {try {Thread. sleep (1000);} catch (InterruptedException e) {e. printStackTrace ();} runOnUiThread (new Runnable () {@ Override public void run () {mTextView. setText ("Update UI ");}});}}. start ();}}
Let's check the source code of runOnUIThread ().
public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); }}
It can be found that, in essence, it is still using the Handler. post () method to update UI 4 and View. post () in the UI thread.
Public class MainActivity extends AppCompatActivity {TextView mTextView; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); new Thread () {@ Override public void run () {try {Thread. sleep (1000);} catch (InterruptedException e) {e. printStackTrace ();} mTextView. post (new Runnable () {@ Override public void run () {mTextView. setText ("Update UI ");}});}}. start ();}}
View the source code, and update the UI using the Handler. post () method.
public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Assume that post will succeed later ViewRootImpl.getRunQueue().post(action); return true;}
10. Check a Demo before updating the UI in a non-UI thread.
Public class MainActivity extends AppCompatActivity {TextView mTextView; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); new Thread () {@ Override public void run () {mTextView. setText ("updated UI ");}}. start ();}}
Result:
We were surprised to find that the UI was successfully updated and no exception was thrown. However, when we first let the thread sleep for 2 s and then updated
Public class MainActivity extends AppCompatActivity {TextView mTextView; @ Override protected void onCreate (Bundle savedInstanceState) {super. onCreate (savedInstanceState); setContentView (R. layout. activity_main); mTextView = (TextView) findViewById (R. id. TV); new Thread () {@ Override public void run () {try {Thread. sleep (2000);} catch (InterruptedException e) {e. printStackTrace ();} mTextView. setText ("updated UI ");}}. start ();}}
Update failed, throwing an exception
Why? There is a ViewRootImpl class in the Activity. When this class is not instantiated, the system will not check whether the current thread is a UI thread. The instantiation of this class is implemented in onResume () of the Activity. Therefore, when we didn't let the sub-thread sleep, we directly updated the UI, and the system was too late to check whether the current thread was a UI thread. So we successfully updated the UI. After two seconds of sleep, ViewRootImpl was instantiated, when the UI is updated, an exception is thrown. Of course, in actual development, this is of little significance. We still need to update the UI in the UI thread. 11. Two common problems Handler may encounter: 1. Non-UI threads update the UI, that is, the problem we encountered above throws this exception: android. view. viewRootImpl $ CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. 2. When Handler is created in a child thread and logoff is missing, this exception is thrown: java. lang. runtimeException: Can't create handler inside thread that has not called logoff. prepare () view source code
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;}
If no logoff is found, this runtime exception will be thrown.