您的位置:首页 > 移动开发 > Android开发

Android实践:高效加载Bitmap

2016-11-21 17:07 447 查看
转自 http://blog.csdn.net/p106786860/article/details/53260463
一、BitmapFactory.Options简介

在Android开发中,加载图片过多、过大很容易引起OutOfMemoryError异常,即我们常见的内存溢出。因为Android对单个应用施加内存限制,默认分配的内存只有几M(具体视不同系统而定)。而载入的图片如果是JPG之类的压缩格式(JPG支持最高级别的压缩,不过该压缩是有损的),在内存中展开会占用大量的内存空间,也就容易形成内存溢出;

那么高效的加载Bitmap是很重要的事情。Bitmap在Android中指的是一张图片,可以是png格式也可以是jpg等常见的格式。BitmapFactory提供了如下四类方法,可分别用于从文件系统、资源、输入流以及字节数组中加载出一个Bitmap对象

1.decodeFile;

2.decodeResource;

3.decodeStream;

4.decodeByteArray;
如果高效的加载类图呢,其实核心就是BitmapFactory.Options来加载所需尺寸的图片。因为很多时间ImageView并没有图片原始尺寸那么大,把整个图片加载进来显然是没有必要的。我们可以使用BitmapFactory.Options从如下几种方式对图片进行采样压缩,降低内存的占有从而减少OOM的可能性:

1. 降低图片加载到内存的分辨率(BitmapFactory.Options.inJustDecodeBounds/outWidth/outHeight/inSampleSize属性);

2. 采用更节更节省内存的编码,如ARGB_4444(BitmapFactory.Options.inPreferredConfig属性);

3. 采用缓存;

这里我们就从方法1和方法2进行处理,该方式需要了解BitmapFactory.Options,先介绍如下:

参数
说明
备注
inJustDecodeBounds为true时,解码不会返回bitmap,只会返回bitmap的尺寸。用于获取图片的尺寸,但有不想将其加载到内存中。
inSampleSize当<1时,当做1处理;>1时会按照比例缩小bitmap的宽高,降低分辨率。如with=100,height=100,inSampleSize=2,则返回width=50,height=50,像素50*50=250降为1/4;
inPreferredConfig色彩模式,默认ARGB_8888,一个像素4bytes。如果对透明不做要求,采用RGB_565,一个像素2bytes;ALPHA_8:每个像素1byte;
ARGB_444:每个像素2byte;
ARGB_8888:每个像素4byte;
RGB_565:每个像素2byte;
如果一个图片分辨率1024*768,采用ARGB_8888,占用空间为1024*768*4=3M,而采用ARGB_444内存就能减半1.5M;
inPremultiplied和透明通道有关,默认true,返回的bitmap颜色通道上预先附加透明通道;透明通道是计算机图形学术语,指的是“非彩色”通道,8位灰度通道,使用256级灰度来记录图像中的透明信息,定义透明、不透明和半透明。如32位存储的图片,8红+8绿+8蓝+8透明;
inDither抖动解码,默认false,标识不采用抖动解码;Bitmap解码是根据它所记录的节点,按照一定的算法来补充两个节点之间的数据,可理解为补充像素点的颜色。一张颜色丰富的图用一个位数比较低的颜色模式解码的话,会感觉颜色不够用,颜色渐变区域有明显断裂带。因为一些丰富的颜色在位数较低的颜色模式下并没有,只能用相近的颜色补充,可能一大片没有,那么这大片都用一个颜色填充,就形成了断裂色带;如果采用抖动解码,就会在这些颜色上采用随机噪声色来填充,这样显示效果更好,色带不那么明显。如果不想有这些色带,就需要采用抖动解码;
inDensity表示这个bitmap的像素密度;对应DisplayMetrics.densityDpi,不是density
inTargetDensity表示要被画出来时的目标像素密度;
inScreenDensity标识实际设备的像素密度;inDensity,inTargetDensity,inScreenDensity这三个值的目的就是为了确定这个Bitmap的宽高和density。详细算法可以查看setDensityFromOptions()方法源码实现;
inScaled设置这个bitmap是否可以被缩放,默认true;
inPurgeable/inInputShareable一般一起使用,设置为true时,表示空间不够可以被释放,后者表示是否可以共享引用。Android5.0后被弃用;
outWidth/outHeight表示bitmap的宽和高,一般和inJustDecodeBounds一起使用获取Bitmap的宽高,但不加载到内存中;
二、BitmapFactory.Options实践

1.为了跟大家更好的体会和展示优化过程,首先我们先使用一个简单Demo,使用Gallery来展示几张图片来模拟OOM,代码如下:

NextActivity.java:

[java] view
plain copy







public class NextActivity extends AppCompatActivity {

private int[] images = new int[]{R.drawable.p1, R.drawable.p2, R.drawable.p3};

@Override

protected void onCreate(Bundle savedInstanceState) {

super.onCreate(savedInstanceState);

setContentView(R.layout.activity_next);

Gallery gallery = (Gallery) findViewById(R.id.gallery);

gallery.setAdapter(new GalleryAdapter());

}

class GalleryAdapter extends BaseAdapter {

@Override

public int getCount() {

return images.length;

}

@Override

public Object getItem(int position) {

return images[position];

}

@Override

public long getItemId(int position) {

return images[position];

}

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ImageView imageView = new ImageView(NextActivity.this);

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position]);

imageView.setImageBitmap(bitmap);

return imageView;le = true;

}

}

}

2.运行后即OOM异常崩溃,错误日志输出如下:

再看看Monitor中,在两张图片加载图片过程中,内存两次迅速上升,达到200M后OOM崩溃;

3.接下来下面我们就使用BitmapFactory.Options来优化该OOM问题:

NextActivity.java

[java] view
plain copy







public class NextActivity extends AppCompatActivity {

... ...

class GalleryAdapter extends BaseAdapter {

... ...

@Override

public View getView(int position, View convertView, ViewGroup parent) {

ImageView imageView = new ImageView(NextActivity.this);

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

//inJustDecodeBounds为true,不返回bitmap,只返回这个bitmap的尺寸

options.inJustDecodeBounds = true;

BitmapFactory.decodeResource(getResources(), images[position], options);

//利用返回的原图片的宽高,我们就可以计算出缩放比inSampleSize,获取指定宽度为300像素,等长宽比的缩略图,减少图片的像素

int toWidth = 300;

int toHeight = options.outHeight * toWidth / options.outWidth;

options.inSampleSize = options.outWidth / toWidth;

options.outWidth = toWidth;

options.outHeight = toHeight;

//使用RGB_565减少图片大小

options.inPreferredConfig = Bitmap.Config.RGB_565;

//释放内存,共享引用(21版本后失效)

options.inPurgeable = true;

options.inInputShareable = true;

//inJustDecodeBounds为false,返回bitmap

options.inJustDecodeBounds = false;

Bitmap bitmap = BitmapFactory.decodeResource(getResources(), images[position], options);

imageView.setImageBitmap(bitmap);

return imageView;

}

}

}

4.程序正常运行,Monitor监控内存减少至21M左右!!!

5.在实际的应用过过程中,inSampleSize计算并不会那么“理想 ”。比如ImagView的大小是100*100像素,而图片的原始大小是200*300呢?inSampleSize为2,则缩放后的图片大小为100*150像素,仍然是适合的;但是为3那么缩小后的图片大小就会小于ImageView的期望大小,这样图片就会拉伸从而导致模糊。下面我们就提供一种计算inSampleSize的计算方式,供大家参考:

[java] view
plain copy







public static int caculateInSampleSize(BitmapFactory.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 halfHeight = height / 2;

final int halfWidth = width / 2;

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

inSampleSize *= 2;

}

}

return inSampleSize;

}

6.代码库

QProject:https://github.com/Pengchengxiang/QProject
分支:feature/bitmapoption
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: