【Android Developers Training】 59. 管理图片

系统 2485 0

注:本文翻译自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
      
        ;

}
      
    

【Android Developers Training】 59. 管理图片存储


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论