過去に出会った不具合について覚書。
Header付きListViewを持つDialogを表示しながら画面回転したり、
言語切替した後に復帰すると稀にエラーが発生しました。
●手順
- Header付きListViewを持つDialogを表示中に画面回転
- Header付きListViewを持つDialogを表示
- Homeボタンでアプリをバックグラウンドへ
- 言語切り替えを行う
- 手順1の画面に復帰
●試験結果
アプリが強制終了する場合がある。エラーログ:
FATAL EXCEPTION: main java.lang.IndexOutOfBoundsException: Invalid index 0, size is 0 at java.util.ArrayList.throwIndexOutOfBoundsException(ArrayList.java:251) at java.util.ArrayList.get(ArrayList.java:304) at android.widget.HeaderViewListAdapter.isEnabled(HeaderViewListAdapter.java:164) at android.widget.ListView.dispatchDraw(ListView.java:3342) at android.view.View.draw(View.java:10999) at android.widget.AbsListView.draw(AbsListView.java:3591) at android.view.View.getDisplayList(View.java:10435) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597) at android.view.View.getDisplayList(View.java:10398) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597) at android.view.View.getDisplayList(View.java:10398) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597) at android.view.View.getDisplayList(View.java:10398) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597) at android.view.View.getDisplayList(View.java:10398) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:2597) at android.view.View.getDisplayList(View.java:10398) at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:875) at android.view.ViewRootImpl.draw(ViewRootImpl.java:2027) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1751) at android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2559) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loop(Looper.java:137) at android.app.ActivityThread.main(ActivityThread.java:4475) at java.lang.reflect.Method.invokeNative(Native Method) at java.lang.reflect.Method.invoke(Method.java:511) at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:559) at dalvik.system.NativeStart.main(Native Method)
●再現率
3/5●原因解析
同様の現象が発生する最小構成のコードが下記。public class HeaderListViewActivity extends Activity { ArrayList<String> elements = new ArrayList<String>(); @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } @Override protected void onDestroy() { super.onDestroy(); elements.clear(); } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { showDialog(1); } return super.onKeyDown(keyCode, event); } @Override protected Dialog onCreateDialog(int id) { if (id == 1) { elements.add("A"); elements.add("B"); elements.add("C"); ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, elements); AlertDialog dialog = new AlertDialog.Builder(this) .setSingleChoiceItems(adapter, 1, null).create(); TextView header = new TextView(this); header.setText("header"); dialog.getListView().addHeaderView(header, null, false); return dialog; } return super.onCreateDialog(id); } }
問題が発生するトリガはonDestroyで実行しているelements.clear()でした。
elementsはDialogが持つListViewに紐付けられたデータセットです。
タイミングによっては、onDestroyの後にDialogのListViewがelementsへアクセスする
ようで、HeaderViewListAdapter.isEnabledで範囲外を参照してしまうようです。
また、この例外は37行目のaddHeaderViewで、第三引数にfalseを渡した場合に発生します。
# つまりtrueを設定すると再現しない
●解決策
今回のケースだと、elementsをclearする理由がなかったので、これをやめるだけで対応できたけれど、clearする必要がある場合はどうするのだろう。。。
stackoverflowをみると「Samsung phneだと起きる」という人がいたりするけれど、
今回の結果を見ると、組む側の問題のような気がします...
# 個人的にListHeaderViewやListFooterView周辺はよく不具合が出るので、
# なるべくなら使用を避けたいところです。
以上です。