Android中ArrayList.size()返回异常及数组越界问题排查
ArrayList 出现 size() 与实际元素数不一致及 IndexOutOfBoundsException 的原因
问题场景
Android应用中,支持增删的多元素列表偶尔出现异常:调试时ArrayList实际包含5个元素,但调用size()返回1,进而触发IndexOutOfBoundsException。
判断与删除代码(主线程执行)
if(amigos.size() == 1){ int a = 0; // 断点处:进入此分支,但调试显示实际有5个元素 } for(int i = 0; i < amigos.size(); i++){ if(amigos.get(i).getNombre().equalsIgnoreCase(nombreAmigo)){ amigos.remove(i); break; } }
列表填充代码(异步后台线程执行)
try { //json = new JSONObject(resultado); json = new JSONObject(resultado.substring(resultado.indexOf("{"), resultado.lastIndexOf("}") + 1)); JSONArray jsonArray = json.optJSONArray("amigosLista"); for (int i = 0; i < jsonArray.length(); i++) { JSONObject jsonArrayChild = jsonArray.getJSONObject(i); amigos.add(new DatosJugador(String.valueOf(jsonArrayChild .optString("avatar")), String.valueOf(jsonArrayChild .optString("nombre")), String.valueOf(jsonArrayChild .optString("puntos")))); } } catch (JSONException e) { return false; }
报错信息
E/AndroidRuntime: FATAL EXCEPTION: main Process: lorenzo.gaizka.tfmtapfastmultimode, PID: 4157 java.lang.IndexOutOfBoundsException: Index: 1, Size: 1 at java.util.ArrayList.get(ArrayList.java:437) at android.widget.HeaderViewListAdapter.isEnabled(HeaderViewListAdapter.java:168) at android.widget.ListView.dispatchDraw(ListView.java:3526) at android.view.View.draw(View.java:23005) at android.widget.AbsListView.draw(AbsListView.java:4508) at android.view.View.updateDisplayListIfDirty(View.java:21869) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ViewGroup.recreateChildDisplayList(ViewGroup.java:4526) at android.view.ViewGroup.dispatchGetDisplayList(ViewGroup.java:4499) at android.view.View.updateDisplayListIfDirty(View.java:21822) at android.view.ThreadedRenderer.updateViewTreeDisplayList(ThreadedRenderer.java:534) at android.view.ThreadedRenderer.updateRootDisplayList(ThreadedRenderer.java:542) at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:625) at android.view.ViewRootImpl.draw(ViewRootImpl.java:4652) at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:4370) at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:3481) at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:2272) at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:8999) at android.view.Choreographer$CallbackRecord.run(Choreographer.java:1160) at android.view.Choreographer.doCallbacks(Choreographer.java:950) at android.view.Choreographer.doFrame(Choreographer.java:879) at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:1142) at android.os.Handler.handleCallback(Handler.java:938) at android.os.Handler.dispatchMessage(Handler.java:99) at android.os.Looper.loopOnce(Looper.java:210) at android.os.Looper.loop(Looper.java:299) at android.app.ActivityThread.main(ActivityThread.java:8319) at java.lang.reflect.Method.invoke(Native Method) at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:556) at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1038)
异常核心原因
- ArrayList 非线程安全的本质:
ArrayList是为单线程场景设计的容器,内部的size变量、元素数组都没有线程同步机制。当后台异步线程执行add操作修改列表时,主线程同时调用size()、get()、remove(),会导致数据结构的不一致:- 后台线程可能已经向数组中添加了元素,但
size变量的更新没有及时同步到主线程,导致主线程读取到过时的size值(比如1),但实际数组里已经有5个元素。 - 反之,主线程修改
size(比如remove操作)时,后台线程的add操作也会破坏数据一致性。
- 后台线程可能已经向数组中添加了元素,但
- ListView绘制时的数据冲突:报错栈显示异常来自
HeaderViewListAdapter.isEnabled,说明ListView在UI线程绘制时,访问了已经因为并发操作而状态混乱的列表——此时列表的size被读取为1,但实际元素数量大于1,当ListView尝试访问索引1的元素时,就触发了越界异常。 - 遍历删除的潜在风险:即使没有并发问题,主线程中
for循环遍历+remove(i)的写法本身也有问题(删除元素后后续索引偏移),但这次异常的根因还是多线程并发操作非线程安全容器导致的数据不一致。
修复建议
- 改用线程安全容器:用
CopyOnWriteArrayList替换ArrayList,它通过写时复制的机制保证多线程下的安全访问。 - 同步所有列表操作:在所有访问
amigos的代码块外加上synchronized(amigos)锁,确保同一时间只有一个线程操作列表。 - 统一线程上下文:后台线程获取数据后,通过
runOnUiThread或Handler切换到主线程再执行列表的add操作,避免跨线程直接修改列表。
内容的提问来源于stack exchange,提问作者Gaizka
相关产品推荐
相关产品推荐

