1. 程式人生 > >Android多執行緒-----非同步(Handlers)

Android多執行緒-----非同步(Handlers)

一、為什麼要使用Handlers?

因為,我們當我們的主執行緒佇列,如果處理一個訊息超過5秒,android 就會丟擲一個 ANP(無響應)的訊息;所以,我們需要把一些要處理比較長的訊息,放在一個單獨執行緒裡面處理,把處理以後的結果,返回給主執行緒執行,就需要用的Handler來進行執行緒建的通訊。

Message物件封裝了所有的訊息,而這些訊息的操作需要Handler(訊息處理類)類完成。什麼是handler?handler起到了處理MQ上的訊息的作用(只處理由自己發出的訊息),即通知MQ它要執行一個任務(sendMessage),並在loop到自己的時候執行該任務(handleMessage),整個過程是非同步的

handler建立時會關聯一個looper,預設的構造方法將關聯當前執行緒的looper,不過這也是可以set的

二、訊息類: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;
        }
    }
}