這兩天一直在優化這個問題.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這些要指定高度(最外面一層)