Android中Gridview和ViewPager显示图片的优化处理(1)

1.GridView显示图片

  在聊天应用程序中经常会显示手机所有图片,供用户浏览选择发送,然而在显示时常常会导致OOM。虽然Android系统会给一个应用程序分配16M的内存,但是一张几KB大小的图片,加载到内存中时,可能会有几M的大小,这样加载几张图片就导致OOM。下面从图片的加载,引用和显示三方面来解决图片显示问题。

  (1)加载

 加载一张图片的时候,会以字节流的方式将图片读到内存中,这样就开始占用操作系统分给程序的内存。但是根据界面显示图片的大小,并不需要加载图片真正的大小,因此通常在加载的时候,可以对图片进行一定的比例压缩。

   

<span style="font-size:14px;">package com.example.album.utils;

import java.io.BufferedInputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

import android.annotation.SuppressLint;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Bitmap.Config;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import android.media.ExifInterface;

public class BitmapUtil {
	@SuppressLint("NewApi")
	public static final Bitmap compress(Context context, String uri,
			int reqWidth, int reqHeight) {
		Bitmap bitmap = null;
		try {
			BitmapFactory.Options opts = new BitmapFactory.Options();
			opts.inJustDecodeBounds = true;
			BitmapFactory.decodeStream(new BufferedInputStream(
					new FileInputStream(uri)), null, opts);
			int height = opts.outHeight;
			int width = opts.outWidth;
			int inSampleSize = 1;

			int degree = readPictureDegree(uri);
			if (degree == 0 || degree == 180) {
				if (width > height && width > reqWidth) {
					inSampleSize = Math.round((float) width / (float) reqWidth);
				} else if (height > width && height > reqHeight) {
					inSampleSize = Math.round((float) height
							/ (float) reqHeight);
				}
			} else if (degree == 90 || degree == 270) {
				// 图片有旋转时,宽和高调换了
				if (width > height && width > reqHeight) {
					inSampleSize = Math
							.round((float) width / (float) reqHeight);
				} else if (height > width && height > reqWidth) {
					inSampleSize = Math
							.round((float) height / (float) reqWidth);
				}
			}

			if (inSampleSize <= 1)
				inSampleSize = 1;
			opts.inSampleSize = inSampleSize;
			opts.inPreferredConfig = Config.RGB_565;
			opts.inPurgeable = true;
			opts.inInputShareable = true;
			opts.inTargetDensity = context.getResources().getDisplayMetrics().densityDpi;
			opts.inScaled = true;
			opts.inTempStorage = new byte[16 * 1024];
			opts.inJustDecodeBounds = false;
			bitmap = BitmapFactory.decodeStream(new BufferedInputStream(
					new FileInputStream(uri)), null, opts);
			// 处理旋转了一定角度的图片,比如有些机型拍出的照片默认旋转了90度的
			    if (bitmap != null) {
                // 处理旋转了一定角度的图片,比如有些机型拍出的照片默认旋转了90度的
                Matrix matrix = new Matrix();
                matrix.postRotate(degree);
                bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(),
                        bitmap.getHeight(), matrix, false);
            }
		} catch (FileNotFoundException e) {
			e.printStackTrace();
		} catch (OutOfMemoryError e) {
			// 防止内存溢出导致程序崩溃而强制退出
			e.printStackTrace();
		}
		return bitmap;
	}

	/**
	 * @param path
	 *            图片路径
	 * @return 图片旋转的度数
	 */
	private static int readPictureDegree(String path) {
		int degree = 0;
		try {
			ExifInterface exifInterface = new ExifInterface(path);
			int orientation = exifInterface.getAttributeInt(
					ExifInterface.TAG_ORIENTATION,
					ExifInterface.ORIENTATION_NORMAL);
			switch (orientation) {
			case ExifInterface.ORIENTATION_ROTATE_90:
				degree = 90;
				break;
			case ExifInterface.ORIENTATION_ROTATE_180:
				degree = 180;
				break;
			case ExifInterface.ORIENTATION_ROTATE_270:
				degree = 270;
				break;
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return degree;
	}
}</span>

  opts.inJustDecodeBounds = true;表示可以查询图片的信息,但不会分配内存。这样就可以在未加载图片之前根据图片大小和界面显示图片大小进行对比。inSampleSize表示缩小的比例,例:inSampleSize=4表示width/height变为原来的1/4,当然只有inSampleSize>1才会节约内存,inSampleSize<=1时会被当成是1。inPreferredConfig表示加载Bitmap的色彩模式,色彩模式有四种:ARGB_8888(一个像素四个字节),ARGB_4444(一个像素两个字节),RGB_565(一个像素两个字节),ALPHA_8(一个像素一个字节),默认加载的色彩模式是ARGB_8888,这种色彩模式显示质量高,但是占用内存大,所以应该选择ARGB_4444,RGB_565,显示质量一般,内存占用不大的色彩模式。BitmapFactory有很多decode的方法,最好用BitmapFactory.decodeStream()或者BitmapFactory.decodeByteArray(),其它的方法会间接调用decodeStream()方法。最后,计算完缩小比例和设置各种参数之后,应将opts.inJustDecodeBounds = false,否则返回的Bitmap对象为null。

  (2)引用

  在创建一个Bitmap对象的时候,不仅Java代码会分配一段内存,而且C代码也会分配一段内存,因此在不用Bitmap对象的时候,一定要调用recycle()方法,释放C代码分配的内存。在显示过多的图片的时候,Android提供了一个LruCache类,该类是内部实现是LinkedHashMap类,可以用该类来管理和记录加载的Bitmap(可以参考操作系统的LRU分页算法)。

<span style="font-size:14px;">private LruCache<String, Bitmap> mLruCach;

mLruCache = new LruCache<String, Bitmap>((int) Runtime.getRuntime()
				.maxMemory() / 8) {
			@Override
			protected int sizeOf(String key, Bitmap value) {
				return value.getByteCount();
			}
		};</span>

  在测试的过程中,用WeakHashMap类来管理Bitmap,效果也差不多。

  (3)显示

  为了提高显示效率,在GridView滑动的时候,不要去加载图片,当停止滑动的时候再加载图片,因此可以实现OnScrollListener接口。

<span style="font-size:14px;">     @SuppressWarnings("unchecked")
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
            //停止滑动,但刚进入页面的时候,不会调用该方法
            case OnScrollListener.SCROLL_STATE_IDLE:
                // 取消未加载完的任务
                for (BitmapAsyncTask bat : bitmapAsyncTasks) {
                    if (bat != null
                            && bat.getStatus() != AsyncTask.Status.FINISHED) {
                        bat.cancel(true);
                        bat = null;
                    }
                }
                //开始加载当前页面要显示的图片
                for (int i = 0; i < mVisibleItemCount; i++) {
                    String uri = uris.get(mFirstVisibleItem + i);
                    ViewHolder viewHolder = (ViewHolder) view.getChildAt(i)
                            .getTag();
                    if (lruCache.get(uri) == null) {
                        BitmapAsyncTask bitmapAsyncTask = new BitmapAsyncTask(
                                context,
                                viewHolder.picture,
                                uri,
                                new int[] {
                                        viewHolder.picture.getMeasuredWidth(),
                                        viewHolder.picture.getMeasuredHeight() });
                        bitmapAsyncTasks.add(bitmapAsyncTask);
                        bitmapAsyncTask.execute(lruCache);
                    } else {
                        viewHolder.picture.setImageBitmap(lruCache.get(uri));
                    }
                }
                break;
            //正在滑动
            case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                if (!isScroll) {
                    isScroll = true;
                }
                break;
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            mFirstVisibleItem = firstVisibleItem;
            mVisibleItemCount = visibleItemCount;
            mTotalItemCount = totalItemCount;
        }
        如果内存实在很有限,那么可以在停止滑动之后,清空除当前页面图片占用的内存。
        for (int j = 0; j < mFirstVisibleItem; j++) {
              String uri = uris.get(j);
              Bitmap bitmap = lruCache.get(uri);
              if (bitmap != null && !bitmap.isRecycled()) {
                  bitmap.recycle();
                  bitmap = null;
                  lruCache.remove(uri);
              }
         }
         int invisibleAfter = mFirstVisibleItem + mVisibleItemCount;
         for (int k = invisibleAfter; k < mTotalItemCount; k++) {
              String uri = uris.get(k);
              Bitmap bitmap = lruCache.get(uri);
              if (bitmap != null && !bitmap.isRecycled()) {
                   bitmap.recycle();
                   bitmap = null;
                   lruCache.remove(uri);
                   }
          }</span>

  当Bitmap回收之后,再次显示时会出现java.lang.IllegalArgumentException: Cannot draw recycled bitmaps异常,这是Bitmap的重用导致的,只需自定义ImageView,并在onDraw()方法中捕获异常就行了。

<span style="font-size:14px;">package com.example.album.view;

import android.content.Context;
import android.graphics.Canvas;
import android.util.AttributeSet;
import android.widget.ImageView;

public class MyImageView extends ImageView {

	public MyImageView(Context context) {
		super(context);
	}

	public MyImageView(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public MyImageView(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@Override
	protected void onDraw(Canvas canvas) {
		try {
			super.onDraw(canvas);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

}
</span>

  另外,通常情况下,希望按照相册那样显示图片,图片的宽和高都相等,因此在View的onMeasure()方法中设置高度和宽度相等。onMeasure()方法主要是用做计算View的大小。

<span style="font-size:14px;">package com.example.album.view;

import android.content.Context;
import android.util.AttributeSet;
import android.widget.FrameLayout;

public class SqaureLayout extends FrameLayout {

	public SqaureLayout(Context context) {
		super(context);
	}

	public SqaureLayout(Context context, AttributeSet attrs, int defStyle) {
		super(context, attrs, defStyle);
	}

	public SqaureLayout(Context context, AttributeSet attrs) {
		super(context, attrs);
	}

	@SuppressWarnings("unused")
	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		// For simple implementation, or internal size is always 0.
		// We depend on the container to specify the layout size of
		// our view. We can't really know what it is since we will be
		// adding and removing different arbitrary views and do not
		// want the layout to change as this happens.
		setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
				getDefaultSize(0, heightMeasureSpec));

		// Children are just made to fill our space.
		int childWidthSize = getMeasuredWidth();
		int childHeightSize = getMeasuredHeight();
		// set height and width
		heightMeasureSpec = widthMeasureSpec = MeasureSpec.makeMeasureSpec(
				childWidthSize, MeasureSpec.EXACTLY);
		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

}</span>

  由于GridView条目的宽高大小不知道,所以会引起多次重复调用position=0时的getView()方法,在实际开发中可以固定条目的宽高。

  到此,使用GridView显示图片的问题都已经解决,在三星、索尼、联想、海信、酷派等手机上测试都不会出现内存溢出导致程序崩溃。唯一会出现问题的手机是小米,有些会出现崩溃,有些不会。现在还在测试解决。

  源代码下载地址:Android中使用GridView和ViewPager显示图片的优化处理

         本项目迁移到了我的GitHub上,可以到我的GitHub上去下载,下载地址:https://github.com/WJRye/Album-for-Android-Studio.git

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值