Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android ListView性能優化實戰方案

Android ListView性能優化實戰方案

編輯:關於Android編程

前言:

對於ListView,大家絕對都不會陌生,只要是做過Android開發的人,哪有不用ListView的呢?

只要是用過ListView的人,哪有不關心對它性能優化的呢?

關於如何對ListView進行性能優化,不僅是面試中常常會被問到的(我前段時間面試了幾家公司,全部都問到了這個問題了),而且在實際項目中更是非常重要的一環,它甚至在某種程度上決定了用戶是否喜歡接受你的APP。(如果你的列表滑起來很卡,我敢說很多人會直接卸載)

網上關於如何對ListView進行性能優化,提出了很多方案。但是我搜過很多資料,卻感覺很多文章都寫得比較模糊,沒有代碼說明,讓我感到很累。要知道能給程序員最直接的,當然是代碼啦!!!

 

 

一、Listview 性能優化方案

 

  1).復用convertView

在getItemView中,判斷convertView是否為空,如果不為空,可復用。如果couvertview中的view需要添加listerner,代碼一定要在if(convertView==null){}之外。

  2).異步加載圖片

item中如果包含有webimage,那麼最好異步加載

  3).快速滑動時不顯示圖片

當快速滑動列表時(SCROLL_STATE_FLING),item中的圖片或獲取需要消耗資源的view,可以不顯示出來;而處於其他兩種狀態(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來

 

二、實戰講解如何優化ListView

2.1 我們先定義一個ListView

 


 

2.2 然後我們去寫一個網絡請求,獲取網絡的json字符串。

 

這裡,我們用到xutils框架的httputil,通過它,可以很方便的進行網絡請求。 至於請求的url,我們使用慕課網提供的視頻數據列表接口“http://www.imooc.com/api/teacher?type=4&num=30”。先讓我們看下我寫的一個HTTP請求的工具類:

 

import android.content.Context;

import com.lidroid.xutils.HttpUtils;
import com.lidroid.xutils.exception.HttpException;
import com.lidroid.xutils.http.RequestParams;
import com.lidroid.xutils.http.ResponseInfo;
import com.lidroid.xutils.http.callback.RequestCallBack;
import com.lidroid.xutils.http.client.HttpRequest.HttpMethod;
import com.lidroid.xutils.util.LogUtils;

/**
 * 網絡請求工具類
 *
 * @author lining
 */
public class HttpUtil {
    /**
     * 請求的根URL地址
     */
    public static final String BASE_URL = http://www.imooc.com/api/teacher?type=4&num=50;

    public static void sendRequest(final Context context,
                                   final HttpMethod method, RequestParams params,
                                   final IOAuthCallBack iOAuthCallBack) {
        HttpUtils http = new HttpUtils();

        http.configCurrentHttpCacheExpiry(1000 * 5);
        // 設置超時時間
        http.configTimeout(5 * 1000);
        http.configSoTimeout(5 * 1000);

        if (method == HttpMethod.GET) {
            http.configCurrentHttpCacheExpiry(5000); // 設置緩存5秒,5秒內直接返回上次成功請求的結果。
        }

        http.send(method, BASE_URL, params,
                new RequestCallBack() {

                    @Override
                    public void onStart() {
                        LogUtils.d(method.name() +  request is onStart.......);
                    }

                    @Override
                    public void onSuccess(ResponseInfo responseInfo) {
                        LogUtils.d(statusCode: + responseInfo.statusCode +  -----> + responseInfo.result);
                        iOAuthCallBack.getIOAuthCallBack(responseInfo.result);// 利用接口回調數據傳輸
                    }

                    @Override
                    public void onFailure(HttpException error, String msg) {
                        LogUtils.d(statusCode: + error.getExceptionCode() +  ----->  + msg);
                        iOAuthCallBack.getIOAuthCallBack(FF);// 利用接口回調數據傳輸
                    }
                });
    }
}
工具類其實並沒有啥特別之處,無非就是利用Xutils框架的HttpUtil發送網絡請求,獲取數據。 方法參數裡,我們加入了一個IOAuthCallBack回調接口,該接口主要用戶在Activity和工具類之間回調請求結果數據。

 

 

/**
 * 數據請求回調接口
 */
public interface IOAuthCallBack
{
    // 成功
    public void getIOAuthCallBack(String result);
}

 

下面,我們Activity發送一個網絡請求,獲取json數據,並回調處理:

 

 private void qryDataFromServer() {
        HttpUtil.sendRequest(this, HttpRequest.HttpMethod.GET, null, this);
    }

    @Override
    public void getIOAuthCallBack(String result) {

        RspData rspData = GsonUtil.getGson().fromJson(result, RspData.class);
        // 更新UI列表

        KechengAdapter mAdapter = new KechengAdapter(this, rspData.data);
        listview.setAdapter(mAdapter);
    }

 

 

這裡關於json數據的解析使用的GSON,無啥特別說明之處,把實體類的代碼貼出來看下:

 

public class RspData {
    public String status;
    public List data;
    public String msg;
}

public class KeCheng {

    public String id;

    public String name;

    public String picSmall;

    public String picBig;

    public String description;

    public String learner;
}

 

2.3 有了集合數據之後,去定義BaseAdapter

 

在此之前,我們先看下list item的布局文件:list_item_kecheng.xml

 




    
    

    

 

 

接下來,讓我們好好看看Adapter是如何定義的:

 

import android.content.Context;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.List;

public class KechengAdapter extends BaseAdapter {

    private Context mContext;

    private LayoutInflater mInflater;

    private List mDatas;

    public KechengAdapter(Context context, List datas) {
        mContext = context;
        mInflater = LayoutInflater.from(mContext);
        mDatas = datas;
    }

    @Override
    public int getCount() {
        return (mDatas != null ? mDatas.size() : 0);
    }

    @Override
    public Object getItem(int position) {
        return (mDatas != null ? mDatas.get(position) : null);
    }

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

    @Override
    public View getView(final int position, View convertView, ViewGroup parent) {
        ViewHolder holder = null;
        if (convertView == null) {
            convertView = mInflater.inflate(R.layout.list_item_kecheng, null);

            holder = new ViewHolder();
            holder.picBig = (ImageView) convertView.findViewById(R.id.picBig);
            holder.name = (TextView) convertView.findViewById(R.id.name);
            holder.description = (TextView) convertView.findViewById(R.id.description);

            convertView.setTag(holder);
        } else {
            holder = (ViewHolder) convertView.getTag();
        }

        final KeCheng keCheng = mDatas.get(position);

        if (keCheng != null) {
            ImageLoaderUtil.getInstance().displayListItemImage(keCheng.picBig, holder.picBig);
            holder.name.setText(keCheng.name);
            holder.description.setText(keCheng.description);

        }
        return convertView;
    }

    static class ViewHolder {

        ImageView picBig;

        TextView name;

        TextView description;
    }
}

 

ListView性能優化的重點就是如何去處理BaseAdapter,且看上面的代碼,我們在getView中,判斷convertView是否為空,如果不為空,可復用。如何復用的呢?

我們通過convertview的setTag方法和getTag方法來將我們要顯示的數據來綁定在convertview上。如果convertview 是第一次展示我們就創建新的Holder對象與之綁定,並在最後通過return convertview 返回,去顯示;如果convertview 是回收來的那麼我們就不必創建新的holder對象,只需要把原來的綁定的holder取出加上新的數據就行了。

如果couvertview中的view需要添加listerner,代碼一定要在if(convertView==null){}之外。

 

看代碼夠仔細的人能夠發現有這麼一行代碼,ImageLoaderUtil.getInstance().displayListItemImage(keCheng.picBig, holder.picBig); 這是使用的圖片異步加載框架Universal-Image-Loader來完成對網絡圖片的異步加載、緩存,(強烈推薦使用)使用這個開源框架後,我們就無需再為如何加載緩存網絡圖片煩惱啦!

快隨我一起看看如何配置這個框架吧:

 

import android.content.Context;
import android.graphics.Bitmap;
import android.widget.ImageView;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiscCache;
import com.nostra13.universalimageloader.cache.disc.naming.Md5FileNameGenerator;
import com.nostra13.universalimageloader.cache.memory.impl.UsingFreqLimitedMemoryCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.assist.QueueProcessingType;

import java.io.File;

/**
 * 配置全局的 Android-Universal-Image-Loader
 */
public class ImageLoaderUtil {
    private static ImageLoaderUtil instance = null;

    private ImageLoader mImageLoader;

    // 列表中默認的圖片
    private DisplayImageOptions mListItemOptions;

    // 頭像圖片
    private DisplayImageOptions mUserHeadOptions;

    private ImageLoaderUtil(Context context) {
        mImageLoader = ImageLoader.getInstance();
        mListItemOptions = new DisplayImageOptions.Builder()
                // 設置圖片Uri為空或是錯誤的時候顯示的圖片
                .showImageForEmptyUri(R.mipmap.load_default_img)
                .showStubImage(R.mipmap.load_default_img)
                        // 設置圖片加載/解碼過程中錯誤時候顯示的圖片
                .showImageOnFail(R.mipmap.load_default_img)
                        // 加載圖片時會在內存、磁盤中加載緩存
                .cacheInMemory()
                .cacheOnDisc()
                .bitmapConfig(Bitmap.Config.RGB_565)
                .delayBeforeLoading(300)
                .build();

    }

    public static ImageLoaderUtil getInstance() {
        return instance;
    }

    public synchronized static ImageLoaderUtil init(Context context) {
        if (instance == null) {
            instance = new ImageLoaderUtil(context);
        }

        File cacheDir = context.getExternalFilesDir(news/pictures);
        ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(context).threadPriority(
                Thread.NORM_PRIORITY - 2).denyCacheImageMultipleSizesInMemory()
                // .imageDownloader(imageDownloader).imageDecoder(imageDecoder)
                .discCacheFileNameGenerator(new Md5FileNameGenerator()).tasksProcessingOrder(QueueProcessingType.LIFO).memoryCacheExtraOptions(
                        360, 360).memoryCache(new UsingFreqLimitedMemoryCache(4 * 1024 * 1024)).discCache(
                        new UnlimitedDiscCache(cacheDir)).build();
        // Initialize ImageLoader with configuration.
        ImageLoader.getInstance().init(config);

        return instance;
    }

    /**
     * 列表圖片
     *
     * @param uri
     * @param imageView
     */
    public void displayListItemImage(String uri, ImageView imageView) {
        String strUri = (isEmpty(uri) ?  : uri);
        mImageLoader.displayImage(strUri, imageView, mListItemOptions);
    }

    public ImageLoader getImageLoader() {
        return mImageLoader;
    }

    private boolean isEmpty(String str) {
        if (str != null && str.trim().length() > 0 && !str.equalsIgnoreCase(null)) {
            return false;
        }
        return true;
    }
}

 


這是我寫好的一個Universal-Image-Loader的工具類,以後可以直接使用它進行圖片的下載緩存處理了。 當然在使用前,還需要進行初始化它,我們推薦在Application中對其進行初始化操作:

 

public class MyApp extends Application {

    public static Context context;

    @Override
    public void onCreate() {
        super.onCreate();
        context = this;
        ImageLoaderUtil.init(context);
    }
}

 

2.4 處理快速滑動時暫停加載圖片

 

我們知道,當快速滑動列表時(SCROLL_STATE_FLING),item中的圖片獲取需要消耗資源的View,可以不顯示出來(因為滑動的過快,我們也不需要看圖片啊);而處於其他兩種狀態(SCROLL_STATE_IDLE 和SCROLL_STATE_TOUCH_SCROLL),則將那些view顯示出來。

那如何實現呢? 這裡我還是推薦使用Universal-Image-Loader已經為大家封裝好了的方法,(當然,別的框架,如Xutils也封裝了相關的方法)。Universal-Image-Loader框架的com.nostra13.universalimageloader.core.assist.PauseOnScrollListener監聽器已經封裝了對滾動時圖片處理的監聽,我們只需要在為ListView組件設置滾動監聽的時候,把PauseOnScrollListener的實例傳入即可。這裡,又必須讓大家先看下PauseOnScrollListener的源碼:

 

public class PauseOnScrollListener implements OnScrollListener {
    private ImageLoader imageLoader;
    private final boolean pauseOnScroll;
    private final boolean pauseOnFling;
    private final OnScrollListener externalListener;

    public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling) {
        this(imageLoader, pauseOnScroll, pauseOnFling, (OnScrollListener)null);
    }

    public PauseOnScrollListener(ImageLoader imageLoader, boolean pauseOnScroll, boolean pauseOnFling, OnScrollListener customListener) {
        this.imageLoader = imageLoader;
        this.pauseOnScroll = pauseOnScroll;
        this.pauseOnFling = pauseOnFling;
        this.externalListener = customListener;
    }

    public void onScrollStateChanged(AbsListView view, int scrollState) {
        switch(scrollState) {
        case 0:
            this.imageLoader.resume();
            break;
        case 1:
            if(this.pauseOnScroll) {
                this.imageLoader.pause();
            }
            break;
        case 2:
            if(this.pauseOnFling) {
                this.imageLoader.pause();
            }
        }

        if(this.externalListener != null) {
            this.externalListener.onScrollStateChanged(view, scrollState);
        }

    }

    public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
        if(this.externalListener != null) {
            this.externalListener.onScroll(view, firstVisibleItem, visibleItemCount, totalItemCount);
        }

    }
}
大家可以看到,PauseOnScrollListener實現了OnScrollListener接口,這也就是剛剛為啥說可以把PauseOnScrollListener的實例設置到ListView監聽器的原因。PauseOnScrollListener有兩個重要的構造方法,其中參數pauseOnScroll控制我們緩慢滑動ListView,GridView是否停止加載圖片,pauseOnFling 控制猛的滑動ListView,GridView是否停止加載圖片。而另一個參數OnScrollListener customListener則可以用於留給開發者繼續回到處理相應的滑動監聽事件,比如列表是否滑動到了最後等等。

 

知道了如何利用PauseOnScrollListener,那我們在Activity之中只需要設置一句簡單的監聽代碼即可:

 

listview.setOnScrollListener(new PauseOnScrollListener(ImageLoaderUtil.getInstance().getImageLoader(), false, true));

如何你的項目需要下來刷新或者是滑動加載等功能,你又必須提供滑動事件的回調參數:

 

 

listview.setOnScrollListener(new PauseOnScrollListener(ImageLoaderUtil.getInstance().getImageLoader(), false, true, onScrollListener));
 private AbsListView.OnScrollListener onScrollListener = new AbsListView.OnScrollListener() {
        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            switch (scrollState) {
                case AbsListView.OnScrollListener.SCROLL_STATE_TOUCH_SCROLL:
                    // 觸摸後滾動
                    break;

                case AbsListView.OnScrollListener.SCROLL_STATE_FLING:
                    // 滾動狀態
                    break;

                case AbsListView.OnScrollListener.SCROLL_STATE_IDLE:
                    // 空閒狀態
                    if (view.getLastVisiblePosition() == view.getCount() - 1) {
                        System.out.println(************滾動到了最後一個***************);
                    }
                    break;
            }
        }
        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {

        }
    };

好啦,這樣做出的ListView已經很完美了,讓我們欣賞下它的效果吧:

 

\

 

 

結束語:

 

本文主要通過三個方面:1、復用convertView;2、異步加載圖片; 3、ListView快速滑動時不顯示圖片介紹了如何對ListView進行性能優化,這是最常見也是最重要的三個方面,建議大家務必將其使用在自己項目的開發中,以提高列表的易用性!

當然,文章還提到了兩個第三方框架的使用:Xutils和Universal-Image-,這是兩個非常使用的框架,建議大家也能學習下。

如果大家還有別的優化方案,建議提出來,共同學習,共同進步。

 

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