`
haliluya4
  • 浏览: 123453 次
  • 性别: 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客户化开发工具包等。

    ABB_pc_sdk帮助文档中文版.7z

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

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

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

    SDK文档.docx

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

    ESP8266SDK文档.rar

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

    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文档应用程序下的太空大战游戏设计与实现 太空大战 完成...

    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. **安装与配置**...

    博通SDK指导文档

    这是一个大纲型指导文档,从整体上对博通SDK架构进行介绍

    aliyun-sdk-oss-3.11.2-API文档-中文版.zip

    包含翻译后的API文档:aliyun-sdk-oss-3.11.2-javadoc-API文档-中文(简体)版.zip; Maven坐标:com.aliyun.oss:aliyun-sdk-oss:3.11.2; 标签:aliyun、oss、sdk、中文文档、jar包、java; 使用方法:解压翻译后的...

Global site tag (gtag.js) - Google Analytics