Android官方开发文档Training系列课程中文版:高效显示位图之位图缓存

原文地址:http://android.xsoftlab.net/training/displaying-bitmaps/cache-bitmap.html

往UI界面中加载单张图片的过程是很简单的,然而如果需要在某个时刻同时加载大量的图片,那么这事情就有些复杂了。在很多情况下,比如使用了ListView、GridView或者是ViewPager来展示一定数量的图片,在本质上这些情况下,屏幕的快速滑动会导致大量的图片被集中展示在屏幕上。

类似这样通过回收移除到屏幕之外的子View的组件会抑制内存的使用(也就是说它们本身不会滥用内存)。垃圾回收器还会释放你所加载的位图,假设你没有使用任何持久化引用的话。这真是极好的,但是为了保持流畅的UI效果,你可能需要在它们每次重新返回到屏幕的时候,对它们按照常规的方式重新处理。内存缓存及磁盘缓存可以在这里提供帮助,可以使这些组件快速的重新加载已经处理过的图片。

这节课将会讨论在加载多张图片的时候,如何通过使用内存缓存以及磁盘缓存来使UI界面更加流畅,响应速度更快。

使用内存缓存

内存缓存提供了一种快速访问位图的能力,不过这会花费宝贵的内存空间。类LruCache极其适合用来处理缓存图片的任务,它会将最近使用到的位图的引用存放在一个LinkedHashMap对象上,并会在超过内存设计大小之前将最后一个没有用到的成员给驱除。

Note: 在过去,使用SoftReference或者是WeakReference来缓存图片是最受欢迎的一种缓存方式,然而却并不推荐这么用。在Android 2.3之后,垃圾回收器对soft/weak引用的回收更加强制,这会使得这些引用几乎无效。此外,在Android 3.0之前,位图的字节数据被存储在本地内存中,可以预见这些数据是不会被释放的,这会导致程序很容易超过自身的内存限制,然后崩溃。

为了给LruCache选择合适的尺寸,有几个因素应该被考虑在内:

  • Activity或者程序在常规状态下的内存使用量是多少?
  • 在同一时间最多会有多少图片集中显示在屏幕上?有多少内存需要为准备显示到屏幕上的图片所用?
  • 设备屏幕的大小和尺寸分别是多少?在加载相同图片数量的情况下,像Galaxy Nexus这种超高的密度(xhdpi)的设备与Nexus S(hdpi)相比则需要更大的内存。
  • 图片的尺寸多大?配置是什么?加载这个位图的时候需要花费的内存是多少?
  • 图片的访问有多频繁?会比其它位图访问更频繁吗?如果是这样,可能你需要将它们永远保持在内存中了,或者甚至是有多个LruCache对象来为图片分组。
  • 你可以在数量与质量之间取得平衡吗?某些时候存储大量的低质图片是很有用处的,可能会潜在的存在一些后台任务来加载一些高质量的版本。

这里特别没有指定尺寸或者配置,不过这适用所有的应用程序,这取决于对内存使用情况的分析,并需要找到一个适合的解决方案。缓存设置的太小会导致无意义的额外开销,缓存设置的太大会再次引起java.lang.OutOfMemory异常,应该将大小设置为应用的常规内存使用量之外的剩余内存之间。

下面是使用LruCache缓存位图的一个例子:

private LruCache<String, Bitmap> mMemoryCache;
@Override
protected 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;mMemoryCache = 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) {mMemoryCache.put(key, bitmap);}
}
public Bitmap getBitmapFromMemCache(String key) {return mMemoryCache.get(key);
}

Note: 在这个例子中,有八分之一的内存被分配给了缓存。在正常的设备上(hdpi)这大概是4MB(32/8)左右。一个铺满了图片的GridView在全屏状态下的800*480的设备上所占的内存大概是1.5MB(800*480*4个字节),所以这可以在内存中存储大概2.5页的图像。

当加载一个位图到ImageView上的时候,首先要检查LruCache。如果发现了与之相匹配的,则会被用来立即更新到ImageView上,否则就会触发一个后台线程来处理图片:

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.@Overrideprotected Bitmap doInBackground(Integer... params) {final Bitmap bitmap = decodeSampledBitmapFromResource(getResources(), params[0], 100, 100));addBitmapToMemoryCache(String.valueOf(params[0]), bitmap);return bitmap;}...
}

使用磁盘缓存

内存缓存对于最近浏览过的图像的快速加载非常有用,然而却不能将所有的图像都存放在内存缓存中。像GridView这样的组件在加载大数据集的时候可以轻易的将内存缓存填满。程序在运行的过程中可能会被其它任务打断,比如一个来电,这时,在后台的任务可能就会被杀死,内存缓存也会被销毁。一旦用户返回了界面,那么程序就需要再次重新处理每张图片。

那么磁盘缓存在这些情况下就很有帮助了,它可以存储处理过的图片,并会辅助提升图片的加载时间,在图片不再在内存缓存中存在的时候。当然,在磁盘上获取一张图片要比内存中要慢,并且还需要开启单独的工作线程,这和从磁盘上读取数据的时间一样,都不可预估。

Note:ContentProvider可能更适合用来存放被缓存过的图像,如果这些图像的访问更加频繁的话,就像在相册应用中的情况一样。

从Android Source中更新的示例代码使用了一个DiskLruCache的实现。下面是个更新后的版本,它对已有的内存缓存增加了磁盘缓存:

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 threadFile cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR);new InitDiskCacheTask().execute(cacheDir);...
}
class InitDiskCacheTask extends AsyncTask<File, Void, Void> {@Overrideprotected Void doInBackground(File... params) {synchronized (mDiskCacheLock) {File cacheDir = params[0];mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE);mDiskCacheStarting = false; // Finished initializationmDiskCacheLock.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) {mMemoryCache.put(key, bitmap);}// Also add to disk cachesynchronized (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 threadwhile (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 dirfinal String cachePath =Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) ||!isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :context.getCacheDir().getPath();return new File(cachePath + File.separator + uniqueName);
}

Note:因为磁盘缓存的初始化需要磁盘操作,所以这个过程不应该放在UI线程中执行。然而,这也意味着在缓存初始化之前这是个访问的机会。为了做到这一点,需要有个lock对象来保证在缓存被初始化之前APP没有从磁盘缓存中读取数据。

内存缓存在UI线程中执行检查,磁盘缓存在后台线程中执行检查。磁盘操作绝不应该放入UI线程。当图像处理完毕后,最终被处理过的图片应当被添加到内存缓存及磁盘缓存中以便备用。

处理配置变更

如果在运行时发生了变更,比如屏幕的方向发生了改变,会引起Android销毁并重启运行中的Activity,你可能想要避免再一次处理图像,这样一旦配置发生了改变,可以使用户有一个流畅快速的用户体验。

幸运的是,你有一个非常赞的内存缓存方案:可以使用设置了setRetainInstance(true)的Fragment,它可以将缓存传入新的Activity实例。在activity重新创建的时候,这个被保留存在的Fragment会被重新附加在Activity上,你可以获得原先内存缓存的访问能力,这使得图像可以快速的被获得并被重新填充在ImageView对象中。

下面这个例子使用了引用LruCache的Fragment,并通过了配置更改的问题:

private LruCache<String, Bitmap> mMemoryCache;
@Override
protected void onCreate(Bundle savedInstanceState) {...RetainFragment retainFragment =RetainFragment.findOrCreateRetainFragment(getFragmentManager());mMemoryCache = retainFragment.mRetainedCache;if (mMemoryCache == null) {mMemoryCache = new LruCache<String, Bitmap>(cacheSize) {... // Initialize cache here as usual}retainFragment.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();fm.beginTransaction().add(fragment, TAG).commit();}return fragment;}@Overridepublic void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setRetainInstance(true);}
}

为了测试这项输出,试着在有和没有Fragment的情况下旋转设备。你应该会注意到这个过程几乎没有延迟。任何图像如果没有在内存缓存中找到,那么这就为磁盘缓存提供了用武之地,如果都没有的话,那么常规的处理方法就会出场。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/479129.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

论文浅尝 | ICLR2020 - 基于组合的多关系图卷积网络

论文笔记整理&#xff1a;吴锐&#xff0c;东南大学计算机学院硕士。来源&#xff1a;ICLR 2020链接&#xff1a;https://arxiv.org/pdf/1911.03082.pdf动机目前针对于GCN的研究大多数都关注在学习无向图的结点表示上&#xff0c;然而我们在研究中更常见的通常是多关系图&#…

Hades:移动端静态分析框架

只有通过别人的眼睛&#xff0c;才能真正地了解自己 ——《云图》 背景 作为全球最大的互联网 生活服务平台&#xff0c;美团点评近年来在业务上取得了飞速的发展。为支持业务的快速发展&#xff0c;移动研发团队规模也逐渐从零星的小作坊式运营&#xff0c;演变为千人级研发军…

GitHub超级火!任意爬取,超全开源爬虫工具箱

文 | 程序员GitHub最近国内一位开发者在 GitHub 上开源了个集众多数据源于一身的爬虫工具箱——InfoSpider&#xff0c;一不小心就火了&#xff01;&#xff01;&#xff01;有多火呢&#xff1f;开源没几天就登上GitHub周榜第四&#xff0c;标星1.3K&#xff0c;累计分支 172 …

Android官方开发文档Training系列课程中文版:高效显示位图之管理位图内存

原文地址&#xff1a;http://developer.android.com/training/displaying-bitmaps/manage-memory.html 除了在上一节中描述的步骤之外&#xff0c;还有一些细节上的事情可以促进垃圾回收器的回收及位图的复用。其推荐的策略取决于Android的目标版本。示例APP BitmapFun展示了如…

Pytorch与tensorflow模型转换

使用pytorch_pretrained_bert将tensorflow模型转化为pytorch模型&#xff1a;https://blog.csdn.net/sunyueqinghit/article/details/103458365/ bert_config.json bert_model.ckpt.data-00000-of-00001 bert_model.ckpt.index bert_model.ckpt.meta vocab.txt 比如&#xff…

LeetCode 413. 等差数列划分(DP)

1. 题目 一个数列的等差数列子数组有多少个。 A [1, 2, 3, 4]返回: 3, A 中有三个子等差数组: [1, 2, 3], [2, 3, 4] 以及自身 [1, 2, 3, 4]。2. 解题 状态公式 if(A[i]−A[i−1]A[i−1]−A[i−2]),thendp[i]dp[i−1]1,i>2if (A[i]-A[i-1] A[i-1]-A[i-2]) , \quad then \…

技术动态 | 针对复杂问题的知识图谱问答最新进展

本文转载自公众号&#xff1a;PaperWeekly。作者&#xff1a;付彬、唐呈光、李杨、余海洋、孙健单位&#xff1a;阿里巴巴达摩院小蜜Conversational AI团队背景介绍知识图谱问答&#xff08;KBQA&#xff09;利用图谱丰富的语义关联信息&#xff0c;能够深入理解用户问题并给出…

百度提出新冠高风险小区预警算法,AAAI21收录!

编&#xff1a;夕小瑶几个月前&#xff0c;小屋推送了一期上帝视角看新型冠状病毒&#xff08;COVID-19&#xff09;对公众出行影响的顶会论文解读——《这篇顶会paper&#xff0c;讲述了疫情期间憋疯的你和我》&#xff0c;这篇有趣的paper来自百度地图团队&#xff0c;发表在…

搜狗地图2016-Android-社招笔试题(包含Java基础部分)

下面是搜狗地图的社招笔试题&#xff0c;由于条件有限&#xff0c;全是手机拍的&#xff0c;请将就着看。另请忽略上面的答案&#xff0c;不一定准确。 大伙可在下方讨论答案&#xff0c;上方答案仅供参考&#xff0c;不一定准确。

机器学习常用的算法整理:线性回归、逻辑回归、贝叶斯分类、支持向量机、K-means聚类、决策树、随机森林以及常用的应用场景整理

什么是机器学习&#xff1f; 机器学习是计算机利用已有的数据(经验)得出了某种模型&#xff0c;并利用这些模型预测未来的一种方法。这个过程其实与人的学习过程极为相似&#xff0c;只不过机器是一个可以进行大维度数据分析而且可以不知疲倦地学习的“怪兽”而已。 具体的机器…

新一代数据库TiDB在美团的实践

1. 背景和现状 近几年&#xff0c;基于MySQL构建的传统关系型数据库服务&#xff0c;已经很难支撑美团业务的爆发式增长&#xff0c;这就促使我们去探索更合理的数据存储方案和实践新的运维方式。而随着分布式数据库大放异彩&#xff0c;美团DBA团队联合基础架构存储团队&#…

我在哥大读博的五年

文 | Mike Shou知乎&#xff08;ID&#xff1a;Showthem)本文已获作者授权&#xff0c;禁止二次转载0. 写在前面「 开始写这边总结的时候是三月&#xff0c;纽约成了疫情震中&#xff0c;看着新闻报道里的中央公园&#xff0c;中国城&#xff0c;第五大道&#xff0c;往事浮现&…

Android官方开发文档Training系列课程中文版:高效显示位图之在UI中展示位图

原文地址&#xff1a;http://android.xsoftlab.net/training/displaying-bitmaps/display-bitmap.html 这节课会将前面的知识点整合到一起&#xff0c;展示如何使用后台线程、位图缓存来加载多张图片到ViewPager或者GridView中&#xff0c;并会涉及并发处理及配置更改的相关知…

论文浅尝 | AAAI2020 - 多分量图卷积协同过滤方法

论文笔记整理&#xff1a;郝凯龙&#xff0c;南京大学硕士。来源&#xff1a;AAAI2020链接&#xff1a;https://arxiv.org/pdf/1911.10699.pdf动机推荐系统实际上是在做用户-商品二部图上的链路预测&#xff0c;仅仅用用户-商品之间的单一购买关系无法精确的进行描述为什么购买…

from torchcrf import CRF

报错CRF函数有问题&#xff0c;多了一个参数。通过源代码查找&#xff0c;发现两个torchcrf。通过pip list安装的时候是大写的TorchCRF&#xff0c;所以导入包的时候肯定也写大写的&#xff0c;没想到报错&#xff0c;后来改成全小写的就对了。

LeetCode 312. 戳气球(DP,难)

1. 题目 有 n 个气球&#xff0c;编号为0 到 n-1&#xff0c;每个气球上都标有一个数字&#xff0c;这些数字存在数组 nums 中。 现在要求你戳破所有的气球。每当你戳破一个气球 i 时&#xff0c;你可以获得 nums[left]∗nums[i]∗nums[right]nums[left] * nums[i] * nums[ri…

美团即时物流的分布式系统架构设计

本文根据美团资深技术专家宋斌在ArchSummit架构师峰会上的演讲整理而成。 背景 美团外卖已经发展了五年&#xff0c;即时物流探索也经历了3年多的时间&#xff0c;业务从零孵化到初具规模&#xff0c;在整个过程中积累了一些分布式高并发系统的建设经验。最主要的收获包括两点&…

Android官方开发文档Training系列课程中文版:OpenGL绘图之环境配置

原文地址&#xff1a;http://android.xsoftlab.net/training/graphics/opengl/index.html 引言 Android framework层为创建绚丽的功能性UI提供了大量的标准工具。然而&#xff0c;如果想要以更多方式来控制屏幕的绘制&#xff0c;或者在三维图形中绘制&#xff0c;那么就需要…

中文常用停用词表(哈工大停用词表、百度停用词表

中文常用停用词表&#xff08;哈工大停用词表、百度停用词表等&#xff1a;https://github.com/goto456/stopwords

论文浅尝 | AAAI2020 - 基于规则的知识图谱组合表征学习

论文笔记整理&#xff1a;康矫健&#xff0c;浙江大学计算机科学与技术系&#xff0c;硕士研究生。论文链接&#xff1a;https://arxiv.org/pdf/1911.08935.pdf发表会议&#xff1a;AAAI 2020Motivation现有的KG Embedding方法大部分仅关注每个三元组的结构化信息有部分的工作把…