美文网首页Android开发经验谈Android开发Android开发
Android 进阶之HandlerThread 使用场景及源码

Android 进阶之HandlerThread 使用场景及源码

作者: 881ef7b85f62 | 来源:发表于2019-01-21 20:33 被阅读8次
    • 眼睛困得要死,但今天的计划不完成又怎么能睡呢?明日复明日,明日何其多啊!

    为了避免 ANR,我们常常需要在线程中做耗时操作,然后把结果抛到主线程进行处理。

    Android 提供了多种用于这种场景的组件,其中一种就是本篇文章要介绍的 HandlerThread。

    HandlerThread 简介

    我们都知道Handler 必须要和 Looper 中结合使用,尤其在子线程中创建 Handler 的话,需要这样写:

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

    可以看到,非常繁琐,一层套一层看着也不美观。

    HandlerThread 就是为了帮我们免去写上面那样的代码而生的。

    官方文档对它的介绍:

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

    有这么神奇?去瞅瞅它的源码。

    HandlerThread 源码

    HandlerThread 源码非常简单,看起来 so easy:

    public 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,创建后别忘了调用 start()

    ②在 run() 方法中创建了 Looper,调用 onLooperPrepared 后开启了循环

    ③我们要做的就是在子类中重写 onLooperPrepared,做一些初始化工作

    ④在创建 HandlerThread 时可以指定优先级,注意这里的参数是 Process.XXX 而不是 Thread.XXX

    Process.setThreadPriority(int) A Linux priority level, from -20 for highest scheduling priority to 19 for lowest scheduling priority.

    可选的值如下:

    public static final int THREAD_PRIORITY_DEFAULT = 0;
    public static final int THREAD_PRIORITY_LOWEST = 19;
    public static final int THREAD_PRIORITY_BACKGROUND = 10;
    public static final int THREAD_PRIORITY_FOREGROUND = -2;
    public static final int THREAD_PRIORITY_DISPLAY = -4;
    public static final int THREAD_PRIORITY_URGENT_DISPLAY = -8;
    public static final int THREAD_PRIORITY_AUDIO = -16;
    

    HandlerThread 的使用场景

    我们知道,HandlerThread 所做的就是在新开的子线程中创建了 Looper,那它的使用场景就是 Thread + Looper 使用场景的结合,即:在子线程中执行耗时的、可能有多个任务的操作

    比如说多个网络请求操作,或者多文件 I/O 等等。

    使用 HandlerThread 的典型例子就是 IntentService,我们下篇文章介绍它。

    举个栗子

    我们写一个使用 HandlerThread 实现子线程完成多个下载任务的 demo。

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

    /**
     * Description:
     * <br> 继承 HandlerThread 模拟下载线程
     * <p>
     * <br> Created by shixinzhang on 17/6/7.
     * <p>
     * <br> Email: shixinzhang2016@gmail.com
     * <p>
     * <a  href="https://about.me/shixinzhang">About me</a>
     */
    
    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 中做了以下工作:

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

    调用 Activity 的代码:

    /**
     * Description:
     * <br> HandlerThread 示例程序
     * <p>
     * <br> Created by shixinzhang on 17/6/7.
     * <p>
     * <br> Email: shixinzhang2016@gmail.com
     * <p>
     * <a  href="https://about.me/shixinzhang">About me</a>
     */
    
    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;
        }
    }
    

    布局文件:

    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  android:layout_width="match_parent"
                  android:layout_height="match_parent"
                  android:gravity="center_horizontal"
                  android:orientation="vertical"
                  android:padding="8dp">
    
        <TextView
            android:id="@+id/tv_start_msg"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:text="下载开始信息"/>
    
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/colorAccent"/>
    
        <TextView
            android:id="@+id/tv_finish_msg"
            android:layout_width="match_parent"
            android:layout_height="200dp"
            android:layout_marginTop="8dp"
            android:text="下载完成信息"/>
    
        <View
            android:layout_width="match_parent"
            android:layout_height="1dp"
            android:background="@color/colorAccent"/>
    
        <Button
            android:id="@+id/btn_start_download"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="8dp"
            android:text="开始下载"/>
    </LinearLayout>
    

    重点是 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?");
        }
    

    在这个方法中我们创建一个 DownloadThread,也就是 HandlerThread,然后传入 UI 线程中的 Handler。

    最后在按钮的点击事件中调用了 start() 方法。

    运行结果

    总结

    image

    上面的例子中 HandlerThread 配合一个主线程 Handler 完成了在子线程中串行执行任务,同时在主线程中反馈状态的功能。

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

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

    代码地址

    喜欢的话请帮忙转发一下能让更多有需要的人看到吧,有些技术上的问题大家可以多探讨一下。

    以上Android资料以及更多Android相关资料及面试经验可在QQ群里获取:936903570。有加群的朋友请记得备注上简书,谢谢。

    相关文章

      网友评论

        本文标题:Android 进阶之HandlerThread 使用场景及源码

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