zoukankan      html  css  js  c++  java
  • 【Android Developers Training】 59. 管理图片存储

    注:本文翻译自Google官方的Android Developers Training文档,译者技术一般,由于喜爱安卓而产生了翻译的念头,纯属个人兴趣爱好。

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


    接着上一节课的步伐,还有很多特定的事情可以让垃圾回收和位图重用变得容易。根据你的目标Android系统的不同版本,推荐的策略也会有所不同。这系列课程的样例代码:BitmapFun(链接:http://www.cnblogs.com/jdneo/p/3512517.html)向你展示了你的应用如何在不同的Android版本间有效率地工作。

    在开始这堂课之前,我们先来看一下Android对于位图存储管理的演变:

    • 在Android 2.2(API Level 8)之前,当垃圾回收发生时,你应用的线程会停止。由此导致的延迟会降低应用的性能表现。在Android 2.3开始加入了并发式的垃圾回收,这意味着当一个位图不再被引用时,对应的内存会被迅速回收
    • 在Android 2.3.3(API Level 10)之前,一副位图的依托像素数据(backing pixel data)存储于本机内存中。它和位图本身是分离开的,位图存储于Dalvik堆中。本机内存中的像素数据并不会以一种可预测的形式进行释放,因此可能会导致一个应用超过它的内存限制而崩溃。从Android 3.0(API Level 11)开始,像素数据和与其关联的位图都存储于Dalvik堆中了

    下面的章节将会描述如何对不同的Android版本,优化位图存储管理。


    一). 在Android 2.3.3及之前的系统版本上管理内存

    在Android 2.3.3(API Level 10)及以前,推荐使用recycle()。如果你在你的应用中要显示大量的位图数据,你极有可能引起OutOfMemoryError错误。recycle()可以让应用尽快地释放内存。

    Caution:

    只有当你确定对应的位图将不再被使用的情况下,你才应该使用recycle()。如果你使用了recycle(),并在之后尝试绘制该位图,那么你将会得到这样的错误提示:“Canvas: trying to use a recycled bitmap.

    下面的代码是调用recycle()的样例。它使用引用计数(变量“mDisplayRefCount”以及“mCacheRefCount”)来追踪位图是否是当前正在显示或是在缓存中。当下列情况发生时,代码会回收位图:

    • 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(API Level 11)引入了BitmapFactory.Options.inBitmap域。如果配置了该选项,接受该Options对象的解码方法会在加载内容时尝试去重用已经存在的位图。这意味着位图内存被重用了,从而提高了性能表现,同时免去了内存分配和回收的工作。然而,对于如何使用inBitmap会有一些限制。特别地,在Android 4.4(API Level 19)之前,只有尺寸吻合的位图才被支持。更多详细信息,可以阅读:inBitmap

    保存一幅位图以备将来使用

    下面的代码样例展示了在相同的应用中,一个已经存在的位图是如何为了将来可能被再次使用到而保存的。当一个应用在Android 3.0及以上的设备上运行,且一个位图从LruCache中移除,该位图的软引用会放置在一个HashSet中,以备之后inBitmap使用:

    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()));
                }
            }
        }
    ....
    }

    使用一个存在的位图

    在运行的程序中,解码方法检查是否有可用的位图。例如:

    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()方法的实现。它寻找一个存在的位图,并将它作为inBitmap的值。注意,该方法仅仅在它找到了一个合适的匹配时,才将位图设置为inBitmap的值(你的代码不可以假定这个匹配一定能找到):

    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;
    }

    最后,此方法决定备选的位图是否符合inBitmap的尺寸标准:

    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;
    }
  • 相关阅读:
    查找list中的重复数据,并得到不重复数据索引位置
    sql server 存储过程中使用事务
    C#获取web.config配置文件内容
    SendKeys.Send 方法
    C# 获取当前路径方法(转载)
    asp.net中URL参数传值中文乱码的三种解决办法
    无法 连接到SQLEXPRESS 已成功与服务器建立连接,但是在登录过程中发生错误。管道的另一端无任何进程
    Windows7 IIS7 无法启动计算机上的服务W3SVC如何修复,计算机上无法找到.was解决方案 visita iis 7.0
    EXCEL开发Interior.ColorIndex 色彩列表
    asp.net web 登录文本框的回车设计
  • 原文地址:https://www.cnblogs.com/jdneo/p/3524299.html
Copyright © 2011-2022 走看看