實現一個最簡單圖片列表所引發的問題,實現圖片列表引發

來源:互聯網
上載者:User

實現一個最簡單圖片列表所引發的問題,實現圖片列表引發
前一陣看了些Universal-Image-Loager的源碼。我覺得看源碼很累的一個原因就是除了看怎麼實現,就是去揣測為什麼這麼實現。這個揣測的過程很容易走馬觀花,看到後面似懂非懂。
人懶到一個地步一句話來說是能躺著就絕對不坐著,能坐著就絕對不蹲著,能蹲著就絕對不站著。有時候看源碼也是,能看懂就不會想著去debug,debug能看明白的就懶得去動手寫寫。

看和寫的感受是不一樣的。看的是結果,寫的是過程。


第三方庫的使用讓開發變得很方便,大量圖片請求的實現,大多數不再是說實現的核心,而是直接說使用什麼第三方。即便如此也沒關係,只要知道別人是怎麼實現的就好了,這很重要。Universal-Image-Loader是一個強大的圖片載入開源架構,應該都被說爛用爛了吧。這篇主要是為了去發現問題,從0開始。


看代碼的時候很多為什麼,為什麼要這麼寫,怎麼就想到會有這些問題,怎麼就想到用這種方法去解決。想找原因還是從最簡單的實現一個圖片列表開始找吧。不用任何架構,也不考慮什麼緩衝,單純的去寫一個網路請求顯示圖片列表這樣一個功能。


建立一個PhotoListActivity,這個類只顯示一個listview:

package com.aliao.learninguil.activity;import android.os.Bundle;import android.support.v7.app.AppCompatActivity;import android.widget.ListView;import com.aliao.learninguil.Constants;import com.aliao.learninguil.R;import com.aliao.learninguil.adapter.PhotoListAdapter;import com.aliao.learninguil.entity.ImageInfo;import java.util.ArrayList;import java.util.List;/** * Created by ALiao on 2015/7/13. */public class PhotoListActivity extends AppCompatActivity {    private ListView mListView;    private PhotoListAdapter mAdapter;    private List<ImageInfo> mImageInfos = new ArrayList<>();    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_photolist);        for (int i = 0; i< Constants.IMAGES.length; i++){            ImageInfo imageInfo = new ImageInfo();            imageInfo.setUrl(Constants.IMAGES[i]);            imageInfo.setName("item-" + i);            mImageInfos.add(imageInfo);        }        mListView = (ListView) findViewById(R.id.photoList);        mAdapter = new PhotoListAdapter(mImageInfos, mListView);        mListView.setAdapter(mAdapter);    }}
listview的item的布局是左邊顯示一個ImageVeiw,右邊顯示一個textview:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="horizontal" android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView        android:id="@+id/iv_img"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <TextView        android:id="@+id/tv_imagename"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>

要想在listview的每個item中顯示一張網路請求的圖片,那麼在PhotoListAdapter中要啟動線程進行網路請求。

package com.aliao.learninguil.adapter;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import com.aliao.learninguil.R;import com.aliao.learninguil.entity.ImageInfo;import com.aliao.learninguil.utils.L;import java.io.IOException;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.List;public class PhotoListAdapter extends BaseAdapter {    private List<ImageInfo> imageInfos;    private ListView mListView;    public PhotoListAdapter(List<ImageInfo> imageInfos, ListView listView) {        this.imageInfos = imageInfos;        mListView = listView;    }    @Override    public int getCount() {        return imageInfos.size();    }    @Override    public Object getItem(int position) {        return imageInfos.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder;        if (convertView == null){            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_photolist, parent, false);            holder = new ViewHolder();            holder.imgView = (ImageView) convertView.findViewById(R.id.iv_img);            holder.imgName = (TextView) convertView.findViewById(R.id.tv_imagename);            convertView.setTag(holder);        }else {            holder = (ViewHolder) convertView.getTag();        }        ImageInfo imageInfo = imageInfos.get(position);        holder.imgName.setText(imageInfo.getName());        holder.imgView.setTag(imageInfo.getUrl());        loadAndSetImage(imageInfo.getUrl());        return convertView;    }    class ViewHolder{        ImageView imgView;        TextView imgName;    }    private void loadAndSetImage(String url) {        new LoadImageAsyncTask().execute(url);    }    class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{        private String mImageUrl;        @Override        protected Bitmap doInBackground(String... params) {            mImageUrl = params[0];            Bitmap bitmap = loadBitmap(mImageUrl);            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(mImageUrl);            if (imageView != null){                imageView.setImageBitmap(bitmap);            }        }    }    private Bitmap loadBitmap(String imageUrl) {        HttpURLConnection connection = null;        Bitmap bitmap = null;        try {            URL url = new URL(imageUrl);            connection = (HttpURLConnection) url.openConnection();            bitmap = BitmapFactory.decodeStream(connection.getInputStream());            return bitmap;        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }finally {            if (connection != null){                connection.disconnect();            }        }        return bitmap;    }}
這是實現一個圖片列表最基本的代碼了。實現的如下:


看起來顯示的效果還不錯,但是當我們上下滑動的時候明顯會感受到不夠流暢。

回到PhotoListAdapter類,每調用一次getView方法,就會去啟動線程進行網路請求。我們期望的效果是當頁面顯示5個item的時候,就進行5次網路請求,這是最理想的狀態。但是實際上getView調用的次數比預想的要多得多,也就意味著會伴隨多餘的網路請求。有哪些情況會導致多餘的網路請求呢(這裡的多餘是表示,除了當前螢幕顯示的圖片以外,進行了額外的其他圖片的網路請求)

情況一:沒有設定item的高度

listview item的布局檔案中看到,並沒有去設定ImageView的高度,item的高度是自動擴充的:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"    android:orientation="horizontal" android:layout_width="match_parent"    android:layout_height="match_parent">    <ImageView        android:id="@+id/iv_img"        android:layout_width="wrap_content"        android:layout_height="wrap_content" />    <TextView        android:id="@+id/tv_imagename"        android:layout_width="wrap_content"        android:layout_height="wrap_content" /></LinearLayout>
所以,當進入圖片列表頁時,由於還未載入出圖片,預設是textview的高度,一屏能夠顯示了27個item,即調用了27次getView,也就是開啟線程進行網路請求的操作就要執行27次。但隨著前面圖片陸續載入完畢,一屏最終顯示5個item,但是剩餘的22次網路請求還在繼續。通常我們希望一屏顯示多少張圖片就去請求多少張,當要看更多的圖片的時候再去請求。而這22次網路請求既然都執行了,是不是往下滑動的時候他就可以直接顯示出已載入完的圖片?答案是否定的,因為除了當前螢幕顯示的item,其他的item都被回收了,通過findViewWithTag(imageUrl)已經找不到對應的imageView(為null)。即使圖片已經請求成功,但是由於當前螢幕沒有對應的imageview,也無法設定圖片。

        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(mImageUrl);            if (imageView != null){                imageView.setImageBitmap(bitmap);            }        }
所以繼續向下滑的時候,還是會重複請求先前已經請求過的圖片。


如果提前已知item的顯示高度,例如設定ImageView的高度為100dp或者設定預設圖片,那麼一屏能夠顯示的item數量是確定的,就可以做到一屏顯示多少個item,進行相應數量的網路請求。


情況二:如果想看第三屏的圖片列表,滑過去的前兩屏的圖片已經進行了網路請求,但並沒必要

對於我們不想查看而快速滑過的圖片是沒有必要浪費資源去載入的。但是由於把載入圖片的操作放在了getView()中,只要滑動螢幕都會調用getView(),我們希望當listview滑動停止時再去載入。listview的滾動監聽事件的回呼函數可以監聽到滾動的狀態,onScrollStateChanged(AbsListView view, int scrollState)中的scrollState有三種表示listvuew的滾動狀態,分別是:

SCROLL_STATE_IDLE( = 0 )  :停止滾動

SCROLL_STATE_TOUCH_SCROLL( = 1 )  :正在滾動

SCROLL_STATE_FLING( = 2 )  :手指做了拋的動作

當scrollState的狀態為SCROLL_STATE_IDLE的時候,去下載圖片。圖片下載的時機確定了,那麼當滑動停止時當前螢幕顯示的可視圖片的地址該怎麼擷取。imageInfos對象列表存放了所有圖片資訊,知道了當前螢幕可視圖片的position位置,也就可以通過索引擷取到圖片地址了。另一個滾動監聽的回調onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)(滾動時一直回調)協助我們擷取到當前螢幕圖片的位置資訊。參數中

firstVisibleItem  :當前螢幕第一張可見item的下標(從0開始)

visibleItemCount  :當前螢幕所有可見item的總數 

totalItemCount  :清單項目總數

具體的代碼實現如下:

package com.aliao.learninguil.adapter;import android.graphics.Bitmap;import android.graphics.BitmapFactory;import android.os.AsyncTask;import android.view.LayoutInflater;import android.view.View;import android.view.ViewGroup;import android.view.ViewTreeObserver;import android.widget.AbsListView;import android.widget.BaseAdapter;import android.widget.ImageView;import android.widget.ListView;import android.widget.TextView;import com.aliao.learninguil.R;import com.aliao.learninguil.entity.ImageInfo;import com.aliao.learninguil.utils.L;import java.io.IOException;import java.net.HttpURLConnection;import java.net.MalformedURLException;import java.net.URL;import java.util.HashSet;import java.util.List;import java.util.Set;public class PhotoListAdapter extends BaseAdapter implements AbsListView.OnScrollListener{    private List<ImageInfo> imageInfos;    private ListView mListView;    private int mFirstVisibleItem;    private int mVisibleItemCount;    private boolean mFirstEnter;    private Set<LoadImageAsyncTask> taskCollection;    public PhotoListAdapter(List<ImageInfo> imageInfos, ListView listView) {        this.imageInfos = imageInfos;        mListView = listView;        mListView.setOnScrollListener(this);        mFirstEnter = true;        taskCollection = new HashSet<>();    }    @Override    public int getCount() {        return imageInfos.size();    }    @Override    public Object getItem(int position) {        return imageInfos.get(position);    }    @Override    public long getItemId(int position) {        return position;    }    @Override    public View getView(int position, View convertView, ViewGroup parent) {        ViewHolder holder;        if (convertView == null){            convertView = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_photolist, parent, false);            holder = new ViewHolder();            holder.imgView = (ImageView) convertView.findViewById(R.id.iv_img);            holder.imgName = (TextView) convertView.findViewById(R.id.tv_imagename);            convertView.setTag(holder);        }else {            holder = (ViewHolder) convertView.getTag();        }        ImageInfo imageInfo = imageInfos.get(position);        holder.imgName.setText(imageInfo.getName());        holder.imgView.setTag(imageInfo.getUrl());//        L.d("position = "+position+", url = "+imageInfo.getUrl());//        loadAndSetImage(imageInfo.getUrl());        return convertView;    }    class ViewHolder{        ImageView imgView;        TextView imgName;    }    @Override    public void onScrollStateChanged(AbsListView view, int scrollState) {        /**         * scrollState = SCROLL_STATE_IDLE( = 0 )停止混動         * scrollState = SCROLL_STATE_TOUCH_SCROLL( = 1 )正在滾動         * scrollState = SCROLL_STATE_FLING( = 2 ) 手指做了拋的動作         */        L.d("-------》onScrollStateChanged scrollState = "+scrollState);        if (scrollState == SCROLL_STATE_IDLE){            loadAndSetImage(mFirstVisibleItem, mVisibleItemCount);        }else {            //當listview再次滑動時取消所有正在下載的任務//            cancelAllTasks();        }    }    public void cancelAllTasks() {        if (taskCollection != null){            for (LoadImageAsyncTask task : taskCollection){                task.cancel(false);            }        }    }    @Override    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {        L.d("onScroll firstVisibleItem = "+firstVisibleItem+", visibleItemCount = "+visibleItemCount+", totalItemCount = "+totalItemCount);        mFirstVisibleItem = firstVisibleItem;//第一張可見圖片的下標        mVisibleItemCount = visibleItemCount;//一屏可見圖片的總數        //首次進入程式時,onScrollStateChanged方法並不會被調用,所以在這裡首次進入程式時啟動下載任務        if (mFirstEnter && visibleItemCount > 0){            loadAndSetImage(mFirstVisibleItem, mVisibleItemCount);            mFirstEnter = false;        }    }    private void loadAndSetImage(int firstVisibleItem, int visibleItemCount) {        for (int i = firstVisibleItem; i< firstVisibleItem + visibleItemCount; i++){            ImageInfo imageInfo = imageInfos.get(i);            L.d("position = "+i+", url = "+imageInfo.getUrl());            LoadImageAsyncTask task = new LoadImageAsyncTask();            task.execute(imageInfo.getUrl());            taskCollection.add(task);        }    }    class LoadImageAsyncTask extends AsyncTask<String, Void, Bitmap>{        private String mImageUrl;        @Override        protected Bitmap doInBackground(String... params) {            mImageUrl = params[0];            Bitmap bitmap = loadBitmap(mImageUrl);            return bitmap;        }        @Override        protected void onPostExecute(Bitmap bitmap) {            super.onPostExecute(bitmap);            ImageView imageView = (ImageView) mListView.findViewWithTag(mImageUrl);            if (imageView != null){                imageView.setImageBitmap(bitmap);            }        }    }    private Bitmap loadBitmap(String imageUrl) {        HttpURLConnection connection = null;        Bitmap bitmap = null;        try {            URL url = new URL(imageUrl);            connection = (HttpURLConnection) url.openConnection();            bitmap = BitmapFactory.decodeStream(connection.getInputStream());            L.d("------------------------------------loadBitmap ");            return bitmap;        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }finally {            if (connection != null){                connection.disconnect();            }        }        return bitmap;    }}
除了當listview再次滑動的時候,取消所有正在下載的任務,當Activity銷毀時,在onDestroy方法中可以調用PhotoListAdapter中的cancelAllTasks()來取消所有還未完成的下載任務。

到目前為止通過確定list item的高度以及對啟動圖片下載任務的時機的修改,做到了只下載所要查看到的圖片,避免多餘的網路請求,減少了網路請求的負荷和節省了手機流量。

解決了上面的問題後,代碼又完善健壯了一步,想想接下來還會有什麼問題。


參考:

使用記憶體緩衝實現圖片牆




著作權聲明:本文為博主原創文章,未經博主允許不得轉載。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.