Interviewer: what is the general principle of the Handler mechanism?

Posted by paolo on Tue, 04 Jan 2022 04:51:19 +0100

preface

This article is not written to analyze how the Handler is used. The purpose is to look at the evolution of the Handler from a design point of view and why there are four classes: looper, messagequeue, Handler and message.

I The nature of thread communication?

The main difference between threads and processes is that threads share memory. In android system, objects in the heap can be accessed by all threads. Therefore, regardless of the thread communication mode, considering the performance problem, an object holding the other thread will be selected to realize the 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;
    }

It can be seen from the usage that AsyncTask also indirectly sends messages from the current thread to the thread corresponding to the Looper through the handler mechanism. If it does not send messages, the Looper of the main thread is selected by default.

1.2 Handler

Get the Looper of thread with ThreadLocal and transmit message for communication. In essence, it is also a Looper object that holds the object thread.

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);
    }  

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;
}

getRunQueue().post(action) simply caches the Runnable into the array without attachToWindow

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++;
        }
    }

Wait until attachToWindow, so it is essentially a handler mechanism for communication.

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

1.4 runOnUiThread

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

Communicate by obtaining the handler of the UIThread.

From the above analysis, it can be seen that the four common communication modes of android system essentially communicate through Handler technology.

II What problem does handler solve?

handler solves the problem of thread communication and thread switching. In essence, it is shared memory and sends messages by holding loopers of other threads.

The Handler technology we often mention usually includes the following four parts

  • Handler
  • Looper
  • MessageQueue
  • Message

III From the perspective of architecture evolution, Handler

3.1 original 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();

3.2 structured data support

In order to send structured data, Message is designed

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();

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();

The thread is blocked through an infinite for loop, and the corresponding in the Handler is Looper.

3.4 thread switching support

The above methods can only accept changes in thread1, but cannot notify thread. Therefore, the Handler is designed, and the method of sending and receiving messages is encapsulated

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();

3.5 support for thread 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();

Add message queue to cache messages, and the processing threads consume them in order. 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 the Handler. Both sides of the communication thread must be able to easily obtain the same Handler.

At the same time, considering the convenience of using threads, we can't restrict the Handler to declare in a fixed place. How wonderful it would be if we could easily get the message queue of the corresponding thread and insert our messages into it.

So Looper and ThreadLocal come on stage.

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

//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();
}

//Loop writes to 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));
}

// Queue abstraction
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

//Handler get 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;
}

3.7 google's helpless compromise on Handler

Think about a problem. Since the Handler can be defined anywhere, sendMessage to the corresponding thread can be executed through the Looper--MessageQueue corresponding to the thread. How can the corresponding Handler be found to handle handleMessage? There is no good way to directly retrieve the Handler corresponding to each message

Two solutions

  • Through the public bus, such as defining map < message, handler > to index, this method requires that the map must be defined where all threads can easily obtain it, such as static
  • Carry the attribute target to the corresponding thread through the Message. After the Message is consumed, you can obtain the Handler through the Message

The problem of the first method is obvious. The public bus needs to maintain its life cycle manually. google adopts the second method.

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);
}

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

Since Message holds the reference of Handler, when we define Handler in the form of internal class, the holding chain is

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

The Thread with long life cycle holds the Activity with short life cycle

Solution: use the static inner class to define the handler. The static inner class does not hold the reference of the external class, so using the static handler will not lead to the disclosure of activity.

IV summary

  • 1. Thread communication is essentially realized through shared memory
  • 2. The four communication modes commonly used in Android system are actually implemented by Handler
  • 3. The handler mechanism consists of four parts: handler, messagequeue, message and looper. It is the result of architecture evolution.
  • 4. The essence of handler disclosure is that Thead, an object with a long life cycle, indirectly holds an object with a short life cycle.

    ####Relevant video recommendations:

[2021 latest version] Android studio installation tutorial + Android zero foundation tutorial video (suitable for Android 0 foundation and introduction to Android) including audio and video bilibili bili

[advanced Android tutorial] - Handler source code analysis for Framework interview

Android advanced system learning -- Gradle introduction and project practice_ Beep beep beep_ bilibili

Android architecture design principle and practice -- Jetpack combines MVP combined application to develop an excellent APP_ Beep beep beep_ bilibili

This article is transferred from https://juejin.cn/post/7045473726929829918 , in case of infringement, please contact to delete.

Topics: Android Programmer Framework