Displaying Bitmaps in Your UI

本文介绍如何使用后台线程和缓存机制优化图片加载过程,确保应用程序的流畅性和资源的有效利用。涵盖图片加载到ViewPager和GridView组件的最佳实践。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

This lesson brings together everything from previous lessons, showing you how to load multiple bitmaps intoViewPager and GridView components using a background thread and bitmap cache, while dealing with concurrency and configuration changes.

Load Bitmaps into a ViewPager Implementation


The swipe view pattern is an excellent way to navigate the detail view of an image gallery. You can implement this pattern using a ViewPager component backed by aPagerAdapter. However, a more suitable backing adapter is the subclass FragmentStatePagerAdapter which automatically destroys and saves state of the Fragmentsin the ViewPager as they disappear off-screen, keeping memory usage down.

Note: If you have a smaller number of images and are confident they all fit within the application memory limit, then using a regular PagerAdapter orFragmentPagerAdapter might be more appropriate.

Here’s an implementation of a ViewPager with ImageView children. The main activity holds theViewPager and the adapter:

译:

这一课会演示如何运用前面几节课的内容,使用后台线程与Cache机制来加载图片到 ViewPager 与 GridView 组件,并且学习处理并发与配置改变问题。

实现加载图片到ViewPager(Load Bitmaps into a ViewPager Implementation)

swipe view pattern是一个用来切换显示不同详情界面的很好的方法。(关于这种效果请先参看Android Design: Swipe Views).

你可以通过 PagerAdapter 与 ViewPager 组件来实现这个效果。 然而,一个更加合适的Adapter是PagerAdapter 的子类 FragmentStatePagerAdapter:它可以在某个ViewPager中的子视图切换出屏幕时自动销毁与保存 Fragments 的状态。这样能够保持消耗更少的内存。

Note: 如果你只有为数不多的图片并且确保不会超出程序内存限制,那么使用 PagerAdapter 或 FragmentPagerAdapter 会更加合适。

下面是一个使用ViewPager与ImageView作为子视图的示例。主Activity包含有ViewPager 和adapter。

1
2
3
4
5
6
7
8
9
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);
}
}
}

Here is an implementation of the details Fragment which holds the ImageView children. This might seem like a perfectly reasonable approach, but can you see the drawbacks of this implementation? How could it be improved?

1
2
3
4
5
6
7
8
9
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];
<strong>mImageView.setImageResource(resId);</strong> // Load image into ImageView
}
}

Hopefully you noticed the issue: the images are being read from resources on the UI thread, which can lead to an application hanging and being force closed. Using an AsyncTask as described in theProcessing Bitmaps Off the UI Thread lesson, it’s straightforward to move image loading and processing to a background thread:

1
2
3
4
5
6
7
8
9
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);
}
 
}
 
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);
}
}
}

Any additional processing (such as resizing or fetching images from the network) can take place in the BitmapWorkerTask without affecting responsiveness of the main UI. If the background thread is doing more than just loading an image directly from disk, it can also be beneficial to add a memory and/or disk cache as described in the lesson Caching Bitmaps. Here’s the additional modifications for a memory cache:

1
2
3
4
5
6
7
8
9
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) {
...
}
 
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 <a href="file:///D:/Users/Administrator/AppData/Local/Android/sdk/docs/training/displaying-bitmaps/cache-bitmap.html#memory-cache">Use a Memory Cache</a> section
}

Load Bitmaps into a GridView Implementation


The grid list building block is useful for showing image data sets and can be implemented using aGridView component in which many images can be on-screen at any one time and many more need to be ready to appear if the user scrolls up or down. When implementing this type of control, you must ensure the UI remains fluid, memory usage remains under control and concurrency is handled correctly (due to the way GridView recycles its children views).

To start with, here is a standard GridView implementation with ImageView children placed inside aFragment. Again, this might seem like a perfectly reasonable approach, but what would make it better?

译:

Grid list building block 是一种有效显示大量图片的方式。这样能够一次显示许多图片,而且那些即将被显示的图片也处于准备显示状态。如果你想要实现这种效果,你必须确保UI是流畅的,能够控制内存使用,并且正确的处理并发问题(因为 GridView 会循环使用子视图)。

下面是一个在Fragment里面内置了ImageView作为GridView子视图的示例:

Once again, the problem with this implementation is that the image is being set in the UI thread. While this may work for small, simple images (due to system resource loading and caching), if any additional processing needs to be done, your UI grinds to a halt.

The same asynchronous processing and caching methods from the previous section can be implemented here. However, you also need to wary of concurrency issues as the GridView recycles its children views. To handle this, use the techniques discussed in the Processing Bitmaps Off the UI Threadlesson. Here is the updated solution:

译:

又一次,这一个实现的问题是图片是在UI线程中被设置。当处理小的图片时可以,但其他需要额外操作的处理,都会使你的UI慢下来。

与前面加载到图片到ViewPager一样,如果setImageResource的操作会比较耗时,有可能会卡到UI Thread。可以使用类似前面异步处理图片与增加缓存的方法来解决那个问题。然而,我们还需要考虑GridView的循环机制所带来的并发问题。为了处理这个问题,请参考前面的课程 。下面是一个更新的解决方案:

1
2
3
4
5
6
7
8
9
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) {
...
<strong>loadBitmap(imageResIds[position], imageView)</strong>
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 ;
}
 

Note: The same code can easily be adapted to work with ListView as well.

This implementation allows for flexibility in how the images are processed and loaded without impeding the smoothness of the UI. In the background task you can load images from the network or resize large digital camera photos and the images appear as the tasks finish processing.

For a full example of this and other concepts discussed in this lesson, please see the included sample application.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值