【问题标题】:Android, Async, CallBack and UIAndroid、异步、回调和 UI
【发布时间】:2015-02-24 18:47:29
【问题描述】:

在将这些东西组合成一个整体时,我遇到了一个棘手的问题。 示例:我想获取用户位置并使用纬度和经度值更新 UI。因为我坚持 MVC 模式,所以我为此任务创建了一个单独的类(例如 LocationWorker)并按照本指南实现了它: http://developer.android.com/training/location/retrie..

问题:在这种情况下,onConnected 回调位于 LocationWorker 类中,无法与 UI 进行通信。我尝试在 MainActivity 类中实现 onConnected callBack(还添加了 GoogleApiClient.ConnectionCallbacks 的实现),但出现错误(尝试在空对象引用上调用虚拟方法“methodName”)。似乎在 Worker 之前执行的回调实际上完成了所有工作(连接后它会获取用户位置,但 OnConnectedCallback 是在连接后立即触发,并且在它有任何时间获取任何数据之前)。

无论如何,这对我来说是个大问题,我应该使用什么来执行异步任务并能够在异步任务完成时更新 UI(并且不破坏 MVC)?

【问题讨论】:

    标签: java android android-asynctask callback


    【解决方案1】:

    AsyncTask 中有一个 get(),它将在请求完成时触发,并返回您在创建 AsyncTask 时指定的 RETURN TYPE。

    eg : ImageGrabber extends AsyncTask<String, String, Bitmap>
    

    这里的返回类型是Bitmap

    所以这里有一个示例,下面的字符串将包含响应。

    String jsonResponse = new JSONHelper().execute("http://example.com").get();
    

    【讨论】:

      【解决方案2】:

      异步类:

       //We can attach any object to the async class for result
      private class MyAsyncTask extends AsyncTask<Void,MyObject,MyObject >{
      
      
          @Override
          protected MyObject doInBackground(Void... params) {
      
              //Do the work to get your data
              //this is done in a background thread
      
              //prepare the result
      
              //MyObject object = new MyObject();
              //object.setLong(LONG);
              //object.setLang(LANG);
      
      
              //You can pass the result here 
              //return object;
              return null;
          }
      
          @Override
          protected void onPostExecute(MyObject object) {
              super.onPostExecute(object);
      
              //Update UI
              //this is done on the main thread
      
              //access data from the Object result
              //object.getLong();
              //object.getLang();
          }
      }
      

      你启动异步类:

      new MyAsyncTask().execute();
      

      【讨论】:

      • 好主意,虽然如果我使用描述的方法来获取一个人的位置,我已经有一个 OnConnected 回调方法。所以,如果我理解正确,我需要的只是从 OnConnected 回调调用 Async 任务? (在这种情况下,异步任务准备所有数据并将其返回给 UI 线程)。如果是,只需在 MainActivity 中实现一个 OnConnected 回调?
      • 回调是一个好方法,是的,我同意。虽然你知道有很多选择:)
      • 等待数据是否与回调一起出现,则无需在异步中进行准备。仅对可能阻塞 UI 的内容使用异步
      • 这正是问题所在,与位置服务建立连接时触发回调,但此时没有数据。我认为此时我应该开始接收数据,但是如何通知 UI 我已经完成了数据获取?这可以被认为是进行回调的一种常见且正确的做法吗?
      • no..yours 正是使用异步任务的情况...建立连接必须在单独的线程上完成
      【解决方案3】:

      如果您想简化 UI 和任何任务之间的通信,我建议使用此框架将您的任务与 UI 分离:Greenrobot's EventBus

      一个事件总线是您以最小的学习曲线完成您尝试做的所有事情。 您的 UI 将订阅您的任务发布的事件:

      public class LocationEvent {
        private float lat, lng;
        public LocationEvent(float lat, float lng) { this.lat=lat; this.lng=lng; }
      }
      
      MainActivity {
        public void onStart() {
          super.onStart();
          EventBus.getDefault().register(this);
        }
      
        public void onStop() {
          EventBus.getDefault().unregister(this);
          super.onStop();
        }
      
        public void onEvent(LocationEvent event) {
          // update user location
        }
      }
      
      LocationWorker {
        void doSomething() {
          eventBus.post(new LocationEvent(lat, lng));
        }
      }
      

      使用作业队列代替 AsyncTask 将使您的任务/作业更易于管理,并具有重试和持久性的额外好处。

      看看Path's Android Priority Job Queue

      【讨论】:

        【解决方案4】:

        使用new Asynctaskname.execute().get();会先完成异步任务再执行下一个。

        【讨论】:

          【解决方案5】:

          EventBus 看起来很有希望。

          另一种选择是使用 Intent 和广播接收器。

          例子:

          在 MainActivity.java 中:

          @Override
          public void onResume() {
              super.onResume();
              IntentFilter iFilter= new IntentFilter("updatelocation");
              iFilter.addAction("someOtherAction"); //if you want to add other actions to filter
              this.registerReceiver(br, iFilter);
          }
          
          @Override
          public void onPause() {
              this.unregisterReceiver(br);
              super.onPause();
          }
          
          private BroadcastReceiver br = new BroadcastReceiver() {
          
              @Override
              public void onReceive(Context context, Intent intent) {
                  String action = intent.getAction();
                  if (action.equals("UpdateLocation")){
          
                    //set these to member variables for later retrieval
                    double lat = intent.getExtras().getDouble("lat");
                    double lon = intent.getExtras().getDouble("lon");
          
                    this.runOnUiThread(mUpdateLocation);
                  }
              }
          
          };
          
          private final Runnable mUpdateLocation = new Runnable() {
          
              @Override
              public void run() {
                //TODO: Update your UI here                        
              }
          };
          

          然后,在您的 LocationWorker 类中,当位置发生变化时发送一个意图。确保您在 LocationWorker 类中引用了应用程序上下文。

          看到这个帖子:passing GPS coordinates to activity

              //Make sure you have a reference to the application context
            Intent updateLocation= new Intent(context, MainActivity.class);
            updateLocation.setAction("UpdateLocation");
            updateLocation.putExtra("lon", xLocation.getLongitude());
            updateLocation.putExtra("lat", xLocation.getLatitude());
            context.sendBroadcast(i);
          

          【讨论】: