学习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并没有创建什么线程,而是发送了一条消息,下面看源码:

[java] view plain copy
  1. public final boolean post(Runnable r)  
  2.    {  
  3.       return  sendMessageDelayed(getPostMessage(r), 0);  
  4.    }  
  1. private static Message getPostMessage(Runnable r) {  
  2.       Message m = Message.obtain();  
  3.       m.callback = r;  
  4.       return m;  
  5.   }  

      可以看到,在getPostMessage中,得到了一个Message对象,然后将我们创建的Runable对象作为callback属性,赋值给了此message.

      注:产生一个Message对象,可以new  ,也可以使用Message.obtain()方法;两者都可以,但是更建议使用obtain方法,因为Message内部维护了一个Message池用于Message的复用,避免使用new 重新分配内存。

[java] view plain copy
  1. public final boolean sendMessageDelayed(Message msg, long delayMillis)  
  2.    {  
  3.        if (delayMillis < 0) {  
  4.            delayMillis = 0;  
  5.        }  
  6.        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);  
  7.    }  

[java] view plain copy
  1. public boolean sendMessageAtTime(Message msg, long uptimeMillis) {  
  2.        MessageQueue queue = mQueue;  
  3.        if (queue == null) {  
  4.            RuntimeException e = new RuntimeException(  
  5.                    this + " sendMessageAtTime() called with no mQueue");  
  6.            Log.w("Looper", e.getMessage(), e);  
  7.            return false;  
  8.        }  
  9.        return enqueueMessage(queue, msg, uptimeMillis);  
  10.    }  
最终和handler.sendMessage一样,调用了sendMessageAtTime,然后调用了enqueueMessage方法,给msg.target赋值为handler,最终加入MessagQueue.

可以看到,这里msg的callback和target都有值,那么会执行哪个呢?

其实上面已经贴过代码,就是dispatchMessage方法:

  1. public void dispatchMessage(Message msg) {  
  2.        if (msg.callback != null) {  
  3.            handleCallback(msg);  
  4.        } else {  
  5.            if (mCallback != null) {  
  6.                if (mCallback.handleMessage(msg)) {  
  7.                    return;  
  8.                }  
  9.            }  
  10.            handleMessage(msg);  
  11.        }  
  12.    }  

第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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值