单例导致内存泄漏
首先来看一下一种单例的写法:多种单例写法参考

public class Utils{private static Utils mInstance;private Context mContext;private Utils(Context context){this.mContext = context;}public static Utils getInstance(Context context){if(mInstance == null){Util = new Utils(context);}return mInstance;}}

以Activity为例,当我们启动一个Activity,并调用Utils.getInstance(Context context),传入Activity.this作为context,这样Utils类的单例mInstance就持有了Activity的引用,当我们退出Activity时,该Activity就没有用了,但是因为mIntance作为静态单例(在应用程序的整个生命周期中存在)会继续持有这个Activity的引用,导致这个Activity对象无法被回收释放,这就造成了内存泄露。
为了避免这样单例导致内存泄露,我们可以将context参数改为全局的上下文:

private Utils(Context context){this.mContext = context.getApplicationContext();}

全局的上下文Application Context就是应用程序的上下文,和单例的生命周期一样长,这样就避免了内存泄漏。单例模式对应应用程序的生命周期,所以我们在构造单例的时候尽量避免使用Activity的上下文,而是使用Application的上下文。

非静态内部类导致内存泄漏
我们都知道内部类会持有外部类引用。 原因通过编译查看.class文件可知,编译器会为内部类构造传入外部类实例.所以非静态内部类会持有外部类引用。
这样在特定情况下就会产生一个问题。就是如果内部类的生命周期比外部类的生命周期长,那么在外部类无用时,内部类依然持有外部类引用,导致外部类无法释放,从而导致内存泄漏。
Android 中典型的使用场景就是Handler.通常我们的写法是这样:

public class MainActivity extends AppCompatActivity{@Overrideprotected void onCreate(Bundle savedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);start();}private void start(){Messagemsg=Message.obtain();msg.what=1;mHandler.sendMessage(msg);}private Handler mHandler=new Handler(){@Overridepublic void handleMessage(Message msg){if(msg.what==1){//做相应逻辑}}};}

熟悉Handler消息机制的都知道,mHandler会作为成员变量保存在发送的消息msg中,即msg持有mHandler的引用,而mHandler是Activity的非静态内部类实例,即mHandler持有Activity的引用,那么我们就可以理解为msg间接持有Activity的引用。msg被发送后先放到消息队列MessageQueue中,然后等待Looper的轮询处理: MessageQueue和Looper都是与线程相关联的,MessageQueue是Looper引用的成员变量,而Looper是保存在ThreadLocal中的。那么当Activity退出后,msg可能仍然存在于消息对列MessageQueue中未处理或者正在处理,那么这样就会导致Activity无法被回收,以致发生Activity的内存泄露。

通常在Android开发中如果要使用内部类,但又要规避内存泄露,一般都会采用静态内部类+弱引用的方式。

在Activity中创建静态内部类:

public class MainActivity extends AppcompatActivity{private static class MyHandler extends Handler{        private WeakReference<MainActivity> activityWeakReference;        public MyHandler(MainActivity activity){            activityWeakReference = new WeakReference<>(activity);        }        @Override        public void handleMessage(@NonNull Message msg) {            if(activityWeakReference.get() != null){                if(msg.what == 1 ){                    //do something                }            }        }    }

上面的做法确实避免了Activity导致的内存泄露,发送的msg不再已经没有持有Activity的引用了,但是msg还是有可能存在消息队列MessageQueue中,所以更好的是在Activity销毁时就将mHandler的回调和发送的消息给移除掉。

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

非静态内部类造成内存泄露还有一种情况就是使用Thread或者AsyncTask。 同样类似于handler,生命周期的不一致导致内存泄漏。
总之,在使用内部类时,要时刻注意生命周期是否一致,如果不一致就要考虑是否会发生内存泄漏的问题。

未取消注册或回调导致内存泄露
在注册观察则模式的时候,如果不及时取消也会造成内存泄露。比如使用Retrofit+RxJava注册网络请求的观察者回调,同样作为匿名内部类持有外部引用,所以需要记得在不用或者销毁的时候取消注册

静态变量导致内存泄露
静态变量存储在方法区,它的生命周期从类加载开始,到整个进程结束。一旦静态变量初始化后,它所持有的引用只有等到进程结束才会释放。
尽量少地使用静态持有的变量,以避免发生内存泄露。当然,我们也可以在适当的时候讲静态量重置为null,使其不再持有引用,这样也可以避免内存泄露。
不要在类初始时初始化静态成员。可以考虑lazy初始化。
架构设计上要思考是否真的有必要这样做,尽量避免。如果架构需要这么设计,那么此对象的生命周期你有责任管理起来。

集合中的对象未清理造成内存泄露
这个比较好理解,如果一个对象放入到ArrayList、HashMap等集合中,这个集合就会持有该对象的引用。当我们不再需要这个对象时,也并没有将它从集合中移除,这样只要集合还在使用(而此对象已经无用了),这个对象就造成了内存泄露。并且如果集合被静态引用的话,集合里面那些没有用的对象更会造成内存泄露了。所以在使用集合时要及时将不用的对象从集合remove,或者clear集合,以避免内存泄漏

属性动画造成内存泄露
动画同样是一个耗时任务,比如在Activity中启动了属性动画(ObjectAnimator),但是在销毁的时候,没有调用cancle方法,虽然我们看不到动画了,但是这个动画依然会不断地播放下去,动画引用所在的控件,所在的控件引用Activity,这就造成Activity无法正常释放。因此同样要在Activity销毁的时候cancel掉属性动画,避免发生内存泄漏。

资源未关闭或释放导致内存泄露
在使用IO、File流或者Sqlite、Cursor等资源时要及时关闭。这些资源在进行读写操作时通常都使用了缓冲,如果及时不关闭,这些缓冲对象就会一直被占用而得不到释放,以致发生内存泄露。因此我们在不需要使用它们的时候就及时关闭,以便缓冲能及时得到释放,从而避免内存泄露
Bitmap 没调用 recycle()方法,对于 Bitmap 对象在不使用时,我们应该先调用 recycle() 释放内存,然后才它设置为 null. 因为加载 Bitmap 对象的内存空间,一部分是 java 的,一部分 C的(因为 Bitmap 分配的底层是通过 JNI 调用的 )。 而这个 recyle() 就是针对 C 部分的内存释放。

总结
内存泄露在Android内存优化是一个比较重要的一个方面,很多时候程序中发生了内存泄露我们不一定就能注意到,所有在编码的过程要养成良好的习惯。
总结下来只要做到以下这几点就能避免大多数情况的内存泄漏:

  • 构造单例的时候尽量别用Activity的引用;
  • 静态引用时注意应用对象的置空或者少用静态引用;
  • 使用静态内部类+软引用代替非静态内部类;
  • 及时取消广播或者观察者注册;
  • 耗时任务、属性动画在Activity销毁时记得cancel;
  • 文件流、Cursor等资源及时关闭;
  • Activity销毁时WebView的移除和销毁(android5.1上的bug,当Activity销毁时,webView并没有被真正移除掉,还持有Activity的引用)。

更多相关文章

  1. android查看程序运行内存
  2. 自定义progressBar显示静态数据
  3. 【Android】图片显示内存优化(解决“bitmap size exceeds VM budg
  4. 显示Android当前可用系统内存方法
  5. Android:注册一个方向传感器的回调,能够让app常驻内存不被杀死
  6. Android FFmpeg 脚本编译静态库
  7. Android之读取手机内存中的文件数据
  8. Android内存泄漏检测-LeakCanary
  9. Android连接WiFi设置IP为静态IP

随机推荐

  1. android 各版本区别
  2. 2018 Android 文章合集 200+ 篇
  3. 安卓基础知识总结
  4. Android 中几种更新UI界面的方法
  5. Android View视图绘制
  6. android之eclipse下查看android系统源代
  7. (android:windowIsTranslucent)影响(andr
  8. Android(安卓)init进程启动过程分析
  9. Unity自动打包工具——Mac上打包android
  10. Android测试教程(16):monkeyrunner简介