原文连接:http://developer.android.com/training/displaying-bitmaps/manage-memory.html
图片内存管理
除了缓存图片,你还可以做一些其他的事情来方便垃圾回收和再利用图片。推荐的策略取决
于设备的Android版本。
在Android 2.2和之前的Android版本中,当垃圾回收发生时,你的应用程序被停止。这将导致
程序滞后,可能会降低性能。Android 2.3增加了并发的垃圾回收,这意味着当一个图片的不
在被引用了,它所占用的内存会被立即回收。
在Android 2.3.3和较低的版本中,对于一个图片的像素数据被存储在本地内存中。而bitmap
对象本身被存储在Dalvik虚拟机的堆中。在本地内存中的像素数据不会以一种可预见的方式
被释放,可能会使得应用程序超过它的内存限制而导致崩溃。而到了Android 3.0,图片的像
素数据也被存储在Dalvik虚拟机的堆中。
在Android 2.3.3和低版本的系统中管理内存
在Android 2.3.3和低版本的系统中,推荐使用recycle()。如果你在app中显示了大量的bitmap
数据,你有可能会导致内存溢出错误。recycle方法使得应用程序尽快回收内存。
下面的代理使用引用计数器来跟踪,一个图片当前被显示或在缓存中。当满足下面的条件时,
bitmap将会被回收。
1.对于mDisplayRefCount和mCacheRefCount的引用数都为0.
2.bitmap不为null,且没有被回收。
在Android 3.0之后的系统中管理内存
Android 3.0引入了BitmapFactory.Options.inBitmap域。如果这个选项被设置,解码方法在加
载内容时将试图重用一个存在的bitmap。这意味着bitmap的内存被重用,使得改善了性能,
消除了内存分配和回收。下面是使用inBitmap时需注意的事项:
1.重用的bitmap必须与源内容的大小相同,而且是JPEG和PNG的格式。
2.重用的bitmap将覆盖设置的inPreferredConfig。
3.你应该总是使用由decode方法返回的bitmap,因为你不能确定重用的bitmap是否能工作。
使用一个存在的bitmap
在UI中显示Bitmap
将Bitmap加载到ViewPager实现
一个交换的视图模式是浏览一个图片库详细信息视图很好的一个方式。你可以使用由PagerAdapter
和ViewPager来实现这种模式。然而,一个更合适的适配器是FragmentStatePagerAdapter,它
能自动销毁和保存在ViewPager中Fragments的状态。
下面是一个持有ImageView的详细Fragment的实现。这似乎是一个完全合理的做法,但你能
看到这种实现的缺点吗?如何进行改善?
问题是:图片在UI线程中被读取。使用一个AsyncTask。
任何额外的处理都能在BitmapWorkerTask中进行,这不会影响UI的响应。
将Bitmaps加载到GridView中的实现
网格列表是用于显示图像的数据集,可以用一个GridView组件实现,其中很多图片能够同时
显示在屏幕上,当用户向上或向下滑动时,更多的图片需要准备好被显示。当实现这种控制
时,你必须确保UI处于活动,内存的使用仍然处于控制,并发被正确处理。
下面是一个包含ImageView子视图置于Fragment中的一个标准GridView。这看起来是一个很好
的方式,但是否有更好的选择?
上面实现方式的问题是图片在UI线程中被设置。虽然这可能对小的、简单的图像是可以接受
的,但如果要做任何额外的处理,你的UI就会使人有停顿的感觉。
在这里可以加入异步处理和告诉缓存的方案。然而,你也需要警惕并发问题,GridView循环
回收子视图。为了解决这个问题,使用上一节中使用的技术:
图片内存管理
除了缓存图片,你还可以做一些其他的事情来方便垃圾回收和再利用图片。推荐的策略取决
于设备的Android版本。
在Android 2.2和之前的Android版本中,当垃圾回收发生时,你的应用程序被停止。这将导致
程序滞后,可能会降低性能。Android 2.3增加了并发的垃圾回收,这意味着当一个图片的不
在被引用了,它所占用的内存会被立即回收。
在Android 2.3.3和较低的版本中,对于一个图片的像素数据被存储在本地内存中。而bitmap
对象本身被存储在Dalvik虚拟机的堆中。在本地内存中的像素数据不会以一种可预见的方式
被释放,可能会使得应用程序超过它的内存限制而导致崩溃。而到了Android 3.0,图片的像
素数据也被存储在Dalvik虚拟机的堆中。
在Android 2.3.3和低版本的系统中管理内存
在Android 2.3.3和低版本的系统中,推荐使用recycle()。如果你在app中显示了大量的bitmap
数据,你有可能会导致内存溢出错误。recycle方法使得应用程序尽快回收内存。
下面的代理使用引用计数器来跟踪,一个图片当前被显示或在缓存中。当满足下面的条件时,
bitmap将会被回收。
1.对于mDisplayRefCount和mCacheRefCount的引用数都为0.
2.bitmap不为null,且没有被回收。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
|
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域。如果这个选项被设置,解码方法在加
载内容时将试图重用一个存在的bitmap。这意味着bitmap的内存被重用,使得改善了性能,
消除了内存分配和回收。下面是使用inBitmap时需注意的事项:
1.重用的bitmap必须与源内容的大小相同,而且是JPEG和PNG的格式。
2.重用的bitmap将覆盖设置的inPreferredConfig。
3.你应该总是使用由decode方法返回的bitmap,因为你不能确定重用的bitmap是否能工作。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
|
HashSet<SoftReference<Bitmap>> mReusableBitmaps;
private
LruCache<String, BitmapDrawable> mMemoryCache;
// If you're running on Honeycomb or newer, create
// a HashSet of references to reusable bitmaps.
if
(Utils.hasHoneycomb()) {
mReusableBitmaps =
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()));
}
}
}
....
}
|
使用一个存在的bitmap
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
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);
}
|
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
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()) {
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;
}
|
01
02
03
04
05
06
07
08
09
|
private
static
boolean
canUseForInBitmap(
Bitmap candidate, BitmapFactory.Options targetOptions) {
int
width = targetOptions.outWidth / targetOptions.inSampleSize;
int
height = targetOptions.outHeight / targetOptions.inSampleSize;
// Returns true if "candidate" can be used for inBitmap re-use with
// "targetOptions".
return
candidate.getWidth() == width && candidate.getHeight() == height;
}
|
在UI中显示Bitmap
将Bitmap加载到ViewPager实现
一个交换的视图模式是浏览一个图片库详细信息视图很好的一个方式。你可以使用由PagerAdapter
和ViewPager来实现这种模式。然而,一个更合适的适配器是FragmentStatePagerAdapter,它
能自动销毁和保存在ViewPager中Fragments的状态。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
public
class
ImageDetailActivity
extends
FragmentActivity {
public
static
final
String EXTRA_IMAGE =
"extra_image"
;
private
ImagePagerAdapter mAdapter;
private
ViewPager mPager;
// A static dataset to back the ViewPager adapter
public
final
static
Integer[] imageResIds =
new
Integer[] {
R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
setContentView(R.layout.image_detail_pager);
// Contains just a ViewPager
mAdapter =
new
ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
mPager = (ViewPager) findViewById(R.id.pager);
mPager.setAdapter(mAdapter);
}
public
static
class
ImagePagerAdapter
extends
FragmentStatePagerAdapter {
private
final
int
mSize;
public
ImagePagerAdapter(FragmentManager fm,
int
size) {
super
(fm);
mSize = size;
}
@Override
public
int
getCount() {
return
mSize;
}
@Override
public
Fragment getItem(
int
position) {
return
ImageDetailFragment.newInstance(position);
}
}
}
|
下面是一个持有ImageView的详细Fragment的实现。这似乎是一个完全合理的做法,但你能
看到这种实现的缺点吗?如何进行改善?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
public
class
ImageDetailFragment
extends
Fragment {
private
static
final
String IMAGE_DATA_EXTRA =
"resId"
;
private
int
mImageNum;
private
ImageView mImageView;
static
ImageDetailFragment newInstance(
int
imageNum) {
final
ImageDetailFragment f =
new
ImageDetailFragment();
final
Bundle args =
new
Bundle();
args.putInt(IMAGE_DATA_EXTRA, imageNum);
f.setArguments(args);
return
f;
}
// Empty constructor, required as per Fragment docs
public
ImageDetailFragment() {}
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mImageNum = getArguments() !=
null
? getArguments().getInt(IMAGE_DATA_EXTRA) : -
1
;
}
@Override
public
View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// image_detail_fragment.xml contains just an ImageView
final
View v = inflater.inflate(R.layout.image_detail_fragment, container,
false
);
mImageView = (ImageView) v.findViewById(R.id.imageView);
return
v;
}
@Override
public
void
onActivityCreated(Bundle savedInstanceState) {
super
.onActivityCreated(savedInstanceState);
final
int
resId = ImageDetailActivity.imageResIds[mImageNum];
mImageView.setImageResource(resId);
// Load image into ImageView
}
}
|
问题是:图片在UI线程中被读取。使用一个AsyncTask。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
ImageDetailActivity
extends
FragmentActivity {
...
public
void
loadBitmap(
int
resId, ImageView imageView) {
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task =
new
BitmapWorkerTask(mImageView);
task.execute(resId);
}
...
// include BitmapWorkerTask class
}
public
class
ImageDetailFragment
extends
Fragment {
...
@Override
public
void
onActivityCreated(Bundle savedInstanceState) {
super
.onActivityCreated(savedInstanceState);
if
(ImageDetailActivity.
class
.isInstance(getActivity())) {
final
int
resId = ImageDetailActivity.imageResIds[mImageNum];
// Call out to ImageDetailActivity to load the bitmap in a background thread
((ImageDetailActivity) getActivity()).loadBitmap(resId, mImageView);
}
}
}
|
任何额外的处理都能在BitmapWorkerTask中进行,这不会影响UI的响应。
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
public
class
ImageDetailActivity
extends
FragmentActivity {
...
private
LruCache<String, Bitmap> mMemoryCache;
@Override
public
void
onCreate(Bundle savedInstanceState) {
...
// initialize LruCache as per Use a Memory Cache section
}
public
void
loadBitmap(
int
resId, ImageView imageView) {
final
String imageKey = String.valueOf(resId);
final
Bitmap bitmap = mMemoryCache.get(imageKey);
if
(bitmap !=
null
) {
mImageView.setImageBitmap(bitmap);
}
else
{
mImageView.setImageResource(R.drawable.image_placeholder);
BitmapWorkerTask task =
new
BitmapWorkerTask(mImageView);
task.execute(resId);
}
}
...
// include updated BitmapWorkerTask from Use a Memory Cache section
}
|
将Bitmaps加载到GridView中的实现
网格列表是用于显示图像的数据集,可以用一个GridView组件实现,其中很多图片能够同时
显示在屏幕上,当用户向上或向下滑动时,更多的图片需要准备好被显示。当实现这种控制
时,你必须确保UI处于活动,内存的使用仍然处于控制,并发被正确处理。
下面是一个包含ImageView子视图置于Fragment中的一个标准GridView。这看起来是一个很好
的方式,但是否有更好的选择?
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
|
public
class
ImageGridFragment
extends
Fragment
implements
AdapterView.OnItemClickListener {
private
ImageAdapter mAdapter;
// A static dataset to back the GridView adapter
public
final
static
Integer[] imageResIds =
new
Integer[] {
R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
// Empty constructor as per Fragment docs
public
ImageGridFragment() {}
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
mAdapter =
new
ImageAdapter(getActivity());
}
@Override
public
View onCreateView(
LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
final
View v = inflater.inflate(R.layout.image_grid_fragment, container,
false
);
final
GridView mGridView = (GridView) v.findViewById(R.id.gridView);
mGridView.setAdapter(mAdapter);
mGridView.setOnItemClickListener(
this
);
return
v;
}
@Override
public
void
onItemClick(AdapterView<?> parent, View v,
int
position,
long
id) {
final
Intent i =
new
Intent(getActivity(), ImageDetailActivity.
class
);
i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
startActivity(i);
}
private
class
ImageAdapter
extends
BaseAdapter {
private
final
Context mContext;
public
ImageAdapter(Context context) {
super
();
mContext = context;
}
@Override
public
int
getCount() {
return
imageResIds.length;
}
@Override
public
Object getItem(
int
position) {
return
imageResIds[position];
}
@Override
public
long
getItemId(
int
position) {
return
position;
}
@Override
public
View getView(
int
position, View convertView, ViewGroup container) {
ImageView imageView;
if
(convertView ==
null
) {
// if it's not recycled, initialize some attributes
imageView =
new
ImageView(mContext);
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(
new
GridView.LayoutParams(
LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
}
else
{
imageView = (ImageView) convertView;
}
imageView.setImageResource(imageResIds[position]);
// Load image into ImageView
return
imageView;
}
}
}
|
上面实现方式的问题是图片在UI线程中被设置。虽然这可能对小的、简单的图像是可以接受
的,但如果要做任何额外的处理,你的UI就会使人有停顿的感觉。
在这里可以加入异步处理和告诉缓存的方案。然而,你也需要警惕并发问题,GridView循环
回收子视图。为了解决这个问题,使用上一节中使用的技术:
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
|
public
class
ImageGridFragment
extends
Fragment
implements
AdapterView.OnItemClickListener {
...
private
class
ImageAdapter
extends
BaseAdapter {
...
@Override
public
View getView(
int
position, View convertView, ViewGroup container) {
...
loadBitmap(imageResIds[position], imageView)
return
imageView;
}
}
public
void
loadBitmap(
int
resId, ImageView imageView) {
if
(cancelPotentialWork(resId, imageView)) {
final
BitmapWorkerTask task =
new
BitmapWorkerTask(imageView);
final
AsyncDrawable asyncDrawable =
new
AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(resId);
}
}
static
class
AsyncDrawable
extends
BitmapDrawable {
private
final
WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public
AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super
(res, bitmap);
bitmapWorkerTaskReference =
new
WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public
BitmapWorkerTask getBitmapWorkerTask() {
return
bitmapWorkerTaskReference.get();
}
}
public
static
boolean
cancelPotentialWork(
int
data, ImageView imageView) {
final
BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if
(bitmapWorkerTask !=
null
) {
final
int
bitmapData = bitmapWorkerTask.data;
if
(bitmapData != data) {
// Cancel previous task
bitmapWorkerTask.cancel(
true
);
}
else
{
// The same work is already in progress
return
false
;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return
true
;
}
private
static
BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if
(imageView !=
null
) {
final
Drawable drawable = imageView.getDrawable();
if
(drawable
instanceof
AsyncDrawable) {
final
AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return
asyncDrawable.getBitmapWorkerTask();
}
}
return
null
;
}
...
// include updated BitmapWorkerTask class
|