Android中使用AsyncTask请求URL返回Null的问题排查
嗨,这个问题其实是异步编程的典型坑——你被AsyncTask的异步特性给坑到啦!
你遇到的情况是:调用getResponse时立刻返回了response,但这时候异步任务的网络请求还没完成,所以返回的是初始的Null值;而类内部能看到正确响应,是因为doInBackground里的Log是在网络请求完成后才打印的,之后onPostExecute才会给response赋值,但这时候你早就拿到那个Null的返回值了。
核心原因
AsyncTask的执行是异步非阻塞的:当你调用execute(url)时,系统会把网络请求放到后台线程去执行,而主线程(调用getResponse的线程)会继续往下走,直接返回当时还是Null的response变量。只有当后台的网络请求完成后,onPostExecute才会被调用,这时候response才会被赋值,但这个时机远远晚于你拿到返回值的时刻。
解决方案
这里给你两种可行的处理方式,推荐第一种(符合Android异步编程的最佳实践):
方案一:用回调接口接收异步结果(推荐)
这种方式不会阻塞线程,也符合Android的设计规范。
首先定义一个回调接口,用来传递结果和错误:
public interface OnResponseReceivedListener { void onResponse(String response); void onError(Exception e); }
然后修改你的JSONParser类,去掉静态的response变量,改用回调传递结果:
import android.app.Activity; import android.os.AsyncTask; import android.util.Log; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.MalformedURLException; import java.net.URL; public class JSONParser extends AsyncTask<String, Void, String> { private Activity activity; private OnResponseReceivedListener listener; private Exception requestError; // 通过构造方法传入回调实例 public JSONParser(Activity activity, OnResponseReceivedListener listener) { this.activity = activity; this.listener = listener; } @Override protected String doInBackground(String... strings) { URL url; HttpURLConnection urlConnection = null; String serverResponse = null; try { url = new URL(strings[0]); urlConnection = (HttpURLConnection) url.openConnection(); int responseCode = urlConnection.getResponseCode(); if (responseCode == HttpURLConnection.HTTP_OK) { serverResponse = readStream(urlConnection.getInputStream()); Log.v("CatalogClient", serverResponse); } } catch (MalformedURLException e) { requestError = e; e.printStackTrace(); } catch (IOException e) { requestError = e; e.printStackTrace(); } finally { // 记得关闭连接,避免资源泄漏 if (urlConnection != null) { urlConnection.disconnect(); } } return serverResponse; } @Override protected void onPostExecute(String s) { super.onPostExecute(s); // 任务完成后通过回调传递结果 if (listener != null) { if (requestError != null) { listener.onError(requestError); } else { listener.onResponse(s); } } } private String readStream(InputStream in) { BufferedReader reader = null; StringBuffer responseBuffer = new StringBuffer(); try { reader = new BufferedReader(new InputStreamReader(in)); String line; while ((line = reader.readLine()) != null) { responseBuffer.append(line); } } catch (IOException e) { requestError = e; e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } return responseBuffer.toString(); } }
最后修改调用代码,通过回调接收结果:
String url = "https://jsonplaceholder.typicode.com/posts/1"; new JSONParser(this, new OnResponseReceivedListener() { @Override public void onResponse(String response) { Log.d(TAG, "Response is :" + response); // 在这里处理拿到的响应数据,比如解析JSON、更新UI等 } @Override public void onError(Exception e) { // 处理请求错误的情况,比如提示用户网络异常 Log.e(TAG, "请求失败:", e); } }).execute(url);
方案二:同步等待异步任务完成(不推荐)
如果你非要用同步返回的方式,可以用AsyncTask.get()方法等待任务完成,但绝对不要在UI线程调用这个方法,否则会导致界面卡顿甚至ANR(应用无响应)。
修改JSONParser的getResponse方法:
public String getResponse(Activity activity, String url) throws ExecutionException, InterruptedException { this.activity = activity; // get()方法会阻塞当前线程,直到异步任务完成 return this.execute(url).get(); }
调用时需要处理异常:
String url = "https://jsonplaceholder.typicode.com/posts/1"; try { // 注意:如果在UI线程调用这个方法,会导致ANR! String response = new JSONParser().getResponse(this, url); Log.d(TAG, "Response is :" + response); } catch (ExecutionException | InterruptedException e) { e.printStackTrace(); }
总结
Android开发中,耗时操作(比如网络请求)必须放在后台线程执行,而结果的接收一定要用异步的方式(比如回调),这是避免UI阻塞、保证应用流畅性的基本准则。
内容的提问来源于stack exchange,提问作者Sunny Bhadana

