接着之前的《一种异步加载资源的方法(源于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); } }
相关推荐
EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档EXCEL二次开发SDK文档...
我们将以“Android图片缓存管理(管理SDK卡例子)”为例,讲解相关知识点。 首先,我们需要理解图片缓存的必要性。当用户浏览应用中的图片时,如果每次都需要从网络下载,不仅会消耗大量的数据流量,还会导致应用...
这个SDK包含了必要的库文件、Python示例代码(demo)以及详细的SDK开发文档,帮助开发者快速理解和集成到自己的应用中。 一、SDK库文件: 这些库文件是SDK的核心部分,通常包括动态链接库(.dll或.so)和静态库(....
ABB机器人PC_SDK文档是ABB公司提供的一个软件开发工具包,主要针对使用PC平台进行ABB机器人系统集成和自动化应用开发的工程师。这个SDK提供了一系列的接口、库函数和示例代码,帮助开发者创建与ABB机器人系统交互的...
28个帮助文档,PDF格式,如:凭证接口参考手册、KFO组件参考手册、EBCGL组件参考手册、二次开发平台VBA参考、K3客户化开发工具包等。
在下载并解压"ABB_pc_sdk帮助文档中文版.7z"后,用户需按照文档中的步骤进行安装和配置,确保所有必要的库和运行时环境都已正确设置。这可能涉及到环境变量的配置,以及Visual Studio等IDE的集成。 3. **API接口**...
SDK 文档概述 本文档概述了语音 SDK 的设计文档,涵盖了 SDK 的详细设计和实现细节。SDK designer 为开发者提供了一份详细的设计文档,涵盖了从 Android 和 iOS 平台的工程导出到 SDK 对象的使用和设置监听接口的...
ESP8266SDK文档是针对ESP8266 WiFi模块的一套开发工具包,它提供了详尽的规格说明、编程指南以及实例演示,帮助开发者深入理解和应用ESP8266进行物联网(IoT)项目开发。以下是这些文档中涵盖的关键知识点: 1. **ESP...
《中控指纹仪Live20R开发包SDK文档与多语言Demo详解》 在现代信息技术领域,生物识别技术因其安全性高、唯一性好而被广泛应用,其中指纹识别是较为常见的一种。本文将深入探讨中控科技针对其Live20R指纹仪提供的...
`images`目录通常包含SDK文档中的示例图片和图标,这些视觉元素有助于开发者更好地理解各种功能和界面元素。开发者可以参考这些图片来设计自己的用户界面或插图。 最后,`pluginsdk`目录是SDK的核心部分,专注于...
这份“Android SDK 中文帮助文档”无疑是为开发者提供了方便,特别是对于那些中文阅读更为舒适的开发者,他们可以更高效地理解和掌握Android开发的各项技术。 文档可能涵盖了以下几个主要部分: 1. **API 文档**:...
包含佳能数码单反相机的开发SDK,内附完整的VC、VB、C#官方代码例子,以及官方API说明文档。
### Android SDK离线文档知识点详解 #### 一、Android SDK离线文档概述 **Android SDK离线文档**是指为了方便开发者在没有网络连接的情况下查阅Android SDK API和其他相关文档而提供的本地文档包。这对于那些在...
完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成windows SDK文档应用程序下的太空大战游戏设计与实现 太空大战 完成...
这篇中文SDK文档是开发者理解和利用Blend4 API的关键资源。 Blend4 SDK的核心内容通常包括以下几个方面: 1. **API参考**:这部分详细介绍了Blend4的各种类、方法、属性和事件,让开发者知道如何在代码中调用和...
intellij-sdk-docs, IntelliJ SDK平台文档 IntelliJ平台SDK文档这是一个 IntelliJ平台SDK文档库的存储库。报告 Bug请告知你在文档和样本中发现的任何内容不一致,过时的材料,外观问题和其他缺陷,提交一个问题到 ...
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 ...
2016年的银联SDK文档,可能是针对当时银联支付接口的技术规范和使用指南。这份文档通常会包含以下内容: 1. **SDK概述**:介绍SDK的基本功能、适用场景和系统需求,帮助开发者了解其核心价值。 2. **安装与配置**...
这是一个大纲型指导文档,从整体上对博通SDK架构进行介绍
包含翻译后的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; 使用方法:解压翻译后的...