HandlerThread存在的真正意義:一個線程執行多個線程的任務!

前言:要了解HandlerThread首先我們必須瞭解Handler消息機制,簡單回顧一下Handler機制。大家都知道Handler是通過發送Messege到一個消息隊列,然後通過Looper輪詢器來取出消息然後處理。處理的方法就是handlerMessegeXXX(方法名大概就是這個)。那麼這個處理方法執行的線程取決於什麼呢?就是取決於Looper輪詢器所在的線程。

本文不僅僅是教你如何使用HandlerThread,並且會告訴你爲何要使用它,它的特殊之處在哪裏?而不是簡單的一個異步任務然後主線程更新UI這麼簡單。

 

好了進入正題,HandlerThread其實就是一個線程,只不過內部封裝了Handler的相關消息處理機制,既然如此那麼它首先就使用Looper.prepare();爲自己創建了一個Looper,所以看了很多網上的文章都是把HandlerThread作爲Asynctask來執行異步任務,主線程刷新的操作,其實這樣根本就沒必要使用HandlerThread。它真正強大的地方並不是說異步執行,主線程刷新,而是創建一個handlerThread可以執行多個任務。當然這個是有個前提的(我們後文會提到),假如我們有個需求:在子線程開啓一個任務,這個任務有個回調也要在子線程執行(爲什麼會有這樣的需求,因爲如果回調過來的數據處理起來也是很耗時的,那麼就不能在主線程執行)。請問大家會怎麼去寫:這裏我寫了一個使用Asynctask的例子,並且通過驗證,回調會在主線程。當然我的例子是在前面提到的前提下執行的。

class MyTask extends AsyncTask<Void,Void,Void> implements Camera.PreviewCallback {
        /**
         * 需求:這裏要在子線程執行
         * @param voids
         * @return
         */
        @Override
        protected Void doInBackground(Void... voids) {
            Log.i("TAG",Thread.currentThread().getName()+"doInBackground..");
            //需求:打開相機,並且預覽相機採集的畫面
            Camera mCamera = Camera.open();
            SurfaceView mSurfaceView = new SurfaceView(AIDLActivity.this);
            SurfaceHolder holder = mSurfaceView.getHolder();
            try {
                mCamera.setPreviewDisplay(holder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            holder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    //這裏代表SurfaceView已經準備好,可以使用相機採集畫面了
                    new MyTask().execute();
                }
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                }
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                }
            });
            mCamera.addCallbackBuffer(new byte[1024 * 12]);
            mCamera.setPreviewCallback(this);
            return null;
        }

        /**
         * 需求:是這裏也要在子線程執行
         * @param data
         * @param camera
         */
        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.i("TAG",Thread.currentThread().getName()+"onPreviewFram..");
        }
    }

講解一下代碼意思:首先我在DoInBackground裏面開啓一個相機,然後給相機設置了一個回調,回調我是用MyTask實現了它要回調的接口,所以我傳的是this,最下面那個onPreviewFrame就是接口實現的回調方法。

明確告訴大家,這是我修改後的代碼,裏面很多邏輯問題,並且不能Copy去運行,但是不影響我們來了解接下來的要講解的內容。大家只要看裏面兩句打印的語句就行了。按照我們正常邏輯來說,兩句打印的ThreadName肯定都是子線程的名稱。但是並非如此,這裏doInBackground()確實是子線程的名稱,這裏相信大家都明白,但是onPreviewFrame打印的是主線程名稱。

這裏大家怎麼理解?如果是要按網上文章說的,在異步執行然後再主線程更新UI這不就實現了嗎?爲啥要用HandlerThread呢?我寫這個主要原因是這個例子很特殊,特殊在哪裏呢,你想想,這個回調裏面是不是有byte[] data這個字節數組,數組裏面就是相機採集到的每一幀的畫面,你想想我們要顯示畫面肯定要解碼是不是,這個是很耗時的,如果把這個放在主線程來處理,是不是會阻塞主線程?所以HandlerThread的強大就提現出來了。他可以讓這個回調也在子線程執行。

直接看代碼:這裏代碼包括網上那些文章教大家如何使用它,並且還告訴你爲什麼要用它,它的強大和特殊之處在哪裏。

public class XxxActivity extends AppCompatActivity {

    HandlerThread handlerThread;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_xxx);
        /**
         * 初始化handlerThread,給一個名字作爲標記這個線程
         */
        handlerThread = new HandlerThread("myHandlerThread");
        handlerThread.start();
        /**
         * 初始化handler,這裏重要的是在這個構造函數裏面穿了一個Looper。而這個Looper是handlerThread
         * 在run方法裏面定義的自己的Looper。
         */
        Handler handler = new Handler(handlerThread.getLooper());
        handler.post(new MyRunnable());
    }
    class MyRunnable implements Runnable,Camera.PreviewCallback {

        @Override
        public void run() {
            Log.i("TAG",Thread.currentThread().getName()+"doInBackground..");
            //需求:打開相機,並且預覽相機採集的畫面
            Camera mCamera = Camera.open();
            SurfaceView mSurfaceView = new SurfaceView(AIDLActivity.this);
            SurfaceHolder holder = mSurfaceView.getHolder();
            try {
                mCamera.setPreviewDisplay(holder);
            } catch (IOException e) {
                e.printStackTrace();
            }
            holder.addCallback(new SurfaceHolder.Callback() {
                @Override
                public void surfaceCreated(SurfaceHolder holder) {
                    //這裏代表SurfaceView已經準備好,可以使用相機採集畫面了
                    new MyTask().execute();
                }
                @Override
                public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
                }
                @Override
                public void surfaceDestroyed(SurfaceHolder holder) {
                }
            });
            mCamera.addCallbackBuffer(new byte[1024 * 12]);
            mCamera.setPreviewCallback(this);
        }

        @Override
        public void onPreviewFrame(byte[] data, Camera camera) {
            Log.i("TAG",Thread.currentThread().getName()+"onPreviewFram..");
        }
    }

   
}

同樣我是在異步任務類打開了相機,並且回調也是實現之後再異步任務類,但是兩個打印都是在子線程。

重點在這裏,爲什麼感覺跟上面使用AsyncTask看起來沒啥區別,但是回調卻不一樣?其實這裏就是我爲啥舉例一個特殊的例子,開啓相機這樣一個例子來處理。因爲相機內部處理回調其實也是用的Handler消息機制。源碼我就不貼了,大家自己去看。那麼使用Handler就必須要指定輪詢器,這個大家都懂。那爲什麼在Asynctask中指定就是主線程的輪詢器呢。因爲Camera在創建的時候就會判斷當前線程如果有Looper就指定Camera內部的EventHandler的Looper爲myLooper(),也就是子線程的Looper,如果爲空就指定爲mainLooper。是不是恍然大悟,那麼這裏就引出了一個概念也就是我文章前面說的那個前提(Looper共享的概念)也就是要使用HandlerThread來執行多個任務的話,首先我們需要Looper共享。就像相機的內部一樣它內部使用的是handler並且制定Looper的時候會判斷是否子線程存在Looper,而我們的HandlerThread剛好在run的時候就會爲自己Looper.prepare()創建一個Looper所以才能回調也在子線程,更合理的來說就是回調會在Looper所在的線程。

這就是一個線程會執行兩個任務的簡單實用!

這個文章僅僅只是用來作爲大家的參考,和我自己的學習筆記!感謝前輩們的分享!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章