本文主要是介绍学习android中的Handler机制和由此引发的内存泄露以及解决办法,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!
一、Handler机制以及使用方法
Handler、Looper、Message都是android的异步消息处理机制,异步消息处理线程启动之后,会进入一个无限循环体中,每循环一次从消息队列中取出一个消息,然后回调消息处理函数,执行完成之后,继续进行循环,如果消息队列为空,则线程会阻塞等待。
1 Looper作用:
创建一个MessageQueue,然后进入一个无限循环体,然后不断的从MessageQueue中读取消息,而消息的创建者是一个或者多个Handler。
(1)与当前线程绑定,保证一个线程只有一个Looper实例,同时Looper实例也之后一个MessageQueue。
(2)loop()方法,不断从MessageQueue中取消息,交给消息的target属性的dispatchMessage去处理。
2 Handler:
Looper.prepare()在本线程中保存一个Looper实例,然后该实例中保存一个MessageQueue,因为 Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
Looper.loop()会让当前线程进入一个无限循环,不断从MessageQueue中读取消息,然后回调
msg.target.dispatchMessage(msg)方法。
一般用来更新UI,声明的时候直接初始化,会首先得到当前线程中保存的Looper实例,然后与Looper中的MessageQueue关联。
sendMessage方法,会给msg的taget赋值为handler本身,然后加入MessageQueue中。
在构造Handler实例时,重写handleMessage方法。
(再Activity中没有显示调用Looper.prepare和Looper.loop()方法,为什么Hander可以成功创建呢,因为再Activity的启动代码中,已经再当前的UI线程中调用了Looper.prepare()和Looper.loop()方法。
3 Handler的post方法创建的线程和UI线程有什么关系?
mHandler.post(new Runnable()){
public void run(){
mTxt.setText("hdjhfjdh");
}
}
然后run方法中可以写更新UI的代码,其实这个Runnable并没有创建什么线程,而是发送了一条消息,下面看源码:
- public final boolean post(Runnable r)
- {
- return sendMessageDelayed(getPostMessage(r), 0);
- }
- private static Message getPostMessage(Runnable r) {
- Message m = Message.obtain();
- m.callback = r;
- return m;
- }
可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.
注:产生一个Message对象,可以new ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。
- public final boolean sendMessageDelayed(Message msg, long delayMillis)
- {
- if (delayMillis < 0) {
- delayMillis = 0;
- }
- return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
- }
- public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
- MessageQueue queue = mQueue;
- if (queue == null) {
- RuntimeException e = new RuntimeException(
- this + " sendMessageAtTime() called with no mQueue");
- Log.w("Looper", e.getMessage(), e);
- return false;
- }
- return enqueueMessage(queue, msg, uptimeMillis);
- }
可以看到,这里msg的callback和target都有值,那么会执行哪个呢?
其实上面已经贴过代码,就是dispatchMessage方法:
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
第2行,如果不为null,则执行callback回调,也就是我们的Runnable对象。
4.Handler在不同线程之间传递消息
可以再子线程中创建一个Handler,然后使用这个Handler实例在任何其他线程中发送消息,最终处理消息的代码会在你创建的Handler实例的线程中运行。
new Thread()
{
private Handler handler();
public void run(){
Looper.prepare();
handler = new Handler(){
public void handleMessage(android.os.Message msg){
};
};
二、内存泄露及处理办法
java中的GC自动检查内存中的对象(虚拟机决定),如果GC发现一个或者一组对象可达状态,则将该对象从内存中回收。也就是说一个对象不被任何引用所指向,该对象会在被GC发现的时候回收。另外如果一组对象中只包含互相的引用,而没有来自他们外部的引用,仍属于不可达状态,同样会被回收。
1、Handler中为什么会在成内存泄露呢?
Handler mHandler = new Handler(){
public void handleMessage(Message msg){
//处理逻辑
}
}
当使用内部类创建Handler的时候,会隐式的持有一个外部类对象(通常是一个Activity)引用,不然怎么通过Handler操作Activity中的view,而Handler一般伴随着一个耗时的后台线程,后台线程处理完成之后,会通过消息机制通知Handler,然后Handler把消息更新到Activity,如果在Handler执行耗时逻辑的时候Activity关闭了,这个时候Handler持有Activity的引用,而Activity持有Handler的引用,此时GC就无法回收Activity,直到耗时逻辑执行结束。另外如果你执行Handler的Delayed方法,该方法会将你的Handler装入一个Message,并把这条Message推入到MessageQueue中,在delay到达之前,会有一条MessageQueue -> Message -> Handler -> Activity的链,导致你的Activity被持有引用而无法被回收。
内存泄露的危害之后一个,就是虚拟机占用内存过高,导致OOM(内存溢出) ,程序出错,比如打开一个Activity,然后关闭,内存泄露,循环几次后,程序占用内存超过系统限制,然后挂掉。
2、解决内存泄露的办法
方法一:在关闭Activity的时候关闭后台程序,线程关闭相当于切断了Handler和外部连接的线。
如果Handler被delay的Message持有了引用,那么使用相应的Handler的removeCallbacks方法,把消息对象从消息队列中移除。
方法二:将Handler声明为静态类
静态类不持有外部类的对象,所以你的Activity可以随意被回收
static class MyHandler extent Handler{
public void handleMessage(Message msg){
//处理逻辑
}
}
但是添加静态内部类之后不持有外部对象引用,导致程序不允许在Handler中处理Activity对象,需要在Handler中增加一个对Activity的弱引用
statica class MyHandler extends Handler{
WeakReference<Activity>mActivityReference;
MyHandler(Activity activity){
mActivityReference = new WeakReference<Activity>(activity);
public void handleMessage(Message msg){
final Activity activity = mActivityReference.get();
if(activity != null){
//处理逻辑
}
}
}
}
当activity结束后需要取消掉该Handler对象的Message和Runnable,可以使用removeCallbacks(Runnable r)和 removeMessages(int what)方法。
public void onDestory(){
mHandler.removeCallbacksAndMessages(null);
}
3、强、软、弱、虚引用
四种引用方式可以更加灵活的控制对象的生命周期。
(1)强引用
Object object = new Object();那么object就是一个强引用,如果对象有强引用,GC不能回收,当内存空间不足时宁愿抛出OutOfMemoryError错误,使程序异常终止,也不会随意回收具有强引用的对象。
(2)软引用
如果内存空间充足,垃圾回收机制不会回收,如果内存空间不足,就会回收。如果一个软引用所引用的对象被回收,这个软引用就会被虚拟机加入到与之关联的引用队列中。软引用可用来实现内存敏感的高速缓存。 软引用可以和一个引用队列(ReferenceQueue)联合使用,如果软引用所引用的对象被垃圾回收,Java虚拟机就会把这个软引用加入到与之关联的引用队列中。
(3)弱引用
具有弱引用的对象生命周期更短,在垃圾回收线程扫描到时,一旦发现都会回收,但是GC优先级是一个优先级比较低的线程,因此不会很快发现只具有弱引的对象。弱引用可以和一个引用队列(ReferenceQueue)联合使用,如果弱引用所引用的对象被垃圾回收,Java虚拟机就会把这个弱引用加入到与之关联的引用队列中。
(4)虚引用
虚引用不会决定对象的生命周期,任何时候都可以被回收,虚引用可以被用来跟踪对象被垃圾回收的活动,虚引用与软引用和弱引用的一个区别在于:虚引用必须和引用队列(ReferenceQueue)联合使用。当垃圾回收器准备回收一个对象时,如果发现它还有虚引用,就会在回收对象的内存之前,把这个虚引用加入到与之关联的引用队列中。程序可以通过判断引用队列中是否已经加入了虚引用,来了解被引用的对象是否将要被垃圾回收。程序如果发现某个虚引用已经被加入到引用队列,那么就可以在所引用的对象的内存被回收之前采取必要的行动。
https://blog.csdn.net/panyongcsd/article/details/46605613
https://blog.csdn.net/jdsjlzx/article/details/51386440
这篇关于学习android中的Handler机制和由此引发的内存泄露以及解决办法的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!