Best Practise for Loading Bitmaps Into a Viewpager

在 Android 开发中,处理 Bitmap 一向需要小心翼翼,因为这块一旦弄得不好的话,轻者会造成应用卡顿,严重地会直接 OOM 或导致 ANR。今天在重构毕业设计引导界面时,使用了一个简单的优化技巧,使得应用的启动性能有了相当明显的改善。

这个页面的 ViewPager 所带动的 Fragment 的布局里含有一个 ImageView,原来在 Fragment 里加载图片时是这么处理的:

1
2
3
4
5
6
7
8
9
10
11
12
public void onViewCreated(View view, Bundle savedInstanceState) {
      // TODO Auto-generated method stub
      super.onViewCreated(view, savedInstanceState);
      mImageView = (ImageView) view.findViewById(R.id.parallax_imageview);
      if (getArguments() != null) {
          if (!getArguments().containsKey("extra_image_id"))
              mImageView.setVisibility(View.GONE);

          //Poor performance!!!
          mImageView.setImageResource(getArguments().getInt("extra_image_id"));
      }
}

不久之后,这个写法糟糕的启动时性能就被许总发现了。于是我仔细分析了一下,整个引导界面有4页,每页的图片文件都在700-800K左右,如果像上面这样写,在 pager 数目较少或者图片较小的情况下问题不大(但一定会造成首次启动时 load 这些图片资源阻塞住 UI 线程),一旦 pager 数量多了或者用户机器性能不行时,load 这些图片的时间要是过了5秒,就直接 ANR 了。所以,最好的办法就是让整个加载过程「Get off the fuckingUI Thread」!

首先,需要一个 BitmapUtils.java 工具类,它完成了 Bitmap 的二次压缩过程

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
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;

/**
 * New skill to optimize bitmap performance
 * 
 * @author linshen
 * @since 2014/04/07
 */
public class BitmapUtils {

  public static Bitmap decodeSampledBitmapFromResource(Resources res,
          int resId, int reqWidth, int reqHeight) {

      // First decode with inJustDecodeBounds=true to check dimensions
      final BitmapFactory.Options options = new BitmapFactory.Options();
      options.inJustDecodeBounds = true;
      BitmapFactory.decodeResource(res, resId, options);

      // Calculate inSampleSize
      options.inSampleSize = calculateInSampleSize(options, reqWidth,
              reqHeight);

      // Decode bitmap with inSampleSize set
      options.inJustDecodeBounds = false;
      return BitmapFactory.decodeResource(res, resId, options);
  }

  public static int calculateInSampleSize(BitmapFactory.Options options,
          int reqWidth, int reqHeight) {
      // Raw height and width of image
      final int height = options.outHeight;
      final int width = options.outWidth;
      int inSampleSize = 1;

      if (height > reqHeight || width > reqWidth) {

          final int halfHeight = height / 2;
          final int halfWidth = width / 2;

          while ((halfHeight / inSampleSize) > reqHeight
                  && (halfWidth / inSampleSize) > reqWidth) {
              inSampleSize *= 2;
          }
      }

      return inSampleSize;
  }
}

接着,需要在 Viewpager 对应的 Activity 里写一个异步任务,用来完成 Bitmap 的加载,like this:

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
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {

  private final WeakReference<ImageView> imageViewReference;
  private int data = 0;

  public BitmapWorkerTask(ImageView imageView) {
      imageViewReference = new WeakReference<ImageView>(imageView);
  }

  @Override
  protected Bitmap doInBackground(Integer... params) {
      data = params[0];
      return BitmapUtils.decodeSampledBitmapFromResource(getResources(),
              data, 100, 100);
  }

  @Override
  protected void onPostExecute(Bitmap bitmap) {
      // TODO Auto-generated method stub
      if (imageViewReference != null && bitmap != null) {
          final ImageView imageView = imageViewReference.get();
          if (imageView != null) {
              imageView.setImageBitmap(bitmap);
          }
      }
  }
}

同时,在 Activity 的类体中提供一个 loadBitmap方法,like this:

1
2
3
4
public void loadBitmap(int resId, ImageView imageView) {
      BitmapWorkerTask task = new BitmapWorkerTask(imageView);
      task.execute(resId);
  }

最后,重构一下 Fragment 里的方法:

mImageView.setImageResource(getArguments().getInt("extra_image_id"));

Write like this:

1
2
((WelcomeActivity) getActivity()).loadBitmap(
                  getArguments().getInt("extra_image_id"), mImageView);

实践表示:数据上,这种优化至少可带来 0.8s 以上的提升;在用户直观体验上,这种加载速度的提升感会更加明显!

Comments

Copyright © 2014 - 2017 - linshen - @ . +