AndroidにおけるGridviewとViewPager表示ピクチャの最適化処理(1)


1.GridView表示画像
チャットアプリには携帯電話のすべての画像が表示され、ユーザーが閲覧して送信するように選択されることが多いが、表示時にOOMを招くことが多い.Androidシステムでは、1つのアプリケーションに16 Mのメモリが割り当てられますが、数KBサイズの画像1枚がメモリにロードされると、数Mのサイズがある可能性があり、数枚の画像をロードするとOOMになる可能性があります.次に、画像のロード、参照、表示の3つの面から、画像表示の問題を解決します.
(1)ロード
1枚のピクチャをロードすると、バイトストリームでピクチャがメモリに読み出され、オペレーティングシステムがプログラムに割り当てたメモリの占有が開始されます.しかし、インタフェース表示ピクチャのサイズによっては、ピクチャの真のサイズをロードする必要はないため、通常、ロード時にピクチャを一定の割合で圧縮することができる.
   
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;
	}
}

  opts.inJustDecodeBounds = true;画像をクエリーできる情報を示しますが、メモリは割り当てられません.これにより、ピクチャをロードする前にピクチャサイズとインタフェースに基づいてピクチャサイズを表示して比較することができる.inSampleSizeは縮小の割合を表し、例:inSampleSize=4はwidth/heightが元の1/4になることを表し、もちろんinSampleSize>1のみがメモリを節約し、inSampleSize<=1の場合は1とみなされる.inPreferredConfigはBitmapをロードするカラーモードを表し、カラーモードは4種類あります:ARGB_8888(1ピクセル4バイト)、ARGB_4444(1ピクセル2バイト)、RGB_565(1ピクセル2バイト)、ALPHA_8(1ピクセル1バイト)、デフォルトでロードされるカラーモードは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は内部実装がLinkedHashMapクラスであり、ロードされたBitmapを管理および記録することができるLruCacheクラスを提供しています(オペレーティングシステムのLRUページングアルゴリズムを参照できます).
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();
			}
		};

テストの過程で、WeakHashMapクラスでBitmapを管理しても、効果はあまりありません.
(3)表示
表示効率を向上させるため、GridViewでスライドする場合は、ピクチャをロードせず、スライドを停止した場合にピクチャをロードするので、OnScrolListenerインタフェースを実現することができる.
     @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);
                   }
          }

Bitmapの回収後、再度表示するとjavaが現れる.lang.IllegalArgumentException:Cannot draw recycled bitmaps異常Bitmapの再利用によるもので、ImageViewをカスタマイズし、onDraw()メソッドで異常をキャプチャするだけでよい.
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();
		}
	}

}

また、通常は、アルバムのようにピクチャを表示することが望ましいが、ピクチャの幅と高さは等しいので、ViewのonMeasure()メソッドでは高さと幅を等しく設定する.onMeasure()メソッドは主にViewのサイズを計算するために用いられる.
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);
	}

}

GridViewエントリの幅の大きさが分からないため,position=0を複数回繰り返し呼び出した場合のgetView()メソッドが起こり,実際の開発ではエントリの幅を固定することができる.
これでGridViewを使って画像を表示する問題はすべて解決し、サムスン、ソニー、レノボ、海信、クール派などの携帯電話でテストしてもメモリが溢れてプログラムがクラッシュすることはありません.唯一問題が発生する携帯電話は小米で、一部は崩壊し、一部はできない.今でもテストで解決しています.
第1版ソースコードダウンロードアドレス:http://download.csdn.net/detail/wangjiang_qianmo/8789451
第2版ソースコードダウンロードアドレス:http://download.csdn.net/detail/wangjiang_qianmo/9089665fd
このプロジェクトは私のGitHubに移行して、今のこのバージョンは第1版と第2版に比べて、すでに多くの問題を最適化して解決して、私のGitHubにダウンロードして、住所をダウンロードすることができます:https://github.com/WJRye/Album-for-Android-Studio.git