首页 > 代码库 > Android OOM 解决方案

Android OOM 解决方案

Out of Memory(内存溢出) 几乎是每个Android程序员都会遇到的事。在网上也能找到一大堆的解决方案,之前写过一篇《Android 内存溢出管理与测试》的博文。但感觉写得不是很好,今天整理一下打算重新写一篇。

 

首先什么是OOM?为什么会出现OOM?

Out Of Memory,一般是由于程序编写者对内存使用不当,如对该释放的内存资源没有释放,导致其一直不能被再次使用而使计算机内存被耗尽的现象。重启计算机即可,但根本解决办法还是对代码进行优化。(摘自百度百科)

 

那么解决OOM的方法有哪些呢?

1、动态回收内存。

2、为应用分配更多的内存。

3、自定义内存大小。

4、如果是因为图片引起的OOM,其实就可以从图片下手。(使图片体积大小变小)

5、加载图片时在内存中做处理。

6、使用图片缓存技术。

 

 

1、动态回收内存算是最简单的解决方法吧,就是手动的调用System.gc();

例如:bit为Bitmap对象

if (bit != null && !bit.isRecycled()) {         bit.recycle();         bit = null;}
System.gc();

bitmap.recycle()方法用于回收该bitmap所占用的内存,用System.gc()调用一下系统的垃圾回收器。

需要注意的是:回收内存要及时,比如说SurfaceView,就应该在onSurfaceDestroyed这个方法中回收。如果Activity使用了bitmap,就可以在onStop或者onDestroy方法中回收等等。

 

2、为应用分配更多的内存。

在清单文件中的< application >节点下,添加如下代码:android:largeHeap="true"。

android:largeHeap应用程序的进程是否会用较大的 Dalvik 堆来创建。 这将作用于所有为该应用程序创建的进程,但只对第一个被装入进程的应用程序生效。 如果通过共享用户 ID 的方式让多个应用程序公用一个进程,那么这些应用程序必须全部指定本选项,否则将会导致不可预知的后果。

大部分应用程序不需要用到本属性,而是应该关注如何减少内存消耗以提高性能。 使用本属性并不能确保一定会增加可用的内存,因为某些设备可用的内存本来就很有限。

要在运行时查询可用的内存大小,请使用 getMemoryClass() 或getLargeMemoryClass() 方法。

 

除上述方法外,还有一个方法。

使用 dalvik.system.VMRuntime类提供的setTargetHeapUtilization方法可以增强程序堆内存的处理效率。

具体的使用如下:

private final static floatTARGET_HEAP_UTILIZATION = 0.75f;  //在程序onCreate时就可以调用 VMRuntime.getRuntime().setTargetHeapUtilization(TARGET_HEAP_UTILIZATION); //即可 

 

3、一直感觉自定义的这种方法实在是太暴力了。

强制定义自己软件的对内存大小,我们使用Dalvik提供的 dalvik.system.VMRuntime类来设置最小堆内存为例:

private final static int CWJ_HEAP_SIZE = 6* 1024* 1024 ;VMRuntime.getRuntime().setMinimumHeapSize(CWJ_HEAP_SIZE); //设置最小heap内存为6MB大小。当然对于内存吃紧来说还可以通过手动干涉GC去处理

 

4、这也分为两个方面:

1、分辨率不变,图片大小减小。  2、分辨率改变,图片减小。(用PS都很容易的)

需要注意的是:不要减小得太小而影响了人眼看上去的美感。

 

5、这里给出一个简单的操作和一个封装后的操作,可以对比看看。

简单的操作:

//压缩,用于节省BITMAP内存空间--解决BUG的关键步骤 BitmapFactory.Options opts = new BitmapFactory.Options();opts.inSampleSize = 2;//这个的值压缩的倍数(2的整数倍),数值越小,压缩率越小,图片越清晰 //返回原图解码之后的bitmap对象Bitmap  bitmap = BitmapFactory.decodeResource(getResources(),                    R.drawable.begin_background, opts);

这里的bitmap就是压缩后得到的图片。

封装后的操作:

private Bitmap imgUtis(Resources res, int img, int reqWidth, int reqHeight) {        BitmapFactory.Options options = new BitmapFactory.Options();        options.inJustDecodeBounds = true;// 让解析方法禁止为bitmap分配内存,返回值也不再是一个Bitmap对象,而是null。        BitmapFactory.decodeResource(getResources(), img, options);        // 在加载图片之前就获取到图片的长宽值和MIME类型,并返回压缩的尺寸        options.inSampleSize = calculateInSampleSize(options, reqWidth,                reqHeight);        // 使用获取到的inSampleSize值再次解析图片        options.inJustDecodeBounds = false;        return BitmapFactory.decodeResource(getResources(), img, options);    }    /**     *      * @param options 操作对象     * @param reqWidth 目标宽     * @param reqHeight 目标高     * @return     */    public static int calculateInSampleSize(BitmapFactory.Options options,            int reqWidth, int reqHeight) {        // 源图片的高度和宽度        final int height = options.outHeight;        final int width = options.outWidth;        int inSampleSize = 1;        if (height > reqHeight || width > reqWidth) {            // 计算出实际宽高和目标宽高的比率            final int heightRatio = Math.round((float) height                    / (float) reqHeight);            final int widthRatio = Math.round((float) width / (float) reqWidth);            // 选择宽和高中最小的比率作为inSampleSize的值,这样可以保证最终图片的宽和高            // 一定都会大于等于目标的宽和高。            inSampleSize = heightRatio < widthRatio ? heightRatio : widthRatio;        }        return inSampleSize;    }

这里得到的bitmap是按照规定的尺度比例来进行压缩的。

 

 

 

6.(此段内容摘自郭神的文章)

内存缓存技术对那些大量占用应用程序宝贵内存的图片提供了快速访问的方法。其中最核心的类是LruCache (此类在android-support-v4的包中提供) 。这个类非常适合用来缓存图片,它的主要算法原理是把最近使用的对象用强引用存储在 LinkedHashMap 中,并且把最近最少使用的对象在缓存值达到预设定值之前从内存中移除。

在过去,我们经常会使用一种非常流行的内存缓存技术的实现,即软引用或弱引用 (SoftReference or WeakReference)。但是现在已经不再推荐使用这种方式了,因为从 Android 2.3 (API Level 9)开始,垃圾回收器会更倾向于回收持有软引用或弱引用的对象,这让软引用和弱引用变得不再可靠。另外,Android 3.0 (API Level 11)中,图片的数据会存储在本地的内存当中,因而无法用一种可预见的方式将其释放,这就有潜在的风险造成应用程序的内存溢出并崩溃。

为了能够选择一个合适的缓存大小给LruCache, 有以下多个因素应该放入考虑范围内,例如:

  • 你的设备可以为每个应用程序分配多大的内存?
  • 设备屏幕上一次最多能显示多少张图片?有多少图片需要进行预加载,因为有可能很快也会显示在屏幕上?
  • 你的设备的屏幕大小和分辨率分别是多少?一个超高分辨率的设备(例如 Galaxy Nexus) 比起一个较低分辨率的设备(例如 Nexus S),在持有相同数量图片的时候,需要更大的缓存空间。
  • 图片的尺寸和大小,还有每张图片会占据多少内存空间。
  • 图片被访问的频率有多高?会不会有一些图片的访问频率比其它图片要高?如果有的话,你也许应该让一些图片常驻在内存当中,或者使用多个LruCache 对象来区分不同组的图片。
  • 你能维持好数量和质量之间的平衡吗?有些时候,存储多个低像素的图片,而在后台去开线程加载高像素的图片会更加的有效。

并没有一个指定的缓存大小可以满足所有的应用程序,这是由你决定的。你应该去分析程序内存的使用情况,然后制定出一个合适的解决方案。一个太小的缓存空间,有可能造成图片频繁地被释放和重新加载,这并没有好处。而一个太大的缓存空间,则有可能还是会引起 java.lang.OutOfMemory 的异常。

下面是一个使用 LruCache 来缓存图片的例子:

private LruCache<String, Bitmap> mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {    // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。    // LruCache通过构造函数传入缓存值,以KB为单位。    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);    // 使用最大可用内存值的1/8作为缓存的大小。    int cacheSize = maxMemory / 8;    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {        @Override        protected int sizeOf(String key, Bitmap bitmap) {            // 重写此方法来衡量每张图片的大小,默认返回图片数量。            return bitmap.getByteCount() / 1024;        }    };}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {    if (getBitmapFromMemCache(key) == null) {        mMemoryCache.put(key, bitmap);    }}public Bitmap getBitmapFromMemCache(String key) {    return mMemoryCache.get(key);}

在这个例子当中,使用了系统分配给应用程序的八分之一内存来作为缓存大小。在中高配置的手机当中,这大概会有4兆(32/8)的缓存空间。一个全屏幕的 GridView 使用4张 800x480分辨率的图片来填充,则大概会占用1.5兆的空间(800*480*4)。因此,这个缓存大小可以存储2.5页的图片。
当向 ImageView 中加载一张图片时,首先会在 LruCache 的缓存中进行检查。如果找到了相应的键值,则会立刻更新ImageView ,否则开启一个后台线程来加载这张图片。

public void loadBitmap(int resId, ImageView imageView) {    final String imageKey = String.valueOf(resId);    final Bitmap bitmap = getBitmapFromMemCache(imageKey);    if (bitmap != null) {        imageView.setImageBitmap(bitmap);    } else {        imageView.setImageResource(R.drawable.image_placeholder);        BitmapWorkerTask task = new BitmapWorkerTask(imageView);        task.execute(resId);    }}

BitmapWorkerTask 还要把新加载的图片的键值对放到缓存中。

class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {    // 在后台加载图片。    @Override    protected Bitmap doInBackground(Integer... params) {        final Bitmap bitmap = decodeSampledBitmapFromResource(                getResources(), params[0], 100, 100);        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);        return bitmap;    }}

大体过程就如上面的例子所示。下面来一个简单的完整的例子:

public class MainActivity extends Activity {    private LruCache<String, Bitmap> mMemoryCache;    private ImageView iv;    public String str = "http://wenwen.sogou.com/p/20100623/20100623101110-601052657.jpg";    @Override    protected void onCreate(Bundle savedInstanceState) {        // TODO Auto-generated method stub        super.onCreate(savedInstanceState);        setContentView(R.layout.main);        iv = (ImageView) findViewById(R.id.iv);        // 获取到可用内存的最大值,使用内存超出这个值会引起OutOfMemory异常。        // LruCache通过构造函数传入缓存值,以KB为单位。        int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);        // 使用最大可用内存值的1/8作为缓存的大小。        int cacheSize = maxMemory / 8;        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {            @Override            protected int sizeOf(String key, Bitmap bitmap) {                // 重写此方法来衡量每张图片的大小,默认返回图片数量。                return bitmap.getByteCount() / 1024;            }        };        loadBitmap(str, iv);    }    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {        if (getBitmapFromMemCache(key) == null) {            mMemoryCache.put(key, bitmap);        }    }    public Bitmap getBitmapFromMemCache(String key) {        return mMemoryCache.get(key);    }    public void loadBitmap(String url, ImageView imageView) {        String imageKey = url;        Bitmap bitmap = getBitmapFromMemCache(imageKey);        if (bitmap != null) {            imageView.setImageBitmap(bitmap);        } else {            imageView.setImageResource(R.drawable.empty_photo);            BitmapWorkerTask1 task = new BitmapWorkerTask1();            task.execute(url);        }    }    /**     * 异步下载图片的任务。     *      * @author guolin     */    class BitmapWorkerTask1 extends AsyncTask<String, Void, Bitmap> {        /**         * 图片的URL地址         */        private String imageUrl;        @Override        protected Bitmap doInBackground(String... params) {            imageUrl = params[0];            // 在后台开始下载图片            Bitmap bitmap = downloadBitmap(imageUrl);            if (bitmap != null) {                // 图片下载完成后缓存到LrcCache中                addBitmapToMemoryCache(imageUrl, bitmap);            }            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            iv.setImageBitmap(bitmap);        }        /**         * 建立HTTP请求,并获取Bitmap对象。         *          * @param imageUrl         *            图片的URL地址         * @return 解析后的Bitmap对象         */        private Bitmap downloadBitmap(String imageUrl) {            Bitmap bitmap = null;            HttpURLConnection con = null;            try {                URL url = new URL(imageUrl);                con = (HttpURLConnection) url.openConnection();                con.setConnectTimeout(5 * 1000);                con.setReadTimeout(10 * 1000);                if (con.getResponseCode() == 200) {                    bitmap = BitmapFactory.decodeStream(con.getInputStream());                } else {                    System.out.println("输入的路径不存在");                }            } catch (Exception e) {                e.printStackTrace();            } finally {                if (con != null) {                    con.disconnect();                }            }            if (bitmap != null) {                return bitmap;            }            return null;        }    }}
View Code

 

运行结果如下(来张吾女王的图片,嘿嘿):

 

PS:如果读者对第6段中提到的 强引用、软引用、弱引用、虚引用 不了解,可以查看相关博文:

http://blog.sina.com.cn/s/blog_6f6a95180100rrox.html

http://supben.iteye.com/blog/1167736

Android OOM 解决方案