BaseAdapter解决OOM问题

来源:互联网 发布:hdmi网络传输器 音频 编辑:程序博客网 时间:2024/06/14 08:21

           上次提到了本地图片的异步加载奋斗,但是当图片量过大的时候就会出现令人头痛的OOM尴尬。后来查到通过ViewHolder可以优化Adapter,然后就搞了半天。弄好了以后发现还真心不错,而且还优化了上下滑动时卡顿现象,当时贼高兴了得意。再后来,我就狠心的不停上下滑,啊哦、、、OOM又出现了安静。最后得出结论--------有时候还真心不能对自个的程序太狠。所以,要狠就要对自个的代码狠,这次采用异步加载图片+ViewHolder+缓存。嘎嘎、、、话不多说直接上图上代码。

      运行结果(滑动效果就不弄了。太麻烦)

              

          1、主界面LookAllPhotoActivity.java

import java.io.File;import java.util.ArrayList;import java.util.List;import com.phont.R;import com.phont.custometoast.CustomToast;import android.app.Activity;import android.os.Bundle;import android.os.Environment;import android.widget.GridView;public class LookAllPhotoActivity extends Activity {// 要查看的指定目录下的图片public static String mImgDir = Environment.getExternalStorageDirectory().toString() + "/StuPhotoInfo/";/** * 所有的图片 */private List<String> mImgs;private GridView mGirdView;private MyAdapter mAdapter;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.look_all_photo_activity);findView();getImageName();// 得到指定目录下的所有图片名称if (mImgs != null) {setAdapter();} else {CustomToast.showToast(LookAllPhotoActivity.this, "没有图片", 2000);}}private void setAdapter() {mAdapter = new MyAdapter(getApplicationContext(), mImgs,R.layout.grid_item, mImgDir);mGirdView.setAdapter(mAdapter);}private void findView() {mGirdView = (GridView) findViewById(R.id.id_gridView);}/** * 得到指定目录下的所有图片名称 */private void getImageName() {File file = new File(mImgDir);if (!file.exists()) {file.mkdirs();}if (file != null) {File[] files = file.listFiles();if (files.length > 0) {mImgs = new ArrayList<String>();for (int i = 0; i < files.length; i++) {String imgPath = files[i].getPath();String imgName = imgPath.substring(imgPath.lastIndexOf("/") + 1);mImgs.add(imgName);}}}}}
        2、主界面布局文件look_all_photo_activity.xml
<?xml version="1.0" encoding="utf-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="match_parent"    android:layout_height="match_parent" >    <GridView        android:id="@+id/id_gridView"        android:layout_width="match_parent"        android:layout_height="match_parent"        android:layout_marginLeft="3dp"        android:layout_marginRight="3dp"        android:layout_marginTop="3dp"        android:cacheColorHint="@android:color/transparent"        android:clipChildren="true"        android:columnWidth="80dp"        android:fastScrollEnabled="true"        android:gravity="center"        android:horizontalSpacing="3dip"        android:listSelector="@android:color/transparent"        android:numColumns="auto_fit"        android:stretchMode="columnWidth"        android:verticalSpacing="3dip" >    </GridView></RelativeLayout>
             关于GridView的一些属性设置(我就不在上面注释了):

               1.android:numColumns=”auto_fit”   //GridView的列数设置为自动

           2.android:columnWidth=”90dp "       //每列的宽度,也就是Item的宽度

           3.android:stretchMode=”columnWidth"//缩放与列宽大小同步

           4.android:verticalSpacing=”10dp”          //两行之间的边距

           5.android:horizontalSpacing=”10dp”      //两列之间的边距 

           6.android:cacheColorHint="#00000000" //去除拖动时默认的黑色背景

           7.android:listSelector="#00000000"        //去除选中时的黄色底色

           8.android:scrollbars="none"                   //隐藏GridView的滚动条

           9.android:fadeScrollbars="true"             //设置为true就可以实现滚动条的自动隐藏和显示

           10.android:fastScrollEnabled="true"      //GridView出现快速滚动的按钮(至少滚动4页才会显示)

           11.android:fadingEdge="none"                //GridView衰落(褪去)边缘颜色为空,缺省值是vertical。(可以理解为上下边缘的提示色)

           12.android:fadingEdgeLength="10dip"   //定义的衰落(褪去)边缘的长度

           13.android:stackFromBottom="true"       //设置为true时,你做好的列表就会显示你列表的最下面

           14.android:transcriptMode="alwaysScroll" //当你动态添加数据时,列表将自动往下滚动最新的条目可以自动滚动到可视范围内

           15.android:drawSelectorOnTop="false"  //点击某条记录不放,颜色会在记录的后面成为背景色,内容的文字可见(缺省为false)

              3、通用的BaseAdapter类CommonAdapter.java
import java.util.List;import android.content.Context;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;/** * 通用的BaseAdapter *  * @param T *            数据类型 */public abstract class CommonAdapter<T> extends BaseAdapter {protected LayoutInflater mInflater;protected Context mContext;protected List<T> mDatas;protected final int mItemLayoutId;public CommonAdapter(Context context, List<T> mDatas, int itemLayoutId) {this.mContext = context;this.mInflater = LayoutInflater.from(mContext);this.mDatas = mDatas;this.mItemLayoutId = itemLayoutId;}@Overridepublic int getCount() {return mDatas.size();}@Overridepublic T getItem(int position) {return mDatas.get(position);}@Overridepublic long getItemId(int position) {return position;}@Overridepublic View getView(int position, View convertView, ViewGroup parent) {final ViewHolder viewHolder = getViewHolder(position, convertView,parent);convert(viewHolder, getItem(position));return viewHolder.getConvertView();}public abstract void convert(ViewHolder helper, T item);private ViewHolder getViewHolder(int position, View convertView,ViewGroup parent) {return ViewHolder.get(mContext, convertView, parent, mItemLayoutId,position);}}
        接下来就是主角上场了异步加载图片+ViewHolder+缓存生气

           初始化数据的MyAdapter.java

import java.util.List;import android.content.Context;import android.view.View;import android.view.View.OnClickListener;import android.widget.ImageView;import com.phont.R;import com.phont.utils.CommonAdapter;import com.phont.utils.ViewHolder;public class MyAdapter extends CommonAdapter<String> {/** * 文件夹路径 */private String mDirPath;public MyAdapter(Context context, List<String> mDatas, int itemLayoutId,String dirPath) {super(context, mDatas, itemLayoutId);this.mDirPath = dirPath;}@Overridepublic void convert(ViewHolder helper, final String item) {// 设置默认图片no_pichelper.setImageResource(R.id.id_item_image, R.drawable.pictures_no);// 设置异步加载的图片helper.setImageByUrl(R.id.id_item_image, mDirPath + item);ImageView mImageView = helper.getView(R.id.id_item_image);// 为每个图片添加监听事件mImageView.setOnClickListener(new OnClickListener() {@Overridepublic void onClick(View v) {}});// 设置图片名字helper.setText(R.id.id_item_name, item);}}
       每个Item的布局grid_item.xml
<?xml version="1.0" encoding="UTF-8"?><RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:layout_width="fill_parent"    android:layout_height="fill_parent" >    <ImageView        android:id="@+id/id_item_image"        android:layout_width="80dp"        android:layout_height="120dp"        android:layout_marginTop="2dp"        android:background="@drawable/pictures_no"        android:scaleType="centerCrop" />    <TextView        android:id="@+id/id_item_name"        android:layout_width="fill_parent"        android:layout_height="wrap_content"        android:layout_below="@id/id_item_image"        android:gravity="center_horizontal" /></RelativeLayout>
      异步加载本地图片ImageLoader.java
import java.lang.reflect.Field;import java.util.LinkedList;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Semaphore;import android.annotation.SuppressLint;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.Handler;import android.os.Looper;import android.os.Message;import android.support.v4.util.LruCache;import android.util.DisplayMetrics;import android.util.Log;import android.view.ViewGroup.LayoutParams;import android.widget.ImageView;public class ImageLoader {/** * 图片缓存的核心类 */private LruCache<String, Bitmap> mLruCache;/** * 线程池 */private ExecutorService mThreadPool;/** * 队列的调度方式 */private Type mType = Type.LIFO;/** * 任务队列 */private LinkedList<Runnable> mTasks;/** * 轮询的线程 */private Thread mPoolThread;private Handler mPoolThreadHander;/** * 运行在UI线程的handler,用于给ImageView设置图片 */private Handler mHandler;/** * 引入一个值为1的信号量,防止mPoolThreadHander未初始化完成 */private volatile Semaphore mSemaphore = new Semaphore(0);/** * 引入一个值为1的信号量,由于线程池内部也有一个阻塞线程,防止加入任务的速度过快,使LIFO效果不明显 */private volatile Semaphore mPoolSemaphore;private static ImageLoader mInstance;/** * 队列的调度方式 *  * @author zhy *  */public enum Type {FIFO, LIFO}/** * 单例获得该实例对象 *  * @return */public static ImageLoader getInstance() {if (mInstance == null) {synchronized (ImageLoader.class) {if (mInstance == null) {mInstance = new ImageLoader(1, Type.LIFO);}}}return mInstance;}private ImageLoader(int threadCount, Type type) {init(threadCount, type);}@SuppressLint("HandlerLeak")private void init(int threadCount, Type type) {// loop threadmPoolThread = new Thread() {@Overridepublic void run() {Looper.prepare();mPoolThreadHander = new Handler() {@Overridepublic void handleMessage(Message msg) {mThreadPool.execute(getTask());try {mPoolSemaphore.acquire();} catch (InterruptedException e) {}}};// 释放一个信号量mSemaphore.release();Looper.loop();}};mPoolThread.start();// 获取应用程序最大可用内存int maxMemory = (int) Runtime.getRuntime().maxMemory();int cacheSize = maxMemory / 8;mLruCache = new LruCache<String, Bitmap>(cacheSize) {@Overrideprotected int sizeOf(String key, Bitmap value) {return value.getRowBytes() * value.getHeight();};};mThreadPool = Executors.newFixedThreadPool(threadCount);mPoolSemaphore = new Semaphore(threadCount);mTasks = new LinkedList<Runnable>();mType = type == null ? Type.LIFO : type;}/** * 加载图片 *  * @param path * @param imageView */@SuppressLint("HandlerLeak")public void loadImage(final String path, final ImageView imageView) {// set tagimageView.setTag(path);// UI线程if (mHandler == null) {mHandler = new Handler() {@Overridepublic void handleMessage(Message msg) {ImgBeanHolder holder = (ImgBeanHolder) msg.obj;ImageView imageView = holder.imageView;Bitmap bm = holder.bitmap;String path = holder.path;if (imageView.getTag().toString().equals(path)) {imageView.setImageBitmap(bm);}}};}Bitmap bm = getBitmapFromLruCache(path);if (bm != null) {ImgBeanHolder holder = new ImgBeanHolder();holder.bitmap = bm;holder.imageView = imageView;holder.path = path;Message message = Message.obtain();message.obj = holder;mHandler.sendMessage(message);} else {addTask(new Runnable() {@Overridepublic void run() {ImageSize imageSize = getImageViewWidth(imageView);int reqWidth = imageSize.width;int reqHeight = imageSize.height;Bitmap bm = decodeSampledBitmapFromResource(path, reqWidth,reqHeight);addBitmapToLruCache(path, bm);ImgBeanHolder holder = new ImgBeanHolder();holder.bitmap = getBitmapFromLruCache(path);holder.imageView = imageView;holder.path = path;Message message = Message.obtain();message.obj = holder;mHandler.sendMessage(message);mPoolSemaphore.release();}});}}/** * 添加一个任务 *  * @param runnable */private synchronized void addTask(Runnable runnable) {try {// 请求信号量,防止mPoolThreadHander为nullif (mPoolThreadHander == null)mSemaphore.acquire();} catch (InterruptedException e) {}mTasks.add(runnable);mPoolThreadHander.sendEmptyMessage(0x110);}/** * 取出一个任务 *  * @return */private synchronized Runnable getTask() {if (mType == Type.FIFO) {return mTasks.removeFirst();} else if (mType == Type.LIFO) {return mTasks.removeLast();}return null;}/** * 单例获得该实例对象 *  * @return */public static ImageLoader getInstance(int threadCount, Type type) {if (mInstance == null) {synchronized (ImageLoader.class) {if (mInstance == null) {mInstance = new ImageLoader(threadCount, type);}}}return mInstance;}/** * 根据ImageView获得适当的压缩的宽和高 *  * @param imageView * @return */private ImageSize getImageViewWidth(ImageView imageView) {ImageSize imageSize = new ImageSize();final DisplayMetrics displayMetrics = imageView.getContext().getResources().getDisplayMetrics();final LayoutParams params = imageView.getLayoutParams();int width = params.width == LayoutParams.WRAP_CONTENT ? 0 : imageView.getWidth(); // 得到控件的宽度if (width <= 0)width = params.width; // 通过LayoutParams获得控件宽度if (width <= 0)width = getImageViewFieldValue(imageView, "mMaxWidth");if (width <= 0)width = displayMetrics.widthPixels;int height = params.height == LayoutParams.WRAP_CONTENT ? 0 : imageView.getHeight(); // 得到控件高度if (height <= 0)height = params.height; // 通过LayoutParams获得控件高度if (height <= 0)height = getImageViewFieldValue(imageView, "mMaxHeight");if (height <= 0)height = displayMetrics.heightPixels;imageSize.width = width;imageSize.height = height;return imageSize;}/** * 从LruCache中获取一张图片,如果不存在就返回null。 */private Bitmap getBitmapFromLruCache(String key) {return mLruCache.get(key);}/** * 往LruCache中添加一张图片 *  * @param key * @param bitmap */private void addBitmapToLruCache(String key, Bitmap bitmap) {if (getBitmapFromLruCache(key) == null) {if (bitmap != null)mLruCache.put(key, bitmap);}}/** * 计算inSampleSize,用于压缩图片 *  * @param options * @param reqWidth * @param reqHeight * @return */private int calculateInSampleSize(BitmapFactory.Options options,int reqWidth, int reqHeight) {// 源图片的宽度int width = options.outWidth;int height = options.outHeight;int inSampleSize = 1;if (width > reqWidth && height > reqHeight) {// 计算出实际宽度和目标宽度的比率int widthRatio = Math.round((float) width / (float) reqWidth);int heightRatio = Math.round((float) width / (float) reqWidth);inSampleSize = Math.max(widthRatio, heightRatio);}return inSampleSize;}/** * 根据计算的inSampleSize,得到压缩后图片 *  * @param pathName * @param reqWidth * @param reqHeight * @return */private Bitmap decodeSampledBitmapFromResource(String pathName,int reqWidth, int reqHeight) {// 第一次解析将inJustDecodeBounds设置为true,来获取图片大小final BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeFile(pathName, options);// 调用上面定义的方法计算inSampleSize值options.inSampleSize = calculateInSampleSize(options, reqWidth,reqHeight);// 使用获取到的inSampleSize值再次解析图片options.inJustDecodeBounds = false;Bitmap bitmap = BitmapFactory.decodeFile(pathName, options);return bitmap;}private class ImgBeanHolder {Bitmap bitmap;ImageView imageView;String path;}private class ImageSize {int width;int height;}/** * 反射获得ImageView设置的最大宽度和高度 *  * @param object * @param fieldName * @return */private static int getImageViewFieldValue(Object object, String fieldName) {int value = 0;try {Field field = ImageView.class.getDeclaredField(fieldName);field.setAccessible(true);int fieldValue = (Integer) field.get(object);if (fieldValue > 0 && fieldValue < Integer.MAX_VALUE) {value = fieldValue;Log.e("TAG", value + "");}} catch (Exception e) {}return value;}}
      最后就是ViewHolder.java
import android.content.Context;import android.graphics.Bitmap;import android.util.SparseArray;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.ImageView;import android.widget.TextView;import com.phont.utils.ImageLoader.Type;public class ViewHolder {private final SparseArray<View> mViews;private int mPosition;private View mConvertView;private ViewHolder(Context context, ViewGroup parent, int layoutId,int position) {this.mPosition = position;this.mViews = new SparseArray<View>();mConvertView = LayoutInflater.from(context).inflate(layoutId, parent,false);mConvertView.setTag(this);}/** * 拿到一个ViewHolder对象 *  * @param context * @param convertView * @param parent * @param layoutId * @param position * @return */public static ViewHolder get(Context context, View convertView,ViewGroup parent, int layoutId, int position) {ViewHolder holder = null;if (convertView == null) {holder = new ViewHolder(context, parent, layoutId, position);} else {holder = (ViewHolder) convertView.getTag();holder.mPosition = position;}return holder;}public View getConvertView() {return mConvertView;}/** * 通过控件的Id获取对于的控件,如果没有则加入views *  * @param viewId * @return */@SuppressWarnings("unchecked")public <T extends View> T getView(int viewId) {View view = mViews.get(viewId);if (view == null) {view = mConvertView.findViewById(viewId);mViews.put(viewId, view);}return (T) view;}/** * 为TextView设置字符串 *  * @param viewId * @param text * @return */public ViewHolder setText(int viewId, String text) {TextView view = getView(viewId);view.setText(text);return this;}/** * 为ImageView设置图片 *  * @param viewId * @param drawableId * @return */public ViewHolder setImageResource(int viewId, int drawableId) {ImageView view = getView(viewId);view.setImageResource(drawableId);return this;}/** * 为ImageView设置图片 *  * @param viewId * @param drawableId * @return */public ViewHolder setImageBitmap(int viewId, Bitmap bm) {ImageView view = getView(viewId);view.setImageBitmap(bm);return this;}/** * 为ImageView设置图片 *  * @param viewId * @param drawableId * @return */public ViewHolder setImageByUrl(int viewId, String url) {ImageLoader.getInstance(3, Type.LIFO).loadImage(url,(ImageView) getView(viewId));return this;}public int getPosition() {return mPosition;}}
         嘘~~~~~终于弄完了!至此,对于BaseAdapter的优化算是完成了。(我只是加了不到50张图片,没有将图片数量上升到100张。我觉得吧、、、人还是不能对自个太狠了。

       源码下载地址





1 0
原创粉丝点击