編輯:Android編程入門
每一個Android應用在啟動的時候都會創建一個線程,這個線程被稱為主線程或者U
I線程,Android應用的所有操作默認都會運行在這個線程中。
但是當我們想要進行數據請求,圖片下載,或者其他耗時操作時,是不可能在這個UI
線程做的,因為Android在3.0以後的版本已經禁止了這件事情,直接拋出一個異常。所以我們需要一個子線程來處理那些除UI
操作的事情。
但是這個又會有一個問題,我們只能在UI
線程進程UI
操作,只能在子線程進行耗時操作,如果我們需要在耗時操作結束後在Android界面上顯示一個View,我們應該怎麼做?我們是不可能在子線程直接刷新UI
的。這時我們需要用到Android的消息機制,來實現主線程和子線程的通信。簡單來說,就是子線程獲取到數據之後,不直接進行UI
更新,而是把數據裝到消息中發送到主線程,主線程中有一個循環輪詢會立即收到子線程發過來的信息,然後拿到消息數據後在主線程更新UI
。說起來比較簡單,我們來仔細的看一下具體是怎麼說的。
我們先講解一下Handler,Handler顧名思義就是處理者,通常對他的用法是在UI
線程中新建一個Handler
,並覆寫他的handleMessage
, 然後再耗時的線程中將消息post
給UI
線程,例子如下:
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg){
//更新UI
}
}
MyHandler mHandler = new MyHandler();
new Thread(){
public void run(){
mHandler.sendEmptyMessage(123);
};
}.start();
這裡規定了Handler
必須在主線程創建,因為只有在UI
線程創建才會讓Handler
關聯到已有的MessageQueue
。而MessageQueue
被封裝到Looper
中,而Looper
又通過ThreadLocal
封裝到一個線程中,最後相當於MessageQueue
關聯了一個線程。所以簡單來說就是Handler將消息投遞到一個關聯了線程的MessageQueue
中,然後Handler
在從MessageQueue
中取出消息,並且處理它。
我們看一下Handler的2個常用的方法
void handleMessage(Message msg) :
處理消息的方法
final boolean sendMessage(Message msg) :
立即發送消息
第一個方法 我們通常在UI
線程中執行,一般用來刷新UI
,至於如果創建了一個非靜態內部類產生對內存洩漏,建議參考這篇博客Handler引發的內存洩漏.第二個方法我們通常在子線程中執行,需要一個Handler的實例化對象,通常是由主線程去去傳遞給子線程。並且需要一個Message對象,指定他的msg.what
作為消息的標示,但是如果我們只是用Handler去處理一個消息的時候,選擇post方法是個更好的選擇,例子如下:
private Handler mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
//UI操作
...
}
});
}
}).start();
下面我們接著討論下消息的循環隊列MessageQueue
與包裝他的Looper
循環
上面提到了在UI
線程中創建並實例化Handler對象不需要Looper
和MessageQueue
,因為我們的應用在啟動的時候先執行了ActivityThreadMain
,在這個方法就是Java語言運行的入口public static void main(String [] args)
在這裡面創建了一個MainLooper
,創建的過程如下:
public static void main(string[] args){
//初始化
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
if(sMainThreadHandler == null){
sMainThreadHandler = thread.getHandler();
}
AsyncTask.init();
//動起來
Looper.loop();
}
這裡面並沒有MessageQueue的出現,我們可以看一看Looper類的源碼,來了解在初始化的時候發生了什麼有趣的事情。
public class Looper {
private static final ThreadLocal sThreadLocal = new ThreadLocal();
// Looper內的消息隊列
final MessageQueue mQueue;
// 當前線程
Thread mThread;
// 。。。其他屬性
// 每個Looper對象中有它的消息隊列,和它所屬的線程
private Looper() {
mQueue = new MessageQueue();
mRun = true;
mThread = Thread.currentThread();
}
// 我們調用該方法會在調用線程的TLS中創建Looper對象
public static final void prepare() {
if (sThreadLocal.get() != null) {
// 試圖在有Looper的線程中再次創建Looper將拋出異常
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
// 其他方法
}
我們一行行的看這段代碼,首先是實例化一個ThreadLocal對象,這個用來實現Looper
循環的本地化存儲,關於ThreadLocal
可以看這篇文章為什麼用ThreadLocal,簡而言之就是當多個線程同時訪問Looper
對象的時候,我們不用synchronized
同步機制來處理他,而是為每個線程創建一個自己的Looper
副本,A線程改變了A的looper
副本,不影響B線程的Looper
,從而比較高效的實現線程安全。後面幾句依次定義了MessageQueue
,並對Looper進行了私有化構造,在prepare
方法中將Looper對象設置給了sThreadLocal
這樣MessageQueue
包裝在了Looper對象中,同時通過ThreadLocal
使得線程和Looper關聯上,從而消息隊列與線程關聯上,並且不同的線程就不能訪問對方的消息隊列。
如下圖所示:
接著就是Looper.loop
循環執行起來,我們看一下,在loop
方法裡面執行了發生了什麼事情
public static final void loop() {
Looper me = myLooper(); //得到當前線程Looper
MessageQueue queue = me.mQueue; //得到當前looper的MQ
while (true) {
Message msg = queue.next(); // 取出message
if (msg != null) {
if (msg.target == null) {
return;
}
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
這是省略版的代碼,我們從這裡看出無限循環執行,首先從消息隊列中不斷取出消息,然後不斷msg
是否為空,msg.target
是否為空,不空的話,執行dispatchMessage
方法,這個方法是handler的一個方法,由此我們可以看出msg.target
是handler
的類型,至此,通過Looper.prepare
和Loop.loop
實現了MessageQueue,Looper,Handler
三者之間的關聯。而Handler
與Looper
,和MessageQueue
關聯則是在Handler的默認構造器中,通過Looper.getLooper
獲取loop對象,從而獲取MessageQueue
,其源碼如下:
public Handler(){
//直接把關聯looper的MQ作為自己的MQ,因此它的消息將發送到關聯looper的MQ上
mLooper = Looper.myLooper();
mQueue = mLooper.mQueue;
mCallback = null;
}
然後我們的流程圖可以多些內容,如下所示:
我們接下來看一下dispatchMessage()
方法,在該方法中實際上只是一個分發方法,如果Runable
類型的callback
為空,則執行handlerMessage
來處理消息,該方法為空,需要覆寫。如果不為空,則執行handleCallback
。實際上,如果我們用handle
的post
方法,則就執行了callback
,如果用sendMessage
,則就執行了handleMessage
這裡無論是post(Runnable callback)
還是handlerMessage
實際上都是在調用一個方法sendMessageDelayed(Message msg)
只不過handlerMessage
是直接接受一個參數,而Runable
callback
實際上是將這個Runable
對象賦給了Message
對象的callback
成員變量,最後將Message
對象插入消息隊列裡面。最後Looper不斷從MessageQueue中讀取消息,並且調用Handler的dispatchMessage消息,在根據callback是否為空,來采用不同的方法執行。Android消息機制分析到此結束。
我們這次知道了為什麼要在主線程中實例化Handler
對象才能更新UI
刷新,因為只有發送到UI
線程的消息,才能被UI
線程的handler
處理,如果我們要在非UI
線程中,實例化Handler,則必須先將線程變成LooperThread
,在實例化。也就是說執行如下的代碼:
Loop.prepare();
hander = new Handler;
Loop.loop
至於原因相信讀完上面的講解,應該知道。
現在我們看一下我們最開始的代碼,最後腦補一下Handler的工作流程。
class MyHandler extends Handler{
@Override
public void handleMessage(Message msg){
//更新UI
}
}
MyHandler mHandler = new MyHandler();
new Thread(){
public void run(){
mHandler.sendEmptyMessage(123);
};
}.start();
在Handler
實例化成mHandler
的時候,系統通過Handler
默認的構造函數完成了Handler
與Looper
的關聯,並通過Looper
關聯到了MessageQueue
。而主線程的Looper
則早在系統啟動的時候通過Loop.prepare
就已經構造完成了,並與UI
線程通過ThreadLocal
關聯起來,然後在新的線程中執行mHandler.sendEmptyMessage
,將Message
發送給了MessageQueue
,Looper.loop在循環的時候,不斷取出message,交給Handler處理,在我們覆寫的HandleMessage中,識別出我們發送的消息,將消息處理。當然這裡只是一個Empty消息,所以在handleMessage中沒有去執行msg.what的判斷。
一、運行時的狀態遇到一個這樣的要求:“不進行掃描操作,怎麼對指定的免密碼WIFI進行連接(之前沒有連接過)”,於是動手寫了一個Demo,如圖所示未
在WebView中使用JavaScript 如果你想要載入的頁面中用了JavaScript,你必須為你的WebView使能JavaScript。 一旦使能之後,你也可
上一篇上一篇介紹了完成Android輸入法的最小化步驟,它只能將按鍵對應的字符上屏。一般的東亞語言都有一個轉換的過程,比如漢語輸入拼音,需要由拼音轉成漢字再上屏。本文將在