在日常使用ArrayList时,相信大家都有遇到过IndexOutOfBoundsException 这样的错误,但是往往遇到的情况是index 大于或者等于 Size的情况,而今天我这里说的却是Index 小于 Size的情况,这是在手机使用过程中突然出现了异常而导致了手机重启,具体报错堆栈如下:
java.lang.IndexOutOfBoundsException: Index: 1, Size: 2
at java.util.ArrayList.get(ArrayList.java:411)
at com.android.server.wm.DisplayContent.findTaskForControlPoint(DisplayContent.java:359)
at com.android.server.wm.TaskTapPointerEventListener.onPointerEvent(TaskTapPointerEventListener.java:109)
at com.android.server.wm.PointerEventDispatcher.onInputEvent(PointerEventDispatcher.java:53)
at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:191)
at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:182)
at android.view.InputEventReceiver.onBatchedInputEventPending(InputEventReceiver.java:133)
at android.view.InputEventReceiver.dispatchBatchedInputEventPending(InputEventReceiver.java:197)
at android.os.MessageQueue.nativePollOnce(Native Method)
at android.os.MessageQueue.next(MessageQueue.java:323)
at android.os.Looper.loop(Looper.java:136)
at android.os.HandlerThread.run(HandlerThread.java:61)
at com.android.server.ServiceThread.run(ServiceThread.java:46)
先说下出现Index 小于Size的情况的原因是因为多线程访问导致的。
大致看了下最后的执行过程,是在调用TaskTapPointerEventListener类的onPointerEvent()方法时出现问题的:
@Override
public void onPointerEvent(MotionEvent motionEvent) {
....
case MotionEvent.ACTION_HOVER_MOVE: {
final int x = (int) motionEvent.getX();
final int y = (int) motionEvent.getY();
final Task task = mDisplayContent.findTaskForControlPoint(x, y);
InputDevice inputDevice = motionEvent.getDevice();
if (task == null || inputDevice == null) {
mPointerIconType = TYPE_NOT_SPECIFIED;
break;
}
task.getDimBounds(mTmpRect);
而在里面调用DisplayContent类的findTaskForControlPoint()方法中会去访问mStacks,看过框架的人知道调用此类方法是需要加锁的,因为框架里面还有很多其他地方对mStacks进行添加或者删除,如果不加锁就会出现由于多线程访问导致的异常问题。
Task findTaskForControlPoint(int x, int y) {
final int delta = mService.dipToPixel(RESIZE_HANDLE_WIDTH_IN_DP, mDisplayMetrics);
for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
TaskStack stack = mStacks.get(stackNdx);
if (!StackId.isTaskResizeAllowed(stack.mStackId)) {
break;
}
final ArrayList<Task> tasks = stack.getTasks();
for (int taskNdx = tasks.size() - 1; taskNdx >= 0; --taskNdx) {
final Task task = tasks.get(taskNdx);
if (task.isFullscreen()) {
return null;
}
// We need to use the task's dim bounds (which is derived from the visible
// bounds of its apps windows) for any touch-related tests. Can't use
// the task's original bounds because it might be adjusted to fit the
// content frame. One example is when the task is put to top-left quadrant,
// the actual visible area would not start at (0,0) after it's adjusted
// for the status bar.
task.getDimBounds(mTmpRect);
mTmpRect.inset(-delta, -delta);
if (mTmpRect.contains(x, y)) {
mTmpRect.inset(delta, delta);
if (!mTmpRect.contains(x, y)) {
return task;
}
// User touched inside the task. No need to look further,
// focus transfer will be handled in ACTION_UP.
return null;
}
}
}
return null;
}
解决此类问题,要么就是给循环加锁,要么就改成用foreach的方式,这样如果出现同步访问会抛出异常,方便查找问题。
另外在Stack Overflow也搜到了类似问题,可以参考下:https://stackoverflow.com/questions/10474612/another-java-lang-indexoutofboundsexception-but-index-size
本来想本地模拟下这种情况,无奈这个线程同步问题确实不好复现出来,如果有同学遇到过类似问题,欢迎在评论区一起讨论。