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 件のコメント:
コメントを投稿