Problem

When we access the url via volley http on android phones, we get this error:

2020-10-02 14:58:12.183 19142-19225/? E/app: java.util.concurrent.ExecutionException: com.android.volley.ParseError: org.json.JSONException: End of input at character 0 of 
        at com.android.volley.toolbox.RequestFuture.doGet(Unknown Source:86)
        at com.android.volley.toolbox.RequestFuture.get(Unknown Source:10)
        at android.os.AsyncTask$3.call(AsyncTask.java:378)
        at java.util.concurrent.FutureTask.run(FutureTask.java:266)
        at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:289)
        at java.util.concurrent.ThreadPoolExecutor.processTask(ThreadPoolExecutor.java:1187)
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1152)
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
        at java.lang.Thread.run(Thread.java:929)
     Caused by: com.android.volley.ParseError: org.json.JSONException: End of input at character 0 of 
        at com.android.volley.toolbox.JsonObjectRequest.parseNetworkResponse(Unknown Source:32)
        at com.android.volley.NetworkDispatcher.processRequest(Unknown Source:57)
        at com.android.volley.NetworkDispatcher.processRequest(Unknown Source:8)
        at com.android.volley.NetworkDispatcher.run(Unknown Source:5)
     Caused by: org.json.JSONException: End of input at character 0 of 
        at org.json.JSONTokener.syntaxError(JSONTokener.java:460)
        at org.json.JSONTokener.nextValue(JSONTokener.java:101)
        at org.json.JSONObject.<init>(JSONObject.java:164)
        at org.json.JSONObject.<init>(JSONObject.java:181)
        at com.android.volley.toolbox.JsonObjectRequest.parseNetworkResponse(Unknown Source:17)
        at com.android.volley.NetworkDispatcher.processRequest(Unknown Source:57) 
        at com.android.volley.NetworkDispatcher.processRequest(Unknown Source:8) 
        at com.android.volley.NetworkDispatcher.run(Unknown Source:5) 

Environment

  • implementation 'com.android.volley:volley:1.1.1'
    

Reason

Sometimes, the server would return an empty object to client, but volley http client does not handle this correctly.

The source code of volley that handle the response:

    @Override
    @SuppressWarnings("DefaultCharset")
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            // Since minSdkVersion = 8, we can't call
            // new String(response.data, Charset.defaultCharset())
            // So suppress the warning instead.
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }

If the response.data is empty, this method would throw an exception that complaining about the invalid json response.

Solution

We should fix the error by overridding the parseNetworkResponse method to handle the empty response.

private static class MyStringRequest extends StringRequest {
        public MyStringRequest(int method, String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
            super(method, url, listener, errorListener);
        }

        public MyStringRequest(String url, Response.Listener<String> listener, @Nullable Response.ErrorListener errorListener) {
            super(url, listener, errorListener);
        }

        @Override
        protected Response<String> parseNetworkResponse(NetworkResponse response) {
            try {
                if (response.data.length == 0) {
                    byte[] responseData = "{}".getBytes("UTF8");
                    response = new NetworkResponse(response.statusCode, responseData,
                            response.notModified, response.networkTimeMs, response.allHeaders);
                }
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return super.parseNetworkResponse(response);
        }
    }

Then you can use the new MyStringRequest like this:

StringRequest getRequest = new MyStringRequest(Request.Method.GET,
                url,requestFuture,requestFuture) {
            @Override
            public Map<String, String> getHeaders() {
                HashMap<String, String> headers = new HashMap<String, String>();
                headers.put("Accept", "application/json");
                headers.put("Content-Type", "application/json; charset=UTF-8");
                return headers;
            }
        };
 VolleySingleton.getInstance(context).addToRequestQueue(getRequest);
 requestFuture.get(DEFAULT_TIMEOUT, TimeUnit.SECONDS);

Ok, problem solved.