这节课将我们前面几节课学习的东西都整合起来,向你展示如何使用后台线程和Bitmap缓存加载多个Bitmap(位图)到ViewPager和GridView组件中,并学习如何处理并发和配置变化问题。
实现加载Bitmap到ViewPager
滑动浏览模式(Swipe View Pattern )是一种很好的浏览详细图片的方式。你可以使用ViewPager 组件配合PagerAdapter (适配器)来实现这种模式。然而,更加合适的适配器是FragmentStatePagerAdapter ,它可以在ViewPager 退出屏幕的时候自动销毁并存储Fragments 的状态,使得内存依然保留下来。
注意 : 如果你只有少量的图片,并且确信它们不会超出程序的内存限制,使用常规的 PagerAdapter 或者FragmentPagerAdapter或许更加合适。
这里有一个包含ImageView的ViewPager的实现类,Main Activity(主活动)持有这个ViewPager和Adapter
public class ImageDetailActivity extends FragmentActivity {
02 public static final String EXTRA_IMAGE = "extra_image";
04 private ImagePagerAdapter mAdapter;
05 private ViewPager mPager;
08 public final static Integer[] imageResIds = new Integer[] {
09 R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
10 R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
11 R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
14 public void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 setContentView(R.layout.image_detail_pager);
18 mAdapter = new ImagePagerAdapter(getSupportFragmentManager(), imageResIds.length);
19 mPager = (ViewPager) findViewById(R.id.pager);
20 mPager.setAdapter(mAdapter);
23 public static class ImagePagerAdapter extends FragmentStatePagerAdapter {
24 private final int mSize;
26 public ImagePagerAdapter(FragmentManager fm, int size) {
32 public int getCount() {
37 public Fragment getItem(int position) {
38 return ImageDetailFragment.newInstance(position);
这里有一个用来持有ImageView并显示详细信息的Fragment的实现类。看起来这似乎是非常合理的方法,但是你能否看到这个方案的缺点呢?应该如何改善它呢?
01 public class ImageDetailFragment extends Fragment {
02 private static final String IMAGE_DATA_EXTRA = "resId";
03 private int mImageNum;
04 private ImageView mImageView;
06 static ImageDetailFragment newInstance(int imageNum) {
07 final ImageDetailFragment f = new ImageDetailFragment();
08 final Bundle args = new Bundle();
09 args.putInt(IMAGE_DATA_EXTRA, imageNum);
15 public ImageDetailFragment() {}
18 public void onCreate(Bundle savedInstanceState) {
19 super.onCreate(savedInstanceState);
20 mImageNum = getArguments() != null ? getArguments().getInt(IMAGE_DATA_EXTRA) : -1;
24 public View onCreateView(LayoutInflater inflater, ViewGroup container,
25 Bundle savedInstanceState) {
27 final View v = inflater.inflate(R.layout.image_detail_fragment, container, false);
28 mImageView = (ImageView) v.findViewById(R.id.imageView);
33 public void onActivityCreated(Bundle savedInstanceState) {
34 super.onActivityCreated(savedInstanceState);
35 final int resId = ImageDetailActivity.imageResIds[mImageNum];
36 mImageView.setImageResource(resId);
希望你能注意到:这些图片是在UI线程从资源中读取过来的,而这极有可能导致应用挂起甚至被强制关闭。使用在“非UI线程处理Bitmap ”一课中提到的AsyncTask ,直接将图片加载和处理移到后台线程中。
任何额外的处理(例如调整大小或者从网络获取图片)可以放在BitmapWorkerTask中而不会影响到主UI线程的响应性。如果后台线程做的不仅仅是直接从硬盘直接加载图片,那么如“缓存Bitmap”一课中说的,将图片缓存到内存或者硬盘是有利于程序优化的。这里是对内存缓存的一些额外修改:
01 public class ImageDetailActivity extends FragmentActivity {
03 private LruCache<String, Bitmap> mMemoryCache;
06 public void onCreate(Bundle savedInstanceState) {
11 public void loadBitmap(int resId, ImageView imageView) {
12 final String imageKey = String.valueOf(resId);
14 final Bitmap bitmap = mMemoryCache.get(imageKey);
16 mImageView.setImageBitmap(bitmap);
18 mImageView.setImageResource(R.drawable.image_placeholder);
19 BitmapWorkerTask task = new BitmapWorkerTask(mImageView);
将上面的代码片段整合在一起会让你的ViewPager具备优良的响应性能,可以实现最小的加载延迟,根据你的图片加载需要或多或少的进行后台处理。
实现加载Bitmap到GridView
网格列表控件(Grid List Building Block )对于显示图片数据集非常有用,也可以使用GridView组件来实现,如果用户上下滚动的话,有很多图片处于就绪状态,随时可以显示在屏幕上。如果要实现这种类型的控制,你必须确保UI保持流畅性,内存使用处于控制之中而且并发也要被正确地处理(取决于GridView回收子视图的方式)。
首先,这里有一个标准的GridView实现,将ImageView子控件存放在Fragment中。我们再一次思考这个问题,这个方法看起来似乎非常完美且合乎情理,但是有没有办法让它便得更好呢?
01 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
02 private ImageAdapter mAdapter;
05 public final static Integer[] imageResIds = new Integer[] {
06 R.drawable.sample_image_1, R.drawable.sample_image_2, R.drawable.sample_image_3,
07 R.drawable.sample_image_4, R.drawable.sample_image_5, R.drawable.sample_image_6,
08 R.drawable.sample_image_7, R.drawable.sample_image_8, R.drawable.sample_image_9};
11 public ImageGridFragment() {}
14 public void onCreate(Bundle savedInstanceState) {
15 super.onCreate(savedInstanceState);
16 mAdapter = new ImageAdapter(getActivity());
20 public View onCreateView(
21 LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
22 final View v = inflater.inflate(R.layout.image_grid_fragment, container, false);
23 final GridView mGridView = (GridView) v.findViewById(R.id.gridView);
24 mGridView.setAdapter(mAdapter);
25 mGridView.setOnItemClickListener(this);
30 public void onItemClick(AdapterView<?> parent, View v, int position, long id) {
31 final Intent i = new Intent(getActivity(), ImageDetailActivity.class);
32 i.putExtra(ImageDetailActivity.EXTRA_IMAGE, position);
36 private class ImageAdapter extends BaseAdapter {
37 private final Context mContext;
39 public ImageAdapter(Context context) {
45 public int getCount() {
46 return imageResIds.length;
50 public Object getItem(int position) {
51 return imageResIds[position];
55 public long getItemId(int position) {
60 public View getView(int position, View convertView, ViewGroup container) {
62 if (convertView == null) {
63 imageView = new ImageView(mContext);
64 imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
65 imageView.setLayoutParams(new GridView.LayoutParams(
66 LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT));
68 imageView = (ImageView) convertView;
70 imageView.setImageResource(imageResIds[position]);
当然,问题还是这个方法在UI线程中处理图片。这种方式或许是和处理小而简单的图片(系统资源的加载和缓存),如果需要做任何的处理,UI就会被阻塞(甚至引起ANR(Application Not Responding) )。
和前一节相同的处理方式,我们在异步线程中进行处理和缓存。然而,考虑到GridView回收子视图的方式,你需要谨慎处理并发问题。可以使用“在非UI线程中处理Bitmap ”一课中提到的技巧。这里是更新的解决方案:
01 public class ImageGridFragment extends Fragment implements AdapterView.OnItemClickListener {
04 private class ImageAdapter extends BaseAdapter {
08 public View getView(int position, View convertView, ViewGroup container) {
10 loadBitmap(imageResIds[position], imageView)
15 public void loadBitmap(int resId, ImageView imageView) {
16 if (cancelPotentialWork(resId, imageView)) {
17 final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
18 final AsyncDrawable asyncDrawable =
19 new AsyncDrawable(getResources(), mPlaceHolderBitmap, task);
20 imageView.setImageDrawable(asyncDrawable);
25 static class AsyncDrawable extends BitmapDrawable {
26 private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
28 public AsyncDrawable(Resources res, Bitmap bitmap,
29 BitmapWorkerTask bitmapWorkerTask) {
31 bitmapWorkerTaskReference =
32 new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
35 public BitmapWorkerTask getBitmapWorkerTask() {
36 return bitmapWorkerTaskReference.get();
40 public static boolean cancelPotentialWork(int data, ImageView imageView) {
41 final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
43 if (bitmapWorkerTask != null) {
44 final int bitmapData = bitmapWorkerTask.data;
45 if (bitmapData != data) {
47 bitmapWorkerTask.cancel(true);
57 private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
58 if (imageView != null) {
59 final Drawable drawable = imageView.getDrawable();
60 if (drawable instanceof AsyncDrawable) {
61 final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
62 return asyncDrawable.getBitmapWorkerTask();
注意: 相同的代码也可以很好的适配ListView。
这里的实现方法允许灵活地处理和加载图片,并且不会影响UI的流畅性。在后台线程中,你可以从网络加载图片,调整大幅的数码相机照片的大小,并在处理任务结束的时候将图片显示在UI界面中。
转至:http://my.oschina.net/ryanhoo/blog/88484