(BUG已修改,最優化)安卓ListView異步加載網絡圖片與緩存軟引用圖片,線程池,只加載當前屏之

前幾天的原文有一個線程管理與加載源過多,形成浪費流量的問題.下面對這進下改進的一些說明(紅色爲新加)

這兩天一直在優化這個問題.google也不少種作法.但發現都是比較不全面.java

好比: android

一些只實現了異步加載,卻沒有線程池與軟引用.web

 一些是用AsynTast的,緩存

一些有了線程池但加載全部的圖片,這樣形成具大資源浪費網絡

一些是用顯示當前屏的item,但卻用的是線程等待喚醒的方法.(這個不推薦)dom

AsynTast這個開始我也是用它的.後來發現很不理想呀
異步

.因而今天就總結了一個思路.同時把前兩天的代碼改一下ide

部份代碼來自網絡函數


最終思路:  子線程加載網絡圖片並用緩存圖片軟引用..線程池管理子線程...根據當前屏第一item的potion與最後item的position加載當前屏顯示的item圖片.佈局

拖動過程當中的不加載..根據onScrollListener來監聽是否中止拖動. l

istView.getFirstVisiblePosition()  當前屏第一條的下標

listView.getLastVisiblePosition();當前屏最後一條下標


最近開發個應用,裏面大量的activity要用到listView這個控件.因爲爲了更加美觀顯示,就要自定義一個.

這下問題出來了.由於是獲取網絡圖片.按傳統的作法沒辦法及時加載對應的圖片或者圖片錯位.
在網上找了好久.也摳了一天的源碼..發現網上的都沒有比較系統的說明.因此這裏整理一下.
方便之後本身回看.
========================
先說一下思路: 個人理解爲---- 由於要網絡操做.因此加載圖片在子線程中. 有延遲.但主線程都不等你子線程是否獲取結果.它就走下去了.這樣setImageDrawable(這裏固然是沒有了).
因此就會在你到ListView加載完時.看不到圖片的緣由. 
那麼.在加載圖片的子線程中,若是獲取到圖片以後.就handler發送一個信息到主線程.讓它根據當前行的下標(postion)來更新圖片.我管你主線程跟到哪了.管你等不等我.
反正我慢慢地下載圖..下載到了我再叫你更新.
========================
首先說自定義的SimpleAdapter..
這裏的傳統作法你們都應懂的了.就是那個getView() 方法能夠有點難理解
簡單地說. 就是加載每一行數據(單行ListView).就調一次getView() 
public View getView(int position, View convertView, ViewGroup parent){}
position: 這個參數是指當前一行的下標. (從0開始的);
converTiew: 是能夠理解爲當前一屏..(不知對不對.我是這樣理解的.)第一次執行convertView,若是是第一次就進行佈局資源的建立操做 
若是拖進屏幕時.就能夠複用到它了.不用每一屏都新建一個.這裏下面代碼裏有說明
到圖片加載了.咱們定義一個圖片加載的類.用一個靜態方法來獲取圖片的Drawable
但因爲優化內存使用,爲了ListView加載了太多圖片在內存中.那麼.咱們就進行緩存軟引用機制來管理圖片.
說得這麼繞..無非就是指, 把獲得的Drawable變成一個軟引用.而後再把它放進map中.讓系統本身的決定何時回收內存中的圖片.
關於軟引用...我我的的用法就是.但到一個drawable以後.立刻new SoftReference<Drawable> (drawable) 存到map 中...那何時變回普通drawable呢
我認爲當要從map中取出來以後.第一步就要變回普通的drawable(--softReference.get()--).這樣的話.當我回來拖進listView時...就不會由於系統清理個人軟引用致使看不到圖了

下面上代碼..先上異步獲取圖片的類

圖片加載器就加了個線程池.

AsyncImageTask.java

package com.naxieshu.util;

import java.io.IOException;
import java.io.InputStream;
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;

import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.Message;

/**
 * 異步加截圖片類
 * @author Hai Jiang
 * @Email 672335219@qq.ciom
 * @Data 2014-2-26
 */
public class AsyncImageTask {	//開線程池
	ExecutorService executorService =  Executors.newCachedThreadPool();
	//緩存圖片 把圖片的軟引用放到map中
	private Map<String, SoftReference<Drawable>> imageMap;
	//構造器
	public AsyncImageTask() {  
        	super();  
        	this.imageMap = new HashMap<String, SoftReference<Drawable>>();  
    	} 
	
	//ID爲標記,標記哪條記錄image . 這個ID來自於自定義adapter的getView()方法中其中一個參數position
	public Drawable loadImage(final int id, final String imageUrl, 
			final ImageCallback callback){
		
		//先看緩存(Map)中是否存在
		if(imageMap.containsKey(imageUrl)){
			SoftReference<Drawable> softReference = imageMap.get(imageUrl);
			Drawable drawable = softReference.get();
			if(drawable != null){
				return drawable;
			}
		}
		
		//主線程更新圖片
		final Handler handler = new Handler() {  
            		public void handleMessage(Message message) {  
                    		callback.imageLoaded((Drawable) message.obj, id);  
            		}  
       	 	};
		//加載圖片的線程
        	executorService.submit(	
		//加載圖片的線程
		new Thread() {  
	            public void run() {  
	                //加載圖片  
	                Drawable drawable = AsyncImageTask.loadImageByUrl(imageUrl);  
	                //加入緩存集合中 注意  這裏就要把獲得的圖片變成軟引用放到map中了
	                imageMap.put(imageUrl, new SoftReference<Drawable>(drawable));  
	                //通知消息主線程更新UI  . 這裏就是是否能異步刷新的留意點.
	                Message message = handler.obtainMessage(0, drawable);  
	                handler.sendMessage(message);  
	            }  
	        });
		return null;
		//到這裏就獲取圖片的靜態方法就完了
	}
	
	//根據圖片地址加載圖片,並保存爲Drawable
	//這裏不用說了吧.都是一些基本的.從API從能夠看
	public static Drawable loadImageByUrl(String imageUrl){
		URL url = null;
		InputStream inputStream = null;
		try {
			url = new URL(Constant.TARGETURL+imageUrl);
			inputStream = (InputStream) url.getContent();  
	        Drawable drawable = Drawable.createFromStream(inputStream,"src");
	        return drawable;
		} catch (Exception e) {
			e.printStackTrace();
		} finally {
			try {
				if(inputStream != null)
					inputStream.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return null;
	}
	
	//利用接口回調,更新圖片UI  
    	public interface ImageCallback {  
        	public void imageLoaded(Drawable obj, int id);  
    	} 
	
}




這裏是自定義adapter類(這裏由於主要是加個中止監聽)

MyListAdapter.java

package com.naxieshu.adapter;

import java.lang.ref.SoftReference;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.content.ClipData.Item;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.AbsListView.OnScrollListener;

import com.naxieshu.activity.FindActivity;
import com.naxieshu.activity.R;
import com.naxieshu.domain.Book;
import com.naxieshu.util.AsyncImageTask;
import com.naxieshu.util.AsyncImageTask.ImageCallback;
import com.naxieshu.util.ImageUtil;

/**
 * 自定義List內容控件
 * @author Hai Jiang
 * @Email 672335219@qq.ciom
 * @Data 2014-2-26
 */
public class MyListAdapter extends SimpleAdapter{
	public List<? extends Map<String, ?>> data;
	private LayoutInflater inflater;
	/**異步加載圖片實例*/
	private AsyncImageTask imageTask;
	/**被綁定對象*/
	private ListView listView;
	/**Item對象集*/
	HashMap<String, Object> itemMap =  new HashMap<String, Object>();
	
	public MyListAdapter(final ListView listView,Context context,
			List<? extends Map<String, ?>> data) {
		super(context, data, 0, null, null);
		this.data = data;
		this.listView = listView;  
        inflater = LayoutInflater.from(context);  
        imageTask = new AsyncImageTask();
        /**註冊監聽事件*/
        listView.setOnScrollListener(onScrollListener);
        
	}
	/**
	 * 在建立View資源對象的時候提供效率的緩存策略
	 */
	class ViewHold{
		//book.cover
		public ImageView image;
		//book.title book.shortIntro
		public TextView namtView,idView,introView;
	}
	ViewHold hold =null;
	@Override
	public View getView(int position, View convertView, ViewGroup parent) {
		//數據源中與當前item對應的數據
		Book book = (Book) data.get(position).get(position+"");
		//判斷是否第一次執行convertView,若是是第一次就進行佈局資源的建立操做
		
		if (convertView == null){
			hold = new ViewHold();
			//填充加載佈局資源
			convertView = inflater.inflate(R.layout.activity_find_listview, null);
			hold.image = (ImageView)convertView.findViewById(R.id.bookImage);
			hold.image.setImageDrawable(imageTask.loadImage(position, book.getCover(),imageCallback));
			hold.namtView = (TextView)convertView.findViewById(R.id.bookName);
			hold.idView = (TextView)convertView.findViewById(R.id.bookId);
			hold.introView = (TextView)convertView.findViewById(R.id.bookShortIntro);
			//保存標記
			convertView.setTag(hold);
		} else {
			hold = (ViewHold) convertView.getTag();
		}
		
		/**獲取數據,進行數據填充*/
		
		// 標記圖片視圖,注意不能放在上面  
        hold.image.setTag(position);
        
        hold.image.setImageResource(R.drawable.ic_launcher);
		hold.namtView.setText(book.getTitle()); 
		hold.idView.setText(book.getId());
		hold.idView.setVisibility(View.GONE);
		hold.introView.setText(book.getShortIntro());
		itemMap.put(position+"", hold);
		return convertView;
	}
	/**
	 * 屏幕中止滾動監聽器
	 */
	
	AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {  
		   
		 public void onScrollStateChanged(AbsListView view, int scrollState) {  
			 if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
				 Log.i("--", "--");
				 int start_index = listView.getFirstVisiblePosition();
				 int end_index = listView.getLastVisiblePosition();
				 
				 pageImgLoad(start_index,end_index);
			 }  
			   
		 }  
		   
		 public void onScroll(AbsListView view, int firstVisibleItem,  
				 int visibleItemCount, int totalItemCount) {  
			 // TODO Auto-generated method stub  
			   
		 }  
	};
	 
	/**
	  * 加載當前屏的圖片
	  */
	private void pageImgLoad(int start_index, int end_index) {  
        for (; start_index < end_index; start_index++) {
        	Book book = (Book) data.get(start_index).get(start_index+"");
        	imageTask.loadImage(start_index, book.getCover(),imageCallback);
        }  
	}
	
	/**回調函數*/
	AsyncImageTask.ImageCallback imageCallback = new ImageCallback(){
		public void imageLoaded(Drawable image, int position) {
			if (image != null) {  
                //獲取剛纔標識的組件,並更新   
               ImageView imageView = (ImageView) listView  
                       .findViewWithTag(position);  
               if (imageView != null) {
            	   imageView.setImageDrawable(image);  
               }  
           }
		}
	};
	
	@Override  
    public int getCount() {  
        return data.size();  
    }  
  
    @Override  
    public Object getItem(int position) {
        return itemMap.get(position+"");  
    }  
  
    @Override  
    public long getItemId(int position) {  
        return position;  
    }
}



到這裏關鍵的都說完了.Activity那裏那就貼代碼了
主要是何時實例MyListAdapter要注意一下
若是你的是像搜結果顯示在listView中的這種.
那麼MyListAdapter的數據源就要放在Button的點擊響應事件裏獲取..而後通Message把數據源發送到handler中.在這handler中實例MyListAdapter對象.再綁定到listView.
順便說一下.另外一種狀況
獲得數據源.但ListView不顯示內容.這是爲何 ?
通常有兩種緣由.
1, ListView不在handler中綁定數據..由於對組件的更新更改操做.一 定要在主線程中弄 
2.就是佈局問題.你的ListView裏的item不指定高度.----這個是最多見的..ListView的item必定要指定高度.
就是你定義準備套在ListView中的那個layout_xxxx.xml這個文件中的LinearLayout這些要指定高度(最外面一層)
相關文章
相關標籤/搜索