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 Handler
the 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 Handler
mechanism is not the same mechanism of Android news, but the Handler
message 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:
Handler
,Looper
,Message
AndMessageQueue
the relationship between them and the principles in the end is what?MessageQueue
What storage structure?- Why must call the child thread
Looper.prepare()
andLooper.loop()
?
Handler's simple to use
I believe that no one should not use Handler
it? Assuming Activity
a 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 Handler
to 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 Handler
sending a content carrying Message
an 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#enqueueMessage
method of the message into the team, we have briefly about MessageQueue
the basic situation.
MessageQueue
As the name suggests, MessageQueue
it is the message queue, i.e., a plurality of message storage Message
container, 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 MessageQueue
the current stack thread calls the blocking nextPollTimeoutMillis
time milliseconds.
The following are nextPollTimeoutMillis
blocking performance values of different situations:
- Less than 0, block until being awakened;
- Equal to 0, it will not block;
- Is greater than 0, the longest blocking
nextPollTimeoutMillis
milliseconds, such as wake-up period immediately returns.
Can be seen, the beginning nextPollTimeoutMillis
of initialization values are 0, so it will not clog, directly pick up Message
the object, if not get to Message
the object data, will directly nextPollTimeoutMillis
set 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 Message
objects returned.
It turned out that nativeWake(long)
the method in the previous MessageQueue#enqueueMessage
method has a call, the call timing is in the MessageQueue
process into the team message.
Now that you know: Handler
send a Message
message with the MessageQueue
storage, use MessageQueue#enqueueMessage
methods into the team, using the MessageQueue#next
method in rotation messages. This can not help but throw the question MessageQueue#next
is to whom to call? Yes, that is Looper
.
Looper
Looper
Played in the Android messaging in the role of the news cycle, specifically, it is that it will be kept from MessageQueue
by next()
to see if there are new messages, if there are new messages processed immediately, otherwise let the MessageQueue
obstruction there.
We direct look at Looper
the 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 Looper
object, if the Looper
return is empty, then direct throw. Otherwise go into a for (;;)
loop, call the MessageQueue#next()
method in rotation to obtain Message
the object, if the acquired Message
object is empty, then exit loop()
method. Otherwise directly through msg.target
to get the Handler
object 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 Message
set callback
is 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 sThreadLocal
what is:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
This ThreadLocal
what is it?
ThreadLocal
About ThreadLocal
We take direct Yanzhen Jie article content.
See ThreadLocal
the first impression is a thread about the class and, indeed, but be aware it is not a thread, or it should call LocalThread
up.
ThreadLocal
Is 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 ThreadnLocal
to store data, as a thread using ThreadnLocal
the 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 ThreadLocal
the 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 ThreadLocal
stored data, the thread can be read, thus the second result is Log: null
. Then the sub-thread is provided in ThreadLocal
value false
, then the third Log to be printed, the principle above, the child thread set ThreadLocal
value 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 sThreadLocal
operate.
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 Handler
before, 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