(源码分析)Android-Universal-Image-Loader (图片异步加载缓存库)对Bitmap的优化处理
来源:互联网 发布:苗阜水平知乎 编辑:程序博客网 时间:2024/05/23 02:04
转载请注明出处:http://blog.csdn.net/u011733020
前言:
前面两篇分别介绍了:
Android-Universal-Image-Loader (图片异步加载缓存库)的使用配置
Android-Universal-Image-Loader (图片异步加载缓存库)的源码解读
通过前两篇,我们了解了 UIL的使用配置,UIL将服务器上的一张图片保存到本地,加载到内存的过程,以及UIL对DiscCache和MemoryCache的策略,但是还有一部分比较重要,因为它是我们的开发日常中经常要处理的一个问题:Bitmap的优化。换句话说:如何将一个大的图片,加载到内存并显示,如果我们不处理,那么很容易发生OOM。
那么UIL作为一款经典图片缓存框架接下来,我们就学习一下UIL中如何优化Bitmap,避免发生OOM的,以后在我们项目开发的时候就可以用相同的方法去解决类似的问题。
正文
大图片加载到内存的两种方法对比
首先我们先不用UIL ,直接加载一张大图片会发生什么?
将上述21M的本地图片aaa.jpg直接通过加载到内存
private String uri_virtual="/mnt/sdcard/UIL/Document/pics/aaa.jpg";Bitmap bm=BitmapFactory.decodeFile(uri_virtual);errImage.setImageBitmap(bm);运行一下程序会发现发生了crash
在logcat中报错如下
这是一个非常常见的错误:内存溢出(Out Of Memory)。
导致这个错误的原因一般是 加载了一个超过dalivk heap 的size(一般16M) 的文件,或者 内存使用频繁,释放不及时,导致内存不够用。
解决OOM的方法就是: 使用 弱引用WeakReference,手动释放内存 System.gc(),将Bitmap压缩 等。
那么我们在用UIL去加载这一张大图片:
image = (ImageView) findViewById(R.id.iv);DisplayImageOptions displayOptions = new DisplayImageOptions.Builder().cacheInMemory(true).bitmapConfig(Bitmap.Config.RGB_565).cacheOnDisk(true).build();ImageLoader.getInstance().displayImage(uri_virtual, image,displayOptions);发现加载成功:
可见UIL 内部对其进行了处理,使其加载成功。
UIL加载优化分析
// 尝试 本地文件中是否有缓存File imageFile = configuration.diskCache.get(uri);if (imageFile != null && imageFile.exists() && imageFile.length() > 0) {L.d(LOG_LOAD_IMAGE_FROM_DISK_CACHE, memoryCacheKey);loadedFrom = LoadedFrom.DISC_CACHE;checkTaskNotActual();bitmap = decodeImage(Scheme.FILE.wrap(imageFile.getAbsolutePath()));}
private Bitmap decodeImage(String imageUri) throws IOException {ViewScaleType viewScaleType = imageAware.getScaleType();ImageDecodingInfo decodingInfo = new ImageDecodingInfo(memoryCacheKey, imageUri, uri, targetSize, viewScaleType,getDownloader(), options);return decoder.decode(decodingInfo);}
/** * Decodes image from URI into {@link Bitmap}. Image is scaled close to incoming {@linkplain ImageSize target size} * during decoding (depend on incoming parameters). * @param decodingInfo Needed data for decoding image: 如果 具体View 没有指定 wh 为手机分辨率 px 否则为 设置的px值 * @return Decoded bitmap * @throws IOException if some I/O exception occurs during image reading * @throws UnsupportedOperationException if image URI has unsupported scheme(protocol) */@Overridepublic Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {Bitmap decodedBitmap;ImageFileInfo imageInfo;InputStream imageStream = getImageStream(decodingInfo);if (imageStream == null) {L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());return null;}try {imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);imageStream = resetStream(imageStream, decodingInfo);Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);} finally {IoUtils.closeSilently(imageStream);}if (decodedBitmap == null) {L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());} else {decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,imageInfo.exif.flipHorizontal);}return decodedBitmap;}再看方法以前,我先解释一下 ImageDecodingInfo 这是一个非常重要的类,它里面封装了我们布局里设置子的ImageView的一些属性,比如 android:layout_width android:layout_height
Options 属性介绍
destOptions.inDensity destOptions.inDither destOptions.inInputShareable destOptions.inJustDecodeBounds destOptions.inPreferredConfig destOptions.inPurgeabledestOptions.inSampleSize destOptions.inScaleddestOptions.inScreenDensitydestOptions.inTargetDensity destOptions.inTempStorage destOptions.inPreferQualityOverSpeeddestOptions.inBitmapdestOptions.inMutable
根据InpuStream 使用Options.inJustDecodeBounds 获取图片信息
/** * //options.outWidth:11935options.outHeight:8554 根据文件流 拿到 本地图片的分辨率 * @param imageStream: 文件流 * @param decodingInfo: 本地图片的文件信息 * @return * @throws IOException */protected ImageFileInfo defineImageSizeAndRotation(InputStream imageStream, ImageDecodingInfo decodingInfo)throws IOException {Options options = new Options();options.inJustDecodeBounds = true;BitmapFactory.decodeStream(imageStream, null, options);ExifInfo exif;String imageUri = decodingInfo.getImageUri();if (decodingInfo.shouldConsiderExifParams() && canDefineExifParams(imageUri, options.outMimeType)) {exif = defineExifOrientation(imageUri);} else {exif = new ExifInfo();}//options.outWidth:11935options.outHeight:8554 根据文件流 拿到 本地图片的分辨率return new ImageFileInfo(new ImageSize(options.outWidth, options.outHeight, exif.rotation), exif);}
意思就是:
根据本地图片属性与布局中的ImagView 比较,算出缩放比例:
/** * @param imageSize 本地图片的大小 * @param decodingInfo :需要的编译规格 比如 设定过 wh 或者默认的 手机分辨率 * @return */protected Options prepareDecodingOptions(ImageSize imageSize, ImageDecodingInfo decodingInfo) {ImageScaleType scaleType = decodingInfo.getImageScaleType();int scale;if (scaleType == ImageScaleType.NONE) {scale = 1;} else if (scaleType == ImageScaleType.NONE_SAFE) {scale = ImageSizeUtils.computeMinImageSampleSize(imageSize);} else {ImageSize targetSize = decodingInfo.getTargetSize();boolean powerOf2 = scaleType == ImageScaleType.IN_SAMPLE_POWER_OF_2;scale = ImageSizeUtils.computeImageSampleSize(imageSize, targetSize, decodingInfo.getViewScaleType(), powerOf2);}if (scale > 1 && loggingEnabled) {L.d(LOG_SUBSAMPLE_IMAGE, imageSize, imageSize.scaleDown(scale), scale, decodingInfo.getImageKey());}Options decodingOptions = decodingInfo.getDecodingOptions();decodingOptions.inSampleSize = scale;// insampleSize =n 表示 缩小到原来的 1/n 比如 1/2 占的容量变小 对已经产生的bitmap 不生效,只能对 BitmapFactory // 只能用BitmapFactory生成的Bitmap才有用,如BitmapFactory.decodeResource(res, id, options)这种方法。把options放到参数里面就可以了。return decodingOptions;}
public static int computeImageSampleSize(ImageSize srcSize, ImageSize targetSize, ViewScaleType viewScaleType,boolean powerOf2Scale) {final int srcWidth = srcSize.getWidth();final int srcHeight = srcSize.getHeight();final int targetWidth = targetSize.getWidth();final int targetHeight = targetSize.getHeight();int scale = 1;switch (viewScaleType) {case FIT_INSIDE: // 过按比例缩小或原来的size使得图片长/宽等于或小于View的长/宽if (powerOf2Scale) {final int halfWidth = srcWidth / 2;final int halfHeight = srcHeight / 2;while ((halfWidth / scale) > targetWidth || (halfHeight / scale) > targetHeight) { // ||scale *= 2;}} else {scale = Math.max(srcWidth / targetWidth, srcHeight / targetHeight); // max}break;case CROP:// 按比例扩大图片的size居中显示,使得图片长(宽)等于或大于View的长(宽) if (powerOf2Scale) {final int halfWidth = srcWidth / 2;final int halfHeight = srcHeight / 2;while ((halfWidth / scale) > targetWidth && (halfHeight / scale) > targetHeight) { // &&scale *= 2;}} else {scale = Math.min(srcWidth / targetWidth, srcHeight / targetHeight); // min}break;}if (scale < 1) {scale = 1;}scale = considerMaxTextureSize(srcWidth, srcHeight, scale, powerOf2Scale);return scale;}注释了两种type ,大图片肯定是要缩放,而缩放的规格就是 13 14行的运算
while ((halfWidth / scale) > targetWidth || (halfHeight / scale) > targetHeight) { // ||scale *= 2;}
private static int considerMaxTextureSize(int srcWidth, int srcHeight, int scale, boolean powerOf2) {final int maxWidth = maxBitmapSize.getWidth();final int maxHeight = maxBitmapSize.getHeight();while ((srcWidth / scale) > maxWidth || (srcHeight / scale) > maxHeight) {if (powerOf2) {scale *= 2;} else {scale++;}}return scale;}
这个方法是为了进一步确定scale的值,应该是尽可能的大,这里涉及到 OpenGL ES 的一些东西,我们先不管。
上过程最终得到的scale 设置给了decodingOptions.inSampleSize = scale。
然后通过前面的decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);最终拿到了适合的Bitmap,接下来的过程就是设置显示的过程,这里就不在分析了。
数据证明
public Bitmap decode(ImageDecodingInfo decodingInfo) throws IOException {Bitmap decodedBitmap;ImageFileInfo imageInfo;InputStream imageStream = getImageStream(decodingInfo);if (imageStream == null) {L.e(ERROR_NO_IMAGE_STREAM, decodingInfo.getImageKey());return null;}try {imageInfo = defineImageSizeAndRotation(imageStream, decodingInfo);imageStream = resetStream(imageStream, decodingInfo);Options decodingOptions = prepareDecodingOptions(imageInfo.imageSize, decodingInfo);decodedBitmap = BitmapFactory.decodeStream(imageStream, null, decodingOptions);Log.e("decode"," bytecount: "+decodedBitmap.getByteCount()+" density:"+decodedBitmap.getDensity()+" H:"+decodedBitmap.getHeight()+" W:"+decodedBitmap.getWidth());} finally {IoUtils.closeSilently(imageStream);}if (decodedBitmap == null) {L.e(ERROR_CANT_DECODE_IMAGE, decodingInfo.getImageKey());} else {decodedBitmap = considerExactScaleAndOrientatiton(decodedBitmap, decodingInfo, imageInfo.exif.rotation,imageInfo.exif.flipHorizontal);}return decodedBitmap;}
模拟器 bytecount: 199182 density:160 bitmap's height :267 bitmap's width:373 scale:32
结语:
前面两篇地址:
Android-Universal-Image-Loader (图片异步加载缓存库)的使用配置
Android-Universal-Image-Loader (图片异步加载缓存库)的源码解读
爱学习的小伙伴请戳戳戳戳戳戳进QQ群:230274309 。
- (源码分析)Android-Universal-Image-Loader (图片异步加载缓存库)对Bitmap的优化处理
- (源码分析)Android-Universal-Image-Loader (图片异步加载缓存库)的源码解读
- (源码分析)Android-Universal-Image-Loader (图片异步加载缓存库)的使用配置
- Android图片异步加载框架Universal Image Loader的源码分析
- Android-Universal-Image-Loader图片异步加载并缓存
- challenge(六)图片异步加载类库(Android-Universal-Image-Loader)的使用
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- [置顶] Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- Android-Universal-Image-Loader 图片异步加载类库的使用(超详细配置)
- C++ explicit构造函数
- IP地址判断有效性
- android学习之Unable to resolve target 'android-15'
- MongoDB安全选项导致的“norepl”错误
- Java代码中转码 中文字符转UTF-8编码
- (源码分析)Android-Universal-Image-Loader (图片异步加载缓存库)对Bitmap的优化处理
- Codeforces Round #336 (Div. 1) A. Chain Reaction DP+(可选二分)
- 读《经济解释》卷二,收入与成本
- ZOJ 1079 Robotic Jigsaw
- HDU 4292 FOOD 网络流
- 反射:参数是数组类型
- 细说Executor框架(上)
- AWS上的游戏服务:Lumberyard + Amazon GameLift + Twitch
- 差分比较法