2012/02/06

Android の Adapter#getView が呼ばれるタイミングと動作

Android の開発では、ListView 等を使うことがよくあります。
ListView 等(他には GridView、Gallery など)を使う場合は Adapter を使いますが、その肝となる getView がどのように呼ばれるのか、気になったので調べてみました。

具体的には、以下のような Adapter を作り、実際に動作させた結果を見てみます。

動作はシンプルで、100個のアイテムがあり、各アイテムはポジション番号と一致したIntegerというものです。
View のインスタンスを新たに作成する際に通し番号をセットしていき、動作を見てみたいと思います。
public class MyAdapter extends BaseAdapter {
    private static final String TAG = "MyAdapter";

    private final LayoutInflater inflater;
    private int inflatedViewNo = 0;

    public TestAdapter(Context context) {
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public int getCount() {
        return 100;
    }

    public Object getItem(int position) {
        return position;
    }

    public long getItemId(int position) {
        return 0;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if (convertView == null) {
            view = inflater.inflate(android.R.layout.two_line_list_item, parent, false);
            // 通し番号をTagにセットする
            view.setTag(inflatedViewNo);
            Log.d(TAG, "Inflate " + inflatedViewNo + "  (position: " + position + ")  " + view.hashCode());
            inflatedViewNo++;
        } else {
            view = convertView;
        }

        {
            TextView tv1 = (TextView)view.findViewById(android.R.id.text1);
            StringBuilder sb = new StringBuilder("position : ");
            sb.append(getItem(position));
            tv1.setText(sb);
        }

        {
            TextView tv2 = (TextView)view.findViewById(android.R.id.text2);
            StringBuilder sb = new StringBuilder("Inflate : ");
            sb.append((Integer)view.getTag());
            tv2.setText(sb);
        }

        Log.d(TAG, "Display (position: " + position + ")  " + view.hashCode());

        return view;
    }

}

2012/3/3 追記
検証に使用したコードを GitHub で公開しました。
以下の要領で取得できます。
2013/10/29 追記
諸事情により GitHub から Bitbucket に引越しました。
$ git clone https://kokufu@bitbucket.org/kokufu/ListViewExperiment.git
$ cd ListViewExperiment
$ git checkout 20120206

まず、最初に表示させると、以下のようになります。


Inflateの通し番号とposition番号が一致しています。
これをゆっくり上に送っていくと、以下のようになります。
ちょっとわかりにくいのですが、上に消えた0番が下で再利用されて12番になっているのがわかります。

つまり、最初に表示される分+α(今回の例では0~11の12個)を表示するとき、 getView の引数 convertView は null で呼び出されます。

その後は、convertView にインスタンスが入った状態で呼び出されます。
このとき、新たにViewをInflateしても表示は問題ありませんが、毎回Inflateしたらパフォーマンスに影響します。
そのため、convertView が null じゃないときは、Viewの表示だけ変えて使いまわすわけです。


ところで、1列ごとに送る場合は話がわかりやすいのですが、スピーディーに列を送った場合、再利用される列の順番はタイミング次第です。
何番目の列がどこに再利用されるのかはわかりませんので、それを意識した実装はしないようにしましょう。(そもそも、そんな実装するのは相当難しそうですが…)




ちなみに、今回の例では findViewById を使って View を取得していますが、これも多少パフォーマンスに影響します。(IDをサーチしているわけなので)
というわけで、各Viewへの参照を Tag に登録しておき、さらにパフォーマンスを上げる手法が 2010年の Google I/O で紹介されていました。
「ViewHolder」で検索するといろいろなページがひっかかりますので、参考にしてみてください。


さて、ここまでで終われば話は簡単なのですが、この実験をしている最中に気になることを見つけてしまいました。
理由は全然わかっていないのですが、近日中にブログに書きたいと思います。

2012/2/7 追記
勢いで書きました
ListView の表示時に無駄が多すぎなのではないか?

0 件のコメント: