Android通过两级缓存加载图片
来源:互联网 发布:word vba编程代码大全 编辑:程序博客网 时间:2024/06/05 10:55
Android通过两级缓存加载图片
前一段时间刚好学习用两级缓存(LruCache和DiskLruCache)加载图片问题,在书上看到一个解决方案思路很清晰易懂,在这里记录一下。
整体思路:
当程序从网络加载图片后,为了节省流量,将其缓存到存储设备上,为了提高用户体验通常还会缓存一份到内存,这样当程序再次请求图片时,首先从内存获取,如果内存中没有就从存储设备中获取,如果存储设备中也没有再从网络上获取。
一、采样率
首先在加载图片的时候涉及到一个问题,就是图片尺寸问题,很多时候ImageView并没有图片原始的尺寸那么大,所以把整个图片加载进来再给ImageView是没有必要的,通过BitmapFactory.Options可以按一定的采样率来加载缩小后的图片,这样就会降低内存的占用,从而在一定程度上避免OOM。
下面是计算采样率的代码,相关解释已经都写在了注释中了:
public class ImageResizer { private static final String TAG = "ImageResizer"; /** * 该方法主要用到了BitmapFactory.Options中的采样率inSampleSize参数 * inSampleSize = 1时,采样后图片大小为图片的原始大小; * inSampleSize = 2时,采样后的图片宽高均为原图的1/2,像素数为原图的1/4,占用内存也是原来的1/4; * inSampleSize < 1时,作用相当于1,即无缩放效果; * inSampleSize的取值总是2的指数,如果传入的值不是2的指数, * 系统会向下取整并选择一个接近2的指数代替,但不是所有的android版本都成立; * @param res 资源值 * @param resId 需要加载的图片id * @param reqWidth 需要加载的图片宽度 * @param reqHeight 需要加载的图片高度 * @return 根据实际需要缩放后的图片 */ public static Bitmap decodeSampledBitmapFromResource( Resources res, int resId, int reqWidth, int reqHeight) { //通过BitmapFactory.Options来加载所需尺寸的图片 final BitmapFactory.Options options = new BitmapFactory.Options(); /** *当inJustDecodeBounds=true时, * BitmapFactory只会解析图片的原始宽/高信息, * 不会真正加载图片 */ options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); //计算inSampleSize options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); } public Bitmap decodeSampleBitmapFromFielDescriptor( FileDescriptor fd, int reqWidth, int reqHeight){ final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeFileDescriptor(fd, null, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeFileDescriptor(fd, null, options); } /** * 根据原始尺寸和需要尺寸计算一个合适的采样率 * @param options * @param reqWidth * @param reqHeight * @return */ 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 halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) >= reqHeight && (halfWidth / inSampleSize) >= reqWidth) { inSampleSize *= 2; } }*/ while (height/inSampleSize > reqHeight && width/inSampleSize > reqWidth){ inSampleSize *= 2; } return inSampleSize; }}
二、两级缓存的使用
1、LruCache
LruCache是Android3.1提供的一个缓存类,通过support-v4兼容包可以兼容到早期版本,不过现在Android3.1以下的版本好像几乎没有了。。。。,LruCache是泛型类,其内部采用LinkedHashMap以强引用的方式存储外界缓存对象,如果不了解强引用、软引用、弱引用、虚引用的请自行google/百度。
/* LruCache初始化*///当前进程可用内存,单位KBint maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);//设置缓存大小为当前进程可用内存的1/8int cacheSize = maxMemory / 8;mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight() / 1024; }};
2、DiskLruCache
DiskLruCache不属于android sdk的一部分,所以需要自行下载源码并稍微做一下改动就可使用,后面我会附上我使用的源码。
/*DiskLruCache初始化*/File diskCacheDir = getDiskCacheDir(mContext, "bitmap");//如果目录不存在则创建目录if (!diskCacheDir.exists()) { diskCacheDir.mkdirs();}if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { e.printStackTrace(); }}
完整代码如下:
public class ImageLoader { private static final String TAG = "ImageLoader"; public static final int MESSAGE_POST_RESULT = 1; private static final int CPU_COUNT = Runtime.getRuntime().availableProcessors(); private static final int CORE_POOL_SIZE = CPU_COUNT + 1; private static final int MAXIMUM_POOL_SIZE = CPU_COUNT * 2 + 1; private static final long KEEP_ALIVE = 10L; /*指定的密钥应该是在应用程序的资源中声明的id, 以确保它是唯一的(请参阅ID资源类型)。 标识为属于Android框架的键或与任何程序包无关的键将导致IllegalArgumentException被抛出*/ private static final int TAG_KEY_URI = R.id.imageloader_uri; private static final long DISK_CACHE_SIZE = 1024 * 1024 * 50; private static final int IO_BUFFER_SIZE = 8 * 1024; private static final int DISK_CACHE_INDEX = 0; private boolean mIsDiskLruCacheCreated = false; private static final ThreadFactory sThreadFactory = new ThreadFactory() { private final AtomicInteger mCount = new AtomicInteger(1); @Override public Thread newThread(@NonNull Runnable r) { return new Thread(r, "ImageLoader#" + mCount.getAndIncrement()); } }; public static final Executor THREAD_POOL_EXECUTOR = new ThreadPoolExecutor( CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(), sThreadFactory); private Context mContext; private ImageResizer mImageResizer = new ImageResizer(); private LruCache<String, Bitmap> mMemoryCache; //内部实现是LinkedHashMap private DiskLruCache mDiskLruCache; private Handler mMainHandler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(Message msg) { LoaderResult result = (LoaderResult) msg.obj; ImageView imageView = result.imageView; String uri = (String) imageView.getTag(TAG_KEY_URI); if (result.uri.equals(uri)) { imageView.setImageBitmap(result.bitmap); } else { Log.w(TAG, "bitmap的uri被修改"); } } }; private ImageLoader(Context context) { mContext = context.getApplicationContext(); /* LruCache初始化 */ //当前进程可用内存,单位KB int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); //设置缓存大小为当前进程可用内存的1/8 int cacheSize = maxMemory / 8; mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { @Override protected int sizeOf(String key, Bitmap value) { return value.getRowBytes() * value.getHeight() / 1024; } }; /* DiskLruCache初始化 */ File diskCacheDir = getDiskCacheDir(mContext, "bitmap"); //如果目录不存在则创建目录 if (!diskCacheDir.exists()) { diskCacheDir.mkdirs(); } if (getUsableSpace(diskCacheDir) > DISK_CACHE_SIZE) { try { mDiskLruCache = DiskLruCache.open(diskCacheDir, 1, 1, DISK_CACHE_SIZE); mIsDiskLruCacheCreated = true; } catch (IOException e) { e.printStackTrace(); } } } /** * 创建一个ImageLoader实例 * * @param context * @return */ public static ImageLoader build(Context context) { return new ImageLoader(context); } public void bindBitmap(final String uri, final ImageView imageView) { bindBitmap(uri, imageView, 0, 0); } /** * 给ImageView添加相应的图片 * * @param uri * @param imageView * @param reqWidth * @param reqHeight */ public void bindBitmap( final String uri, final ImageView imageView, final int reqWidth, final int reqHeight) { imageView.setTag(TAG_KEY_URI, uri); Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { imageView.setImageBitmap(bitmap); return; } Runnable loadBitmapTask = new Runnable() { @Override public void run() { Bitmap bitmap = loadBitmap(uri, reqWidth, reqHeight); if (bitmap != null) { LoaderResult result = new LoaderResult(imageView, uri, bitmap); mMainHandler.obtainMessage(MESSAGE_POST_RESULT, result).sendToTarget(); } } }; THREAD_POOL_EXECUTOR.execute(loadBitmapTask); } /** * 从内存、硬盘或者网络中加载图片 * 如果内存中能找到,返回bitmap, * 如果内存中找不到,从硬盘中加载; * 如果硬盘中能找到,返回bitmap, * 如果硬盘中找不到,从网络中加载; * 如果创建了磁盘缓存,则将图片下载到硬盘中, * 如果没有创建磁盘缓存,则直接将图片加载到内存中 * * @param uri * @param reqWidth * @param reqHeight * @return */ public Bitmap loadBitmap(String uri, int reqWidth, int reqHeight) { Bitmap bitmap = loadBitmapFromMemCache(uri); if (bitmap != null) { return bitmap; } try { bitmap = loadBitmapFromDiskCache(uri, reqWidth, reqHeight); if (bitmap != null) { return bitmap; } bitmap = loadBitmapFromHttp(uri, reqWidth, reqHeight); } catch (IOException e) { e.printStackTrace(); } if (bitmap == null && !mIsDiskLruCacheCreated) { downloadBitmapFromUrl(uri); } return bitmap; } /** * 从根据图片的哈希码内存缓存中取出图片 * * @param key * @return */ private Bitmap getBitmapFromMemCache(String key) { return mMemoryCache.get(key); } /** * 如果内存缓存中没有图片,向其中加入该图片 * * @param key * @param bitmap */ private void addBitmapToMemCache(String key, Bitmap bitmap) { if (getBitmapFromMemCache(key) == null) { mMemoryCache.put(key, bitmap); } } /** * 从内存缓存中加载图片 * * @param url * @return */ private Bitmap loadBitmapFromMemCache(String url) { final String key = hashKeyFormString(url); return getBitmapFromMemCache(key); } /** * 从磁盘缓存中加载图片 * * @param url * @param reqWidth * @param reqHeight * @return * @throws IOException */ private Bitmap loadBitmapFromDiskCache( String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { Log.w(TAG, "不推荐使用UI线程加载图片"); } if (mDiskLruCache == null) { return null; } Bitmap bitmap = null; String key = hashKeyFormString(url); DiskLruCache.Snapshot snapShot = mDiskLruCache.get(key); if (snapShot != null) { FileInputStream fileInputStream = (FileInputStream) snapShot.getInputStream(DISK_CACHE_INDEX); FileDescriptor fileDescriptor = fileInputStream.getFD(); bitmap = mImageResizer.decodeSampleBitmapFromFielDescriptor( fileDescriptor, reqWidth, reqHeight); if (bitmap != null) { addBitmapToMemCache(key, bitmap); } } return bitmap; } /** * 从网络中加载图片 * * @param url 图片地址 * @param reqWidth 所需宽度 * @param reqHeight 所需高度 * @return 所需的图片 * @throws IOException */ private Bitmap loadBitmapFromHttp(String url, int reqWidth, int reqHeight) throws IOException { if (Looper.myLooper() == Looper.getMainLooper()) { throw new RuntimeException("不能在主线程中进行联网操作!"); } if (mDiskLruCache == null) { return null; } String key = hashKeyFormString(url); DiskLruCache.Editor editor = mDiskLruCache.edit(key); if (editor != null) { OutputStream outputStream = editor.newOutputStream(DISK_CACHE_INDEX); if (downloadUrlToStream(url, outputStream)) { editor.commit(); } else { editor.abort(); } mDiskLruCache.flush(); } return loadBitmapFromDiskCache(url, reqWidth, reqHeight); } /** * 将网络图片通过输出流输出 * * @param urlString 需要下载图片的地址 * @param outputStream 输出流 * @return 下载成功返回真,否则返回假 */ 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(urlConnection.getInputStream(), IO_BUFFER_SIZE); out = new BufferedOutputStream(outputStream, IO_BUFFER_SIZE); int n; while ((n = in.read()) != -1) { out.write(n); } return true; } catch (IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (in != null) { in.close(); } if (out != null) { out.close(); } } catch (IOException e) { e.printStackTrace(); } } return false; } /** * 直接将网络中的图片加载到内存中,赋值给bitmap变量 * * @param urlString 网络图片的地址 * @return 返回下载的图片 */ private Bitmap downloadBitmapFromUrl(String urlString) { Bitmap bitmap = null; HttpURLConnection urlConnection = null; BufferedInputStream in = null; try { final URL url = new URL(urlString); urlConnection = (HttpURLConnection) url.openConnection(); in = new BufferedInputStream(urlConnection.getInputStream(), IO_BUFFER_SIZE); bitmap = BitmapFactory.decodeStream(in); } catch (IOException e) { e.printStackTrace(); } finally { if (urlConnection != null) { urlConnection.disconnect(); } try { if (in != null) { in.close(); } } catch (IOException e) { e.printStackTrace(); } } return bitmap; } /** * 获取储存图片的路径,如果挂载了sd卡则目录为sd卡的路径,否则用缓存 * * @param context 上下文 * @param uniqueName 文件名 * @return 返回文件 */ public File getDiskCacheDir(Context context, String uniqueName) { //判断是否挂载sd卡 boolean externalStorageAvailable = Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); final String cachePath; if (externalStorageAvailable) { //如果挂载了sd卡,则使用sd卡的路径 cachePath = context.getExternalCacheDir().getPath(); } else { //没有挂载sd卡,使用缓存中的路径 cachePath = context.getCacheDir().getPath(); } return new File(cachePath + File.separator + uniqueName); } /** * 得到可用空间的大小 * * @param path 文件 * @return 可用空间大小 */ private long getUsableSpace(File path) { //当api<9时使用如下方法 if (Build.VERSION.SDK_INT < Build.VERSION_CODES.GINGERBREAD) { final StatFs statFs = new StatFs(path.getPath()); return (long) statFs.getBlockSize() * (long) statFs.getAvailableBlocks(); } return path.getUsableSpace(); } /** * 将字符串转换成MD5码 * * @param str 待转换的字符串 * @return 转换成功返回MD5码,否则返回哈希码 */ private String hashKeyFormString(String str) { String cacheKey; try { final MessageDigest mDigest = MessageDigest.getInstance("MD5"); mDigest.update(str.getBytes()); cacheKey = bytesToHexString(mDigest.digest()); } catch (NoSuchAlgorithmException e) { cacheKey = String.valueOf(str.hashCode()); } return cacheKey; } /** * 将字节数组转换成16进制形式字符串 * * @param bytes 字节数组 * @return 16进制形式字符串 */ private String bytesToHexString(byte[] bytes) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < bytes.length; i++) { String hex = Integer.toHexString(0xff & bytes[i]); if (hex.length() == 1) { sb.append('0'); } sb.append(hex); } return sb.toString(); } private static class LoaderResult { public ImageView imageView; public String uri; public Bitmap bitmap; public LoaderResult(ImageView imageView, String uri, Bitmap bitmap) { this.imageView = imageView; this.uri = uri; this.bitmap = bitmap; } }}
附件下载:
DiskLruCache源码
阅读全文
0 0
- Android通过两级缓存加载图片
- Android 图片缓存、加载器
- Android 图片缓存、加载器
- android 图片加载+缓存技术
- Android图片加载缓存类
- android图片加载与缓存
- android异步图片加载中的图片缓存
- Android图片加载优化--图片缓存
- android 新闻图片加载,缓存处理
- android 之图片异步加载,带缓存。
- android 图片缓存 异步加载 简要介绍
- Android异步加载网络图片 + 双缓存
- android 异步加载网络图片缓存机制
- android加载图片优化(三级缓存)
- Android图片加载缓存库<1>
- Android图片加载缓存库<2>
- Android图片加载缓存库<3>
- Android 图片如何高效加载与缓存
- SQL Select语句完整的执行顺序
- ubuntu 12.04只能以客户会话模式进行访问解决
- 插入排序
- 解决手机Android Studio安装app 报错的问题It is possible that this issue is resolved by uninstalling an existi
- Android颜色透明度(不透明度)计算
- Android通过两级缓存加载图片
- C#获取目录及子目录文件名,文件流写入txt
- Swift: 获取系统字体
- Git 仓库
- 前端学习笔记1:HTML文件基本结构
- dp与px,sp的安卓转换。
- JS回调函数原理及用法
- Maven添加自定义jar包
- Coderforces Boy or Girl