BitmapFactory 加载图片到内存

Bitmap所占用内存

Android的虚拟机是基于寄存器的Dalvik,它的最大堆(单个进程可用内存)大小一般是16M,当然不同设备是不一样的,可以查看/system/build.prop文件,[注:现在的机子可能一般都有160M左右]。程序本身运行就占有一定的内存,在使用较大的bitmap时,由于Bitmap自身的特性(将每个像素的属性全部保存在内存中),导致稍有不慎就会创建出一个占用内存非常大的Bitmap对象,从而导致加载过慢,还会有内存溢出的风险。所以,加载Bitmap时对其进行优化是必不可少的一步。
例如:
mImageView.setImageResource(R.drawable.my_image);
这是一行从资源文件中加载图片到ImageView的代码。实际上,以上这行代码会在运行时使用BitmapFactory.decodeStream()方法将资源图片生成一个Bitmap,然后由这个Bitmap生成一个Drawable,最后再将这个Drawable设置到ImageView。由于在过程中生成了Bitmap,因此如果你使用的图片过大,就会导致性能和内存占用的问题。

Bitmap占用的内存为:像素总数 * 每个像素占用的内存。在Android中,Bitmap有四种像素类型:ARGB_8888、ARGB_4444、ARGB_565、ALPHA_8,他们每个像素占用的字节数分别为4、2、2、1。因此,一个2000*1000的ARGB_8888类型的Bitmap占用的内存为2000*1000*4=8000000B=8MB。
注意:Bitmap占用内存大小和硬盘中的.png图片的大小没有一毛钱关系!

Android根据设备屏幕尺寸和dpi的不同,给系统分配的单应用程序内存大小也不同,具体如下表(表格取自Android 4.4 Compatibility Definition Document (CDD)): 屏幕尺寸                  DPI                  应用内存
small / normal / large	 ldpi / mdpi	 16MB
small / normal / large	 tvdpi / hdpi	 32MB
small / normal / large	 xhdpi	         64MB
small / normal / large	 400dpi	         96MB
small / normal / large	 xxhdpi	         128MB
xlarge	                         mdpi	         32MB
xlarge	                         tvdpi / hdpi	 64MB
xlarge	                         xhdpi	         128MB
xlarge	                         400dpi	         192MB
xlarge	                         xxhdpi	         256MB

实际测试
在720P的华为荣耀2和1080P的华为荣耀7上都能正常加载 5016*7891*4/1024/1024 = 150M 的图片
Log.i("bqt", bitmap.getWidth() * bitmap.getHeight() * 4 / 1024 / 1024 + "-" + bitmap.getByteCount() / 1024 / 1024);//150-150
而对于 5612*8414*4/1024/1024 = 180M 的图片都加载失败了,但应用并没有挂掉,所以估计单个应用的内存被调到了160M。
注意区分占用内存(宽*高*每个像素占用的内存)和图片像素(宽*高)的区别
对于ARGB_8888来说,内存=像素*4
所以1000万像素占用内存近似为:10M*4=40M

BitmapFactory所有方法


public static Bitmap decodeByteArray(byte[] data, int offset, int length)

public static Bitmap decodeByteArray(byte[] data, int offset, int length, Options opts)

public static Bitmap decodeFile(String pathName)

public static Bitmap decodeFile(String pathName, Options opts)

public static Bitmap decodeFileDescriptor(FileDescriptor fd)

public static Bitmap decodeFileDescriptor(FileDescriptor fd, Rect outPadding, Options opts)

public static Bitmap decodeResource(Resources res, int id)

public static Bitmap decodeResource(Resources res, int id, Options opts)

public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts)

public static Bitmap decodeStream(InputStream is)

public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts)

以上所有重载方法,参数较少的方法都是直接调用参数较多的方法,调用时传入的其他参数为null。 以上方法返回的Bitamp都是不可修改的。

BitmapFactory.Options


boolean inJustDecodeBounds——如果设置为true,不获取图片,不分配内存,但会返回图片的宽高信息

int outWidth——获取图片的宽度值
int outHeight——获取图片的高度值

int inSampleSize——图片缩放的倍数。如果设为4,则宽和高都为原来的1/4,则图是原来的1/16
int inDensity——用于位图的像素压缩比
int inTargetDensity——用于目标位图的像素压缩比(要生成的位图)
boolean inScaled——设置为true时进行图片压缩,从inDensity到inTargetDensity

boolean inPurgeable——设置图片是否可以被回收,创建Bitmap时用于存储像素的内存空间在系统内存不足时可以被回收

boolean inInputShareable ——设置是否解码位图的尺寸信息

boolean inDither——设置是否进行图片抖动处理

Bitmap.Config inPreferredConfig——设置颜色格式,设为null可让解码器以最佳方式解码

关于inSampleSize
该参数为int型,他的值指示了在解析图片为Bitmap时在长宽两个方向上像素缩小的倍数。inSampleSize的默认值和最小值为1,当小于1时,解码器将该值当做1来处理,且在大于1时,该值只能为2的幂(当不为2的幂时,解码器会取与该值最接近的2的幂)。例如,当inSampleSize为2时,一个2000*1000的图片,将被缩小为1000*500,相应地,它的像素数和内存占用都被缩小为了原来的1/4:

加载大图详细demo


public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        ImageView imageView = new ImageView(this);

        //通过设置不同的显示模式,可以观察压缩后的Bitmap到底多大

        imageView.setScaleType(ScaleType.CENTER);//按图片原来大小居中显示,若图片长/宽 > View的长/宽,则截取图片的居中部分显示

        //imageView.setScaleType(ScaleType.CENTER_INSIDE);//将图片完整居中显示,通过按比例缩小使得图片长/宽 <= View的长/宽

        //1.得到加载Bitmap的View(屏幕)的宽高,API13 android3.2 之后才能用         Point point = new Point();         getWindowManager().getDefaultDisplay().getSize(point);         int screenWidth = point.x;         int screenHeight = point.y;         Log.i("bqt", "屏幕宽高:" + screenWidth + "-" + screenHeight);//屏幕宽高:1080-1794
        //2.得到原始图片的宽高         BitmapFactory.Options options = new Options();//加载和显示图片是很消耗内存的,Options 类允许我们定义图片以何种方式读到内存         options.inJustDecodeBounds = true;//不去解析真实的位图,只是获取这个位图的边界等信息。这是关键!         Bitmap bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory().getPath() + "/big_image.jpg", options);         int bitmapWidth = options.outWidth;         int bitmapHeight = options.outHeight;         Log.i("bqt", "图片宽高:" + bitmapWidth + "-" + bitmapHeight + "-" + (null == bitmap));//3600-5400-true。返回的是null
        //3.计算缩放比例。这里为了后面的演示将scale设为了float,实际中全部使用int计算即可,因为后面用到的也是int         float scaleX = 1.0f * bitmapWidth / screenWidth;         float scaleY = 1.0f * bitmapHeight / screenHeight;         float scale = Math.max(scaleX, scaleY);//以最大限度显示图片         Log.i("bqt", "缩放前:" + scaleX + "-" + scaleY + "-" + scale);//3.3333333-3.0100334-3.3333333
        //4.缩放加载图片到内存         //4.1.要想节约内存,必须使用 inSampleSize 这个成员变量         //options.inSampleSize = (int) scale;//此值最终会被解析器解析成2的n次幂(1、2、4、8),表示图片宽高缩小到原来的几分之一         options.inSampleSize =calculateInSampleSize(options, screenWidth, screenHeight);         //4.2.使用下面两个成员变量并不会改变加载进内存的Bitmap的大小,所以根本用不着         options.outWidth = (int) (bitmapWidth / scale);         options.outHeight = (int) (bitmapHeight / scale);         Log.i("bqt", "options中设置的宽高:" + options.outWidth + "-" + options.outHeight + "-" + options.inSampleSize);//1080-1620-3         //4.3.下面这些参数都不是必须的         options.inPurgeable = true;//建议加上。设置图片可以被回收,创建Bitmap时用于存储像素的内存空间在系统内存不足时可以被回收         options.inInputShareable = true;//建议加上。设置解码位图的尺寸信息         options.inDither = false; //不进行图片抖动处理         options.inPreferredConfig = null; //让解码器以最佳方式解码         //4.4.真正的去解析这个位图。         options.inJustDecodeBounds = false;         bitmap = BitmapFactory.decodeFile(Environment.getExternalStorageDirectory().getPath() + "/big_image.jpg", options);         Log.i("bqt", "实际bitmap的宽高:" + bitmap.getWidth() + "-" + bitmap.getHeight() + "-" + options.inSampleSize);//1800-2700-3         //注意,实际bitmap的宽高既不是我们设置的options.outWidth=1080,也不是bitmapWidth/(int) scale=1200,而是bitmapWidth/2=1800         imageView.setImageBitmap(bitmap);         setContentView(imageView);     }
    /**在保证解析出的bitmap宽高分别大于目标尺寸宽高的前提下,取可能的inSampleSize的最大值*/     public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {         // 原始图片的宽高         int height = options.outHeight;         int width = options.outWidth;         int inSampleSize = 1;         if (height > reqHeight || width > reqWidth) {             int halfHeight = height / 2;             int halfWidth = width / 2;             while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {                 inSampleSize *= 2;             }         }         return inSampleSize;     } }

简洁版demo


public class MainActivity extends Activity {

    @Override

    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);

        ImageView imageView = new ImageView(this);

        Bitmap bitmap = decodeSampledBitmapFromFile(Environment.getExternalStorageDirectory().getPath() + "/a.jpg", 1080, 1920);

        imageView.setImageBitmap(bitmap);

        Log.i("bqt", "加载进内存的bitmap的宽高:" + bitmap.getWidth() + "-" + bitmap.getHeight());//1800-2700

        setContentView(imageView);

    }

    public static Bitmap decodeSampledBitmapFromFile(String pathName, int reqWidth, int reqHeight) {

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

        options.inJustDecodeBounds = true;

        BitmapFactory.decodeFile(pathName, options);

        options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);

        options.inJustDecodeBounds = false;

        return BitmapFactory.decodeFile(pathName, options);

    }

    public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight) {

        int width = options.outWidth;

        int height = options.outHeight;

        Log.i("bqt", "原始图片的宽高:" + width + "-" + height);//3600-5400

        int inSampleSize = 1;

        if (height > reqHeight || width > reqWidth) {

            int halfHeight = height / 2;

            int halfWidth = width / 2;

            while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) {

                inSampleSize *= 2;

            }

        }

        return inSampleSize;

    }

}

来自为知笔记(Wiz)

时间: 2024-08-05 02:33:14

BitmapFactory 加载图片到内存的相关文章

Android加载图片导致内存溢出(Out of Memory异常)

Android在加载大背景图或者大量图片时,经常导致内存溢出(Out of Memory  Error),本文根据我处理这些问题的经历及其它开发者的经验,整理解决方案如下(部分代码及文字出处无法考证):  方案一.读取图片时注意方法的调用,适当压缩  尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗

图片--Android加载图片导致内存溢出(Out of Memory异常)

Android在加载大背景图或者大量图片时,经常导致内存溢出(Out of Memory  Error),本文根据我处理这些问题的经历及其它开发者的经验,整理解决方案如下(部分代码及文字出处无法考证):  方案一.读取图片时注意方法的调用,适当压缩  尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗

WPF循环加载图片导致内存溢出的解决办法

程序场景:一系列的图片,从第一张到最后一张依次加载图片,形成"动画". 生成BitmapImage的方法有多种: 1. var source=new BitmapImage(new Uri("图片路径",UriKind.xxx)); 一般的场景使用这种方法还是比较方便快捷,但是对于本场景,内存恐怕得爆. 2. var data =File.ReadAllBytes("图片路径"); var ms = new System.IO.MemoryStr

解决Android加载图片时内存溢出的问题

尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存. 因此,改用先通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的source,decodeStream最大的秘密在于其直接调用JNI>>nativeDecodeAsset()

使用Picasso加载图片的内存优化实践

0 说明 现在Android开源库中有许多图片加载框架,本文以picasso为例,总结下开发过程中的一些优化经验,使用的picasso版本如下 compile 'com.squareup.picasso:picasso:2.5.2' 1 图片裁剪 在列表页尽量使用裁剪后的图片,在查看大图模式下才加载完整的图片. 图片裁剪示例 Picasso.with( imageView.getContext() ) .load(url) .resize(dp2px(250),dp2px(250)) .cent

加载图片到内存

1.图片解析配置 2.只解析图片的尺寸 3.获取图片的宽高信息 4.获取手机屏幕的宽高 5.计算缩放比例(图片的大小/窗口的大小)按大的进行缩放 6.解析图片全部信息 scale 缩放比例

BitmapFactory.Options解决Android加载图片内存溢出的问题

BitmapFactory.Options解决Android加载图片内存溢出的问题 1. 在Android软件开发过程中,图片处理是经常遇到的. 在将图片转换成Bitmap的时候,由于图片的大小不一样,当遇到很大的图片的时候会出现超出内存的问题,为了解决这个问题Android API提供了BitmapFactory.Options这个类. 2. 由于Android对图片使用内存有限制,若是加载几兆的大图片便内存溢出.Bitmap会将图片的所有像素(即长x宽)加载到内存中,如果图片分辨率过大,会直

解决imageView加载图片时候造成内存溢出的问题

图片加载  出现内存溢出的问题很普遍  而且解决的方法五花八门 各有优劣 主要是因为  imageView  在底层创建图片层的时候 会占用很大的内存空间 尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource直接使用图片路径来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存.改用先通过BitmapFactory.decodeStream方法,创

android 网络加载图片,对图片资源进行优化,并且实现内存双缓存 + 磁盘缓存

经常会用到 网络文件 比如查看大图片数据 资源优化的问题,当然用开源的项目  Android-Universal-Image-Loader  或者 ignition 都是个很好的选择. 在这里把原来 写过的优化的代码直接拿出来,经过测试千张图片效果还是不错的. 免费培训课:http://www.jinhusns.com/Products/Curriculum/?type=xcj 工程目录 至于 Activity 就是加载了 1个网格布局 01./** 02.*   实现 异步加载 和   2级缓