Handler与Message、Looper、MessageQueue一起构成了Android的消息机制,Android通过大量的消息来与用户进行交互。
一、Handler
1.什么是handler、handler的作用是什么?
Handler是一个消息的分发对象。Handler在消息机制中的作用是发送和处理消息,handler还有一个重要的作用就是跨线程通信,例如:子线程进行网络请求,请求到的数据用handler发送给主线程。
2.为什么要使用handler?
在Android系统中规定主线程不能阻塞超过5s,否则就会报ANR。也就是说不能在主线程中进行耗时操作,例如网络请求、数据库操作等。这些耗时操作只能在子线程中进行,而Android的主线程又是不安全的,如果多线程并发访问控件会出现不可控的问题,而加锁处理又会使效率降低,这个时候就用到了Handler。
3.handler的常用方法
send系列
- 发送一条消息,发送完成后handler的handlerMessage(Message msg)会处理消息
handler.sendMessage(message)
- 延迟一秒发送消息
handler.sendMessageDelayed(message,1000)
- 发送一个空的消息
handler.sendEmptyMessage(msg.what)
- 延迟发送一条空的消息
handler.sendEmptyMessageDelayed
- 在指定时间发送一条消息
handler.sendMessageAtTime(Message message,long uptimeMillis)
- 在指定时间发送一条空的消息
handler.sendEmptyMessageAtTime(Message message,long uptimeMillis)
- 立即发送一条消息到消息队列中,并且是放在消息队列最前面
handler.sendMessageAtFrontOfQueue(Message)
post系列 - 立即发送一条消息到队列
public final boolean post(Runnable r){
return sendMessageDelayed(getPostMessage(r), 0);
}
- 立即发送Message到队列,而且是放在队列的最前面
public final boolean postAtFrontOfQueue(Runnable r){
return sendMessageAtFrontOfQueue(getPostMessage(r));
}
- 设置时间,发送Message到队列
public final boolean postAtTime(Runnable r, long uptimeMillis){
return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}
public final boolean postAtTime(Runnable r, Object token, long uptimeMillis){
return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}
- 在延时若干毫秒后,发送Message到队列
public final boolean postDelayed(Runnable r, long delayMillis){
return sendMessageDelayed(getPostMessage(r), delayMillis);
}
4.handler的使用
- 主线程发送消息到子线程
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
button = findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
handler.obtainMessage(1,2).sendToTarget();
}
});
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
if(msg.what == 1){
Toast.makeText(MainActivity.this, "接收到了消息!", Toast.LENGTH_SHORT).show();
}
}
};
Looper.loop();
}
}
- 子线程发送消息到主线程
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(MainActivity.this, "接收到了消息", Toast.LENGTH_SHORT).show();
}
};
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
- 子线程发送消息到子线程
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new MyThread().start();
new MyThread2().start();
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
handler.sendEmptyMessage(0);
}
}
class MyThread2 extends Thread{
@Override
public void run() {
super.run();
Looper.prepare();
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.d("sss","success");
}
};
Looper.loop();
}
}
- 在子线程中更新UI
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
textView = findViewById(R.id.textView);
final Handler handler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
handler.postDelayed(new Runnable() {
@Override
public void run() {
textView.setText("更新UI");
}
},5000);
}
}){}.start();
}
也可使发送消息给Handler,在Handler的handlerMessage方法中更新。
5.handler的内存泄露及解决方法
- 为什么会出现内存泄漏
我们知道在Java中非静态的内部类会隐形的持有外部类的引用,而Handler的使用其实就是将Handler声明为Activity的内部类,所以Handler会持有外部类的引用,这样当外部Activity需要被回收时还有持有他引用的内部类,所以Activity不会被回收,这样就会导致内存泄漏。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Handler handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
handler.sendMessageDelayed(message,1000*60*10);
}
如上面的例子所示:这里有一条延迟了10分钟的消息,如果在这十分钟内外部Activity需要被回收,但是内部的handler还存在并持有他的引用,这样就导致了Activity无法被回收,造成了内存泄漏。
- 如何解决handler的内存泄露
1.使用弱引用
Java中的引用分为强引用,软引用,弱引用
强引用:GC宁愿报OOM也不会回收它
软引用:能不回收就不回收,如果内存不够了就会回收
弱引用:碰到了就会回收,但是一般GC的优先级不是很高,不会那么容易发现
很显然handler对Activity是强引用,导致了GC在回收Avtivity时无法回收,我们可以让handler对Activity弱引用,这样就不会影响Activity被回收,就解决了内存泄漏的问题。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
MyHandler handler = new MyHandler(this);
Message message = Message.obtain();
handler.sendMessageDelayed(message,60*1000*10);
}
private static class MyHandler extends Handler{
private WeakReference mActicity;
private MyHandler(MainActivity activity) {
mActicity = new WeakReference<>(activity);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
}
2.在Activity被销毁时清除所有消息
正是因为被延时处理的 message 持有 Handler 的引用,Handler 持有对 Activity 的引用导致 Activity 的泄露。因此我们可以尝试在当前界面结束时将消息队列中未被处理的消息清除,从源头上解除了这条引用链,从而使 Activity 能被及时的回收。
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
handler = new Handler(){
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
}
};
Message message = Message.obtain();
handler.sendMessageDelayed(message,60*1000*10);
}
@Override
protected void onDestroy() {
super.onDestroy();
handler.removeCallbacksAndMessages(null);
}
二、Looper
Looper(消息循环)用来将一个普通的线程变成一个消息循环的线程,调用looper.prepare()来初始化looper,调用looper.loop()来开始循环处理消息,一个线程对应一个looper。主线程默认会初始化looper,所以可以直接使用handler。例:
private class LooperThread extends Thread {
@Override
public void run() {
//将当前线程初始化为looper
Looper.prepare();
//处理在这个线程中的MessageQueue
Looper.loop();
}
}
三、Message
用来定义一个发送给handler任意数据对象的消息,包含两个int字段和一个object字段。message构造方法是公开的,但获取message对象的方法最好调用Message.obtain()或HandlerobtainMessage(),这样是从一个可回收的消息对象池中获取到的Message对象。message常用成员变量如下:
1.what
用来区分消息身份的标志,一般使用16进制表示
2.arg1、arg2
两个int型变量,不用访问obj就可以读到数据
3.obj
用来接收对象
四、MessageQueue
MessageQueue相当于是一个存放Message的容器,Handler往这个容器里面放入Message,Looper从这个容器取数据。