【问题标题】:Android - Issue with async tasksAndroid - 异步任务问题
【发布时间】:2015-08-24 12:42:34
【问题描述】:

我有一个显示信息的回收站视图,从服务器下载。我希望它在执行所有这些操作时具有响应性,因此我使用 async/await 操作和 Tasks 编写了它。问题是,当它等待下载信息时,UI 仍然继续工作,并且它要么因为尚未下载任何内容而崩溃,要么它没有显示所有信息。

如果GetDevicesInfo() 是异步的,则应用程序会崩溃,因为还没有要显示的信息。出于某种原因,如果我将代码放在方法中调用方法的位置(然后注释调用),它就可以正常工作。

DisplaySensorStates() 的情况不同。 当方法不是异步时,这就是一切的样子:

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

显然,当它下载温度信息时,视图已经显示了现有项目,这只是第一个。

代码:

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; }
        }
    }
}

我该如何解决这个问题?

【问题讨论】:

    标签: c# android asynchronous android-asynctask xamarin


    【解决方案1】:

    您必须将您的 UI 设计为异步的。

    也就是说,当您的 UI 加载时,它应该(同步)加载到没有数据的有效、预期状态。它同时开始下载。下载完成后,您必须更新 UI 以显示新数据。我有一系列MSDN articles on async MVVM applications,您可能会觉得有帮助。

    另外,避免async void。我还有另一个MSDN article on async best practices 更详细。

    【讨论】:

    • 你能举例说明如何使 UI 异步吗?我是应用程序开发的新手,我不知道如何使 RecyclerView 异步。谢谢。
    • @Milen:示例在 MSDN“异步 MVVM”文章中。
    • 据我了解,因为方法是无效的,他们不能等待,必须改变它,看看会发生什么。
    • 是的,做到了。现在可以等待任务,它们会一步一步地加载所有内容。经验教训 - 不再有 async voids,感谢您的帮助 :)
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-06-30
    • 1970-01-01
    相关资源
    最近更新 更多