在Android设备上,UI更新仅在用户交互后发生

时间:2017-12-09 11:53:23

标签: c# android xamarin async-await mvvmcross

我在那里找到了有趣的(至少对我来说)错误。

我正在创建一个(原型)应用程序,它执行一些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 Info 20385 mvx android.view.ViewRootImpl $ CalledFromWrongThreadException:   只有创建视图层次结构的原始线程才能触及它   观点。在   android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6898)at at   android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)at   android.view.View.requestLayout(View.java:19785)at   android.view.View.requestLayout(View.java:19785)at   android.view.View.requestLayout(View.java:19785)at   android.view.View.requestLayout(View.java:19785)at   android.view.View.requestLayout(View.java:19785)at   android.widget.AbsListView.requestLayout(AbsListView.java:1997)at   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}");
        }

输出:

enter image description here

此外,试过这种方法但没有成功:

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更改是否更为创建另一个线程或什么?

1 个答案:

答案 0 :(得分:0)

不要窃取@jazzmasterkc的信用,但是认为这需要作为答案发布。使用MvxObservableCollection而非ObservableCollection进行列表值绑定可解决此问题,因为MvxObservableCollection会在主线程上调用所有PropertyChanged和CollectionChanged事件。