編輯:關於Android編程
關於這個隊列先說明一點,該隊列的實現既非Collection的子類,亦非Map的子類,而是Message本身。因為Message本身就是鏈表節點。隊列中的Message mMessages;
成員即為隊列,同時該字段直接指向隊列中下一個需要處理的消息。
enqueueMessage()
要將message添加到隊列除了提供message之外,還需提供消息觸發時間when
。
如果當前隊列為空則直接mMessage=message即可。否則就需要逐個對比隊列中每個message的when和新消息的when來確定新消息在隊列中的位置。
先給出核心源碼(有刪減)
Message p = mMessages;
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
} else {
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
}
msg.next = p;
prev.next = msg;
}
if (needWake) {
nativeWake(mPtr);
}
先看下新消息需要放到隊頭的情況:p == null || when == 0 || when < p.when
。即隊列為空,或者新消息需要立即處理,或者新消息處理的事件比隊頭消息更早被處理。這時只要讓新消息的next
指向當前隊頭,讓mMessages
指向新消息即可完成插入操作。
除了上述三種情況就需要遍歷隊列來確定新消息位置了,下面結合示意圖來說明。
假設當前消息隊列如下
開始遍歷:p向隊尾移,引入prev指向p上一個元素
假設此時p所指消息的when比新消息晚,則新消息位置在prev與p中間
最後便是調用native方法來喚醒(Linux的epoll,有興趣的自行百度)。
next()
這部分內容有點高能,請根據個人BPU(BrainProcessUnit)酌情理解。
首先這個方法需要返回Message
,那麼我們現在來看看哪裡有return
。(共三段,我們最後看第二段。)
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
如果mPtr為0則返回null。那麼mPtr是什麼?值為0又意味著什麼?在MessageQueue
構造方法中調用了native方法並返回了mPtrmPtr = nativeInit();
;在dispose()
方法中將其值置0mPtr = 0;
並且調用了nativeDestroy()
。而dispose()
方法又在finalize()
中被調用。另外每次mPtr的使用都調用了native的方法,其本身又是long類型,因此推斷它對應的是C/C++的指針。因此可以確定,mPtr
為一個內存地址,當其為0說明消息隊列被釋放了。這樣就很容易理解為什麼mPtr==0
的時候返回null了。
你沒有看錯,第二段在後面
if (mQuitting) {
dispose();
return null;
}
這裡的意思也很明顯,當這個消息隊列退出的時候,返回空。而且在返回前調用了dispose()
方法,顯然這意味著該消息隊列將被釋放。
這部分涉及到的代碼基本上就是這個next()
方法本身了,但可以肯定的是這裡的返回語句是return msg;
。同時從enqueueMessage()
方法可以看出來,在這個隊列中取到的message對象不可能為空,因此這裡的返回絕對不為空。
如此一來就可以得出一個結論:如果next()
方法為空說明這個消息隊列正在退出或將被釋放回收。
繼續來看這個next()
,這個代碼有點長,所以先做個減法。
第一個要減的就是pendingIdleHandlerCount
,這個局部變量初始為-1,後面被賦值mIdleHandlers.size();
。這裡的mIdleHandlers
初始為new ArrayList
,在addIdleHander()
方法中增加元素,在removeIdleHander()
方法中移除元素。而我們所用的Handeler
並未實現IdleHandler
接口,因此在next()
方法中pendingIdleHandlerCount
的值要麼為0,要麼為-1,因此可以看出與該變量相關的部分代碼運行情況是確定的,好的,把不影響循環控制的代碼減掉。
第二個要減的是Binder.flushPendingCommands()
這個代碼看源碼說明:
Flush any Binder commands pending in the current thread to the kernel driver. This can be useful to call before performing an operation that may block for a long time, to ensure that any pending object references have been released in order to prevent the process from holding on to objects longer than it needs to.
這段話啥意不懂也沒關系,這裡只需要知道:Binder.flushPendingCommands()
方法被調用說明後面的代碼可能會引起線程阻塞。然後把這段減掉。
第三個要減的是一個log語句if (DEBUG) Log.v(TAG, "Returning message: " + msg);
第四個要減的是上面提到的“第一段”返回null的語句,但是“第三段”得留著。
最後再把注釋干掉給上代碼:
Message next() {
int nextPollTimeoutMillis = 0;
for (;;) {
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
if (msg != null && msg.target == null) {
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now < msg.when) {
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
nextPollTimeoutMillis = -1;
}
if (mQuitting) {
dispose();
return null;
}
if (pendingIdleHandlerCount <= 0) {//上面分析過該變量要麼為0要麼為-1
mBlocked = true;
continue;
}
}
nextPollTimeoutMillis = 0;
}
}
雖然還是很長,但也不能再減了。大致思路如下:先獲取第一個同步的message。如果它的when
不晚與當前時間,就返回這個message;否則計算當前時間到它的when
還有多久並保存到nextPollTimeMills
中,然後調用nativePollOnce()
來延時喚醒(Linux的epoll,有興趣的自行百度),喚醒之後再照上面那樣取message,如此循環。代碼中對鏈表的指針操作占了一定篇幅,其他的邏輯很清楚,就不一句句分析了。
removeMessages()
該方法有2個重載,除此之外還有removeCallbacksAndMessages()
等方法也可以移除消息。但代碼段都基本一樣,這裡以void removeMessages(Handler h, int what, Object object){}
方法為例。
該方法完整源碼如下
void removeMessages(Handler h, int what, Object object) {
if (h == null) {
return;
}
synchronized (this) {
Message p = mMessages;
// Remove all messages at front.
while (p != null && p.target == h && p.what == what
&& (object == null || p.obj == object)) {
Message n = p.next;
mMessages = n;
p.recycleUnchecked();
p = n;
}
// Remove all messages after front.
while (p != null) {
Message n = p.next;
if (n != null) {
if (n.target == h && n.what == what
&& (object == null || n.obj == object)) {
Message nn = n.next;
n.recycleUnchecked();
p.next = nn;
continue;
}
}
p = n;
}
}
}
最開始判斷handler是否為空不必多說,然後便是同步代碼段,只裡面有兩個while循環。為什麼有兩個呢?學過數據結構鏈表的都知道,鏈表分兩種:帶頭結點和不帶頭結點。而這兩種鏈表的遍歷方式有所不同:不帶頭結點的鏈表中,第一個元素需要單獨處理,然後才能將後續部分當做帶頭結點的鏈表來使用while循環遍歷。可以看出MessageQueue是不帶頭結點的鏈表,而且遍歷過程中有需要刪除節點,因此要特殊處理的不只是第一個元素,而是第一組符合刪除條件的元素。有點暈了是吧,不要緊,我們開始斗圖。
假設需要遍歷的消息隊列如圖所示。
為了讓第一個while可以執行,我們假設前3個元素符合移除條件,即前三個Message的targe
、what
、obj
分別與指定的handler
、what
、object
相同。首先第一個元素滿足條件進行如下操作:
執行n=p.next;
後移mMessage;
回收p指向的元素,即第一個元素。
讓p指向新的隊頭。
此時又與初始隊列狀態一樣了。先前我們假設隊頭有三個元素符合移除條件,因此再循環執行上面4圖2邊後又得到初始狀態的隊列,此時隊頭元素不滿足移除條件因此while終止,同時新的隊列變成了“帶頭結點的鏈表”,因此mMessage指向的元素永遠不用被判斷是否滿足移除條件。
此時消息隊列狀態如下:
執行n=p.next;
假設n指向的元素不滿足移除條件,則只需要將p和n後移,如此也說明,p指向的元素總是已經被判斷過不滿足移除條件的。這部分邏輯很簡單到給圖就是看不起讀者的智商,現在我們假設n指向的元素滿足移除條件,即當前隊列如下:
執行nn=n.next;
回收n指向的元素
執行p.next=nn;
這時p之後的隊列又是一個帶頭結點的鏈表。可以繼續while了。
Ionic是一款流行的移動端開發框架,但是剛入門的同學會發現,Ionic在iOS和Android的底部tabs顯示不一樣。在安卓情況下底部tabs會浮上去。如下圖展示:網
本文實現了Android程序文字翻轉動畫的小程序,具體代碼如下:先上效果圖如下:要求:沿Y軸正方向看,數值減1時動畫逆時針旋轉,數值加1時動畫順時針旋轉。實現動畫的具體細
Universal-Image-Loader是一個強大而又靈活的用於加載、緩存、顯示圖片的Android庫。它提供了大量的配置選項,使用起來非常方便。基本概念基本使用首次
AlphaAnimation:透明度(alpha)漸變效果,對應< alpha/>”標簽。 TranslateAnimation:位移漸變,需要