Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android異步加載全解析之使用多線程

Android異步加載全解析之使用多線程

編輯:關於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中修改下代碼:
@Override
public 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,已經可以正常顯示了。

 


  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved