Daily Q: Android messaging, I need to repeat that again!

Adhere to the original day more, fast track Android Advanced Series, please directly in the micro-channel public number Search: nanchen, and direct attention to the star, not to be missed.

My 17-year interview series , wrote an article entitled: Android interview (V): Exploring Handler Android's article is mainly about Handlerthe principles of the relevant interview questions, and then simply give some conclusions. I did not expect the past two years, I opened the interview series replica of the Day asked the topic, but this time the comeback, only to be ascertained by source details we usually ignore the possibility.

Our daily development, always inevitably will be used Handler, although the Handlermechanism is not the same mechanism of Android news, but the Handlermessage mechanism in Android development has long been familiar with the heart, is very important!

In this article, you can very easily get answers to questions about:

  1. Handler, Looper, MessageAnd MessageQueuethe relationship between them and the principles in the end is what?
  2. MessageQueue What storage structure?
  3. Why must call the child thread Looper.prepare()and Looper.loop()?

Handler's simple to use

I believe that no one should not use Handlerit? Assuming Activitya time-consuming task processing needs to be updated UI, a brief look at how we usually are handled.

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_main3)
    // 请求网络
    subThread.start()
}

override fun onDestroy() {
    subThread.interrupt()
    super.onDestroy()
}

private val handler by lazy(LazyThreadSafetyMode.NONE) { MyHandler() }
private val subThread by lazy(LazyThreadSafetyMode.NONE) { SubThread(handler) }

private class MyHandler : Handler() {
    override fun handleMessage(msg: Message) {
        super.handleMessage(msg)
        // 主线程处理逻辑,一般这里需要使用弱引用持有 Activity 实例,以免内存泄漏
    }
}

private class SubThread(val handler: Handler) : Thread() {
    override fun run() {
        super.run()
        // 耗时操作 比如做网络请求

        // 网络请求完毕,咱们就得哗哗哗通知 UI 刷新了,直接直接考虑 Handler 处理,其他方案暂时不做考虑
        // 第一种方法,一般这个 data 是请求结果解析的内容
        handler.obtainMessage(1,data).sendToTarget()
        // 第二种方法
        val message = Message.obtain() // 尽量使用 Message.obtain() 初始化
        message.what = 1
        message.obj = data // 一般这个 data 是请求结果解析的内容
        handler.sendMessage(message)
        // 第三种方法
        handler.post(object : Thread() {
            override fun run() {
                super.run()
                // 处理更新操作
            }
        })
    }
}

The code is very simple, because the network request is a time-consuming task, so we opened a new thread, and parsed completely by the end of the network request Handlerto notify the main thread to update the UI, using a simple three ways, attentive junior partner You may find, in fact, the first and second method is the same. Is the use of Handlersending a content carrying Messagean object, it is worth mentioning: we should use as much as possible Message.obtain () rather than a new Message () conducted Message initialization, mainly Message.obtain () can reduce the memory Application.

Subject suggest that you made in the previous article, we try to be less attached to some source, and we can direct easily find all of the above methods will eventually call this method:

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

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

The above code there is a MessageQueue, and end up calling a MessageQueue#enqueueMessagemethod of the message into the team, we have briefly about MessageQueuethe basic situation.

MessageQueue

As the name suggests, MessageQueueit is the message queue, i.e., a plurality of message storage Messagecontainer, it uses a one-way linked list data structure, instead of the queue. Next Message element its next () points to the list.

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) {
            // New head, wake up the event queue if blocked.
            msg.next = p;
            mMessages = msg;
            needWake = mBlocked;
        } else {
            // Inserted within the middle of the queue.  Usually we don't have to wake
            // up the event queue unless there is a barrier at the head of the queue
            // and the message is the earliest asynchronous message in the queue.
            needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            for (;;) {
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

Team news from the enqueueMessage()implementation point of view, its main operations actually a single list of insertion, do not do too much to explain here, we should probably be more concerned about its method of operation of the team next():

Message next() {
    // ...
    int nextPollTimeoutMillis = 0;
    for (;;) {
        // ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        synchronized (this) {
            // Try to retrieve the next message.  Return if found.
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // Got a message.
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                // No more messages.
                nextPollTimeoutMillis = -1;
            }
            //...
        }
        //...
        // While calling an idle handler, a new message could have been delivered
        // so go back and look again for a pending message without waiting.
        nextPollTimeoutMillis = 0;
    }
}

next()The method is very long, but we just put up a very small part, can be seen, but there is a for (;;)infinite loop, interior loop body is called a nativePollOnce(long, int)method. This is a Native method, the practical effect is by Native layer of MessageQueuethe current stack thread calls the blocking nextPollTimeoutMillistime milliseconds.

The following are nextPollTimeoutMillisblocking performance values of different situations:

  1. Less than 0, block until being awakened;
  2. Equal to 0, it will not block;
  3. Is greater than 0, the longest blocking nextPollTimeoutMillismilliseconds, such as wake-up period immediately returns.

Can be seen, the beginning nextPollTimeoutMillisof initialization values are 0, so it will not clog, directly pick up Messagethe object, if not get to Messagethe object data, will directly nextPollTimeoutMillisset to -1, then less than 0 satisfying the condition, is has been blocks until the rest of Native call another method nativeWake(long)to wake up. If we take it to a value, obtained directly to Messageobjects returned.

It turned out that nativeWake(long)the method in the previous MessageQueue#enqueueMessagemethod has a call, the call timing is in the MessageQueueprocess into the team message.

Now that you know: Handlersend a Messagemessage with the MessageQueuestorage, use MessageQueue#enqueueMessagemethods into the team, using the MessageQueue#nextmethod in rotation messages. This can not help but throw the question MessageQueue#nextis to whom to call? Yes, that is Looper.

Looper

LooperPlayed in the Android messaging in the role of the news cycle, specifically, it is that it will be kept from MessageQueueby next()to see if there are new messages, if there are new messages processed immediately, otherwise let the MessageQueueobstruction there.

We direct look at Looperthe most important way: 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.");
    }
    // ...

    for (;;) {
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }

        //...
        try {
            // 分发消息给 handler 处理
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } finally {
            // ...
        }
        // ...
    }
}

The method eliminates the need for a lot of code, leaving only the core logic. It can be seen through the first will myLooper()get the method Looperobject, if the Looperreturn is empty, then direct throw. Otherwise go into a for (;;)loop, call the MessageQueue#next()method in rotation to obtain Messagethe object, if the acquired Messageobject is empty, then exit loop()method. Otherwise directly through msg.targetto get the Handlerobject and call Handler#dispatchMessage()methods.

Let's take a look at the Handler#dispatchMessage()ways:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

Code is relatively simple, if the Messageset callbackis called directly message.callback.run(), or determine whether to initialize the `m

Let's look at myLooper()methods:

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

To see sThreadLocalwhat is:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

This ThreadLocalwhat is it?

ThreadLocal

About ThreadLocalWe take direct Yanzhen Jie article content.
See ThreadLocalthe first impression is a thread about the class and, indeed, but be aware it is not a thread, or it should call LocalThreadup.

ThreadLocalIs used to specify the data storage thread when the scope is some data that can be used when all the specified thread and the thread execution through the data needs ThreadnLocalto store data, as a thread using ThreadnLocalthe stored data, only the thread can read the stored data, apart from other threads except for a thread is no way to read the data.

Some readers read the above paragraph should still do not understand ThreadLocalthe role we give chestnuts:

ThreadLocal<Boolean> local = new ThreadLocal<>();
// 设置初始值为true.
local.set(true);

Boolean bool = local.get();
Logger.i("MainThread读取的值为:" + bool);

new Thread() {
    @Override
    public void run() {
        Boolean bool = local.get();
        Logger.i("SubThread读取的值为:" + bool);

        // 设置值为false.
        local.set(false);
    }
}.start():

// 主线程睡1秒,确保上方子线程执行完毕再执行下面的代码。
Thread.sleep(1000);

Boolean newBool = local.get();
Logger.i("MainThread读取的新值为:" + newBool);

Code is nothing to say it, print out the log, you will see something like this:

MainThread读取的值为:true
SubThread读取的值为:null
MainThread读取的值为:true

Log first doubt, because the set value true, since the print result to say nothing. Log For the second, according to the description above, the use of a thread ThreadLocalstored data, the thread can be read, thus the second result is Log: null. Then the sub-thread is provided in ThreadLocalvalue false, then the third Log to be printed, the principle above, the child thread set ThreadLocalvalue does not affect the data of the main thread, so printing is the true.

The results confirmed: Even with a ThreadLocal objects, either of its threads set () and get () methods of operation are independent of each other affect each other's.

Looper.myLooper()

We returned Looper.myLooper():

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

We look at where to sThreadLocaloperate.

public static void prepare() {
    prepare(true);
}

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

So we know it, which is used in the child thread Handlerbefore, must call Looper.prepare()reason.

Maybe you will doubt me when the main thread to use, does not require Looper.prepare()it.

It turned out that we are in ActivityThread, there are going to show the call Looper.prepareMainLooper():

 public static void main(String[] args) {
        // ...
        Looper.prepareMainLooper();
        // ...
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        //...
        Looper.loop();
        // ...
    }

We take a look at Looper.prepareMainLooper():

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

This reference to:
"Android developers artistic exploration"
Android messaging and applications

Guess you like

Origin www.cnblogs.com/liushilin/p/11234107.html