文章目錄
Handler是消息機制的上層接口,開發中基本只用和Handler交互即可。Handler可以將一個任務切換到Handler指定的線程中執行。如在用Handler在子線程更新UI。
Android消息機制主要就是Handler的運行機制。Handler的運行還依賴MessageQueue、Looper,及Looper內部使用到的ThreadLocal。
MessageQueue是消息隊列,用於存放Handler發送的消息,實際是單鏈表的結構。
Looper會在消息隊列中無限循環的查找消息,有消息就取出,沒有就等待。
ThreadLocal本質作用是在每個線程中存儲數據。在Looper中的作用就是給每個線程存Looper實例。因爲我們知道,創建Handler時是需要線程的Looper實例的,而非UI線程默認是沒有Looper的。
一、Handler使用與概述
1.1使用步驟
1. 在任務執行的線程,使用Looper.prepare()來給線程創建Looper實例。
2. 在任務執行的線程,創建Handler實例。
3. 在任務執行的線程,使用Looper.loop()開啓消息循環。
4. 在任務發出的線程,使用Handler實例發送消息。
舉個例子🌰
如下所示,點擊按鈕,在主線程發送消息,就會在子線程執行。
(這個例子爲了完整展示使用步驟,所以在子線程創建了handler,在主線程發送和消息。通常實際我們使用是在主線程創建handler,在子線程發送消息然後再主線程執行UI的更新,而主線程默認是有Looper並開啓的,所以一般不需要第一步和第三部。)
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
testHandler();
}
private void testHandler() {
new Thread(new Runnable() {
@Override
public void run() {
//1、準備looper,即threadLocal<Looper>.set(new Looper())
Looper.prepare();
//2、創建handler實例
// 這個重寫了handleMessage,handler是屬於Handler的子類的實例
mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i(TAG, "child thread, handleMessage: what="+msg.what);
}
};
//3、looper啓動,sThreadLocal.get()拿到looper,拿到queue,開始queue.next
Looper.loop();
}
}).start();
}
public void onClick(){
//4.2、handler.sendMessage發送消息,queue.enqueueMessage(msg),即消息入隊列。
Log.i(TAG, "main thread, sendMessage");
Message message = Message.obtain();
message.what = 100;
mHandler.sendMessage(message);
}
1.2Handler的使用背景
Handler可以將子線程中更新UI的任務切換到主線程。爲什麼要切換呢?我們知道,UI的訪問只能在主線程進行。子線程訪問UI就會出現異常,因爲在ViewRootImpl中對線程做了校驗,只有創建了這個View樹的線程,才能訪問這個view。 一般情況創建View的線程就是主線程,即UI線程,所以子線程訪問會異常。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
而且,UI線程一般不能做耗時操作,不然會發生ANR。所以當 在子線程做完耗時操作後 又需要更新UI,這時就需要用到Handler了。那爲啥一定要用checkThread()保證不讓子線程訪問UI呢? 因爲UI控件不是線程安全的。那爲啥不加鎖呢?一是加鎖會讓UI訪問變得複雜;二是加鎖會降低UI訪問效率,會阻塞一些線程訪問UI。所以乾脆使用單線程模型處理UI操作,使用時用Handler切換即可。
二、Android消息機制分析
前面說了,Android消息機制包含幾個概念:Handler、MessageQueue、Looper、Looper內部使用到的ThreadLocal。下面詳細介紹下。
2.1 ThreadLocal
外界想要在不同thread中存值,就可以threadLocal = new ThreadLocal,然後在不同線程中threadLocal.set(value)就可以了,獲取值用threadLocal.get() 。
舉個例子🌰,下面例子中 先只看booleanThreadLocal,在主線程設置true,a線程設置false,b線程設置null,然後每個線程都打印 booleanThreadLocal.get()的結果,發現每個線程get的值是不同的,是在每個線程中set的值。這就是神奇之處,同樣的booleanThreadLocal.get(),所在線程不同,結果就不同。
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, "testThreadLocal: a thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, "testThreadLocal: b thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
結果:
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
下面看下ThreadLocal的get()、set()方法。
/**
* Returns the value in the current thread's copy of this
* thread-local variable. If the variable has no value for the
* current thread, it is first initialized to the value returned
* by an invocation of the {@link #initialValue} method.
*
* @return the current thread's value of this thread-local
*/
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();
}
/**
* Variant of set() to establish initialValue. Used instead
* of set() in case user has overridden the set() method.
*
* @return the initial value
*/
private T setInitialValue() {
T value = initialValue();
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
return value;
}
get():獲取當前線程的ThreadLocalMap,這裏可以先理解成普通 鍵值對的Map。然後傳入threadLocal實例,獲取鍵值對Entry,然後獲取Entry的value。如果map爲空或value爲空則會初始化map、value。
/**
* Sets the current thread's copy of this thread-local variable
* to the specified value. Most subclasses will have no need to
* override this method, relying solely on the {@link #initialValue}
* method to set the values of thread-locals.
*
* @param value the value to be stored in the current thread's copy of
* this thread-local.
*/
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
/**
* Create the map associated with a ThreadLocal. Overridden in
* InheritableThreadLocal.
*
* @param t the current thread
* @param firstValue value for the initial entry of the map
*/
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
set()中也是獲取當前線程的ThreadLocalMap,然後ThreadLocal實例作爲key, 和value一起設置給map。沒有map就去創建並把value初始化進去。
我們再去看下Thread,有個默認爲空的ThreadLocalMap實例threadLocals。
/* ThreadLocal values pertaining to this thread. This map is maintained
* by the ThreadLocal class. */
ThreadLocal.ThreadLocalMap threadLocals = null;
那ThreadLocalMap是啥呢?ThreadLocalMap是ThreadLocal的內部類,作用類似Map,內部有個Entry[]的屬性table。所以上面看的get、set方法就是對ThreadLocalMap的Entry[]取和存
。下面詳細看下。
/**
* Construct a new map initially containing (firstKey, firstValue).
* ThreadLocalMaps are constructed lazily, so we only create
* one when we have at least one entry to put in it.
*/
ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
table = new Entry[INITIAL_CAPACITY];
int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
table[i] = new Entry(firstKey, firstValue);
size = 1;
setThreshold(INITIAL_CAPACITY);
}
/**
* Get the entry associated with key. This method
* itself handles only the fast path: a direct hit of existing
* key. It otherwise relays to getEntryAfterMiss. This is
* designed to maximize performance for direct hits, in part
* by making this method readily inlinable.
*
* @param key the thread local object
* @return the entry associated with key, or null if no such
*/
private Entry getEntry(ThreadLocal<?> key) {
int i = key.threadLocalHashCode & (table.length - 1);
Entry e = table[i];
if (e != null && e.get() == key)
return e;
else
return getEntryAfterMiss(key, i, e);
}
/**
* Set the value associated with key.
*
* @param key the thread local object
* @param value the value to be set
*/
private void set(ThreadLocal<?> key, Object value) {
// We don't use a fast path as with get() because it is at
// least as common to use set() to create new entries as
// it is to replace existing ones, in which case, a fast
// path would fail more often than not.
Entry[] tab = table;
int len = tab.length;
int i = key.threadLocalHashCode & (len-1);
for (Entry e = tab[i];
e != null;
e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get();
if (k == key) {
e.value = value;
return;
}
if (k == null) {
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
使用Entry[] 存多個threadLocal-value鍵值對,數組下標index與是ThreadLocal 實例的hashCode相關。而ThreadLocalMap唯一實例是createMap(Thread t, T firstValue)賦給Thread的變量threadLocals。
例如 線程A threadLocalMap的table[] 可以存儲 int、String、boolean類型的3個鍵值對threadLocal-int, threadLocal-String、threadLocal-Boolean。還是上面的例子。
(常規的HashMap的鍵值得類型是固定的;threadLocalMap的key是ThreadLocal,value是T,即可以存多種類型的value)
ThreadLocal<Boolean> booleanThreadLocal = new ThreadLocal<>();
ThreadLocal<Integer> integerThreadLocal = new ThreadLocal<>();
booleanThreadLocal.set(true);
integerThreadLocal.set(0);
Log.i(TAG, "testThreadLocal: main thread, boolean= "+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: main thread, int = "+integerThreadLocal.get());
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(false);
integerThreadLocal.set(1);
Log.i(TAG, "testThreadLocal: a thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: a thread, int = "+integerThreadLocal.get());
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
booleanThreadLocal.set(null);
integerThreadLocal.set(2);
Log.i(TAG, "testThreadLocal: b thread, boolean="+booleanThreadLocal.get());
Log.i(TAG, "testThreadLocal: b thread, int = "+integerThreadLocal.get());
}
}).start();
結果:
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, boolean= true
2020-01-08 10:15:38.623 8976-8976/com.hfy.demo01 I/hfy: testThreadLocal: main thread, int = 0
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, boolean=false
2020-01-08 10:15:38.624 8976-9226/com.hfy.demo01 I/hfy: testThreadLocal: a thread, int = 1
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, boolean=null
2020-01-08 10:15:38.626 8976-9227/com.hfy.demo01 I/hfy: testThreadLocal: b thread, int = 2
到目前爲止我們知道,ThreadLocal的作用,就是操作線程內部的threadLocals,存和取value。value的實際類型就是 實例化ThreadLocal時定義的泛型T。
2.2 messageQueue
messageQueue,消息隊列,實際是單向鏈表。看下存、取消息。
enqueueMessage(),存消息,單鏈表的插入。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
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;
}
next():取一條消息,沒有消息就無限循環,會阻塞。
Message next() {
//...
//有msg就return,沒有消息就無限循環,會阻塞。如quit,return null。
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
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
return msg;
}
} else {
// No more messages.
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
//quit後返回null
return null;
}
// ...
}
2.3 Looper
looper,消息循環器。
先看靜態方法prepare():
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the loop. Be sure to call
* {@link #loop()} after calling this method, and end it by calling
* {@link #quit()}.
*/
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));
}
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
可見sThreadLocal是個靜態常量,value類型是Looper。
prepare()方法調sThreadLocal.set(new Looper),創建looper實例,設置給當前線程ThreadLocalMap屬性中的table[i](i是threadLocal實例的hashCode相關)。
且創建looper實例時默認創建了對應的消息隊列mQueue實例。另外,prepareMainLooper()是主線程,是給主線程創建looper實例。
再看下獲取looper實例、queue實例的方法:
/**
* Returns the application's main looper, which lives in the main thread of the application.
*/
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
/**
* Return the {@link MessageQueue} object associated with the current
* thread. This must be called from a thread running a Looper, or a
* NullPointerException will be thrown.
*/
public static @NonNull MessageQueue myQueue() {
return myLooper().mQueue;
}
myLooper() 方法,調用sThreadLocal.get()。就是上面講解的ThreadLocal的使用方法。通過靜態常量sThreadLocal獲取對應每個線程的Looper實例。
looper的quit,兩種,立即退出,執行完消息再退出。
/**
* Quits the looper.
* <p>
* Causes the {@link #loop} method to terminate without processing any
* more messages in the message queue.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p><p class="note">
* Using this method may be unsafe because some messages may not be delivered
* before the looper terminates. Consider using {@link #quitSafely} instead to ensure
* that all pending work is completed in an orderly manner.
* </p>
*
* @see #quitSafely
*/
public void quit() {
mQueue.quit(false);
}
/**
* Quits the looper safely.
* <p>
* Causes the {@link #loop} method to terminate as soon as all remaining messages
* in the message queue that are already due to be delivered have been handled.
* However pending delayed messages with due times in the future will not be
* delivered before the loop terminates.
* </p><p>
* Any attempt to post messages to the queue after the looper is asked to quit will fail.
* For example, the {@link Handler#sendMessage(Message)} method will return false.
* </p>
*/
public void quitSafely() {
mQueue.quit(true);
}
靜態方法loop():用threadLocal.get()獲取當前線程的Looper,然後拿到queue,循環取消息,給到handler的dispatchMessage方法-handleMessage方法。唯一跳出循環是取到null,null是因爲調用了quit或quitSafly。
因爲靜態方法loop()是在線程中調用的,所以不論handler從哪裏發送msg都會在loop的線程中執行。
/**
* Run the message queue in this thread. Be sure to call
* {@link #quit()} to end the 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.");
}
final MessageQueue queue = me.mQueue;
// ...
for (;;) {
//沒有msg ,queue.next()阻塞,loop() 也就阻塞了。next有msg就處理,無限循環。
Message msg = queue.next(); // might block
if (msg == null) {
//調用quit()時纔會 跳出循環
// No message indicates that the message queue is quitting.
return;
}
// ...
//用target(handler)處理消息,dispatchMessage執行在loop() 調用的地方,即looper所在線程。
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
}
}
流程 prepare()-new hanler()- loop() 連續的在同個線程調用。保證handleMessage執行在當前線程。即使handler.sengMessage()在其他線程調用。
2.4 Handler
發送,處理消息。
先看Handler構造方法,可見調用了Looper.myLooper(),就是獲取當前線程的looper,沒有就會拋出異常。
/**
* Default constructor associates this handler with the {@link Looper} for the
* current thread.
*
* If this thread does not have a looper, this handler won't be able to receive messages
* so an exception is thrown.
*/
public Handler() {
this(null, false);
}
public Handler(Callback callback) {
this(callback, false);
}
public Handler(Callback callback, boolean async) {
...
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;
}
發送消息,就是把消息放入隊列
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
處理消息,根據Handler的創建形式和使用方法對應處理。
/**
* Handle system messages here.
*/
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
//msg.callback就是handler.post()發送的runable
handleCallback(msg);
} else {
if (mCallback != null) {
//mCallback是創建Handler時傳入CallBack的情況。
if (mCallback.handleMessage(msg)) {
return;
}
}
//覆寫handleMessage()創建handler的情況
handleMessage(msg);
}
}
三、主線程的消息機制
主線程的消息
Looper中:
/**
* Initialize the current thread as a looper, marking it as an
* application's main looper. The main looper for your application
* is created by the Android environment, so you should never need
* to call this function yourself. See also: {@link #prepare()}
*/
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
ActivityThread的靜態方法main:
final H mH = new H();
public static void main(String[] args) {
...
//1、準備主線程的Looper
Looper.prepareMainLooper();
// Find the value for {@link #PROC_START_SEQ_IDENT} if provided on the command line.
// It will be in the format "seq=114"
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
//這裏實例化ActivityThread,也就實例化了上面的mH,就是handler。
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
//獲取handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
...
//主線程looper開啓
Looper.loop();
//因爲主線程的Looper是不能退出的,退出就無法接受事件了。一旦意外退出,會拋出異常
throw new RuntimeException("Main thread loop unexpectedly exited");
}
H處理了四大組件的啓動停止等。ActivityThread通過ApplicationThread和AMS進行進程間通信,AMS完成ActivityThread的請求後,回調到ApplicationThread中的binder方法,然後ApplicationThread使用H發送消息,然後就把此消息切換到ApplicationThread中執行,即在主線程執行。這就是主線程的消息循環。