Android Component_Handler Looper Message Understanding
1. Overview of Handler mechanism
The Handler mechanism is a message processing mechanism in Android.
Major components or key concepts:
1. Message, the data unit for inter-thread communication.
2. Message Queue, Message Queue, which stores messages published by Handler and executes according to FIFO.
3. Handler is the primary handler of a Message and handles messages in a Message queue by adding messages to the Message queue comments.
4. Looper Loop, which loops away messages from Message Queue and delivers them to the appropriate Handler for processing.
5. Thread UI thread s are usually the main thread for which a Message Queue is created when Android starts the program.
Each thread can contain a Looper object and a MessageQueue data structure.
6. ThreadLocal his role is to help Handler get the ooper of the current thread (multiple threads may have multiple Looper s)
2. Use scenarios
We often use Handler to update the UI, but not to say that Handler is the one that updates the UI, time-consuming I/O operations, reading files, accessing the network, and so on.
Subthreads communicate, you can create a handler in one subthread, then use this handler instance to send messages in any other thread, and the code that eventually processes the messages will run in the thread where you created the handler instance.
Delay tasks, etc.
3. Usage
3.1. Update UI in child thread
public class MainActivity extends AppCompatActivity {
//handler in main thread
Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//Get the Message object you just sent and do the UI here
Log.e(TAG,"------------> msg.what = " + msg.what);
...
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//Send an update message to the handler of the main thread in the child thread to update the UI
new Thread(new Runnable() {
@Override
public void run() {
Message m = mHandler.obtainMessage();
...
mHandler.sendMessage(m);
}
}).start();
}
}
3.2 Interthread Communication
final Handler[] h = new Handler[1];
new Thread(new Runnable() {
@Override
public void run() {
Looper.prepare();//To create a looper for a child thread, the child thread uses handler messaging, which must be done because the default child thread does not have a Looper object
h[0] = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG,"------------> msg.what = " + msg.what);
}
};
Looper.loop();//Message Pool Message Cycle Processing
}
}, "work1").start();
new Thread(new Runnable() {
@Override
public void run() {
Message msg = h[0].obtainMessage();
//msg.sendToTarget();
h[0].sendEmptyMessage(0);//Send messages to work1 using handler holding looper for work1 subthread in work2 subthread for message processing
}
}, "work2").start();
3.3 Use of HandlerThread
{
MyHandlerThread mHandlerThread = new MyHandlerThread("work");
Handler mHandler = new Handler(mHandlerThread.getLooper()){//Associate the mHandler with the Looper of the HandlerThread object
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
MyHandlerThread mHandlerThread = new MyHandlerThread("work");
Handler mHandler = new Handler(mHandlerThread.getLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
}
3.4 Handler CallBack parameter use
The CallBack parameter can be passed in when constructing the Handler object as follows
//Main thread:
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
//In other threads:
handler.sendMessageXXX(msg);
3.5 Handler post method uses
//Create mPostHandler in main thread
Handler mPostHandler = new Handler();
//Use post method in child threads
new Thread(new Runnable() {
@Override
public void run() {
/* Message m = mHandler.obtainMessage();
mHandler.sendMessage(m);*/
mPostHandler.post(new Runnable() {
@Override
public void run() {
//Update the UI, note that although the operation is written in a sub-thread, the fact runs in the main thread, Reason Analysis is in 4.2.5
}
});
}
}).start();
3.6 Handler Delayed Tasks/Loop Timer Operations
Delay Task new Handler().postDelayed(new Runnable(){ public void run() { //show dialog } }, 5000); //Delay 5 seconds to execute runnable run method
Cycle Timer Operation:
1,Create one firstHandlerobject
Handler handler=new Handler();
2,Then create one Runnable Yes
Runnable runnable=new Runnable(){
@Override
public void run() {
// TODO Auto-generated method stub
//What to do, call this Runnable object again here to implement timer operations every two seconds
handler.postDelayed(this, 2000);
}
};
3,Use PostDelayed Method, call this in two seconds Runnable object
handler.postDelayed(runnable, 2000);
4,If you want to turn off this timer, you can do so
handler.removeCallbacks(runnable);
4. Principle Analysis
The iron triangle-Handler, Looper, and MesageQueue of the Handler mechanism are also analyzed shallowly below.
Overall architecture:
4.1 The main thread Handler object is associated with the main thread Looper object
A shallow analysis of the example in 3.1 is provided.
4.1.1 Handler Object Initialization Gets the ooper object for the main thread
1. Main thread Handler object initialization:
Handler mHandler = new Handler(){...}
The default constructor associates the Handler object with the ooper object of the current thread.Let's see how the constructor relates the looper of the current thread.
Handler's default constructor:
frameworks/base/core/java/android/os/Handler.java
public Handler() {
this(callback:null, async:false);
}
2. If the thread does not have a looper object, the handler will throw an exception:
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();//
if (mLooper == null) {//An exception will be thrown if the current thread does not have a Looper object
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
3.The sThreadLocal object holds the looper object for the current thread, Looper.myLooper() is the ooper object used to get the current thread:
frameworks/base/core/java/android/os/Looper.java
public static @Nullable Looper myLooper() {
return sThreadLocal.get();//Get the ooper object for the current thread here
}
4.1.2 Create Looper and MessageQueue objects when the main thread initializes
1. For UI threads, which we call Main threads and ActivityThread, when ActivityThread was created, Looper objects were created in the main function and entered into the message loop through Looper.loop():
frameworks/base/core/java/android/app/ActivityThread.java
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //Create Main Thread Looper Object
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
Looper.loop();//Cycle through messages in MessageQueue
throw new RuntimeException("Main thread loop unexpectedly exited");
}
2.Looper.prepareMainLooper() creates the ooper and MessageQueue for the main thread and opens the message loop for the main thread through Looper.loop():
frameworks/base/core/java/android/os/Looper.java
public static void prepareMainLooper() {
prepare(false);//Create a Looper object and associate the new looper object with the current thread
synchronized (Looper.class) {
if (sMainLooper != null) { //You can see that the main thread should have only one Looper object
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//Returns the ooper object for the current thread
}
}
3. The Looper.prepare() function creates a Looper object for the current thread (here the main thread, or another thread) and sets the object to the thread's sThreadLocal variable:
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) { // Explains that prepare cannot be called twice, otherwise an exception will be thrown, guaranteeing that a thread has only one Looper instance
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));//sThreadLocal is a ThreadLocal object that stores variables in a thread.
}
4.Looper.myLooper() returns the ooper object for the current thread, which verifies that myLooper() does get the ooper object for the current thread from the sThreadLocal variable.
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
To summarize, the main thread initially generates a Looper object with a non-exitable MesageQueue and stores the Looper object in the sThreadLocal variable of the main thread.
When a Handler object is created in the main thread, it is associated with the main thread Looper object stored in the sThreadLocal variable of the current thread.
Therefore, in the example of 3.1 above, when the main thread new a Handler object associates the Handler object with the Looper object in the main thread, Looper.loop() is executed to process messages in a continuous loop;
4.2 Brief Analysis of Handler, Looper, Message and Handler Thread
4.2.1 Handler
1. Methods available
post(Runnable)
postAtTime(Runnable,long)
postDelayed(Runnable long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageDelayed(Message,long)
sendMessageAtTime(Message,long)
2. All of the above methods will eventually call sendMessageAtTime(), add the message to the message queue held by Looper associated with the target Handler object of msg, and then queue the message queue to cycle through the message:
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); //Add the message to the message queue in the looper associated with the message's target handler
}
4.2.2 Looper:
1.Looper constructor, you see a new MessageQueue object in Looper's constructor:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);//Create Message Queue
mThread = Thread.currentThread();
}
2.Looper.loop(), a function that loops through messages in a message queue:
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
...
// 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(); // May block, if there is no message it will block here.
...
try {
msg.target.dispatchMessage(msg); //Call the dispatchMessage method of the handler that sent the message;
} finally {
}
...
msg.recycleUnchecked();//The processed msg can be recycled, and the implementation clears the contents of the msg object and keeps it in the message pool for recycling
}
}
3. Processing messages in dispatchMessage, where the runnable in the post method is msg.callback
frameworks/base/core/java/android/os/Handler.java
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //Execute the runnable method in the post method
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) { //If a callback is passed in when a handler is created
return;
}
}
handleMessage(msg);//Replicate the handleMessage() method to process messages when creating handler s
}
}
3.1 will first determine that the msg.callback memory does not exist, msg.callback is a Runnable type, if msg.callback exists, then the Message is put into the Message queue by executing the Handler's postXXX series method. In this case handleCallback (msg) will be executed, and the handleCallback source code is as follows:
private static void handleCallback(Message message) {
message.callback.run();
}
So we clearly see that we executed the run method of msg.callback, which is the run method of the Runable object passed by postXXX.
3.2. If we don't put the Message into the Message queue through the postXXX family method, then msg.callback is null, and the code continues to execute, then we will judge that the member field mCallback memory of Handler does not exist.MCallback is of type Hanlder.Callback. As mentioned above, in the constructor of Handler we can pass an object of type Hanlder.Callback which needs to implement the handleMessage method. If we pass the Callback object in the constructor, we will have the handleMessage side of the CallbackMethod to process messages.
3.3. If we do not pass in a Callback type object in the constructor, then mCallback is null, then we will call Handler's own hanldeMessage method, which is empty by default and we need to override ourselves to implement it.
To summarize, we can see that Handler provides three ways to process messages, with priority given to the former: first, try to have the Runable passed in postXXX execute, second, try to have the handleMessage method of the Callback passed in the Handler constructor handle, and finally, let Handler handle its own handleMessage methodReason Message.
4.2.3 MessageQueue
next() method of 1 MessageQueue
The next method is an infinite loop in which messages are constantly fetched from the message queue, and if a message is returned and removed from the chain table, no message is always blocked.
Message next() {
for (;;) {
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
...
// Got a message.
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
}
}
}
4.2.4 HandlerThread
1. Thread with Looper:
frameworks/base/core/java/android/os/HandlerThread.java
public class HandlerThread extends Thread {
@Override
public void run() {
...
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
onLooperPrepared();
Looper.loop();
...
}
}
We see that HandlerThread inherits from Thread and uses Looper.prepare() in the run function to create a Looper object for use with a thread and places the object in a variable within the thread scope (sThreadLocal); during the construction of the Looper object, a MessageQueue is initialized as the Looper objectMember variable; loop() is turned on, continuous loops cancel message processing from MessageQueue, block when there is no message, and wake up when there is one.
So you can also use HandlerThread, which eliminates the need for Looper.prepare() and Looper.loop() as normal threads do, because HandlerThread is ready for us;
2. Additionally, the Looper object of the HandlerThread can be obtained by the getLooper method of the HandlerThread:
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
handleMessage
post method in 4.2.5 Handler
In the example in 3.5, the run method in post in the sub-thread runs in the main thread for the following reasons:
1. Incoming Runnable Object
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
2. Encapsulate Runnable into a Message object here
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r; //Pass runnable into callback variable in message object
return m;
}
3. Consistent with the normal handler sendMessage method flow, the call uses sendMessageAtTime to add the message to the target handler of the message and to the message queue in the looper associated with the mPostHandler, as mentioned in 4.2.1.
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
That is, although the runnable replication method is in the child thread, it is also in the Message Queue object held by looper encapsulating the message object into the main thread, where messages encapsulating the runnable variable are processed, so the run method in the post runs in the main thread.
5. Notes
- Handler handles messages with a strict distinction between whether it is in a UI thread or not. Handler is generally used to deliver messages in non-UI threads and to send messages in non-UI threads.
Processing is strictly enforced, but if Handler is used to send messages in the UI thread, the same messages are merged internally and the timing of execution is not guaranteed - Because HandlerThread has its own message queue, it does not interfere with or block UI threads and is better suited for tasks that take longer to process.We just need to send the task to HandlerThread and wait for the notification to return to the main thread when the task finishes executing.
- Handler usage in android should be aware of OOM memory leaks caused by handler) http://blog.csdn.net/javazejian/article/details/50839443
- When using mechanisms in normal threads, remember to Looper.prepare(); and finally Looper.loop();
6. Case Analysis
Make it up later