Android通过PHP从MySQL获取长篇文本时解析失败的问题排查
我来帮你拆解这个问题——你遇到的部分请求JSON解析失败,大概率是代码潜在bug和服务商限制共同作用的结果,咱们结合你的代码和场景一步步分析:
问题背景回顾
我在在线MySQL数据库中存储了书籍量级的长文本,需要通过PHP服务器在Android应用中下载。最初尝试单次请求获取全部文本时,出现了JSON解析错误:
E/JSON Parser: Error parsing data org.json.JSONException: End of input at character 0 of
推测是文本过大导致的,于是改成分块(每次1000字符)获取,单独在浏览器测试PHP文件是可行的,但Android端的AsyncTask执行时仅部分请求成功(比如前8次及随机几次),其余还是报相同的解析错误。服务器托管在1and1,想知道这是服务商限制、代码bug,还是有更合适的文本获取方法?
你当前使用的代码
1. JSONParser类
public class JSONParser { static InputStream is = null; static JSONObject jObj = null; static String json = ""; // constructor public JSONParser() {} // function get json from url // by making HTTP POST or GET method public JSONObject makeHttpRequest(String url, String method, List<NameValuePair> params) { // Making HTTP request try { // check for request method if(method == "POST"){ // request method is POST // defaultHttpClient DefaultHttpClient httpClient = new DefaultHttpClient(); HttpPost httpPost = new HttpPost(url); httpPost.setEntity(new UrlEncodedFormEntity(params)); HttpResponse httpResponse = httpClient.execute(httpPost); HttpEntity httpEntity = httpResponse.getEntity(); is = httpEntity.getContent(); }else if(method == "GET"){ // request method is GET DefaultHttpClient httpClient = new DefaultHttpClient(); String paramString = URLEncodedUtils.format(params, "utf-8"); url += "?" + paramString; HttpGet httpGet = new HttpGet(url); HttpResponse httpResponse = httpClient.execute(httpGet); HttpEntity httpEntity = httpResponse.getEntity(); is = httpEntity.getContent(); } } catch (UnsupportedEncodingException e) { e.printStackTrace(); } catch (ClientProtocolException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { BufferedReader reader = new BufferedReader(new InputStreamReader( is, "iso-8859-1"), 10000000); StringBuilder sb = new StringBuilder(); String line = null; while ((line = reader.readLine()) != null) { sb.append(line + "\n"); } is.close(); json = sb.toString(); } catch (Exception e) { Log.e("Buffer Error", "Error converting result " + e.toString()); } Log.i("json", "here"); Log.i("json", json + " , "); // try parse the string to a JSON object try { jObj = new JSONObject(json); } catch (JSONException e) { Log.e("JSON Parser", "Error parsing data " + e.toString()); } // return JSON String return jObj; } }
2. Android端AsyncTask代码
protected Boolean doInBackground(String... params) { boolean suc = false; // Check for success tag int success; try { // Building Parameters List<NameValuePair> parametri = new ArrayList<NameValuePair>(); parametri.add(new BasicNameValuePair(TAG_BOOKID, Integer.toString(id))); JSONObject json = jsonParser.makeHttpRequest( url_book_file_length, "GET", parametri); // check your log for json response Log.d("Single Book length", json.toString()); // json success tag success = json.getInt(TAG_SUCCESS); if (success == 1) { // successfully received product details JSONArray productObj = json .getJSONArray(TAG_BOOK); // JSON Array // get first product object from JSON Array JSONObject jbook = productObj.getJSONObject(0); int length = jbook.getInt(TAG_LENGTH); int current = 1; String file = ""; suc = true; while (current <= length){ parametri = new ArrayList<NameValuePair>(); parametri.add(new BasicNameValuePair(TAG_BOOKID, Integer.toString(id))); parametri.add(new BasicNameValuePair(TAG_START, Integer.toString(current))); if (length-current>=LENGTH_MAX){ parametri.add(new BasicNameValuePair(TAG_LENGTH, Integer.toString(LENGTH_MAX))); current+= LENGTH_MAX; } else { parametri.add(new BasicNameValuePair(TAG_LENGTH, Integer.toString(length-current+1))); current = length+1; } json = jsonParser.makeHttpRequest( url_book_file, "GET", parametri); // check your log for json response Log.d("Single Book pezzo file", "current " + Integer.toString(current) + " " + json.toString()); // json success tag success = json.getInt(TAG_SUCCESS); if (success == 1) { // successfully received product details productObj = json .getJSONArray(TAG_BOOK); // JSON Array // get first product object from JSON Array jbook = productObj.getJSONObject(0); file += jbook.getString(TAG_FILE); }else{ suc = false; break; // product with pid not found } } book.setFile(file); Log.i("File", "saved"); }else{ // product with pid not found } } catch (JSONException e) { e.printStackTrace(); } return suc; }
3. 对应的PHP文件代码
<?php // array for JSON response $response = array(); // include db connect class require_once __DIR__ . '/db_connect.php'; // connecting to db $db = new DB_CONNECT(); // check for post data if ((isset($_GET["bookid"]))&&(isset($_GET["start"]))&&(isset($_GET["length"]))) { $start = $_GET['start']; $length = $_GET['length']; $bookid = $_GET['bookid']; // get a book from books table $result = mysql_query("SELECT SUBSTRING(file, $start, $length) AS file FROM book WHERE bookid = $bookid"); if (!empty($result)) { // check for empty result if (mysql_num_rows($result) > 0) { $result = mysql_fetch_array($result); $book = array(); $book["file"] = $result["file"]; // success $response["success"] = 1; // user node $response["book"] = array(); array_push($response["book"], $book); // echoing JSON response echo json_encode($response); } else { // no book found $response["success"] = 0; $response["message"] = "No book found"; // echo no users JSON echo json_encode($response); } } else { // no book found $response["success"] = 0; $response["message"] = "No book found"; // echo no users JSON echo json_encode($response); } } else { // required field is missing $response["success"] = 0; $response["message"] = "Required field(s) is missing"; //echoing JSON response echo json_encode($response); } ?>
问题排查与解决方案
1. 代码层面的核心bug
(1)请求方式判断错误
在JSONParser的makeHttpRequest方法中,你用method == "POST"和method == "GET"做判断——这在Java中是引用比较,不是内容比较!当传入的method字符串是不同对象时,判断会失效,导致请求逻辑错误,无法获取到输入流,最终返回空JSON触发解析错误。
修复方式:改成内容比较:
if("POST".equals(method)){ // POST逻辑 }else if("GET".equals(method)){ // GET逻辑 }
(2)字符编码不匹配
你的JSONParser用iso-8859-1编码读取流,但json_encode默认输出UTF-8编码的JSON,编码不匹配会导致部分字符解析错误,甚至截断JSON内容。
修复方式:把InputStreamReader的编码改成utf-8:
BufferedReader reader = new BufferedReader(new InputStreamReader(is, "utf-8"), 10000000);
(3)HttpClient频繁创建导致连接耗尽
你每次请求都新建DefaultHttpClient,短时间内发起大量分块请求时,会频繁创建销毁连接,可能触发客户端连接池耗尽或服务器连接限制,导致部分请求无响应。
修复方式:复用HttpClient实例,比如把DefaultHttpClient作为JSONParser的成员变量初始化一次:
public class JSONParser { static InputStream is = null; static JSONObject jObj = null; static String json = ""; private DefaultHttpClient httpClient; public JSONParser() { httpClient = new DefaultHttpClient(); } // 后续请求直接复用这个httpClient }
2. 服务商1and1的限制可能性
1and1(现IONOS)确实有请求频率和连接数限制,短时间内发起大量GET请求可能被限流,导致部分请求被拦截返回空内容。
验证方式:在JSONParser中添加日志打印HTTP响应状态码:
HttpResponse httpResponse = httpClient.execute(httpGet); Log.d("HTTP Status", "Code: " + httpResponse.getStatusLine().getStatusCode());
如果看到429(限流)或5xx错误,说明是服务商限制,解决方法是在分块请求之间添加短暂延迟:
// 每次请求后延迟100ms Thread.sleep(100);
3. 更优的文本获取方案
分块JSON请求不是最优选择,推荐两种更高效的方式:
(1)直接返回纯文本而非JSON
修改PHP代码,直接输出截取的文本内容,避免JSON解析的开销和潜在错误:
if ((isset($_GET["bookid"]))&&(isset($_GET["start"]))&&(isset($_GET["length"]))) { $start = $_GET['start']; $length = $_GET['length']; $bookid = $_GET['bookid']; $result = mysql_query("SELECT SUBSTRING(file, $start, $length) AS file FROM book WHERE bookid = $bookid"); if (!empty($result) && mysql_num_rows($result) > 0) { $result = mysql_fetch_array($result); header('Content-Type: text/plain; charset=utf-8'); echo $result["file"]; } else { header("HTTP/1.1 404 Not Found"); echo "No content"; } }
Android端直接读取流拼接字符串,无需解析JSON。
(2)使用HTTP范围请求
如果服务器支持,实现HTTP Range Requests,让Android端通过一次请求的分块获取下载整个文本,这种方式更符合HTTP规范,适合大文件场景。
内容的提问来源于stack exchange,提问作者GC Apps Lab

