`
haliluya4
  • 浏览: 123751 次
  • 性别: Icon_minigender_1
  • 来自: 福州
社区版块
存档分类
最新评论

图片缓存(源于SDK文档)

阅读更多

接着之前的《一种异步加载资源的方法(源于SDK文档)》,SDK文档在《Caching Bitmaps》中介绍了内存缓存与磁盘缓存的使用。

大家都知道,现在的手机屏幕分辨率是越来越大了,虽然之前我们介绍了异步加载图片的方法。但要知道,一个应用可用的内存是有限的。我们不可能将所有的内存都用来存储图片,也不可能为了内存而每次取图片时都上网下载(流量费是很贵滴,而且下载也很耗电啊)。

因此,对于已下载的图片,我们需要在本地维持一个缓存。

内存缓存

LurCache是一个内存缓存类(Android3.1引入,通过v4的支援包,可以在API Level 4以上使用)。它使用一个强连接的LinkedHashMap,将使用频率最高的图片缓存在内存中。

PS:在这之前,最流行的缓存方式是使用SoftReference与WeakReference。但从Android2.3开始,垃圾回收对于软引用与弱引用来说,变得越来越积极了。这也就造成了软引用与弱引用的效率变得很低(没几下就被回收了,然后又得再创建,和没缓存没太大区别)。同时,在Android3.0之前,Bitmap的数据是储存在所谓的native内存区中(可以想象成是用C语言申请的内存,很难被垃圾回收自动释放)。这也造成了应用非常容易发生内存溢出。

当然,要想使用LurCache,我们需要给定一个缓存大小。而要想确定缓存占多少内存,需要考虑以下条件:

  • 应用的其他地方对内存的占用有多紧张?
  • 有多少图片会同时在屏幕上显示?在它们显示时,要确保多少的其余图片可以马上显示而无明显延迟?
  • 屏幕大小与密度如何?xhdpi的机子明显要比hdpi的机子需要更多的缓存。
  • 每张图片大概有多大?
  • 图片访问的频率是多少?有没有部分图片的访问频率远高于其他图片?如果是的话,你可能需要将这些图片进行缓存,甚至需要多个LurCache对象来缓存不同等级的图片。
  • 你可以平衡质量与数量吗?有的时候,存储大量低分辨率的图片更有用。你可以在后台任务中加载高分辨率的。

没有绝对合适的大小或计算方法,你必须根据自身应用的特点来确定相应的解决方案。缓存太小,反而会由于频繁的重新创建而降低效率。缓存太大,自然也同样会造成内存溢出了。

以下是一个使用LurCache的例子,使用八分之一的可用内存作为缓存。在一个普通的hdpi的机子上,大概是4MB以上。

在一个800x480的屏幕上的全屏GridView显示的图片,大概需要1.5MB (800*480*4 bytes)的内存。也就是说,这个例子里的缓存,可以保存至少2.5屏的图片。

    private LruCache<String, Bitmap> mMemoryCache;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // 获取可用内存的最大值,超过这个数,就会产生OutOfMemory.
        // 将其以KB为单位存储.
        final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
        // 使用八分之一的可用内存作为缓存.
        final int cacheSize = maxMemory / 8;
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {
            @Override
            protected int sizeOf(String key, Bitmap bitmap) {
                // 缓存大小以KB为单位进行计算.
                return bitmap.getByteCount() / 1024;
            }
        };
        ...
    }

    public void addBitmapToMemoryCache(String key, Bitmap bitmap) {
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
    }

    public Bitmap getBitmapFromMemCache(String key) {
        return mMemoryCache.get(key);
    }

接着《一种异步加载资源的方法(源于SDK文档)》的例子,在加载图片时,先去缓存里查一下。如果缓存里有,直接设上去就行了。

    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也需要更新一下,当图片获取到后,将其加入缓存。

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

 磁盘缓存

虽然内存缓存很有用,但光靠它还是不够的。像一些专门看图片的应用,里面的照片多了去了。很容易缓存就满了。或者当应用在后台被杀死时,内存缓存也会立刻清空。你还是得重新建立。

在这种情况下,就需要磁盘缓存来将图片保存到本地。这样,当内存缓存被清空时,可以通过磁盘缓存加快图片的加载。

以下的例子就是使用源码中提供的DiskLurCache来完成磁盘缓存的功能。

它并不是替代内存缓存,而是在内存缓存之外再额外备份了一次。

    private DiskLruCache mDiskLruCache;
    private final Object mDiskCacheLock = new Object();
    private boolean mDiskCacheStarting = true;
    private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB
    private static final String DISK_CACHE_SUBDIR = "thumbnails";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...
        // Initialize memory cache

        ...
        // Initialize disk cache on background thread
        File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);
        new InitDiskCacheTask().execute(cacheDir);
        ...
    }

    class InitDiskCacheTask extends AsyncTask<File, Void, Void> {
        @Override
        protected Void doInBackground(File... params) {
            synchronized (mDiskCacheLock) {
                File cacheDir = params[0];
                mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);
                mDiskCacheStarting = false; // Finished initialization
                mDiskCacheLock.notifyAll(); // Wake any waiting threads
            }
            return null;
        }
    }

    class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {
        ...
        // Decode image in background.
        @Override
        protected Bitmap doInBackground(Integer... params) {
            final String imageKey = String.valueOf(params[0]);
            // Check disk cache in background thread
            Bitmap bitmap = getBitmapFromDiskCache(imageKey);
            if (bitmap == null) {
                // Not found in disk cache
                // Process as normal
                final Bitmap bitmap = decodeSampledBitmapFromResource(
                        getResources(), params[0], 100, 100);
            }
            // Add final bitmap to caches
            addBitmapToCache(imageKey, bitmap);
            return bitmap;
        }
        ...
    }

    public void addBitmapToCache(String key, Bitmap bitmap) {
        // Add to memory cache as before
        if (getBitmapFromMemCache(key) == null) {
            mMemoryCache.put(key, bitmap);
        }
        // Also add to disk cache
        synchronized (mDiskCacheLock) {
            if (mDiskLruCache != null && mDiskLruCache.get(key) == null) {
                mDiskLruCache.put(key, bitmap);
            }
        }
    }

    public Bitmap getBitmapFromDiskCache(String key) {
        synchronized (mDiskCacheLock) {
            // Wait while disk cache is started from background thread
            while (mDiskCacheStarting) {
                try {
                    mDiskCacheLock.wait();
                } catch (InterruptedException e) {
                }
            }
            if (mDiskLruCache != null) {
                return mDiskLruCache.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 dir

        final String cachePath = Environment.MEDIA_MOUNTED.equals(Environment
                .getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(
                context).getPath() : context.getCacheDir().getPath();
        return new File(cachePath + File.separator + uniqueName);
    }

 

PS:即使是初始化磁盘缓存,也需要相应的磁盘操作。因此,使用了一个异步任务InitDiskCacheTask来进行。另外,为了防止在磁盘缓存建立成功前就去访问,在getBitmapFromDiskCache方法中,进行了wait操作。当磁盘缓存初始化后,如果提前访问了,相应的线程将被notifyAll唤醒。

处理Configuration Change

在程序运行时,我们经常会遇到Configuration Change,像横竖屏切换、语言改变等等。

而这些情况,往往会使得当前运行的Activity被销毁并重新创建。

为了防止在销毁与创建过程中重新建立缓存(耗时太久且影响效率,要是能直接保存就好了),我们可以通过Fragment。只要setRetainInstance(true)就行了。

如下图所示,在Activity的onCreate里,先判断相应的Fragment在不在FragmentManager里,要是在的话,直接获取相应的缓存对象。并且在Fragment的onCreate中setRetainInstance(true)。

private LruCache<String, Bitmap> mMemoryCache;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...
    RetainFragment mRetainFragment =
            RetainFragment.findOrCreateRetainFragment(getFragmentManager());
    mMemoryCache = RetainFragment.mRetainedCache;
    if (mMemoryCache == null) {
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize);
        ... // Initialize cache here as usual

        mRetainFragment.mRetainedCache = mMemoryCache;
    }
    ...
}

class RetainFragment extends Fragment {
    private static final String TAG = "RetainFragment";
    public LruCache<String, Bitmap> mRetainedCache;

    public RetainFragment() {
    }

    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);
        if (fragment == null) {
            fragment = new RetainFragment();
        }
        return fragment;
    }

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setRetainInstance(true);
    }
}

 

0
0
分享到:
评论

相关推荐

    EXCEL二次开发SDK文档 EXCEL二次开发SDK文档

    EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档...

    Android图片缓存管理(管理SDK卡例子)

    我们将以“Android图片缓存管理(管理SDK卡例子)”为例,讲解相关知识点。 首先,我们需要理解图片缓存的必要性。当用户浏览应用中的图片时,如果每次都需要从网络下载,不仅会消耗大量的数据流量,还会导致应用...

    海康NVR开发SDK,sdk开发文档

    这个SDK包含了必要的库文件、Python示例代码(demo)以及详细的SDK开发文档,帮助开发者快速理解和集成到自己的应用中。 一、SDK库文件: 这些库文件是SDK的核心部分,通常包括动态链接库(.dll或.so)和静态库(....

    ABB机器人PC_SDK文档

    ABB机器人PC_SDK文档是ABB公司提供的一个软件开发工具包,主要针对使用PC平台进行ABB机器人系统集成和自动化应用开发的工程师。这个SDK提供了一系列的接口、库函数和示例代码,帮助开发者创建与ABB机器人系统交互的...

    金蝶K3 SDK文档

    28个帮助文档,PDF格式,如:凭证接口参考手册、KFO组件参考手册、EBCGL组件参考手册、二次开发平台VBA参考、K3客户化开发工具包等。

    broadcom提供的BCM SDK培训文档

    ### Broadcom BCM SDK培训文档知识点概览 #### 一、SDK架构简介 Broadcom提供的BCM SDK是一套用于开发基于Broadcom芯片解决方案的应用程序的工具包。该SDK为开发者提供了丰富的资源和工具,帮助他们构建高性能、...

    ABB_pc_sdk帮助文档中文版.7z

    在下载并解压"ABB_pc_sdk帮助文档中文版.7z"后,用户需按照文档中的步骤进行安装和配置,确保所有必要的库和运行时环境都已正确设置。这可能涉及到环境变量的配置,以及Visual Studio等IDE的集成。 3. **API接口**...

    SDK文档.docx

    SDK 文档概述 本文档概述了语音 SDK 的设计文档,涵盖了 SDK 的详细设计和实现细节。SDK designer 为开发者提供了一份详细的设计文档,涵盖了从 Android 和 iOS 平台的工程导出到 SDK 对象的使用和设置监听接口的...

    ESP8266SDK文档.rar

    ESP8266SDK文档是针对ESP8266 WiFi模块的一套开发工具包,它提供了详尽的规格说明、编程指南以及实例演示,帮助开发者深入理解和应用ESP8266进行物联网(IoT)项目开发。以下是这些文档中涵盖的关键知识点: 1. **ESP...

    中控指纹仪Live20R开发包SDK文档和C#等多语言Demo

    《中控指纹仪Live20R开发包SDK文档与多语言Demo详解》 在现代信息技术领域,生物识别技术因其安全性高、唯一性好而被广泛应用,其中指纹识别是较为常见的一种。本文将深入探讨中控科技针对其Live20R指纹仪提供的...

    photoshop 2015 cc版的sdk文档

    `images`目录通常包含SDK文档中的示例图片和图标,这些视觉元素有助于开发者更好地理解各种功能和界面元素。开发者可以参考这些图片来设计自己的用户界面或插图。 最后,`pluginsdk`目录是SDK的核心部分,专注于...

    Android SDK 中文帮助文档

    这份“Android SDK 中文帮助文档”无疑是为开发者提供了方便,特别是对于那些中文阅读更为舒适的开发者,他们可以更高效地理解和掌握Android开发的各项技术。 文档可能涵盖了以下几个主要部分: 1. **API 文档**:...

    佳能相机SDK及说明文档

    包含佳能数码单反相机的开发SDK,内附完整的VC、VB、C#官方代码例子,以及官方API说明文档。

    Android SDK离线文档

    ### Android SDK离线文档知识点详解 #### 一、Android SDK离线文档概述 **Android SDK离线文档**是指为了方便开发者在没有网络连接的情况下查阅Android SDK API和其他相关文档而提供的本地文档包。这对于那些在...

    太空大战 完成windows SDK文档应用程序下的太空大战游戏设计与实现

    完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成...

    博通broadcom bcm sdk 的培训文档

    这份"博通bcm sdk 的培训文档"详细介绍了BCM SDK的代码架构和调用关系,对于深入理解SDK的运行机制至关重要。 一、BCM SDK概述 BCM SDK是一系列库、工具和示例代码的集合,它提供了与博通芯片交互所需的接口和功能...

    Blend4 中文 SDK文档

    这篇中文SDK文档是开发者理解和利用Blend4 API的关键资源。 Blend4 SDK的核心内容通常包括以下几个方面: 1. **API参考**:这部分详细介绍了Blend4的各种类、方法、属性和事件,让开发者知道如何在代码中调用和...

    intellij-sdk-docs, IntelliJ SDK平台文档.zip

    intellij-sdk-docs, IntelliJ SDK平台文档 IntelliJ平台SDK文档这是一个 IntelliJ平台SDK文档库的存储库。报告 Bug请告知你在文档和样本中发现的任何内容不一致,过时的材料,外观问题和其他缺陷,提交一个问题到 ...

    MS XML 6.0 SDK 文档

    MS XML 6.0 SDK 的文档,所有ms xml的东西都在里面了. The Document Object Model (DOM), a standard library of application programming interfaces (APIs) for accessing XML documents. Helper APIs to assist ...

    银联SDK以及文档

    2016年的银联SDK文档,可能是针对当时银联支付接口的技术规范和使用指南。这份文档通常会包含以下内容: 1. **SDK概述**:介绍SDK的基本功能、适用场景和系统需求,帮助开发者了解其核心价值。 2. **安装与配置**...

Global site tag (gtag.js) - Google Analytics