Android的垃圾回收与内存泄露

2016-06-24  本文已影响400人  背影杀手不太冷

标签(空格分隔): Android


我们知道App都有一个UI线程,也叫主线程,那是Android框架帮我们创建的,这里注意的是不是每个activity对应一个UI主线程,而是一个App。
为Message都在一个队列中,拥有它下一个对象的引用非常重要,这里的写法其实跟我们上学时学习的队列类似,每一个实体类都拥有下一个的引用,这样就构成了一个队列

内存泄漏的基本知识请见博客一
如何高效使用handler避免内存泄漏请见博客二

Looper造成内存泄漏的总结为:
1、因为Looper里面的MesageQueue持有外部传进来的runnable引用,runnable又持有handler的引用,handler持有activity的引用//这种情况在用handler开启一个耗时的后台操作时,这时开启的线程也会持有handler的引用,在关闭`Activity`的时候停掉你的后台线程。线程停掉了,就相当于切断了`Handler`和外部连接的线,`Activity`自然会在合适的时候被回收。
2、在使用handler.handleMessage()这个方法时,实际上是一个回调的过程
,在message的内部handleMessage()将肯定是持有Handler的引用,而handler又持有activity的引用

会有一条链`MessageQueue -> Message -> Handler -> Activity`,由于它的引用导致你的`Activity`被持有引用而无法被回收`

weakHandler博客
WeakHandler的实现原理:
WeakHandler的思想是将Handler和Runnable做一次封装,我们使用的是封装后的WeakHandler,但其实真正起到handler作用的是封装的内部,而封装的内部对handler和runnable都是用的弱引用。

自己的理解:###

因为内部封装了的handler与runnable都是弱引用,所以实际上供调用者使用的是封装之后的weakHandler与weakRunnable,所虽然在Looper持有的weakhandler与weakRunnable,而weakHandler与weakRunnable又分别持有弱引用的handler与弱引用的runnable,所以当被封装了的handler与runnabl被回收时,weakHandler内部的弱引用handler与弱引用的runnable也会被回收,这时Looper持有的weakHandler内部已经没有了handler与runnable,只是一个空客,所以不会内存泄露。


以下的内容参考博客
内存泄漏潜在危害非常大,比如无意泄漏了一个Drawable,它可能只有几百K的占用,但是由于它一般会引用View,就意味着同时泄漏了View,Context,Activity 以及 Activity中的resource,这个内存的泄漏就非常可观了。
安卓中很容易出现这种连锁的引用泄露

造成内存泄露的情况有下面两种:##

1、try/catch/finally中网络文件等流的没有手动关闭###

2、onDestroy() 或者 onPause()中未及时关闭对象###


static class/method/variable 的区别,你真的懂了吗?##

(1). Static inner class 与 non static inner class 的区别
static inner class即静态内部类,它只会出现在类的内部,在某个类中写一个静态内部类其实同你在IDE里新建一个.java 文件是完全一样的。


此处输入图片的描述此处输入图片的描述

可以看到,在生命周期中,埋下了内存泄漏的隐患,如果它的生命周期比activity更长,那么可能会发生泄露,更可怕的是,有可能会产生难以预防的空指针问题。这个泄露的例子,详见内存管理(2)的文章

(2). static inner method
静态内部方法,也就是虚函数:可以被直接调用,而不用去依赖它所在的类,比如你需要随机数,只用调用Math.random()即可,而不用实例化Math这个对象。在工具类(Utils)中,建议用static修饰方法。static方法的调用不会泄露内存

(3). static inner variable
慎重使用静态变量,静态变量是被分配给当前的Class的,由类的所有实例共享,而不是一个独立的实例,当ClassLoader停止加载这个Class时,它才会回收。在Android中,需要手动置空才会卸掉ClassLoader,才能出现GC。

当你旋转屏幕后,Drawable就会泄露。

匿名内部类实际上就是non-static inner class,所以也会有non-static inner class的缺点##

单例模式(Singleton)是不是内存泄漏?##

在单例模式中,只有一个对象被产生,看起来一直占用了内存,但是这个不意味就是浪费了内存,内存本来就是用来装东西的,只要这个对象一直被高效的利用就不能叫做泄露。但是也不要偷懒,一个劲的全整成了单例,越多的单例会让内存占用过多,放在Application中初始化的内容也越多,意味着APP打开白屏的时间会更久,而且软件维护起来也变得复杂。

好的例子:GlobalContext,SmsReceiver动态注册,EventBus

为什么大神喜欢用static final来修饰常数?##

static由于是所有实例共享的,说到共享一定要加锁,万一某个实例更改它后,其它的实例也会受到影响,所以加入final作为永久只读锁以防止常数被修改。

下面的话什么意思,看不懂???

全局变量生命周期是classloader,有坑。你的activity在finish后变量并不会改变。这个在面试中经常遇到,问你经过多次计算后,static的值是多少。比如在Android中有个坑,最常见的就是把一个sharedpreference赋值给一个static变量,然后又把sharedpreference改变后,再次调用这个static变量,就发现变量并没有改变,这个在debug中很难发现。

顺便说下final吧##

final 变量:是只读的;
final 方法:是不能继承或者重写的。
final 引用:引用不能修改,但是对象本身的属性可以修改;
final class:不可继承;
final不会让代码速度更快

Bitmap的使用##

使用前注意配置Bitmap的Config,比如长宽,参数(565, 8888),格式;
使用中注意缓存;
使用后注意recycle以清理native层的内存。
2.3以后的bitmap不需要手动recycle了,内存已经在java层了。同时,Bitmap还有别人做好的轮子,比如PhotoView,Picasso,就可以方便的解决OOM问题。

线程泄露可能是最严重的泄露问题##

例如在activity中开了一个线程去上传图片,完成之后弹出toast,但是在还没有上传完成之前,点击了推出了activity,注意上传线程是还在跑,当上传完成之后,却发现window没了,toast弹不出所以抛出异常
所以应该在activity退出的时候把上传线程也要停止了。

Context与ApplicationContext##

Context的生命周期是一个Activiy,而ApplicationContext的生命周期是整个程序。我们最要注意的就是Context的内存泄露。
在Activiy的UI中要使用Context,而在其他的地方比如数据库、网络、系统服务的需要频繁调用Context的情况时,要使用ApplicationContext,以防止内存泄露。
为什么ApplicationContext不会内存泄漏???


其他的小技巧##

1、Listview的item泄露###

这个是入门问题了,加入ViewHolder可以减少findViewById的时间,或者使用RecyclerView,来解决“滑动很卡”的问题。这个实质也是一个单例。

2、StringBuilder###

首先说一下String,StringBuilder,StringBuffer的区别:


StringBuffer原理分析:###

将字符串拼接时(不管是字面常量也好,或者是变量,方法调用的结果也好),即用“+”将多个字符串拼接时,实际上都是变成StringBuilder。如果一个字符串(不管是字面常量也好,或者是变量,方法调用的结果也好)
new StringBuilder().append( string_exp ).append( any_exp ).toString()
如果表达式里有多个+号的话,后面相应也会多多几个StringBuilder.append的调用,最后才是toString方法。
   StringBuilder(String)这个构造方法会分配一块16个字符的内存缓冲区。因此,如果后面拼接的字符不超过16的话,StringBuilder不需要再重新分配内存,不过如果超过16个字符的话StringBuilder会扩充自己的缓冲区。最后调用toString方法的时候,会拷贝StringBuilder里面的缓冲区,新生成一个String对象返回。
  所以在在我们经常将一些基本数据类型转化成字符串时,例如经常是这样做的:String text=100+"";虽然可以将整数100转化成“100”字符串,但是一个StringBuilder对象,一个char[16]数组,一个String对象,一个能把输入值存进去的char[]数组。这样是很浪费内存的,所以推荐使用String.valueOf,即String text=String.valueOf(100);这样至少StringBuilder对象省掉了。
  有的时候或许你根本就不需要转化基础类型。比如,你正在解析一个字符串,它是用单引号分隔开的。最初你可能是这么写的:
  final int nextComma = str.indexOf("'");
  或者是这样
   final int nextComma = str.indexOf('\'');


综上所述:尽量使用StringBuilder,而不用String来累加字符串

多用基本类型##

使用int而不用Integer,较少的对象花销。在Android中使用sparseArrayMap取代HashMap就是把key变成了int,而一定程度上减小了内存占用。

Native代码不受GC控制##

使用弱引用##

使用弱引用可以防止一定程度的无意引用造成的泄露,比如在Handler中使用弱引用作为参数,当销毁的时候就有可能不会发生泄露。
但是弱引用随时可能为null,使用前需要判断是否为空

上一篇下一篇

猜你喜欢

热点阅读