最近在温习Android基础知识的时候遇到了一个老生常谈的问题,那就是使用Handler的时候会造成内存泄漏的问题,这个问题一直都是面试官最得意的问题,但是如果说为什么会造成内存泄漏,还真的难住了好多人,今天有了一些自己的思路,存档以备日后参考。
首先有几个问题需要理清一下:
1、内存溢出、内存泄漏的概念
2、内部类、静态内部类、匿名内部类、java内存管理(主要是堆栈的分布)
3、Handler源码
注意:本人参考广大资料、博客,为上述问题作出解答,基本是CV,内容价值请自己考量。
虽然网上类似的博客有很多,讲解的也很详细,我还是对最先接触的最有感触
参考http://www.cnblogs.com/vamei/archive/2013/04/01/2992484.html
以及里面的链接http://www.cnblogs.com/vamei/archive/2012/10/09/2715388.html
一些重点内容概要:
1、我们平常开发的APP基本都是单进程应用,它在安装到手机上时获取的内存空间是固定的,这部分内存空间自上而下分为Stack、Unused Area、Heap、Global Data、Text(instruction code)五部分。Text区域用来储存指令(instruction),说明每一步的操作。Global Data用于存放全局变量,栈(Stack)用于存放局部变量,堆(heap)用于存放动态变量 (dynamic variable. 程序利用malloc系统调用,直接从内存中为dynamic variable开辟空间)。Text和Global data在进程一开始的时候就确定了,并在整个进程中保持固定大小。
2、基本数据类型及对象的引用存在于栈上,new出来的对象存在于堆上,包括对象的方法及变量。
3、垃圾回收的基本原则是,当存在引用指向某个对象时,那么该对象不会被回收; 当没有任何引用指向某个对象时,该对象被清空。它所占据的空间被回收。
4、非静态内部类持有对外部类的引用,静态内部类不持有对外部类的引用。
5、什么是Handler?
handler通俗一点讲就是用来在各个线程之间发送数据的处理对象。在任何线程中,只要获得了另一个线程的handler,则可以通过 handler.sendMessage(message)方法向那个线程发送数据。基于这个机制,我们在处理多线程的时候可以新建一个thread,这个thread拥有UI线程中的一个handler。当thread处理完一些耗时的操作后通过传递过来的handler向UI线程发送数据,由UI线程去更新界面。
主线程:运行所有UI组件,它通过一个消息队列来完成此任务。设备会将用户的每项操作转换为消息,并将它们放入正在运行的消息队列中。主线程位于一个循环中,并处理每条消息。如果任何一个消息用时超过5秒,Android将抛出ANR。所以一个任务用时超过5秒,应该在一个独立线程中完成它,或者延迟处理它,当主线程空闲下来再返回来处理它。
6、常用类(Handler、Looper、Message、MessageQueue)
Message:消息,被传递和处理的数据。其中包含了消息ID,消息处理对象以及处理的数据等,由MessageQueue统一列队,终由Handler处理 。
Handler:处理者,负责Message的发送及处理。使用Handler时,需要实现handleMessage(Message msg)方法来对特定的Message进行处理,例如更新UI等。Handler类的主要作用:(有两个主要作用)1)、在工作线程中发送消息;2)、在主线程中获取、并处理消息。
MessageQueue:消息队列,本质是一个数据结构,用来存放Handler发送过来的消息,并按照FIFO规则执行。当然,存放Message并非实际意义的保存,而是将Message串联起来,等待Looper的抽取。
Looper:消息泵或循环器,不断从MessageQueue中抽取Message。因此,一个MessageQueue需要一个Looper。
Thread:线程,负责调度整个消息循环,即消息循环的执行场所。
7、Looper对象用来为一个线程开启一个消息循环、从而操作MessageQueue;默认情况下,Android创建的线程没有开启消息循环Looper,但是主线程例外。
针对以上概要,我提出几点自己的看法:
1、
private Handler mHandler=new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case GO_GUIDE: goGuide(); break; case GO_MAIN: goMain(); break; } } };
在主线程使用这种方式创建Handler并使用,并不一定会出现内存泄漏,而是极少情况下。如果出现内存泄漏,那么一定是匿名内部类持有外部类的引用,也就是Handler持有外部Activity,那么Handler为什么没有释放,是因为有个对象的引用mHander指向了堆内存上存放Handler对象的首地址空间。其实还是因为你使用了mHandler做了一些类似的
mHandler.sendEmptyMessageDelayed(GO_GUIDE, TIME);
Handler以这种方式发出去消息msg之后,消息会加入到MessageQueue(消息队列中),然后由Looper循环的去接收处理。所以会出现这样一条引用链Looper——>msg——>handler——>activity导致在Activity执行onDestory方法的时候GC不能及时释放内存导致短暂时间内的内存泄漏。因为主线程的Looper是系统自动开启并循环执行的,所以它不可能释放,只有等msg被它接收并处理掉后msg会释放,这时候执行GC,handler会释放,activity也释放。如果你仅仅是使用handler发送一条消息,从原理上讲,并不会出现内存泄漏。但是如果你写了个死循环,
for(::){
mHandler.sendMessage(msg);
}
如果真的写出这样的代码,这程序员脑子有坑吧,这是要完成怎样的需求?如果真的这样写了,那请参考百度千篇一律的文档,类似于这样的
但是一般情况下,简单的消息传递,根本不需要这样的操作,我项目中经常使用handler进行简单的消息传递,如果一旦使用handler就要这样写一套静态内部类,多繁琐,自定义内部类继承Handler,重写构造方法,传递的Activity每次都不一样,当然你可以进行封装。
还有我最后想说的是,即使真的因为mHand.sendMessageDelayed(msg);延时发送消息,导致Activity在执行onDestory的时候,GC检测到msg还未处理,即存在msg——>handler——>Activity这样的一条引用链导致Activity未及时回收内存,那么等一段时间后(delayed)msg被looper接收并处理,等到下次GC的时候,此时Activity内存不会被释放吗?所以,只要编程人员逻辑思维够清晰,不写死循环之类的变态代码,handler内存泄漏不应该是困扰我们的问题。