1 引出问题
在启动一个Android应用的同时,便会开启一个主线程——Main Thread(也叫UI线程),主线程负责处理与UI相关的事件。
但是在开发中经常会做一些耗时的任务,这些耗时的任务会阻塞主线程,若长时间地阻塞主线程则会导致应用发生ANR(应用程序无响应)。
因此我们需要将这些耗时任务放在子线程中去处理,并且在处理耗时任务的过程中,我们需要更新UI以便告知用户耗时任务的进度、状态等信息。
那么如何在子线程中更新主线程中的UI控件呢?
对此,我们可以借助Handler来完成。Handler提供了三种方式来解决上述问题:
- 调用Handler的sendMessage方法;
- 调用Handler的post方法;
- 调用Handler的obtainMessage方法;
2 Handler的简单使用
2.1 调用sendMessage方法
为了更好地理解Handler的使用,我们创建一个Demo来做一个简单的演示示例。点击屏幕上的按钮开始执行任务,同时文本框显示“开始执行任务”,用休眠5秒钟模拟执行耗时任务,当任务执行完(休眠结束后),文本框显示“任务执行完毕”。
布局文件代码如下:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<TextView
android:id="@+id/tooltip_tv"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<Button
android:id="@+id/start_execute_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="开始执行" />
</LinearLayout>
MainActivity的代码编写如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
private static final String TAG = "MA-cxs";
//工具提示文本框
private TextView tooltipTv;
//开始执行任务按钮
private Button startExecute;
//是否开始执行
private boolean isExecute = false;
public final int MSG_EXECUTE_START = 1000;
public final int MSG_EXECUTE_COMPLETE = 1001;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
/**
* 初始化控件
*/
private void initViews() {
tooltipTv = findViewById(R.id.tooltip_tv);
startExecute = findViewById(R.id.start_execute_btn);
startExecute.setOnClickListener(this);
}
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
new MyThread().start();
}
}
}
/**
* 创建一个执行耗时任务的子线程,并发送消息
*/
class MyThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "子线程开始执行");
//发送消息给Handler
mExecuteTaskHandler.sendEmptyMessage(MSG_EXECUTE_START);
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
Message message = new Message();
//此处也可设置message.arg1、message.arg2、message.obj、message.setData(Bundle对象)方法
message.what = MSG_EXECUTE_COMPLETE;
message.setData(new Bundle());
mExecuteTaskHandler.sendMessage(message);
isExecute = false;
Log.d(TAG, "子线程执行完毕");
}
}
//接收消息并进行处理
private Handler mExecuteTaskHandler = new Handler() {
@Override
public void handleMessage(@NonNull Message msg) {
switch (msg.what) {
case MSG_EXECUTE_START:
Log.d(TAG, "收到开始执行任务的消息");
tooltipTv.setText("开始执行任务");
break;
case MSG_EXECUTE_COMPLETE:
Log.d(TAG, "收到任务执行完毕的消息");
tooltipTv.setText("任务执行完毕");
break;
}
}
};
}
Handler的使用步骤总结:
1.发送消息:在执行耗时任务时发送消息给给Handler;
2.接收消息并进行处理:在主线程(UI线程)中创建一个Handler对象,并实现其handleMessage()方法,并且根据message参数(what、arg1、arg2或obj)的不同进行相应的处理——更新UI。
设置Message除了可以设置其what、arg1及arg2之外,还可以借助Message的setData方法,传入一个Bundle对象。
发送消息的方法除了有sendMessage外还有其他的方法:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final boolean | sendEmptyMessage(int what) 发送仅包含what值的消息 |
public final boolean | sendEmptyMessageAtTime(int what, long uptimeMillis) 发送仅包含what值并且在指定的绝对时间传递的消息 |
public final boolean | sendEmptyMessageDelayed(int what, long delayMillis) 仅包含what值的消息,并且该消息将在延迟指定的时间后发送 |
public final boolean | sendMessage(Message msg) 将消息放在当前待处理的消息队列的末尾 |
public final boolean | sendMessageAtFrontOfQueue(Message msg) 将消息放入消息队列的最前面,以在消息循环的下一次迭代中进行处理 |
public boolean | sendMessageAtTime(Message msg, long uptimeMillis) 在指定的绝对运行时间发送消息 |
public final boolean | sendMessageDelayed(Message msg, long delayMillis) 在延迟指定的时间后发送消息 |
对于延时、定时消息,有时候需要取消,则可以通过以下方法将指定消息移除:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final void | removeCallbacksAndMessages(Object token) 移除obj为token的任何待处理的回调及已发送的消息 |
public final void | removeMessages(int what) 删除消息队列中为what参数为what值的待处理的消息 |
public final void | removeMessages(int what, Object object) 删除消息队列中为what参数what值并且obj参数为object的待处理的消息 |
2.2 调用post方法
则改写上述代码如下:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//其余代码不变
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
//new MyThread().start();
new PostThread().start();
}
}
}
class PostThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "PostThread run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
//发送消息给Handler
mExecuteTaskHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
tooltipTv.setText("开始执行任务");
}
});
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
Message message = new Message();
//此处也可设置message.arg1、message.arg2、message.obj、message.setData(Bundle对象)方法
message.what = MSG_EXECUTE_COMPLETE;
message.setData(new Bundle());
mExecuteTaskHandler.post(new Runnable() {
@Override
public void run() {
Log.d(TAG, "Runnable run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
tooltipTv.setText("任务执行完毕");
}
});
isExecute = false;
}
}
}
运行应用程序后点击按钮,打印日志如下:
PostThread run(): ThreadId=154, ThreadName=Thread-2
Runnable run(): ThreadId=2, ThreadName=main
Runnable run(): ThreadId=2, ThreadName=main
总结:
从上面代码并结合日志可以看出:Handler的post方法参数为一个Runnable对象,由于Handler是在主线程中创建的,因此,Runnable也是在主线程中运行,则Runnable与创建它的线程无关,与调用post方法的线程无关。并且Runnable的run方法是在主线程中更新UI的。
与sendMessage方法类似,post方法也有多个相似的方法:
修饰符和返回值类型 | 方法及其描述 |
---|---|
public final boolean | post(Runnable r) 将Runnable对象添加到消息队列中 |
public final boolean | postAtFrontOfQueue(Runnable r) 将Runnable对象添加到消息队列的最前面 |
public final boolean | postAtTime(Runnable r, long uptimeMillis) 将Runnable对象添加到消息队列中,并且在指定的绝对时间执行 |
public final boolean | postAtTime(Runnable r, Object token, long uptimeMillis) 同上 |
public final boolean | postDelayed(Runnable r, long delayMillis) 将Runnable对象添加到消息队列中,并在经过指定的时间后运行 |
public final boolean | postDelayed(Runnable r, Object token, long delayMillis) 同上 |
同sendMessage方法,可以通过removeCallbacks(Runnable r)、removeCallbacks(Runnable r, Object token)及removeCallbacksAndMessages(Object token)方法取消post定时、延时处理的Runnable。
2.3 调用obtainMessage方法
obtainMessage方法与sendMessage方法类似,也可以看成是一种。通过下面的代码就能看出这一点:
public class MainActivity extends AppCompatActivity implements View.OnClickListener {
//其余代码不变
@Override
public void onClick(View view) {
if (view.getId() == R.id.start_execute_btn) {
if (!isExecute) {
// new MyThread().start();
// new PostThread().start();
new ObtainThread().start();
}
}
}
class ObtainThread extends Thread {
@Override
public void run() {
isExecute = true;
Log.d(TAG, "PostThread run(): ThreadId=" + Thread.currentThread().getId() +
", ThreadName=" + Thread.currentThread().getName());
//发送消息给Handler
mExecuteTaskHandler.obtainMessage(MSG_EXECUTE_START).sendToTarget();
//借助休眠模拟执行任务的过程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完任务后再次发送一个执行成功的消息
mExecuteTaskHandler.obtainMessage(MSG_EXECUTE_COMPLETE).sendToTarget();
isExecute = false;
}
}
}
总结:
通过调用obtainMessage方法即可生成Message对象,此对象携带其target对象,通过调用sendToTarget方法即可将消息发送到Handler的消息队列中,然后再由Handler的handleMessage方法进行处理。
参考资料:
- Android Handler的基本使用
- Handler | Android Developers
- Android Handler详解
发表回复