Android中,Handler是一类用于异步消息传递和线程之间通信的基础框架。一个Handler是一个线程的处理器,可以接收消息,并调度运行它们。使用Handler,应用程序可以将处理器与一个线程关联,以将来的时间运行任务。而使用Handler,就可以避免启动额外的线程,从而提高代码的效率。本文将详细介绍Handler的概念、使用方法和常见问题等。
什么是Handler?
在 Android 开发中,使用多线程是非常常见的,但是在 Android 中,有一个 UI 线程,也就是主线程,所有的UI操作必须在主线程中完成,否则就会抛出 CalledFromWrongThreadException 异常,这个异常的原因是 Android 不允许在子线程中直接访问 UI 控件。而 Handler 的主要作用就是用于子线程与主线程之间进行通讯,在应用程序内传递消息、更新 UI 等各种场景下,都可以使用 Handler 来实现。
一个Handler是与Looper关联的对象,它拥有处理消息队列的能力,即将消息加入到队列中,并在将来的某个时间处理这些消息。每个线程只能有一个Looper,而每个Looper所属的线程可以拥有一个或多个Handler。当创建一个新的Handler实例时,它们会自动附加到创建它的线程的消息队列。当线程被销毁时,它的消息队列也会被销毁。
Handler使用消息队列来进行异步任务的处理,消息队列是基于先进先出(FIFO)的原则,即在消息队列中消息加入的顺序与取出的顺序相同。消息通常是如下形式的数据结构:
class Message {int what; //消息idint arg1; //消息参数1int arg2; //消息参数2Object obj; //消息的Object对象Handler target; // 处理该消息的Handler对象//省略其他代码
}
为什么要使用Handler?
在 Android 应用程序中,不能在非 UI 线程中直接更新界面,因为 Android 不支持跨线程更新界面。也就是说我们必须找到一种间接的方法来实现,在应用程序中常用的解决方法有以下几种:
-
Timer、 TimerTask :使用定时器 Timer 和 TimerTask 进行轮询,实现 UI 刷新。
-
AsyncTask :非常适合于轻量级的后台任务,并发数默认为一个。
-
Thread :使用普通线程或者线程池配合 Handler、 Message, Runnable,Timer 等类来更新 UI。
实际上,这些方法之间并不是完全割裂的,各个方法之间经常会相互搭配使用。
- 在 Handler 的基础上,Android 还提供了 Looper 和 MessageQueue 来协同完成线程的消息传递工作。
如何使用Handler?
要实现在线程中更新 TextView 的文字,基本的实现思路如下:
-
在子线程中新建一个 Handler。
-
在子线程中开启轮询,执行子线程任务。
-
在子线程任务执行完毕后,新建一个 Message 对象,并通过 Handler 将 Message 发送出去。
-
在主线程中新建一个 Handler,并在其 handleMessage 方法中将 Message 对象取出。
-
通过 Message 中的内容更新 UI。
代码如下:
private TextView textView;
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);textView = (TextView)findViewById(R.id.textview);mHandler = new Handler(){@Overridepublic void handleMessage(Message msg) {textView.setText((String)msg.obj);}};new Thread(new Runnable() {@Overridepublic void run() {// 子线程耗时操作Message msg = mHandler.obtainMessage();msg.obj = "子线程更新UI";mHandler.sendMessage(msg);}}).start();
}
Handler的常见使用方式
在 Android 应用程序中,将消息发送给 Handler 进行处理的方式有两种:
- post()方式
可以通过调用 Handler 的 post() 方法,将 Runnable 对象通过 MessageQueue 发送到消息队列中,即可让主线程处理相应的操作。这种方式可以用于解决在子线程中不能进行 UI 操作的问题,例如我们可以在子线程中通过 post 方式将更新 UI 的任务传递到主线程来完成,这样就不会因为在非 UI 线程中更新 UI 而导致 ANR(Application Not Responding)了。
示例代码如下:
new Thread(new Runnable() {@Overridepublic void run() {//子线程中更新UIHandler mainHandler = new Handler(Looper.getMainLooper());mainHandler.post(new Runnable() {@Overridepublic void run() {//在主线程中更新UI}});}
}).start();
- sendMessage()方式
前面提到了,sendMessage方法会将Message发送到消息队列的尾部并等待处理,而post方法则是将Runnable对象直接添加到消息队列中。sendMessage 和 post 的区别在于,sendMessage 会将传递的消息封装成 Message 对象,最后再发送到 MessageQueue 中,适用于需要传递参数并处理结果的情况。
常见的使用方式包括:
- Handler.sendMessage(Message msg) 发送 Message 对象到消息队列。
- Handler.sendMessageDelayed(Message msg, long delayMillis) 延迟指定时间后发送 Message 对象到消息队列。
- Handler.sendMessageAtTime(Message msg, long uptimeMillis) 在指定时间后(毫秒)将 Message 对象加入到消息队列中。
- Handler.sendEmptyMessage(int what) 发送一个不携带任何数据的空消息。
- Handler.post(Runnable r) 将 Runnable 对象添加到消息队列中。
示例代码如下:
private Handler mHandler = new Handler(Looper.getMainLooper()) {@Overridepublic void handleMessage(Message msg) {switch (msg.what) {case 1://处理消息break;case 2://处理消息break;default:break;}}
};private void sendMessage() {Message msg = mHandler.obtainMessage();msg.what = 1;msg.arg1 = 2;msg.obj = "message content";mHandler.sendMessage(msg);
}private void sendEmptyMessage() {mHandler.sendEmptyMessage(2);
}private void postRunnable() {mHandler.post(new Runnable() {@Overridepublic void run() {//执行操作}});
}
Handler的作用及其核心流程
Handler的常见用途包括:
- 在子线程中执行任务后,通过Handler回到主线程更新UI。
- 延迟执行任务或周期性执行任务。
- 将多个耗时任务按顺序串行执行。
使用Handler的核心流程如下:
- 首先,创建一个Handler实例,并与当前线程的Looper关联。
Handler mHandler = new Handler(Looper.getMainLooper());
- Handler负责处理消息队列。要想从消息队列中获取消息,需要使用Handler的公共方法之一post()或sendMessage()。通常,它们的使用方式相似。
mHandler.post(new Runnable() {@Overridepublic void run() {// UI更新代码}
});
或者:
Message message = Message.obtain();
message.what = MSG_ID;
message.arg1 = arg1;
message.arg2 = arg2;
mHandler.sendMessage(message);
- 当消息被加入到消息队列后,Handler会根据先进先出(FIFO)的原则将它们按顺序从队列中取出,然后调用associated Handler对象的handleMessage()方法处理它们。定制Handler子类的常见做法就是在handleMessage()方法内填充相应的处理逻辑。
@Override
public void handleMessage(Message msg) {switch (msg.what) {case MSG_ID:// 执行任务逻辑break;default:super.handleMessage(msg);break;}
}
Handler的基本用法
- 在主线程中使用Handler
在主线程中使用Handler,可以直接使用getMainLooper()获取主线程Looper对象,并创建Handler实例。例如,在Activity中实现在子线程中更新UI:
new Thread(new Runnable() {@Overridepublic void run() {// 子线程中进行耗时操作...// 完成后在主线程中更新UImHandler.post(new Runnable() {@Overridepublic void run() {// UI更新代码}});}
}).start();
- 在子线程中使用Handler
在子线程中更新UI,需要将当前线程的Looper对象关联给Handler实例,以便于在消息队列中执行此消息。
new Thread(new Runnable() {@Overridepublic void run() { 在子线程中进行耗时操作...Message message = mHandler.obtainMessage(MSG_ID, arg1, arg2);mHandler.sendMessage(message);}
}).start();mHandler = new Handler(new Handler.Callback() {@Overridepublic boolean handleMessage(Message msg) {switch (msg.what) {case MSG_ID:// UI更新代码return true;default:return false;}}
});
- Handler常见任务
3.1 延迟执行任务
使用Handler.postDelayed()方法可将消息延迟一段时间后再发送到消息队列中。例如:
mHandler.postDelayed(new Runnable() {@Overridepublic void run() {//执行延迟任务}
}, DELAY_MILLIS);
3.2 周期性执行任务
使用Handler.postDelayed()方法和Handler.sendMessage()方法结合,可实现周期性执行任务的功能。
mHandler.postDelayed(new Runnable() {@Overridepublic void run() {// 周期性执行任务mHandler.sendMessageDelayed(Message.obtain(mHandler, MSG_ID), INTERVAL);}
}, DELAY_MILLIS);
3.3 多任务按顺序执行
多任务按顺序串行运行,可以利用消息队列处理多个消息。
mHandler.sendMessage(Message.obtain(mHandler, TASK1_ID, arg1, arg2));
mHandler.sendMessage(Message.obtain(mHandler, TASK2_ID, arg1, arg2));
mHandler.sendMessage(Message.obtain(mHandler, TASK3_ID, arg1, arg2));
在Handler的handleMessage()方法中分别处理每个消息。
Handler调用方式的异同
使用Handler,有两种方法将消息加入消息队列中:post()方法和sendMessage()方法。两种方法的异同如下:
4.1 同异步方式
- post()方法是同步方式。即加入消息到消息队列中后,会直接处理此消息,不必等待消息阻塞的处理程序返回。
- sendMessage()方法是异步方式。即加入消息到消息队列中后,不会立即执行此消息,而是等待消息阻塞的处理程序返回。
4.2 方法参数
- post()方法的参数是Runnable对象,而Runnable对象中run()方法是待执行的任务代码。
- sendMessage()方法的参数是Message对象,而Message对象中的what字段是消息ID,arg1和arg2是可传递给处理程序的参数。
4.3 延迟时间参数
- postDelayed()方法是将Runnable对象放入到消息队列中,延迟指定时间执行,其第二个参数为DelayMillis。而post()方法不支持延迟时间参数。
- sendMessageDelayed()方法是将Message对象放入到消息队列中,延迟指定时间执行,其第二个参数为DelayMillis。而sendMessage()方法不支持延迟时间参数。
Handler的优缺点
5.1 优点
- 提供了方便的UI线程更新的方法。
- 避免了创建新线程带来的线程切换开销。
- 通过消息队列的方式,提高了线程的响应能力。
5.2 缺点
- Handler发送的消息会保存在消息队列中,如果一直发送大量的消息,将可能导致消息队列过长,影响应用的响应能力。
- LiveData和RxJava等现在比较流行的框架,能够替代Handler实现更优异的异步编程和UI更新。
总结
Handler是Android系统的核心组件,在线程与消息传递中发挥着重要的作用。它可以帮助用户在不同的线程中实现消息的传递和任务的执行,并且提供了许多具有实际用途的方法,例如post()、sendMessage()、postDelayed()、sendMessageDelayed()等等。但是,Handler也有其缺点,比如常规使用可能会导致消息队列过度拥挤