android缓存图片LruCache和DiskLruCache

2024-05-11 04:08

本文主要是介绍android缓存图片LruCache和DiskLruCache,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

使用内存缓存LruCache

内存缓存可以提供对位图的快速访问,但代价是会占用宝贵的应用内存。LruCache 类(支持库中也提供了该类,最低可支持 API 级别 4)非常适合用于以下任务:缓存位图,将最近引用的对象保持在强引用的 LinkedHashMap 中,并且在缓存超出其指定大小之前移除上次使用时间最早的成员。

注意:过去,最常用的内存缓存实现是 SoftReference 或 WeakReference 位图缓存,但现在已不建议使用。从 Android 2.3(API 级别 9)开始,垃圾回收器会更积极地回收软引用/弱引用,导致它们效用不佳。此外,在 Android 3.0(API 级别 11)之前,位图的后备数据存储在原生内存中,该内存不会以可预测的方式释放,因此可能会导致应用短暂超出其内存限制并崩溃。

要为 LruCache 选择合适的大小,需要考虑多种因素,例如:

  • Activity 和/或应用的其余部分对内存的占用情况如何?
  • 一次会在屏幕上显示多少张图片?有多少张图片需要准备好随时可以显示在屏幕上?
  • 设备的屏幕尺寸和密度是多少?相比于 Nexus S (hdpi) 这样的设备,超高密度屏幕 (xhdpi) 设备(如 Galaxy Nexus)需要更大的缓存才能在内存中保存相同数量的图片。
  • 位图的尺寸和配置如何?每个位图会占用多少内存?
  • 图片的访问频率是多少?是否有一些图片的访问频率会高于其他图片?如果是这样,您可能需要将某些项始终保留在内存中,甚至为不同的位图组创建多个 LruCache 对象。
  • 能否在质量和数量之间取得平衡?有时,存储更多低质量的位图会更有用,这样做可能需要在另一个后台任务中加载更高质量的位图。

没有适合所有应用的特定大小或公式,您应该自行分析使用情况并找到适合的解决方案。缓存过小会产生额外的开销且没有任何好处,缓存过大又会造成 java.lang.OutOfMemory 异常并让应用的其余部分没有多少内存可用。

以下是为位图设置 LruCache 的示例:

JAVA

    private LruCache<String, Bitmap> memoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {...// 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;memoryCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap bitmap) {// The cache size will be measured in kilobytes rather than// number of items.return bitmap.getByteCount() / 1024;}};...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {if (getBitmapFromMemCache(key) == null) {memoryCache.put(key, bitmap);}}public Bitmap getBitmapFromMemCache(String key) {return memoryCache.get(key);}

 

注意:在本示例中,将八分之一的应用内存分配给了缓存。在普通/hdpi 设备上,此内存最少为 4MB(32/8)左右。在分辨率为 800x480 的设备上,填充了图片的全屏 GridView 大约会占用 1.5MB(800*480*4 字节)的内存,这会在内存中缓存至少 2.5 页的图片。

将位图加载到 ImageView 时,首先会检查 LruCache。如果找到条目,则会立即使用该条目来更新 ImageView,否则会生成一个后台线程来处理图片:

JAVA

    public void loadBitmap(int resId, ImageView imageView) {final String imageKey = String.valueOf(resId);final Bitmap bitmap = getBitmapFromMemCache(imageKey);if (bitmap != null) {mImageView.setImageBitmap(bitmap);} else {mImageView.setImageResource(R.drawable.image_placeholder);BitmapWorkerTask task = new BitmapWorkerTask(mImageView);task.execute(resId);}}

 

此外,还需要更新 BitmapWorkerTask 才能将条目添加到内存缓存:

JAVA

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);return bitmap;}...}

 

使用磁盘缓存DiskLruCache

内存缓存有助于加快对最近查看过的位图的访问,但您不能依赖于此缓存中保留的图片。GridView 这样拥有较大数据集的组件很容易将内存缓存填满。您的应用可能被其他任务(如电话)中断,而在后台时,应用可能会被终止,而内存缓存则会销毁。用户恢复操作后,您的应用必须重新处理每张图片。

在这些情况下,可以使用磁盘缓存来保存经过处理的位图,并在图片已不在内存缓存中时帮助减少加载时间。当然,从磁盘获取图片比从内存中加载缓慢,而且应该在后台线程中完成,因为磁盘读取时间不可预测。

注意:如果对缓存图片的访问频率较高(例如在图库应用中),则可能更适合将其存储在 ContentProvider 中。

这个类的代码示例使用了从 Android 源代码中提取的 DiskLruCache 实现。 以下是更新后的代码示例,该示例在现有的内存缓存之外又添加了一个磁盘缓存:

DiskLruCache文件下载地址:

https://github.com/JakeWharton/DiskLruCache/tree/master/src/main/java/com/jakewharton/disklrucache

JAVA

    private DiskLruCache diskLruCache;private final Object diskCacheLock = new Object();private boolean diskCacheStarting = true;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";@Overrideprotected void onCreate(Bundle savedInstanceState) {...// Initialize memory cache...// Initialize disk cache on background threadFile cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);new InitDiskCacheTask().execute(cacheDir);...}class InitDiskCacheTask extends AsyncTask<File, Void, Void> {@Overrideprotected Void doInBackground(File... params) {synchronized (diskCacheLock) {File cacheDir = params[0];diskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);diskCacheStarting = false; // Finished initializationdiskCacheLock.notifyAll(); // Wake any waiting threads}return null;}}class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {...// Decode image in background.@Overrideprotected Bitmap doInBackground(Integer... params) {final String imageKey = String.valueOf(params[0]);// Check disk cache in background threadBitmap bitmap = getBitmapFromDiskCache(imageKey);if (bitmap == null) { // Not found in disk cache// Process as normalfinal Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));}// Add final bitmap to cachesaddBitmapToCache(imageKey, bitmap);return bitmap;}...}public void addBitmapToCache(String key, Bitmap bitmap) {// Add to memory cache as beforeif (getBitmapFromMemCache(key) == null) {memoryCache.put(key, bitmap);}// Also add to disk cachesynchronized (diskCacheLock) {if (diskLruCache != null && diskLruCache.get(key) == null) {diskLruCache.put(key, bitmap);}}}public Bitmap getBitmapFromDiskCache(String key) {synchronized (diskCacheLock) {// Wait while disk cache is started from background threadwhile (diskCacheStarting) {try {diskCacheLock.wait();} catch (InterruptedException e) {}}if (diskLruCache != null) {return diskLruCache.get(key);}}return null;}// Creates a unique subdirectory of the designated app cache directory. Tries to use external// but if not mounted, falls back on internal storage.public static File getDiskCacheDir(Context context, String uniqueName) {// Check if media is mounted or storage is built-in, if so, try and use external cache dir// otherwise use internal cache dirfinal String cachePath =Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :context.getCacheDir().getPath();return new File(cachePath + File.separator + uniqueName);}

/**
     * 用hash将url转换成对应的key值
     * @param key
     * @return
     */
    public static String hashKeyForDisk(String key) {
        String cacheKey;
        try {
            final MessageDigest mDigest = MessageDigest.getInstance("MD5");
            mDigest.update(key.getBytes());
            cacheKey = bytesToHexString(mDigest.digest());
        } catch (NoSuchAlgorithmException e) {
            cacheKey = String.valueOf(key.hashCode());
        }
        return cacheKey;
    }
 
/**
     * 将字节数组 转换成 十六进制 的字符串
     * @param bytes 摘要内容
     * @return
     */
    private static String bytesToHexString(byte[] bytes) {
        // http://stackoverflow.com/questions/332079
        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();
    }

注意:即使是初始化磁盘缓存也需要执行磁盘操作,因此不应在主线程上执行。不过,这也意味着可能会在初始化之前访问该缓存。为了解决此问题,上述实现利用了一个 lock 对象来确保应用在磁盘缓存初始化之前不会从该缓存中读取数据。

虽然内存缓存是在界面线程中检查,但磁盘缓存会在后台线程中检查。界面线程上不应执行磁盘操作。图片处理完毕后,系统会将最终的位图同时添加到内存缓存和磁盘缓存中以供将来使用。

处理配置更改

运行时配置更改(例如屏幕方向更改)会导致 Android 销毁并使用新的配置重新启动正在运行的 Activity(有关此行为的更多信息,请参阅处理运行时更改)。您需要避免重新处理所有图片,以便用户在配置发生更改时能够获得快速、流畅的体验。

幸运的是,您在使用内存缓存部分构建了一个实用的位图内存缓存。您可以使用通过调用 setRetainInstance(true) 保留的 Fragment 将该缓存传递给新的 Activity 实例。重新创建 Activity 后,系统会重新附加这个保留的 Fragment,并且您将可以访问现有的缓存对象,从而能够快速获取图片并将其重新填充到 ImageView 对象中。

以下是使用 Fragment 在配置更改时保留 LruCache 对象的示例:

JAVA

    private LruCache<String, Bitmap> memoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {...RetainFragment retainFragment =RetainFragment.findOrCreateRetainFragment(getFragmentManager());memoryCache = retainFragment.retainedCache;if (memoryCache == null) {memoryCache = new LruCache<String, Bitmap>(cacheSize) {... // Initialize cache here as usual}retainFragment.retainedCache = memoryCache;}...}class RetainFragment extends Fragment {private static final String TAG = "RetainFragment";public LruCache<String, Bitmap> retainedCache;public RetainFragment() {}public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);if (fragment == null) {fragment = new RetainFragment();fm.beginTransaction().add(fragment, TAG).commit();}return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);}}

 

要对此进行测试,请尝试在保留和不保留 Fragment 的情况下旋转设备。在保留缓存的情况下,您几乎会看不到延迟,因为图片会立即从内存填充到 Activity 中。在内存缓存中找不到的图片有可能会在磁盘缓存中,如果不在,系统会照常处理它们。

这篇关于android缓存图片LruCache和DiskLruCache的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/978458

相关文章

Java实现将HTML文件与字符串转换为图片

《Java实现将HTML文件与字符串转换为图片》在Java开发中,我们经常会遇到将HTML内容转换为图片的需求,本文小编就来和大家详细讲讲如何使用FreeSpire.DocforJava库来实现这一功... 目录前言核心实现:html 转图片完整代码场景 1:转换本地 HTML 文件为图片场景 2:转换 H

Java实现在Word文档中添加文本水印和图片水印的操作指南

《Java实现在Word文档中添加文本水印和图片水印的操作指南》在当今数字时代,文档的自动化处理与安全防护变得尤为重要,无论是为了保护版权、推广品牌,还是为了在文档中加入特定的标识,为Word文档添加... 目录引言Spire.Doc for Java:高效Word文档处理的利器代码实战:使用Java为Wo

基于C#实现PDF转图片的详细教程

《基于C#实现PDF转图片的详细教程》在数字化办公场景中,PDF文件的可视化处理需求日益增长,本文将围绕Spire.PDFfor.NET这一工具,详解如何通过C#将PDF转换为JPG、PNG等主流图片... 目录引言一、组件部署二、快速入门:PDF 转图片的核心 C# 代码三、分辨率设置 - 清晰度的决定因

Python从Word文档中提取图片并生成PPT的操作代码

《Python从Word文档中提取图片并生成PPT的操作代码》在日常办公场景中,我们经常需要从Word文档中提取图片,并将这些图片整理到PowerPoint幻灯片中,手动完成这一任务既耗时又容易出错,... 目录引言背景与需求解决方案概述代码解析代码核心逻辑说明总结引言在日常办公场景中,我们经常需要从 W

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

使用Spring Cache本地缓存示例代码

《使用SpringCache本地缓存示例代码》缓存是提高应用程序性能的重要手段,通过将频繁访问的数据存储在内存中,可以减少数据库访问次数,从而加速数据读取,:本文主要介绍使用SpringCac... 目录一、Spring Cache简介核心特点:二、基础配置1. 添加依赖2. 启用缓存3. 缓存配置方案方案

使用Python实现无损放大图片功能

《使用Python实现无损放大图片功能》本文介绍了如何使用Python的Pillow库进行无损图片放大,区分了JPEG和PNG格式在放大过程中的特点,并给出了示例代码,JPEG格式可能受压缩影响,需先... 目录一、什么是无损放大?二、实现方法步骤1:读取图片步骤2:无损放大图片步骤3:保存图片三、示php

Java实现本地缓存的四种方法实现与对比

《Java实现本地缓存的四种方法实现与对比》本地缓存的优点就是速度非常快,没有网络消耗,本地缓存比如caffine,guavacache这些都是比较常用的,下面我们来看看这四种缓存的具体实现吧... 目录1、HashMap2、Guava Cache3、Caffeine4、Encache本地缓存比如 caff

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按

Java使用Thumbnailator库实现图片处理与压缩功能

《Java使用Thumbnailator库实现图片处理与压缩功能》Thumbnailator是高性能Java图像处理库,支持缩放、旋转、水印添加、裁剪及格式转换,提供易用API和性能优化,适合Web应... 目录1. 图片处理库Thumbnailator介绍2. 基本和指定大小图片缩放功能2.1 图片缩放的