美文网首页
Android多线程之HandlerThread

Android多线程之HandlerThread

作者: Leon_hy | 来源:发表于2018-03-26 14:18 被阅读14次

    原文链接 张拭心的博客
    Android如果在主线程做耗时操作(IO操作、下载)会造成ANR,所以会在子线程中处理耗时操作,最后通知主线程更新。在Android中提供了很多机制解决,今天要讲的HanderThread就是其中的一种。

    HandlerThread 简介

    在Android的消息机制中,我们知道Handler必须和Looper配合使用,尤其是在子线程中使用,必须先Looper.prepare在子线程中new一个Looper对象,然后才能new一个Handler,最后调用Looper.loop开启消息循环,如下:

    class LooperThread extends Thread {
        public Handler mHandler;
          public void run() {
              Looper.prepare();
              mHandler = new Handler() {
                  public void handleMessage(Message msg) {
                      // 这里处理消息
                  }
              };
              Looper.loop();
          }
    

    采用这种方式感觉一层套一层非常繁琐,所以Android封装好了HandlerThread来简化流程。
    官方文档对它的介绍:

    HandlerThread 是一个包含 Looper 的 Thread,我们可以直接使用这个 Looper 创建 Handler。
    

    HandlerThread 源码

    ublic class HandlerThread extends Thread {
        int mPriority;
        int mTid = -1;
        Looper mLooper;
    
        public HandlerThread(String name) {
            super(name);
            mPriority = Process.THREAD_PRIORITY_DEFAULT;
        }
    
        //也可以指定线程的优先级,注意使用的是 android.os.Process 而不是 java.lang.Thread 的优先级!
        public HandlerThread(String name, int priority) {
            super(name);
            mPriority = priority;
        }
    
        // 子类需要重写的方法,在这里做一些执行前的初始化工作
        protected void onLooperPrepared() {
        }
    
        //获取当前线程的 Looper
        //如果线程不是正常运行的就返回 null
        //如果线程启动后,Looper 还没创建,就 wait() 等待 创建 Looper 后 notify
        public Looper getLooper() {
            if (!isAlive()) {
                return null;
            }
    
            synchronized (this) {
                while (isAlive() && mLooper == null) {    //循环等待
                    try {
                        wait();
                    } catch (InterruptedException e) {
                    }
                }
            }
            return mLooper;
        }
    
        //调用 start() 后就会执行的 run()
        @Override
        public void run() {
            mTid = Process.myTid();
            Looper.prepare();            //帮我们创建了 Looepr
            synchronized (this) {
                mLooper = Looper.myLooper();
                notifyAll();    //Looper 已经创建,唤醒阻塞在获取 Looper 的线程
            }
            Process.setThreadPriority(mPriority);
            onLooperPrepared();    
            Looper.loop();        //开始循环
            mTid = -1;
        }
    
        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;
        }
    
        public int getThreadId() {
            return mTid;
        }
    }
    

    根据源码我们可以知道,HandlerThread就是一个Thread,在run()方法里面创建了一个Looper对象并开启循环。

    HandlerThread 的使用场景

    我们知道,HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作
    比如说多个网络请求操作,或者多文件 I/O 等等。
    使用 HandlerThread 的典型例子就是 IntentService,我们下篇文章介绍它。

    我们写一个使用 HandlerThread 实现子线程完成多个下载任务的 demo。
    先创建一个 HandlerThread 子类,它有两个 Handler 类型的成员变量,一个是用于在子线程传递、执行任务,另一个用于外部传入,在主线程显示下载状态:

    public class DownloadThread extends HandlerThread implements Handler.Callback {
    
        private final String TAG = this.getClass().getSimpleName();
        private final String KEY_URL = "url";
        public static final int TYPE_START = 1;
        public static final int TYPE_FINISHED = 2;
    
        private Handler mWorkerHandler;
        private Handler mUIHandler;
        private List<String> mDownloadUrlList;
    
        public DownloadThread(final String name) {
            super(name);
        }
        @Override
        protected void onLooperPrepared() {    //执行初始化任务
            super.onLooperPrepared();
            mWorkerHandler = new Handler(getLooper(), this);    //使用子线程中的 Looper
            if (mUIHandler == null) {
                throw new IllegalArgumentException("Not set UIHandler!");
            }
    
            //将接收到的任务消息挨个添加到消息队列中
            for (String url : mDownloadUrlList) {
                Message message = mWorkerHandler.obtainMessage();
                Bundle bundle = new Bundle();
                bundle.putString(KEY_URL, url);
                message.setData(bundle);
                mWorkerHandler.sendMessage(message);
            }
        }
        public void setDownloadUrls(String... urls) {
            mDownloadUrlList = Arrays.asList(urls);
        }
        public Handler getUIHandler() {
            return mUIHandler;
        }
        //注入主线程 Handler
        public DownloadThread setUIHandler(final Handler UIHandler) {
            mUIHandler = UIHandler;
            return this;
        }
        /**
         * 子线程中执行任务,完成后发送消息到主线程
         *
         * @param msg
         * @return
         */
        @Override
        public boolean handleMessage(final Message msg) {
            if (msg == null || msg.getData() == null) {
                return false;
            }
            String url = (String) msg.getData().get(KEY_URL);
            //下载开始,通知主线程
            Message startMsg = mUIHandler.obtainMessage(TYPE_START, "\n 开始下载 @" + DateUtils.getCurrentTime() + "\n" + url);
            mUIHandler.sendMessage(startMsg);
            SystemClock.sleep(2000);    //模拟下载
            //下载完成,通知主线程
            Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, "\n 下载完成 @" + DateUtils.getCurrentTime() + "\n" + url);
            mUIHandler.sendMessage(finishMsg);
    
            return true;
        }
    
        @Override
        public boolean quitSafely() {
            mUIHandler = null;
            return super.quitSafely();
        }
    }
    

    可以看到,DownloadThread 中做了以下工作:

    1. 创建一个子线程 Handler
    2. 然后在 onLooperPrepared()中初始化 Handler,使用的是 HandlerThread 创建的 Looper
      同时将外部传入的下载 url 以 Message 的方式发送到子线程中的 MessageQueue 中
    3. 这样当调用 DownloadThread.start() 时,子线程中的 Looper 开始工作,会按顺序取出消息队列中的队列处理,然后调用子线程的 Handler 处理
    4. 也就是上面的 handleMessage() 方法,在这个方法中进行耗时任务
    5. 然后通过 mUIHandler 将下载状态信息传递到主线程

    调用 Activity 的代码:

    public class HandlerThreadActivity extends AppCompatActivity implements Handler.Callback {
    
        @BindView(R.id.tv_start_msg)
        TextView mTvStartMsg;
        @BindView(R.id.tv_finish_msg)
        TextView mTvFinishMsg;
        @BindView(R.id.btn_start_download)
        Button mBtnStartDownload;
    
        private Handler mUIHandler;
        private DownloadThread mDownloadThread;
    
        @Override
        protected void onCreate(@Nullable final Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_handler_thread_test);
            ButterKnife.bind(this);
            init();
        }
    
        private void init() {
            mUIHandler = new Handler(this);
            mDownloadThread = new DownloadThread("下载线程");
            mDownloadThread.setUIHandler(mUIHandler);
            mDownloadThread.setDownloadUrls("http://pan.baidu.com/s/1qYc3EDQ",
                                "http://bbs.005.tv/thread-589833-1-1.html", "http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?");
        }
    
        @OnClick(R.id.btn_start_download)
        public void startDownload() {
            mDownloadThread.start();
            mBtnStartDownload.setText("正在下载");
            mBtnStartDownload.setEnabled(false);
        }
    
        //主线程中的 Handler 处理消息的方法
        @Override
        public boolean handleMessage(final Message msg) {
            switch (msg.what) {
                case DownloadThread.TYPE_FINISHED:
                    mTvFinishMsg.setText(mTvFinishMsg.getText().toString() + "\n " + msg.obj);
                    break;
                case DownloadThread.TYPE_START:
                    mTvStartMsg.setText(mTvStartMsg.getText().toString() + "\n " + msg.obj);
                    break;
            }
            return true;
        }
    }
    

    如果用一句话总结 HandlerThread 的特点:

    它就是一个帮我们创建 Looper 的线程,让我们可以直接在线程中使用 Handler 来处理异步任务。

    相关文章

      网友评论

          本文标题:Android多线程之HandlerThread

          本文链接:https://www.haomeiwen.com/subject/qtkrcftx.html