2012/02/06
Android の Adapter#getView が呼ばれるタイミングと動作
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 件のコメント:
コメントを投稿