preface

Writing this article is not in order to analysis how to use Handler, the purpose is to want to from the point of view of design evolution process of the Handler, and why stars, MessageQueue, Handler, the Message the four classes.

The nature of thread communication?

The main difference between threads and processes is that they share memory. On Android, objects in the heap can be accessed by all threads. Therefore, no matter what kind of thread communication method, for the sake of performance, you must choose some object that holds the other thread to achieve communication.

1.1 AsyncTask

public AsyncTask(@Nullable Looper callbackLooper) { mHandler = callbackLooper == null || callbackLooper == Looper.getMainLooper() ? getMainHandler() : new Handler(callbackLooper); mWorker = new WorkerRunnable<Params, Result>() { public Result call() throws Exception { mTaskInvoked.set(true); Result result = null; try { Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); //noinspection unchecked result = doInBackground(mParams); Binder.flushPendingCommands(); } catch (Throwable tr) { mCancelled.set(true); throw tr; } finally { postResult(result); } return result; }}; mFuture = new FutureTask<Result>(mWorker) { @Override protected void done() { try { postResultIfNotInvoked(get()); } catch (InterruptedException e) { android.util.Log.w(LOG_TAG, e); } catch (ExecutionException e) { throw new RuntimeException("An error occurred while executing doInBackground()", e.getCause()); } catch (CancellationException e) { postResultIfNotInvoked(null); }}}; } private Result postResult(Result result) { @SuppressWarnings("unchecked") Message message = getHandler().obtainMessage(MESSAGE_POST_RESULT, new AsyncTaskResult<Result>(this, result)); message.sendToTarget(); return result; }Copy the code

AsyncTask sends messages from the current thread to the corresponding thread of Looper through handler mechanism. If not, the default is the main thread Looper.

1.2 Handler

ThreadLocal retrieves a thread’s Looper and transmits a message to communicate. Is essentially a Looper object that holds a thread of objects.

public Handler(@Nullable 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 " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }


public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
    
  public boolean sendMessageAtTime(@NonNull 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);
    }  
Copy the code

1.3 View.post(Runnable)

public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo ! = null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; }Copy the code

GetRunQueue ().post(action) simply caches Runnable into the array until no attachToWindow has been attached

private HandlerAction[] mActions; public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; }}Copy the code

Wait until attachToWindow, so it’s essentially a handler mechanism to communicate.

void dispatchAttachedToWindow(AttachInfo info, int visibility) { mAttachInfo = info; . // Transfer all pending runnables. if (mRunQueue ! = null) { mRunQueue.executeActions(info.mHandler); mRunQueue = null; }... }Copy the code

1.4 runOnUiThread

public final void runOnUiThread(Runnable action) {
        if (Thread.currentThread() != mUiThread) {
            mHandler.post(action);
        } else {
            action.run();
        }
    }
Copy the code

Communicate by getting a handler for UIThread.

As can be seen from the above analysis, the four common communication modes of Android system are all through the Handler technology in essence.

What problem does handler solve?

Handler handles thread communication and thread switching. Essentially shared memory, messages are sent by loopers that hold other threads.

The Handler technology we often refer to consists of the following four parts

  • Handler
  • Looper
  • MessageQueue
  • Message

Handler from the perspective of architecture evolution

3.1 Primitive thread communication

String msg = "hello world"; Thread thread = new Thread(){ @Override public void run() { super.run(); System.out.println(msg); }}; thread.start(); Thread thread1 = new Thread(){ @Override public void run() { super.run(); System.out.println(msg); }}; thread1.start();Copy the code

3.2 Structured data support

Message is designed to send structured data

Message msg = new Message(); Thread thread = new Thread(){ @Override public void run() { super.run(); msg.content = "hello"; System.out.println(msg); }}; thread.start(); Thread thread1 = new Thread(){ @Override public void run() { super.run(); System.out.println(msg); }}; thread1.start();Copy the code

3.3 Continuous communication support

Message msg = new Message(); Thread thread = new Thread(){ @Override public void run() { for (;;) { msg.content = "hello"; }}}; thread.start(); Thread thread1 = new Thread(){ @Override public void run() { super.run(); for (;;) { System.out.println(msg.content); }}}; thread1.start();Copy the code

The thread is blocked by an infinite for loop, which corresponds to a Looper in a Handler.

3.4 Thread switching

In each of the above methods, thread1 can only accept the change without notifying the thread. Therefore, a Handler was designed to encapsulate the methods for sending and receiving messages.

class Message{ String content = "123"; String from = "hch"; } abstract class Handler{ public void sendMessage(Message message){ handleMessage(message); } public abstract void handleMessage(Message message); } Message msg = new Message(); Thread thread = new Thread(){ @Override public void run() { for (;;) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } msg.content = "hello"; if (handler ! = null){ handler.sendMessage(msg); }}}}; thread.start(); Thread thread1 = new Thread(){ @Override public void run() { super.run(); handler = new Handler(){ @Override public void handleMessage(Message message) { System.out.println(message.content); }}; }}; thread1.start();Copy the code

3.5 Support for threaded message throughput

abstract class Handler{ BlockingDeque<Message> messageQueue = new LinkedBlockingDeque<>(); public void sendMessage(Message message){ messageQueue.add(message); } public abstract void handleMessage(Message message); }... Thread thread1 = new Thread(){ @Override public void run() { super.run(); handler = new Handler(){ @Override public void handleMessage(Message message) { if (! handler.messageQueue.isEmpty()){ System.out.println(messageQueue.pollFirst().content); }}}; }}; thread1.start();Copy the code

Add MessageQueue MessageQueue to cache messages and process threads to consume them sequentially. Form a typical producer – consumer model.

3.6 Support for multithreading

The biggest inconvenience of the above model lies in the declaration and use of Handler. Both communication threads must be able to easily obtain the same Handler.

And for the convenience of using threads, we can’t restrict handlers to declaring in one place. It would be nice if we could easily get to the message queue of the corresponding thread and stuff it with our messages.

Hence Looper and ThreadLocal.

  • Looper abstracts the infinite loop and moves the MessageQueue from Handler to Looper.
  • ThreadLocal binds Looper to Thread through ThreadLocalMap to ensure that any Thread can obtain the corresponding Looper object, and then obtain the key MessageQueue required by Thread.

//ThreadLocal get Looper public T get() {Thread T = thread.currentThread (); ThreadLocalMap map = getMap(t); if (map ! = null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e ! = null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); } 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)); } private Looper(Boolean quitAllowed) {mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } //Handler gets Looper public Handler(@nullable 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 " + Thread.currentThread() + " that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }Copy the code

3.7 Google’s helpless compromise with Handler

Since handlers can be defined anywhere, sendMessage to the corresponding thread can be handled by the thread’s corresponding Looper–MessageQueue. How can handleMessage be handled by the corresponding Handler? There is no good way to directly retrieve the Handler for each message

Two solutions

  • Using the public bus, such as defining Map

    to index, this method requires that the Map be defined to be accessible to all threads, such as static
    ,handler>
  • The Message carries the target attribute to the thread via Message. After the Message is consumed, the Handler can be obtained via Message.

The problem with the first approach is obvious. The public bus requires manual maintenance of its life cycle. Google takes the second approach.

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

3.8. Compromise is the root cause of the Handler leak problem

Since Message holds a reference to Handler, when we define Handler as an inner class, the holding chain is

Thread->MessageQueue->Message->Handler->Activity/Fragment

Long-life Threads hold short-life activities.

Workaround: Use static inner classes to define handlers. Static inner classes do not hold references to external classes, so using static handlers does not leak activities.

4. To summarize

  • 1. Thread communication is essentially implemented through shared memory
  • 2. The four communication modes commonly used by the Android system are actually implemented by Handler
  • 3. The Handler mechanism consists of four parts Handler, MessageQueue, Message, stars, it is the result of the evolution of architecture.
  • The nature of the Handler leak is that the long-life object Thead indirectly holds the short-life object.