IllegalStateException 表示调用 'notifyDataSetChanged() when its content changes'

IllegalStateException says to call 'notifyDataSetChanged() when its content changes'

关于这个 topic/error 我已经看到很多其他问题,但是 none 似乎符合我的情况。以下 LogCat 与我的导航抽屉列表 adapter.

相关

LogCat下方的简短代码)

java.lang.IllegalStateException: The content of the adapter has changed but ListView did not receive a notification. Make sure the content of your adapter is not modified from a background thread, but only from the UI thread. Make sure your adapter calls notifyDataSetChanged() when its content changes. [in ListView(2131558613, class android.widget.ListView) with Adapter(class com.---.---.DrawerAdapter)]
    at android.widget.ListView.layoutChildren(ListView.java:1572)
    at android.widget.AbsListView.onTouchUp(AbsListView.java:3959)
    at android.widget.AbsListView.onTouchEvent(AbsListView.java:3758)
    at android.view.View.dispatchTouchEvent(View.java:9349)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2553)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2240)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2559)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2260)
    at com.android.internal.policy.PhoneWindow$DecorView.superDispatchTouchEvent(PhoneWindow.java:2453)
    at com.android.internal.policy.PhoneWindow.superDispatchTouchEvent(PhoneWindow.java:1755)
    at android.app.Activity.dispatchTouchEvent(Activity.java:2776)
    at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:67)
    at android.support.v7.view.WindowCallbackWrapper.dispatchTouchEvent(WindowCallbackWrapper.java:67)
    at com.android.internal.policy.PhoneWindow$DecorView.dispatchTouchEvent(PhoneWindow.java:2402)
    at android.view.View.dispatchPointerEvent(View.java:9590)
    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:4436)
    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4292)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3816)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3875)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3841)
    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:3971)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3849)
    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4028)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3821)
    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:3875)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:3841)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:3849)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:3821)
    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:6150)
    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6118)
    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6072)
    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:6253)
    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:216)
    at android.os.MessageQueue.nativePollOnce(Native Method)
    at android.os.MessageQueue.next(MessageQueue.java:323)
    at android.os.Looper.loop(Looper.java:144)
    at android.app.ActivityThread.main(ActivityThread.java:5845)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)

在我的 onCreate() 在我的 MainActivity 我只是这样做:

        mDrawerList = (ListView) findViewById(R.id.drawer_list);
        mDrawerLayout = (DrawerLayout) findViewById(R.id.drawer_layout);
        String[] calcTypeList = new String[]{"item1", "item2", "etc".}; // 13 items total
        // override with new list if user upgraded? (1 less item)
        if (myApp.didUpgrade) {
              calcTypeList = new String[]{"item1", "item2", "etc".}; // 12 items total
        }

        dAdapter = new DrawerAdapter(MainFragmentActivity.this, lcTypeList);
        mDrawerList.setAdapter(dAdapter);

我绝不会向此列表中添加任何内容。唯一一次改变是如果用户 "upgrades" 应用程序,然后我删除了一个项目。

这是执行此操作的代码:

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
        = new IabHelper.OnIabPurchaseFinishedListener() {
    public void onIabPurchaseFinished(IabResult result,
                                      Purchase purchase) {
        if (result.isFailure()) {
           Log.i("Purchase Result", "There was a problem with the purchase: " + result.getMessage());
            return;
        } else if (purchase.getSku().equals(SKU_UPGRADE)) {
            Toast.makeText(MainFragmentActivity.this, "Upgrade Successful!", Toast.LENGTH_SHORT).show();
            MyApp.didUpgrade = true;
            dAdapter.notifyDataSetChanged();
        }
    }
};

认为 我一直在读到您不应该在后台线程中添加项目或执行 notifyDataSetChanged()。不确定上面是否是后台线程?即便如此,我不相信这是在错误发生时被触发的。 (但我不会完全打折)。

如果用户升级,那么当应用程序启动时——因为升级菜单项是最后一项,我不再需要显示它,所以我简单地这样做:

DrawerAdapter中:

@Override
public int getCount() {
   if (MyApp.didUpgrade) {
            return 12;
   } else {
            return 13;
   }
}

我想不出任何其他代码。

感谢所有反馈!

编辑:

根据建议更新了代码。 notifyDataSetChanged() 的一个实例现在被移动到 UI 线程。但是这段代码只有在用户直接点击内购升级后才会被调用。我不相信它会被再次调用。出现此错误的用户每次进入应用程序并单击菜单项时都会报告。

IabHelper.OnIabPurchaseFinishedListener mPurchaseFinishedListener
            = new IabHelper.OnIabPurchaseFinishedListener() {
        public void onIabPurchaseFinished(IabResult result,
                                          Purchase purchase) {
            if (result.isFailure()) {
               Log.i("Purchase Result", "There was a problem with the purchase: " + result.getMessage());
                return;
            } else if (purchase.getSku().equals(SKU_UPGRADE)) {
                Toast.makeText(MainFragmentActivity.this, "Upgrade Successful!", Toast.LENGTH_SHORT).show();
                MyApp.didUpgrade = true;
                setDrawerAdapter();
            }
        }
    };

private void setDrawerAdapter() {

        new Thread() {
            public void run() {
                try {
                    runOnUiThread(new Runnable() {

                        @Override
                        public void run() {
                            dAdapter.notifyDataSetChanged();
                        }
                    });
                    Thread.sleep(300);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

更新 #2

用户在最近更新后报告了这个错误,我也报告过。该应用程序已 9 个月未更新。我真正做的唯一改变是将 targetSdkVersion 从 23 -> 25 更改。同时升级依赖项。我不记得旧版本,新闻是

    compile "com.android.support:appcompat-v7:25.0.1"
    compile 'com.android.support:cardview-v7:25.0.1'
    compile 'com.android.support:design:25.0.1'

我的 Nav 抽屉或适配器中的代码在他的更新中没有更改。

我搜索了一下这个问题:

ListView 缓存元素计数,每当它必须对其子项进行布局时,它会再次检查此计数与绑定适配器中的当前项目数。如果计数已更改且未通知 ListView,则会引发错误:

else if (mItemCount != mAdapter.getCount()) {
    throw new IllegalStateException("The content of the adapter has changed but "
    + "ListView did not receive a notification. Make sure the content of "
    + "your adapter is not modified from a background thread, but only from "
    + "the UI thread. Make sure your adapter calls notifyDataSetChanged() "
    + "when its content changes. [in ListView(" + getId() + ", " + getClass()
    + ") with Adapter(" + mAdapter.getClass() + ")]");
}

我不确定是否在另一个线程上调用了 onIabPurchaseFinished()。但如果你真的不改变其他地方的任何东西,我认为这就是问题所在。 比方说,它在另一个线程上。因此,当您调用 MyApp.didUpgrade = true; 时,适配器内容会被另一个线程更改,因为您在适配器内部调用了 if (MyApp.didUpgrade) {。然后 listview 调用 layoutChildren() 并具有旧的 getcount() 数。我认为这就是您收到此错误的原因。

尝试调用 runOnUiThread()

中的两条线路
MyApp.didUpgrade = true;
dAdapter.notifyDataSetChanged();

我建议您检查一下您写信给 MyApp.didUpgrade 的任何其他地方。
也许在 mHelper.queryInventoryAsync(..) 的回调中?

我怀疑如下:
1. 您致电 mHelper.queryInventoryAsync 查询用户库存。调用是异步的,因此线程将等待答案。
2. activity 及其抽屉内容膨胀,列表中填充了 13 个项目。
3. queryInventoryAsync 现在 returns 并触发你的回调,你设置 MyApp.didUpgrade = true 但忘记调用 notifyDataSetChanged().
4. 由于任何原因重绘抽屉内容 - 抛出异常,因为 count() 现在 returns 12 并且 android 认为列表已损坏。

您是否只收到已购买升级的用户的错误报告?
另请注意,一旦创建活动,抽屉中的视图就会膨胀,即使抽屉未打开也是如此。