1. 使用後臺線程,讓ListView流暢滾動
有經驗的開發者經常建議:不要在主線程進行耗時的操作。但對於初學者來說,到底什麼是主線程呢?這個問題我琢磨了很久。主線程其實就是UI線程,這個線程負責處理跟UI相關的操作。界面的繪製、界面的加載、控件的調整等操作都是由主線程來執行的。UI線程之所以被稱作主線程,是因爲在絕大多數情況下,我們寫的代碼都會在跑在UI線程裏,比如界面的各個生命週期的回調方法(onCreate(),onResume(), onPause()等)。
由於主線程處理跟UI相關的操作,如果主線程處理耗時的操作(文件處理、網絡請求等),那麼就會導致主線程無法及時處理其他的UI操作請求,這樣我們在界面上就會明顯的感覺到卡頓。這個問題在處理ListView這種需要重複的繪製列表元素的情況下會變得更加嚴峻。因此需要小心處理ListAdapter裏getView()方法裏可能存在的耗時操作。
但使用後臺線程就要接觸多線程了,而多線程操作又是衆所周知的坑多。如果不想自己手動去維護自己的線程池或實現自己的多線程機制,那麼可以使用AsyncTask類來實現後臺處理任務的需求。這是Android提供的一個非常便捷的方法。
- // 使用AsyncTask在後臺加載一張加載很慢的圖片
- // 這裏的三個泛型參數的意義分別是:
- // 1) UI控件、2)進度回調的類型、3)任務執行完畢後的返回類型
- new AsyncTask<ViewHolder, Void, Bitmap>() {
- // ViewHolder技術是實現複雜ListView的一個優化性能的技術,後面會介紹
- private ViewHolder v;
- // doInBackground()方法在後臺線程執行
- @Override
- protected Bitmap doInBackground(ViewHolder... params) {
- v = params[0]; // 這裏保存一份界面的引用
- // 這裏假定有一個圖片加載器,getImage()方法是它的一個耗時方法
- return mFakeImageLoader.getImage();
- }
- // onPostExecute()方法在主線程執行,確保所有耗時的操作在doInBackground()方法做完了
- @Override
- protected void onPostExecute(Bitmap result) {
- super.onPostExecute(result);
- if (v.position == position) {
- v.progress.setVisibility(View.GONE);
- v.icon.setVisibility(View.VISIBLE);
- v.icon.setImageBitmap(result);
- }
- }
- }.execute(holder);
從Android 3.0(API 11)開始,AsyncTask增加了一個方法:AsyncTask.executeOnExecutor()。這個方法會利用處理器的多核特性,進一步提升性能。這個方法的具體效果取決於具體設備的處理器核心數。
2. 使用ViewHolder技術來優化ListView
前面說到,在ListView這種需要重複的繪製列表元素的情況下,性能問題將變得更加嚴峻。由於內存的原因,ListView只有一個固定數量的View列表來顯示列表的每一項,通過回收不可見的項,重新調整控件來顯示新出現在界面上的項。因此在ListView的滾動過程中,將會頻繁的調用ListAdapter.getView()方法。通過上面的優化建議,我們已經把耗時操作放到後臺線程去執行了,只把必須的UI操作留在主線程。那還能不能再優化呢?答案是肯定的。
如果有經常在網上查找教程,那麼應該對ListView的教程裏的ViewHolder技術不陌生。這個技術的核心是減少主線程裏的執行步驟,以達到優化性能的目的。通過使用ViewHolder,緩存每個View的引用,減少不必要的查找控件操作,以達到優化ListView性能的效果。
首先需要創建一個ViewHolder類來持有View的引用:
- static class ViewHolder {
- TextView text;
- TextView timestamp;
- ImageView icon;
- ProgressBar progress;
- }
然後實例化ViewHolder併爲其賦值,再將ViewHolder存儲在view的tag裏:
- ViewHolder holder = new ViewHolder();
- holder.text = (TextView) convertView.findViewById(R.id.list_item_text);
- holder.timestamp = (TextView) convertView.findViewById(R.id.list_item_timestamp);
- holder.icon = (ImageView) convertView.findViewById(R.id.list_item_icon);
- holder.progress = (ProgressBar) convertView.findViewById(R.id.list_item_progress);
- convertView.setTag(holder);
之後就可以從convertView的tag裏取出ViewHolder來直接訪問對應的控件進行操作了。
- ViewHolder holder = (ViewHolder) convertView.getTag();
- holder.text.setText(text);
- // ...這裏省略對其他控件的操作