Android多執行緒-----非同步(Handlers)
一、為什麼要使用Handlers?
因為,我們當我們的主執行緒佇列,如果處理一個訊息超過5秒,android 就會丟擲一個 ANP(無響應)的訊息;所以,我們需要把一些要處理比較長的訊息,放在一個單獨執行緒裡面處理,把處理以後的結果,返回給主執行緒執行,就需要用的Handler來進行執行緒建的通訊。
Message物件封裝了所有的訊息,而這些訊息的操作需要Handler(訊息處理類)類完成。什麼是handler?handler起到了處理MQ上的訊息的作用(只處理由自己發出的訊息),即通知MQ它要執行一個任務(sendMessage),並在loop到自己的時候執行該任務(handleMessage),整個過程是非同步的
二、訊息類:Message類
android.os.Message的主要功能是進行訊息的封裝,同時可以指定訊息的操作形式,Message類定義的變數和常用方法如下:
(1)public int what:變數,用於定義此Message屬於何種操作
(2)public Object obj:變數,用於定義此Message傳遞的資訊資料,通過它傳遞資訊
(3)public int arg1:變數,傳遞一些整型資料時使用
(4)public int arg2:變數,傳遞一些整型資料時使用
(5)public Handler getTarget():普通方法,取得操作此訊息的Handler物件。
在整個訊息處理機制中,message又叫task,封裝了任務攜帶的資訊和處理該任務的handler。message的用法比較簡單,但是有這麼幾點需要注意:
(1)儘管Message有public的預設構造方法,但是你應該通過Message.obtain()來從訊息池中獲得空訊息物件,以節省資源。
(2)如果你的message只需要攜帶簡單的int資訊,請優先使用Message.arg1和Message.arg2來傳遞資訊,這比用Bundle更省記憶體
(3)使用message.what來標識資訊,以便用不同方式處理message。
(4)使用setData()存放Bundle物件。
三、訊息通道:Looper
在使用Handler處理Message時,需要Looper(通道)來完成。在一個Activity中,系統會自動幫使用者啟動Looper物件,而在一個使用者自定義的類中,則需要使用者手工呼叫Looper類中的方法,然後才可以正常啟動Looper物件。Looper的字面意思是“迴圈者”,它被設計用來使一個普通執行緒變成Looper執行緒。所謂Looper執行緒就是迴圈工作的執行緒。在程式開發中(尤其是GUI開發中),我們經常會需要一個執行緒不斷迴圈,一旦有新任務則執行,執行完繼續等待下一個任務,這就是Looper執行緒。使用Looper類建立Looper執行緒很簡單:
public class LooperThread extends Thread {
@Override
public void run() {
// 將當前執行緒初始化為Looper執行緒
Looper.prepare();
// ...其他處理,如例項化handler
// 開始迴圈處理訊息佇列
Looper.loop();
}
}
通過上面兩行核心程式碼,你的執行緒就升級為Looper執行緒了!那麼這兩行程式碼都做了些什麼呢?
1)Looper.prepare():建立Loop而物件。
現在你的執行緒中有一個Looper物件,它的內部維護了一個訊息佇列MQ。注意,一個Thread只能有一個Looper物件;prepare()背後的工作方式一目瞭然,其核心就是將looper物件定義為ThreadLocal。
2)Looper.loop():迴圈獲取MQ中的訊息,併發送給相應Handler物件。
呼叫loop方法後,Looper執行緒就開始真正工作了,它不斷從自己的MQ中取出隊頭的訊息(也叫任務)執行。
3)Looper類其他方法:
Looper.myLooper()得到當前執行緒looper物件
getThread()得到looper物件所屬執行緒
quit()方法結束looper迴圈
4)綜上,Looper有以下幾個要點:
每個執行緒有且只能有一個Looper物件,它是一個ThreadLocal
Looper內部有一個訊息佇列,loop()方法呼叫後執行緒開始不斷從佇列中取出訊息執行
Looper使一個執行緒變成Looper執行緒。
四、 訊息操作類:Handler類
Handler建立的時候肯定會在一個執行緒當中(主執行緒或者子執行緒),並且建立一個Looper例項與此執行緒繫結(無論是系統幫我們建立或者通過prepare自己繫結),在Looper中維護一個訊息佇列,然後looper迴圈的從訊息佇列中讀取訊息執行(在訊息佇列所線上程執行)。這就是整個Handler的執行機制了。
1、通過Handler有很多種傳送訊息的方式:
post(Runnable)
postAtTime(Runnable, long)
postDelayed(Runnable, long)
sendEmptyMessage(int)
sendMessage(Message)
sendMessageAtTime(Message, long)
sendMessageDelayed(Message, long)
其實無論是通過post的方式或者send的方式,最後都是通過(但其實post發出的Runnable物件最後都被封裝成message物件了)public final boolean sendMessageDelayed(Message msg, long delayMillis)發出的
2、Handler擁有下面兩個重要的特點:
1)handler可以在任意執行緒傳送訊息,這些訊息會被新增到關聯的MQ上,見原始碼:
2)訊息的處理是通過核心方法dispatchMessage(Message msg)與鉤子方法handleMessage(Message msg)完成的,handler是在它關聯的looper執行緒中處理訊息的。
這就解決了android最經典的不能在其他非主執行緒中更新UI的問題。
3、使用
public class DownloadActivity extends AppCompatActivity implements View.OnClickListener {
private TextView downloadTV;
private Button downloadBtn;
private Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
downloadTV.setText("下載完成");
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_download);
bind();
}
private void bind() {
downloadBtn=findViewById(R.id.download_btn);
downloadTV=findViewById(R.id.download_tv);
downloadBtn.setOnClickListener(this);
}
@Override
public void onClick(View v) {
switch (v.getId()){
case R.id.download_btn:
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(1);
}
}).start();
break;
}
}
}