浅谈 Android 内存溢出与内存泄漏

概念

  • 内存溢出(Out of memory):系统会给每个APP分配内存,默认16M内存,每个手机厂商的默认值不一样,当APP所需要的内存大于了系统分配的内存,就会造成内存溢出;内存溢出就是分配的内存被用光了,不够用了。

  • 内存泄漏(Memory leak):当一个对象不再使用了,本应该被垃圾回收器(GC)回收,但是这个对象由于被其他正在使用的对象所持有,造成无法被回收,导致一部分内存一直被占着。

  • 内存泄漏和内存溢出的关系:内存泄露过多会导致内存溢出。内存泄露导致一部分内存没有被回收,一直被占着,可利用内存变少了,当泄露过多 时,可利用的内存越来越少,就会引起内存溢出了。

原因及优化

内存溢出

  • Bitmap使用不当
    1、使用Bitmap对象要用recycle释放
    2、使用软引用、弱引用
    3、使用图片缓存技术(例如:LruCache)

内存泄漏

  • 在内存比较低的系统上对大量的图片、音频、视频处理
    优化: 建议使用第三方,或者JNI来进行处理

  • Handler使用不当
    例如:
    在日常开发中我们通常都是这样定义Handler对象:

Handler handler = new Handler() {  
        @Override  
        public void handleMessage(Message msg) {  
            dosomething();  

        }  
    };

但是这样也存在着一个隐藏的问题:在Activity中使用Handler创建匿名内部类会隐式的持有外部Activity对象的引用,当子线程使用Handler暂时无法完成异步任务时,handler对象无法销毁,同时由于隐式的持有activity对象的引用,造成activity对象以及相关的组件与资源文件同样无法销毁,造成内存泄露。我们普通的Handler,平时使用时也是可以不用考虑这些的,但是当你在子线程中有耗时操作通知UI时,要考虑到这一点,以免activity结束时子线程持有Handler对象,导致Activity无法被释放。
优化解决方法:
1)使用静态变量定义Handler

static Handler handler = new Handler() {  

        @Override  
        public void handleMessage(Message msg) {  
            dosomething();  

        }  
    };  

这样的话,handler对象由于是静态类型无法持有外部activity的引用,但是这样Handler不再持有外部类的引用,导致程序不允许在Handler中操作activity中的对象了,这时候我们需要在Handler对象中增加一个队activity的弱引用;

2)Handler使用弱引用

static class MyHandler extends Handler {  
    WeakReference<Activity > mActivityReference;  
    MyHandler(Activity activity) {  
        mActivityReference= new WeakReference<Activity>(activity);  
    }  
    @Override  
    public void handleMessage(Message msg) {  
        final Activity activity = mActivityReference.get();  
        if (activity != null) {  
            mImageView.setImageBitmap(mBitmap);  
        }  
    }  
} 

最后在ondestory方法中将后面的消息移除
在activity执行onDestory时,判断是否有handler已经执行完成,否则做相关逻辑操作;

mhanHandler.removeCallbacksAndMessages(null);
  • Context对象使用不当
    例如自定义单例对象
public class Single {
    private static Single instance;
    private Context context;
    private Object obj = new Object();

    private Single(Context context) {
        this.context = context;
    }

    public static Single getInstance(Context context) {
        if (instance == null) {
            synchronized(obj) {
                if (instance == null) {
                    instance = new Single(context);
                }
            }
        }
        return instance;
    }
}

优化解决方法:使用全局的的Context或者context.getApplication();
单列模式应该尽量少持有生命周期不同的外部对象,一旦持有该对象的时候,必须在该对象的生命周期结束前制null

  • 非静态内部类,静态实例化
    static关键字修饰的变量由于生命周期过长,容易造成内存泄漏
    例如:
public class MyActivity extends AppCompatActivity {
    /** 静态成员变量*/
    public static InnerClass innerClass = null;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);

        innerClass = new InnerClass();
    }

    class InnerClass {

        public void test() {
            //TODO ...
        }
    }
}

这里内部类InnerClass隐式的持有外部类MyActivity的引用,而在MyActivity的onCreate方法中调用了 innerClass = new InnerClass();这样innerClass就会在MyActivity创建的时候是有了他的引用,而innerClass是静态类型的不会被垃圾回收,MyActivity在执行onDestory方法的时候由于被innerClass持有了引用而无法被回收,所以这样MyActivity就总是被innerClass持有而无法回收造成内存泄露。

优化解决方法:尽量少使用静态变量,一定要使用要及时进行制null处理

  • 属性动画或循环动画使用不当
    优化解决方法:在Activity中使用了属性循环动画,在onDestroy()方法中未正确停止动画

  • BraodcastReceiver、File、Cursor等资源的使用未及时关闭
    优化解决方法:在销毁activity时,应该及时销毁或者回收

  • 框架使用了注册方法而未反注册
    例如:
    使用的事件总线框架-EventBus,当我们需要注册某个Activity时需要在onCreate中:

EventBus.getDefault().register(this);

然后这样之后就没有其他操作的话就会出现内存泄露的情况,因为EventBus对象会是有该Activity的引用,即使执行了改Activity的onDestory方法,由于被EventBus隐式的持有了该对象的引用,造成其无法被回收。
优化解决方法:需要在onDestory方法中执行反注册:

EventBus.getDefault().unregister(this);

参考文章
https://blog.csdn.net/qq_28607155/article/details/76148989
https://blog.csdn.net/u014674558/article/details/62234008
https://blog.csdn.net/guolin_blog/article/details/9316683

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章