1. 程式人生 > >Android面試收集錄8 HandlerThread詳解

Android面試收集錄8 HandlerThread詳解

ast 等待 androi color 們的 pause exceptio 虛擬 tee


1.前言

我們知道在Android系統中,我們執行完耗時操作都要另外開啟子線程來執行,執行完線程以後線程會自動銷毀。

想象一下如果我們在項目中經常要執行耗時操作,如果經常要開啟線程,接著又銷毀線程,

這無疑是很消耗性能的?那有什麽解決方法呢?

  1. 使用線程池
  2. 使用HandlerThread

本篇文章主要講解一下問題

  1. HandlerThread的使用場景以及怎樣使用HandlerThread?
  2. HandlerThread源碼分析


2.HandlerThread的使用場景以及怎樣使用HandlerThread?

使用場景

HandlerThread是Google幫我們封裝好的,可以用來執行多個耗時操作,而不需要多次開啟線程,

裏面是采用handler和Looper實現的

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?

  1. 創建HandlerThread的實例對象
HandlerThread handlerThread = new HandlerThread("myHandlerThread");

該參數表示線程的名字,可以隨便選擇。

  1. 啟動我們創建的HandlerThread線程
handlerThread.start();

3.將我們的handlerThread與Handler綁定在一起。 還記得是怎樣將Handler與線程對象綁定在一起的嗎?

其實很簡單,就是將線程的looper與Handler綁定在一起,代碼如下:

mThreadHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        checkForUpdate();
        if(isUpdate){
            mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
        }
    }
};

註意必須按照以上三個步驟來,下面在講解源碼的時候會分析其原因

完整測試代碼如下:

public class MainActivity extends AppCompatActivity {
    private TextView mTv;
    Handler mMainHandler = new Handler();
    private Handler mThreadHandler;
    private static final int MSG_UPDATE_INFO = 0x100;
    private HandlerThread mHandlerThread;
    private boolean isUpdate=true;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mTv = (TextView) findViewById(R.id.tv);
        initHandlerThread();
    }
    private void initHandlerThread() {
        mHandlerThread = new HandlerThread("xujun");
        mHandlerThread.start();
        mThreadHandler = new Handler(mHandlerThread.getLooper()) {
            @Override
            public void handleMessage(Message msg) {
                checkForUpdate();
                if(isUpdate){
                    mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
                }
            }
        };
    }
    /**
     * 模擬從服務器解析數據
     */
    private void checkForUpdate() {
        try {
            //模擬耗時
            Thread.sleep(1200);
            mMainHandler.post(new Runnable() {
                @Override
                public void run() {
                    String result = "實時更新中,當前股票行情:<font color=‘red‘>%d</font>";
                    result = String.format(result, (int) (Math.random() * 5000 + 1000));
                    mTv.setText(Html.fromHtml(result));
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    @Override
    protected void onResume() {
        isUpdate=true;
        super.onResume();
        mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
    }
    @Override
    protected void onPause() {
        super.onPause();
        isUpdate=false;
        mThreadHandler.removeMessages(MSG_UPDATE_INFO);
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mHandlerThread.quit();
        mMainHandler.removeCallbacksAndMessages(null);
    }
}

運行以上測試代碼,將可以看到如下效果圖(例子不太恰當,主要使用場景是在handleMessage中執行耗時操作)

技術分享圖片


3.HandlerThread源碼分析

官方源代碼如下,是基於sdk23的,可以看到,只有一百多行代碼而已

public class HandlerThread extends Thread {
    int mPriority;
    int mTid = -1;
    Looper mLooper;
    public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }
    /**
     * 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();
        Looper.prepare();
        //持有鎖機制來獲得當前線程的Looper對象
        synchronized (this) {
            mLooper = Looper.myLooper();
            //發出通知,當前線程已經創建mLooper對象成功,這裏主要是通知getLooper方法中的wait
            notifyAll();
        }
        //設置線程的優先級別
        Process.setThreadPriority(mPriority);
        //這裏默認是空方法的實現,我們可以重寫這個方法來做一些線程開始之前的準備,方便擴展
        onLooperPrepared();
        Looper.loop();
        mTid = -1;
    }
    public Looper getLooper() {
        if (!isAlive()) {
            return null;
        }
        // 直到線程創建完Looper之後才能獲得Looper對象,Looper未創建成功,阻塞
        synchronized (this) {
            while (isAlive() && mLooper == null) {
                try {
                    wait();
                } catch (InterruptedException e) {
                }
            }
        }
        return mLooper;
    }
    public boolean quit() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quit();
            return true;
        }
        return false;
    }
    public boolean quitSafely() {
        Looper looper = getLooper();
        if (looper != null) {
            looper.quitSafely();
            return true;
        }
        return false;
    }
    /**
     * Returns the identifier of this thread. See Process.myTid().
     */
    public int getThreadId() {
        return mTid;
    }
}

1)首先我們先來看一下它的構造方法

 public HandlerThread(String name) {
        super(name);
        mPriority = Process.THREAD_PRIORITY_DEFAULT;
    }
    public HandlerThread(String name, int priority) {
        super(name);
        mPriority = priority;
    }

有兩個構造方法,一個參數的和兩個參數的,name代表當前線程的名稱,priority為線程的優先級別

2)接著我們來看一下run()方法,在run方法裏面我們可以看到我們會初始化一個Looper,並設置線程的優先級別

public void run() {
    mTid = Process.myTid();
    Looper.prepare();
    //持有鎖機制來獲得當前線程的Looper對象
    synchronized (this) {
        mLooper = Looper.myLooper();
        //發出通知,當前線程已經創建mLooper對象成功,這裏主要是通知getLooper方法中的wait
        notifyAll();
    }
    //設置線程的優先級別
    Process.setThreadPriority(mPriority);
    //這裏默認是空方法的實現,我們可以重寫這個方法來做一些線程開始之前的準備,方便擴展
    onLooperPrepared();
    Looper.loop();
    mTid = -1;
}


  • 還記得我們前面我們說到使用HandlerThread的時候必須調用start()方法,接著才可以將我們的HandlerThread和我們的handler綁定在一起嗎?其實原因就是我們是在run()方法才開始初始化我們的looper,而我們調用HandlerThread的start()方法的時候,線程會交給虛擬機調度,由虛擬機自動調用run方法
mHandlerThread.start();
mThreadHandler = new Handler(mHandlerThread.getLooper()) {
    @Override
    public void handleMessage(Message msg) {
        checkForUpdate();
        if(isUpdate){
            mThreadHandler.sendEmptyMessage(MSG_UPDATE_INFO);
        }
    }
};


  • 這裏我們為什麽要使用鎖機制和notifyAll();,原因我們可以從getLooper()方法中知道


public Looper getLooper() {
    if (!isAlive()) {
        return null;
    }
    // 直到線程創建完Looper之後才能獲得Looper對象,Looper未創建成功,阻塞
    synchronized (this) {
        while (isAlive() && mLooper == null) {
            try {
                wait();
            } catch (InterruptedException e) {
            }
        }
    }
    return mLooper;
}

總結:在獲得mLooper對象的時候存在一個同步的問題,

    只有當線程創建成功並且Looper對象也創建成功之後才能獲得mLooper的值。

    這裏等待方法wait和run方法中的notifyAll方法共同完成同步問題。

 

3)接著我們來看一下quit方法和quitSafe方法

//調用這個方法退出Looper消息循環,及退出線程
public boolean quit() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quit();
        return true;
    }
    return false;
}
//調用這個方法安全地退出線程
@TargetApi(Build.VERSION_CODES.JELLY_BEAN_MR2)
public boolean quitSafely() {
    Looper looper = getLooper();
    if (looper != null) {
        looper.quitSafely();
        return true;
    }
    return false;
}

跟蹤這兩個方法容易知道只兩個方法最終都會調用MessageQueue的quit(boolean safe)方法

void quit(boolean safe) {
    if (!mQuitAllowed) {
        throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        if (mQuitting) {
            return;
        }
        mQuitting = true;
        //安全退出調用這個方法
        if (safe) {
            removeAllFutureMessagesLocked();
        } else {//不安全退出調用這個方法
            removeAllMessagesLocked();
        }
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

不安全的會調用removeAllMessagesLocked();這個方法,我們來看這個方法是怎樣處理的,

其實就是遍歷Message鏈表,移除所有信息的回調,並重置為null

private void removeAllMessagesLocked() {
    Message p = mMessages;
    while (p != null) {
        Message n = p.next;
        p.recycleUnchecked();
        p = n;
    }
    mMessages = null;
}

安全地會調用removeAllFutureMessagesLocked();這個方法,它會根據Message.when這個屬性,

判斷我們當前消息隊列是否正在處理消息,沒有正在處理消息的話,直接移除所有回調,正在處理的話,

等待該消息處理處理完畢再退出該循環。因此說quitSafe()是安全的,而quit()方法是不安全的,

因為quit方法不管是否正在處理消息,直接移除所有回調。


private void removeAllFutureMessagesLocked() {
    final long now = SystemClock.uptimeMillis();
    Message p = mMessages;
    if (p != null) {
        //判斷當前隊列中的消息是否正在處理這個消息,》沒有的話,直接移除所有回調
        if (p.when > now) {
            removeAllMessagesLocked();
        } else {//正在處理的話,等待該消息處理處理完畢再退出該循環
            Message n;
            for (;;) {
                n = p.next;
                if (n == null) {
                    return;
                }
                if (n.when > now) {
                    break;
                }
                p = n;
            }
            p.next = null;
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();
            } while (n != null);
        }
    }
} 


4.參考文章

https://github.com/LRH1993/android_interview/blob/master/android/basis/HandlerThread.md


Android面試收集錄8 HandlerThread詳解