Xamarin进度条不实时更新

5

大家好,

我对Xamarin和尤其是C#都比较陌生,请多包涵……!

我有一个使用DependencyService在.iOS上运行并遍历设备上所有歌曲返回自定义“Song”模型的Xamarin.Forms应用程序。

无疑有更好的方法,但为了将专辑封面返回到PCL,我已经将iOS UIImage转换为System.IO.Stream对象,并通过Song模型返回它。

添加这个艺术品功能导致处理每首歌曲时开销变大。 为了让用户感受到正在发生的事情,我在页面上放置了一个进度条,并希望每次处理一首歌曲时更新它。

我的问题是,我无法实时更新进度条。 它只有在过程完成后才会更新。

我目前没有使用MVVM,所以这是代码背后……

using Xamarin.Forms;
using TestApplication.Interfaces;
using System.Threading.Tasks;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System;

namespace TestApplication
{
    public partial class TestApplicationPage : ContentPage, INotifyPropertyChanged
    {
        private double _progress;
        public double Progress
        {
            get { return _progress; }
            set
            {
                _progress = value;
                OnPropertyChanged();
            }
        }

        public event PropertyChangedEventHandler PropertyChanged;

        public TestApplication()
        {
            InitializeComponent();
            BindingContext = this;
        }

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        async void GetNoOfSongs(object sender, System.EventArgs e)
        {
            Action<double> progressUpdate = UpdateProgress;

            var songs = await DependencyService.Get<IMedia>().GetSongs(progressUpdate);

            await DisplayAlert("No. of Songs", string.Format("You have { 0} songs on your device!", songs.Count), "Ok");
        }

        void UpdateProgress(double obj)
        {
            Progress = (double)obj;
        }
    }
}

...这是XAML页面...

<?xml version="1.0" encoding="utf-8"?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:TestApplication" x:Class="TestApplication.TestApplicationPage">
    <StackLayout VerticalOptions="CenterAndExpand" HorizontalOptions="CenterAndExpand">
        <Button Text="No. of Songs" Clicked="GetNoOfSongs"></Button>
        <ProgressBar Margin="20" x:Name="progressBar" Progress="{Binding Progress}" WidthRequest="400"></ProgressBar>
    </StackLayout>
</ContentPage>

这是 Song 模型...

using System;
using System.IO;

namespace TestApplication.Models
{
    public class Song
    {
        public string Artist { get; set; }
        public string Album { get; set; }
        public string Title { get; set; }
        public Stream Artwork { get; set; }
        public TimeSpan Duration { get; set; }
    }
}

...这是IMedia接口...

using System.Threading.Tasks;
using System.Collections.Generic;
using TestApplication.Models;
using System;

namespace TestApplication.Interfaces
{
    public interface IMedia
    {
        Task<bool> IsAuthorised();
        Task<List<Song>> GetSongs(Action<double> callback);
    }
}

这是.iOS项目中的DependencyService实现...

using TestApplication.Interfaces;
using TestApplication.Models;
using MediaPlayer;
using System;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.IO;

[assembly: Xamarin.Forms.Dependency(typeof(TestApplication.iOS.Media))]
namespace TestApplication.iOS
{
    public class Media : IMedia
    {
        public List<Song> Songs { get; private set; }

        public async Task<bool> IsAuthorised()
        {
            await MPMediaLibrary.RequestAuthorizationAsync();

            if (MPMediaLibrary.AuthorizationStatus == MPMediaLibraryAuthorizationStatus.Authorized)
                return true;
            else
                return false;
        }

        public async Task<List<Song>> GetSongs(Action<double> callback)
        {
            Songs = new List<Song> { };

            if (await IsAuthorised())
            {
                var songs = MPMediaQuery.SongsQuery.Items;
                double index = 0;

                foreach (var song in songs)
                {
                    index++;

                    callback.Invoke(index / songs.Length);

                    Stream artwork = null;

                    if (song.Artwork != null)
                        artwork = song.Artwork.ImageWithSize(song.Artwork.Bounds.Size).AsPNG().AsStream();

                    Songs.Add(new Song
                    {
                        Artist = song.AlbumArtist,
                        Album = song.AlbumTitle,
                        Title = song.Title,
                        Artwork = artwork,
                        Duration = TimeSpan.FromSeconds(song.PlaybackDuration),
                    });
                }
            }

            return Songs;
        }
    }
}

...你会发现我已经将进度值绑定到了一个属性上。也许为了实现这个功能,当进度条运行时我可以直接通过ProgressBar对象进行更新,但我知道绑定是有效的。

我无法确定为什么它不能实时更新。如果我调试,它会进入回调函数并更新属性并触发OnPropertyChanged事件,但UI直到最后才更新。

我认为这与整个异步/等待(Aysnc/Await)有关,但不能确定。

我相信有人能帮我解决,感谢您提供的任何帮助。

谢谢


对于您的流量开销,我建议您将流转换为Base64字符串,这将大大减少开销。 - FreakyAli
谢谢,我也会检查一下。这个线程的主要目的是正确更新进度条并附带开销,在工作时更清晰,但是是的,为了性能我同意,会查看 Base64。 - Skin
1个回答

7
看起来你把所有耗时的计算都放在了UI线程中。这个线程应该专门用于更新UI。如果你在这个线程中进行大量计算并且想要更新UI,那么它将无法工作,因为UI线程正忙于你的计算。你必须在另一个线程中启动计算(使用类似于Task.Factory.StartNewTask.Run之类的东西)。 现在你在另一个线程中运行了长时间的进程,你必须通过调用Device.BeginInvokeOnMainThread来在UI线程中更新UI。
最终,你可以得到以下结果:
var songs = await Task.Run(async () => await DependencyService.Get<IMedia>().GetSongs(progressUpdate));

并且
void UpdateProgress(double obj)
        {
            Device.BeginInvokeOnMainThread(() => Progress = (double)obj);
        }

1
为什么要两次使用 Task.Run?为了提高可读性,最好引入一个变量来保存第一个 await 的结果。 - VMAtm
我不得不稍微修改你提供的代码,因为它无法编译,但这个可以运行...await Task.Run(async () => { var songs = await DependencyService.Get().GetSongs(progressUpdate); });... 我知道这与整个async/await有关。我对后台任务的理解有些误解。谢谢你的回答!至于Device.BeginInvokeOnMainThread,我以前用过,但显然它从未起作用,因为我没有调用Task.Run方程式的一侧。 - Skin
@VMAtm,你是对的。我不知道为什么要放两个Task.Run。我已经编辑过了。 - Daniel

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接