Android大图加载内存优化(如何防止OutOfMemory)

一、简介

移动设备不断发展的今天,有的人认为内存已经足够大了,不用再管什么内存优化,Java是虚拟机可以帮我维护内存。其实内存空间资源还是很宝贵的,不管手机内存有多大,系统分配给单个应用的内存空间还是很有限的大致有16M,64M,128M等。在Android中加载大图会非常消耗系统资源,16M的图片大致可以存储3张1024X1536质量为ARGB_8888的图片,这里边还不包含其它Object所占的资源。软件在系统上运行,环境是很复杂的,可能测试的时候有限的测试次数上没有发现内存泄漏问题,但是在成千上万的用户使用过程中,总会有各种内存泄露问题。

二、图片所占内存空间的计算规则

大图加载首先要清楚图片的质量与所占内存的计算规则:Bitmap.Config


Bitmap.Config


介绍(每个像素点的构成)


1pix所占空间

1byte = 8位


1024*1024图片大小

(分辨率)


ALPHA_8


只有透明度,没有颜色,那么一个像素点占8位。


1byte


1M


RGB_565


即R=5,G=6,B=5,没有透明度,那么一个像素点占5+6+5=16位


2byte


2M


ARGB_8888


由4个8位组成,即A=8,R=8,G=8,B=8,那么一个像素点占8+8+8+8=32位


4byte


4M


ARGB_4444


由4个4位组成,即A=4,R=4,G=4,B=4,那么一个像素点占4+4+4+4=16位


2byte


2M

三、加载和显示的常见策略

1.直接加载并显示:

直接把图片解码并显示加载到控件上,常用的setImageResource方法就是直接加载图片到ImageView上边展示,这种方法是加载原图的方式;

2.加载到内存做缩放:

解码到内存或者Bitmap,判断Bitmap的大小判断是否需要二次处理,缩放到指定的大小再显示;

3.降低质量加载:

解码的时候设置Bitmap.inPreferredConfig ,尝试加载不同质量的位图到内存;

4.预加载的方式:

就是先获取将要加载的图片的大小,预计算内存的占用和是否我们需要这样分辨率的图片。

四、大图的加载优化

采用BitmapFactory加载图片,它提供了decodeResource 和decodeFile两个方法给我们解码Resource目录和本地SDCard目录下的图片,提供了强大的BitmapFactory.Options 给我在解码图片的过程中的配置。

1.使用decodeResource方法加载Drawable下边的图片

图片分辨率为:8176 x 2368 颜色模型是:ARGB_8888, 完全加载到内存所需资源是:73.6M左右,意味着巨大多数的Android手机都会瞬间内存泄漏。

加载图片时先获取图片的大小和图片的格式:将inJustDecodeBounds设置为true,不解码图片到内存,只读取图片的基本信息。

BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeResource(getResources(), R.id.myimage, options);
int imageHeight = options.outHeight;
int imageWidth = options.outWidth;
String imageType = options.outMimeType;

这时候可以获取到图片的大小和格式,然后根据需要加载的模型计算所占内存空间的大小:

/**
 * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
 */
public static int getBytesPerPixel(Bitmap.Config config) {
    if (config == Bitmap.Config.ARGB_8888) {
        return 4;
    } else if (config == Bitmap.Config.RGB_565) {
        return 2;
    } else if (config == Bitmap.Config.ARGB_4444) {
        return 2;
    } else if (config == Bitmap.Config.ALPHA_8) {
        return 1;
    }
    return 1;
}

根据图片的宽和高获取图片大小:

/**
 * get the image size in the RAM
 *
 * @param imageW
 * @param imageH
 * @return
 */
public static long getBitmapSizeInMemory(int imageW, int imageH) {
    return imageH * imageW * getBytesPerPixel(Bitmap.Config.ARGB_8888);
}

这时候可以增加一些策略,比如只加载控件大小的图片,或者判断一下当前的内存使用情况,在视情况加载图片:这时候要把inJustDecodeBounds设置为false,这里我们演示一下按照指定分辨率加载图片的方式:

/**
 * Load bitmap from resources
 *
 * @param res        resource
 * @param drawableId resource image id
 * @param imgH       destination image height
 * @param imgW       destination image width
 * @return
 */
public static Bitmap loadHugeBitmapFromDrawable(Resources resources, int drawableId, int imgH, int imgW) {
    Log.d(TAG, "imgH:" + imgH + " imgW:" + imgW);

    BitmapFactory.Options options = new BitmapFactory.Options();

    //preload set inJustDecodeBounds true, this will load bitmap into memory
    options.inJustDecodeBounds = true;
    //options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
    BitmapFactory.decodeResource(resources, drawableId, options);

    //get the image information include: height and width
    int height = options.outHeight;
    int width = options.outWidth;
    String mimeType = options.outMimeType;

    Log.d(TAG, "width:" + width + " height:" + height + " mimeType:" + mimeType);

    //get sample size
    int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
    options.inSampleSize = sampleSize;
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    Log.d(TAG, "memory size:" + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
    Bitmap bitmap = BitmapFactory.decodeResource(resources, drawableId, options);
    Log.d(TAG, "w=" + bitmap.getWidth() + " h=" + bitmap.getHeight() + " bitmap size:" + bitmap.getRowBytes() * bitmap.getHeight());
    return bitmap;
}

2.使用decodeFile方法加载SDCard下边的图片

图片信息同上。

/**
 * load the bitmap from SDCard with the imgW and imgH
 *
 * @param imgPath  resource path
 * @param imgH     result image height
 * @param imgW     result image width
 * @return result bitmap
 */
public static Bitmap loadHugeBitmapFromSDCard(String imgPath, int imgH, int imgW) {
    Log.d(TAG, "imgH:" + imgH + " imgW:" + imgW);

    BitmapFactory.Options options = new BitmapFactory.Options();

    //preload set inJustDecodeBounds true, this will load bitmap into memory
    options.inJustDecodeBounds = true;
    //options.inPreferredConfig = Bitmap.Config.ARGB_8888;//default is Bitmap.Config.ARGB_8888
    BitmapFactory.decodeFile(imgPath, options);

    //get the image information include: height and width
    int height = options.outHeight;
    int width = options.outWidth;
    String mimeType = options.outMimeType;

    Log.d(TAG, "width:" + width + " height:" + height + " mimeType:" + mimeType);

    //get sample size
    int sampleSize = getScaleInSampleSize(width, height, imgW, imgH);
    options.inSampleSize = sampleSize;
    // Decode bitmap with inSampleSize set
    options.inJustDecodeBounds = false;

    Log.d(TAG, "memory size:" + getBitmapSizeInMemory(width / sampleSize, height / sampleSize));
    Bitmap bitmap = BitmapFactory.decodeFile(imgPath, options);
    Log.d(TAG, "w=" + bitmap.getWidth() + " h=" + bitmap.getHeight() + " bitmap size:" + bitmap.getRowBytes() * bitmap.getHeight());

    return bitmap;
}

两种方式的原理完全一样。

3.其中inSampleSize的计算方式和注意点

根据官方文档我们可以了解到,inSampleSize必须是2的指数倍,如果不是2的指数倍会自动转位教下的2的指数倍的数,下边我提供一个计算方法:

/**
     * get the scale sample size
     *
     * @param resW resource width
     * @param resH resource height
     * @param desW result width
     * @param desH result height
     * @return
     */
    public static int getScaleInSampleSize(int resW, int resH, int desW, int desH) {
        int scaleW = resW / desW;
        int scaleH = resH / desH;
        int largeScale = scaleH > scaleW ? scaleH : scaleW;

        int sampleSize = 1;
        while (sampleSize < largeScale) {
            sampleSize *= 2;
        }

        Log.d(TAG, "sampleSize:" + sampleSize);

        return sampleSize;
    }

接下来,我讲继续讲解:图片加载之图片缓存和异步加载技术。

最后我附上该项目的github地址:https://github.com/CJstar/HugeLocalImageLoad.git

参考:http://developer.android.com/training/displaying-bitmaps/load-bitmap.html

版权声明:本文为博主原创文章,未经博主允许不得转载。

时间: 2024-08-06 16:31:01

Android大图加载内存优化(如何防止OutOfMemory)的相关文章

android图片加载内存优化方法,有效解决大图片内存溢出(oom)

低内存的手机如果直接加载大图片,往往会出现OOM的情况.即便是主流手机,也不能无限制的加载大图片.所以在显示图片之前,需要对图片处理,把图片缩放为最合适的尺寸再显示. 网上很大方法都是不管三七二十一,直接压缩图片.这样可能会导致图片失真,显示模糊.我采用的方式是,显示尺寸有多大,就等比例压缩成多大尺寸的图片,关键关于在于如何寻找最合适的尺寸,下面分享两个关键方法,提取至google开源框架volley private static int getResizedDimension(int maxP

Android之——性能与内存优化

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46987951 写出高效代码的两条基本的原则:(1)不要做不必要的事:(2)不要分配不必要的内存. 1. 内存优化      Android系统对每个软件所能使用的RAM空间进行了限制(如:Nexus one 对每个软件的内存限制是24M),同时Java语言本身比较消耗内存,dalvik虚拟机也要占用一定的内存空间,所以合理使用内存,彰显出一个程序员的素质和技能. 1) 了解JIT

android 开发如何做内存优化

网上看的一篇很好的文章;http://www.gforetell.com/?/question/id-111__uid-focus 不少人认为JAVA程序,因为有垃圾回收机制,应该没有内存泄露.其实如果我们一个程序中,已经不再使用某个对象,但是因为仍然有引用指向它,垃圾回收器就无法回收它,当然该对象占用的内存就无法被使用,这就造成了内存泄露.如果我们的java运行很久,而这种内存泄露不断的发生,最后就没内存可用了.当然java的,内存泄漏和C/C++是不一样的.如果java程序完全结束后,它所有

Android之——图片的内存优化

转载请注明出处:http://blog.csdn.net/l1028386804/article/details/46972817 1. 对图片本身进行操作 尽量不要使用 setImageBitmap.setImageResource. BitmapFactory.decodeResource 来设置一张大图,因为这些方法在完成 decode 后,最终都是通过 Java 层的 createBitmap 来完成的,需要消耗更多内存.因此,改用先通过 BitmapFactory.decodeStre

Android 异步加载图像优化,如:引入线程池、引入缓存

关于Android 从网络上异步加载图像: 个人总结,重在分享! 异步加载图像,由于Adnroid Ui 更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程 首先要想到以下方法. 1.在主线程中 new 一个Handler对象,加载图像(优化) 示1:private void  loadImage(final String url, final int id){ handler.post(new Runnable(){ public void run(){ Drawable

android. 长图加载

import android.content.Context;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.graphics.BitmapRegionDecoder;import android.graphics.Canvas;import android.graphics.Matrix;import android.graphics.Rect;import android.

吸顶条+大图加载滚动

<!doctype html><html><head><meta charset="utf-8"><title>无标题文档</title><style>*{margin:0;padding:0;}.box{ position:relative;padding:50px; border-bottom:1px solid red;}.box img{ display:block;width:1200px;h

android加载速度优化,通过项目的优化过程分析

通过这么长时间的盒子开发以及之前手机项目的经验,总体感觉两种不同设备还是有很多不同的地方的,首先一点不同的就是,手机项目和电视项目默认启动页面加载速度有重要区别 对于手机:手机加载网络数据,由于屏幕小,如果主页有网络图片的情况下,基本都是显示默认图片,这也是由于网速的限制,更重要的是手机上基本是图文混排,用户没看到图片可能焦点就在文本上了. 对于电视:如果应用首页加载使用默认图,会感觉特别丑,因为屏幕大,重要信息都是图片,如果没有图片,那用户看到的都是空白,用户的焦点没有了,只有等待和抱怨. 因

APP性能优化系列:内存优化-bitmap详解

??在Android应用开发中,我们经常需要跟图片打交道,而图片一个很麻烦的问题是占用内存非常大,经常导致OOM,了解Bitmap相关信息,不同sdk版本中Android图片处理的变化,以及一些优化处理的方式对我们平时开发中对图片的会非常有帮助. ??在开始本节的内容之前我们.先来区分几个名词的概念: Drawable:通用的图形对象,用于装载常用格式的图像,既可以是PNG,JPG这样的图像, 也是前面学的那13种Drawable类型的可视化对象!我们可以理解成一个用来放画的--画框! Bitm