編輯:關於Android編程
對於ListView,大家絕對都不會陌生,只要是做過Android開發的人,哪有不用ListView的呢?
只要是用過ListView的人,哪有不關心對它性能優化的呢?
關於如何對ListView進行性能優化,不僅是面試中常常會被問到的(我前段時間面試了幾家公司,全部都問到了這個問題了),而且在實際項目中更是非常重要的一環,它甚至在某種程度上決定了用戶是否喜歡接受你的APP。(如果你的列表滑起來很卡,我敢說很多人會直接卸載)
網上關於如何對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顯示出來
這裡,我們用到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工具類其實並沒有啥特別之處,無非就是利用Xutils框架的HttpUtil發送網絡請求,獲取數據。 方法參數裡,我們加入了一個IOAuthCallBack回調接口,該接口主要用戶在Activity和工具類之間回調請求結果數據。() { @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);// 利用接口回調數據傳輸 } }); } }
/** * 數據請求回調接口 */ 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 Listdata; public String msg; }
public class KeCheng { public String id; public String name; public String picSmall; public String picBig; public String description; public String learner; }
在此之前,我們先看下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 ListmDatas; 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); } }
我們知道,當快速滑動列表時(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) { } };
本文主要通過三個方面:1、復用convertView;2、異步加載圖片; 3、ListView快速滑動時不顯示圖片介紹了如何對ListView進行性能優化,這是最常見也是最重要的三個方面,建議大家務必將其使用在自己項目的開發中,以提高列表的易用性!
當然,文章還提到了兩個第三方框架的使用:Xutils和Universal-Image-,這是兩個非常使用的框架,建議大家也能學習下。
如果大家還有別的優化方案,建議提出來,共同學習,共同進步。
前一節我們學習了Intent的基本用法,多個Activity之間的轉跳並傳遞信息。 今天我們使用Intnet來完成一些 特殊的操作,比如撥打電話,發送短信,浏覽網頁等……
(一).前言:這幾天正在更新錄制實戰項目,整體框架是采用仿照QQ5.X側滑效果的。那麼我們一般的做法就是自定義ViewGroup或者采用開源項目MenuDra
Fragment的主要意義就是提供與Activity綁定的生命周期回調。Fragment不一定要向Activity的視圖層級中添加View. 當某個模塊需要獲得Activ
這次是一個時鐘類應用,目前依舊是主要的功能,長得還是很挫。當然了,核心功能是有的……鬧鐘之前的准備布局的話,不管是采用FrameLayout或