• 眼睛困得要死,但今天的计划不完成又怎么能睡呢?明日复明日,明日何其多啊!

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

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

HandlerThread 简介

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

class LooperThread extends Thread {
    public Handler mHandler;
  public void <span class="hljs-function"><span class="hljs-title">run</span></span>() {
      Looper.prepare();

      mHandler = new <span class="hljs-function"><span class="hljs-title">Handler</span></span>() {
          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 <span class="hljs-function"><span class="hljs-title">onLooperPrepared</span></span>() {
}

//获取当前线程的 Looper
//如果线程不是正常运行的就返回 null
//如果线程启动后,Looper 还没创建,就 <span class="hljs-built_in">wait</span>() 等待 创建 Looper 后 notify
public Looper <span class="hljs-function"><span class="hljs-title">getLooper</span></span>() {
    <span class="hljs-keyword">if</span> (!isAlive()) {
        <span class="hljs-built_in">return</span> null;
    }

    synchronized (this) {
        <span class="hljs-keyword">while</span> (isAlive() &amp;&amp; mLooper == null) {    //循环等待
            try {
                <span class="hljs-built_in">wait</span>();
            } catch (InterruptedException e) {
            }
        }
    }
    <span class="hljs-built_in">return</span> mLooper;
}

//调用 start() 后就会执行的 run()
@Override
public void <span class="hljs-function"><span class="hljs-title">run</span></span>() {
    mTid = Process.myTid();
    Looper.prepare();            //帮我们创建了 Looepr
    synchronized (this) {
        mLooper = Looper.myLooper();
        notifyAll();    //Looper 已经创建,唤醒阻塞在获取 Looper 的线程
    }
    Process.setThreadPriority(mPriority);
    onLooperPrepared();    
    Looper.loop();        //开始循环
    mTid = -1;
}

public boolean <span class="hljs-function"><span class="hljs-title">quit</span></span>() {
    Looper looper = getLooper();
    <span class="hljs-keyword">if</span> (looper != null) {
        looper.quit();
        <span class="hljs-built_in">return</span> <span class="hljs-literal">true</span>;
    }
    <span class="hljs-built_in">return</span> <span class="hljs-literal">false</span>;
}

public boolean <span class="hljs-function"><span class="hljs-title">quitSafely</span></span>() {
    Looper looper = getLooper();
    <span class="hljs-keyword">if</span> (looper != null) {
        looper.quitSafely();
        <span class="hljs-built_in">return</span> <span class="hljs-literal">true</span>;
    }
    <span class="hljs-built_in">return</span> <span class="hljs-literal">false</span>;
}

public int <span class="hljs-function"><span class="hljs-title">getThreadId</span></span>() {
    <span class="hljs-built_in">return</span> 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 = <span class="hljs-string">"url"</span>;
public static final int TYPE_START = 1;
public static final int TYPE_FINISHED = 2;

private Handler mWorkerHandler;
private Handler mUIHandler;
private List&lt;String&gt; mDownloadUrlList;

public DownloadThread(final String name) {
    super(name);
}

@Override
protected void <span class="hljs-function"><span class="hljs-title">onLooperPrepared</span></span>() {    //执行初始化任务
    super.onLooperPrepared();
    mWorkerHandler = new Handler(getLooper(), this);    //使用子线程中的 Looper
    <span class="hljs-keyword">if</span> (mUIHandler == null) {
        throw new IllegalArgumentException(<span class="hljs-string">"Not set UIHandler!"</span>);
    }

    //将接收到的任务消息挨个添加到消息队列中
    <span class="hljs-keyword">for</span> (String url : mDownloadUrlList) {
        Message message = mWorkerHandler.obtainMessage();
        Bundle bundle = new Bundle();
        bundle.putString(KEY_URL, url);
        message.setData(bundle);
        mWorkerHandler.sendMessage(message);
    }
}

public void <span class="hljs-built_in">set</span>DownloadUrls(String... urls) {
    mDownloadUrlList = Arrays.asList(urls);
}

public Handler <span class="hljs-function"><span class="hljs-title">getUIHandler</span></span>() {
    <span class="hljs-built_in">return</span> mUIHandler;
}

//注入主线程 Handler
public DownloadThread <span class="hljs-built_in">set</span>UIHandler(final Handler UIHandler) {
    mUIHandler = UIHandler;
    <span class="hljs-built_in">return</span> this;
}

/**
 * 子线程中执行任务,完成后发送消息到主线程
 *
 * @param msg
 * @<span class="hljs-built_in">return</span>
 */
@Override
public boolean handleMessage(final Message msg) {
    <span class="hljs-keyword">if</span> (msg == null || msg.getData() == null) {
        <span class="hljs-built_in">return</span> <span class="hljs-literal">false</span>;
    }

    String url = (String) msg.getData().get(KEY_URL);

    //下载开始,通知主线程
    Message startMsg = mUIHandler.obtainMessage(TYPE_START, <span class="hljs-string">"\n 开始下载 @"</span> + DateUtils.getCurrentTime() + <span class="hljs-string">"\n"</span> + url);
    mUIHandler.sendMessage(startMsg);

    SystemClock.sleep(2000);    //模拟下载

    //下载完成,通知主线程
    Message finishMsg = mUIHandler.obtainMessage(TYPE_FINISHED, <span class="hljs-string">"\n 下载完成 @"</span> + DateUtils.getCurrentTime() + <span class="hljs-string">"\n"</span> + url);
    mUIHandler.sendMessage(finishMsg);

    <span class="hljs-built_in">return</span> <span class="hljs-literal">true</span>;
}

@Override
public boolean <span class="hljs-function"><span class="hljs-title">quitSafely</span></span>() {
    mUIHandler = null;
    <span class="hljs-built_in">return</span> 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);
    <span class="hljs-built_in">set</span>ContentView(R.layout.activity_handler_thread_test);
    ButterKnife.bind(this);
    init();
}

private void <span class="hljs-function"><span class="hljs-title">init</span></span>() {
    mUIHandler = new Handler(this);
    mDownloadThread = new DownloadThread(<span class="hljs-string">"下载线程"</span>);
    mDownloadThread.setUIHandler(mUIHandler);
    mDownloadThread.setDownloadUrls(<span class="hljs-string">"http://pan.baidu.com/s/1qYc3EDQ"</span>,
                        <span class="hljs-string">"http://bbs.005.tv/thread-589833-1-1.html"</span>, <span class="hljs-string">"http://list.youku.com/show/id_zc51e1d547a5b11e2a19e.html?"</span>);
}

@OnClick(R.id.btn_start_download)
public void <span class="hljs-function"><span class="hljs-title">startDownload</span></span>() {
    mDownloadThread.start();
    mBtnStartDownload.setText(<span class="hljs-string">"正在下载"</span>);
    mBtnStartDownload.setEnabled(<span class="hljs-literal">false</span>);
}

//主线程中的 Handler 处理消息的方法
@Override
public boolean handleMessage(final Message msg) {
    switch (msg.what) {
        <span class="hljs-keyword">case</span> DownloadThread.TYPE_FINISHED:
            mTvFinishMsg.setText(mTvFinishMsg.getText().toString() + <span class="hljs-string">"\n "</span> + msg.obj);
            <span class="hljs-built_in">break</span>;
        <span class="hljs-keyword">case</span> DownloadThread.TYPE_START:
            mTvStartMsg.setText(mTvStartMsg.getText().toString() + <span class="hljs-string">"\n "</span> + msg.obj);
            <span class="hljs-built_in">break</span>;
    }
    <span class="hljs-built_in">return</span> <span class="hljs-literal">true</span>;
}

}

复制代码

布局文件:

<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">
&lt;TextView
    android:id=<span class="hljs-string">"@+id/tv_start_msg"</span>
    android:layout_width=<span class="hljs-string">"match_parent"</span>
    android:layout_height=<span class="hljs-string">"200dp"</span>
    android:text=<span class="hljs-string">"下载开始信息"</span>/&gt;

&lt;View
    android:layout_width=<span class="hljs-string">"match_parent"</span>
    android:layout_height=<span class="hljs-string">"1dp"</span>
    android:background=<span class="hljs-string">"@color/colorAccent"</span>/&gt;

&lt;TextView
    android:id=<span class="hljs-string">"@+id/tv_finish_msg"</span>
    android:layout_width=<span class="hljs-string">"match_parent"</span>
    android:layout_height=<span class="hljs-string">"200dp"</span>
    android:layout_marginTop=<span class="hljs-string">"8dp"</span>
    android:text=<span class="hljs-string">"下载完成信息"</span>/&gt;

&lt;View
    android:layout_width=<span class="hljs-string">"match_parent"</span>
    android:layout_height=<span class="hljs-string">"1dp"</span>
    android:background=<span class="hljs-string">"@color/colorAccent"</span>/&gt;

&lt;Button
    android:id=<span class="hljs-string">"@+id/btn_start_download"</span>
    android:layout_width=<span class="hljs-string">"wrap_content"</span>
    android:layout_height=<span class="hljs-string">"wrap_content"</span>
    android:layout_marginTop=<span class="hljs-string">"8dp"</span>
    android:text=<span class="hljs-string">"开始下载"</span>/&gt;

</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

    开放手机联盟(一个由 30 多家科技公司和手机公司组成的团体)已开发出 Android,Android 是第一个完整、开放、免费的手机平台。

    293 引用
感谢    赞同    分享    收藏    关注    反对    举报    ...