標籤:android 非同步 多線程 圖片 緩衝
非同步載入之使用多線程初次嘗試非同步、非同步,其實說白了就是多任務處理,也就是多線程執行,多線程那就會有各種問題,我們一步步來看,首先,我們建立一個class——ImageLoaderWithoutCaches,從命名上,大家也看出來,這個類,我們實現的是不帶緩衝的映像載入,不多說,我們再建立一個方法——showImageByThread,通過多線程來載入映像:
/** * Using Thread * @param imageView * @param url */public void showImageByThread(final ImageView imageView, final String url) { mHandler = new Handler() { @Override public void handleMessage(Message msg) { Bitmap bitmap = (Bitmap) msg.obj; imageView.setImageBitmap(bitmap); } }; new Thread() { @Override public void run() { Bitmap bitmap = getBitmapFromUrl(url); Message message = Message.obtain(); message.obj = bitmap; mHandler.sendMessage(message); } }.start();}
在這個方法中,我們開啟了一個線程,並在新線程中使用我們前面說的下載映像的方法進行下載,由於這時候已經不是主線程了,所以我們想怎麼下就可以怎麼下,天高皇帝遠。下載好了之後,通過Handler對象將訊息告知在主線程中等了一輩子的Handler,收到訊息之後,Handler操縱UI,修改imageView的映像。OK,跑起。
臥草,坑我啊,有的圖老閃,有的圖動都不動,滑一滑還不停的變,這是什麼鬼。這當然不是我們想要看見的結果。但是為什麼會這樣呢?我們先來分析下,首先,我們需要瞭解下ListView的Recycler機制。
ListView之Recycler機制
public View getView(int position, View convertView, ViewGroup parent)
這個就是ListView中的getView()方法,參數convertView就是我們的頭號嫌疑犯。假如我們有100條資料,但是頁面上只能顯示10條。Android還不會白癡到全部先建立出來。在初始建立的時候,顯示多少,convertView就建立了多少,當你滑動的時候,比如向上滑動一個Item,那麼Item1就會隱藏,Item11會從下面出來,那麼這時候,如果Android再去建立一個convertView,那它就要被人罵死了,因為它首先要回收Item1,再去建立Item11,有意思嗎?為瞭解決這樣一個問題,Android在ListView中提供了Recycler機制,說白了就是建立了一個Item的緩衝池,當Item1消失後,它並沒有被回收,而是進入了緩衝池,成了二手貨,當Item11顯示的時候,它會從緩衝池中取出Item1,把Item1的資料擦擦乾淨繼續用,所以這個時候,Item11和Item1顯示的內容會是相同的,因為它們在記憶體中的地址是一樣的,本質上是一個Item,但這個時候,Item1已經看不見了,所以它顯示成什麼,跟Item11沒一點關係。當它再顯示的時候,重新再寫上正確的資料就好了。這裡在網上盜了張圖,協助大家來理解這樣一個撿二手貨的概念:
當然,如果不是非同步作業,屁事都沒有,因為Android UI是單線程的,或者說你重用了convertView但每次都new一個,那也沒事,當然你也不會這樣做,太Low了。但是非同步就不一樣了,就拿我們這裡下載圖片來說,每個圖片都有自己的脾氣,大家下載的速度都是不一樣的,當初始顯示ListView時,Item1的圖片下載太慢,當你滑上去,顯示Item11了,Item11的圖顯示的飛起,馬上就下好了,OK,Item11的圖顯示好了,可馬上,Item1下的慢的圖也下好了,它去找Item1,但是它不知道這個時候的Item1已經變成Item11了,它還去給人家設定映像,這不就亂了嗎?OK,知道了原因,我們怎麼解決呢?現在的問題就是,下載的映像就好像一個冬眠的人,它睡了20年,起來發現,他大爺已經不是當年的那個大爺了,但它的記憶還停在20年前。所以,我們需要建立一個標誌,來讓冬眠的朋友回來看看清楚,現在的這個大爺是不是你以前的那個大爺。在Android中,我們可以非常方便的使用tag來對View進行標識,Item1顯示的時候,tag被設定為url1,然後它就去下載url1了,當滑動後,顯示Item11了,這時候tag被修改為url11,下載url11的圖了。而當url1的圖片下載好了之後,回來只要看看tag是不是url1就知道大爺還是不是那個大爺了,如果不是,就不顯示了,這樣就可以避免錯位的問題了。當然,前面的例子中還有一個坑,我就不埋大家了,下載完畢後,通過handler將圖片通知UI線程修改ImageView,但是這個時候,參數中的ImageView與這時候傳遞過來的映像可能並不是一一對應的關係,顯示肯定不正確了,所以我們還需要讓url和對應的ImageView進行下配對,最簡單的方法就是使用一個對象來進行儲存。多線程非同步下載正解在進行初次嘗試並分析了失敗原因後,我們將上面兩個坑填起來,修改後的方法如下:
/** * Using Thread * @param imageView * @param url */public void showImageByThread(final ImageView imageView, final String url) { mHandler = new Handler() { @Override public void handleMessage(Message msg) { ImgHolder holder = (ImgHolder) msg.obj; if (holder.imageView.getTag().equals(holder.url)) { holder.imageView.setImageBitmap(holder.bitmap); } } }; new Thread() { @Override public void run() { Bitmap bitmap = getBitmapFromUrl(url); Message message = Message.obtain(); message.obj = new ImgHolder(imageView, bitmap, url); mHandler.sendMessage(message); } }.start();}
private class ImgHolder { public Bitmap bitmap; public ImageView imageView; public String url; public ImgHolder(ImageView iv, Bitmap bm,String url) { this.imageView = iv; this.bitmap = bm; this.url = url; }}
修改後的方法,依然是使用Handler,這個沒有別的選擇,首先我們下載映像,然後將映像和ImageView通過一個對象來進行匹配儲存。在Handler拿到對象後,通過判斷當前的tag與url是否對應來決定是否載入。這裡就利用到了我們在Android非同步載入全解析之開篇瞎扯淡裡面所提到的:
viewHolder.imageView.setTag(url);
這裡就派上用場了。
最後,在Adapter中修改下代碼:
@Overridepublic View getView(int position, View convertView, ViewGroup parent) { String url = mData.get(position); ViewHolder viewHolder = null; if (convertView == null) { viewHolder = new ViewHolder(); convertView = mInflater.inflate(R.layout.listview_item, null); viewHolder.imageView = (ImageView) convertView.findViewById(R.id.iv_lv_item); convertView.setTag(viewHolder); } else { viewHolder = (ViewHolder) convertView.getTag(); } viewHolder.imageView.setTag(url); viewHolder.imageView.setImageResource(R.drawable.ic_launcher); mImageLoader.showImageByThread(viewHolder.imageView, url); return convertView;}
在getView的時候,非同步載入圖片。
這時候,我們再來運行程式,效果如下:
OK,已經可以正常顯示了。
我的Github
我的視訊 慕課網
Android非同步載入全解析之使用多線程