Handler的使用:
Handler是Android线程间通讯的一种方式,它常被我们用来更新UI,是的,我是这么用,还有延时,只有拿出来总结的时候,才会发现有时候使用的时候是有缺漏的。所以总结很重要啊!
目前为止总结的一些使用情况如下:
1.子线程发送消息到主线程
2.在子线程中更新UI
3.在子线程中使用Handler
4.使用HandlerThread
5.Handler的callback回调
-
子线程发送消息到主线程
Handler mainHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(HandlerActivity.this, "接收到啦", Toast.LENGTH_SHORT).show();
}
};
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*子线程传给主线程*/
mainHandler.sendEmptyMessage(0);
}
}).start();
这里是在子线程中Handler对象发送一个空消息,然后在handleMessage方法中进行操作,此时Toast执行已经是在UI线程了。
然后刚刚测试了一下,不仅仅是子线程往主线程发消息,主线程也可以向子线程发消息,子线程也可以向子线程发消息,自己手动去试一下才会理解Handler这个线程间通信是怎么回事。
-
在子线程中更新UI
Handler updateHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
/*在子线程中更新UI*/
updateHandler.post(new Runnable() {
@Override
public void run() {
/*
*更新UI的操作
* */
}
});
}
}).start();
这里的代码都是一些局部的代码块,这里的updateHandler是在主线程声明的,子线程是开在主线程下的, 然后updateHandler对象在子线程使用post方法,new了一个Runnable去切换线程到主线程执行更新UI的代码。当然,也可以像上面那样发送一个消息在Handler的handleMessage里更新UI喔~!
-
在子线程中使用Handler
匿名内部类实现
new Thread(new Runnable() {//创建一个子线程
@Override
public void run() {
Looper.prepare();//创建与当前线程相关的Looper
myThreadTwoHandler = new Handler() { //创建一个子线程里的Handler
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//log日志测试结果,是在子线程的Handler
Log.e(TAG, "当前存在线程为" + Thread.currentThread().getName());
}
};
Looper.loop();//调用此方法,消息才会循环处理
}
}).start();
myThreadTwoHandler.sendEmptyMessageDelayed(0, 5000);//主线程调用子线程的Handler对象发送消息
子类继承Thread实现
//MyThread 子类继承 Thread
public class MyThread extends Thread {
public Looper childLooper;
@Override
public void run() {
Looper.prepare();//创建与当前线程相关的Looper
childLooper = Looper.myLooper();//获取当前线程的Looper对象
Looper.loop();//调用此方法,消息才会循环处理
}
}
/*在子线程使用Handler*/
MyThread myThread = new MyThread();
myThread.start();
myThreadHandler = new Handler(myThread.childLooper) {//与MyThread线程绑定
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "当前存在线程为" + Thread.currentThread().getName());
}
};
//主线程调用子线程的Handler对象发送消息
myThreadHandler.sendEmptyMessageDelayed(0, 5000);
HandlerThread实现
HandlerThread handlerThread = new HandlerThread("ceshi");
handlerThread.start();
//通过HandlerThread的getLooper方法可以获取Looper
Looper looper = handlerThread.getLooper();
//通过Looper我们就可以创建子线程的handler了
Handler handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
//测试结果是在ceshi这个线程内
Log.e(TAG, "这个是HandlerThread线程哦 : " + Thread.currentThread().getName());
}
};
handler.sendEmptyMessageDelayed(0, 1000);
-
使用HandlerThread
HandlerThread上面已经展示过代码了,为了和其他两种对比,我就把HandlerThread实现Handler的使用写在上面的部分了。HandlerThread其实就是系统封装好的Thread子类,和自己封装的子类不同的是,HandlerThread里面做了更好的判断来避免一些问题。
例如:Only one Looper may be created per thread。这个问题我在自己写MyThread这个子类并使用的时候,子类对象调用run就报了这个错误,调用start可以运行了。目前为止,我觉得HandlerThread这个子类相对自己写来说,会比较好用,在实际的项目操作中,如何需要,应该还是看具体需求了。目前是这样,以后有了新的理解进行更新。
-
Handler的Callback回调
其实这个Handler还有一个Callback的回调这个东西,我一开始并不知道的,当时应该是从大佬同事那里了解到这个东西的时候,感觉很神奇,又感觉很懵逼,为什么会有布尔类型的返回值?然后在我的实验中发现,咦?true和false的结果是一样的?脑子里更懵逼了,这是什么???黑人问号脸...
Handler callBackHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(HandlerActivity.this, "这里是Callback", Toast.LENGTH_SHORT).show();
/*
* callback的返回值
* */
return true;
}
});
对,就是这样,我的Toast不论true还是false,完全没有影响,照弹不误!为了内心世界的和平,肯定不能放弃治疗!源码什么的,对不起,一开始我肯定选择了百度。百度果然是没有辜负我的期望,什么有用的都没有找到,找到的都是一模一样的无数个copy的没有用的文字~~~
然后,在我努力追求真理的情况下,大佬告诉我其实是这样的。。。
Handler callBackHandler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(HandlerActivity.this, "这里是Callback", Toast.LENGTH_SHORT).show();
/*
* callback的返回值
* */
return true;
}
}) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.e(TAG, "handleMessage: 返回true不处理");
}
};
对,没有看错!handleMessage有两个,一个属于Callback,一个属于Handler。
然后,让我们和源码结合一下~~~
//这里是Handler的源码
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
然后其实一看就恍然大悟了,返回值为true的时候直接return掉了,不会去执行Handler的handleMessage这个方法了,如果为false的话就会执行啦,当然,好像默认情况都是执行false。
这里也是暂时理解是这样的,对于它的应用场景,具体需求,以后遇到了再更新!
哦对了,还有AndroidStudio中,我们平常使用的Handler其实都会可能导致内存泄露的,AS会标黄色,表示警告。然后在使用这个Handler.Callback的时候,我一开始发现警告消除掉了。其实并不是,在只使用Callback的handleMessage方法时,是没有警告的,但是加上Handler的handleMessage后,警告就有了,所以,网上有的人说的Handler.Callback会解决内存泄露,是错误的,没有警告了只是写法有问题罢了,哈哈哈哈哈!
Handler的内存泄露
Handler的内存泄漏,一开始让我注意到这个问题的时候,是我的AS报警告。更新以后的AS,对于Handler的这个警告,不要太凶喔,一报就是一大片屎黄色,咦~~~受不了
比如说这样:
然后就开始探究了啊,为什么会导致内存泄露呢?哈哈哈,上面图片里其实有说啦,这种Handler的使用其实就是将Handler声明为Activity的内部。而在Java语言中,非静态内部类会持有外部类的一个隐式引用,所以,Handler会持有Activity的引用啦,然后就会有可能造成外部类,也就是Activity无法被回收,导致内存泄露~~~
然后,经过询问等等等方法探究了他人的经验之后,发现其实很多开发一直使用的是可能会导致内存泄露的版本的Handler,因为正面看上去并不会影响App的运行,不像图像过大会导致OOM这种很致命的问题就会让很多忽视,或者说没有注意到这个问题,比如了解这个问题之前的那个我~
那么,如何避免内存泄漏,使用正确的Handler呢?先看一下AS给我们的建议
This Handler class should be static or leaks might occur (anonymous android.os.Handler) less (Ctrl+F1)
Since this Handler is declared as an inner class, it may prevent the outer class from being garbage collected.
If the Handler is using a Looper or MessageQueue for a thread other than the main thread, then there is no issue.
If the Handler is using the Looper or MessageQueue of the main thread,
you need to fix your Handler declaration, as follows: Declare the Handler as a static class;
In the outer class, instantiate a WeakReference to the outer class and pass this object to your Handler when you instantiate the Handler;
Make all references to members of the outer class using the WeakReference object.
哇,全是英文呐,扔谷歌翻译进化一下~
这个处理程序类应该是静态的或可能发生泄漏(匿名android.os.Handler)少...(按Ctrl+ F1)
由于此Handler被声明为内部类,因此可能会阻止外部类被垃圾收集。
如果处理程序对主线程以外的线程使用Looper或MessageQueue,则没有问题。
如果Handler使用主线程的Looper或MessageQueue,则需要修复Handler声明,
如下所示:将Handler声明为静态类; 在外部类中,实例化WeakReference到外部类并在实例化Handler时将此对象传递给Handler;
使用WeakReference对象创建对外部类成员的所有引用。
然后我们就开始搞我们的正确姿势啦~
正确使用Handler,避免内存泄露
- 使用静态的匿名内部类,并持有外部类的弱引用
声明静态的Handler内部类,持有外部类的弱引用,通过外部类实例去引用外部类的各种控件实例,参数实例等等。然后当GC回收时,因为外部类是弱引用,所以会被回收。
/**
* 声明一个静态的Handler内部类,并持有外部类的弱引用
*/
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity> mActivty;
private MyHandler(HandlerActivity mActivty) {
this.mActivty = new WeakReference<>(mActivty);
}
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
HandlerActivity activity = mActivty.get();
if (activity != null) {
Log.e("eee", "handleMessage: " + Thread.currentThread().getName());
}
}
}
在外部类中声明MyHandler对象
private final MyHandler mHandler = new MyHandler(this);
然后调用发送消息,post的方式和sendMessage的方式
mHandler.post(sRunnable);
mHandler.sendMessage(message);
如果使用sendMessage方法的话,会被MyHandler的 handleMessage方法接收。那么,若使用post方法的话,我们还需要声明一个静态的Runable来完成我们的post
/**
* 静态的匿名内部类不会持有外部类的引用
*/
private static final Runnable sRunnable = new Runnable() {
@Override
public void run() {
// ...你的操作
Log.e(TAG, "这里是run");
}
};
结束语
还有很多没有讲到的,讲到的也都感觉没有说的很彻底,先写到这里,然后多钻研再修改更新~~~