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

简介: 原文地址:http://developer.android.com/training/displaying-bitmaps/manage-memory.html除了在上一节中描述的步骤之外,还有一些细节上的事情可以促进垃圾回收器的回收及位图的复用。

原文地址:http://developer.android.com/training/displaying-bitmaps/manage-memory.html

除了在上一节中描述的步骤之外,还有一些细节上的事情可以促进垃圾回收器的回收及位图的复用。其推荐的策略取决于Android的目标版本。示例APP BitmapFun展示了如何使应用程序在不同的版本上高效的工作。

为了给这节课的知识奠定一些基础,下面有一些Android系统如何管理位图内存的一些改进需要了解:

  • 在Android 2.2之前,当垃圾回收器回收时,应用的线程会被停止。这会降低性能发生延迟。Android 2.3增加了并发收集垃圾的功能,这意味着内存回收不久之后位图不可再被引用。
  • 在Android 2.3.3之前,位图对应的支撑数据被存放在本地内存中。这与位图本身是分离的,它被存储在Dalvik堆栈之中。在意料之中位于本地内存中的像素数据是不会被释放的,可能会导致程序超过自身的内存限制然后崩溃。从在Android 3.0开始,像素数据与之相关的位图被一同存入了Dalvik虚拟机堆栈内。

下面的部分会介绍如何对不同的安卓版本进行位图内存的优化与管理。

在Android 2.3之前的版本上管理内存

在2.3之前推荐使用recycle()方法。如果你在APP内展示了大量的位图数据,那么你很有可能会遇到OutOfMemoryError错误。recycle()方法允许应用尽可能的回收内存。

Caution: 你应该在确保位图不再使用的时候使用recycle()方法。如果你调用了recycle()方法之后去尝试绘制这个位图,你将会得到错误:”Canvas: trying to use a recycled bitmap”。

下面的代码是recycle()方法的示例用法。它使用了引用计数的方式来追踪位图当前是处于被展示状态还是被缓存状态。当处于以下状况时,代码会回收位图:

  • 引用数mDisplayRefCount及 mCacheRefCount都是0。
  • 位图不为null,并且还没有被回收。
private int mCacheRefCount = 0;
private int mDisplayRefCount = 0;
...
// Notify the drawable that the displayed state has changed.
// Keep a count to determine when the drawable is no longer displayed.
public void setIsDisplayed(boolean isDisplayed) {
    synchronized (this) {
        if (isDisplayed) {
            mDisplayRefCount++;
            mHasBeenDisplayed = true;
        } else {
            mDisplayRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}
// Notify the drawable that the cache state has changed.
// Keep a count to determine when the drawable is no longer being cached.
public void setIsCached(boolean isCached) {
    synchronized (this) {
        if (isCached) {
            mCacheRefCount++;
        } else {
            mCacheRefCount--;
        }
    }
    // Check to see if recycle() can be called.
    checkState();
}
private synchronized void checkState() {
    // If the drawable cache and display ref counts = 0, and this drawable
    // has been displayed, then recycle.
    if (mCacheRefCount <= 0 && mDisplayRefCount <= 0 && mHasBeenDisplayed
            && hasValidBitmap()) {
        getBitmap().recycle();
    }
}
private synchronized boolean hasValidBitmap() {
    Bitmap bitmap = getBitmap();
    return bitmap != null && !bitmap.isRecycled();
}

在Android 3.0之后的版本上管理内存

Android 3.0中介绍了BitmapFactory.Options.inBitmap属性。如果设置了这个选项,那么解码方法会尝试去重用一个已经存在的位图。这也就是说位图的内存是可重用的,这可以促使改善性能,并且不需要再申请内存及释放内存。然而,inBitmap如何使用还有一些限制,在Android 4.4之前,仅支持相同大小的位图。更多信息,请看文档:inBitmap

保存位图以便稍后使用

下面的代码演示了如何存储一个位图以便稍后使用。当APP运行在Android 3.0以上时,位图会被LruCache移除,接着一个引用位图的软性引用会被放置到一个HashSet中,以便稍后可能被用到:

Set<SoftReference<Bitmap>> mReusableBitmaps;
private LruCache<String, BitmapDrawable> mMemoryCache;
// If you're running on Honeycomb or newer, create a
// synchronized HashSet of references to reusable bitmaps.
if (Utils.hasHoneycomb()) {
    mReusableBitmaps =
            Collections.synchronizedSet(new HashSet<SoftReference<Bitmap>>());
}
mMemoryCache = new LruCache<String, BitmapDrawable>(mCacheParams.memCacheSize) {
    // Notify the removed entry that is no longer being cached.
    @Override
    protected void entryRemoved(boolean evicted, String key,
            BitmapDrawable oldValue, BitmapDrawable newValue) {
        if (RecyclingBitmapDrawable.class.isInstance(oldValue)) {
            // The removed entry is a recycling drawable, so notify it
            // that it has been removed from the memory cache.
            ((RecyclingBitmapDrawable) oldValue).setIsCached(false);
        } else {
            // The removed entry is a standard BitmapDrawable.
            if (Utils.hasHoneycomb()) {
                // We're running on Honeycomb or later, so add the bitmap
                // to a SoftReference set for possible use with inBitmap later.
                mReusableBitmaps.add
                        (new SoftReference<Bitmap>(oldValue.getBitmap()));
            }
        }
    }
....
}

使用已经存在的位图

在运行中的APP内,解码方法会检查是否有已经存在的位图可以被再次使用:

public static Bitmap decodeSampledBitmapFromFile(String filename,
        int reqWidth, int reqHeight, ImageCache cache) {
    final BitmapFactory.Options options = new BitmapFactory.Options();
    ...
    BitmapFactory.decodeFile(filename, options);
    ...
    // If we're running on Honeycomb or newer, try to use inBitmap.
    if (Utils.hasHoneycomb()) {
        addInBitmapOptions(options, cache);
    }
    ...
    return BitmapFactory.decodeFile(filename, options);
}

addInBitmapOptions():

private static void addInBitmapOptions(BitmapFactory.Options options,
        ImageCache cache) {
    // inBitmap only works with mutable bitmaps, so force the decoder to
    // return mutable bitmaps.
    options.inMutable = true;
    if (cache != null) {
        // Try to find a bitmap to use for inBitmap.
        Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
        if (inBitmap != null) {
            // If a suitable bitmap has been found, set it as the value of
            // inBitmap.
            options.inBitmap = inBitmap;
        }
    }
}
// This method iterates through the reusable bitmaps, looking for one 
// to use for inBitmap:
protected Bitmap getBitmapFromReusableSet(BitmapFactory.Options options) {
        Bitmap bitmap = null;
    if (mReusableBitmaps != null && !mReusableBitmaps.isEmpty()) {
        synchronized (mReusableBitmaps) {
            final Iterator<SoftReference<Bitmap>> iterator
                    = mReusableBitmaps.iterator();
            Bitmap item;
            while (iterator.hasNext()) {
                item = iterator.next().get();
                if (null != item && item.isMutable()) {
                    // Check to see it the item can be used for inBitmap.
                    if (canUseForInBitmap(item, options)) {
                        bitmap = item;
                        // Remove from reusable set so it can't be used again.
                        iterator.remove();
                        break;
                    }
                } else {
                    // Remove from the set if the reference has been cleared.
                    iterator.remove();
                }
            }
        }
    }
    return bitmap;
}

最后,这里方法会检查候选位图是否满足大小:

static boolean canUseForInBitmap(
        Bitmap candidate, BitmapFactory.Options targetOptions) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
        // From Android 4.4 (KitKat) onward we can re-use if the byte size of
        // the new bitmap is smaller than the reusable bitmap candidate
        // allocation byte count.
        int width = targetOptions.outWidth / targetOptions.inSampleSize;
        int height = targetOptions.outHeight / targetOptions.inSampleSize;
        int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
        return byteCount <= candidate.getAllocationByteCount();
    }
    // On earlier versions, the dimensions must match exactly and the inSampleSize must be 1
    return candidate.getWidth() == targetOptions.outWidth
            && candidate.getHeight() == targetOptions.outHeight
            && targetOptions.inSampleSize == 1;
}
/**
 * A helper function to return the byte usage per pixel of a bitmap based on its configuration.
 */
static int getBytesPerPixel(Config config) {
    if (config == Config.ARGB_8888) {
        return 4;
    } else if (config == Config.RGB_565) {
        return 2;
    } else if (config == Config.ARGB_4444) {
        return 2;
    } else if (config == Config.ALPHA_8) {
        return 1;
    }
    return 1;
}

下面片段所展示的方法会被上面的片段所调用。它会寻找存在的位图然后将其设置为值。注意这个方法只是对适合的匹配对象设置值。


PS:除了大家对图片的内存关心之外,可能大家还对图片的缓存异步加载加载大图也比较关注。

Note:这篇文章是前面有关位图的所有知识点的综合练习。

目录
相关文章
|
9月前
|
存储 数据库 Android开发
🔥Android Jetpack全解析!拥抱Google官方库,让你的开发之旅更加顺畅无阻!🚀
【7月更文挑战第28天】在Android开发中追求高效稳定的路径?Android Jetpack作为Google官方库集合,是你的理想选择。它包含多个独立又协同工作的库,覆盖UI到安全性等多个领域,旨在减少样板代码,提高开发效率与应用质量。Jetpack核心组件如LiveData、ViewModel、Room等简化了数据绑定、状态保存及数据库操作。引入Jetpack只需在`build.gradle`中添加依赖。例如,使用Room进行数据库操作变得异常简单,从定义实体到实现CRUD操作,一切尽在掌握之中。拥抱Jetpack,提升开发效率,构建高质量应用!
190 4
|
10月前
|
缓存 Java Linux
Android 匿名内存深入分析
Android 匿名内存深入分析
64 0
|
10月前
|
API Android开发
55. 【Android教程】位图:Bitmap
55. 【Android教程】位图:Bitmap
124 0
|
11月前
|
存储 缓存 算法
深入理解操作系统内存管理:分页系统的优势与挑战构建高效Android应用:探究Kotlin协程的优势与实践
【5月更文挑战第27天】 在现代计算机系统中,内存管理是操作系统的核心功能之一。分页系统作为一种内存管理技术,通过将物理内存划分为固定大小的单元——页面,为每个运行的程序提供独立的虚拟地址空间。这种机制不仅提高了内存的使用效率,还为多任务环境提供了必要的隔离性。然而,分页系统的实现也带来了一系列的挑战,包括页面置换算法的选择、内存抖动问题以及TLB(Translation Lookaside Buffer)的管理等。本文旨在探讨分页系统的原理、优势及其面临的挑战,并通过分析现有解决方案,提出可能的改进措施。
|
11月前
|
JSON Android开发 数据格式
Android框架-Google官方Gson解析,android开发实验报告总结
Android框架-Google官方Gson解析,android开发实验报告总结
|
11月前
|
XML Android开发 数据格式
Fragment的使用,零基础入门android逆向视频课程
Fragment的使用,零基础入门android逆向视频课程
|
11月前
|
缓存 算法 网络协议
Android面试回忆录移动应用开发专业核心课程
Android面试回忆录移动应用开发专业核心课程
|
11月前
|
Android开发
Android 设备清理内存 RAM
Android 设备清理内存 RAM
85 0
|
11月前
|
算法 安全 Android开发
深入理解操作系统的内存管理机制构建高效Android应用:Kotlin的协程优势
【4月更文挑战第30天】 在现代计算机系统中,操作系统的内存管理是确保系统高效、稳定运行的关键。本文将探讨操作系统内存管理的核心技术,包括内存分配、虚拟内存、分页和分段等概念,以及它们是如何协同工作以提高内存利用率和系统性能的。通过对这些技术的详细分析,我们可以更好地理解操作系统背后的原理,并评估不同内存管理策略对系统行为的影响。 【4月更文挑战第30天】 在移动开发领域,尤其是针对Android平台,性能优化和流畅的用户体验始终是开发者追求的核心目标。随着Kotlin语言的普及,协程作为其在异步编程领域的杀手锏特性,已经逐渐成为提高应用性能和简化代码结构的重要工具。本文将深入探讨Kotli
|
11月前
|
存储 Java Shell
Android系统 实现低内存白名单防LMK原理分析
Android系统 实现低内存白名单防LMK原理分析
640 0

热门文章

最新文章