消息機制經常是Android開發中比較核心的一部分,做爲Android開發者這部分是必定要掌握的。本文經過結合Native層的部分代碼來分析Android framework層消息機制。java
因爲Android規定在子線程中沒法更新UI,而網絡請求通常又只能在子線程中進行。這時,當咱們請求到網絡以後一定要將數據展現在UI上,因此說Android爲咱們提供了好多好多的方法來更新UI。android
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
Handler
複製代碼
要了解原理,才能更好地使用Handler
,因此說本文將帶你走入Android消息機制
的世界裏。c++
public class MainActivity extends AppCompatActivity {
private static final int CODE = 1;
private TextView tv;
private Handler mainHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = findViewById(R.id.tv);
mainHandler = new Handler(getMainLooper()){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == CODE){
tv.setText((String)msg.obj);
}
}
};
new Thread(()->{
try {
Thread.sleep(3000);//模擬網絡耗時
Message message = Message.obtain();
message.what = CODE;
message.obj = "子線程用Handler更新UI";
mainHandler.sendMessage(message);
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
@Override
protected void onDestroy() {
super.onDestroy();
mainHandler.removeCallbacksAndMessages(null);
}
}
複製代碼
代碼很簡單,無非就是在子線程中將Message發送到主線程中,再利用Handler處理。bash
提及看源碼,我大概的步驟是先看構造器,而後再根據咱們平常使用的流程進行分析,明白各個類之間的關係。若是有更有效方法,能夠傳授一下。消息機制,無非就是Handler
、Looper
、MessageQueue
之間的關係。網絡
首先咱們分析三個重要類的構造方法less
Handler
構造函數構造函數一共有6個,但最重要的是下面這一個,其他構造器均是重載的下面這個構造器。異步
public Handler(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;
}
複製代碼
咱們能夠看到,首先在這裏進行判斷,若是找到了潛在的內存泄露則會打印一個Log。以後就是得到Handler
所在線程的Looper
和MessageQueue
。若是未找到Looper
,即沒有執行Looper.prepare
則會拋出異常。async
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
複製代碼
在Looper
的構造函數中對MessageQueue
進行了初始化,而且綁定了當前線程。可是值得一提的是這個構造器被private了,意思是不能顯式地實例化,因此說咱們要尋找在哪裏初始化了Looper
。ide
通過一番尋找,果真在Looper.prepare方法中找到了Looper實例化的地方。函數
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));
}
複製代碼
這裏進行了3件事情
ThreadLocal
裏是否已經存在Looper
,若存在了則拋出異常。TheadLocal
在每一個線程中只存在一個,通常用來保存每一個線程獨有的東西。Looper
。在Looper.prepare
中,quitAllowed
默認爲true。這個quitAllowed
會傳入MessageQueue
的構造器裏,做用是判斷是否Message
能夠退出,這裏以後會講。Looper
保存到當前線程的ThreadLocal
中。MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
複製代碼
在MessageQueue
的構造函數中,設置了是否容許Message
退出,而後調用nativeInit
方法初始化native層的nativeMessageQueue
,以後再將生成的nativeMessageQueue
,經過reinterpret_cast
方法將指針轉化爲long值返回給Framework層保存。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
複製代碼
Handler發送消息有不少種:
sendMessage
sendMessageDelayed
MessageAtTime
sendMessageAtFrontOfQueue
post
(篇幅有限,這個方法邏輯差很少。)public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
複製代碼
sendMessage
和sendMessageDelayed
最後都會調用sendMessageAtTime
,惟一不一樣的是發送延時消息時,參數須要傳入(當前時間+延時時間)
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);
}
public final boolean sendMessageAtFrontOfQueue(Message msg) {
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, 0);
}
複製代碼
sendMessageAtTime
和sendMessageAtFrontOfQueue
均會調用enqueueMessage
,不一樣的是sendMessageAtTime
傳入的時間是uptimeMillis
,而sendMessageAtFrontOfQueue
傳入的是0。顧名思義,後者是想把把消息插在消息隊列的比隊頭。
因此說咱們大膽猜想一下,隊列會根據uptimeMillis
值的不一樣來插入隊列。咱們接着往下看。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
複製代碼
邏輯很簡單,這個方法將message
的target
設置爲當前的Handler,而後設置是否異步發送該消息,最後調用MessageQueue.enqueueMessage
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {//message沒有target咋會知道發送到哪兒?
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {//使用過的message確定不能再用吧?
throw new IllegalStateException(msg + " This message is already in use.");
}
synchronized (this) {
if (mQuitting) {//若是Message正在退出,會打印Log而且回收Message
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();//這個方法會把message回收到Message池
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
//下面是對message進行處理
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;
}
複製代碼
能夠看出,MessageQueue
使用Message.next
保存下一個Message
,從而按照時間將全部的Message
排序,從而達到入隊的效果。同時,此時若是須要喚醒,則調用nativeWake
方法來喚醒以前等待的線程。
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
複製代碼
這樣,咱們就探索完Message是如何進入消息隊列的邏輯。
在消息機制中,Looper
主要做用是用來負責輪訓消息、分發消息的。接下來咱們分析一個消息是如何分發的。首先咱們看Looper.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 (;;) {
Message msg = queue.next(); //取出Message
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
//...省略一波代碼
final long dispatchStart = needStartTime ? SystemClock.uptimeMillis() : 0;
final long dispatchEnd;
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
//省略一波代碼target
msg.recycleUnchecked();
}
}
複製代碼
能夠看到,經過msg.target
將消息分發下去,最後回收了msg
。這裏的msg.target
就是在Handler.enqueueMessage
設置的Handler
。因此說這裏調用的是Handler.dispatchMessage
方法
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
複製代碼
若是該消息的callback
不爲空,則容許該消息的callback
對象的run
方法。不然,若是當前Handler.mCallback
屬性不爲空,則mCallback
處理該消息,若是msg.callback
和Handler.mCallback
都爲空,則直接利用Handler.handleMessage
內部處理該消息,該方法就是handler初始化時,本身寫的方法。
借用一下僞代碼:
public void dispatchMessage(Message msg) {
if (該消息的callback屬性不爲空) {
運行該消息的callback對象的run()方法;
} else {
if (當前Handler對象的mCallback屬性不爲空) {
if (mCallback對象成功處理了消息) {
return;
}
}
Handler內部處理該消息;
}
}
複製代碼
另外,在Looper.loop
方法中,消息從消息隊列中被取出。彷佛咱們還沒分析消息如何取出來的吧?那咱們立刻分析一下姍姍來遲的MessageQueue.next
Message next() {
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
//..省略一大波代碼
nativePollOnce(ptr, nextPollTimeoutMillis);//使Native的Looper阻塞
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//若是msg不爲null,target(目標Handler)不爲空
if (msg != null && msg.target == null) {
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 {//到時間就取出message
// 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;//沒有更多消息則阻塞本身
}
// Process the quit message now that all pending messages have been handled.
if (mQuitting) {
dispose();
return null;
}
//..省略一大波代碼
}
//..省略一大波代碼
}
}
複製代碼
根據我寫的註釋咱們能夠知道,在這個方法裏會有一個死循環不斷輪訓消息,若是沒有消息則該循環會被阻塞,若是有消息,該消息會被移出單鏈表,而後返回該消息。
在註釋中,咱們發現Google推薦用Message.obtain
方法來得到一個Message
而不是new
一個Message
。因此咱們這裏探索一下是爲何呢?
public static Message obtain() {
synchronized (sPoolSync) {
if (sPool != null) {
Message m = sPool;
sPool = m.next;
m.next = null;
m.flags = 0; // clear in-use flag
sPoolSize--;
return m;
}
}
return new Message();
}
複製代碼
顯而易見,這裏將運用了相似於對象池的東西,不過實現方法確實單鏈表。當sPool
不爲空,則說明該單鏈表上還有回收的Message
能夠複用。不然則建立一個新的Message
。咱們在以前的源碼裏看見了Message.recycle
方法,這個方法用於在回收Message
前進行一系列檢查,真實調用的是Message.recycleUnchecked
void recycleUnchecked() {
// Mark the message as in use while it remains in the recycled object pool.
// Clear out all other details.
flags = FLAG_IN_USE;
what = 0;
arg1 = 0;
arg2 = 0;
obj = null;
replyTo = null;
sendingUid = -1;
when = 0;
target = null;
callback = null;
data = null;
synchronized (sPoolSync) {
if (sPoolSize < MAX_POOL_SIZE) {
next = sPool;
sPool = this;
sPoolSize++;
}
}
}
複製代碼
在這裏清空Message的全部屬性,而且將其放入對象池中。
這就是Message如何複用的過程。
native層的邏輯與framework層大體相同,不過實現方法略有不一樣。值得一提的是,native層中NativeMessageQueue已經被弱化掉了,大部分邏輯都放在NativeLooper中處理。
在上面,咱們提到了MessageQueue的構造函數中同時Native層也要初始化MessageQueue,前面也提到,這裏的MessageQueue指針將會被轉化成long值保存在framework的MessageQueue中。
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env);
return reinterpret_cast<jlong>(nativeMessageQueue);
}
複製代碼
在MessageQueue中一樣也和Framework層同樣,初始化了一個Native層的Looper,同時把Looper保存在當前線程中。
NativeMessageQueue::NativeMessageQueue() :
mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
複製代碼
Native層的Looper構造函數以下:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
LOG_ALWAYS_FATAL_IF(mWakeEventFd < 0, "Could not make wake event fd: %s",
strerror(errno));
AutoMutex _l(mLock);
rebuildEpollLocked();
}
複製代碼
這裏建立了mWakeEventFd
,與以前5.0利用管道的mWakeWritePipeFd
與mWakeReadPipeFd
實現讀取/寫入不同,eventfd
是等待/響應。同時這裏有一個很重要的方法rebuildEpollLocked
void Looper::rebuildEpollLocked() {
//...
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
struct epoll_event eventItem;
memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not add wake event fd to epoll instance: %s",
strerror(errno));
for (size_t i = 0; i < mRequests.size(); i++) {
const Request& request = mRequests.valueAt(i);
struct epoll_event eventItem;
request.initEventItem(&eventItem);
int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, request.fd, & eventItem);
if (epollResult < 0) {
ALOGE("Error adding epoll events for fd %d while rebuilding epoll set: %s",
request.fd, strerror(errno));
}
}
}
複製代碼
在rebuildEpollLocked
方法中,經過epoll_create建立了一個epoll專用的文件描述符,參數中的EPOLL_SIZE_HINT
是mEpollFd
最大可監聽的文件描述符數。以後調用epoll_ctl
方法對mWakeEventFd
的Epoll事件進行監聽,mWakeEventFd
若是有事件可讀,就會喚醒當前正在等待的線程。
剛剛提到了Epoll,可能沒有了解的人根本不知道Epoll是什麼東西。當framework層Looper.loop
的時候,會用MessageQueue.next
來取出消息。而next方法中是一個暴力死循環,Android固然沒這麼傻,不可能一直循環,因此說在循環裏又調用了nativePollOnce(ptr, nextPollTimeoutMillis)
方法,這裏就是消息隊列阻塞的關鍵,也是Epoll的主要做用。
回到正題,傳統的阻塞型I/O,一個線程只能處理一個一個的IO流,就像5.0版本同樣利用mWakeWritePipeFd
與mWakeReadPipeFd
實現讀寫。而若是想要一個線程處理多個流,就得采用非阻塞、輪訓的I/O,這樣就出現了select(無差異輪詢)
、epoll(事件輪詢)
這兩種方法。而Epoll(相似於Event poll,即事件輪詢)
是基於事件的,不會無差異輪詢,性能會有提升。
因此說咱們看看剛纔的rebuildEpollLocked
方法中幹了什麼事情:
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
複製代碼
mEpollFd
代理epoll_event
中設置mWakeEventFd
mWakeEventFd
中的Epoll事件這就是EPOLLIN事件所作的事情。
因此說,native層主要負責的是消息的調度,什麼時候阻塞線程、什麼時候喚醒線程、避免不停地空循環,避免性能消耗過大。
在Handler
發送消息的時候,會調用MessageQueue.enqueueMessage
方法。在這個方法裏,若是線程阻塞,又會調用nativeWake
來喚醒線程。咱們接下來看看nativeWake
方法的實現
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->wake();
}
複製代碼
經過ptr(就是framework層保存的long值)來拿到NativeMessageQueue
,其實是調用的wake方法
void NativeMessageQueue::wake() {
mLooper->wake();
}
複製代碼
最後調用了Looper的wake方法
void Looper::wake() {
//...
uint64_t inc = 1;
ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
if (nWrite != sizeof(uint64_t)) {
//...
}
//...
}。
複製代碼
這裏經過TEMP_FAILURE_RETRY
這個宏定義,不斷往mWakeEventFd
裏面寫一個無用的事件,直到framework層MessageQueue.next
被喚醒。
前面咱們提到在MessageQueue.next
方法中,會調用一個native方法nativePollOnce(ptr, nextPollTimeoutMillis)
來阻塞線程
Message next() {
//..省略一大波代碼
int nextPollTimeoutMillis = 0;
for (;;) {
//..省略一大波代碼
nativePollOnce(ptr, nextPollTimeoutMillis);//使Native的Looper阻塞
synchronized (this) {
//..省略一大波代碼
}
//..省略一大波代碼
}
}
複製代碼
咱們看看它的具體實現:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jlong ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
nativeMessageQueue->pollOnce(env, obj, timeoutMillis);
}
複製代碼
一樣是調用的NativeMessageQueue
的pollOnce
方法
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
mPollEnv = env;
mPollObj = pollObj;
mLooper->pollOnce(timeoutMillis);
mPollObj = NULL;
mPollEnv = NULL;
if (mExceptionObj) {
env->Throw(mExceptionObj);
env->DeleteLocalRef(mExceptionObj);
mExceptionObj = NULL;
}
}
複製代碼
和以前的wake同樣,這裏一樣最終調用了Looper
的pollOnce
方法,因此說,native層的MessageQueue實際上已經被弱化了許多,大多數操做都在Looper中進行:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) {
int result = 0;
for (;;) {
while (mResponseIndex < mResponses.size()) {
//...省略一波代碼
}
//...省略一波代碼
result = pollInner(timeoutMillis);
}
}
複製代碼
pollOnce
中又是一個死循環,就相似於MessageQueue.next方法同樣,最終會調用pollInner(timeoutMillis)
方法,這裏就是最終的調用。
int Looper::pollInner(int timeoutMillis) {
//...
int result = POLL_WAKE;
mResponses.clear();
mResponseIndex = 0;
// We are about to idle.
mPolling = true;
struct epoll_event eventItems[EPOLL_MAX_EVENTS];
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
// No longer idling.
mPolling = false;
// Acquire lock.
mLock.lock();
//...省略一大波代碼
for (int i = 0; i < eventCount; i++) {
int fd = eventItems[i].data.fd;
uint32_t epollEvents = eventItems[i].events;
if (fd == mWakeEventFd) {
if (epollEvents & EPOLLIN) {
awoken();//經過awoken喚醒線程
} else {
ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
}
} else {
//...
}
}
// Release lock.
mLock.unlock();
//...
return result;
}
複製代碼
這裏能夠看到,利用epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)
方法獲取到事件到個數。若是個數爲0,就繼續阻塞。若是不爲0,則會遍歷每個事件,若是有mWakeEventFd
而且是EPOLLIN
事件,就會經過awoken
方法真正地喚醒線程。
最後附上native層代碼:
NativeMessageQueue
Looper 歡迎你們來和我交流。Android學習之路還很漫長。