Android 进阶15:HandlerThread 使用场景及源码解析

来源:互联网 发布:查找淘宝用户购买记录 编辑:程序博客网 时间:2024/05/28 01:35
  • 眼睛困得要死,但今天的计划不完成又怎么能睡呢?明日复明日,明日何其多啊!

读完本文你将了解:

    • HandlerThread 简介
    • HandlerThread 源码
    • HandlerThread 的使用场景
    • 举个栗子
    • 运行结果
    • 总结
    • 代码地址

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

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

HandlerThread 简介

源码解读 Android 消息机制( Message MessageQueue Handler Looper) 中我们介绍了,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() 方法。

运行结果

这里写图片描述

总结

这里写图片描述

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

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

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

代码地址

阅读全文
0 0
原创粉丝点击