【问题标题】:On Android device UI update happens only after user interaction在 Android 设备上,UI 更新仅在用户交互后发生
【发布时间】:2018-05-23 12:31:42
【问题描述】:

我在那里发现的有趣(至少对我而言)错误。

我正在制作一个(原型)应用程序,它会执行一些 Web 请求并返回简单的数据。

ObservableCollection<DownloadbleEntity>动态更新(因为DownloadbleEntity包含我们通过其他请求获得的图像,以输出带有图像的列表元素)。

这是布局部分:

   <MvvmCross.Binding.Droid.Views.MvxListView
        android:id="@+id/searchlist"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent"
        android:layout_alignParentLeft="true"
        android:layout_alignParentStart="true"
        local:MvxBind="ItemsSource FoundItems; ItemClick OnItemClickCommand; OnScrollToBottom GetNewAsyncCommand"
        local:MvxItemTemplate="@layout/listitem" />

这是 ViewModel 代码,用于显示更新的进展情况:

        private ObservableCollection<DownloadableEntity> _foundItems;

        public ObservableCollection<DownloadableEntity> FoundItems
        {
            get { return _foundItems; }

            set
            {
                if (_currentPage > 0)
                {
                    _foundItems = new ObservableCollection<DownloadableEntity>(_foundItems.Concat(value));
                }
                else
                {
                    _foundItems = value;
                }

                RaisePropertyChanged(() => FoundItems);
            }
        }

 private async Task PrepareDataForOutput(SearchResult searchResult)
        {
            _currentListLoaded = false;
            IMvxMainThreadDispatcher dispatcher = Mvx.Resolve<IMvxMainThreadDispatcher>();
            List<Task<DownloadableEntity>> data = searchResult.Tracks.Items.ToList().Select(async (x) => await PrepareDataOutputAsync(x).ConfigureAwait(false)).ToList();
            Android.Util.Log.Verbose("ACP", "PrepareDataForOutput");

            try
            {
                var result = new ObservableCollection<DownloadableEntity>();
                while (data.Count > 0)
                {
                    var entityToAdd = await Task.Run(async () =>
                    {
                        Task<DownloadableEntity> taskComplete = await Task.WhenAny(data).ConfigureAwait(false);
                        data.Remove(taskComplete);
                        DownloadableEntity taskCompleteData = await taskComplete.ConfigureAwait(false);

                        await Task.Delay(500);

                        return taskCompleteData;

                    }).ConfigureAwait(false);

                    result.Add(entityToAdd);

                    // as it recommended by mvvmcross providers
                    dispatcher.RequestMainThreadAction(async () =>
                        await Task.Run(() =>
                        {
                            Android.Util.Log.Verbose("ACP", $"RequestMainThreadAction update {result.Last().Title}");
                           _toastService.ShowToastMessage($"Got {result.Last().Title}");
                            FoundItems = result;
                        }).ConfigureAwait(false)
                    );

                }

                await Task.WhenAll(data).ContinueWith((x) =>
                 {
                     Android.Util.Log.Verbose("ACP", "Output is Done");
                     _currentListLoaded = true;
                 });
            }
            catch (Exception e)
            {
                Android.Util.Log.Verbose("ACP", e.Message);
            }
        }

           private async Task<DownloadableEntity> PrepareDataOutputAsync(PurpleItem x)
        {
            return new DownloadableEntity
            {
                Title = x.Title,
                ArtistName = x.Artists.Select(y => y.Name).Aggregate((cur, next) => cur + ", " + next),
                Image = await Task.Run(() => _remoteMusicDataService.DownloadCoverByUri(x.Albums.FirstOrDefault().CoverUri)).ConfigureAwait(false),
                AlbumName = x.Albums.First().Title ?? "",
                AlbumId = x.Albums.First().Id,
                TrackId = x.Id
            };
        }

嗯,事情是 - 在设备上,数据输出开始后,它会输出一个列表元素,同时输出:

时间设备名称类型 PID 标签消息 12-09 13:45:22.012 Wileyfox 斯威夫特 2 X 信息 20385 mvx android.view.ViewRootImpl$CalledFromWrongThreadException: 只有创建视图层次结构的原始线程才能触及其 意见。在 android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6898) 在 android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.view.View.requestLayout(View.java:19785) 在 android.widget.AbsListView.requestLayout(AbsListView.java:1997) 在 android.widget.AdapterView$AdapterDataSetObserver.onChanged(AdapterView.java:840) 在 android.widget.AbsListView$AdapterDataSetObserver.onChanged(AbsListView.java:6380) 在 android.database.DataSetObservable.notifyChanged(DataSetObservable.java:37) 在 android.widget.BaseAdapter.notifyDataSetChanged(BaseAdapter.java:50)

但它会继续更新我的收藏,所以Android.Util.Log.Verbose("ACP", $"RequestMainThreadAction update {result.Last().Title}"); 切换,我可以在设备日志窗口中看到它。

但只有在我执行某些操作后,它才会继续在我的设备屏幕上呈现 - 触摸屏或触摸我的 SearchView 输入或旋转它。

这有点奇怪,我想知道是什么原因造成的。 是因为我在收藏更新方面做错了吗?

我录制了正在发生的事情的视频,所以here it is(对不起我的英语和口音:()

UPD(关于最后一条评论):

如果更新发生在dispatcher.RequestMainThreadAction 内部,是否真的从后台线程更新集合?

UPD2

我添加了线程编号检测,所以,看起来编号总是相同的

代码:

 // as it recommended
 dispatcher.RequestMainThreadAction(async () =>
     await Task.Run(() =>
     {
         var poolId = TaskScheduler.Current.Id;
         Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}");
         _toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}");
         FoundItems = result;
     }).ConfigureAwait(false)
 );

View我也加了:

protected override void OnCreate(Bundle bundle)
        {
            base.OnCreate(bundle);
            _searchView = FindViewById<SearchView>(Resource.Id.search10);

            ViewModel.OnSearchStartEvent += ViewModel_OnSearchStartEvent;
            var poolId = TaskScheduler.Current.Id;
            Android.Util.Log.Verbose("ACP THREAD INFO", $"VIEW CREATED FROM THREAD NUMBER {poolId}");
        }

输出:

另外,尝试了这种方法但没有成功:

Mvx.Resolve<IMvxAndroidCurrentTopActivity>().Activity.RunOnUiThread(async () =>
    await Task.Run(() =>
    {
        var poolId = TaskScheduler.Current.Id;
        Android.Util.Log.Verbose("ACP THREAD INFO", $"RequestMainThreadAction update {result.Last().Title} THREAD NUMBER {poolId}");
        _toastService.ShowToastMessage($"Got {result.Last().Title} by {result.Last().ArtistName}");
        FoundItems = result;
    }).ConfigureAwait(false)
);

UPD3

我找到了一种解决方法(但仍然不是解决方案) - 如果我使用 MvxObservableCollection 而不是仅使用 ObservableCollection 代替 FoundItems - 一切都按预期工作!

如果我们查看这个类 (MvxObservableCollection.cs),我们会看到它具有触发更新的那些函数,看起来它在那里做同样的事情:

        protected virtual void InvokeOnMainThread(Action action)
        {
            var dispatcher = MvxSingleton<IMvxMainThreadDispatcher>.Instance;
            dispatcher?.RequestMainThreadAction(action);
        }

        protected override void OnPropertyChanged(PropertyChangedEventArgs e)
        {
            InvokeOnMainThread(() => base.OnPropertyChanged(e));
        }

但我不明白为什么在我的情况下它不能正常工作,我的意思是普通的ObservableCollection

ObservableCollection 的 notyfier 更改会创建另一个线程还是什么?

【问题讨论】:

  • 你有线程问题。可能是由于从后台线程更新 Observable 引起的。阅读hereherehere
  • @MatPag 我看到了,但我觉得dispatcher.RequestMainThreadAction 中的调用函数就够了,还是不行?
  • 使用dispatcher就够了。但是,您的 UI 更新在 Task.Run 很多地方。
  • @Cheesebaron wellp 即使我不使用Task.Run 包装器,我也得到了相同的图片:(
  • @Cheesebaron 但我只是想更新问题,所以我在那里发现了一件事

标签: c# android xamarin async-await mvvmcross


【解决方案1】:

不要窃取 @jazzmasterkc 的功劳,但认为这需要作为答案发布。使用MvxObservableCollection 代替 ObservableCollection 进行列表值绑定修复了 MvxObservableCollection 调用主线程上的所有 PropertyChanged 和 CollectionChanged 事件的问题。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-10-22
    • 1970-01-01
    • 2019-08-09
    • 1970-01-01
    • 2011-11-04
    • 1970-01-01
    • 2019-12-01
    • 2018-10-24
    相关资源
    最近更新 更多