OOM主要有两种原因导致:
1. 加载大图片;
2. 内存泄漏;
一、加载大图片
在Android应用中加载Bitmap的操作是需要特别小心处理的,因为Bitmap会消耗很多内存。比如,Galaxy Nexus的照相机能够拍摄2592x1936 pixels (5 MB)的图片。 如果bitmap的图像配置是使用ARGB_8888 (从Android 2.3开始的默认配置) ,那么加载这张照片到内存大约需要19MB(2592*1936*4 bytes) 的空间,从而迅速消耗掉该应用的剩余内存空间。
对于分辨率比我们手机屏幕的分辨率高得多的图片,我们应该加载一个缩小版本的图片,从而避免超出程序的内存限制。下面我们就来看一看,如何对一张大图片进行适当的压缩,让它能够以最佳大小显示的同时,还能防止OOM的出现。
2.单例模式
不正确使用单例模式是引起内存泄漏的一个常见问题,单例对象在被初始化后将在JVM的整个生命周期中存在(以静态变量的方式),如果单例对象持有外部对象的引用,那么这个外部对象将不能被JVM正常回收,导致内存泄漏。
如果需要Context,尽量引用Application,而不用Activity。
3.Handler
Handler的生命周期与Activity不一致
由于Handler属于TLS(Thread Local Storage)变量,生命周期和Activity是不一致的。
当Android应用启动的时候,会先创建一个UI主线程的Looper对象,Looper实现了一个简单的消息队列,一个一个的处理里面的Message对象。主线程Looper对象在整个应用生命周期中存在。
当在主线程中初始化Handler时,该Handler和Looper的消息队列关联(没有关联会报错的)。发送到消息队列的Message会引用发送该消息的Handler对象,这样系统可以调用 Handler#handleMessage(Message) 来分发处理该消息。
只要Handler发送的Message尚未被处理,则该Message及发送它的Handler对象将被线程MessageQueue一直持有。因此这种实现方式一般很难保证跟View或者Activity的生命周期保持一致,故很容易导致无法正确释放。
handler引用Activity阻止了GC对Acivity的回收
在Java中,非静态(匿名)内部类会默认隐性引用外部类对象。而静态内部类不会引用外部类对象。
如果外部类是Activity,则会引起Activity泄露。
当Activity finish后,延时消息会继续存在主线程消息队列中1分钟,然后处理消息。而该消息引用了Activity的Handler对象,然后这个Handler又引用了这个Activity。这些引用对象会保持到该消息被处理完,这样就导致该Activity对象无法被回收,从而导致了上面说的 Activity泄露。
错误示例:
private final Handler mHandler = new Handler() {
@Override public void handleMessage(Message msg) { // … }
};
解决办法:
使用显形的引用,1.静态内部类。 2. 外部类
使用弱引用 2. WeakReference
正确示例:
private static class MyHandler extends Handler {
private final WeakReference<HandlerActivity2> mActivity; public MyHandler(HandlerActivity2 activity) {
mActivity = new WeakReference<HandlerActivity2>(activity);
}
if (mActivity.get() == null) { return; } mActivity.get().todo();
}
@Override public void onDestroy() {
// If null, all callbacks and messages will be removed.
mHandler.removeCallbacksAndMessages(null);
}
4.非静态内部类导致的内存泄漏:
非静态的内部类会持有外部类的一个引用,所以和前面context说到的一样,如果该内部类生命周期超过外部类的生命周期,就可能引起内存泄露了,如AsyncTask和Handler。因为在Activity中我们可能会用到匿名内部类,所以要小心管理其生命周期。 如果明确生命周期较外部类长的话,那么应该使用静态内部类。
5.Context导致内存泄漏:
android 中很多地方都要用到context,连基本的Activty 和 Service都是从Context派生出来的,我们利用Context主要用来加载资源或者初始化组件,在Activity中有些地方需要用到Context的时候,我们经常会把context给传递过去了,将context传递出去就有可能延长了context的生命周期,最终导致了内存泄漏。例如 我们将activty context对象传递给一个后台线程去执行某些操作,如果在这个过程中因为屏幕旋转而导致activity重建,那么原先的activity对象不会被回收,因为它还被后台线程引用着,如果这个activity消耗了比较多的内存,那么新建activity或者后续操作可能因为旧的activity没有被回收而导致内存泄漏。所以,遇到需要用到context的时候,我们要合理选择不同的context,对于android应用来说还有一个单例的Application Context对象,该对象生命周期和应用的生命周期是绑定的。选择context应该考虑到它的生命周期,如果使用该context的组件的生命周期超过该context对象,那么我们就要考虑是否可以用application context。如果真的需要用到该context对象,可以考虑用弱引用来WeakReference来避免内存泄漏。