前言:要了解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所在的线程。

这就是一个线程会执行两个任务的简单实用!

这个文章仅仅只是用来作为大家的参考,和我自己的学习笔记!感谢前辈们的分享!