Need I authorize this article reprinted author: kidou @ criminal in this link: www.jianshu.com/p/f70ee1765…

I was a little bored at home on the weekend, and I didn’t know what to do. I thought there was nothing about Android since I started my blog for so long, so I decided to write a blog about Android — Handler.

Why do I write Handler

It’s true that Handler is a very, very common thing in Android development, and there are countless blogs about Handler. Why am I writing about Handler?

Cause is that the company in order to expand the business is going to make a new product line so to distribution on the mobile end side four recruitment quota (each two iOS and Android), head for a week because the busy I do demand did not participate in the company, is the company’s two other colleagues to participate in the interview, after a week I also involved in it, However, I found a very serious problem: although several people I interviewed had 3-6 years of work experience, they did not explain Handler clearly.

How is it different from other blogs

There are so many blogs out there that talk about Handler, so how can MY blog be refreshing and actually benefit everyone?

After thinking about it, I found that the basic principle of Handler is very simple, but there are a lot of details. This time, I found the problem through the interview, so I decided to tell you about Handler through a mock interview.

convention

The questions in this article are my own ideas, not from real interviews (except for some of the questions I asked others in interviews). The rest are my ideas for introducing the Handler mechanism to you.

The source code will appear later in this article. In order to avoid friends to see the code in Android Studio and my blog is inconsistent, here is a unified environment:

  • SDK version: API 27
android{
  compileSdkVersion 27
  // ......
}
Copy the code
  • Source code version: 27_R03


Depth screenshot _ Select Area _20180623165118.png




Depth screenshot _ Select Area _20180623165324.png

Q: What classes are involved in the Handler mechanism? What are their functions

A: Handler, Looper, MessageQueue, and Message are the main ones. ThreadLocal is not included in this list.

The Handler sends the Message object to the MessageQueue and assigns its own reference to the Message#target.

Looper takes the Message object out of the MessageQueue and passes it to the Handler#dispatchMessage(Message) method. The main things we need here are: Instead of calling the Handler#handleMessage(Message) method, for reasons I’ll explain later.

MessageQueue is responsible for inserting and fetching messages

Q: What methods does the Handler use to send messages

I asked two people if they knew anything about post, and neither of them knew anything about post

sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)
Copy the code

The following methods may not be that important in my eyes

sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)
Copy the code

Q: Are messages in MessageQueue ordered? What is the basis of ordering

It’s ordered. Queues are ordered, sets are unordered, and what sort are they internally based on? Messagewhen is a relative time, This value is set by the MessageQueue#enqueueMessage(Message, Long) method.

Java: 554,566~578 Boolean enqueueMessage(Message MSG, long when) {//.... synchronized (this) { // .... msg.markInUse(); msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; } else { needWake = mBlocked && p.target == null && msg.isAsynchronous(); Message prev; for (;;) { prev = p; p = p.next; / / cycle, until we find the tail (p = = null) / / or when the message is less than our current this message when the if (p = = null | | the when < p.w hen) {break; } if (needWake && p.isAsynchronous()) { needWake = false; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } } return true; }Copy the code

If the current inserted message#when is between 5 and 8, then prev and p at the end of the for loop should look like the following


The relationship between prev and P

Because of this feature, when two Message#when are the same, the insertion sequence is in order, for example, when both of them are 7, then the first entry will look like the following picture (yellow) :


So the first 7 goes into the queue

The second entry looks like (red) :


The second 7 is in the queue

Q: what does Message#when refer to

Message#when is a time when the Message is expected to be delivered, SystemClock#uptimeMillis() plus delayMillis.

// Handler.java:596
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code

SystemClock#uptimeMillis() is a relative time of the current time, representing the number of milliseconds between 0 and the time the method was called.

Q: Message#when = system.currentTimemillis (

System.currenttimemillis () represents the number of milliseconds from 1970-01-01 00:00:00 to the current time. This value is strongly associated with the System time and can be modified by changing the System time. Therefore, this value is unreliable.

For example, if the mobile phone is not turned on for a long time and the system time is reset to the factory time, we send a delay message and synchronize the latest time through NTP after a period of time, the delay message will become invalid

Message# at the same time the when just using the time difference to represent the relationship, so only need a relative time can achieve the purpose, it can start from the system start time, also can begin when APP launch timing, can even be periodically reset (all message minus the same value, but the complex is not necessary).

Q: Can Handler objects be created in child threads?

Handler’s no-argument constructor cannot be called directly in, because the Handler must bind a Looper object when it is created. There are two method bindings

  • Initialize a Looper on the current thread by calling looper.prepare ()
Looper.prepare(); Handler handler = new Handler(); / /... Looper.loop();Copy the code
  • Pass a Looper through the constructor
Looper looper = ..... ; Handler handler = new Handler(looper);Copy the code

Q: How is Handler associated with Looper

The previous question should have told you about one of these situations: passing parameters through a constructor.

There is also an automatic binding process when we call the no-argument constructor directly

// Handler.java:192 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 been called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code

Q: How does Looper relate to Thread

A Looper is associated with a Thread using a ThreadLocal method. See Looper#prepare() for this

// Looper.java:93 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)); }Copy the code

Looper has a static sThreadLocal field of type ThreadLocal. Looper assigns and values via its GET and set methods.

Since ThreadLocal is bound to a Thread, Looper is associated with a Thread as long as it is bound to a ThreadLocal

ThreadLocal is also a common question when asking about the Handler mechanism, but there are a lot of blogs and not a lot of source code, so I won’t introduce it here. I will write a new blog later if necessary.

Q: What are the constructor methods of Handler

If you don’t answer the question above can create Handler objects in child threads, I usually use this question as a guide.

The main purpose of this question is to ask you what parameters can be passed by constructors, not to say exactly, but once you know what parameters can be passed, you can also figure out how many constructors there are.

First of all, which types can be passed (open apis only, not counting those tagged by @hide). There are only two types: Looper and Handler$Callback, so we can back up how many public constructors there are: None, single Looper, single Callback, Looper, and Handler.

public Handler() {
    this(null, false);
}
public Handler(Callback callback) {
    this(callback, false);
}
public Handler(Looper looper) {
    this(looper, null, false);
}
public Handler(Looper looper, Callback callback) {
    this(looper, callback, false);
}
Copy the code

There is also a Boolean async, but this is not an open API, even if you don’t know personally feel completely fine.

Q: Why looper calls Handler’s dispatchMessage method

Look at the dispatchMessage method

// Handler.java:97 public void dispatchMessage(Message msg) { if (msg.callback ! = null) { handleCallback(msg); } else { if (mCallback ! = null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }}Copy the code

From the above code, you can see that there are two reasons:

  • whenmsg.callback ! = nullWhen performinghandleCallback(msg), which means that the MSG object is passedhandler#postAtTime(Runnable, long)Related methods are sent, somsg.whatandmsg.objThey’re all zero. They won’t be handed overHandler#handleMessageMethods.
  • You can see that from the last questionHandlerCan accept oneCallbackThe argument, similar to the OnTouchListener in the View, will hand the event in firstCallback#handleMessage(Message)If false is returned, the message is deliveredHandler#handleMessage(Message)Methods.

Q: How do I get the current thread’s Looper in a child thread

Looper.myLooper()
Copy the code

The internal principle is the same as sThreadLocal#get() above to get Looper

// Looper.java:203
public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}
Copy the code

Q: If you get the main thread Looper on any thread

Looper.getMainLooper()
Copy the code

This is especially useful when we’re developing the library, because you don’t know which thread your library will be initialized on when someone calls your library, so we keep the library running by specifying the main thread Looper every time we create a Handler.

Q: How do I determine if the current thread is the main thread

Knowing the above two questions, this question is easy to answer

Method one:

Looper.myLooper() == Looper.getMainLooper()
Copy the code

Method 2:

Looper.getMainLooper().getThread() == Thread.currentThread()
Copy the code

Method 3: simplified version of Method 2

Looper.getMainLooper().isCurrentThread()
Copy the code

Q: Will looper.loop () exit?

It won’t quit automatically, but you can make it quit by looperate #quit() or looperate #quitSafely().

Both methods call MessageQueue#quit(Boolean), The MessageQueue#next() method will return null to terminate the current call if it finds that MessageQueue#quit(Boolean) has already been called, otherwise it will block and wait even if the MessageQueue is already empty.

Q: MessageQueue#next will block when there is no message.

MessageQueue is woken up when another thread calls MessageQueue#enqueueMessage. This method is called by a series of message-sending methods, such as Handler#sendMessage, Handler#post, etc.

Boolean enqueueMessage(Message MSG, long when) {// omit synchronized (this) {// omit Boolean needWake; if (p == null || when == 0 || when < p.when) { // New head, wake up the event queue if blocked. msg.next = p; mMessages = msg; needWake = mBlocked; } else {// omit} if (needWake) {nativeWake(mPtr); // Wake up}} return true; }Copy the code

Q: The looper.loop () method is an infinite loop. Why doesn’t it block the APP

If the operating system is driven by interrupts, then Android applications are driven by handlers at a macro level, so Looper in the main thread will not block all the time for the following reasons:

  • In Android, there is not only one thread in a process. Due to the Handler mechanism, if we want to operate the View, we need to send events to the main thread through the Handler, so it will wake up the block.
  • Sensor events, such as touch events, keyboard input, etc.
  • Draw events: we know that the screen must have a refresh rate of 60fps for smooth display, so draw events wake up when queued.
  • There is always aMessageA steady stream of people are being addedMessageQueueIn the middle, events are a chain of events. Give me an exampleFragmentThe chestnut:
getSupportFragmentManager()
        .beginTransaction()
        .replace(android.R.id.content,new MyFragment())
        .commit();
Copy the code

Instead of displaying MyFragment immediately, we call the FragmentManager#scheduleCommit() method, where we queue the MyFragment.

// FragmentManager.java:2103 private void scheduleCommit() { synchronized (this) { boolean postponeReady = mPostponedTransactions ! = null && ! mPostponedTransactions.isEmpty(); boolean pendingReady = mPendingActions ! = null && mPendingActions.size() == 1; if (postponeReady || pendingReady) { mHost.getHandler().removeCallbacks(mExecCommit); mHost.getHandler().post(mExecCommit); // There are queue operations}}}Copy the code

Is the commit followed by a series of lifecycle event distributions? So…

Do you have any questions about Handler, let me know in the comments

Let me know if you have any Handler related questions that haven’t been addressed on this blog, and I’ll keep adding them to this blog.