Handler如何有效地避免内存泄漏?

640?wx_fmt=gif


温馨提示

请拖动到文章末尾,长按识别「抽奖」小程序。现金红包等你来拿。


1


「小新」近来遇到了 Handler 的泄露,百思不得其解。

640?wx_fmt=png

2


640?wx_fmt=png

在正文开始之前,听「大师兄」讲述一段经历,作为一名职场的老前辈,问过很多人,你知道 Handler 吗?大家回答都知道,那什么是 Handler ?没多少人能够准备的概述,如果是你,你会怎么答?


官方 API 是这么介绍的:

Handler是用来结合线程的消息队列来发送、处理“Message对象”和“Runnable对象”的工具。每一个Handler实例之后会关联一个线程和该线程的消息队列。当你创建一个Handler的时候,从这时开始,它就会自动关联到所在的线程/消息队列,然后它就会陆续把Message/Runnalbe分发到消息队列,并在它们出队的时候处理掉。


从官方文档中,我们不难找出其中的关键词,就是“线程”。


我们都知道,一个涉及到网络操作,耗时操作等的 Android 应用,都离不开多线程操作,然而,如果这时我们允许并发更新 UI,那么最终导致控件的状态都是不可确定的。


所以,我们可以通过对控件进行加锁,在不需要用时解锁,这是一个解决方案之一,但最后很容易造成线程阻塞,效率会非常差。所以,谷歌采用了只允许在主线程更新 UI,所以作为线程通信桥梁的 Handler 也就应运而生了。


在耗时操作,通过 Handler 来更新 UI,经常会遇到这里提示 This Handler class should be static or leaks might occur 那么为什么会有这样的内存泄露警告呢?


  • 在 Java 中,非静态内部类和匿名内部类都会隐式地持有其外部类的引用。静态内部类不会持有外部类的应用。


  • 每个 Handler 都需要一个 Looper 来处理消息,但是线程是默认没有 Looper 的,如果需要使用 Handler,就需要为线程创建 Looper。在应用的主线程(ActivityThread)创建时会自动初始化一个 Looper,这也是为什么主线程中可以默认使用Handler 的原因。因此主线程中的 Looper 生命周期是和应用的一样长。


  • 发送的消息中包含有 Handler 实例的引用,只有消息被处理后,handler 的引用才会释放,如果 Activity 已经销毁,但是Looper 中还有消息没有处理完。handler 就不能释放,如果handler 不是 Activity 的静态内部类,那么 handler 就会持有 Activity 的引用,导致该 Activity 不能正常回收,这样就造成了内存泄露。


大家都在说内部类,那么什么又是匿名内部类,方法内部类呢?


3


640?wx_fmt=png

java内部类分为:


  • 成员内部类

  • 静态嵌套类

  • 方法内部类

  • 匿名内部类 


内部类的共性


  • 内部类仍然是一个独立的类,在编译之后内部类会被编译成独立的 .class 文件,但是前面冠以外部类的类名和 $ 符号。


  • 内部类不能用普通的方式访问。内部类是外部类的一个成员,因此内部类可以自由地访问外部类的成员变量,无论是否是 private 的。


  • 内部类声明成静态的,就不能随便的访问外部类的成员变量了,此时内部类只能访问外部类的静态成员变量。


成员内部类


 
 
public class User {	
    class People{	
       	
    }	
}

People 为 User 的内部类,编译后会产生两个文件:User.class 和User$People.class。


方法内部类 


 
 
public class User {	
    // 方法内部类	
    public void doSomething(){	
        class People{	
	
        }	
    }	
}


(1)、方法内部类只能在定义该内部类的方法内实例化,不可以在此方法外对其实例化。


(2)、方法内部类对象不能使用该内部类所在方法的非 final 局部变量。


因为方法的局部变量位于栈上,只存在于该方法的生命期内。当一个方法结束,其栈结构被删除,局部变量成为历史。但是该方法结束之后,在方法内创建的内部类对象可能仍然存在于堆中!例如,如果对它的引用被传递到其他某些代码,并存储在一个成员变量内。正因为不能保证局部变量的存活期和方法内部类对象的一样长,所以内部类对象不能使用它们。


来看下完整例子:


 
 
public class User {	
    // 方法内部类	
    public void doSomething() {	
        final int a = 10;	
        int b = 20;	
        class People {	
            public void seeUser() {	
                System.out.println(a);	
                // 报错 System.out.println(b);	
            }	
        }	
        People people = new People();	
        people.seeUser();	
    }	
	
    public static void main(String[] args) {	
        User user = new User();	
        user.doSomething();	
    }	
}

结果打印:10


匿名内部类


顾名思义,没有名字的内部类。当程序中使用匿名内部类时,在定义匿名内部类的地方往往直接创建该类的一个对象。


匿名内部类的声明格式如下:


 
 
    new User(){	
        	
    };


那么什么情况下需要使用匿名内部类?如果满足下面的一些条件,使用匿名内部类是比较合适的:


只用到类的一个实例 。

类在定义后马上用到。

类非常小(SUN推荐是在4行代码以下)

给类命名并不会导致你的代码更容易被理解。


在使用匿名内部类时,要记住以下几个原则:


匿名内部类不能有构造方法。


匿名内部类不能定义任何静态成员、静态方法。


匿名内部类不能是


public,protected,private,static。


只能创建匿名内部类的一个实例。


一个匿名内部类一定是在new的后面,用其隐含实现一个接口或实现一个类。


因匿名内部类为局部内部类,所以局部内部类的所有限制都对其生效。


继承式的匿名内部类


 
 
public class Car {	
    public void drive() {	
        System.out.println("Driving a car!");	
    }	
	
    public static void main(String[] args) {	
        Car car = new Car() {	
            @Override	
            public void drive() {	
                // super.drive();	
                System.out.println("Driving another car!");	
            }	
        };	
        // 调用	
        car.drive();	
    }	
}

输出结果:Driving another car!


接口式的匿名内部类


 
 
public class Animal {	
    public static void main(String[] args) {	
        Move move = new Move() {	
            @Override	
            public void eat() {	
                System.out.println("Animal eat!");	
            }	
        };	
        move.eat();	
    }	
}	
interface Move {	
    void eat();	
}

输入结果:Animal eat!


静态嵌套类


 
 
public class User {	
    static class People {	
	
    }	
	
    public static void main(String[] args) {	
        User.People people = new User.People();	
    }	
}

静态的含义是该内部类可以像其他静态成员一样,没有外部类对象时,也能够访问它。


静态嵌套类仅能访问外部类的静态成员和方法。


4


那么如何避免 Handler 引起的内存泄露,这里推荐以下两种做法。


当 Activity 生命周期结束时,移除 Handler 中未执行的消息:


 
 
    @Override	
    protected void onDestroy(){	
        super.onDestroy();	
        mHandler.removeCallbacksAndMessages(null);	
    }

 

第二种方法,将 Handler 类声明为静态内部类,同时持有 Activity 的弱引用,在 gc 时就可以对 Activity 进行回收。代码可以这样写:


 
 
    private static class MyHandler extends Handler {	
        private final WeakReference<Activity> mActivityWeakReference;	
	
        private MyHandler(Activity activity) {	
            mActivityWeakReference = new WeakReference<Activity>(activity);	
        }	
	
        @Override	
        public void handleMessage(Message msg) {	
            super.handleMessage(msg);	
            if (mActivityWeakReference == null || mActivityWeakReference.get() == null) {	
                return;	
            }	
        }	
    }


以上仅个人观点,有什么疑问,请留言哟。


推荐阅读


安卓dalvik虚拟机和Art虚拟机的优化升级点


LinearLayout和RelativeLayout性能对比,你认为谁的效率更高?


640?wx_fmt=png长按识别小程序,参与抽奖

640?wx_fmt=png


更多现金红包,请长按二维码640?wx_fmt=other 640?wx_fmt=png

目前100000+人已关注加入我们

640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif

640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif 640?wx_fmt=gif


微信扫描参与抽奖,喜欢请点击再看


640?wx_fmt=png

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值