Android - 异步任务出现问题

3
我有一个Recycler View, 用于显示从服务器下载的信息。为了使其具有响应性,我使用了async/await操作和Tasks进行编写。问题是当它等待信息下载完成时,UI仍在运行,这要么导致程序崩溃因为尚未下载任何信息,要么没有显示所有信息。
如果GetDevicesInfo() 是异步的,应用程序将崩溃,因为还没有信息可以显示。但奇怪的是,如果我将代码放入调用该方法的位置(然后注释掉该调用),它就可以正常工作。
而对于DisplaySensorStates()则不同,如果该方法不是异步的话,情况也是如此:

enter image description here

...当它是异步的时候:

enter image description here

显然,当它下载温度信息时,视图已经显示了现有的项目,这只是第一个。
代码:
namespace *********.Fragments {
    public class Dashboard : GridLayoutBase {
        JsonFetcher jsonFetcher;
        private ISharedPreferences pref;
        private SessionManager session;
        private string cookie;
        private DeviceModel deviceModel;
        private RecyclerView recyclerView;
        private RecyclerView.Adapter adapter;
        private RecyclerView.LayoutManager layoutManager;
        private List<ItemData> itemData;
        public static Activity activity;
        private SwipeRefreshLayout swipeRefreshLayout;
        private const string URL_DASHBOARD = "http://10.1.1.20/appapi/getdashboard";
        private const string URL_DATA = "http://10.1.1.20/appapi/getdata";

        public async override void OnStart() {
            base.OnStart();

            activity = Activity;
            session = new SessionManager();
            pref = Activity.GetSharedPreferences("UserSession", FileCreationMode.Private);
            cookie = pref.GetString("PHPSESSID", string.Empty);

            GetDevicesInfo();

            DisplaySensorStates();

            DisplayLastPhoto();

            adapter = new ViewAdapter(itemData);

            new System.Threading.Thread(new System.Threading.ThreadStart(() => {
                Activity.RunOnUiThread(() => {
                    recyclerView.SetAdapter(adapter);
                });
            })).Start();
        }

        public async void GetDevicesInfo() {
            var jsonFetcher = new JsonFetcher();
            JsonValue jsonDashboard = await jsonFetcher.FetchDataWithCookieAsync(URL_DASHBOARD, cookie);
            deviceModel = new DeviceModel();
            deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonDashboard);
        }

        // Shows sensor states
        public async void DisplaySensorStates() {
            itemData = new List<ItemData>();

            foreach (var sensor in this.deviceModel.Sensors) {
                string lastValue = String.Empty;

                if (sensor.Type == "2") { // Temperature
                    var jsonFetcher = new JsonFetcher();
                    JsonValue jsonData = await jsonFetcher.FetchSensorDataAsync(URL_DATA, sensor.Id, "ASC", cookie);
                    var deviceModel = new DeviceModel();
                    deviceModel = JsonConvert.DeserializeObject<DeviceModel>(jsonData);
                    lastValue = deviceModel.SensorData.Last().Value;
                }

                itemData.Add(new ItemData() {
                    id = sensor.Id,
                    value = lastValue,
                    type = sensor.Type,
                    image = Resource.Drawable.smoke_red,
                    title = sensor.Name.First().ToString().ToUpper() + sensor.Name.Substring(1).ToLower(),
                });
            }
        }

        // Shows the last camera photo
        public async void DisplayLastPhoto() {
            //          if (deviceModel.Error == "true" && deviceModel.ErrorType == "noPhoto") {
            //              //TODO: Show a "No photo" picture
            //          } else {
            //              string url = deviceModel.LastPhotoLink;
            //              Bitmap imageBitmap = await new ImageDownloader().GetImageBitmapFromUrlAsync(url, Activity, lastPhoto.Width, lastPhoto.Height);
            //              lastPhoto.SetImageBitmap(imageBitmap);
            //              imageBitmap.Dispose();
            //          }
        }

        public override View OnCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
            View view = inflater.Inflate(Resource.Layout.Dashboard, container, false);

            SwipeRefreshLayout swipeRefreshLayout = view.FindViewById<SwipeRefreshLayout>(Resource.Id.swipe_container);
            //          swipeRefreshLayout.SetColorSchemeResources(Color.LightBlue, Color.LightGreen, Color.Orange, Color.Red);

            // On refresh button press/swipe, refreshes the recycler view
            swipeRefreshLayout.Refresh += async (sender, e) => {
                this.GetDevicesInfo();

                new System.Threading.Thread(new System.Threading.ThreadStart(() => {
                    Activity.RunOnUiThread(() => {
                        this.DisplaySensorStates();
                        adapter = new ViewAdapter(itemData);
                        //                      itemData[0].title = "Dooooooors";
                        //                      Console.WriteLine(itemData[0].title);
                        recyclerView.SetAdapter(adapter);
                        //                      Console.WriteLine ("yes");
                        adapter.NotifyDataSetChanged();
                        //                      recyclerView.Invalidate();
                        swipeRefreshLayout.Refreshing = false;

                    });
                })).Start();

            };

            recyclerView = view.FindViewById<RecyclerView>(Resource.Id.dashboard_recycler_view);
            layoutManager = new GridLayoutManager(Activity, 3);

            recyclerView.HasFixedSize = true;

            recyclerView.SetLayoutManager(layoutManager);
            recyclerView.SetItemAnimator(new DefaultItemAnimator());
            recyclerView.AddItemDecoration(new SpaceItemDecoration(8));

            return view;
        }

        public class ViewAdapter : RecyclerView.Adapter {
            private List<ItemData> itemData;
            public string sensorId;
            public string sensorType;
            private ImageView imageId;
            private TextView sensorValue;
            private TextView sensorStatus;

            public ViewAdapter(List<ItemData> itemData) {
                this.itemData = itemData;
            }

            public class ItemView : RecyclerView.ViewHolder {
                public View mainView { get; set; }

                public string id { get; set; }

                public string type { get; set; }

                public ImageView image { get; set; }

                public TextView value { get; set; }

                public TextView status { get; set; }

                public ItemView(View view) : base(view) {
                    mainView = view;
                }
            }

            public override RecyclerView.ViewHolder OnCreateViewHolder(ViewGroup parent, int viewType) {
                View itemLayoutView = LayoutInflater.From(parent.Context).Inflate(Resource.Layout.DashboardItems, null);
                CardView cardView = itemLayoutView.FindViewById<CardView>(Resource.Id.dashboard_card_view);
                imageId = itemLayoutView.FindViewById<ImageView>(Resource.Id.sensor_image);
                sensorValue = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_value);
                sensorStatus = itemLayoutView.FindViewById<TextView>(Resource.Id.sensor_status);

                var viewHolder = new ItemView(itemLayoutView) {
                    id = sensorId,
                    type = sensorType,
                    image = imageId,
                    value = sensorValue,
                    status = sensorStatus
                };

                return viewHolder;
            }

            public override void OnBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
                ItemView itemHolder = viewHolder as ItemView;

                if (itemData[position].type == "2") { // Temperature
                    //                  itemHolder.image.Visibility = ViewStates.Invisible;
                    itemHolder.value.Text = itemData[position].value;
                }

                itemHolder.image.SetImageResource(itemData[position].image);
                itemHolder.status.Text = itemData[position].title;

                EventHandler clickUpdateViewEvent = ((sender, e) => {
                    var bundle = new Bundle();
                    var dualColumnList = new DualColumnList();

                    bundle.PutString("id", itemData[position].id);
                    dualColumnList.Arguments = bundle;

                    ((FragmentActivity)activity).ShowFragment(dualColumnList, itemData[position].title, itemData[position].type);
                });

                itemHolder.image.Click += clickUpdateViewEvent;
                itemHolder.value.Click += clickUpdateViewEvent;
                itemHolder.status.Click += clickUpdateViewEvent;
            }

            public override int ItemCount {
                get { return itemData.Count; }
            }
        }

        public class ItemData {
            public string id { get; set; }

            public string type { get; set; }

            public int image { get; set; }

            public string value { get; set; }

            public string title { get; set; }
        }
    }
}

我该如何修复这个问题?

1个回答

1

你需要设计你的UI为异步。

也就是说,当你的UI加载时,它应该(同步地)加载到一个没有数据的有效,预期的状态。同时,它也会在那个时候开始下载。当下载完成后,你必须更新UI以显示新数据。我有一系列关于异步MVVM应用程序的MSDN文章,你可能会发现有帮助。

此外,避免使用async void。我有另一篇MSDN最佳实践文章,其中详细介绍了这一点。


你能展示一下如何使UI异步吗?我是应用程序开发的新手,不知道如何使RecyclerView异步。谢谢。 - Milen Pivchev
@Milen:示例在MSDN的“异步MVVM”文章中。 - Stephen Cleary
据我所了解,由于这些方法是void类型的,无法等待执行完成,需要进行更改并观察结果。 - Milen Pivchev
好的,搞定了。现在可以等待任务并逐步执行,加载所有内容。吸取教训 - 不再使用“async voids”,感谢您的帮助 :) - Milen Pivchev

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