michael-rapp / ChromeLikeTabSwitcher

Provides a tab switcher similar to the one, which is used in Google Chrome on Android
Apache License 2.0
1.23k stars 140 forks source link

IndexOutOfBoundsException when calling getTab() #20

Closed cooltey closed 6 years ago

cooltey commented 6 years ago

Steps to reproduce:

  1. Have 20+ tabs opened
  2. Scroll up to the top of tab list
  3. Click on the "x" button to close tab
  4. After closing ~10 tabs, try to scroll down.

Stacktrace:

java.lang.IndexOutOfBoundsException: Index: 15, Size: 8
    at java.util.ArrayList.get(ArrayList.java:437)
    at de.mrapp.android.tabswitcher.model.TabSwitcherModel.getTab(TabSwitcherModel.java:1158)
    at de.mrapp.android.tabswitcher.TabSwitcher.getTab(TabSwitcher.java:1482)
    at de.mrapp.android.tabswitcher.model.TabItem.create(TabItem.java:100)
    at de.mrapp.android.tabswitcher.iterator.ItemIterator.getItem(ItemIterator.java:134)
    at de.mrapp.android.tabswitcher.iterator.AbstractItemIterator.next(AbstractItemIterator.java:229)
    at de.mrapp.android.tabswitcher.layout.AbstractTabSwitcherLayout.calculatePositionsWhenDraggingToStart(AbstractTabSwitcherLayout.java:706)
    at de.mrapp.android.tabswitcher.layout.AbstractTabSwitcherLayout.onDrag(AbstractTabSwitcherLayout.java:1573)
    at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.notifyOnDrag(AbstractDragTabsEventHandler.java:342)
    at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.handleDrag(AbstractDragTabsEventHandler.java:675)
    at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.onDrag(AbstractDragTabsEventHandler.java:735)
    at de.mrapp.android.tabswitcher.gesture.AbstractTouchEventHandler.handleTouchEvent(AbstractTouchEventHandler.java:317)
    at de.mrapp.android.tabswitcher.gesture.TouchEventDispatcher.handleTouchEvent(TouchEventDispatcher.java:272)
    at de.mrapp.android.tabswitcher.TabSwitcher.onTouchEvent(TabSwitcher.java:1942)
    at android.view.View.dispatchTouchEvent(View.java:11722)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2955)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2636)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
    at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:455)
    at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1828)
    at android.app.Activity.dispatchTouchEvent(Activity.java:3292)
    at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
    at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:69)
    at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:417)
    at android.view.View.dispatchPointerEvent(View.java:11961)
    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4790)
    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4604)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4142)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4195)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4161)
    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4288)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4169)
    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4345)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4142)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4195)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4161)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4169)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4142)
    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6663)
    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6637)
    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6598)
    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6766)
    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:186)
    at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
    at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:177)
    at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6737)
    at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6789)
    at android.view.Choreographer$CallbackRecord.run(Choreographer.java:966)
    at android.view.Choreographer.doCallbacks(Choreographer.java:778)
    at android.view.Choreographer.doFrame(Choreographer.java:707)
    at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:952)
    at android.os.Handler.handleCallback(Handler.java:789)
    at android.os.Handler.dispatchMessage(Handler.java:98)
    at android.os.Looper.loop(Looper.java:251)
    at android.app.ActivityThread.main(ActivityThread.java:6572)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
michael-rapp commented 6 years ago

Thanks for reporting this issue. I just tried to reproduce the crash you have reported, but unfortunately I wasn't able to do so. Do you know a way to reliably trigger the crash other than randomly removing/adding tabs and swiping around?

cooltey commented 6 years ago

No, I don't have a way to reliably trigger the crash now.

dbrant commented 6 years ago

To provide a little more context, we've started using this component in the official Wikipedia app, and have been seeing this crash very sporadically from our users, although we haven't been able to reproduce it ourselves.

It doesn't seem to be specific to any particular device or API version, and the actual "index out of bounds" numbers are highly variable (not just "Index: 15, Size: 8"). We'll keep trying to reproduce.

michael-rapp commented 6 years ago

Wow, I wasn't aware that this library is used in an app that popular. I will see, if I can make any guesses about what might go wrong in these cases by taking a deeper look at the code.

dbrant commented 6 years ago

I have managed to reproduce the crash in your "example" app that is included in the repo. It happens if I repeatedly click near the bottom-right corner (which closes a tab that is off-screen at the bottom), and also keep clicking the "Undo" action in the snackbar, which is in the same location. If I do this a sufficient number of times very quickly, then scroll up, the crash occurs.

Here is a video that demonstrates my actions: https://youtu.be/AXCIDuQlNTY

michael-rapp commented 6 years ago

I was able to reproduce the crash that is shown in your video. Thank's for that!

However, I am not sure if this is actually the crash the stacktrace that was posted above corresponds to. This is because Logcat does not show an IndexOutOfBoundsException but a NullPointerException:

java.lang.NullPointerException: Attempt to invoke virtual method 'de.mrapp.android.tabswitcher.model.Tag de.mrapp.android.tabswitcher.model.AbstractItem.getTag()' on a null object reference
        at de.mrapp.android.tabswitcher.layout.AbstractTabSwitcherLayout.calculatePositionsWhenDraggingToStart(AbstractTabSwitcherLayout.java:708)
        at de.mrapp.android.tabswitcher.layout.AbstractTabSwitcherLayout.onDrag(AbstractTabSwitcherLayout.java:1573)
        at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.notifyOnDrag(AbstractDragTabsEventHandler.java:342)
        at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.handleDrag(AbstractDragTabsEventHandler.java:675)
        at de.mrapp.android.tabswitcher.layout.AbstractDragTabsEventHandler.onDrag(AbstractDragTabsEventHandler.java:735)
        at de.mrapp.android.tabswitcher.gesture.AbstractTouchEventHandler.handleTouchEvent(AbstractTouchEventHandler.java:317)
        at de.mrapp.android.tabswitcher.gesture.TouchEventDispatcher.handleTouchEvent(TouchEventDispatcher.java:272)
        at de.mrapp.android.tabswitcher.TabSwitcher.onTouchEvent(TabSwitcher.java:1963)
        at android.view.View.dispatchTouchEvent(View.java:11725)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2955)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2636)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
        at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2961)
        at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2650)
        at com.android.internal.policy.DecorView.superDispatchTouchEvent(DecorView.java:445)
        at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1828)
        at android.app.Activity.dispatchTouchEvent(Activity.java:3292)
        at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:68)
        at com.android.internal.policy.DecorView.dispatchTouchEvent(DecorView.java:407)
        at android.view.View.dispatchPointerEvent(View.java:11964)
        at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4776)
        at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4590)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4128)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4181)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4147)
        at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4274)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4155)
        at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4331)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4128)
        at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4181)
        at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4147)
        at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4155)
        at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4128)
        at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6642)
        at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6616)
        at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6577)
        at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6745)
        at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
2018-11-05 21:41:40.104 24555-24555/de.mrapp.android.tabswitcher.example E/AndroidRuntime:     at android.view.InputEventReceiver.nativeConsumeBatchedInputEvents(Native Method)
        at android.view.InputEventReceiver.consumeBatchedInputEvents(InputEventReceiver.java:176)
        at android.view.ViewRootImpl.doConsumeBatchedInput(ViewRootImpl.java:6716)
        at android.view.ViewRootImpl$ConsumeBatchedInputRunnable.run(ViewRootImpl.java:6768)
        at android.view.Choreographer$CallbackRecord.run(Choreographer.java:911)
        at android.view.Choreographer.doCallbacks(Choreographer.java:723)
        at android.view.Choreographer.doFrame(Choreographer.java:652)
        at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:897)
        at android.os.Handler.handleCallback(Handler.java:789)
        at android.os.Handler.dispatchMessage(Handler.java:98)
        at android.os.Looper.loop(Looper.java:164)
        at android.app.ActivityThread.main(ActivityThread.java:6541)
        at java.lang.reflect.Method.invoke(Native Method)
        at com.android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.java:240)
        at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:767)
dbrant commented 6 years ago

Yes, I have seen this stack trace as well, but the original one also happens (with the same repro steps), perhaps less frequently.

michael-rapp commented 6 years ago

Okay, so the stacktraces are probably related to each other. I will investigate further and will see how to possibly fix this.

dbrant commented 6 years ago

One other update: I can reproduce the crash even if I disable the Undo snackbar (never show it at all), and simply keep clicking to close tabs at the bottom right, then after a few clicks scroll up. https://youtu.be/ELYBzx7cJ1g

dbrant commented 6 years ago

OK! we have a much better minimal reproducible case (thanks @cooltey):

https://youtu.be/0SimZWcE1O0

michael-rapp commented 6 years ago

I just published a new release 0.3.6. that provides kind of a workaround. Clicking the close buttons of tabs that are "stacked" at the bottom (or top) is now prevented (they shouldn't be clickable anyway).

This should prevent crashes as shown in the first two of your videos. However, this is not a proper fix of the underlying problem and it is still possible to cause a crash as shown in the third video (I was able to do so).

michael-rapp commented 6 years ago

I am now able to very reliably reproduce the issue and I know its cause. I just need to find a way to properly solve it...

michael-rapp commented 6 years ago

...and it is solved! At least I wasn't able to cause any more crashes when testing. The fix is included in release 0.3.7. It would be great if anyone could confirm that the issue is gone now.

cooltey commented 6 years ago

Thanks, @michael-rapp! I did a quick test and it looks great now! Will let @dbrant to confirm it as well.

dbrant commented 6 years ago

That is looking much better! Thanks for the quick turnaround. I can no longer reproduce it myself, so I'm going to deploy to our Beta audience and see if any further reports come in.

michael-rapp commented 6 years ago

Great! Thank you for the collaboration, @cooltey and @dbrant! I will close this issue for now.