最近在做一个android播放器客户端。需要把服务器上的资源信息拉到手机上显示。这就离不开listview。一开始我举得很容易的,但真正做起来就会出现很多意想不到的问题。比如说listview滑动不流畅,图片加载混乱,甚至会有OOM。等等这些问题我都碰到过,可能我是菜鸟,这些低级的问题都被我碰到了,但我很肯定的说,以后再做类似的事情,问题就迎刃而解了。
说一下listview 和gridview的区别。其实没有什么区别,我们能理解生活中用到的桶和盆有什么区别,就能理解这两个的区别,同样的容器,只是外表不一样而已。实现方式都会是new 一个adapter出来,然后用listview(gridview).setAdapter(adapter)。再加一些监听之类的就差不多了。
说了半天,还没进入主题。listview加载网络图片的实现,我有找过很多资料,但用的都不是很符合自己的要求。最后找到了一个高手的code,得以解决之。添加url在此:http://www.cnblogs.com/liongname/articles/2345087.html
文章中只有用到临时缓存,我这里再添加一个存储,保存到sdcard。
首先我们要实现一个异步加载图片的类,用来对listview的每一个item加载图片:
public enum BitmapManager {
INSTANCE;
public static final String TAG = "BitmapManager";
private final Map<String, SoftReference<Bitmap>> cache; //对图片做缓存
private final ExecutorService pool;
private Map<ImageView, String> imageViews = Collections
.synchronizedMap(new WeakHashMap<ImageView, String>());
private Bitmap placeholder;
BitmapManager() {
cache = new HashMap<String, SoftReference<Bitmap>>();
pool = Executors.newFixedThreadPool(5); //线程池
}
public void setPlaceholder(Bitmap bmp) { //设置占位图,如果加载未完成前应该显示的图片
placeholder = bmp;
}
public Bitmap getBitmapFromCache(String url) {
if (cache.containsKey(url)) {
return cache.get(url).get();
}
return null;
}
public void queueJob(final String url, final ImageView imageView,
final int width, final int height) {
/* Create handler in UI thread. */
final Handler handler = new Handler() {
@Override
public void handleMessage(Message msg) {
String tag = imageViews.get(imageView);
if (tag != null && tag.equals(url)) { //根据url对应的ImageView 设置图片显示
if (msg.obj != null) {
imageView.setImageBitmap((Bitmap) msg.obj);
} else {
imageView.setImageBitmap(placeholder);
Log.d(null, "fail " + url);
}
}
}
};
pool.submit(new Runnable() { //线程异步load图片
@Override
public void run() {
final Bitmap bmp = downloadBitmap(url, width, height);
Message message = Message.obtain();
message.obj = bmp;
Log.d(null, "Item downloaded: " + url);
handler.sendMessage(message);
}
});
}
public void loadBitmap(final String url, final ImageView imageView,
final int width, final int height) {
imageViews.put(imageView, url); //对传进来的imageView 和url做关联,防止图片混乱
Bitmap bitmap = getBitmapFromCache(url);
// check in UI thread, so no concurrency issues
if (bitmap != null) {
Log.d(null, "Item loaded from cache: " + url);
imageView.setImageBitmap(bitmap);
} else {
imageView.setImageBitmap(placeholder);
queueJob(url, imageView, width, height);
}
}
private Bitmap downloadBitmap(String url, int width, int height) {
Bitmap sdcardFile = getSDCardBitmap(url); //优先从sdcard中取图片
if(sdcardFile != null){
sdcardFile = Bitmap.createScaledBitmap(sdcardFile, width, height, true);
cache.put(url, new SoftReference<Bitmap>(sdcardFile));
return sdcardFile;
}
try {
Bitmap bitmap = BitmapFactory.decodeStream((InputStream) new URL(
url).getContent());
bitmap = Bitmap.createScaledBitmap(bitmap, width, height, true);
saveSDCardBitmap(url,bitmap); //存入sdcard;
cache.put(url, new SoftReference<Bitmap>(bitmap));
return bitmap;
} catch (MalformedURLException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private void saveSDCardBitmap(String url,Bitmap bm){
String filePath = Environment.getExternalStorageDirectory()+"/imagecache/"+url.substring(url.lastIndexOf("/")+1); //从url中取文件名以保存到sdcard,方便取。
final File f = new File(filePath);
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){
File path = new File(Environment.getExternalStorageDirectory()+"/imagecache");
if(!path.exists()){
path.mkdirs();
}
try {
FileOutputStream out = new FileOutputStream(f);
bm.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();
out.close();
} catch (FileNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}catch(IOException e){
e.printStackTrace();
}
}
}
private Bitmap getSDCardBitmap(String url){
String filePath = Environment.getExternalStorageDirectory()+"/imagecache/"+url.substring(url.lastIndexOf("/")+1);
final File f = new File(filePath);
if(Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)){ //sdcard 是否存在
File path = new File(Environment.getExternalStorageDirectory()+"/imagecache");
if(!path.exists()){
path.mkdirs();
}
if(f.exists()){
Bitmap tmp = BitmapFactory.decodeFile(filePath);
return tmp;
}
}
return null;
}
}
这个类写好后,我们就只需要在Adapter中的getView()方法中调用这个类就行了。
下面我们来看getView()方法:
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
if(convertView == null){
holder = new ViewHolder();
convertView = mInflater.inflate(R.layout.list_unit,null); /获取list单元布局
holder.Image = (ImageView)findViewById(R.id.mImage);
holder.Title = (TextView)findViewbyId(R.id.mTitle);
convertView.setTag(hodler);//添加标签
}else{
holder = convertView.getTag();
}
if(!mBusy){
BitmapManager.INSTANCE.setPlaceholder(bm); //设置默认图片
BitmapManager.INSTANCE.loadBitmap(url, holder.Image, imageWidth,imageHeight);
}else{
holder.Image.setImageBitmap(bm);
}
return convertView;
}
public static class ViewHolder{
ImageViwe Image;
TextView Title;
}
OnScrollListener mScrollListener = new OnScrollListener() { //对listview的滚动监听
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
switch (scrollState) {
case OnScrollListener.SCROLL_STATE_FLING:
ListViewAdapter.this.setFlagBusy(true);
break;
case OnScrollListener.SCROLL_STATE_IDLE:
ListViewAdapter.this.setFlagBusy(false);
break;
case OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
ListViewAdapter.this.setFlagBusy(false);
break;
default:
break;
}
ListViewAdapter.this.notifyDataSetChanged();
}
@Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
};
protected void setFlagBusy(boolean b) {
// TODO Auto-generated method stub
mBusy = b; //全局变量 mBusy,表示listview是否滚动
}
到这里,我们的ListView异步加载图片的主要工作基本完成。剩下的就是在activity中实例化以及set的工作。这个方法对ListView 和GridView都是通用的。目前我还没有发现有图片混乱,内存溢出,滑动不流畅等问题。当然,再次申明,这里的大部分代码不是我的,我只是拿来优化成了一个适合我自己的东西了。平时都忙着coding,对于很多临时找到的好东西,自己感悟出来的东西(当然很少了),都没有及时记下来,等到需要时又找不到了,不得不说这是一大损失。