ListView的優(yōu)化

ListView的優(yōu)化問(wèn)題可以說(shuō)是面試的必考題。我之前看過(guò)一遍視頻 Android必學(xué)-異步加載,感覺(jué)里面講解的知識(shí)都是ListView優(yōu)化常用的,這里我就通過(guò)里面的示例來(lái)做一個(gè)總結(jié)。本篇準(zhǔn)備通過(guò)這個(gè)示例來(lái)談一談ListView的優(yōu)化,通過(guò)分析在寫(xiě)code的過(guò)程中遇到的問(wèn)題,來(lái)談一下解決辦法。

示例是這樣的:
http://www.imooc.com/api/teacher?type=4&num=30 中加載json格式的數(shù)據(jù),解析出來(lái),然后用ListView顯示出來(lái)。下面是完成后的圖片(沒(méi)有找到有效設(shè)置圖片大小的方法,大家有知道的話,留言給我)
先看我的示例listView的item的布局:

item.png

com.mecury.javamai.png
1.對(duì)于這樣一個(gè)需求,我們首先應(yīng)該是解析得到的數(shù)據(jù),然后將得到的數(shù)據(jù)提供給ListView的Adapter,有關(guān)于這部分的代碼,寫(xiě)在了MainActivity.java,我放到文章后面,畢竟重點(diǎn)不在這。
2.當(dāng)獲得到數(shù)據(jù)之后,接下來(lái)就是編寫(xiě)Adapter了。
  • 使用ViewHolder模式來(lái)提高效率

Viewholder模式充分了ListView的視圖緩存機(jī)制,避免了每次在調(diào)用getView的時(shí)候都去通過(guò)findViewById實(shí)例化數(shù)據(jù)。《Android群英傳》中說(shuō):據(jù)測(cè)試,使用ViewHolder將提高50%的效率。對(duì)應(yīng)代碼中就是這樣,先在NewsAdapter(本示例中的Adapter)新建一個(gè)內(nèi)部類(lèi):(詳細(xì)代碼見(jiàn)文章后的NewsAdapter.java

//使用ViewHolder
    private static class ViewHolder {
        private TextView tvTitle, tvContent;
        private ImageView ivIcon;
    }

在重寫(xiě)的getView方法中這樣寫(xiě):

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        //判斷是否有緩存
        if (convertView == null) {
            //通過(guò)LayoutInflate實(shí)例化布局
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.item_layout, parent, false);
            viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
            viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
            viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
            convertView.setTag(viewHolder);
        } else {
           //通過(guò)tag找到緩存的布局
            viewHolder = (ViewHolder) convertView.getTag();
        }
        NewsBean newsBean = newsBeanList.get(position);

        String urlString = newsBean.newsIconUrl;
        viewHolder.ivIcon.setTag(urlString); // 將ImageView與url綁定
        //普通異步加載
       // mImageLoader.showImageByThread(viewHolder.ivIcon,urlString);
        mImageLoader.showImageByAsyncTask(viewHolder.ivIcon,urlString);
        viewHolder.tvTitle.setText(newsBean.newsTitle);
        viewHolder.tvContent.setText(newsBean.newsContent);
        return convertView;
    }
  • 異步加載:耗時(shí)的操作放在異步線程中

如果在adapter中的某些操作需要耗費(fèi)大量的時(shí)間,這個(gè)時(shí)候就要用到異步線程來(lái)進(jìn)行異步就在數(shù)據(jù)。比如:現(xiàn)在要加載圖片,此時(shí)我們需要根據(jù)url訪問(wèn)網(wǎng)絡(luò)得到數(shù)據(jù),然后將數(shù)據(jù)解析為Bitmap設(shè)置給View。這些操作如果不進(jìn)行異步處理而直接放入adapter,可想而知,我們的ListView會(huì)有多卡。
這里向大家提供兩種異步加載線程的方式:(代碼查看下面的ImageLoader.java)
向網(wǎng)絡(luò)獲得數(shù)據(jù),異步中需要進(jìn)行的操作:

    public Bitmap getBitmapFormURL(String urlString) {
        Bitmap bitmap;
        InputStream inputStream = null;

        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            inputStream = new BufferedInputStream(conn.getInputStream());  //得到圖片的數(shù)據(jù)流
            bitmap = BitmapFactory.decodeStream(inputStream);  //根據(jù)數(shù)據(jù)流來(lái)解析出圖片的bitmap
            conn.disconnect();
            return bitmap;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return null;
    }
一種是通過(guò)多線程方式通過(guò)Handler+Message進(jìn)行異步加載
private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            //這個(gè)getTag是下面的問(wèn)題要講的優(yōu)化,大家可以先看看
            if (mImageView.getTag().equals(mUrl)) { //當(dāng)url標(biāo)記和原先設(shè)置的一樣時(shí),才設(shè)置ImageView
                mImageView.setImageBitmap((Bitmap) msg.obj);
            }

        }
    };
    public void showImageByThread(ImageView imageView, final String url) {

        this.mImageView = imageView;
        this.mUrl = url;
        new Thread() {
            @Override
            public void run() {
                super.run();
                Bitmap bitmap = getBitmapFormURL(url);
                Message message = Message.obtain();
                message.obj = bitmap;
                handler.sendMessage(message);
            }
        }.start();
    }
另一種是通過(guò)AsyncTask來(lái)進(jìn)行異步操作

AsyncTask的使用方法,不熟悉的可以學(xué)習(xí)下,這里不講了,畢竟不是重點(diǎn)。

public void showImageByAsyncTask(ImageView imageView,String url){  
        new NewsAsyncTask(imageView,url).execute(url);  
    } 

構(gòu)建的AsyncTask的內(nèi)部類(lèi)

class NewsAsyncTask extends AsyncTask<String,Void,Bitmap>{  
          
        private ImageView myImageView;  
        private String mUrl;  
          
        public NewsAsyncTask(ImageView imageView,String url){  
            myImageView = imageView;  
            mUrl = url;  
        }  
              //String...params是可變參數(shù)接受execute中傳過(guò)來(lái)的參數(shù)  
        @Override  
        protected Bitmap doInBackground(String... params) {  
              
            String url=params[0];  
            //這里同樣調(diào)用我們的getBitmapFromeUrl  
            Bitmap bitmap = getBitmapFromUrl(params[0]);  
            return bitmap;  
        }  
        //這里的bitmap是從doInBackgroud中方法中返回過(guò)來(lái)的  
        @Override  
        protected void onPostExecute(Bitmap bitmap) {  
            super.onPostExecute(bitmap);  
                imageView.setImageBitmap(bitmap);  
        }  
    } 

listView錯(cuò)位加載問(wèn)題

當(dāng)上面的代碼寫(xiě)完時(shí),如果先不用讓大家先看看的mImageView.getTag().equals(mUrl)判斷一下(上面的代碼并不完整,下面會(huì)講),你會(huì)發(fā)現(xiàn)如下圖的情況,在Listview中的各個(gè)item加載的過(guò)程中,出現(xiàn)了item錯(cuò)位的情況。一張圖片在多個(gè)位置顯示,過(guò)了一段時(shí)間才正常。

GIF.gif

對(duì)于上面的情況,我們先來(lái)看看產(chǎn)生這種情況的原因(下面內(nèi)容主要引用自android listview 異步加載圖片并防止錯(cuò)位):

231543494598347.jpg

網(wǎng)上找了一張圖, listview 異步加載圖片之所以錯(cuò)位的根本原因是重用了 convertView 且有異步操作.
如果不重用 convertView 不會(huì)出現(xiàn)錯(cuò)位現(xiàn)象, 重用 convertView 但沒(méi)有異步操作也不會(huì)有問(wèn)題。
我簡(jiǎn)單分析一下:
當(dāng)重用 convertView 時(shí),最初一屏顯示 7 條記錄, getView 被調(diào)用 7 次,創(chuàng)建了 7 個(gè) convertView.
當(dāng) Item1 劃出屏幕, Item8 進(jìn)入屏幕時(shí),這時(shí)沒(méi)有為 Item8 創(chuàng)建新的 view 實(shí)例, Item8 復(fù)用的是
Item1 的 view 如果沒(méi)有異步不會(huì)有任何問(wèn)題,雖然 Item8 和 Item1 指向的是同一個(gè) view,但滑到
Item8 時(shí)刷上了 Item8 的數(shù)據(jù),這時(shí) Item1 的數(shù)據(jù)和 Item8 是一樣的,因?yàn)樗鼈冎赶虻氖峭粔K內(nèi)存,
但 Item1 已滾出了屏幕你看不見(jiàn)。當(dāng) Item1 再次可見(jiàn)時(shí)這塊 view 又涮上了 Item1 的數(shù)據(jù)。
但當(dāng)有異步下載時(shí)就有問(wèn)題了,假設(shè) Item1 的圖片下載的比較慢,Item8 的圖片下載的比較快,你滾上去
使 Item8 可見(jiàn),這時(shí) Item8 先顯示它自己下載的圖片沒(méi)錯(cuò),但等到 Item1 的圖片也下載完時(shí)你發(fā)現(xiàn)
Item8 的圖片也變成了 Item1 的圖片,因?yàn)樗鼈儚?fù)用的是同一個(gè) view。 如果 Item1 的圖片下載的比
Item8 的圖片快, Item1 先刷上自己下載的圖片,這時(shí)你滑下去,Item8 的圖片還沒(méi)下載完, Item8
會(huì)先顯示 Item1 的圖片,因?yàn)樗鼈兪峭豢靸?nèi)存,當(dāng) Item8 自己的圖片下載完后 Item8 的圖片又刷成
了自己的,你再滑上去使 Item1 可見(jiàn), Item1 的圖片也會(huì)和 Item8 的圖片是一樣的,
因?yàn)樗鼈冎赶虻氖峭粔K內(nèi)存。

解決的辦法由很多種,這里說(shuō)一下最簡(jiǎn)單的一種:使用findViewWithTag
在NewsAdapter中為ImageView設(shè)置Tag標(biāo)識(shí)位:

//將url設(shè)為imagView的標(biāo)識(shí)位
String urlString = newsBean.newsIconUrl; 
viewHolder.ivIcon.setTag(urlString); // 將ImageView與url綁定

然后再加載的過(guò)程中通過(guò)url來(lái)判斷對(duì)應(yīng)imagview位置是否一致來(lái)決定是否加載。

if (mImageView.getTag().equals(mUrl)) { 
//當(dāng)url標(biāo)記和原先設(shè)置的一樣時(shí),才設(shè)置ImageView    
mImageView.setImageBitmap((Bitmap) msg.obj);
}

為圖片設(shè)置緩存

設(shè)置緩存的重要性不言而喻,在加載數(shù)據(jù)的過(guò)程中,你不可能每次都從網(wǎng)絡(luò)上加載(除非數(shù)據(jù)量小)。如果即之一這樣做就要面對(duì)兩個(gè)問(wèn)題:1.加載數(shù)據(jù)浪費(fèi)的時(shí)間 2.加載數(shù)據(jù)消耗的流量 。信不信用戶(hù)一言不合就把你這破app給卸載了。。。。。。
設(shè)置緩存的原理這里也不再說(shuō)了,要說(shuō)的話估計(jì)又成一片博文了。這里介紹用法,讓你看看怎么寫(xiě):(這部分代碼,在文章末尾的ImageLoader.java中)
1.首先聲明一個(gè)LrcCache
private LruCache<String,Bitmap> mMemoryCaches;
2.然后初始化

 //下面是建立緩存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();  //運(yùn)行時(shí)最大內(nèi)存
        int cacheSize = maxMemory/4;  //緩存的大小
        mCaches = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };

3.添加方法,在適當(dāng)?shù)牡胤秸{(diào)用

//將bitmap添加到緩存
    public void addBitmapToCache(String url,Bitmap bitmap){
        if (getBitmapFormCache(url) == null){
            mCaches.put(url, bitmap);
        }
    }

    //從緩存中獲取數(shù)據(jù)
    public Bitmap getBitmapFormCache(String url){
        return mCaches.get(url);
    }

ListView的滑動(dòng)時(shí)停止加載和分頁(yè)加載

先說(shuō)分頁(yè)加載,我們不用每次把ListView所有的Item都一次性加載完畢,這樣做沒(méi)必要也很累。我們僅僅需要加載那部分顯示在屏幕部分的Item即可,這樣所要加載的數(shù)據(jù)就減少了很多。另一方面,我們需要考慮另一個(gè)問(wèn)題,當(dāng)用戶(hù)滑動(dòng)時(shí),顯示在屏幕的Item會(huì)不斷的變化,如果只是加載顯示在屏幕的Item,這也沒(méi)有必要,因此我們應(yīng)該在停止滑動(dòng)時(shí)再加載數(shù)據(jù)。這樣說(shuō),understand?
實(shí)現(xiàn)步驟:
1.讓NewsAdapter實(shí)現(xiàn)接口AbsListView.OnScrollListener,并復(fù)寫(xiě)里面的方法
2.在復(fù)寫(xiě)的方法里面進(jìn)行一些操作。

@Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState){
            case SCROLL_STATE_IDLE:  //滑動(dòng)停止時(shí)。
                mImageLoader.loadImages(mStart, mEnd);
                break;
            case SCROLL_STATE_TOUCH_SCROLL: //正在滑動(dòng)時(shí)
                mImageLoader.cancelAllTasks();
                break;
            case SCROLL_STATE_FLING: //手指拋動(dòng)時(shí),即手指用力滑動(dòng)在離開(kāi)后ListView由于慣性而繼續(xù)滑動(dòng)

                break;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        //第一次的時(shí)候預(yù)加載
        if (mFirstIn && visibleItemCount > 0){
            mImageLoader.loadImages(mStart, mEnd);
            mFirstIn = false;
        }
    }

可以看到在滑動(dòng)停止時(shí)我們調(diào)用了ImageLoader類(lèi)的loadIamge方法,這是一個(gè)加載圖片的方法,用于加載Item對(duì)應(yīng)的第mSart項(xiàng)t到第mEnd項(xiàng)之間的數(shù)據(jù)(這兩個(gè)數(shù)據(jù)怎么來(lái)的,你先把這個(gè)問(wèn)題放在心里),看下loadImage法的代碼實(shí)現(xiàn):

public void loadImages(int start, int end){
        for (int i = start; i < end; i++){
            String url = NewsAdapter.URLS[i];
            //由緩存中得到bitmap
            Bitmap bitmap = getBitmapFormCache(url);
            if (bitmap == null){
                //當(dāng)bitmap為空時(shí),由AsyncTask進(jìn)行加載,并在onPostExecute()方法中setImageBitmap
                NewsAsyncTask task = new NewsAsyncTask(url);
                task.execute(url);
                mAsyncTask.add(task);
            } else {
                //當(dāng)bitmap不為空時(shí),直接進(jìn)行setImageBitmap
                ImageView imageView = (ImageView) mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }
    }

由代碼中可以看到我們由NewsAdatper.URLS[]中取出了start到end的url,然后加載了這些數(shù)據(jù),我們看下這個(gè)數(shù)組,這是在NewsAdapter的構(gòu)造器中,可以明顯看到里面存放了所有Item的圖片的url:

//將圖片的url存儲(chǔ)在數(shù)組中
        URLS = new String[data.size()];
        for (int i = 0; i < data.size(); i++) {
            URLS[i] = data.get(i).newsIconUrl;
        }

此時(shí)再來(lái)看NewsAsyncTask的代碼:

  //參數(shù)1:?jiǎn)?dòng)任務(wù)輸入的參數(shù),參數(shù)2:后臺(tái)任務(wù)執(zhí)行的百分比,參數(shù)3,后臺(tái)執(zhí)行任務(wù)的返回方法
    private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> {

        private String mUrl;

        public NewsAsyncTask(String stringUrl) {
            mUrl = stringUrl;
        }

        //doInBackground方法的參數(shù)是上面輸入的第一個(gè)參數(shù),返回的對(duì)象會(huì)傳遞給onPostExecute方法
        @Override
        protected Bitmap doInBackground(String... params) {
            String url = params[0];
            Bitmap bitmap = getBitmapFormURL(url);
            if (bitmap != null){
                addBitmapToCache(url,bitmap); //將bitmap添加到緩存
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //根據(jù)url從listView中找到對(duì)應(yīng)的ImageView
            ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
            if (imageView != null && bitmap != null){
                imageView.setImageBitmap(bitmap); //為imageView設(shè)置圖片
            }
            mAsyncTask.remove(this);
        }
    }

可以看到這就是根據(jù)url為所對(duì)應(yīng)ImageView設(shè)置圖片。看到這就只有一個(gè)問(wèn)題了,那就是mStart和mEnd是怎么得到的,再看代碼我們重寫(xiě)的onScroll方法:

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        //第一次的時(shí)候預(yù)加載
        if (mFirstIn && visibleItemCount > 0){
            mImageLoader.loadImages(mStart, mEnd);
            mFirstIn = false;
        }
    }

這個(gè)方法有三個(gè)參數(shù),對(duì)應(yīng)的為
firstVisibleItem:ListView所有當(dāng)前可見(jiàn)的Item第一個(gè)Item
visibleItemCount:可見(jiàn)Item的總數(shù)
totalItemCount :所有Item的總數(shù)
這樣一來(lái)大家是不是都懂了。

code

MainActivity.java

public class MainActivity extends AppCompatActivity {

    private ListView listView;

    private static final String url = "http://www.imooc.com/api/teacher?type=4&num=30";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        listView = (ListView) findViewById(R.id.lv);

        new MainAsyncTask().execute(url);

    }


    public List<NewsBean> getJsonData(String url){
        List<NewsBean> newsBeanList = new ArrayList<>();
        try {
            String jsonString = readStream(new URL(url).openStream());//利用readStream得到String數(shù)據(jù)
            Log.e("JSON",jsonString); //打印出string數(shù)據(jù)

            //下面解析得到的json數(shù)據(jù)
            JSONObject jsonObject;
            NewsBean newsBean;
            try {
                jsonObject = new JSONObject(jsonString);
                JSONArray jsonArray = jsonObject.getJSONArray("data");
                for (int i=0; i<jsonArray.length(); i++){
                    jsonObject = jsonArray.getJSONObject(i);
                    newsBean = new NewsBean();
                    newsBean.newsIconUrl = jsonObject.getString("picSmall");
                    newsBean.newsTitle = jsonObject.getString("name");
                    newsBean.newsContent = jsonObject.getString("description");
                    newsBeanList.add(newsBean);
                }
            } catch (JSONException e) {
                e.printStackTrace();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return newsBeanList;
    }

    //由輸入流中讀取數(shù)據(jù)并將數(shù)據(jù)返回
    public String readStream(InputStream in){
        InputStreamReader reader;
        String result = "";
        String line = "";
        try {
            reader = new InputStreamReader(in, "UTF-8");
            BufferedReader br = new BufferedReader(reader);
            while((line = br.readLine())!=null){
                result += line;
            }
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return result;
    }


    //異步線程類(lèi)
    class MainAsyncTask extends AsyncTask<String, Void, List<NewsBean>>{

        //該方法運(yùn)行在后臺(tái)線程中
        @Override
        protected List<NewsBean> doInBackground(String... params) {
            return getJsonData(params[0]);
        }

        @Override
        protected void onPostExecute(List<NewsBean> newsBeanList) {
            super.onPostExecute(newsBeanList);
            NewsAdapter newsAdapter = new NewsAdapter(MainActivity.this, newsBeanList, listView);
            listView.setAdapter(newsAdapter);
        }
    }
}

NewsAdapter.java

public class NewsAdapter extends BaseAdapter implements AbsListView.OnScrollListener {

    private List<NewsBean> newsBeanList = new ArrayList<>();
    private LayoutInflater mInflater;
    private ImageLoader mImageLoader;
    private int mStart, mEnd;
    public static String[] URLS;
    private boolean mFirstIn;

    public NewsAdapter(Context context, List<NewsBean> data, ListView listView) {
        newsBeanList = data;
        mInflater = LayoutInflater.from(context);
        mImageLoader = new ImageLoader(listView);

        //將圖片的url存儲(chǔ)在數(shù)組中
        URLS = new String[data.size()];
        for (int i = 0; i < data.size(); i++) {
            URLS[i] = data.get(i).newsIconUrl;
        }

        listView.setOnScrollListener(this);
        mFirstIn = true;
    }

    @Override
    public int getCount() {
        return newsBeanList.size();
    }

    @Override
    public Object getItem(int position) {
        return newsBeanList.get(position);
    }

    @Override
    public long getItemId(int position) {
        return position;
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        ViewHolder viewHolder;
        //判斷是否有緩存
        if (convertView == null) {
            //通過(guò)LayoutInflate實(shí)例化布局
            viewHolder = new ViewHolder();
            convertView = mInflater.inflate(R.layout.item_layout, parent, false);
            viewHolder.ivIcon = (ImageView) convertView.findViewById(R.id.iv_icon);
            viewHolder.tvTitle = (TextView) convertView.findViewById(R.id.tv_title);
            viewHolder.tvContent = (TextView) convertView.findViewById(R.id.tv_content);
            convertView.setTag(viewHolder);
        } else {
           //通過(guò)tag找到緩存的布局
            viewHolder = (ViewHolder) convertView.getTag();
        }
        NewsBean newsBean = newsBeanList.get(position);

        String urlString = newsBean.newsIconUrl;
        viewHolder.ivIcon.setTag(urlString); // 將ImageView與url綁定
        //普通異步加載
       // mImageLoader.showImageByThread(viewHolder.ivIcon,urlString);
        mImageLoader.showImageByAsyncTask(viewHolder.ivIcon,urlString);
        viewHolder.tvTitle.setText(newsBean.newsTitle);
        viewHolder.tvContent.setText(newsBean.newsContent);
        return convertView;
    }

    @Override
    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch (scrollState){
            case SCROLL_STATE_IDLE:  //滑動(dòng)停止時(shí)。
                mImageLoader.loadImages(mStart, mEnd);
                break;
            case SCROLL_STATE_TOUCH_SCROLL: //正在滑動(dòng)時(shí)
                mImageLoader.cancelAllTasks();
                break;
            case SCROLL_STATE_FLING: //手指拋動(dòng)時(shí),即手指用力滑動(dòng)在離開(kāi)后ListView由于慣性而繼續(xù)滑動(dòng)

                break;
        }
    }

    @Override
    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        mStart = firstVisibleItem;
        mEnd = firstVisibleItem + visibleItemCount;
        //第一次的時(shí)候預(yù)加載
        if (mFirstIn && visibleItemCount > 0){
            mImageLoader.loadImages(mStart, mEnd);
            mFirstIn = false;
        }
    }

    //使用ViewHolder
    private static class ViewHolder {
        private TextView tvTitle, tvContent;
        private ImageView ivIcon;
    }
}

ImageLoder.java

public class ImageLoader {

    private ImageView mImageView;
    private String mUrl;

    private LruCache<String, Bitmap> mCaches;
    private ListView mListView;
    private Set<NewsAsyncTask> mAsyncTask;

    public ImageLoader(ListView listView){

        mListView = listView;
        mAsyncTask = new HashSet<>();

        //下面是建立緩存
        int maxMemory = (int) Runtime.getRuntime().maxMemory();  //運(yùn)行時(shí)最大內(nèi)存
        int cacheSize = maxMemory/4;
        mCaches = new LruCache<String, Bitmap>(cacheSize){
            @Override
            protected int sizeOf(String key, Bitmap value) {
                return value.getByteCount();
            }
        };
    }

    //將bitmap添加到緩存
    public void addBitmapToCache(String url,Bitmap bitmap){
        if (getBitmapFormCache(url) == null){
            mCaches.put(url, bitmap);
        }
    }
    //從緩存中獲取數(shù)據(jù)
    public Bitmap getBitmapFormCache(String url){
        return mCaches.get(url);
    }
//===================================下面為普通異步加載===========================================
private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            if (mImageView.getTag().equals(mUrl)) { //當(dāng)url標(biāo)記和原先設(shè)置的一樣時(shí),才設(shè)置ImageView
                mImageView.setImageBitmap((Bitmap) msg.obj);
            }
        }
    };

    public void showImageByThread(ImageView imageView, final String url) {

        this.mImageView = imageView;
        this.mUrl = url;
        new Thread() {
            @Override
            public void run() {
                super.run();
                Bitmap bitmap = getBitmapFormURL(url);
                Message message = Message.obtain();
                message.obj = bitmap;
                handler.sendMessage(message);
            }
        }.start();
    }

//====================上面是使用普通的異步加載,下面是使用AsyncTask進(jìn)行的異步加載==================
    public Bitmap getBitmapFormURL(String urlString) {
        Bitmap bitmap;
        InputStream inputStream = null;

        try {
            URL url = new URL(urlString);
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            inputStream = new BufferedInputStream(conn.getInputStream());  //得到圖片的數(shù)據(jù)流
            bitmap = BitmapFactory.decodeStream(inputStream);  //根據(jù)數(shù)據(jù)流來(lái)解析出圖片的bitmap
            conn.disconnect();
            return bitmap;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        return null;
    }

   
    //加載圖片
    public void showImageByAsyncTask(ImageView ImageView, String url) {
        Bitmap bitmap = getBitmapFormCache(url);
        if (bitmap == null){
            ImageView.setImageResource(R.mipmap.ic_launcher);
        }else{
            ImageView.setImageBitmap(bitmap);
        }
    }

    public void cancelAllTasks(){
        if (mAsyncTask != null){
            for (NewsAsyncTask task : mAsyncTask){
                task.cancel(false);
            }
        }
    }

    public void loadImages(int start, int end){
        for (int i = start; i < end; i++){
            String url = NewsAdapter.URLS[i];
            //由緩存中得到bitmap
            Bitmap bitmap = getBitmapFormCache(url);
            if (bitmap == null){
                //當(dāng)bitmap為空時(shí),由AsyncTask進(jìn)行加載,并在onPostExecute()方法中setImageBitmap
                NewsAsyncTask task = new NewsAsyncTask(url);
                task.execute(url);
                mAsyncTask.add(task);
            } else {
                //當(dāng)bitmap不為空時(shí),直接進(jìn)行setImageBitmap
                ImageView imageView = (ImageView) mListView.findViewWithTag(url);
                imageView.setImageBitmap(bitmap);
            }
        }
    }

    //參數(shù)1:?jiǎn)?dòng)任務(wù)輸入的參數(shù),參數(shù)2:后臺(tái)任務(wù)執(zhí)行的百分比,參數(shù)3,后臺(tái)執(zhí)行任務(wù)的返回方法
    private class NewsAsyncTask extends AsyncTask<String, Void, Bitmap> {

        private String mUrl;

        public NewsAsyncTask(String stringUrl) {
            mUrl = stringUrl;
        }

        //doInBackground方法的參數(shù)是上面輸入的第一個(gè)參數(shù),返回的對(duì)象會(huì)傳遞給onPostExecute方法
        @Override
        protected Bitmap doInBackground(String... params) {
            String url = params[0];
            Bitmap bitmap = getBitmapFormURL(url);
            if (bitmap != null){
                addBitmapToCache(url,bitmap); //將bitmap添加到緩存
            }
            return bitmap;
        }

        @Override
        protected void onPostExecute(Bitmap bitmap) {
            super.onPostExecute(bitmap);
            //根據(jù)url從listView中找到對(duì)應(yīng)的ImageView
            ImageView imageView = (ImageView) mListView.findViewWithTag(mUrl);
            if (imageView != null && bitmap != null){
                imageView.setImageBitmap(bitmap);
            }
            mAsyncTask.remove(this);
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容