欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-19 16:07:37
...

概念

  • 内存溢出(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