2014年5月26日 星期一

Android 開發 (四十八) performance optimization (ListView)

最近在寫project時遇到listview滑動非常不順暢的情形,通常只有在view包了太多層時才會出現這樣的情形,然而我已經將整個layout 攤平了....即使如此在滑動的過程依然是非常的頓。

在我蒐集了相關的資料後,大致上有了初步debug的方向,


讓 adapter在 getview時少做點事情

根據android developer文件描述

Your code might call findViewById() frequently during the scrolling of ListView, which can slow down performance. Even when the Adapter returns an inflated view for recycling, you still need to look up the elements and update them. A way around repeated use of findViewById() is to use the "view holder" design pattern.

意思就是使用viewholder pattern來減少 findviewById的動作,

範例如下

  @Override
  public View getView(int position, View convertView, ViewGroup parent) {
   ViewHolder holder ;
   if(convertView==null){
    LayoutInflater inflater =  LayoutInflater.from(getContext());
    convertView =  inflater.inflate(R.layout.listviewLayout, null,false);
    holder = new ViewHolder();
    holder.title = (TextView)convertView.findViewById(R.id.title);
    convertView.setTag(holder);    
   }else{
    holder = (ViewHolder)convertView.getTag();
   }
   holder.title.setText("ha");
   
   return convertView;
  }

  static class ViewHolder{
   TextView title;
  }



除了使用viewholder pattern之外,當然也要極力避免在getview時做init的動作,
所有的事情最好在第一次  if(convertView == null) 時就將事情做掉,
例如background的設置,文字顏色設置,view的size,時間的計算...etc,總之能不在getview裡做的就別在裡面做


避免GC頻繁的發生

當我們在getView create Object 並且將他們destroy ,這會造成GC頻繁的被觸發,我們只需要看debug log,就可以知道GC是否在getview時不斷的觸發。


Load Image

根據android developer的建議是使用async task的方式去讀取圖片,
// Using an AsyncTask to load the slow images in a background thread
new AsyncTask<ViewHolder, Void, Bitmap>() {
    private ViewHolder v;

    @Override
    protected Bitmap doInBackground(ViewHolder... params) {
        v = params[0];
        return mFakeImageLoader.getImage();
    }

    @Override
    protected void onPostExecute(Bitmap result) {
        super.onPostExecute(result);
        if (v.position == position) {
            // If this item hasn't been recycled already, hide the
            // progress and set and show the image
            v.progress.setVisibility(View.GONE);
            v.icon.setVisibility(View.VISIBLE);
            v.icon.setImageBitmap(result);
        }
    }
}.execute(holder);
其實網路上有一些 solution,PicassoUniversal Image Loader,使用他們可以快速解決讀取圖片的問題。


使用scrollingCache 和 animateCache

使用scrollingCache其實就是個DrawingCache,使用他可以避免在每個frame重畫view,進而達到速度的提升,當然他的缺點就是會花費memory。

animationCache 就是layout animation時是否需要使用drawingCache,使用animationCache 需要花費較多的memory,但是可以換來較好的performance。
範例如下

    <ListView
        android:id="@android:id/list"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:animationCache="true"
        android:scrollingCache="true" />

盡量將layout攤平


看一下下面的範例

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="horizontal" >

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="a" />

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="b" />

        <TextView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:text="c" />
    </LinearLayout>

</LinearLayout>

其實上面的layout使用relativeLayout 一層就可以搞定了,越多層layout會使getview花越多時間,基本上三層以上就會很有感覺了


如果能使用圖片,盡量使用圖片

有些時候會為了客制某些圖樣,我們必須使用如下的方式去繪製圖片

    <LinearLayout
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:orientation="vertical" >

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/a" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/b" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/c" />

        <ImageView
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:src="@drawable/d" />
    </LinearLayout>

example of a.xml

        <item>
        <layer-list>
            <item android:bottom="4dp" android:bottom="4dp">
                ...
            </item>
            <item android:left="4dp" android:top="4dp">
                ...
            </item>
        </layer-list>
    </item>

自己畫的好處是,即使螢幕變寬變長,layout不至於被影響,缺點是當所有的layout都用畫的,其實getview會花費較多的時間,我會發現這個的原因,是由於我客製的layout不過是一個linearlayout裡面包含分隔線還有一些文字,但是只要這個layout出現,畫面就會變得很頓,在經過了多方嘗試之後,我決定使用圖片,所有的問題就迎刃而解了。

最後附上 reference
在android 中要讓 listview平順的滑動真的需要花很多時間去tune啊...

沒有留言:

張貼留言