Android 中的位图图像

29

我有几个关于位图对象、内存和它们的一般分类的问题。

  1. 什么是内存或本地位图?
  2. 位图内存与堆内存有何不同?
4个回答

33

用于支持 Bitmap 对象的内存是使用本地代码 (malloc()) 分配的,而不是 Java 的 new 关键字。这意味着内存直接由操作系统管理,而不是 Dalvik 管理。

本地堆和 Dalvik 堆之间唯一的真正区别是 Dalvik 堆会进行垃圾回收,而本地堆不会。

但对于这些目的,两者之间没有太大区别。当您的 Bitmap 对象被垃圾回收时,它的析构函数将在本地堆中回收相关内存。

来源:


你知道这个 bug 是否被修复了吗?我在 Thunderbolt 上遇到了相同的问题。我正在回收位图,但它们从未清空本地堆上的任何空间,导致 oom 错误。 - Brian Griffey
5
为了更加清楚,上面的答案适用于Android 2.x及以下版本。从Android 3开始,位图实例会计入堆内存。这可以很容易地检查:在Android 2.x中创建位图几乎不会影响Java堆大小,而在Android 3.x中创建位图将会向Java堆中添加大量字节。 - Shivan Dragon
啊!你是说现在计数更直观了吗?那太好了——除了我们所有写了hacky代码来将Android堆与本地堆相加的人…… - Dave Dopson

33

这里有一个重要的细节:尽管位图像素在本地堆中分配,但Dalvik中的一些特殊技巧使其归入Java堆。这样做有两个原因:

(1) 控制应用程序分配内存的数量。如果没有计算,应用程序可以分配大量内存(因为位图对象本身非常小,但可以拥有任意大量的本地内存),超过16MB或24MB堆限制。

(2) 帮助确定何时进行垃圾回收。如果没有计算,您可以在100个位图对象上分配和释放引用;GC不会运行,因为这些对象很小,但它们实际上可能代表了大量兆字节的实际分配,现在无法及时进行GC。通过将这些分配计入Java堆,垃圾收集器将运行,因为它认为正在使用内存。

请注意,从许多方面来看,这是一个实现细节;未来很可能会发生变化,但这种基本行为以某种形式保持不变,因为这些都是管理位图分配的重要特性。


1
即使在将位图计入Java堆的手机上,本地malloc也可以分配超过16 MiB的内存。 - Dave Dopson
10
第一条评论是错误的,所有手机都会将位图分配计入Dalvik堆限制中。需要注意的主要变化是,从Android 3.0开始,实际上是在Dalvik堆中进行分配,因此您不再需要处理GC不会如应该那样积极运行的情况。 - hackbod
2
你在那一点上是正确的。然而,当处理回收的位图时,垃圾回收器表现得很低效。我发现除非在回收位图后手动运行GC,否则会出现不可预测的行为(包括下一个“加载”返回已回收位图的副本而不是实际从磁盘加载)。 - Dave Dopson
@hackbod...从Android 3.0开始,分配实际上是在Dalvik堆中完成的。那么,“recycle()”就没用了吗?据我所知,您并不能总是精确地按命令调用GC。 - S.D.

13

从实际部署情况来看,我发现以下设备:

  • 限制 Java 堆大小为 16 MiB(位图几乎是无限的)的设备。
  • 限制 Java 堆大小和本地位图存储之和为 16 MiB 的设备。
  • 限制 Java 堆大小为 24 MiB(位图几乎是无限的)的设备。
  • 限制 Java 堆大小和本地位图存储之和为 24 MiB 的设备。

24 MiB 通常用于高分辨率设备,并可以通过 Runtime.getRuntime().maxMemory() 进行检测。现在也有32MiB的设备,一些已经越狱的手机默认具有64MiB。以前,我曾多次因尝试弄清楚所发生的情况而迷惑不解。我认为所有设备都将位图计入堆限制中。但要对 Android 设备群体做出任何概括性的总结是极其困难的。

这是 Android 上非常麻烦且非常令人困惑的问题。这个限制及其行为文档不完整、复杂,极不直观。它们在不同设备和操作系统版本之间也会有所差异,并存在一些已知的 bug。问题的一部分在于限制并不精确 - 由于堆碎片化,你将在真正的限制之前遇到 OOM,必须保守地留下一两个 meg 的缓冲区。更糟糕的是,我有几台设备在出现 Java OOM 异常之前会发生本地 segfault(这完全是 Android 本身的 bug),这使得永远不超过限制变得更加重要,因为你甚至无法捕获本地崩溃。如需了解更多详情,请查看此帖子。在同一帖子中,我还解释了如何针对限制测量使用情况并避免崩溃。

Java 堆大小为 Runtime.getRuntime().totalMemory()。

没有简单的方法来测量本地位图存储的大小。整个本地堆可以通过 Debug.getNativeHeapAllocatedSize() 进行测量,但只有位图计入限制(我认为)。


由于任何人都可以发布自己的Android版本,因此似乎很有可能存在实现不同堆内存限制策略的版本,包括您上面描述的那些。例如,CyanogenMod允许用户自行设置堆限制,因此我不明白为什么不能有一个实现您所描述的限制策略的操作系统发布。我很想知道在这方面是否有任何官方指南。如果没有,那么我们就受制于那些操作系统变体的心情。 - Carl
@Carl。从我所看到的情况来看,这是纯粹的混乱。尽管通常情况下,mods和rooted phones往往会增加堆限制,一般为64M。如此严重地限制Java堆大小的决定是愚蠢的,并导致了可怕的用户体验。 - Dave Dopson
它确实迫使我们作为开发人员严格审视我们对内存的使用。我正在开发一个具有非常大数据集的应用程序,这个数据集必须完全驻留在堆上(因为存在密集搜索过程),而我的原始基于String数组的实现仅针对该数据就占用了15 MB的堆空间(数组中每个String引用占用32字节)。在接近许多当前设备仍然强制执行的24 MB极限时,我设计了一种自定义数据结构,仅需要3.5MB来保存完全相同的信息。现在我的应用程序甚至可以在16MB限制的设备上运行。 - Carl
5
@Carl - 我带了一个有18万个单词的字典,平均长度为10个字符(以Java字符串形式存储需要15M),然后压缩成了400k。问题在于很难减少位图内存,因为任何专有的存储格式都不兼容本地呈现代码。此外,在具有半GB物理内存的设备上人为地增加内存显得非常愚蠢,而且本地代码可以分配1GB+内存而不会引起操作系统的投诉。这只是惩罚Java开发者的一种方式。 - Dave Dopson

-1
我们可以在清单文件中使用android:largeheap="true"来增加堆大小。这将解决你的一些问题。

考虑到用户的电池寿命,请求大量堆内存来弥补内存分配中的一些错误显然不是一个解决方案。 - JuSchz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接