跑在主线程(即UI线程)当中的,而且所有的 UI 刷新以及输入处理必须在主线程中执行。这样一旦任务多了就会阻塞 UI 线程导致画面卡顿,从而严重影响性能,所以正确的做法是将耗时的操作单独放在子线程中与 UI 线程隔离,等到耗时操作完成之后再把结果传到 UI 线程进行展示,这就要用到本节学到的消息传递工具——Handler。
1. Handler 基本原理
Handler 是连接不同线程的管道,它让你能够在不同线程之间自由的传递数据,当然我们用的比较多的场景是在子线程中 与主线程通信。因为 Android 系统要求只能在主线程操作 UI,所以常规的做法是将子线程耗时操作的结果传递到 UI 线程进行刷新。
Handler 的基本原理如下图所示:
每一个 Handler 实例与一个线程关联,每一个线程又会维护一个自己的 MessageQueue,当我们创建一个 Handler 的时候我们需要制定一个 Looper 对象(Looper对象对应一个线程),这样就将一个 Handler 对象和一个线程绑定到了一起,随后就可以编写我们的耗时操作,然后通过 Handler 将消息塞入线程的 MessageQueue中,当对应线程从 MessageQueue 取出该条消息的时候,就会回调 Handler 的 handleMessage方法并拿到消息,这样就完成了跨线程通信。
2. Handler 相关方法介绍
- void handleMessage(Message msg):
- 在该方法中处理其他线程传递过来的消息*(用的非常多,一定要掌握!)*
sendEmptyMessage(int what):
发送一条空消息,what 可以理解为消息 ID
sendEmptyMessageDelayed(int what,long delayMillis):
延时发送空消息,what 为自定义 ID
sendMessage(Message msg):
发送消息,msg 是消息内容
sendMessageDelayed(Message msg):
延时发送,msg是消息内容
hasMessage(int what):
检查 MessageQueue 中是否包含一条 ID 为 what 的消息。what 是用户自定义的整形数,可作为消息 ID
3. Handler 使用示例
讲了这么多理论知识,我们来通过一个示例来演示一下如何通过 Handler 完成线程通信。一个很常见的场景就是当 App 需要执行一个耗时任务的时候,会把任务放在子线程中执行,但是在主线程通过一个进度条(ProgressBar)来实时更新进度,这样让用户能够随时看到任务执行的进展。
3.1 布局文件
布局比较简单,主要由三个部分:
- 启动任务
- 进度文本
- 进度条
<?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent"> <ProgressBar android:id="@+id/progressBar" style="?android:attr/progressBarStyleHorizontal" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_alignParentStart="true" android:layout_alignParentTop="true" /> <Button android:id="@+id/start_progress" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/progressBar" android:layout_alignParentStart="true" android:layout_marginStart="24dp" android:layout_marginTop="62dp" android:text="开始任务" /> <TextView android:id="@+id/textView" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_alignTop="@+id/start_progress" android:layout_alignBottom="@+id/start_progress" android:layout_alignParentEnd="true" android:layout_marginEnd="85dp" android:gravity="center" android:text="当前进度:0%" android:textSize="16sp" /> </RelativeLayout>
3.2 MainActivity
主要的逻辑全在 MainActivity 中实现,要完成以下 3 个任务:
- 线程切换: 在子线程中执行耗时操作
- 更新进度: 在主线程更新
ProgressBar
,并同步更新TextView
- 消息传递: 通过
Handler
将进度从子线程传递到主线程
基本完成了上面 3 个任务,整个线程通信就搞定了。代码如下:
package com.emercy.myapplication; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.view.View; import android.widget.Button; import android.widget.ProgressBar; import android.widget.TextView; public class MainActivity extends Activity { private static final int MAX = 100; private static final int START_PROGRESS = 100; private static final int UPDATE_COUNT = 200; private ProgressBar progressBar; private Button startProgress; private TextView textView; private boolean mHasStart; // 任务2:在主线程刷新进度条 Handler mHandlerThread = new Handler() { @Override public void handleMessage(Message msg) { super.handleMessage(msg); if (msg.what == START_PROGRESS) { if (!mHasStart) { thread.start(); mHasStart = true; } } else if (msg.what == UPDATE_COUNT) { textView.setText("当前进度:" + msg.arg1 + "%"); progressBar.setProgress(msg.arg1); } } }; // 任务1:在子线程执行耗时操作,通过sleep模拟耗时任务 Thread thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i <= 100; i++) { try { // 一秒钟的耗时操作 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } Message message = new Message(); message.what = UPDATE_COUNT; message.arg1 = i; mHandlerThread.sendMessage(message); } } }); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); progressBar = findViewById(R.id.progressBar); startProgress = findViewById(R.id.start_progress); textView = findViewById(R.id.textView); progressBar.setMax(MAX); startProgress.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // 任务3:通过Handler传递进度消息 Message message = new Message(); message.what = START_PROGRESS; mHandlerThread.sendEmptyMessage(START_PROGRESS); } }); } }
4. 小结
本节学习了 Android 消息传递机制,详细介绍了 Handler 的基本原理,以及 Looper、线程、MessageQueue、Message 等概念。Handler 最常见的用途就是在子线程执行耗时操作的时候与主线程通信,通知主线程进行 UI 的刷新。一个完成的线程通信主要有 3 大任务,并用一个完成的示例演示了如何完成这 3 个任务,这一节的内容非常重要,请大家务必掌握!