Android 进阶15:HandlerThread 使用场景及源码解析 男娘i 2022-06-14 02:41 317阅读 0赞 * 眼睛困得要死,但今天的计划不完成又怎么能睡呢?明日复明日,明日何其多啊! 读完本文你将了解: * * HandlerThread 简介 * HandlerThread 源码 * HandlerThread 的使用场景 * 举个栗子 * 运行结果 * 总结 * 代码地址 为了避免 ANR,我们常常需要在线程中做耗时操作,然后把结果抛到主线程进行处理。 Android 提供了多种用于这种场景的组件,其中一种就是本篇文章要介绍的 HandlerThread。 ## HandlerThread 简介 ## [源码解读 Android 消息机制( Message MessageQueue Handler Looper)][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()` 方法。 ## 运行结果 ## ![这里写图片描述][SouthEast] ## 总结 ## ![这里写图片描述][SouthEast 1] 上面的例子中 `HandlerThread` 配合一个主线程 `Handler` 完成了在子线程中串行执行任务,同时在主线程中反馈状态的功能。 如果用一句话总结 `HandlerThread` 的特点: * 它就是一个帮我们创建 Looper 的线程,让我们可以直接在线程中使用 Handler 来处理异步任务。 ## [代码地址][Link 1] ## [Android _ Message MessageQueue Handler Looper]: http://blog.csdn.net/u011240877/article/details/72892321 [SouthEast]: /images/20220614/23d7374397b14dd2b87e54fcb2c26eca.png [SouthEast 1]: /images/20220614/e3536472d17f40618c0bcaeb848332ab.png [Link 1]: https://github.com/shixinzhang/LearnAndroid/blob/dev/app/src/main/java/net/sxkeji/shixinandroiddemo2/activity/async/DownloadThread.java
还没有评论,来说两句吧...