注:本文翻譯自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
;
}
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061
微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

