1.为什么要二次采样
OK,那么首先我要 解决的一个问题就是为什么我们要二次采样?
不知道大家在开发App的过程中有没有遇到过类似于图片墙这样的功能?在做图片墙的时候你有没有遇到过OOM异常呢?遇到了又是怎么解决的?再比如我现在有一张100M大的图片,我想把这张图片用一个ImageView显示出来,那么你的ImageView能够显示出来这张图片吗?上面我们说的这两种情况其实都涉及到图片加载时内存溢出的问题,内存溢出可能发生在加载一张大图的时候,也有可能发生在加载多张普通小图的时候,如果我们不对图片做二次采样,那么OOM就是一把悬在头上的剑,随时可能会掉下。所以一定要对图片进行二次采样。事实上,我在手机上显示一张分辨率特别大的图片和显示一张分辨率小的图片(不要小的太离谱即可),对用户的视觉体验来说,并不会有多大变化,但是对我们手机的内存来说,影响却是非常巨大的。总而言之,二次采样就是为了避免图片加载时的OOM异常。
2.二次采样分别是哪两次?每次采样的目的是什么
既然是二次采样,那当然要分为两步了,下面我们来说说每次采样的主要工作:
1.第一次采样
第一次采样我主要是想要获得图片的压缩比例,假如说我有一张图片是200*200,那么我想把这张图片的缩略图显示在一个50*50的ImageView上,那我的压缩比例应该为4,那么这个4应该怎么样来获得呢?这就是我们第一步的操作了,我先加载图片的边界到内存中,这个加载操作并不会耗费多少内存,加载到内存之后,我就可以获得这张图片的宽高参数,然后根据图片的宽高,再结合控件的宽高计算出缩放比例。
2.第二次采样
在第一次采样的基础上,我来进行二次采样。二次采样的时候,我把第一次采样后算出来的结果作为一个参数传递给第BitmapFactory,这样在加载图片的时候系统就不会将整张图片加载进来了,而是只会加载该图片的一张缩略图进来,这样不仅提高了加载速率,而且也极大的节省了内存,而且对于用户来说,他也不会有视觉上的差异。
3.代码实现
import android.content.Context; import android.graphics.Bitmap; import android.graphics.BitmapFactory; import android.os.AsyncTask; import android.widget.ImageView; import com.nostra13.universalimageloader.core.DisplayImageOptions; import com.nostra13.universalimageloader.core.ImageLoader; import com.nostra13.universalimageloader.core.ImageLoaderConfiguration; import com.nostra13.universalimageloader.core.assist.ImageScaleType; import java.io.ByteArrayOutputStream; /** * Created by 蜕变~成蝶 on 2017/10/26. */ public class ImageLoaderUtile { /** * /** * 使用imageLoader加载Bitmap进行二次采样 * 加载到imageview上 * * @param context 上下文 * @param url 图片网址 * @param img 控件 */ public static void fillingImgeview(Context context, String url, final ImageView img) { ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).build(); //开始构建 ImageLoader imageLoader = ImageLoader.getInstance(); //设置初始化 imageLoader.init(config); DisplayImageOptions options = new DisplayImageOptions.Builder() .showImageOnLoading(R.mipmap.ic_launcher) //设置图片在下载期间显示的图片 .showImageForEmptyUri(R.mipmap.ic_launcher)//设置图片Uri为空或是错误的时候显示的图片 .showImageOnFail(R.mipmap.ic_launcher) //设置图片加载/解码过程中错误时候显示的图片 .cacheInMemory(true)//设置下载的图片是否缓存在内存中 .cacheOnDisk(true)//设置下载的图片是否缓存在SD卡中 .imageScaleType(ImageScaleType.IN_SAMPLE_INT)//设置图片以如何的编码方式显示 .bitmapConfig(Bitmap.Config.RGB_565)//设置图片的解码类型 .build();//构建完成 new AsyncTask<String, Void, Bitmap>() { @Override protected Bitmap doInBackground(String... params) { Bitmap bitmap = ImageLoader.getInstance().loadImageSync(params[0]); return bitmap; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100, baos); Bitmap dimensionBitmap = getDownDimensionBitmap(baos.toByteArray(), 50, 50); img.setImageBitmap(dimensionBitmap); } }.execute(url); } /** * 获取二次采样图片 * * @param data * @param requestWidth * @param requestHeight * @return */ public static Bitmap getDownDimensionBitmap(byte[] data, int requestWidth, int requestHeight) { Bitmap bitmap = null; if (data != null) { //按照原始的图片尺寸,进行Bitmap的生存 //按照Bitmap生成,是按照图片的原始宽高,进行生成,并且每一个像素占用4个字节 ARGB // ret = BitmapFactory.decodeByteArray(data, 0, data.length); //采用二次采样(缩小图片尺寸的方式) //1.步骤1 获取原始图片的宽高信息,用于进行采样的计算 //1.1创建Options ,给BitmapFactory的内部解码器设置参数 BitmapFactory.Options options = new BitmapFactory.Options(); //1.2设置inJustDecodeBounds 来控制解码器,只会进行图片宽高的获取,不会获取图片 //不占用内存,当使用这个参数,代表BitmapFactory.decodexxx()不会返回bitmap options.inJustDecodeBounds = true; //解码,使用options参数 设置解码方式 BitmapFactory.decodeByteArray(data, 0, data.length, options); //2.步骤2 根据图片的真实尺寸,与当前需要显示的尺寸,进行计算,生成采样率 //2.1 //int picW = options.outWidth; //int picH = options.outHeight; //2.2准备 显示在手机上 256x128 128x64 //尺寸是根据程序需要来设置的 // int reqW = 256; // int reqH = 128; //2.3计算 设置 图片采样率 options.inSampleSize = calculateInSampleSize(options, requestWidth, requestHeight);//宽度的1/32 高度的1/32 //2.4开放 解码,实际生成Bitmap options.inJustDecodeBounds = false; //2.4.1 Bitmap.Config的说明 //要求解码器对于每一个采样的像素,使用RGB_565 存储方式 //一个像素,占用两个字节,比ARGB_8888笑了一半 //如果解码器不能使用指定配置,就自动使用ARGB_8888 options.inPreferredConfig = Bitmap.Config.RGB_565; //2.4.2是一个过时的设置,可以及时清除内存 options.inPurgeable = true; //2.5使用设置采样的参数,来进行 解码,获取bitmap bitmap = BitmapFactory.decodeByteArray(data, 0, data.length, options); } return bitmap; } /** * 计算图片二次采样的采样率,使用获取图片宽高之后的Options作为第一个参数 * * @param options * @param reqWidth * @param reqHeight * @return --by Google */ private static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { // Raw height and width of image final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; //只有当请求的宽度、高度 > 0时,进行缩放 //否则,图片不进行缩放 if (reqHeight > 0 && reqWidth > 0) { if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; // Calculate the largest inSampleSize value that is a power of 2 and keeps both // height and width larger than the requested height and width. while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } } return inSampleSize; } }最后记得加上网络权限