1. 程式人生 > 其它 >android 使用post傳值 獲取的值為null_面試官:能說說HandlerThread的原理和使用場景嗎?...

android 使用post傳值 獲取的值為null_面試官:能說說HandlerThread的原理和使用場景嗎?...

技術標籤:android 使用post傳值 獲取的值為null

ae8f808c-2930-eb11-8da9-e4434bdf6706.png

初次看到HandlerThread的名字,我們可能會聯想到Handler和Thread這兩個類,沒錯,它其實就是跟Handler和Thread有莫大的關係。HandlerThread繼承自Thread,它本質上就是一個Thread,而且專門用來處理Handler的訊息。

一、HandlerThread簡介

看看官方對它的解釋:

Handy class for starting a new thread that has a looper. The looper can then be used to create handler classes. Note that start() must still be called.

大致就是說HandlerThread可以建立一個帶有looper的執行緒,looper物件可以用於建立Handler類來進行來進行排程,而且start()方法必須被呼叫。

在Android開發中,不熟悉多執行緒開發的人一想到要使用執行緒,可能就用new Thread(){…}.start()這樣的方式。實質上在只有單個耗時任務時用這種方式是可以的,但若是有多個耗時任務要序列執行呢?那不得要多次建立多次銷燬執行緒,這樣導致的代價是很耗系統資源,容易存在效能問題。那麼,怎麼解決呢?

我們可以只建立一個工作執行緒,然後在裡面迴圈處理耗時任務,建立過程如下:

Handler mHandler;private void createWorkerThread() {  new Thread() {      @Override        public void run() {            super.run();            Looper.prepare();             mHandler = new Handler(Looper.myLooper()) {                @Override                public void handleMessage(Message msg) {                    ......                }            };            Looper.loop();        }    }.start();}複製程式碼

在該工作執行緒中:

  • 呼叫 Looper.prepare()建立與當前執行緒繫結的Looper例項;
  • 使用上面建立的Looper生成Handler例項;
  • 呼叫Looper.loop()實現訊息迴圈;

然後透過Looper的迴圈,在Handler的handlerMessage()中進行非同步任務的迴圈處理。而這也正好是HandlerThread的實現。

@Overridepublic void run() {    mTid = Process.myTid();    Looper.prepare();    synchronized (this) {        mLooper = Looper.myLooper();        notifyAll();    }    Process.setThreadPriority(mPriority);    onLooperPrepared();    Looper.loop();    mTid = -1;}複製程式碼
  • HandlerThread本質上是一個執行緒類,它繼承了Thread;
  • HandlerThread有自己的內部Looper物件,通過Looper.loop()進行looper迴圈;
  • 通過獲取HandlerThread的looper物件傳遞給Handler物件,然後在handleMessage()方法中執行非同步任務;
  • 建立HandlerThread後必須呼叫HandlerThread.start()方法來啟動執行緒。

二、HandlerThread使用步驟

1、建立HandlerThread例項物件
HandlerThread handlerThread = new HandlerThread("Handler Thread");//HandlerThread handlerThread = new HandlerThread("Handler Thread",Process.THREAD_PRIORITY_DEFAULT);複製程式碼

HandlerThread預設有兩個建構函式,提供了執行緒名引數和執行緒優先順序引數的設定。

2、啟動HandlerThread執行緒
handlerThread.start();複製程式碼

通過start()方法就可以啟動一個HandlerThread了,該執行緒會不斷地迴圈執行。

3、通過Handler構建迴圈訊息處理機制
Handler workderHandler = new Handler(handlerThread.getLooper(), new Handler.Callback() {            @Override            public boolean handleMessage(Message msg) {                //執行在工作執行緒(子執行緒)中,用於實現自己的訊息處理                return true;            }        });複製程式碼

通過將HandlerThread繫結的Looper物件傳遞給Handler作為引數,構建一個非同步的Handler物件,為了能實現耗時任務的非同步執行,我們重寫了Handler的Callback介面的handleMessage()方法,當然也可以不重寫該方法,而通過post()方法進行耗時任務操作。

Handler workderHandler = new Handler(handlerThread.getLooper());workderHandler.post(new Runnable() {    @Override    public void run() {        //執行在工作執行緒(子執行緒)中,用於實現自己的訊息處理    }});複製程式碼

最後,我們就可以通過呼叫workerHandler以傳送訊息的形式傳送耗時任務到工作執行緒HandlerThread中去執行,實際上就是在Handler.Callback裡的handleMessage()中執行。

這裡要注意,在建立Handler作為HandlerThread執行緒訊息執行者的時候必須先呼叫start()方法,因為建立Handler所需要的Looper引數是從HandlerThread中獲得的,而Looper物件的賦值又是在HandlerThread的run()方法中建立。

三、HandlerThread使用例項

import android.app.Activity;import android.os.Bundle;import android.os.Handler;import android.os.Message;import android.view.View;import android.widget.Button;import android.widget.TextView;/** * Created by Administrator on 2016/9/18. */public class HandlerThreadActivity extends Activity implements Handler.Callback {    private DBHandlerThread mDBHandlerThread;    private Handler mUIHandler; //與UI執行緒相關聯的Handler    Button mBtnQuery;    TextView mTextResult;    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        mBtnQuery = (Button) findViewById(R.id.buttonQuery);        mTextResult = (TextView) findViewById(R.id.result);        mBtnQuery.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //將非同步耗時任務傳送到HandlerThread中                //Message msg = Message.obtain(null, DBHandlerThread.MSG_QUERY_FRIENDS);                //mDBHandlerThread.getWorkerHandler().sendMessage(msg);                mDBHandlerThread.queryFriends();            }        });        mUIHandler = new Handler(this);        initWorkerThread();    }    protected void initWorkerThread() {        mDBHandlerThread = new DBHandlerThread("Handler Thread");        mDBHandlerThread.setUIHandlerCallBack(mUIHandler);        mDBHandlerThread.start(); //start()後會執行Thread的run()方法    }    @Override    protected void onDestroy() {        super.onDestroy();        mDBHandlerThread.setUIHandlerCallBack(null);        mDBHandlerThread.quit();        mDBHandlerThread = null;    }    @Override    public boolean handleMessage(Message msg) {        switch (msg.what) {            case DBHandlerThread.MSG_QUERY_FRIENDS:                // update UI                break;        }        return false;    }}複製程式碼

DBHandlerThread類如下:

import android.os.Handler;import android.os.HandlerThread;import android.os.Message;/** * Created by Administrator on 2016/9/18. */public class DBHandlerThread extends HandlerThread implements Handler.Callback {    public static final int MSG_QUERY_FRIENDS = 100;    private Handler mWorkerHandler; //與工作執行緒相關聯的Handler    private Handler mUIHandler; //與UI執行緒相關聯的Handler    public DBHandlerThread(String name) {        super(name);    }    public DBHandlerThread(String name, int priority) {        super(name, priority);    }    public void setUIHandlerCallBack(Handler handler) {        this.mUIHandler = handler;    }    public Handler getWorkerHandler() {        return mWorkerHandler;    }    @Override    protected void onLooperPrepared() {        mWorkerHandler = new Handler(getLooper(), this);    }    @Override    public boolean handleMessage(Message msg) {        switch (msg.what) {            case MSG_QUERY_FRIENDS:                //...查詢資料庫操作...                Message message = Message.obtain(null, MSG_QUERY_FRIENDS);                mUIHandler.sendMessage(message); //通知UI更新                break;        }        return true;    }    public void queryFriends() {        Message msg = Message.obtain(null, MSG_QUERY_FRIENDS);        mWorkerHandler.sendMessage(msg);    }}複製程式碼

四、HandlerThread原始碼解析

HandlerThread的原始碼不多,先看下它的建構函式:

/** * Handy class for starting a new thread that has a looper. The looper can then be * used to create handler classes. Note that start() must still be called. */public class HandlerThread extends Thread {    int mPriority; //執行緒優先順序    int mTid = -1; //當前執行緒id    //當前執行緒持有的Looper物件    Looper mLooper;    public HandlerThread(String name) {        //呼叫父類預設的方法建立執行緒        super(name);        mPriority = Process.THREAD_PRIORITY_DEFAULT;    }   //帶優先順序引數的構造方法    public HandlerThread(String name, int priority) {        super(name);        mPriority = priority;    }    ...............}複製程式碼

從程式碼中得知,HandlerThread帶有兩個建構函式,可傳遞兩個引數,一個引數是name,指的是執行緒的名稱,另一個引數是priority,指的是執行緒優先順序。執行緒的優先順序的取值範圍為-20到19。優先順序高的獲得的CPU資源更多,反之則越少。-20代表優先順序最高,19最低。 我們可以根據自己的需要去設定執行緒的優先順序,也可以採用預設的優先順序,HandlerThread的預設優先順序是Process.THREAD_PRIORITY_DEFAULT,具體值為0。該優先順序是再run()方法中設定的,我們看它的run()方法:

public class HandlerThread extends Thread {  /**     * Call back method that can be explicitly overridden if needed to execute some     * setup before Looper loops.     */    protected void onLooperPrepared() {    }    @Override    public void run() {        mTid = Process.myTid(); //獲得當前執行緒的id        Looper.prepare(); //準備迴圈條件        //通過鎖機制來獲得當前執行緒的Looper物件        synchronized (this) {            mLooper = Looper.myLooper();            //喚醒等待執行緒            notifyAll();        }        //設定當前執行緒的優先順序        Process.setThreadPriority(mPriority);        //線上程迴圈之前做一些準備工作(子類可實現也可不實現)        onLooperPrepared();        //啟動loop        Looper.loop();        mTid = -1;    }}複製程式碼

run()方法主要是通過Looper.prepare()和Looper.loop()構造了一個迴圈執行緒。這裡要注意,在建立HandlerThread物件後必須呼叫其start()方法才能進行run()方法體的執行。

在Looper.prepare()執行後,Looper物件會被建立,然後通過同步鎖機制,將Looper物件賦值給HandlerThread的內部變數mLooper,並通過notifyAll()方法去喚醒等待執行緒。接著為執行緒賦予優先順序,然後執行onLooperPrepared()方法,該方法是一個空實現,留給我們必要的時候去重寫的,主要用來做一些初始化工作。最後通過執行Looper.loop()線上程中啟動訊息佇列。

我們看到在run()方法中進行了喚醒等待執行緒,為什麼要這麼做呢?答案就在getLooper()方法中:

/** * This method returns the Looper associated with this thread. If this thread not been started * or for any reason is isAlive() returns false, this method will return null. If this thread * has been started, this method will block until the looper has been initialized. * @return The looper. */public Looper getLooper() {   if (!isAlive()) { //判斷當前執行緒是否啟動       return null;   }   // If the thread has been started, wait until the looper has been created.   synchronized (this) {       while (isAlive() && mLooper == null) {           try {               wait(); //等待,直到另一個執行緒呼叫notify()或notifyAll()來喚醒它           } catch (InterruptedException e) {           }       }   }   return mLooper;}複製程式碼

該方法用來獲取當前子執行緒HandlerThread所關聯的Looper物件例項。首先判斷HandlerThread執行緒是否存活,如果沒有存活就直接返回null,否則繼續執行,進入同步塊並判斷Looper物件是否為空以及執行緒是否啟動,若都滿足,則呼叫wait()方法進入阻塞階段,直到Looper物件被成功建立並且通過notifyAll()方法喚醒該等待執行緒,最後才返回該Looper物件。

Looper物件的建立是在run()方法進行的,也即在子執行緒中執行的,而getLooper()方法是在UI執行緒中呼叫的,若不使用等待喚醒機制,我們就無法保證在UI執行緒中呼叫getLooper()方法時Looper物件已經被建立,會面臨一個同步的問題,所以HandlerThread就通過等待喚醒機制來解決該同步問題。

HandlerThread既然是一個迴圈執行緒,那麼怎麼退出呢?有兩種方式,分別是不安全的退出方法quit()和安全的退出方法quitSafely():

/** * Quits the handler thread's looper. * 

* Causes the handler thread's looper to terminate without processing any * more messages in the message queue. *

* Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. *

* Using this method may be unsafe because some messages may not be delivered * before the looper terminates. Consider using {@link #quitSafely} instead to ensure * that all pending work is completed in an orderly manner. *

* * @return True if the looper has been asked to quit or false if the * thread had not yet started running. * * @see #quitSafely */public boolean quit() { Looper looper = getLooper(); if (looper != null) { looper.quit(); return true; } return false;}複製程式碼
/** * Quits the handler thread's looper safely. * 

* Causes the handler thread's looper to terminate as soon as all remaining messages * in the message queue that are already due to be delivered have been handled. * Pending delayed messages with due times in the future will not be delivered. *

* Any attempt to post messages to the queue after the looper is asked to quit will fail. * For example, the {@link Handler#sendMessage(Message)} method will return false. *

* If the thread has not been started or has finished (that is if * {@link #getLooper} returns null), then false is returned. * Otherwise the looper is asked to quit and true is returned. *

* * @return True if the looper has been asked to quit or false if the * thread had not yet started running. */public boolean quitSafely() { Looper looper = getLooper(); if (looper != null) { looper.quitSafely(); return true; } return false;}複製程式碼

通過檢視開發者文件知道,quit()方法在API Level 5時被新增,而quitSafely()方法在API Level 18時才被新增。

quit()方法,主要是把MessageQueue中所有的訊息全部清空,無論是延遲訊息還是非延遲訊息。

而quitSafely()方法只會清空MessageQueue中所有的延遲訊息,並將所有的非延遲訊息繼續分發出去,最後等到Handler處理完後才停止Looper迴圈。

這裡的延遲訊息是指通過sendMessageDelayed或通過postDelayed等方法傳送的訊息。

五、HandlerThread應用場景

HandlerThread比較適用於單執行緒+非同步佇列的場景,比如IO讀寫操作資料庫、檔案等,耗時不多而且也不會產生較大的阻塞。對於網路IO操作,HandlerThread並不適合,因為它只有一個執行緒,還得排隊一個一個等著。

這裡單執行緒+非同步佇列模型的使用場景還舉幾個例子:

  • 應用剛啟動時的資料初始化操作,如果開啟多個執行緒同時執行,有可能爭奪UI執行緒的CPU執行時間,造成卡頓,而使用該模型,通過設定優先順序就可以將同步工作順序的執行,而又不影響UI的初始化;
  • 2.從資料庫中讀取資料展現在ListView中,通過Handler的postAtFrontOfQueue方法,快速將讀取操作加入佇列前端執行,必要時返回給主執行緒更新UI;
  • 3.HandlerThread應用在IntentService中,IntentService是一個需要長時間執行任務Service,優先順序要比執行緒高。

最後

有需要更多資料的朋友可以私信我【進階】我可以分享給你。

直接點選即可領取

Android學習PDF+架構視訊+面試文件+原始碼筆記

如果你有其他需要的話,也可以在 GitHub 上檢視,下面的資料也會陸續上傳到Github

330頁PDFAndroid核心筆記

b48f808c-2930-eb11-8da9-e4434bdf6706.png