上次提到了本地图片的异步加载

,但是当图片量过大的时候就会出现令人头痛的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;
@Override
protected 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;
}
@Override
public int getCount() {
return mDatas.size();
}
@Override
public T getItem(int position) {
return mDatas.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public 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;
}
@Override
public void convert(ViewHolder helper, final String item) {
// 设置默认图片no_pic
helper.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() {
@Override
public 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 thread
mPoolThread = new Thread() {
@Override
public void run() {
Looper.prepare();
mPoolThreadHander = new Handler() {
@Override
public 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) {
@Override
protected 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 tag
imageView.setTag(path);
// UI线程
if (mHandler == null) {
mHandler = new Handler() {
@Override
public 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() {
@Override
public 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为null
if (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张。我觉得吧、、、人还是不能对自个太狠了。
)
源码下载地址