Android Bitmap深入介绍(二)--- 优化技术
2016-06-13 15:14
375 查看
这一篇主要介绍Bitmap相关的一些优化技术,包括加载图片,图片内存管理,图片缓存。
会很大,我最好的方式就是在加载的时候就把图片缩小。Options提供了inJustDecodeBounds来先获取图片的大小,
如下代码:
options.outHeight和options.outWidth会获取图片的宽和高,通过获取到图片的宽和高,就可以使用options的inSample来缩放图片,
在加载图片显示到屏幕的时候,我们最好跟屏幕的密度一致,所以可以通过inSample来设置最终要缩放到多少,另外一方面我们有时候只需要缩略图,也需要进行缩放。下面是Android提供的代码:
通过这个方法得到inSampleSize,然后通过将options的inJustDecodeBounds设置为false就可以利用BitmapFactory加载图片了,得到的图片是经过缩放了的。
下面是Android提供的一段引用计数的代码:
另外一方面也可以考虑使用inPurgeable,inPurgeable让Android在需要的时候可以回收像素,减少OOM。需要重新绘制的时候,又重新解码。其实这会导致更多的计算消耗。
在3.0之后开始放到Java层内存中,在3.0之后也增加了inBitmap。inBitmap的使用方式如下:
需要注意的时候在4.4之前不支持不同大小的图片使用inBitmap,4.4才开始支持不同的大小,只要inBitmap比需要新加载的图片更大。另外inBitmap其实可以与下一节介绍的缓存一起使用,可以使用缓存了的图片作为inBitmap来加载新的图片。这里也有例子。
Android可以通过Runtime.getRuntime().getMaxMemory()获取最大内存,Android提供了一段小代码来使用LruCache:
上面的代码是将一张raw目录下面的图片保存到AshmemFile当中,然后再利用BitmapFactory读取图片。并且使用inPurgeable标志。AshmemFile和inPurgeable合起来使用。
参考
https://developer.android.com/training/displaying-bitmaps/index.html
https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=403263974&idx=1&sn=b0315addbc47f3c38e65d9c633a12cd6&scene=0&key=41ecb04b051110037b72d05bba1495f596e848534fc51afe877d63329a16dc24dc1d3606aaaba3745a05bfdb8c624a74&ascene=0&uin=Mjc3OTU3Nzk1&devicetype=iMac+MacBookPro10%2C1+OSX+OSX+10.10.5+build%2814F27%29&version=11020201&pass_ticket=kK4%2F6316QveG8O0vFtthPfBeKkNjyaL4HapsUAokHL5mUKCgI5hKTIKMc3D8uyqk
加载图片
图片缩放
我们在加载图片的时候,经常会遇到OOM的问题,也许我们测试的时候图片比较小,但是实际上使用的图片可能会很大,我最好的方式就是在加载的时候就把图片缩小。Options提供了inJustDecodeBounds来先获取图片的大小,
如下代码:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType; // 图片的mimeType
options.outHeight和options.outWidth会获取图片的宽和高,通过获取到图片的宽和高,就可以使用options的inSample来缩放图片,
在加载图片显示到屏幕的时候,我们最好跟屏幕的密度一致,所以可以通过inSample来设置最终要缩放到多少,另外一方面我们有时候只需要缩略图,也需要进行缩放。下面是Android提供的代码:
int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, int reqHeight){ final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1 ; if(reqWidth < width || reqHeight <height){ final int halfWidth = width / 2; final int halfHeight = height / 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( (halfWidth / inSampleSize > reqWidth && halfHeight / inSampleSize > reqHeight){ inSampleSize *= 2; } } }
通过这个方法得到inSampleSize,然后通过将options的inJustDecodeBounds设置为false就可以利用BitmapFactory加载图片了,得到的图片是经过缩放了的。
Bitmap.Config
除了根据屏幕的情况,图片在屏幕中显示的大小来缩放图片,另外也可以通过options的inPreferredConfig设置图片的Config,使用RGB_565的图片肯定比RGB_8888占用的内存要小的。PNG or JPG
另外还需要考虑的一个问题是图片的格式,使用png图片还是jpg图片,jpg压缩率要高意味着解码的时候消耗的时间肯能会更高,它没有alpha通道,但是对于内置在apk里面的图片,如果图片小,那么apk的大小也会变小。另外如果图片的色值丰富的话,用可能有好点,色值单调可能用png好点(比如我们的icon)。图片内存管理
在前面一篇有介绍过Android中图片的存储相关知识,在Android 2.3以及之前图片会存储在native内存中,Android推荐在Bitmap不再使用的时候,使用recycle方法回收Bitmap,因为图片存储在native内存当中,所以需要手动回收,另外也可以使用引用计数的方式在Bitmap的计数为0时,调用Bitmap的recycle方法。下面是Android提供的一段引用计数的代码:
private int mCacheRefCount = 0; private int mDisplayRefCount = 0; ... // Notify the drawable that the displayed state has changed. // Keep a count to determine when the drawable is no longer displayed. public void setIsDisplayed(boolean isDisplayed) { synchronized (this) { if (isDisplayed) { mDisplayRefCount++; mHasBeenDisplayed = true; } else { mDisplayRefCount--; } } // Check to see if recycle() can be called. checkState(); } // Notify the drawable that the cache state has changed. // Keep a count to determine when the drawable is no longer being cached. public void setIsCached(boolean isCached) { synchronized (this) { if (isCached) { mCacheRefCount++; } else { mCacheRefCount--; } } // Check to see if recycle() can be called. checkState(); } private synchronized void checkState() { // If the drawable cache and display ref counts = 0, and this drawable // has been displayed, then recycle. if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed && hasValidBitmap()) { getBitmap().recycle(); } } private synchronized boolean hasValidBitmap() { Bitmap bitmap = getBitmap(); return bitmap != null && !bitmap.isRecycled(); }
另外一方面也可以考虑使用inPurgeable,inPurgeable让Android在需要的时候可以回收像素,减少OOM。需要重新绘制的时候,又重新解码。其实这会导致更多的计算消耗。
在3.0之后开始放到Java层内存中,在3.0之后也增加了inBitmap。inBitmap的使用方式如下:
Bitmap inBitmap ; //已经使用了的Bitmap BitmapFactory.Options optiosn = new BitmapFactory.Options(); options.inBitmap = inBitmap ; Bitmap newBitmap = BitmapFactory.Options.decodeFile(filename, options);
需要注意的时候在4.4之前不支持不同大小的图片使用inBitmap,4.4才开始支持不同的大小,只要inBitmap比需要新加载的图片更大。另外inBitmap其实可以与下一节介绍的缓存一起使用,可以使用缓存了的图片作为inBitmap来加载新的图片。这里也有例子。
缓存方式
如果都是每次使用BitmapFactory从sdcard读取Bitmap,那么将会非常浪费时间,因为从磁盘读取图片是非常慢的,而且有的图片需要经常使用,如果把图片缓存在内存当中将能够很好地节省图片读取的时间。这样图片缓存就出现了。LruCache
LruCache是一个非常适合缓存图片的类,它是基于LinkedHashMap实现的。把最近经常使用的对象保存在LinkedHashMap里面,把最近没使用的从LinkedHashMap中移除。LinkedHashMap是一个LinkedList和HashMap一起实现的。需要注意一点的是在以前经常使用SoftReference或WeakReference来引用Bitmap来缓存,但是在2.3后,Android虚拟机的垃圾回收机制回收Soft和Weak引用更加积极了,也就是说它会很快就回收软引用和弱引用对象。Android可以通过Runtime.getRuntime().getMaxMemory()获取最大内存,Android提供了一段小代码来使用LruCache:
// Get max available VM memory, exceeding this amount will throw an // OutOfMemory exception. Stored in kilobytes as LruCache takes an // int in its constructor. final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8th of the available memory for this memory cache. final int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap bitmap) { // The cache size will be measured in kilobytes rather than // number of items. return bitmap.getByteCount() / 1024; } };
DiskLruCache
我们的图片可能是从网络中下载下来的,但是我们的应用中可能也没办法把图片全部放进内存,我们需要把图片保存在Disk中,另外一方面我们也不想要每次都从网络中下载图片。LruDiskCache就提供了一种磁盘缓存。当然如果图片访问非常频繁,使用ContentProvider的话将会更好。Ashmem
除了一般的缓存方式,强大的Fresco在5.0之前利用ashmem来缓存图片,将图片保存在ashmem当中,这样就不会占用太多Java堆内存而导致出现OOM的情况。下面是一段简单的将图片存在ashmem中并且读取出来的例子:private void testBitmapMemoryFile(){ // Log.i(LOGTAG,""+bitmap.getConfig().name()); InputStream is = getResources().openRawResource(R.raw.test4); byte[] imgArr = new byte[10 * 1024 * 1024]; int imgBytes = 0; try { while (is.available() > 0) { int bytes = is.read(imgArr, imgBytes, 1024); imgBytes += bytes; Log.i(LOGTAG, "bytes read:" + bytes); } is.close(); Log.i(LOGTAG, "bytes : " + imgBytes); memoryFile = new MemoryFile(null, imgBytes); OutputStream os = memoryFile.getOutputStream(); os.write(imgArr, 0, imgBytes); imgArr = null; os.flush(); os.close(); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPurgeable = true; fd = getMemoryFileFd(memoryFile,null,options); if (fd == null) { memoryFile.close(); Log.e(LOGTAG, "fd read error"); return; } Log.i(LOGTAG, "fd read ok"); bitmap = BitmapFactory.decodeFileDescriptor(fd); iv.setImageBitmap(bitmap); memoryFile.close(); } catch (IOException e) { e.printStackTrace(); } }
上面的代码是将一张raw目录下面的图片保存到AshmemFile当中,然后再利用BitmapFactory读取图片。并且使用inPurgeable标志。AshmemFile和inPurgeable合起来使用。
配置改变时缓存
Activity可能会经常遇到旋转屏幕的情况,会被重新加载,另外在跳转的时候也可能会被finish掉,返回来又得重新加载。这种时候如果对于已经加载了的东西都全部重新加载那会非常耗费时间,这种时候保存Cache,不重新加载将会是一个非常的方式,比如在Fragment中使用了Cache,可以使用setRetainInstance(true),避免重新创建Fragment,这样也能避免加载Fragment中的Cache。总结
这篇文章主要介绍了图片加载过程中的相关配置,比如利用Options的参数配置输出图片的大小,Bitmap.Config配置解码图片像素格式,以及如何使用PNG和JPG图片。另外介绍了图片缓存(LRUCache和AshmemFile)以及图片存储管理。参考
https://developer.android.com/training/displaying-bitmaps/index.html
https://mp.weixin.qq.com/s?__biz=MzA3NTYzODYzMg==&mid=403263974&idx=1&sn=b0315addbc47f3c38e65d9c633a12cd6&scene=0&key=41ecb04b051110037b72d05bba1495f596e848534fc51afe877d63329a16dc24dc1d3606aaaba3745a05bfdb8c624a74&ascene=0&uin=Mjc3OTU3Nzk1&devicetype=iMac+MacBookPro10%2C1+OSX+OSX+10.10.5+build%2814F27%29&version=11020201&pass_ticket=kK4%2F6316QveG8O0vFtthPfBeKkNjyaL4HapsUAokHL5mUKCgI5hKTIKMc3D8uyqk
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories