Bitmap做为重要Android应用之一,在不少时候若是应用不当,很容易形成内存溢出,那么这篇文章的目的就在于探讨Bitmap的有效运用及其优化html
当屡次发送请求的时候,请求同一内容,为了使资源获得合理利用,那么就须要设置缓存,避免同一内容被屡次请求
在这里使用一个Http的缓存策略,对http自带的缓存策略作一个简单的使用介绍,从而引出今天的主角java
http自带缓存的使用前提:服务器设置了缓存时间android
response.addHeader("Cache-control", "max-age=10"); //HttpServletResponse response
以上表明了在10秒重内不会再请求服务器,此时客户端开启了缓存的话,在10内就不会重复请求了
http自带缓存的策略的使用:web
try { File cacheDir = new File(getCacheDir(), "http");//缓存目录,在应用目录的cache文件夹下生成http文件夹 long maxSize = 10 * 1024 * 1024;//缓存大小:byte HttpResponseCache.install(cacheDir, maxSize ); Log.d(TAG, "打开缓存"); } catch (IOException e) { e.printStackTrace(); }
new Thread(new Runnable() { @Override public void run() { try { BitmapFactory.decodeStream((InputStream) new URL("http://192.168.1.7:8080/test.png").getContent()); Log.d(TAG, "下载图片"); } catch (Exception e) { e.printStackTrace(); } } }).start();
HttpResponseCache cache = HttpResponseCache.getInstalled(); if(cache!=null){ try { cache.delete(); Log.d(TAG, "清空缓存"); } catch (IOException e) { e.printStackTrace(); } }
通过以上步骤,就走完了http缓存的一个流程,在实际应用中,通常会采用本身设计缓存的方法,这里只是引出缓存这个概念算法
在使用Bitmap的时候,容易使得Bitmap超过系统分配的内存阈值,发么此时就产生了常见的OOM报错,所以,优化Bitmap的思路也就在于如何避免OOM的发生canvas
那么避免OOM发生就要从图片自己下手了,常见的处理方式即是将图片进行压缩,经常使用的压缩算法有三种:数组
质量压缩
质量压缩是经过同化像素点周围的相近的像素点,从而达到下降文件大小的做用,也就是说其自己的像素多少并无改变,压缩出来的图片虽然大小发生了改变,但分辨率没有发生改变,而在Bitmap中,其是按照像素大小,即图片的像素多少来计算内存空间的,所以这种方法并不能有效避免OOM,这也就是为什么只改变图片大小对于Bitmap的内存使用没有做用的缘由
那么质量压缩有什么做用呢?其实它的做用就是减小存储体积,方便传输或者保存缓存
尺寸压缩
尺寸压缩的思路就是使用Canvas读取如今的bitmap,而后对其尺寸进行修改,这里是真实的改变了图片的像素大小,因此在Bitmap使用的时候,就会获得改变尺寸后的大小,那么就能够对Bitmap进行有效优化,这种操做通常用于缓存缩略图服务器
采样率压缩
设置图片的采样率,下降图片像素,其原理和尺寸压缩相似,不过实现的方式不一样网络
因为Bitmap的底层CPP代码涉及到的东西略多,这里就简单介绍下Java层的源码就好
在Bitmap使用的使用,一般会使用以下三个方法加载来自不一样地方的图片
BitmapFactory.decodeFile(path) BitmapFactory.decodeResource(res, id) BitmapFactory.decodeStream(is)
那么在BitmapFactory
源代码中,这三个方法是怎么处理的呢?
打开源代码,咱们能够看到:
public static Bitmap decodeFile(String pathName) { return decodeFile(pathName, null); } ··· public static Bitmap decodeResource(Resources res, int id) { return decodeResource(res, id, null); } ··· public static Bitmap decodeStream(InputStream is) { return decodeStream(is, null, null); }
在这里又传递了一次参数,那么找到这三个传递参数的方法
public static Bitmap decodeFile(String pathName, Options opts) { Bitmap bm = null; InputStream stream = null; try { stream = new FileInputStream(pathName); bm = decodeStream(stream, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. */ Log.e("BitmapFactory", "Unable to decode stream: " + e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // do nothing here } } } return bm; }
在decodeFile()
方法中,将传入文件转化成了流,送到了decodeStream()
进行处理,其余就没作什么了,那么咱们再看看decodeResource()
作了啥
public static Bitmap decodeResource(Resources res, int id, Options opts) { Bitmap bm = null; InputStream is = null; try { final TypedValue value = new TypedValue(); is = res.openRawResource(id, value); bm = decodeResourceStream(res, value, is, null, opts); } catch (Exception e) { /* do nothing. If the exception happened on open, bm will be null. If it happened on close, bm is still valid. */ } finally { try { if (is != null) is.close(); } catch (IOException e) { // Ignore } } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } return bm; }
由以上代码看出,decodeResource()
也只是中转,把参数设置好,传到decodeResourceStream()
中去了,那么顺藤摸瓜,看一看这个方法里干了啥,因而咱们找到了这个方法
public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } if (opts.inTargetDensity == 0 && res != null) { opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
查看代码发现,这里其实也没有干吗,设置了Options参数,这里涉及到一个像素密度和分辨率的转化问题,其中,DisplayMetrics.DENSITY_DEFAULT = 160
,这个问题网上有不少讨论,这里简明说明一下
px = dp * Density
详细介绍能够参阅此文:dpi、dip、分辨率、屏幕尺寸、px、density关系以及换算
综合上面的分析,全部的线索都指向了decodeStream()
这个方法,那么咱们就来看看这个方法干了啥吧
public static Bitmap decodeStream(InputStream is, Rect outPadding, Options opts) { // we don't throw in this case, thus allowing the caller to only check // the cache, and not force the image to be decoded. if (is == null) { return null; } Bitmap bm = null; Trace.traceBegin(Trace.TRACE_TAG_GRAPHICS, "decodeBitmap"); try { if (is instanceof AssetManager.AssetInputStream) { final long asset = ((AssetManager.AssetInputStream) is).getNativeAsset(); bm = nativeDecodeAsset(asset, outPadding, opts); } else { bm = decodeStreamInternal(is, outPadding, opts); } if (bm == null && opts != null && opts.inBitmap != null) { throw new IllegalArgumentException("Problem decoding into existing bitmap"); } setDensityFromOptions(bm, opts); } finally { Trace.traceEnd(Trace.TRACE_TAG_GRAPHICS); } return bm; }
看一看上面的代码,先是判断了输出流,而后新建Bitmap对象,以后开始写入跟踪信息,这里能够忽略这个写入跟踪信息的玩意儿,下一步就是校验输入流的来源,是assets里面的,仍是其余的,由于位置不同,其处理方法不同,这里的nativeDecodeAsset()
方法就是JNI的方法了,而decodeStreamInternal
又是啥呢?再往下看看
private static Bitmap decodeStreamInternal(InputStream is, Rect outPadding, Options opts) { // ASSERT(is != null); byte [] tempStorage = null; if (opts != null) tempStorage = opts.inTempStorage; if (tempStorage == null) tempStorage = new byte[DECODE_BUFFER_SIZE]; return nativeDecodeStream(is, tempStorage, outPadding, opts); }
这里就是设置了一个数组,以供JNI使用,而后又传递到了JNI里面去了,也就是说这里调用了两个JNI的方法,一个是assets目录,一个是非assets目录
private static native Bitmap nativeDecodeStream(InputStream is, byte[] storage, Rect padding, Options opts); private static native Bitmap nativeDecodeAsset(long nativeAsset, Rect padding, Options opts);
那么JNI中作了什么事情呢?
static jobject nativeDecodeStream(JNIEnv* env, jobject clazz, jobject is, jbyteArray storage, jobject padding, jobject options) { jobject bitmap = NULL; std::unique_ptr<SkStream> stream(CreateJavaInputStreamAdaptor(env, is, storage)); if (stream.get()) { std::unique_ptr<SkStreamRewindable> bufferedStream( SkFrontBufferedStream::Create(stream.release(), SkCodec::MinBufferedBytesNeeded())); SkASSERT(bufferedStream.get() != NULL); bitmap = doDecode(env, bufferedStream.release(), padding, options); } return bitmap; }
nativeDecodeAsset()
处理与其相似,这里就再也不展开了,这篇文章进行了详细分析:android 图片占用内存大小及加载解析
那么,Java层代码到此分析结束,在Java层,其最终调用的就是decodeStream()
方法了
decodeStream()
方法参数分析由上面的代码分析,咱们已经知道,BitmapFactory的调用参数能够设置的由三个InputStream
,Rect
和Options
那么Options又有什么参数能够设置呢?
inDensity
:bitmap的像素密度
inTargetDensity
:bitmap输出的像素密度
BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); public void qualitCompress(Bitmap bmp, File file){ int quality = 50; //0~100 ByteArrayOutputStream baos = new ByteArrayOutputStream(); bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); //图片格式能够选择JPEG,PNG,WEBP try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
BitmapFactory.Options options = new BitmapFactory.Options(); Bitmap bitmap = BitmapFactory.decodeFile(imageFile.getAbsolutePath(), options); public static void sizeCompress(Bitmap bmp,File file){ int ratio = 4; //缩小的倍数 Bitmap result = Bitmap.createBitmap(bmp.getWidth() / ratio, bmp.getHeight() / ratio, Bitmap.Config.ARGB_8888);//这里设置的是Bitmap的像素格式 Canvas canvas = new Canvas(result); RectF rect = new RectF(0, 0, bmp.getWidth() / ratio, bmp.getHeight() / ratio); canvas.drawBitmap(bmp, null, rect, null); //经过Canvas从新画入 ByteArrayOutputStream baos = new ByteArrayOutputStream(); result.compress(Bitmap.CompressFormat.JPEG, 100, baos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
public static void rateCompress(String filePath, File file){ int inSampleSize = 8; //设置采样率,数值越大,像素越低 BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = false; //为true的时候不会真正加载图片,而是获得图片的宽高信息 options.inSampleSize = inSampleSize; //采样率 Bitmap bitmap = BitmapFactory.decodeFile(filePath, options); ByteArrayOutputStream baos = new ByteArrayOutputStream(); bitmap.compress(Bitmap.CompressFormat.JPEG, 100 ,baos); // 把压缩后的数据存放到baos中 try { if(file.exists()){ file.delete(); } else { file.createNewFile(); } FileOutputStream fos = new FileOutputStream(file); fos.write(baos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
以上就是系统自带的压缩算法实现Bitmap的处理 质量压缩:适合网络传输 尺寸压缩和采样率压缩:适合生成缩略图