美文网首页
Android的垃圾回收与内存泄露

Android的垃圾回收与内存泄露

作者: 背影杀手不太冷 | 来源:发表于2016-06-24 10:28 被阅读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中网络文件等流的没有手动关闭###

    • HTTP
    • File
    • ContendProvider
    • Bitmap
    • Uri
    • Socket

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

    • 线程泄漏:当你执行耗时任务,在onDestroy()的时候考虑调用Thread.close(),如果对线程的控制不够强的话,可以使用RxJava自动建立线程池进行控制,并在生命周期结束时取消订阅;
    • Handler泄露:当退出activity时,要注意所在Handler消息队列中的Message是否全部处理完成,可以考虑removeCallbacksAndMessages(null)手动关闭
    • 广播泄露:手动注册广播时,记住退出的时候要unregisterReceiver() 第三方SDK/开源框架泄露:ShareSDK,
    • JPush等第三方SDK需要按照文档控制生命周期,它们有时候要求你继承它们丑陋的activity,其实也为了帮你控制生命周期
    • 各种callBack/Listener的泄露,要及时设置为Null,特别是static的callback
    • EventBus等观察者模式的框架需要手动解除注册
    • 某些Service也要及时关闭,比如图片上传,当上传成功后,要stopself()
    • Webview需要手动调用WebView.onPause()以及WebView.destory()

    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是可变的,这样他们就可以改变它们的值
    • StringBuffer是线程安全的。当应用程序只需要运行在单个线程则最好使用StringBuilder。StringBuilder比StringBuffer更有效率,因为StringBuffer其实是给StringBuilder加了同步锁;其实你使用Log.d(TAG,"xx"+"yy")这类写法后,编译器生成的代码已经自动帮你变成StringBuilder了

    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('\'');


    • 同时,使用字符串进行逻辑运算是相当缓慢的,,不建议,因为JVM将字符串转换为字节码的StringBuffer。浪费大量的开销将从字符串转换为StringBuffer然后再返回字符串

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

    多用基本类型##

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

    Native代码不受GC控制##

    使用弱引用##

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

    相关文章

      网友评论

          本文标题:Android的垃圾回收与内存泄露

          本文链接:https://www.haomeiwen.com/subject/twyodttx.html