Bitmap作为重要Android应用之一,在很多时候如果应用不当,很容易造成内存溢出,那么这篇文章的目的就在于探讨Bitmap的有效运用及其优化

缓存介绍

当多次发送请求的时候,请求同一内容,为了使资源得到合理利用,那么就需要设置缓存,避免同一内容被多次请求
在这里使用一个Http的缓存策略,对http自带的缓存策略做一个简单的使用介绍,从而引出今天的主角

http自带缓存的使用前提:服务器设置了缓存时间

response.addHeader("Cache-control", "max-age=10"); //HttpServletResponse response

以上代表了在10秒重内不会再请求服务器,此时客户端开启了缓存的话,在10内就不会重复请求了
http自带缓存的策略的使用:

  1. 打开缓存,Android在默认情况下HttpResponseCache(网络请求响应缓存)是关闭的
try {File cacheDir = new File(getCacheDir(), "http");//缓存目录,在应用目录的cache文件夹下生成http文件夹long maxSize = 10 * 1024 * 1024;//缓存大小:byteHttpResponseCache.install(cacheDir, maxSize );Log.d(TAG, "打开缓存");
} catch (IOException e) {e.printStackTrace();
}
  1. 发送请求,这里以向服务器请求一张图片为例
    此时,会连接服务器,获取服务器里面的内容
new Thread(new Runnable() {@Overridepublic 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();
  1. 删除缓存,就算现在不删,也要找时间清理
HttpResponseCache cache = HttpResponseCache.getInstalled();
if(cache!=null){try {cache.delete();Log.d(TAG, "清空缓存");} catch (IOException e) {e.printStackTrace();}
}

经过以上步骤,就走完了http缓存的一个流程,在实际应用中,一般会采用自己设计缓存的方法,这里只是引出缓存这个概念

Bitmap优化着手点

在使用Bitmap的时候,容易使得Bitmap超过系统分配的内存阈值,发么此时就产生了常见的OOM报错,因此,优化Bitmap的思路也就在于如何避免OOM的发生

那么避免OOM发生就要从图片本身下手了,常见的处理方式便是将图片进行压缩,常用的压缩算法有三种:

  • 质量压缩
    质量压缩是通过同化像素点周围的相近的像素点,从而达到降低文件大小的作用,也就是说其本身的像素多少并没有改变,压缩出来的图片虽然大小发生了改变,但分辨率没有发生改变,而在Bitmap中,其是按照像素大小,即图片的像素多少来计算内存空间的,因此这种方法并不能有效避免OOM,这也就是为何只改变图片大小对于Bitmap的内存使用没有作用的原因
    那么质量压缩有什么作用呢?其实它的作用就是减少存储体积,方便传输或者保存

  • 尺寸压缩
    尺寸压缩的思路就是使用Canvas读取现在的bitmap,然后对其尺寸进行修改,这里是真实的改变了图片的像素大小,所以在Bitmap使用的时候,就会得到改变尺寸后的大小,那么就可以对Bitmap进行有效优化,这种操作一般用于缓存缩略图

  • 采样率压缩
    设置图片的采样率,降低图片像素,其原理和尺寸压缩类似,不过实现的方式不同

Bitmap的Java层源代码分析

由于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的调用参数可以设置的由三个InputStreamRectOptions
那么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~100ByteArrayOutputStream baos = new ByteArrayOutputStream();bmp.compress(Bitmap.CompressFormat.JPEG, quality, baos); //图片格式可以选择JPEG,PNG,WEBPtry {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的处理
质量压缩:适合网络传输
尺寸压缩和采样率压缩:适合生成缩略图

转载于:https://www.cnblogs.com/cj5785/p/10664635.html

性能优化-Bitmap内存管理及优化相关推荐

  1. Android进阶——性能优化之内存管理机制和垃圾采集回收机制(六)

    文章大纲 引言 一.内存泄漏和内存溢出概述 二.Java运行时内存模型 1.线程私有数据区 1.1.程序计数器PC 1.2.虚拟机栈 1.3 本地方法栈 2.所有线程共享数据区 2.1.Java堆 2 ...

  2. 关于mysql内存管理_MYSQL内存管理及优化

    MYSQL内存管理及优化 内存是影响数据库性能的主要资源,也是mysql性能优化的一个重要方面: 内存优化的原则 将尽量多的内存分配给mysql做缓存,但是要给操作系统和其他程序的运行预留足够的内存, ...

  3. 【Unity】Unity内存管理与优化(一)内存域、堆栈、垃圾回收、内存泄漏、内存碎片

    文章目录 Unity内存 内存域 - 托管域 - 本地域 - 外部库 - 跨桥操作 堆和栈 - 栈 - 堆 - 堆栈的使用 垃圾回收 - Mono内存分配过程 - 内存泄漏 - 内存碎片 - 运行时垃 ...

  4. Mysql 内存管理及优化

    Mysql 内存管理及优化 1)内存优化原则 1) 将尽量多的内存分配给 MySQL 做缓存,但要给操作系统和其他程序预留足够内存. 2) MyISAM 存储引擎的数据文件读取依赖于操作系统自身的 I ...

  5. 【Linux 内核 内存管理】优化内存屏障 ③ ( 编译器屏障 | 禁止 / 开启内核抢占 与 方法保护临界区 | preempt_disable 禁止内核抢占源码 | 开启内核抢占源码 )

    文章目录 一.禁止 / 开启内核抢占 与 方法保护临界区 二.编译器优化屏障 三.preempt_disable 禁止内核抢占 源码 四.preempt_enable 开启内核抢占 源码 一.禁止 / ...

  6. C语言 --- 动态内存管理(上)+优化版通讯录+笔试题

    文章目录 前言 一.为什么存在动态内存分配 二.动态内存函数的介绍 2.1.malloc函数+free函数 2.2.calloc函数+free函数 2.3.realloc函数 三.常见的动态内存错误 ...

  7. Unity 3D中的内存管理与优化游戏运行性能的经验

    Unity3D在内存占用上一直被人诟病,特别是对于面向移动设备的游戏开发,动辄内存占用飙上一两百兆,导致内存资源耗尽,从而被系统强退造成极差的体验.类似这种情况并不少见,但是绝大部分都是可以避免的.虽 ...

  8. Android性能优化OOM内存管理——ADJ

    前言 OOM_ADJ (Out of Memory Adjustment)是android系统在内存不足情况下进行内存调整的重要参数.在处理app启动速度的时候,可以设置主线程的优先级,保证主线程占用 ...

  9. Android 内存管理之优化建议

    OOM(OutOfMemory)转:http://hukai.me/android-performance-oom/ 前面我们提到过使用getMemoryClass()的方法可以得到Dalvik He ...

最新文章

  1. ajax更新,AJAX网址更新(AJAX URL update)
  2. linux 进程创建 进程启动 监控
  3. android base64解密,android Base64 AES加密解密
  4. Pattern-No.03 设计模式之策略模式
  5. AngularJS学习笔记一:简单入门
  6. java mysql 文本导入数据语句_Java利用MYSQL LOAD DATA LOCAL INFILE实现大批量导入数据到MySQL...
  7. linux—用nc命令监控检测服务器端口
  8. Python案例:房源信息分析
  9. k8s核心技术-Helm(概述)---K8S_Google工作笔记0044
  10. 50岁的哆啦A梦“撞”上63岁的卡西欧,阿里云数据中台告诉你火花能有多大
  11. HDU 操作系统实验二 -设计一个系统调用,返回指定进程的相关时间信息
  12. 11年瑞纳手动挡值多少钱_10年瑞纳值多少钱(10年的手动高配瑞纳,跑了4万公里,现在值多少钱?)...
  13. HSB/HSV/HSL区别
  14. 智慧养老模式和智慧养老系统
  15. 机器学习笔记马尔可夫链蒙特卡洛方法(二)马尔可夫链与平稳分布
  16. bigemap如何应用卫星图像到Auto CAD
  17. JavaSwimg jacob调用模板,斑马打印机打印条码
  18. gis可达性分析步骤_CYD软件技能 | 城市动态设计分析制图 Design for the Urban Dynamics...
  19. 新建vivado工程仿真或修改原仿真代码后,仿真波形不更新问题(已解决)
  20. js实现浏览器中的复制粘贴

热门文章

  1. ipad运行python爬虫_Python爬虫之UserAgent的使用实例
  2. python php array,python处理PHP数组文本文件实例
  3. 手游 android 分辨率 适配,安卓不得不玩的神作,十大超高画质手游(安卓)
  4. linux vga 分辨率低,vga输出 1440x900 分辨率问题
  5. 培养产品思维,每个人都应该是产品经理
  6. java文件上传下载服务并发超时,先收藏了
  7. 解决The server cannot be started because one or more of the ports are invalid.
  8. SqLite中的事务
  9. python基础练习(三)
  10. 领计算机二级证材料,计算机二级证书怎么领