Preventing/catching “IllegalArgumentException: par

2020-01-24 11:23发布

问题:

I have a ListView with some focusable components inside (mostly EditTexts). Yeah, I know this isn't exactly recommended, but in general, almost everything is working fine and the focus goes where it has to go (with a few tweaks I had to code). Anyway, my problem is that there's a weird race condition when scrolling the list with your finger and then suddenly using the trackball when the IME keyboard is being displayed. Something must go out of bounds and get recycled at which point the offsetRectBetweenParentAndChild() method must kick in and throw the IllegalArgumentException.

The problem is that this exception is thrown outside of any block in which I can insert a try/catch (as far as I know). So there are two valid solutions to this question, either:

  1. Someone knows why this exception being thrown and how to stop it from happening
  2. Someone knows how to put a try/catch block somewhere that will at least let my application survive. As far as I know the problem is that of focus, so it definitely shouldn't kill my application (which is what it's doing). I tried overriding the ViewGroup's methods but those two offset* methods are marked as final.

Stack trace:

08-17 18:23:09.825: ERROR/AndroidRuntime(1608): FATAL EXCEPTION: main
08-17 18:23:09.825: ERROR/AndroidRuntime(1608): java.lang.IllegalArgumentException: parameter must be a descendant of this view
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewGroup.offsetRectBetweenParentAndChild(ViewGroup.java:2633)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewGroup.offsetDescendantRectToMyCoords(ViewGroup.java:2570)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.scrollToRectOrFocus(ViewRoot.java:1624)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.draw(ViewRoot.java:1357)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.performTraversals(ViewRoot.java:1258)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.view.ViewRoot.handleMessage(ViewRoot.java:1859)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.os.Handler.dispatchMessage(Handler.java:99)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.os.Looper.loop(Looper.java:130)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at android.app.ActivityThread.main(ActivityThread.java:3683)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at java.lang.reflect.Method.invokeNative(Native Method)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at java.lang.reflect.Method.invoke(Method.java:507)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:839)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:597)
08-17 18:23:09.825: ERROR/AndroidRuntime(1608):     at dalvik.system.NativeStart.main(Native Method)

回答1:

I am sorry to tell you, I found my previous answer isn't the most perfect way to solve this problem.

So i try this :
Append a ScrollListener to your Activity, when listView start scrolling, clear current focus.

protected class MyScrollListener implements OnScrollListener {

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem,
                int visibleItemCount, int totalItemCount) {
            // do nothing 
        }

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            if (SCROLL_STATE_TOUCH_SCROLL == scrollState) {
                View currentFocus = getCurrentFocus();
                if (currentFocus != null) {
                    currentFocus.clearFocus();
                }
            }
        }

    }


回答2:

While Bruce's answer does solve the problem, it does it in a very brutal way which harms the UX, as it will clear the focus of every view once we did a scroll.

It deals with the symptom of the problem but it does not solve the actual cause.

how to reproduce the problem:

Your EditText has focus and the keyboard is opened, you then scroll till the point the EditText is off the screen, and it wasn't recycled to a new EditText that is now shown.

Let's first understand why this problem happens:

ListView recycles its views and uses them again as you all know, but sometimes it does not need to use a view that has gone off the screen immediately so it keeps it for future use, and because it doesn't need to be shown anymore it will detach it causing that view.mParent to be null. however the keyboard needs to know how to pass the input to, and it does it by choosing the focused view, or EditText to be precise.

So the problem is that we have an EditText who has the focus, but suddenly does not have a parent, so we get a "parameter must be a descendant of this view” error. makes sense.

By using the scroll listener we are causing more problems.

The Solution:

We need to listen to an event which will tell us when a view has gone to the side heap and is no longer attached, luckily ListView exposes this event.

listView.setRecyclerListener(new AbsListView.RecyclerListener() {
        @Override
        public void onMovedToScrapHeap(View view) {
            if ( view.hasFocus()){
                view.clearFocus(); //we can put it inside the second if as well, but it makes sense to do it to all scraped views
                //Optional: also hide keyboard in that case
                if ( view instanceof EditText) {
                    InputMethodManager imm = (InputMethodManager) view.getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
                    imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
                }
            }
        }
    });


回答3:

try this

 @Override
public View getView(int position, View convertView, ViewGroup parent) {
    //abandon current focus
    View currentFocus = ((Activity)mContext).getCurrentFocus();
    if (currentFocus != null) {
        currentFocus.clearFocus();
    }

    // other code
}

EDIT:

See also: Better Solution



回答4:

For what it's worth (or whoever stumbles on this), I've abandoned the ListView approach for this Activity. Aside from the random crashes, it's almost impossible to get the focus behavior correctly without setting the windowSoftInputMode="adjustPan" which opens a bunch of other cans of worms. Instead, I just went for a "simple" ScrollView and that has been working great.



回答5:

I used bruce's answer with a slight adjustment.

I needed adjustResize in my activity instead of adjustpan but when I tried it the error occurred again.
I replaced ScrollView with <android.support.v4.widget.NestedScrollView and it works fine now. Hope this helps someone!



回答6:

I faced with the same problem and found out this solution - in OnGroupCollapseListener/OnGroupExpandListener and OnScrollListener for ExpandableListView i clear focuse and hide forced keyboard. Also do not forget to set in manifest for your activity windowSoftInputMode="adjustPan":

    expListView.setOnGroupCollapseListener(new OnGroupCollapseListener() {

        @Override
        public void onGroupCollapse(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnGroupExpandListener(new OnGroupExpandListener() {

        @Override
        public void onGroupExpand(int groupPosition) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getWindow().getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }
    });

    expListView.setOnScrollListener(new OnScrollListener() {

        @Override
        public void onScrollStateChanged(AbsListView view, int scrollState) {
            InputMethodManager inputManager = (InputMethodManager) getApplicationContext().getSystemService(Context.INPUT_METHOD_SERVICE);
            if (getCurrentFocus() != null) {
                inputManager.hideSoftInputFromWindow(getCurrentFocus().getWindowToken(), 0);
                getCurrentFocus().clearFocus();
            }
        }

        @Override
        public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {}

    });

I don't know exactly OnGroupExpandListener is needed or no, it could be useless.



回答7:

I have the simplest but not good solution. Just extend the NestedScrollView and override onSizeChanged method, add a try catch block.

public class FixFocusErrorNestedScrollView extends NestedScrollView {

    public FixFocusErrorNestedScrollView(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        try {
            super.onSizeChanged(w, h, oldw, oldh);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

In my case, I have tow layer view, top layer is listView, bottom is NestedScrollView. The error is happend when I switch the layer. The focus be taked by ListeView item (button).

So I can't make button lose focus. Then the best solution is extends NestedScrollView.



回答8:

I faced that problem, too, and the solution by validcat worked for me, but I had to call getWindow().getCurrentFocus().clearFocus().



回答9:

In case of Expandable List View, if your child items have edit text then you need to change the focusability before descendants for Expandable List View

expandableListView.setDescendantFocusability(ViewGroup.FOCUS_BEFORE_DESCENDANTS);


回答10:

I faced the same problem when using EditText in Recyclerview. After a lot of struggle and trying different option i found out the after deleting the row when my keyboard is opened produces this issue. I solved it by force closing my keyboard and changing notifyItemRemoved(position) with notifyDataSetChanged().



回答11:

In my case it was related to windowSoftInputMode="adjustPan", listView and editText on list element (header view).

In order to fix that I call hide soft keyboard method before activity is finished.

public void hideKeyboard(Activity activity) {
    InputMethodManager inputMethodManager = (InputMethodManager) activity.getSystemService(Activity.INPUT_METHOD_SERVICE);
    View focusView = activity.getCurrentFocus();
    if (focusView != null) {
        inputMethodManager.hideSoftInputFromWindow(focusView.getWindowToken(), InputMethodManager.HIDE_NOT_ALWAYS);
    }
}


回答12:

Based on @Bruce answer, can resolve error with recyclerview like this:

@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

        View currentFocus = ((Activity)context).getCurrentFocus();
        if (currentFocus != null) {
            currentFocus.clearFocus();
        }
}


回答13:

If none of the solutions suggested here apply to you...

I've experienced a similar error and noticed it was reported by my users' devices (after a crash) without any clear explanation on what was causing it (same as the log shown on the question) - more specifically the issue only happened on Samsung Galaxy (including S6) devices (but not on Nexus devices or others, which is why my testing initially failed to reveal the issue). So, first, it is worth checking if the issue is device specific or not.

What I later found is that when pressing the back button while a Samsung virtual keyboard was displayed on a text field, the application would crash throwing this error - but not always!

Indeed, the text field causing the crash also happened to be displayed within a scrollview with fillViewPort="true" enabled.

What I found is that removing the fillViewPort option from the scrollview would not conflict with the Samsung keyboard being displayed/hidden. I suspect the issue is partly due to the fact that Samsung keyboards are different virtual keyboards than the stock Nexus keyboards, which is why only a subset of my users were experiencing the issue and it would crash on their devices only.

As a general rule, and if none of the other solutions suggested here apply to you, I would check if the issue is device specific, and also attempt to simplify the view I am working on until I can find the "culprit component" (component and view that, I should add, wasn't reported in the crash logs - so I only stumbled on the specific view causing the issue by chance!).

Sorry I cannot be more specific, but I hope this gives some pointers for further investigation if someone experience a similar but unexplained issue.



回答14:

My answer is related to most of the answers here, but I just wanted to add that in my case, this crash occurred due to removing a row with an edit text that currently had the focus.

So all I did was override the remove method of the adapter, and queried whether the removed row contains the current focus edit and if so, clear the focus.

That solved it for me.



回答15:

I'm using RecyclerView and none of the presented solutions worked. I got the error when removing items.

What did work was overriding the Adapter's 'onItemDismiss(int position)' so that it first does a 'notifyDataSetChanged()' prior to removing the item and then does 'notifyItemRemoved(position)' after removing the item. Like this:

// Adapter code
@Override
public void onItemDismiss(int position) {
    if (position >= 0 && getTheList() != null && getTheList().size() > position) {
        notifyDataSetChanged();  // <--- this fixed it.
        getTheList().remove(position);
        scrollToPosition(position);
        notifyItemRemoved(position);
    }
}

Also do an Override of 'removeAt(int position)' in the TabFragment to invoke the new cleanup code, like this:

// TabFragment code
@Override
public void removeAt(int position) {
    mAdapter.onItemDismiss(position);
    mAdapter.notifyItemRemoved(position); // <--- I put an extra notify here too
}