過去に出会った不具合について覚書。
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周辺はよく不具合が出るので、
# なるべくなら使用を避けたいところです。
以上です。