Search code examples
androidandroid-support-libraryswiperefreshlayout

IllegalArgumentException: pointerIndex out of range from SwipeRefreshLayout


I've been getting some of these IllegalArgumentException: pointerIndex out of range crashes on crashlytics and I don't understand what's happening. It's not limited to one android build or device, it happens on 5.0.1, 4.4.4, 4.4.2, 4.0.4, 2.3.6 all on various devices. Below is the full log output for more context.

java.lang.RuntimeException: Unable to destroy activity {com.mypackage.myapp/com.mypackage.myapp.MyListActivity}: java.lang.IllegalArgumentException: pointerIndex out of range
       at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3671)
       at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3689)
       at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3889)
       at android.app.ActivityThread.access$900(ActivityThread.java:144)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1284)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:135)
       at android.app.ActivityThread.main(ActivityThread.java:5221)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:898)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)
Caused by: java.lang.IllegalArgumentException: pointerIndex out of range
       at android.view.MotionEvent.nativeGetAxisValue(MotionEvent.java)
       at android.view.MotionEvent.getY(MotionEvent.java:1998)
       at android.support.v4.view.MotionEventCompatEclair.getY(MotionEventCompatEclair.java:35)
       at android.support.v4.view.MotionEventCompat$EclairMotionEventVersionImpl.getY(MotionEventCompat.java:95)
       at android.support.v4.view.MotionEventCompat.getY(MotionEventCompat.java:228)
       at android.support.v4.widget.SwipeRefreshLayout.onTouchEvent(SwipeRefreshLayout.java:772)
       at android.view.View.dispatchTouchEvent(View.java:8388)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2398)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2158)
       at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2400)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2172)
       at android.view.ViewGroup.cancelTouchTarget(ViewGroup.java:2340)
       at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4156)
       at android.view.ViewGroup.removeViewInternal(ViewGroup.java:4136)
       at android.view.ViewGroup.removeView(ViewGroup.java:4068)
       at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1045)
       at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1126)
       at android.support.v4.app.FragmentManagerImpl.moveToState(FragmentManager.java:1108)
       at android.support.v4.app.FragmentManagerImpl.dispatchDestroy(FragmentManager.java:1954)
       at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:313)
       at android.support.v7.app.ActionBarActivity.onDestroy(ActionBarActivity.java:169)
       at com.mypackage.myapp.BaseActivity.onDestroy(BaseActivity.java:105)
       at android.app.Activity.performDestroy(Activity.java:6112)
       at android.app.Instrumentation.callActivityOnDestroy(Instrumentation.java:1140)
       at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3658)
       at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3689)
       at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:3889)
       at android.app.ActivityThread.access$900(ActivityThread.java:144)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1284)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:135)
       at android.app.ActivityThread.main(ActivityThread.java:5221)
       at java.lang.reflect.Method.invoke(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:372)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:898)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:693)

Here is another related crash report coming from android.view.MotionEvent.getY().

java.lang.RuntimeException: Unable to destroy activity {com.mypackage.myapp/com.mypackage.myapp.MyListActivity}: java.lang.ArrayIndexOutOfBoundsException
       at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:2683)
       at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:2701)
       at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:2817)
       at android.app.ActivityThread.access$1600(ActivityThread.java:117)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:946)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:130)
       at android.app.ActivityThread.main(ActivityThread.java:3733)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:507)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:931)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:689)
       at dalvik.system.NativeStart.main(NativeStart.java)
Caused by: java.lang.ArrayIndexOutOfBoundsException
       at android.view.MotionEvent.getY(MotionEvent.java:903)
       at android.support.v4.view.MotionEventCompatEclair.d(MotionEventCompatEclair.java:35)
       at android.support.v4.view.MotionEventCompat$EclairMotionEventVersionImpl.d(MotionEventCompat.java:95)
       at android.support.v4.view.MotionEventCompat.d(MotionEventCompat.java:228)
       at android.support.v4.widget.SwipeRefreshLayout.onTouchEvent(SwipeRefreshLayout.java:772)
       at android.view.View.dispatchTouchEvent(View.java:3971)
       at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:903)
       at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:1154)
       at android.view.ViewGroup.removeViewInternal(ViewGroup.java:2201)
       at android.view.ViewGroup.removeViewInternal(ViewGroup.java:2187)
       at android.view.ViewGroup.removeView(ViewGroup.java:2135)
       at android.support.v4.app.FragmentManagerImpl.a(FragmentManager.java:1045)
       at android.support.v4.app.FragmentManagerImpl.a(FragmentManager.java:1126)
       at android.support.v4.app.FragmentManagerImpl.a(FragmentManager.java:1108)
       at android.support.v4.app.FragmentManagerImpl.t(FragmentManager.java:1954)
       at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:313)
       at android.support.v7.app.ActionBarActivity.onDestroy(ActionBarActivity.java:169)
       at com.mypackage.myapp.BaseActivity.onDestroy(BaseActivity.java:105)
       at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:2670)
       at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:2701)
       at android.app.ActivityThread.handleRelaunchActivity(ActivityThread.java:2817)
       at android.app.ActivityThread.access$1600(ActivityThread.java:117)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:946)
       at android.os.Handler.dispatchMessage(Handler.java:99)
       at android.os.Looper.loop(Looper.java:130)
       at android.app.ActivityThread.main(ActivityThread.java:3733)
       at java.lang.reflect.Method.invokeNative(Method.java)
       at java.lang.reflect.Method.invoke(Method.java:507)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:931)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:689)
       at dalvik.system.NativeStart.main(NativeStart.java)

So my question is what is causing this error and what would be the acceptable method for mitigating this issue?

EDIT: Here is the link to MotionEvent.java:1998 which is referenced in the crash above.

EDIT: Here is my onDestroy looks like:

@Override
public void onDestroy() {

    AppMsg.cancelAll();
    SuperCardToast.cancelAllSuperCardToasts();

    super.onDestroy();
}

Specifically BaseActivity.java:105 is where I call super.onDestroy();.


Solution

  • I assume that exception is thrown while there is touch event still occurring (streamed down to the native touch while the Activity is about to onDestroy(). It is ok to mitigate this by catching the exception for the sake of avoiding crash. Activity is about to be destroyed if it has enter this state.

    I'm not sure (havent tested), but you could try to prevent any events to be passed to implementation at all, if catching exception does not fit you.

    public class ComeUpWithBetterNameSwipeRefreshLayout extends SwipeRefreshLayout {
    
        private boolean mAcceptEvents;
    
        public ComeUpWithBetterNameSwipeRefreshLayout(Context context) {
            super(context);
        }
    
        public void setAcceptEvents(boolean mAcceptEvents) {
            this.mAcceptEvents = mAcceptEvents;
        }
        @Override
        public boolean onInterceptTouchEvent(MotionEvent ev) {
            return mAcceptEvents? super.onInterceptTouchEvent(ev) : true;
        }
    
        public ComeUpWithBetterNameSwipeRefreshLayout(Context context, AttributeSet attrs) {
            super(context, attrs);
        }
        @Override
        protected void onAttachedToWindow() {
            super.onAttachedToWindow();
            mAcceptEvents = true;
        }
    
        @Override
        protected void onDetachedFromWindow() {
            super.onDetachedFromWindow();
            mAcceptEvents = false;
        }
    }
    

    or

    @Override
    public void onDestroy() {
        mSwipeRefreshLayout.setAcceptEvents(false);
        AppMsg.cancelAll();
        SuperCardToast.cancelAllSuperCardToasts();
    
        super.onDestroy();
    }
    

    Take 2:

    SwipeRefreshLayout tries to getY() from invalid pointer index, calling findPointerIndex(ev, activePointer) returns -1 if it fails to find. Preventing dispatching touch event with invalid pointer less than 0 and pointer index greater or equal than pointer count for that event will probably prevent validation of pointer inside native MotionEvent implementation from throwing IAE.

     @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if(ev.getAction() == MotionEvent.ACTION_CANCEL) {
            int pointerCount = MotionEventCompat.getPointerCount(ev);
            int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            index = MotionEventCompat.findPointerIndex(ev,mActivePointerId);
            if (index > -1 && index < pointerCount) {
                super.onInterceptTouchEvent(ev);
            } else {
                return true;
            }
        }else if(ev.getAction() == MotionEventCompat.ACTION_POINTER_DOWN && super.onInterceptTouchEvent(ev)) {
            final int index = MotionEventCompat.getActionIndex(ev);
            mActivePointerId = MotionEventCompat.getPointerId(ev, index);
            return false;
        }else if(ev.getAction() == MotionEventCompat.ACTION_POINTER_UP && super.onInterceptTouchEvent(ev)){
            onSecondaryPointerUp(ev);
            return false;
        }else if(ev.getAction() == MotionEvent.ACTION_DOWN && super.onInterceptTouchEvent(ev)){
            mActivePointerId = MotionEventCompat.getPointerId(ev, 0);
            return false;
        }
        return super.onInterceptTouchEvent(ev);
    }
    
    private void onSecondaryPointerUp(MotionEvent ev) {
        final int pointerIndex = MotionEventCompat.getActionIndex(ev);
        final int pointerId = MotionEventCompat.getPointerId(ev, pointerIndex);
        if (pointerId == mActivePointerId) {
            final int newPointerIndex = pointerIndex == 0 ? 1 : 0;
            mActivePointerId = MotionEventCompat.getPointerId(ev, newPointerIndex);
        }
    }