Android|Android 性能优化之——高性能使用图片全面总结

Android 性能优化之——高性能使用图片全面总结 移动设备的系统资源有限,所以应用应该尽可能的降低内存的使用。在应用运行过程中,Bitmap (图片)往往是内存占用最大的一个部分,Bitmap 图片的加载和处理,通常会占用大量的内存空间,所以在操作 Bitmap 时,应该尽可能的小心。
Bitmap 会消耗很多的内存,特别是诸如照片等内容丰富的大图。例如,一个手机拍摄的 2700 * 1900 像素的照片,需要 5.1M 的存储空间,但是在图像解码配置 ARGB_8888(Android 2.3开始的默认配置)时,它加载到内存需要 19.6M 内存空间(2592 * 1936 * 4 bytes),从而迅速消耗掉该应用的剩余内存空间。
OOM 的问题也是我们常见的严重问题,OOM 的产生的一个主要场景就是在大图片分配内存的时候产生的,如果 APP 可用内存紧张,这时加载了一张大图,内存空间不足以分配该图片所需要的内存,就会产生 OOM,所以控制图片的高效使用是必备技能。
那么,我们如何能减少图片的内存占用呢?
大图的内存优化 首先,我们来看大图的处理,大图会占用大量的内存空间,通常使用不当就会造成各种内存问题。
如何能减少大图的内存占用,从而高效的使用大图呢?
图片尺寸的优化 通常在大多数情况下,图片的实际大小都比需要呈现的尺寸大很多,例如,我们的原图是一张 2700 * 1900 像素的照片,加载到内存就需要 19.6M 内存空间,但是,我们需要把它展示在一个列表页中,组件可展示尺寸为 270 * 190,这时,我们实际上只需要一张原图的低分辨率的缩略图即可(与图片显示所对应的 UI 控件匹配),那么实际上 270 * 190 像素的图片,只需要 0.2M 的内存即可。
Android|Android 性能优化之——高性能使用图片全面总结
文章图片
我们可以看到,优化前后相差了 98 倍,原来显示 1 张,现在可以显示 98 张图片,效果非常显著。
既然在对原图缩放可以显著减少内存大小,那么我们应该如何操作呢?先加载到内存,再进行操作吗,可以如果先加载到内存,好像也不太对,这样只接占用了 19.6M + 0.2M 2份内存了,而我们想要的是,在原图不加载到内存中,只接将缩放后的图片加载到内存中,可以实现吗?
将缩略图直接加载到内存 现在来探讨怎么才能将缩略后的最终图片加载到内存,这样我们就不用先加载原图了,从而满足了我们节省内存的目的。要想直接加载缩略后的图片,首先我们应该知道原图的尺寸,才能判断是否需要缩小,以及缩小多少的问题。
1. 读取 Bitmap 的尺寸与类型 工具类 BitmapFactory 提供了从不同资源创建 Bitmap 的解码方法:decodeByteArray()、decodeFile()、decodeResource() 等。但是,这些方法在构造位图的时候会尝试分配内存,也就是它们会导致原图直接加载到内存了,不满足我们的需求。but! 我们可以通过 BitmapFactory.Options 设置一些附加的标记,指定解码选项,以此来解决该问题。
如何操作呢?答案来了:
将 inJustDecodeBounds 属性设置为 true,可以在解码时避免内存的分配,它会返回一个 null 的 Bitmap ,但是可以获取 outWidth、outHeight 和 outMimeType 值。利用该属性,我们就可以在图片不占用内存的情况下,在图片压缩之前获取图片的尺寸。

BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(getResources(), R.id.myimage, options); int imageHeight = options.outHeight; int imageWidth = options.outWidth; String imageType = options.outMimeType;

注意:为了避免 java.lang.OutOfMemory 的异常,我们需要在真正解析图片之前检查它的尺寸(除非你能确定这个数据源提供了准确无误的图片且不会导致占用过多的内存)。
2. 加载压缩后的图片 通过上面的步骤我们已经获取到了图片的尺寸,这些数据可以用来帮助我们决定应该加载整个图片到内存中还是加载一个缩小的版本。
拿上面的例子继续,我们现在获取了原图的尺寸为 2700 * 1900 像素,而现实 UI 控件的尺寸为 270 * 190 像素。我们可以使用 BitmapFactory.Options 中设置 inSampleSize 的值,来实现图片的压缩(不加载原图到内存中)。设置 inSampleSize 为 10,这样图片加载进内存的尺寸就是 270 * 190 像素了。
我们可以将计算缩小倍数的方法,作为一个工具类使用,例如:
public static int calculateInSampleSize( BitmapFactory.Options options, int reqWidth, int reqHeight) { final int height = options.outHeight; final int width = options.outWidth; int inSampleSize = 1; if (height > reqHeight || width > reqWidth) { final int halfHeight = height / 2; final int halfWidth = width / 2; while ((halfHeight / inSampleSize) > reqHeight && (halfWidth / inSampleSize) > reqWidth) { inSampleSize *= 2; } } return inSampleSize; }

注意: 设置 inSampleSize 为 2 的幂是因为解码器最终还是会对非 2 的幂的数进行向下处理,获取到最靠近 2 的幂的数。
为了使用该方法,首先需要设置 inJustDecodeBounds 为 true, 把options的值传递过来,然后设置 inSampleSize 的值并设置 inJustDecodeBounds 为 false,之后重新调用相关的解码方法。
public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, int reqHeight) { final BitmapFactory.Options options = new BitmapFactory.Options(); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); options.inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }

好了,我们的缩略图已经加载到内存中了,并且它实际上占用的内存大小与原图相比,非常非常小。
Android 中的色彩格式及内存优化 RGB 色彩模式是工业界的一种颜色标准,是通过对红?、绿(G)、蓝(B)三个颜色通道的变化以及它们相互之间的叠加来得到各式各样的颜色的,RGB即是代表红、绿、蓝三个通道的颜色,这个标准几乎包括了人类视力所能感知的所有颜色,是运用最广的颜色系统之一。
Android 中,像素的存储方式使用的色彩模式正是 RGB 色彩模式。
ARGB 色彩模式 在 Android 中,我们常见的一些颜色设置,都是 RGB 色彩模式来描述像素颜色的,并且他们都带有透明度通道,也就是所谓的 ARGB。
例如,我们常见的颜色定义如下:
在代码中定义颜色值:public final int blue=0xff0000ff; //蓝色或者在xml中定义:#ff0000ff

以上设置中,颜色值都是使用 16 进制的数字来表示的。以上颜色值都是带有透明度(透明通道)的颜色值,格式是 AARRGGBB,透明度、红色、绿色、蓝色四个颜色通道,各占有 2 位,也就是一个颜色通道,使用了 1 个字节来存储。
Android 中的 RGB 颜色种类 Android 中有多种 RGB 模式,我们可以设置不同的格式,来控制图片像素颜色的显示质量和存储空间。
Android.graphics.Bitmap 类里有一个内部类 Bitmap.Config 类,它定义了可以在 Android 中使用的几种色彩格式:
public enum Config { ALPHA_8(1), RGB_565(3), @Deprecated ARGB_4444(4), ARGB_8888(5), RGBA_F16(6), HARDWARE(7); }

我来解释一下这几个值分别代表了什么含义?
我们已经知道了:A 代表透明度、R 代表红色、G 代表绿色、B 代表蓝色。
那么,
  1. ALPHA_8:表示,只存在 Alpha 通道,没有存储色彩值,只含有透明度,每个像素占用 1 个字节的空间。
  2. RGB_565:表示,R 占用 5 位二进制的位置,G 占用了6位,B 占用了 5 位。每个像素占用 2 个字节空间,并且不包含透明度。
  3. ARGB_4444:表示,A(透明度)、R(红色)、G(绿色)、B(蓝色)4个通道各占用 4 个 bit 位。每个像素占用 2 个字节空间。
  4. ARGB_8888:表示,A(透明度)、R(红色)、G(绿色)、B(蓝色)4个通道各占用 8 个 bit 位。每个像素占用 4 个字节空间。
  5. RGBA_F16:表示,每个像素存储在8个字节上。此配置特别适合广色域和HDR内容。
  6. HARDWARE:特殊配置,当位图仅存储在图形内存中时。 此配置中的位图始终是不可变的。
改变色彩格式优化内存 Android 中的图片在加载时,默认的色彩格式是 ARGB_8888,也就是每个像素占用 4 个字节空间,一张 2700 * 1900 像素的照片,加载到内存就需要 19.6M 内存空间(2592 * 1936 * 4 bytes)。
如果图片在 UI 组件中显示时,不需要太高的图片质量,例如显示一张缩略图(不透明图片)等场景,这时,我们就没必要使用 ARGB_8888 的色彩格式了,只需要使用 RGB_565 模式即可满足显示的需要。
那么,我们的优化操作就可以是:
  1. 将 2700 * 1900 像素的原图,压缩到原图的低分辨率的缩略图 270 * 190 像素的图片,这时需要 0.2M 的内存。也就是从 19.6M内存,压缩为 0.2 M内存。
  2. 我们还可以进一步优化色彩格式,由 ARGB_8888 改为 RGB_565 模式,这时,目标图片需要的内存就变为 270 * 190 * 2 = 0.1M 了。
图片内存空间又减小了一倍。
所以,我们需要针对不同的应用场景做不同的处理,大图和小图可以采用不同的解码率(也就是设置不同的色彩格式)。在Android里面可以通过下面的代码来设置解码率:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; BitmapFactory.decodeResource(getResources(), R.id.myimage, options);

缓存的使用 当存应用中存在大量图片时,使用缓存,可以有效的提高内存的使用率以及用户体验。
内存缓存 LruCache 类特别适合用来缓存 Bitmap,它使用一个强引用的 LinkedHashMap 保存最近引用的对象,并且在缓存超出设定大小时,删除最近最少使用的对象。
给 LruCache 确定一个合适的缓存大小非常重要,我们需要考虑几个因素:
  • 应用剩余多少可用内存?
  • 需要有多少张图片同时显示到屏幕上?有多少图片需要准备好以便马上显示到屏幕?
  • 设备的屏幕大小和密度是多少?高密度的设备需要更大的缓存空间来缓存同样数量的图片。
  • Bitmap 的尺寸配置是多少,花费多少内存?
  • 图片被访问的频率如何?如果其中一些比另外一些访问更频繁,那么我们可能希望在内存中保存那些最常访问的图片,或者根据访问频率给 Bitmap 分组,为不同的 Bitmap 组设置多个 LruCache 对象。
  • 是否可以在缓存图片的质量和数量之间寻找平衡点?有时,保存大量低质量的 Bitmap 会非常有用,加载更高质量的图片的任务可以交给另外一个后台线程处理。
缓存太小会导致额外的花销却没有明显的好处,缓存太大同样会导致 java.lang.OutOfMemory 的异常,并且使得你的程序只留下小部分的内存用来工作(缓存占用太多内存,导致其他操作会因为内存不够而抛出异常)。所以,我们需要分析实际情况之后,提出一个合适的解决方案。
使用磁盘缓存(Use a Disk Cache) 内存缓存能够提高访问最近用过的 Bitmap 的速度,但是我们无法保证最近访问过的 Bitmap 都能够保存在缓存中。像类似 GridView 等需要大量数据填充的控件很容易就会用尽整个内存缓存。另外,我们的应用可能会被类似打电话等行为而暂停并退到后台,因为后台应用可能会被杀死,那么内存缓存就会被销毁,里面的 Bitmap 也就不存在了。一旦用户恢复应用的状态,那么应用就需要重新处理那些图片。
磁盘缓存可以用来保存那些已经处理过的 Bitmap,它还可以减少那些不再内存缓存中的 Bitmap 的加载次数。当然从磁盘读取图片会比从内存要慢,而且由于磁盘读取操作时间是不可预期的,读取操作需要在后台线程中处理。
注意:如果图片会被更频繁的访问,使用 ContentProvider 或许会更加合适,比如在图库应用中。
注意:因为初始化磁盘缓存涉及到 I/O 操作,所以它不应该在主线程中进行。但是这也意味着在初始化完成之前缓存可以被访问。为了解决这个问题,在上面的实现中,有一个锁对象(lock object)来确保在磁盘缓存完成初始化之前,应用无法对它进行读取。
内存缓存的检查是可以在 UI 线程中进行的,磁盘缓存的检查需要在后台线程中处理。磁盘操作永远都不应该在 UI 线程中发生。当图片处理完成后,Bitmap 需要添加到内存缓存与磁盘缓存中,方便之后的使用。
处理配置改变 如果运行时设备配置信息发生改变,例如屏幕方向的改变会导致 Android 中当前显示的 Activity 先被销毁然后重启。我们需要在配置改变时避免重新处理所有的图片,这样才能提供给用户一个良好的平滑过度的体验。
我们可以通过调用 setRetainInstance(true)) 保留一个 Fragment 实例的方法把缓存传递给新的 Activity。在这个 Activity 被重新创建之后,这个保留的 Fragment 会被重新附着上。这样你就可以访问缓存对象了,从缓存中获取到图片信息并快速的重新显示到 ImageView 上。
Fragment 具有属性 retainInstance,默认值为 false,当设备旋转时,fragment 会随托管 activity 一起销毁并重建。调用 setRetainInstance(true) 方法可保留 fragment,已保留的 fragment 不会随着 activity 一起被销毁,它会一直保留(进程不消亡的前提下),并在需要时原封不动地传递给新的 Activity。另外,Fragment 中定义了isAdded 接口,用于判断 Fragment 是否已经绑定到某个 Activity。
下面是配置改变时使用Fragment来保留LruCache的代码示例:
private LruCache mMemoryCache; @Overrideprotected void onCreate(Bundle savedInstanceState) { ... RetainFragment retainFragment = RetainFragment.findOrCreateRetainFragment(getFragmentManager()); mMemoryCache = retainFragment.mRetainedCache; if (mMemoryCache == null) { mMemoryCache = new LruCache(cacheSize) { ... // Initialize cache here as usual } retainFragment.mRetainedCache = mMemoryCache; } ... }class RetainFragment extends Fragment { private static final String TAG = "RetainFragment"; public LruCache 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; }@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setRetainInstance(true); } }

管理 Bitmap 的内存使用 Android 图片使用的对象最终都是用 Bitmap 来创建并存储的。在不同的 Android 版本中,Bitmap 或多或少都存在差异,尤其是在其内存分配上,针对于不同的 Android 版本,Bitmap 的推荐的内存管理策略也会存在一些差异。
Android 管理 Bitmap 内存使用的演变进程
  • 在Android 2.3.3 (API level 10)以及之前, 一个 Bitmap 的像素数据是存放在 Native 内存空间中的。这些数据与 Bitmap 对象本身是隔离的,Bitmap 本身被存放在 Dalvik 堆中。并且无法预测在 Native 内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。
  • 自 Android 3.0 (API Level 11)开始,像素数据则是与 Bitmap 本身一起存放在 Dalvik 堆中。
  • Android 8.0(Android O)及之后的版本中,Bitmap 的像素数据的内存分配又回到了 Native 层,它是在 Native 堆空间进行分配的。
应用的最大内存限制 我们知道,在特定手机上,每个应用使用的最大内存都是有限制的,当内存超过最大限制就会发生 OOM。
dalvik.vm.heapstartsize=8m dalvik.vm.heapgrowthlimit=192m dalvik.vm.heapsize=512m dalvik.vm.heaptargetutilization=0.75 dalvik.vm.heapminfree=512k dalvik.vm.heapmaxfree=8m

在以上配置的手机中,如果没有在 AndroidManifest 中启用 largeheap,那么 Java 堆内存达到 192M 的时候就会 OOM 崩溃。但是,随着主流手机的配置越来越高,内存 8G 都变得非常常见,192M 甚至 512M 最大内存上限有时就不太够用了,如果增加最大堆大小,这样也是不行的,因为我们的手机上装了大量的应用,大幅提高 Java 堆上限很容易出问题,导致内存不够。于是,Android 8.0 就把 Bitmap 的像素数据存储在了 Native 层,这样 Java 堆上限没有变化,但是 Bitmap 的像素数据不占用 Java 堆的内存空间,这样就可以通过 Native 层,分配手机剩余的大量内存空间,没有最高额度的限制,但是,当整个手机内存耗尽时,也会导致崩溃,但是在 Java 层就不会捕捉到崩溃日志了,也就是不会产生 OOM了。
管理 Bitmap 的内存使用
  • 管理 Android 2.3.3 及以下版本的内存使用
在 Android 2.3.3 (API level 10) 以及更低版本上,推荐使用 recycle() 方法。 如果在应用中显示了大量的 Bitmap 数据,我们很可能会遇到 OutOfMemoryError 的错误。 recycle() 方法可以使得程序更快的释放内存。
  • 管理 Android 3.0 及其以上版本的内存
从 Android 3.0 (API Level 11)开始,引进了 BitmapFactory.Options.inBitmap 字段。 如果使用了这个设置字段,decode 方法会在加载 Bitmap 数据的时候去重用已经存在的 Bitmap。这意味着 Bitmap 的内存是被重新利用的,这样可以提升性能,并且减少了内存的分配与回收。然而,使用 inBitmap 有一些限制,特别是在Android 4.4 (API level 19)之前,只有同等大小的位图才可以被重用。
  • 管理 Android 8.0 及其以上版本的内存
在 Android 8.0 及其以上版本,处理内存,也遵循 Android 3.0 以上版本同样的方式。同时,图片像素数据存储在 native 层,并且不占用 Java 堆的空间,这也代表着我们拥有更大的图片存储空间,可以加载质量更高、数据更多的图片到内存中。但是,内存依然不是无限的,应用还是要受到手机内存的限制,所以一定要注意这一点。
减少 PNG 图片的使用 尽量减少 PNG 图片的大小是 Android 里面很重要的一条规范。相比起 JPEG,PNG 能够提供更加清晰无损的图片,但是 PNG 格式的图片会更大,占用更多的磁盘空间。到底是使用 PNG 还是 JPEG,需要设计师仔细衡量,对于那些使用 JPEG 就可以达到视觉效果的,可以考虑采用 JPEG 即可。
这里要介绍一种新的图片格式:Webp,它是由 Google 推出的一种既保留 png 格式的优点,又能够减少图片大小的一种新型图片格式。
在 Android 4.0(API level 14) 中支持有损的 WebP 图像,在 Android 4.3(API level 18) 和更高版本中支持无损和透明的 WebP 图像。
提高 Bitmap 内存的重复利用率 我们知道 bitmap 会占用大量的内存空间,我们可以利用 inBitmap 这个属性来提升 bitmap 的循环效率。前面我们介绍过使用对象池的技术来解决对象频繁创建再回收的效率问题,使用这种方法,bitmap 占用的内存空间会差不多是恒定的数值,每次新创建出来的 bitmap 都会需要占用一块单独的内存区域,如下图所示:
Android|Android 性能优化之——高性能使用图片全面总结
文章图片

为了解决上图所示的效率问题,Android 在解码图片的时候引进了 inBitmap 属性,使用这个属性可以得到下图所示的效果:
Android|Android 性能优化之——高性能使用图片全面总结
文章图片

使用 inBitmap 属性可以告知 Bitmap 解码器去尝试使用已经存在的内存区域,新解码的 bitmap 会尝试去使用之前那张 bitmap 在 heap 中所占据的像素数据内存区域,而不是去问内存重新申请一块区域来存放 bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。
使用inBitmap的代码示例:
BitmapFactory.Options options = new BitmapFactory.Options(); options.inBitmap = mCurrentBitmap; mCurrentBitmap = BitmapFactory.decodeFile(fileName, options);

使用inBitmap需要注意几个限制条件:
  • 在SDK 11 -> 18之间,重用的 bitmap 大小必须是一致的,例如给 inBitmap 赋值的图片大小为 100-100,那么新申请的 bitmap 必须也为 100-100 。从 SDK 19 开始,新申请的 bitmap 大小必须小于或者等于已经赋值过的 bitmap 大小。
  • 新申请的 bitmap 与旧的 bitmap 必须有相同的解码格式,例如大家都是 8888 的,如果前面的 bitmap 是 8888,那么就不能支持 4444 与 565 格式的 bitmap 了。
我们可以创建一个包含多种典型可重用 bitmap 的对象池,这样后续的 bitmap 创建都能够找到合适的“模板”去进行重用。如下图所示:
Android|Android 性能优化之——高性能使用图片全面总结
文章图片

Google 推荐使用一个开源的加载 bitmap 的库:Glide,这里面包含了各种对 bitmap 的优化技巧。
图片资源的压缩 本节重点介绍,图片资源在上线前的一些优化方法。
我们应用中使用的图片,设计师出的原图通常都非常大,他们通常会使用工具,经过一定的压缩,缩减到比较小一些的大小。
但是,这些图片通常都有一定的可压缩空间,我在之前的项目中,对图片进行了二次压缩,整体压缩率达到了 40%~50% ,效果还是非常不错的。压缩工具,大家可以使用线下的一些软件,当然比较方便的是一些在线图片压缩网站,这些网站很多都是免费的,可以自己在搜索引擎上搜下就行,这里不做过多介绍了。
这里介绍下常用的,图片压缩的方法:
  1. 使用压缩工具对图片进行二次压缩。
  2. 根据最终图片是否需要透明度展示,优先选择不透明的图片格式,例如,我们应该避免使用 png 格式的图片。
  3. 对于色彩简单,例如,一些背景之类的图片,可以选择使用布局文件来定义(矢量图),这样就会非常节省内存了。
  4. 如果包含透明度,优先使用 WebP 等格式图像。
图片在上线前进行压缩处理,不但可以减少内存的使用,如果图片是网络获取的,也可以减少网络加载的流量和时间。
总结
  1. 将适合 UI 组件展示的图片尺寸加载到内存中,而不是使用原图。
  2. 将BitmapFactory.Options 设置 inJustDecodeBounds 属性设置为 true,可以在解码时避免内存的分配,从而避免将原图加载到内存,而是将缩略图直接加载到内存。
  3. 在 Android 中,我们常见的一些颜色设置,都是 RGB 色彩模式来描述像素颜色的,并且他们都带有透明度通道,也就是所谓的 ARGB。
  4. Android 中的图片在加载时,默认的色彩格式是 ARGB_8888,也就是每个像素占用 4 个字节空间。我们需要针对不同的应用场景做不同的处理,大图和小图可以采用不同的解码率(也就是设置不同的色彩格式)以优化图片内存的使用。
  5. 当存应用中存在大量图片时,使用缓存,可以有效的提高内存的使用率以及用户体验。
  6. LruCache 类特别适合用来缓存 Bitmap,它使用一个强引用的 LinkedHashMap 保存最近引用的对象,并且在缓存超出设定大小时,删除最近最少使用的对象。
  7. 磁盘缓存可以用来保存那些已经处理过的 Bitmap,它还可以减少那些不再内存缓存中的 Bitmap 的加载次数。当然从磁盘读取图片会比从内存要慢,而且由于磁盘读取操作时间是不可预期的,读取操作需要在后台线程中处理。
  8. 内存缓存的检查是可以在 UI 线程中进行的,磁盘缓存的检查需要在后台线程中处理。磁盘操作永远都不应该在 UI 线程中发生。当图片处理完成后,Bitmap 需要添加到内存缓存与磁盘缓存中,方便之后的使用。
  9. Fragment 具有属性 retainInstance,默认值为 false,当设备旋转时,fragment 会随托管 activity 一起销毁并重建。调用 setRetainInstance(true) 方法可保留 fragment,已保留的 fragment 不会随着 activity 一起被销毁,它会一直保留(进程不消亡的前提下),并在需要时原封不动地传递给新的 Activity。另外,Fragment 中定义了isAdded 接口,用于判断 Fragment 是否已经绑定到某个 Activity。
  10. Android 图片使用的对象最终都是用 Bitmap 来创建并存储的。在不同的 Android 版本中,Bitmap 或多或少都存在差异,尤其是在其内存分配上,针对于不同的 Android 版本,Bitmap 的推荐的内存管理策略也会存在一些差异。
  11. 在Android 2.3.3 (API level 10)以及之前, 一个 Bitmap 的像素数据是存放在 Native 内存空间中的。这些数据与 Bitmap 对象本身是隔离的,Bitmap 本身被存放在 Dalvik 堆中。并且无法预测在 Native 内存中的像素级数据何时会被释放,这意味着程序容易超过它的内存限制并且崩溃。
  12. 自 Android 3.0 (API Level 11)开始,像素数据则是与 Bitmap 本身一起存放在 Dalvik 堆中。
  13. Android 8.0(Android O)及之后的版本中,Bitmap 的像素数据的内存分配又回到了 Native 层,它是在 Native 堆空间进行分配的。
  14. 尽量减少 PNG 图片的大小是 Android 里面很重要的一条规范。相比起 JPEG,PNG 能够提供更加清晰无损的图片,但是 PNG 格式的图片会更大,占用更多的磁盘空间。推荐使用 Webp 格式的图片。
  15. 我们可以利用 inBitmap 这个属性来提升 bitmap 的循环效率。使用 inBitmap 属性可以告知 Bitmap 解码器去尝试使用已经存在的内存区域,新解码的 bitmap 会尝试去使用之前那张 bitmap 在 heap 中所占据的像素数据内存区域,而不是去问内存重新申请一块区域来存放 bitmap。利用这种特性,即使是上千张的图片,也只会仅仅只需要占用屏幕所能够显示的图片数量的内存大小。
  16. 使用压缩工具对图片进行二次压缩。
  17. 根据最终图片是否需要透明度展示,优先选择不透明的图片格式,例如,我们应该避免使用 png 格式的图片。
  18. 对于色彩简单,例如,一些背景之类的图片,可以选择使用布局文件来定义(矢量图),这样就会非常节省内存了。
【Android|Android 性能优化之——高性能使用图片全面总结】**PS:更多精彩内容,请查看 --> 《Android 性能优化》
**PS:更多精彩内容,请查看 --> 《Android 性能优化》
**PS:更多精彩内容,请查看 --> 《Android 性能优化》

    推荐阅读