简体   繁体   中英

How to deal with ConcurrentModificationException while doing swapCursor()?

I recieve exception while manipulating with navigation forward and backward from FragmentActivity , containing ListView with data loaded from custom Loader (it is loading from local sqlite database), and do not know how to deal with it...

Here is a stacktrace:

Uncaught exception: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.PriceTracerActivity}: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.PriceTracerActivity}: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3106)
    at android.app.ActivityThread.handleDestroyActivity(ActivityThread.java:3171)
    at android.app.ActivityThread.access$2100(ActivityThread.java:132)
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1071)
    at android.os.Handler.dispatchMessage(Handler.java:99)
    at android.os.Looper.loop(Looper.java:150)
    at android.app.ActivityThread.main(ActivityThread.java:4293)
    at java.lang.reflect.Method.invokeNative(Native Method)
    at java.lang.reflect.Method.invoke(Method.java:507)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:849)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:607)
    at dalvik.system.NativeStart.main(Native Method)
Caused by: java.lang.RuntimeException: Unable to destroy activity {com.snyer.bestprice/com.snyer.bestprice.CartFragmentActivity}: java.util.ConcurrentModificationException
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3106)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:2994)
    at android.app.LocalActivityManager.dispatchDestroy(LocalActivityManager.java:625)
    at android.app.ActivityGroup.onDestroy(ActivityGroup.java:85)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3088)
    ... 11 more
Caused by: java.util.ConcurrentModificationException
    at java.util.ArrayList$ArrayListIterator.next(ArrayList.java:576)
    at android.database.DataSetObservable.notifyInvalidated(DataSetObservable.java:42)
    at android.widget.BaseAdapter.notifyDataSetInvalidated(BaseAdapter.java:54)
    at android.support.v4.widget.CursorAdapter.swapCursor(CursorAdapter.java:352)
    at com.snyer.bestprice.CartFragmentActivity.onLoaderReset(CartFragmentActivity.java:412)
    at android.support.v4.app.LoaderManagerImpl$LoaderInfo.destroy(LoaderManager.java:337)
    at android.support.v4.app.LoaderManagerImpl.doDestroy(LoaderManager.java:773)
    at android.support.v4.app.FragmentActivity.onDestroy(FragmentActivity.java:318)
    at android.app.ActivityThread.performDestroyActivity(ActivityThread.java:3088)
    ... 15 more

Exceptions occures at line mCartCursorAdapter.swapCursor(null); of this code-fragment (CartFragmentActivity.java:412):

public void onLoaderReset(Loader<Cursor> loader) {
    switch (loader.getId()) {
    case LOADER_CART_LIST_ID:
        mCartListCursorAdapter.swapCursor(null);
        break;

    case LOADER_CART_CONTENTS_ID:
        mCartCursorAdapter.swapCursor(null);
        break;
    }
}

What can be done here to remove this exception?

The sdk source gives a good hint as a comment:

In Android 4.0.4 it is:

public void notifyInvalidated() {
    synchronized(mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onInvalidated();
        }
    }
}

In earlier versions it was:

 synchronized (mObservers) {
        for (DataSetObserver observer : mObservers) {
            observer.onInvalidated();
        }
    }

The 4.0.4 source has the following comment regarding the same changes in onChange method:

// since onChanged() is implemented by the app, it could do anything, including
// removing itself from {@link mObservers} - and that could cause problems if
// an iterator is used on the ArrayList {@link mObservers}.
// to avoid such problems, just march thru the list in the reverse order.

I am not aware of manipulating the ArrayList of observers in any illegal way though. Since this is solved in later APIs and I can't think of any impact on my App I think I am simply going to ignore the exception with a log entry or similar.

It may be that onLoaderReset() is being called on a background thread by the Loader . Try using runOnUiThread() or similar mechanisms to move your swapCursor() call to the main application thread.

The fastest way it is to copy DataSetObservable from "right" version of the sdk to your app and use it. DataSetObservable should looks like this:

public class DataSetObservable extends Observable<DataSetObserver> {
/**
 * Invokes {@link DataSetObserver#onChanged} on each observer.
 * Called when the contents of the data set have changed.  The recipient
 * will obtain the new contents the next time it queries the data set.
 */
public void notifyChanged() {
    synchronized(mObservers) {
        // since onChanged() is implemented by the app, it could do anything, including
        // removing itself from {@link mObservers} - and that could cause problems if
        // an iterator is used on the ArrayList {@link mObservers}.
        // to avoid such problems, just march thru the list in the reverse order.
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onChanged();
        }
    }
}

/**
 * Invokes {@link DataSetObserver#onInvalidated} on each observer.
 * Called when the data set is no longer valid and cannot be queried again,
 * such as when the data set has been closed.
 */
public void notifyInvalidated() {
    synchronized (mObservers) {
        for (int i = mObservers.size() - 1; i >= 0; i--) {
            mObservers.get(i).onInvalidated();
        }
    }
} 

In your case you should copy BaseAdapter from the source code too. And change in it android DataSetObservable to your app DataSetObservable .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM