Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【BUG反馈】 #218

Closed
super-cc opened this issue Jul 13, 2022 · 55 comments
Closed

【BUG反馈】 #218

super-cc opened this issue Jul 13, 2022 · 55 comments
Labels
bug Something isn't working

Comments

@super-cc
Copy link

super-cc commented Jul 13, 2022

BUG 反馈 PopMenu 在线程中修改数据导致闪退

问题描述:

在使用 PopMenu 进行展示时,我可能会随时修改传入的 menuList,所以在 PopMenu 进行初始化的时候,应该是将 menuList 进行一次重新的add添加,否则当我在外部修改 menuList 的时候,就会发生闪退,闪退的日志如下:

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(2131231193, class com.kongzue.dialogx.util.views.PopMenuListView) with Adapter(class com.kongzue.dialogx.util.PopMenuArrayAdapter)]

希望可以尽快修复一下,感谢。

@super-cc super-cc added the bug Something isn't working label Jul 13, 2022
@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

没理解你的意思,根据错误日志是没有在主线程执行。

@super-cc
Copy link
Author

我重新整理一下语言,log 报的是你的库中在使用ListView的时候的问题,可以这样复现,我将 menuList 传入到 PopMenu 中,然后我在外部将 menuList 进行了修改,在这时,就会发生闪退,因为对于 ListView 来讲,数据发生了变化,但又没有进行 notifyDataSetChanged,这时数据和 UI 就对不上了,然后发生的闪退,所以库中 PopMenu 应将我传入的 menuList 进行addAll 到库中自己的 List 当中,这样就可以避免这样的闪退。

@super-cc
Copy link
Author

不知道这次我有没有描述清楚。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

是使用setMenuList(...)进行的设置么?正在排查

@super-cc
Copy link
Author

不是,是使用 PopMenu.show(view, items) ,随后我对 items 进行了修改。对于我的代码来讲,items 是共用的,而且是会变化的。

@super-cc
Copy link
Author

不过 setMenuList 也应该把传入进来的 menuList 进行 addAll 到 PopMenu 中的 menuList。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

是需要在 setMenuList(...) 时更新 menu 列表么?

@super-cc
Copy link
Author

不是,是我在改了要传入的 menuList 的时候,会闪退。

@super-cc
Copy link
Author

因为随后我对传入的 menuList 进行了修改。对于我的代码来讲,menuList 是共用的,而且是会变化的。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

很抱歉我这边未能在Demo中复现,目前版本Demo的表现是setMenuList(...)执行后不刷新界面上的列表,这与预期表现不符,此问题会在下一版本修复,但你所提到的menuList 进行了修改,需要重新执行setMenuList(...)才能刷新PopMenu,PopMenu并不包含DataWatcher功能,需要手动执行方法刷新界面。

@super-cc
Copy link
Author

我再换一种描述方式,对于 ListView 来讲,传入 ListView 的 List 数据是可以修改的,不过如果修改了数据,必须要进行 notifyDataSetChanged,不然就会出现数据和 UI 对不上的情况,就会导致闪退。 PopMenu 使用了 ListView,对于现在的 PopMenu 来讲传入的 List 是不可以修改,不然就会造成我刚才讲的闪退,但是我现在的需求是外面的 List 是可变的,我需要修改他,在下次 show PopMenu 的时候,列表的数据是需要不一样的,所以内部不应该直接使用我传入的 List ,也就是不要用赋值的操作,而应该 new 一个 List 然后使用 addAll 的方式。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

很抱歉未能复现此错误,上述代码在最新版本的Demo中执行正常,没有发生崩溃

@super-cc
Copy link
Author

复现很容易,我给你写段伪代码,刚才写的代码不够全面。

List<CharSequence> items = new ArrayList();
items.add("选项1");
items.add("选项2");
items.add("选项3");
PopMenu.show(view, items);

//会造成闪退的代码
new Thread(new Runable{
    for(int i = 0; i < 10000; i++) {
        items.clear();
        items.add("选项4");
    }
}).start()

@super-cc
Copy link
Author

原因我刚才描述错了,重新梳理一下,就是当 ListView 在使用传入的 List 的时候,同时数据也在发生变化,这样就会造成闪退,因为 ListView 拿数据的时候会有点晕。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

很抱歉依然未能复现你所描述的问题。
在Google Pixel5(Android 13 beta2)上调试通过,运行结果如图:
image

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

发现问题,在 thread 中加入 sleep 后发生 crash

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

稍等正在排查

@super-cc
Copy link
Author

我这边不需要 sleep,你加上循环久一些,可能是时间不够久。这个时机就是 ListView 正在使用 List 数据的时候,刚好 List 也在发生变化,产生的闪退,所以只需要不直接使用我传入的 List 数据,而是在 PopMenu 里复制一份自己用的,就好了,不要使用同一个 List 对象。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

这个应该是ListView本身的问题。目前我的解决方案是在 ListView 中重写:

@Override
protected void layoutChildren() {
    try{
        super.layoutChildren();
    }catch (IllegalStateException e){
        ((BaseAdapter)getAdapter()).notifyDataSetChanged();
        super.layoutChildren();
    }
}

其表现为,当数据发生变化后只要触发此问题,会强制刷新列表后继续执行。

@super-cc
Copy link
Author

我认为 try catch 不是好的解决方式,而是去防止数据随意发生变化,比如我一直有在提到的解决方案。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

但也存在在 PopMenu 运行中需要对菜单进行修改的需求,此时若对于 menuList 直接进行 newArray 可能造成 adapter 无法关联并刷新菜单的问题,我正在思考合理的解决方案。

@super-cc
Copy link
Author

不会的,只要在修改menuList 的时候,同时进行 notifyDataSetChanged 就不会出现这种情况。刚才的闪退是因为在子线程进行了数据更新,如果是在主线程也不会闪退。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

做不到,原因是 DialogX 必须等待主线程执行

@super-cc
Copy link
Author

大佬,你听我的试一下,保证不会闪退,我打包票。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

我说了,得考虑存在在 PopMenu 运行中需要对菜单进行修改的需求,此时若对于 menuList 直接进行 newArray 可能造成 adapter 无法关联并刷新菜单的问题,我正在思考合理的解决方案。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

这种情况下只按照你的想法去处理会导致菜单无法刷新

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

预期的合理方案为:
1.setMenuList(...) 必 newArray 来承载需要设置的数据内容;
2.当用户在 PopMenu 已经处于运行状态中使用 setMenuList(...) 修改数据的情况,当 DialogImpl#refreshUI() 检测到当前 listView 的 adapter 中的 menuList 内存指针和当前 PopMenu 的 menuList 不一致时,强行重新创建 adapter

@super-cc
Copy link
Author

如果你还会担心有人在使用的时候在子线程进行 setMenuList 的话,你可以在 setMenuList 上加上注解 @mainthread ,这样也能避免问题。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

DialogX 必须尽可能的自己考虑线程,而不是要求用户去处理线程问题。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

按照上述预期的合理方案,既能满足你的想法,也能兼顾需要更新菜单内容的用户。

@super-cc
Copy link
Author

那你可以自己去 post 到主线程呀,而且你本身如果传入的是 CharSequence[] 类型,你就是会去 new 一个呀,所以有什么区别呢?

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

对啊,请仔细阅读我刚刚发的预期的合理方案

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

我没否认你的修改建议,但我必须兼顾需要更新菜单内容的用户,这两者并不冲突

@super-cc
Copy link
Author

大佬,你再认真看一下出现的原因,不是因为我使用了 setMenuList,而是我在外部对 menuList 进行了修改,因为我外面用的 list 和 PopMenu 里的 menuList 是同一个数据,对象是传值,List是传址,你所讲的方案,并没有解决我在外问修改 list 的问题呀,我修改的只是 list ,但是我并不想要现在已经展示给用户的 PopMenu 上的 item 发生改变。只是在下次使用的时候改变。

@super-cc
Copy link
Author

而我所说的解决方案,很简单,并不需要影响到老用户。只需要 public PopMenu setMenuList(List menuList) 里和 public PopMenu setMenuList(String[] menuList) 一样,都是 new ArrayList<>() ,然后再 addAll() ,就解决了。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

是这样的啊

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

image
我没否认你的建议

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

只是额外的:
image

@super-cc
Copy link
Author

了解了,要不这样,你更新一版,我看一下代码得了。可能在这交流不是很好描述。

@super-cc
Copy link
Author

我懂了,你只是将 notifyDataSetChanged 的时机改到了其它地方,了解了。

@super-cc
Copy link
Author

更新好新的一版请发我哦,谢谢了,我这边今天需要发版,感谢。

@super-cc
Copy link
Author

大佬之前是写 c/c++ 的吗?经常用指针这样的词,其实不是指针发生变化,是数据发生变化 。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

请更新至 0.0.45.beta24 版本,更新日志在:https://github.com/kongzue/DialogX/releases/tag/0.0.45.beta24

@super-cc
Copy link
Author

好的,谢谢了,已更新。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

Referen

是指针没有发生变化, adapter里还是指向了你 new 的array,而array发生了变化,但listview依然按照指针去找之前的 list 拿数据就崩掉了

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

新版本已经处理了,全部会在设置 list 的时候转移到一个新的 list 拷贝,这样不会出现问题了

@super-cc
Copy link
Author

大佬请确认一下aar包没问题,我这边看aar包中的代码没有更新,是不是upload失败了。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

提交日志在这里:8103602

@super-cc
Copy link
Author

我这里显示的还不是对,要不大佬在demo里看一下。
image

@super-cc
Copy link
Author

一共还有3处使用的 this.menuList = menuList;

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

好的,稍等

@super-cc
Copy link
Author

麻烦了。

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

我这边刚刚调试没有用 view参数

@kongzue
Copy link
Owner

kongzue commented Jul 13, 2022

请更换 0.0.45.beta25 版本,已修复全部问题。

@super-cc
Copy link
Author

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

No branches or pull requests

2 participants