編輯:關於Android編程
異步消息處理線程是指,線程在啟動後會進入一個無線循環體中,沒循環一次,從內部的消息隊列中取出一個一個消息,並回調相應的消息處理函數,執行完一個消息後則繼續循環。如果消息隊列為空,線程會暫停,知道消息隊列中有新的消息。
異步消息處理線程本質上仍然是一個線程,只不過這種線程的執行代碼設置成如上所述的邏輯而已。在android中實現異步線程主要涉及到如下幾個類:ThreadLocal,Looper,MessageQueue,Handler,Message,接下來我們一一介紹這幾個類,解析Android的異步線程機制。
先上一張框架圖:
ThreadLocal並不是Android的sdk中的類,而是java.lang中的一個類,該類的作用是為線程創建一個基於線程的變量存儲,我們可以稱之為線程局部存儲。ThreadLocal可以使對象達到線程隔離的目的,它為每一個線程維護自己的變量拷貝,通過其中的set方法將變量綁定到線程上。ThreadLocal提供了一種解決多線程同步的問題解決方案,通過為每一份變量進行拷貝,這樣的話,每個線程操作的都是屬於自己的變量,而不是共同的一個變量,因此也就不需要同步鎖了。舉個栗子,我們創建出一個變量,而這個變量會被兩個線程操作。一般情況下,我們會給這個變量加鎖,通過這種方式來解決同步的問題。但是當我們使用ThreaLoca時,我們就可以通過ThreadLoca來為每一個線程做一個拷貝,而且這個拷貝是跟線程綁定在一起的,也就是說每個線程可以更改自己的變量而不影響另外一個線程。這樣也就不需要鎖了。ThreadLocal在set時會自動綁定到當前的線程,而不需要自己去綁定。代碼這裡就不寫了,知道中心思想就行。有人可能會問,這跟Android有什麼關系呢。這就要說到我們的Looper了,因為在Android的異步線程中,ThreadLocal綁定的這個線程變量就是Looper的一個對象。
Looper有什麼用呢,要實現異步線程,必須要Looper,因為Looper是用來產生一個MessageQueue。我們可以通過查看源代碼知道,在Looper類中有一個成員變量mQueue(MessageQueue類的實例),該變量用於保存Looper中MessageQueue。
Looper通過靜態方法Looper.prepare()方法來創建出一個MessageQueue對象。注意,Looper.prepare()方法在一個線程中只能執行一次。
查看源代碼:
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));
}
以上為android的源代碼,執行了兩次prepare時,就會拋出一個異常。這個其實很自然就可以想到,因為前面提到,Looper是用來創建MessageQueue的。一個線程只能有一個MessageQueue,因此自然只能有一個Looper對象。看完prepare()源代碼好像並沒有發現它創建出了MessageQueue的一個實例啊,對的,這裡是看不到,但是。我們來看看在創建一個新的Looper對象時所做的工作:
我們找到Looper的構造函數:
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread();
}
我們可以發現,Looper的這個構造函數是私有的,而且Looper只有這一個構造函數。所以我們可以看出我們不能在其他的類中new出一個Looper對象。但是,這不是重點,重點是Looper在這裡創建出了一個MessageQueue對象。
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;
// Make sure the identity of this thread is that of the local process,
// and keep track of what that identity token actually is.
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
// Make sure that during the course of dispatching the
// identity of the thread wasn't corrupted.
final long newIdent = Binder.clearCallingIdentity();
if (ident != newIdent) {
Log.wtf(TAG, "Thread identity changed from 0x"
+ Long.toHexString(ident) + " to 0x"
+ Long.toHexString(newIdent) + " while dispatching to "
+ msg.target.getClass().getName() + " "
+ msg.callback + " what=" + msg.what);
}
msg.recycle();
}
}
函數myLooper用來返回當前線程的Looper對象,通過一個for(;;)來執行循環。在for循環內部,通過MessageQueue的next方法來去取出其中的Message,這裡有一點需要注意,就是取出來的message,最後會調用msg.target.dispatchMessage(msg);來處理消息,該方法在Handler中,因為msg.target是一個Handler對象。這個放到Handler中說
MessageQueue是用來處理消息隊列的。該類中有幾個方法,一個是next()方法,用於取出隊列中下一個元素的。另外一個是enqueueMessage方法,用於向消息隊裡中添加一個元素。該方法會在Handler中的sendMessage中使用到,這裡稍微提一下,在Handler的sendMessage中,會調用這個方法,將Message添加到接收線程的MessageQueue中。
Handler是用於發送信息的,我們熟知的方法就是其中的sendMessage方法了,用於向消息接收線程發送消息。上面的框架圖中有說到,Handler一定要在接收消息的線程中創建,只有這樣的話才可以給該線程發送消息。因為創建出的Handler對象handler必須要有該線程的MessageQueue消息隊列才可以給該線程發送消息。當Handler在創建時,會用到如下的構造函數:
public Handler(Callback callback, boolean async) {
if (FIND_POTENTIAL_LEAKS) {
final Class 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 that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在創建Handler時, 會通過mLooper = Looper.myLooper();來獲取當前線程的Looper對象,而Looper對象又是獲取MessageQueue對象前提。Handler在調用handler.sendMessage(Message)發送message時,最終會調用到這個方法:
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);
}
在這裡可以看出,handler會獲得當前線程的MessageQueue對象。之後調用enqueueMessage(queue, msg, uptimeMillis);這個方法是Handler中的方法,而不是MessgaeQueue中的enqueueMessage方法,我們查看Handler中的enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
可以看到個方法體中的第一行代碼:msg.target = this;將當前發送的message.target對象設置成當前的handler。這個在Looper.loop()方法中,調用了 msg.target.dispatchMessage(msg);方法。這個很快就會介紹。先不說這個。可以看到,handler中的enqueueMessage方法會設置message的target為當前對象,之後會調用MessageQueue對象的enqueueMessage(該方法在MessageQueue中介紹過,是用於向消息隊列添加消息的方法)方法,將message添加到MessageQueue對象中。
好了,我 們現在來說一下handler的dispatchMessage(msg)方法。我們看一下這個方法的源代碼,該方法會在looper的loop方法中使用的,當獲取每個消息時,會調用方法 msg.target.dispatchMessage(msg);來處理每個消息。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
該方法中先判斷消息本身是否有回調函數,有的話則調用消息的回調函數,如果沒有,再判斷創建這個Handler時有沒有傳遞Callback接口,注意,這個 接口的名字就是Callback,Handler可以通過構造函數來給這個mCallback賦值,如果這個mCallback傳遞了值的話 就會調用這個方法,否則調用Handler本身的handleMessage方法。這個方法在Handler中是空的,需要在繼承Handler的類中,重寫該方法。也就是說重寫的該方法實際上是優先級最低的。
後面兩種比較常見,但是前面一種,也就是消息本身攜帶了接口的一般通過Message.obtain(Handler h, Runnable callback)方法來設置。
好了 Android的異步線程說到這裡就拆不多了。
獲取數據用GET請求 ??增刪改查修改數據用POST請求 package com.example.jreduch07;import android.
本文分享自己在視頻錄制播放過程中遇到的一些問題,主要包括: 視頻錄制流程 視頻預覽及SurfaceHolder 視頻清晰度及文件大小 視頻文件旋轉 一、視頻錄制
Android TextView 圓弧效果圖:布局代碼:<TextView android:id=@+id/product_tag
先看效果圖:1.顯示三個頁面的Activity 用view pager去加載三個fragment實現,控制點點點的切換,監聽view pager的切換,控制fragmen