从远程MySQL获取JSON时触发IllegalStateException异常问题排查
问题:切换MySQL查询表后触发OkHttp ResponseBody closed异常
我原本用Android代码从远程MySQL数据库获取tb_tickets_reparacion表的数据时运行正常,但把PHP中的SQL语句改成查询tb_ventas表后,触发了如下异常:
2019-10-09 12:12:13.448 27800-27875/? E/AndroidRuntime: FATAL EXCEPTION: AsyncTask #2 Process: com.mpidesarrollo.jogua, PID: 27800 java.lang.RuntimeException: An error occurred while executing doInBackground() at android.os.AsyncTask$3.done(AsyncTask.java:353) at java.util.concurrent.FutureTask.finishCompletion(FutureTask.java:383) at java.util.concurrent.FutureTask.setException(FutureTask.java:252) at java.util.concurrent.FutureTask.run(FutureTask.java:271) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:245) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636) at java.lang.Thread.run(Thread.java:764) Caused by: java.lang.IllegalStateException: closed at okio.RealBufferedSource.rangeEquals(RealBufferedSource.java:408) at okio.RealBufferedSource.rangeEquals(RealBufferedSource.java:402) at okhttp3.internal.Util.bomAwareCharset(Util.java:432) at okhttp3.ResponseBody.string(ResponseBody.java:174) at com.mpidesarrollo.jogua.activity.MisComprasFragment$4.doInBackground(MisComprasFragment.java:271) at com.mpidesarrollo.jogua.activity.MisComprasFragment$4.doInBackground(MisComprasFragment.java:251) at android.os.AsyncTask$2.call(AsyncTask.java:333) at java.util.concurrent.FutureTask.run(FutureTask.java:266) ... 4 more
相关代码
Android获取数据代码:
private void getComprasFromDB(int id) { Log.d("HOLA ADDRESSES", "DIRECCION LEIDA SIN BUSCAR: "); compras.clear(); AsyncTask<Integer, Void, Void> asyncTask = new AsyncTask<Integer, Void, Void>() { @Override protected Void doInBackground(Integer... addressesIds) { Log.d("HOLA ADDRESSES", "DIRECCION LEIDA SIN BUSCAR: "+id_usuario); OkHttpClient client = new OkHttpClient(); okhttp3.Request request = new okhttp3.Request.Builder() .url("https://.../mis_compras.php?id="+id_usuario) .build(); try { okhttp3.Response response = client.newCall(request).execute(); JSONArray array = new JSONArray(response.body().string()); for (int i = 0; i < array.length(); i++) { JSONObject object = array.getJSONObject(i); Log.d("HOLA PERFIL", "mis compras response: " + response.body().string()); Compras compra = new Compras( object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas"), object.getString("id_ventas") ); compras.add(compra); } } catch (IOException e) { Log.d("HOLA ADDRESSES", "DIRECCION LEIDA SIN BUSCAR:IO "+e); e.printStackTrace(); } catch (JSONException e) { Log.d("HOLA ADDRESSES", "DIRECCION LEIDA SIN BUSCAR:json "+e); e.printStackTrace(); } return null; } @Override protected void onPostExecute(Void aVoid) { adapter.notifyDataSetChanged(); if (compras.size() == 0){ txtNohaytickets.setVisibility(View.VISIBLE); } else { txtNohaytickets.setVisibility(View.GONE); } } }; asyncTask.execute(id); }
原本正常的PHP查询代码:
$con->set_charset("utf8"); if (mysqli_connect_errno()) { echo "Failed to connect to MySQL: " . mysqli_connect_error(); } $id = mysqli_real_escape_string($con,$_GET["id"]); $sql= "Select tr.*,tc.nombre as nombre_categoria,tm.nombre as nombre_marca, modelos.nombre as nombre_modelo from tb_tickets_reparacion tr LEFT JOIN tb_categorias tc ON tc.id = tr.categoria LEFT JOIN tb_marcas tm ON tm.id = tr.marca LEFT JOIN tb_modelos modelos ON modelos.id = tr.modelo WHERE id_cliente = '" .$id. "' "; $result = mysqli_query($con ,$sql); while ($row = mysqli_fetch_assoc($result)) { $array[] = $row; } header('Content-Type:Application/json'); echo json_encode($array); mysqli_free_result($result); mysqli_close($con);
修改后触发异常的PHP查询代码:
$con->set_charset("utf8"); if (mysqli_connect_errno()) { echo "Failed to connect to MySQL: " . mysqli_connect_error(); } $id = mysqli_real_escape_string($con,$_GET["id"]); $sql= "Select tr.* from tb_ventas tr "; $result = mysqli_query($con ,$sql); while ($row = mysqli_fetch_assoc($result)) { $array[] = $row; } header('Content-Type:Application/json'); echo json_encode($array); mysqli_free_result($result); mysqli_close($con);
问题原因与解决方案
核心问题:重复读取ResponseBody流
OkHttp的ResponseBody.string()方法会一次性读取流中的所有数据,然后直接关闭流。你的代码里犯了一个关键错误:
- 先调用
JSONArray array = new JSONArray(response.body().string());读取了一次流并关闭 - 紧接着在循环里又调用
Log.d("HOLA PERFIL", "mis compras response: " + response.body().string());,这时候流已经关闭,直接触发IllegalStateException: closed异常
之前查询tickets表时可能因为日志代码没被执行到(比如数组长度为0)或者其他巧合没暴露问题,但切换表后数据正常返回,循环执行到日志代码就触发了异常。
修复步骤:
先把响应内容读取到变量,重复使用该变量
修改Android代码中的doInBackground部分,先把响应内容存入字符串变量,后续解析和日志都用这个变量:try { okhttp3.Response response = client.newCall(request).execute(); // 先读取一次响应内容到变量,避免重复读取流 String responseContent = response.body().string(); Log.d("HOLA PERFIL", "mis compras response: " + responseContent); JSONArray array = new JSONArray(responseContent); for (int i = 0; i < array.length(); i++) { JSONObject object = array.getJSONObject(i); Compras compra = new Compras( // 这里要改成tb_ventas表对应的字段,别全用id_ventas object.getString("id_ventas"), object.getString("campo2"), object.getString("campo3"), // ... 其他字段 ); compras.add(compra); } } catch (IOException | JSONException e) { Log.e("HOLA ADDRESSES", "Error fetching data", e); e.printStackTrace(); }修正Compras构造函数的参数
你现在的构造函数所有参数都是object.getString("id_ventas"),这明显是测试代码,一定要改成tb_ventas表中实际存在的字段,不然就算解析成功也拿不到正确数据。PHP端的健壮性优化
修改后的PHP代码如果查询不到数据,$array会未定义,导致json_encode返回null,Android端解析JSON会报错。建议先初始化空数组:$array = []; // 初始化空数组,避免未定义问题 while ($row = mysqli_fetch_assoc($result)) { $array[] = $row; } header('Content-Type:Application/json'); echo json_encode($array);
内容的提问来源于stack exchange,提问作者mvasco
相关产品推荐
相关产品推荐

