【问题标题】:How to wait for an asynchronous method如何等待异步方法
【发布时间】:2018-07-18 12:57:09
【问题描述】:

我需要返回值uId。我在onResponse() 函数内的第一个日志语句中得到了正确的值。但是当涉及到 return 语句时,它返回 null

我认为 onResponse() 正在另一个线程上运行。如果是这样,我怎样才能让 getNumber() 函数等到 onResponse() 函数完成执行。(如 thread.join())

或者有其他解决方案吗?

代码:

String uId;
public String getNumber() {

    ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
    Call<TopLead> call = apiInterface.getTopLead();
    call.enqueue(new Callback<TopLead>() {
        @Override
        public void onResponse(Call<TopLead> call, Response<TopLead> response) {
            String phoneNumber;
            TopLead topLead = response.body();
            if (topLead != null) {
                phoneNumber = topLead.getPhoneNumber().toString();
                uId = topLead.getUId().toString();
                //dispaly the correct value of uId
                Log.i("PHONE NUMBER, UID", phoneNumber +", " + uId);
                onCallCallback.showToast("Calling " + phoneNumber);
            } else {
                onCallCallback.showToast("Could not load phone number");
            }
        }

        @Override
        public void onFailure(Call<TopLead> call, Throwable t) {
            t.printStackTrace();
        }
    });
    //output: Return uid null
    Log.i("Return"," uid" + uId);
    return uId; 

【问题讨论】:

  • 您应该阅读范围和执行顺序。 return 语句在设置 uId 值之前运行
  • 可能Callback 稍后会被执行,因此在执行线程到达return 的时刻,Callback 尚未执行。
  • 你的方法调用是异步的。 IMO 最好的方法是使 getNumber() 方法异步:而不是返回一个值,而是让 getNumber() 接受一个回调方法并在 onResponse() 的末尾调用它。
  • 我知道它在另一个线程中。我需要知道是否有任何方法可以让主线程等到 onResponse() 完成。就像我们在 java 中做 thread.join() 一样
  • @AbhijithK - 通常你会向用户显示一些正在完成的后台工作(例如进度条)。但是您不应该阻止用户界面。您可以禁用某些 UI 元素,直到您的回调(请参阅答案)获得所需的数据。但是“等待”会冻结 UI,这是禁忌。查看guidelines 以了解预期的行为类型。

标签: java android retrofit2


【解决方案1】:

您的方法执行异步请求。因此“return uId;”操作不会等到您的请求完成,因为它们位于不同的线程上。

我可以建议几种解决方案

  1. 使用接口回调

     public void getNumber(MyCallback callback) {
       ...
        phoneNumber = topLead.getPhoneNumber().toString();
        callback.onDataGot(phoneNumber);
     }
    

你的回调接口

     public interface MyCallback {

        void onDataGot(String number);
     }

最后,调用方法

getNumber(new MyCallback() {
    @Override
    public void onDataGot(String number) {
    // response
    }
});
  1. 使用 Kotlin 时(我认为是时候使用 Kotlin 而不是 Java 了 :))

    fun getNumber(onSuccess: (phone: String) -> Unit) {
      phoneNumber = topLead.getPhoneNumber().toString()
      onSuccess(phoneNumber)
    }
    

调用方法

    getNumber {
      println("telephone $it")
    }

【讨论】:

    【解决方案2】:

    由于我无法理解@David 的答案,我不得不自己想出一个解决方案。我必须使用库OkHttp3 从网络接收数据。为了解决这个问题,我使用了LiveData

    fun getText(): LiveData<String> {
        val liveData = MutableLiveData<String>()
        var result = ""
        val client = OkHttpClient()
    
        client.newCall(request).enqueue(object : Callback {
            override fun onFailure(call: Call, e: IOException) {
                e.printStackTrace()
                result = e.toString()
                liveData.postValue(result)
            }
    
            override fun onResponse(call: Call, response: Response) {
                result = response.body.toString()
                liveData.postValue(result)
            }
        })
        return liveData
    }
    

    一旦你有了 LiveData 对象,我们就可以观察它:

    getText().observe(viewLifecycleOwner) {
        Toast.makeText(requireContext(), "Received $it", Toast.LENGTH_SHORT).show()
    }
    

    【讨论】:

      【解决方案3】:

      问题似乎是你想要 uId 而你没有得到它。您的函数 getNumber() 执行 TopDown,但您发出的请求是异步的,在不同的线程上运行。因此,在您返回 uId 时,uId 中没有任何值。在call.enqueue 拥有的回调中,我的意思是onResponse()onFailure(),你不可能在onFailure 中得到uId,这很明显,但你会得到@ 并不是那么明显987654329@ 在“onResponse()”中。 来自 Retrofit 的 javadoc:

      onResponse void onResponse(Call&lt;T&gt; call, Response&lt;T&gt; response)

      为收到的 HTTP 响应调用。 注意:HTTP 响应仍可能指示应用程序级故障,例如 404 或 500。调用 Response.isSuccessful() 以确定响应是否指示成功。

      所以在onResponse 中,您仍然需要输入一些代码以确保您获得uId,然后只返回它。并且不要直接返回uId,设置为某个字符串(全局),确保可用后访问。

      所以改变你的代码::

      public void getNumber() {
      
      ApiInterface apiInterface = ApiClient.getClient().create(ApiInterface.class);
      Call<TopLead> call = apiInterface.getTopLead();
      call.enqueue(new Callback<TopLead>() {
          @Override
          public void onResponse(Call<TopLead> call, Response<TopLead> response) {
              if(response.isSuccesful(){
                  String phoneNumber;
              TopLead topLead = response.body();
              if (topLead != null) {
                  phoneNumber = topLead.getPhoneNumber().toString();
                  uId = topLead.getUId().toString();
                  //dispaly the correct value of uId
                  Log.i("PHONE NUMBER, UID", phoneNumber +", " + uId);
                  onCallCallback.showToast("Calling " + phoneNumber);
                  //output: Return uid 
                 Log.i("Return"," uid" + uId); 
      
              } else {
                  onCallCallback.showToast("Could not load phone number");
              }
              } else{
                  Log.e("in ", " response is not successful" )
              }
          }
      
          @Override
          public void onFailure(Call<TopLead> call, Throwable t) {
              t.printStackTrace();
          }
      });
      

      这仍然在不同的线程上运行,所以uId 将在一段时间后设置。设置后,您可以使用该值。如果你想在主线程上运行,你也可以使用Synchronous请求并使用call.execute().body();获取uId,它返回TopLead对象,然后你可以使用topLead.getUId.toString()从那里获取uId

      希望对您有所帮助,如果您需要澄清,请在 cmets 中询问。

      【讨论】:

        【解决方案4】:

        不返回值。因为 AsyncTask 是一个单独的,在正常进程之外运行。

        所以请创建一个带有参数的方法并将你的值传递给该方法。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2013-02-15
          • 2013-07-24
          • 2013-08-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多