Android批量加载图片OOM问题

Android批量加载图片OOM问题

  • 前言
  • 使用内存缓存
  • 使用磁盘缓存
  • 处理配置更改

前言

将单个位图加载到界面中非常简单,但如果您需要同时加载较大的一组图片,则操作起来会比较复杂。实际上,在许多情况下(比如使用 ListViewGridViewViewPager 等组件时),屏幕上的图片与可能很快会滚动到屏幕上的图片加起来,数量是无限的。

系统通过循环利用移出屏幕的子视图来限制此类组件对内存的占用。垃圾回收器假设您不会保留任何长期的引用,因此也会释放已加载的位图。这些都没有问题,但是为了确保能够快速、流畅地加载界面,您必须避免每次这些图片返回到屏幕上时都要处理这些图片。通常采用内存和磁盘缓存会有所帮助,因为这可以让组件快速重新加载经过处理的图片。

使用内存缓存

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

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

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

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

private LruCache<String, Bitmap> memoryCache;@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;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,否则会生成一个后台线程来处理图片:

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 这样拥有较大数据集的组件很容易将内存缓存填满。应用可能被其他任务(如电话)中断,而在后台时,应用可能会被终止,而内存缓存则会销毁。用户恢复操作后,应用必须重新处理每张图片。

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

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

private DiskLruCache diskLruCache;
private final Object diskCacheLock = new Object();
private boolean diskCacheStarting = 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 (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);
}

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

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

处理配置更改

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

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

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

private LruCache<String, Bitmap> memoryCache;@Override
protected 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 中。在内存缓存中找不到的图片有可能会在磁盘缓存中,如果不在,系统会照常处理它们。

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

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

相关文章

JDBC连接过程

JDBC连接过程 创建一个以JDBC连接数据库的程序&#xff0c;主要包含以下6个步骤&#xff1a; 1.加载数据库驱动 2.获取数据库连接 3.创建Statement 4.执行SQL 5.处理结果 6.关流&#xff0c;释放资源 1 加载JDBC驱动 在连接数据库之前&#xff0c;首先要加载对应数据库的驱动…

useGeneratedKeys=“true” keyProperty=“id”

useGeneratedKeys“true” keyProperty“id” 这个注解在xml文件的insert方法中&#xff0c;可以用于返回主键值&#xff1b; 并且useGeneratedKeys参数只针对 insert 语句生效&#xff0c;默认为 false&#xff1b;

Java - @JSONField和@JsonProperty注解

JSONField注解是阿里巴巴的fastjson框架中的注解&#xff0c;用于指定JSON字符串中的属性名和Java对象中的属性名之间的映射关系 JsonProperty注解是Jackson框架中的注解&#xff0c;用法类似于JSONField&#xff0c;也是指定JSON字符串中的属性名和Java对象中的属性名之间的映…

【STM32】1.8寸LCD显示实验

目录 一、硬件介绍 1. STM32F03C8T6 2. 1.8寸LCD 二、STM32CubeMX配置 1. 接口配置 ​编辑 2. 其他配置 三、LCD图片取模 1. 打开图片&#xff08;.bmp格式&#xff09; 2. 设置 3. 点击保存数组。 4. 将生成的数组复制到lcd_picture.h文件中。 四、代码测试&…

精通Nmap:网络扫描与安全的终极武器

一、引言 Nmap&#xff0c;即NetworkMapper&#xff0c;是一款开源的网络探测和安全审计工具。它能帮助您发现网络中的设备&#xff0c;并识别潜在的安全风险。在这个教程中&#xff0c;我们将一步步引导您如何有效地使用Nmap&#xff0c;让您的网络更加安全。 因为Nmap还有图…

顺序表详解(SeqList)

本文使用C语言进行顺序表的代码实现。 博主将使用代码和相关知识相结合的方式进行讲解&#xff0c;简单易懂&#xff0c;懵懂的大学生一听就会~ 顺序表是一种线性表的存储结构&#xff0c;它将数据元素存储在一段连续的存储空间中&#xff0c;每个元素占据一个存储单元&#x…

怎样使用Pyglet库给推箱子游戏画关卡地图

目录 pyglet库 画图事件 按键事件 程序扩展 关卡地图 pyglet库 是一个跨平台的Python多媒体库&#xff0c;提供了一个简单易用的接口来创建窗口、加载图像和视频、播放音频、处理用户输入事件以及进行2D图形绘制。特别适合用于游戏开发、视听应用以及其它需要高效图形渲染…

NestJS入门9:管道入门

前文参考 NestJS入门1&#xff1a;创建项目 NestJS入门2&#xff1a;创建模块 NestJS入门3&#xff1a;不同请求方式前后端写法 NestJS入门4&#xff1a;MySQL typeorm 增删改查 NestJS入门5&#xff1a;加入Swagger NestJS入门6&#xff1a;日志中间件 NestJS入门7&…

Unity数据持久化之PlayerPrefs

这里写目录标题 PlayerPrefs概述基本方法PlayerPrefs存储位置实践小项目反射知识补充数据管理类的创建反射存储数据----常用成员反射存储数据----List成员反射存储数据----Dictionary成员反射存储数据----自定义类成员反射读取数据----常用成员反射读取数据----List成员反射读取…

【C语言】中的位操作符和移位操作符,原码反码补码以及进制之间的转换

欢迎大家来到c语言知识小课堂&#xff0c;今天的知识点是操作符和进制 目录 一、进制之间的转化1、什么是二进制&#xff0c;八进制&#xff0c;十进制&#xff0c;十六进制2、进制之间的转化其他进制转化为十进制十进制转化为二进制二进制转化为八进制八进制转化为二进制二进…

力扣55. 跳跃游戏(动态规划)

Problem: 55. 跳跃游戏 文章目录 题目描述思路复杂度Code 题目描述 思路 我们将问题稍做转换每次求取当前位置可以走到的最远位置&#xff0c;在此基础上我们将最终判断是否能走出整个nums&#xff1b;同时我们要判断中途会不会遇到某个位置是0使得不能继续走下去 复杂度 时间…

【GO语言卵细胞级别教程】06.GO语言的字符串操作

【GO语言卵细胞级别教程】06.GO语言的字符串操作 温馨提示&#xff1a; 本文中使用的项目模块均是 【05.项目创建和函数讲解】 中创建的&#xff0c;具体如何创建项目&#xff0c;请参考 【GO语言卵细胞级别教程】05.项目创建和函数讲解 演示项目仓库&#xff1a;https://gi…

ArcGIS中查看栅格影像最大值最小值的位置

如果只是想大概获取栅格影像中最大值最小值的位置进行查看&#xff0c;可以不用编写程序获取具体的行列信息&#xff0c;只需要利用分类工具即可。 假设有一幅灰度影像数据&#xff0c;如下图所示。 想要查看最大值2116的大概位置在哪里&#xff0c;可以右击选择图层属性&…

k-邻近算法(kNN)

目录 k-近邻算法概述 k-近邻算法的一般流程 kNN算法伪代码 k-近邻算法概述 优点&#xff1a;精度高、对异常值不敏感、无数据输入假定 缺点&#xff1a;计算复杂度高、空间复杂度高 适用数据范围&#xff1a;数值型和标称型 k-近邻算法的一般流程 &#xff08;1&#x…

LeetCode 0105.从前序与中序遍历序列构造二叉树:分治(递归)——五彩斑斓的题解(若不是彩色的可以点击原文链接查看)

【LetMeFly】105.从前序与中序遍历序列构造二叉树&#xff1a;分治&#xff08;递归&#xff09;——五彩斑斓的题解&#xff08;若不是彩色的可以点击原文链接查看&#xff09; 力扣题目链接&#xff1a;https://leetcode.cn/problems/construct-binary-tree-from-preorder-a…

【hoare基础版】快速排序算法(1)

目录 交换排序 QuickSort快速排序 Hoare整体思路 图解分析 ​ Hoare版本代码 总代码 时间复杂度 交换排序 基本思想&#xff1a;所谓交换&#xff0c;就是根据序列中两个记录键值的比较结果来对换这两个记录在序列中的位置&#xff0c;交换排序的特点是&#xff1a;将键…

相机图像质量研究(38)常见问题总结:编解码对成像的影响--呼吸效应

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

【C#】List泛型数据集如何循环移动,最后一位移动到第一位,以此类推

欢迎来到《小5讲堂》 大家好&#xff0c;我是全栈小5。 这是《C#》系列文章&#xff0c;每篇文章将以博主理解的角度展开讲解&#xff0c; 特别是针对知识点的概念进行叙说&#xff0c;大部分文章将会对这些概念进行实际例子验证&#xff0c;以此达到加深对知识点的理解和掌握。…

有关JAVA中文乱码问题的解析及解决办法

大家好我是咕噜美乐蒂&#xff0c;很高兴又和大家见面了&#xff01;下面我就和大家一起来了解一下由于Java中文乱码问题比较复杂&#xff0c;需要从字符编码、文件编码、输入输出流等多个方面进行分析和解决。在本文中&#xff0c;我将详细介绍Java中文乱码问题的原因、解析方…

Arm的Cortex-A和Cortex-X内核架构对比

Arm的Cortex-A和Cortex-X内核系列都是基于ARM架构设计的高性能处理器核心&#xff0c;但它们在设计目标、市场定位以及性能优化上有所不同&#xff1a; ### Cortex-A系列 - **设计目标**&#xff1a;Cortex-A系列是为广泛的移动计算设备和服务器平台设计的通用处理器内核&…