Post

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
51
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 以上的提升;在用户直观体验上,这种加载速度的提升感会更加明显!

This post is licensed under CC BY 4.0 by the author.