• preface

Android development partners for Handler must not be unfamiliar, the basic interview must ask things, but many people are rote do not understand the principle, so the interview is easy to lose points, so this article will briefly take you to understand the source Hander implementation. Because it is a brief analysis, so some things will not be too detailed, but will bring everyone to the source code through.

  • In the code

public class HandlerActivity extends AppCompatActivity {

    private Handler mHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.d("lkx", (String) msg.obj); }};@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_handler);
    }

    /** * Click send message */
    public void onClick(View view) {
        new Thread(() -> {
            Message message = Message.obtain();
            message.obj = "Study hard."; mHandler.sendMessage(message); }).start(); }}Copy the code

This should be everyone usually write to vomit the code bar, mainly from the child thread to send a message to the main thread process.

  • Message to queue (source code parsing)

Create a message and assign the parameter obj, then call sendMessage with the message

new Thread(() -> {
    Message message = Message.obtain();
    message.obj = "Study hard.";
    mHandler.sendMessage(message);
}).start();
Copy the code
  • Handler#sendMessage

SendMessage calls sendMessageDelayed

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
Copy the code
  • Handler#sendMessageDelayed

SendMessageDelayed called sendMessageAtTime

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
Copy the code
  • Handler#sendMessageAtTime

SendMessageAtTime calls enqueueMessage, and there’s mQueue, which is the message queue, but I’ll explain that.

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue; // Message queue
    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);
}
Copy the code
  • Handler#enqueueMessage

EnqueueMessage calls queue.enqueuemessage () and passes the message in

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}
Copy the code
  • Message to queue (summary)

What is an mQueue? The mQueue should first meet with the sendMessageAtTime method:

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) { MessageQueue queue = mQueue; .return enqueueMessage(queue, msg, uptimeMillis);
}
Copy the code

It looks like a member variable, so let’s go to a member variable and look for it:

public class Handler {...finalMessageQueue mQueue; . }Copy the code

Find out where the value is assigned:

public Handler(@Nullable Callback callback, boolean async) {... mLooper = Looper.myLooper(); . mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code

Call looper.mylooper () to get mLooper, and then call mlooper.mqueue, which is a member variable of Looper.

In our current analysis of the source code, we only know that we put the Message in the MessageQueue:

  • Looper source parsing

Looper first meets in the Handler constructor with two arguments, but Looper is not created here, so where is it created? Everything starts with Pangu opening heaven and earth:

After our program starts, we go through a series of startup processes that will eventually call our activityThread.java class, which has a main() method in it.

  • ActivityThread#main

PrepareMainLooper The ActivityThread’s main() first calls looper.prepareMainLooper, initializing Looper

public static void main(String[] args) {... Looper.prepareMainLooper(); . Looper.loop(); }Copy the code
  • Looper#prepareMainLooper

Prepare is called with false passed

public static void prepareMainLooper(a) {
    prepare(false);
    synchronized (Looper.class) {
        if(sMainLooper ! =null) {
            throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); }}Copy the code
  • Looper#prepare

So here we have a new Looper, and we put it inside a ThreadLocal

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#Looper

The Looper constructor initializes a MessageQueue

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
Copy the code
  • What is a ThreadLocal?

ThreadLocal source code is still more complex, so I will write a special article to explain, we understand: the same ThreadLocal in different threads set and get can achieve data isolation, between the threads do not affect each other. If thread A stores A 1 and thread B stores A 2, then thread A gets A value of 1 and thread B gets A value of 2

public class HelloThreadLocal {
    public static void main(String[] args) {
        ThreadLocal<Integer> threadLocal = new ThreadLocal<>();

        new Thread(() -> {
            threadLocal.set(1);
            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
        },"Thread A").start();

        new Thread(() -> {
            threadLocal.set(2);
            System.out.println(Thread.currentThread().getName()+":"+threadLocal.get());
        },"Thread B").start(); }} thread A:1Thread B:2
Copy the code
  • Back to Looper# prepare

If the value of a ThreadLocal is not null, an exception will be thrown. Otherwise, a Looper will be stored in the current thread. This ensures that we only have one Looper per thread at most.

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
  • Back to ActivityThread# main

PrepareMainLooper () creates a Looper object and stores it in ThreadLocal. Now we see that main() also executes looper.loop ().

public static void main(String[] args) {... Looper.prepareMainLooper(); . Looper.loop(); }Copy the code
  • Looper#loop

The loop method has a lot of code in it, so here’s what’s left:

  1. Get our Looper object me from the ThreadLocal by myLooper()
  2. Get Looper message queue from me
  3. Create an infinite loop to retrieve data from the queue
  4. If the data is null then return it. If the data is null then call the Handler’s dispatchMessage method
public static void loop(a) {
    finalLooper me = myLooper(); .finalMessageQueue queue = me.mQueue; .for (;;) {
        Message msg = queue.next();
        if (msg == null) {
            return; }... msg.target.dispatchMessage(msg);// If the message is not equal to null, it will go here}... }Copy the code
  • Handler#dispatchMessage

If the message queue loop is not equal to null, then it goes through this method and calls handleMessage(MSG)

public void dispatchMessage(@NonNull Message msg) {
    if(msg.callback ! =null) {
        handleCallback(msg);
    } else {
        if(mCallback ! =null) {
            if (mCallback.handleMessage(msg)) {
                return; } } handleMessage(msg); }}Copy the code
  • Handler#handleMessage

This method is an empty implementation and is ultimately implemented by a user-defined subclass of Handler.

public void handleMessage(@NonNull Message msg) {}Copy the code
  • Self defined Handler#handleMessage

Calls back to our own Handler’s handleMessage method and returns a Message

private Handler mHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d("lkx", (String) msg.obj); }};Copy the code

Maybe the process is working…

  • conclusion

The core source code of Handler has been briefly analyzed above. Many people may not be able to string this process together, so let’s summarize it briefly:

  1. ActivityThread mian() is performed when the user opens the APP.
  2. The main() method calls looper.prepareMainLooper () to create a Looper to store in the ThreadLocal.
  3. Looper.loop() is called after the main() method has created a Looper.
  4. The loop method creates an infinite loop to retrieve messages from the MessageQueue.
  5. We call back to our handler.handleMessage () method if we get a message.
  6. The message loop mechanism above the OOOOOOOOO has started to perform ooOOOOOOOOOOOO
  7. Now the user creates a message and calls handler.sendMessage (Message).
  8. This will eventually be called to queue.enqueuemessage (), in the Message method MessageQueue.
  9. If there is a message on the message queue, step 5 above is performed and a complete closed loop is formed.

  • Frequent meeting questions

  • How many handlers can a thread have?

A thread can have multiple handlers.

  • How many Loopers can a thread have? How is that guaranteed?

A thread can only have one Looper. When the thread is created, looper.prepare () is called to initialize it. It creates a new Looper object and puts it in the ThreadLocal. Looper is pulled from ThreadLocal first, and if it is not null, an exception is thrown. This ensures that only one Looper is allowed per thread.

  • Can handlers be used in child threads?

The child thread can use Handler, but we need to create Looper ourselves, so we need to call looper.prepare () to initialize, and then call looper.loop () to loop.

  • How to create Message better?

We can create it directly using new Message(), but this is not good. Instead, we recommend using message.obtain () to reuse messages, reduce frequent object creation, and avoid memory jitter.

  • Does Handler cause memory leaks? Why is that?

Handler can cause memory leaks. If you send a delayed Message and destroy the Activity before the delay is over, the Activity cannot be reclaimed because the Message holds a reference to Handler, which is an inner class that by default holds a reference to an outer class. Memory leaks can occur.

  • How to solve Handler memory leak?

We can change the Handler to a static inner class, and then use weak references to hold references to activities. When GC collects objects, it will collect weak references directly, thus avoiding memory leaks.