上次提到了本地圖檔的異步加載

,但是當圖檔量過大的時候就會出現令人頭痛的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張。我覺得吧、、、人還是不能對自個太狠了。
)
源碼下載下傳位址