Simple parsing of Handler Looper MessageQueue Thread Handler Thread IntentService
These classes should be very basic and important for Android. You can use these classes to do a lot of interesting things.
such as
Handler class parsing
Handler generally acts as the receiver of a Message, receiving and processing messages sent by a thread. How does Handler receive and process messages? Let's first look at the construction of this class.
public Handler() { // Construction Method 1
this(null, false);
}
public Handler(Callback callback, boolean async) { // Construction Method 2
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;
}
We usually create a Handler by the way of new Handler(). Through the above construction method, we can know that this method finally calls the construction method 2 to construct a Handler, and the construction method 2 finally executes the following key code.
// Gets the Looper bound to the current thread
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
// mQueue to get the current thread looper
mQueue = mLooper.mQueue;
// Hold the callback method, which will be used in the dispatchMessage method
mCallback = callback;
mAsynchronous = async;
Looking at the source code of Handler, you will see that there is another way to construct it.
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
From this constructive approach, we can directly assign values to mLooper, mCalback and mAsynchronous. It seems that there is no special difference between the two constructions mentioned above. Think carefully whether the difference is quite big. The biggest difference should be the assignment of mLooper. The first two constructions can not directly specify the thread bound by mLooper. In which thread is initialized, mLooper is the Looper bound by which thread, and the last one is that mLooper can be specified. R, that is, you can specify a thread's Loper at will, including UI threads.
After the construction of Handler is completed, we usually send the message, and then receive the post-processing message. Let's first look at the commonly used method of sending the message in Handler.
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;
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
As you can see from the above code, a process is probably like this. First, a Message is created, which carries some information, what, obj, callback, etc. This message was eventually added to the mQueue of the Handler. This mQueue is managed by mLooper. So after the message is sent out, how does it work? We need to look at Looper's code.
Looper class parsing
Looper is a small class, but let's start with the construction method.
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
As you can see, it's a private constructor, so generally we can't create a Looper object in a new way. Since you can't directly get a new object, there must be other ways to get a Looper, otherwise how can we get a Looper? Looking at the source code again, we found the following code
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Only these two methods can get a Looper. From the code, we can see whether they are basically returning an object or not. If we continue to look at the code, we find the following methods.
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
// new has a Looper and binds to the current thread
sThreadLocal.set(new Looper(quitAllowed));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
As you can see, these methods roughly mean that if the current thread is not bound to Looper, a new Looper is created and the current thread is bound.
Almost all the code in the Looper class is described, and the most critical method is the following.
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the loop.
*/
public static void loop() {
// Get the Loper of the current thread
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();
// A dead loop, constantly pulling out the Message from the queue, and then processing
// If there is no Message, wait
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();
}
}
As you can see from the code, the main thing that this method does is to constantly take out the Message from the queue and process it. If there is no Message, wait.
Looking at the source code of Looper alone, it seems that it is not particularly clear. Let's find a scenario to get a better understanding. Let's take a look at the start of App. There's a main method in the ActivityThread class. This method can only focus on the following two lines of code
public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();
}
As you can see from the code, the Looper.prepareMainLooper() method is called to bind the UI thread to Looper, and then the Looper.loop() method is called to process the message from the mQueue of Looper bound by the current thread. This also tells us that if you need to use Handler in a thread, you need to call the Looper.prepare() method first, and then Looper.loop(), before you can use Handler.
It's critical to have a line of code in the Looper.loop() method
msg.target.dispatchMessage(msg);
This line of code means that when you fetch the msg, you give the MSG target to dispatch the msg. From the previous enqueueMessage method, we know that the target of the Message is the Hadler that sends the Message. Let's look at Handler's dispatchMessage method.
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
private static void handleCallback(Message message) {
message.callback.run();
}
This code tells us that we will first see if the callback of Message is empty, and if it is not, give it to the callback for processing. If it is empty, then go to see if the mCallback of Handler is empty, not for the mCallback of empty traffic. If it is empty, hand in the handler Message method of Handler, which you should be familiar with.
From the previous post call analysis, we know that msg.callback is actually the unnablle of that post, so the runnable of the post will eventually be processed when the looper bound by the current thread fetches the message from the message queue. The processing thread is the thread in which the looper is located. If the looper is the main thread, then time-consuming operation cannot be done.
Handler also has a common method
public final boolean sendMessage(Message msg) {
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
In fact, it is not very different from the post method, but Message is created by ourselves. It does not necessarily set the value of callback. If the value of callback is set, the order of calls is consistent with that of post.
At this point, Handler and Looper's analysis is probably complete. First, call Looper's prepare() method and loop() method, let Looper's framework run, and constantly cancel information processing from mQueue. Then a new handler and Looper binding is created to facilitate subsequent messages to be sent to Looper's mQueue. When the messages are sent to Looper's mQueue, Looper eventually takes out the message and processes it.
HandlerThread
This is also a special class. Preliminary view from the class name is a thread, first look at the main code of this class.
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
//... Ignore other code
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
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;
}
}
Looking carefully, we find that there is an mLooper attribute and a getLooper method in it. Combined with the previous analysis of Handler, we can pass in a Looper when creating a Handler, and the processing of the Message sent by the Handler is under the thread bound to Looper. If we want to use Handler to handle time-consuming tasks, we can use Handler Thread to handle them.
IntentService
The reason for mentioning this class is that HandlerThread is used, and it is also a useful class. From the name of the class, we can see that this is a service, which is generally used to handle background tasks.
Let's take a brief look at the source code first.
public abstract class IntentService extends Service {
...
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
/**
* Creates an IntentService. Invoked by your subclass's constructor.
*
* @param name Used to name the worker thread, important only for debugging.
*/
public IntentService(String name) {
super();
mName = name;
}
...
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
@Override
public void onStart(Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}
...
/**
* This method is invoked on the worker thread with a request to process.
* Only one Intent is processed at a time, but the processing happens on a
* worker thread that runs independently from other application logic.
* So, if this code takes a long time, it will hold up other requests to
* the same IntentService, but it will not hold up anything else.
* When all requests have been handled, the IntentService stops itself,
* so you should not call {@link #stopSelf}.
*
* @param intent The value passed to {@link
* android.content.Context#startService(Intent)}.
*/
@WorkerThread
protected abstract void onHandleIntent(Intent intent);
}
First, IntentService is an abstract class that cannot be used directly. Subclasses need to implement onHandleIntent.
Then I look at the startup process, create a HandlerThread and a Service Handler in the onCreate() method, and bind the HandlerThread's Looper and ServiceHandler. After we call the startService() method, the onStart method is finally called. You can see that the intent is finally handled by the ServiceHandler's handleMessage method. In H The andleMessage method gives onHandleIntent to handle.
Think carefully, because Looper handles messages one by one, IntentService handles events one by one, and after the event is handled, it tries to shut down, save resources and simplify the code.