Android官方开发文档Training系列课程中文版:高效显示位图之管理位图内存
原文地址: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,并且還沒有被回收。
在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.@Overrideprotected 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 1return 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:這篇文章是前面有關位圖的所有知識點的綜合練習。
總結
以上是生活随笔為你收集整理的Android官方开发文档Training系列课程中文版:高效显示位图之管理位图内存的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于话题演化关系网络生成的路线思考:从话
- 下一篇: 如何实现一个循环显示超长图片的控件