• Bitmap的载入与Cache(一)


    怎样有效的载入一个bitmap,因为Bitmap的特殊性以及Android对单个应用所施加的内存限制。比方16MB,这就导致载入Bitmap的时候非常easy出现内存溢出。

    因此,怎样高效的载入bitmap是一个非常重要也非常easy被开发人员忽略的问题。


    Bitmap的高效载入:

    怎样载入一张图片呢?BitmapFactory类提供了四类方法:decodedFile,decodedResource,decodedStream和decodedByteArray,分别用于支持文件系统,资源,输入流以及字节数组中载入出一个Bitmap对象,当中decodedFile和decodedResource又间接调用了decodedStream方法。这四类方法终于是在Android的底层实现的,相应着BitmapFactory类的几个方法。


    高效载入Bitmap的核心就是採用BitmapFactory.Options来载入所需尺寸的图片。这里加色通过ImageView来显示图片,非常多时候ImageView并没有图片的原始尺寸那么大,这个时候把整个图片载入进来后再射给ImageView,这显然是没有必要的,由于ImageView并没有办法显示原始的图片。通过Options參数就能够按一定的採样率来载入缩小后的图片,将缩小后的图片在ImageView中显示。这样就会减少内存占用从而在一定程度上避免OOM,提高载入时的性能。其缩放图片主要是用到了Options的inSampleSize參数。即採样率,当inSampleSize为1时,採样后的图片大小为图片的原始大小。当inSampleSize大于1时。比方2,那么採样后的图片其宽/高均为原图大小的1/2,而像素数为原图的1/4,其占有的内存大小也为原图的1/4
    拿一张1024*1024像素的图片来说,嘉定採用ARGB8888格式存储,那么他占有的内存为1024*1024*4。即4MB。假设inSampleSize为2,那么採样后的图片其内存占用仅仅有512*512*4即1MB。能够发现採样率inSampleSize必须是大于1的整数,图片才会有缩小的效果,而且採样率同一时候作用于宽/高,这将导致缩放后的图片大小以採样率的2次方形式递减。

    即inSampleSize=4是,那么缩放比例就是1/16。有一种 特殊情况,就是当inSampleSize小雨1时。其作用相当于1,即无缩放效果。


    通过採样率就可以有效的载入图片。那么究竟怎样获取採样率呢?

    (1)将BitmapFactory.Options的inJustDecodeBounds參数设为true并载入图片(仅仅会解析 图片的原始宽/高信息,并不会去真正载入图片)

    (2)从BitmapFactory.Options中取出图片的原始宽高信息。他们对英语outWitdh和outHeight參数

    (3) 依据採样率的规则并结合目标View的所需大小计算出採样率inSampleSize

    (4)将itmapFactory.Options的inJustDecodeBounds參数谁为false,然后又一次载入图片。

    public static Bitmap decodeSanpledBitmapFromResource(Resources res,
    			int resId, int reqWidth, int reqHeight) {
    		final BitmapFactory.Options options = new BitmapFactory.Options();
    		options.inJustDecodeBounds = true;
    		BitmapFactory.decodeResource(res, resId, options);
    		options.inSampleSize = calculateInSampleSize(options, reqWidth,
    				reqHeight);
    		options.inJustDecodeBounds = false;
    		return BitmapFactory.decodeResource(res, resId, options);
    	}
    
    	public static int calculateInSampleSize(BitmapFactory.Options options,
    			int reqWidth, int reqHeight) {
    		final int height = options.outHeight;
    		final int width = options.outWidth;
    		int inSampleSize = 1;
    
    		if (height > reqHeight || width > reqWidth) {
    			final int halfHieght = height / 2;
    			final int halfWidth = width / 2;
    
    			while ((halfHieght / inSampleSize) >= reqHeight
    					&& (halfWidth / inSampleSize >= reqWidth)) {
    				inSampleSize *= 2;
    			}
    		}
    		return inSampleSize;
    	}

    除了BitmapFactory的decodedResource方法,其它三个decode系列的方法也是支持採样载入的。而且处理方式也是类似的。可是decodeStream方法略微特殊,后面会解说。


    Android中的缓存策略:


    缓存策略在Android中有着广泛的使用场景,尤其在图片载入这个场景下。缓存策略变得很重要。

    眼下经常使用的一种缓存算法是LRU,LRU是最近最少使用算法,他的核心思想是当缓存满时,会优先淘汰那些最近最少使用的缓存对象。採用LRU算法的缓存由两种:LruCache和DiskLruCache,前者用于实现内存缓存,而后者则充当了存储设备缓存。通过二者的结合就能够非常方便的实现一个非常高使用价值的ImageLoader。

    LruCache:

    LruCache是3.1所提供的一个缓存类。通过v4兼容包可以兼容到早起的版本号,为了可以兼容至2.2版本号。在使用LruCache的时候建议採用v4兼容包中提供的LruCache,而不要直接使用3.1提供的LruCache

    LruCache是一个泛型类。他内部採用一个LinkedHashMap以强引用的方法存储外界的缓存对象,其提供了get和put方法来完毕缓存的获取和加入操作。

    接下来简答说一个几个引用:


    强引用:     直接的对象引用

    软引用:    当一个对象仅仅有软引用存在时,系统内存不足时此对象会被gc回收。

    弱引用:     当一个对象仅仅有弱引用存在时,此对象会随时被gc回收。

    另外LruCache是线程安全的,由于他使用了LinkedHashMap:
    public class LruCache<K, V> {
        private final LinkedHashMap<K, V> map;

    功欲善事实上,必先利器。接下来我们就看看LruCache的使用,首先典型的初始化:

    int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
    		int cacheSize = maxMemory / 8;
    		mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
    			@Override
    			protected int sizeOf(String key, Bitmap bitmap) {
    				// TODO Auto-generated method stub
    				return bitmap.getRowBytes() * bitmap.getHeight() / 1024;
    			}
    		};

    仅仅须要提供缓存的总容量大小并重写sizeOf方法就可以。sizeOf方法的作用是计算缓存对象的大小。这里的大小的单位须要和总容量的单位保持一致。对于上面代码,总容量的大小为当前进程可用内存的1/8,单位为KB,sizeOf方法则完毕了Bitmap对象的大小计算。

    一些特殊情况。还须要重写LruCache的entryRemoved方法,LruCache移除旧缓存时会调用这种方法,因此能够在这种方法中完毕一些资源回收(假设须要的话)

    除了创建以外还有缓存的获取和加入。


    mMemoryCache.get(key);
    mMemoryCache.put(key, value);

    DiskLruCache:

    DiskLruCache用于实现存储设备缓存。即磁盘缓存。他通过对缓存对象写入文件系统从而实现缓存效果。其源代码地址:



    DiskLruCache的创建:

    DiskLruCache并不能通过构造方法来创建,他提供了open方法用于创建自身,例如以下所看到的:

    public static DiskLruCache open(File directory,int appVersion,int valueCount,long manSize)

    第一个參数表示磁盘缓存在文件系统中的存储路径,缓存路径能够选择SD卡上的缓存文件夹,详细是指/sdcard/Android/data/package_name/cache文件夹。当中package_name表示当前应用包名,当应用被卸载后。此文件夹会一并被删除。当然也能够选择SD卡上的其它指定文件夹,还能够选择data下的当前应用文件夹,详细可依据须要灵活设定。假设应用卸载后就希望删除缓存文件夹。那么就选择SD卡上的缓存文件夹,否则就选择SD卡上的其它文件夹。


    第二个參数表示应用的版本,一般设为1就可以。

    当版本发生变化时DiskLruCache会清空之前全部的缓存文件,而这个特性在实际开发中作用并不大,非常多情况下既是应用版本发生了变化缓存文件仍然是有效的,因此这个參数设为1比較好。


    第三个參数表示单个节点所相应的数据的个数,一般设为1就可以。

    第四个參数表示缓存的总大小,比方50MB,当缓存大小超出这个设定值后。DiskLruCache会清楚一些缓存从而保证总大小不大于这个设定值:

    private static final long DISK_CACHE_SIZE=1024*1024*50;
    File diskCacheDir=getDiskCacheDir(mContext,"bitmap");
    if(!diskCacheDir.exists()){
    	diskCacheDir.mkdir();
    }
    mDiskLruCache=DiskLruCache.open(diskCacheDir,1,1,DISK_CACHE_SIZE);


    DiskLruCache的缓存加入:
    DiskLruCache的缓存加入的操作是通过Editor完毕的。Editor表示一个缓存对象的编辑对象。这里仍以图片缓存举样例,首先须要获取图片url所相应的key,然后依据key就能够通过edit()来获取Editor对象,假设这个缓存正在被编辑,那么会返回null,即DiskLruCache不同意同一时候编辑一个缓冲对象,之所以要把url转换为key,是由于图片的url中非常可能有特殊字符,这将影响url在Android中直接使用,一般採用url的md5值作为key:

    private String hashKeyFormUrl(String url) {
    		String cacheKey;
    		try {
    			final MessageDigest mDigest = MessageDigest.getInstance("MD5");
    			mDigest.update(url.getBytes());
    			cacheKey = bytesToHexString(mDigest.digest());
    		} catch (Exception e) {
    			// TODO: handle exception
    			cacheKey = String.valueOf(url.hashCode());
    		}
    		return cacheKey;
    	}
    
    	private String bytesToHexString(byte[] digest) {
    		// TODO Auto-generated method stub
    		StringBuilder sb = new StringBuilder();
    		for (int i = 0; i < digest.length; i++) {
    			String hex = Integer.toHexString(0xFF & digest[i]);
    			if (hex.length() == 1) {
    				sb.append('0');
    			}
    			sb.append(hex);
    		}
    		return sb.toString();
    	}

    将图片的url转化为key以后,就能够获取Editor对象了,对于这个key来说,假设当前不存在其它Editor对象。那么edit()就会返回一个新的Editor对象,通过他就能够得到一个文件输出流。须要注意的是,因为前面在DiskLruCache的open方法中设置了一个子节点仅仅能有一个数据。因此以下的DISK_CACHE_INDEX常量直接设为0就可以:

    String key=hashKeyFormUrl(url);
    		DiskLruCache.Editor editor=mDiskLruCache.edit(key);
    		if(editor!=null){
    			OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
    		}

    有了文件输出流。接下来就是。从网络下载图片时,图片就能够通过这个文件输出流写入到文件系统上:

    public boolean downloadUrlToStream(String urlString,
    			OutputStream outputStream) {
    		HttpURLConnection urlConnection = null;
    		BufferedOutputStream out = null;
    		BufferedInputStream in = null;
    
    		try {
    			final URL url = new URL(urlString);
    			urlConnection = (HttpURLConnection) url.openConnection();
    			in = new BufferedInputStream(outputStream, IO_BUFFER_SIZE);
    
    			int b;
    			while ((b = in.read()) != -1) {
    				out.write(b);
    			}
    		} catch (Exception e) {
    			// TODO: handle exception
    		} finally {
    			if (urlConnection != null) {
    				urlConnection.disconnect();
    			}
    			MyUtils.close(out);
    			MyUtils.close(in);
    		}
    		return false;
    	}


    经过上面的步骤,事实上并没有真正的将图片写入文件系统,还必须通过Editor的commit()来提交写入操作,假设图片下载 过程发生了异常。那么还能够通过Editor的abort()来回退整个操作:

    OutputStream outputStream=editor.newOutputStream(DISK_CACHE_INDEX);
    		if(downloadUrlToStream(url,outputStream)){
    			editor.commit();
    		}else{
    			editor.abort();
    		}
    		mDiskLruCache.flush();
    经过上面的步骤。图片就已经被正确的写入到文件系统中了。接下来图片的获取操作就不须要请求网络了。



    DiskLruCache的缓存查找:
    和缓存加入过程类似,缓存找找过程也须要将url转换为key,然后通过DiskLruCache的get方法得到一个Snapshot对象,接着再通过Snapshot对象就可以得到缓存的文件输入流,有了文件输入流,自然就能够得到Bitmap对象了,为了避免载入图片过程中导致的OOM问题,一般不建议直接载入原始图片。

    可是BitmapFactory.Options的方式压缩图片对FileInputStream的缩放存在问题,原因是,FileInputStream是一种 有序的文件流,而两次decodeStream调用印象了文件流的位置属性。导致了第二次decodeStream时得到的是null。为了解决问题,能够通过文件流来得到他所相应的文件描写叙述符,然后再通过BitmapFactory.decodeFileDescriptor方法来载入一张缩放后的图片,代码例如以下:



    Bitmap bitmap = null;
    		String key = hashKeyFormUrl(url);
    		DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key);
    		if (snapShot != null) {
    			FileInputStream fileInputStream = (FileInputStream) snapShot
    					.getInputStream(DISK_CACHE_INDEX);
    			FileDescriptor fileDescriptor = fileInputStream.getFD();
    			bitmap = mImageResizer.decodeSampledBitmapFromFileDescriptor(
    					fileDescriptor, reqWidth, reqHeight);
    			if(bitmap!=null){
    				addBitmapToMemoryCache(key,bitmap);
    			}
    		}

    好了,就讲到这里,主要介绍了一些缓存策略和高效载入bitmap的方式。兴许会继续对于ImageLoader进行学习并解析。

  • 相关阅读:
    Vxlan基础理解
    ODPS基础
    关系型和非关系型数据库的区别?
    交换机的互连技术
    MYSQL 查看最大连接数和修改最大连接数
    Ceph添加/删除Mon(ceph.conf)
    java 线程的几个注解
    UML建模之类图
    单例模式的N种写法
    java工具jar包—Lombok
  • 原文地址:https://www.cnblogs.com/wgwyanfs/p/7249745.html
Copyright © 2020-2023  润新知