1. The Role and Relation of the Three
Mesage (message) is the medium through which the same or different threads interact, corresponding to an operation.
Looper (Looper) is a prerequisite for creating handler. It is mainly used to create an instance of MessageQueue (message queue) of the current thread (a Looper corresponds to a Thread number of threads one by one, and there is no one-to-many relationship between the two), and then enter an infinite loop body to read and cancel information from the MessageQueue continuously. The common methods are prepare() and loop(). Note the English annotations on 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);
}
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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();
}
}
Handler is a tool for transmitting and processing messages. Handler can insert messages into the end of the Message queue and process messages at the head of the Message queue. Handler can send messages in seven ways. These seven methods go around and around. In fact, the sendMessageAtTime method is finally called to insert this Message into the end of the Message queue.
/**
* Enqueue a message into the message queue after all pending messages
* before the absolute time (in milliseconds) <var>uptimeMillis</var>.
* <b>The time-base is {@link android.os.SystemClock#uptimeMillis}.</b>
* Time spent in deep sleep will add an additional delay to execution.
* You will receive it in {@link #handleMessage}, in the thread attached
* to this handler.
*
* @param uptimeMillis The absolute time at which the message should be
* delivered, using the
* {@link android.os.SystemClock#uptimeMillis} time-base.
*
* @return Returns true if the message was successfully placed in to the
* message queue. Returns false on failure, usually because the
* looper processing the message queue is exiting. Note that a
* result of true does not mean the message will be processed -- if
* the looper is quit before the delivery time of the message
* occurs then the message will be dropped.
*/
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);
}
Handler handles messages as follows. Actually, it is similar to View event handling mechanism. If the Message itself has a callback, it will be handed directly to the callback processing of the Message. If the handler sets mCallback, it will be handed to the mCallback processing. Finally, no one recognizes it, we will execute the handleMessage method. Generally, we rewrite the handleMessage method directly.
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg); //If the Message itself has a callback, it is handed directly to the callback processing of the Message.
} else {
//If mCallback is set in this Handler, it is handed over to mCallback for processing.
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
2. Confusion that sub-threads can update UI
android stipulates that only the main thread can operate the UI, and those time-consuming operations are handled by the sub-thread, and when processed, a Message is sent to the main thread, which then updates the UI. As mentioned above, Looper is a necessary condition for Handler creation. The reason why we don't see prepare() and loop() methods in Acticity is that google has already wrapped them for us in Activity Thread (as follows).
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
SamplingProfilerIntegration.start();
// CloseGuard defaults to true and can be quite spammy. We
// disable it here, but selectively enable it later (via
// StrictMode) on debug builds, but using DropBox, not logs.
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
// Set the reporter for event logging in libcore
EventLogger.setReporter(new EventLoggingReporter());
// Make sure TrustedCertificateStore looks in the right place for CA certificates
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper(); //Watch out!! Look here
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop(); //Watch out!! Look here
throw new RuntimeException("Main thread loop unexpectedly exited");
}
Now I create a SubLooper and SubHandler in the sub-thread. At this time, the SubLooper and SubHandler should belong to the sub-thread. Then, through the sub-Handler, the Message is sent to the sub-thread in the main thread, and the sub-thread processes the Message to update the UI. The dialog and toast do pop up. Doesn't that overturn the proposition that sub-threads can't update the UI? Does it mean that all handlers belong to the main thread?
new Thread(new Runnable() {
public void run() {
Handler handlerMain = new Handler(Looper.getMainLooper()) { //Note that Looper.getMainLooper() is used here to get the Looper of the main thread
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 2:
Toast.makeText(getApplicationContext(),"Main threaded Handler Processing messages from the main thread"+Process.myTid(),Toast.LENGTH_SHORT).show();
break;
default:
break;
}
Log.d(TAG, "handleMainMessageInner: " + Process.myTid()); //At this point, print the id of the main thread
}
};
Looper.prepare();
looperSub = Looper.myLooper();
handlerSub = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 2:
Toast.makeText(getApplicationContext(), "Subthreads process messages from the main thread" + Process.myTid(), Toast.LENGTH_SHORT).show();
AlertDialog.Builder builder = new AlertDialog.Builder(MainActivity.this);
builder.setTitle("hha")
.setMessage("zzzzzzz")
.show();
break;
default:
break;
}
Log.d(TAG, "handleMessage: " + Process.myTid()); //At this point, print the id of the sub-thread
}
};
handlerSub.sendEmptyMessage(1);
handlerMain.sendEmptyMessage(1);
Looper.loop();
}
}).start();
}