error.networkResponse 为空时 Android Volley 中的 Http 状态代码
Posted
技术标签:
【中文标题】error.networkResponse 为空时 Android Volley 中的 Http 状态代码【英文标题】:Http Status Code in Android Volley when error.networkResponse is null 【发布时间】:2014-05-21 19:07:18 【问题描述】:我在 android 平台上使用 Google Volley。
我遇到了一个问题,其中onErrorResponse
中的error
参数返回一个空值networkResponse
对于我使用的 RESTful API,我需要确定通常以 401 (SC_UNAUTHORIZED) 或 500 (SC_INTERNAL_SERVER_ERROR) 的形式到达的 Http 状态代码,我偶尔可以通过以下方式检查:
final int httpStatusCode = error.networkResponse.statusCode;
if(networkResponse == HttpStatus.SC_UNAUTHORIZED)
// Http status code 401: Unauthorized.
这会引发NullPointerException
,因为networkResponse
为空。
如何确定函数onErrorResponse
中的Http状态码?
或者,我如何确保error.networkResponse
在onErrorResponse
中不为空?
【问题讨论】:
【参考方案1】:或者,我如何确保 error.networkResponse 在 onErrorResponse?
我的第一个想法是检查对象是否为空。
@Override
public void onErrorResponse(VolleyError error)
NetworkResponse networkResponse = error.networkResponse;
if (networkResponse != null && networkResponse.statusCode == HttpStatus.SC_UNAUTHORIZED)
// HTTP Status Code: 401 Unauthorized
或者,您也可以尝试通过扩展Request
类并覆盖parseNetworkResponse
来获取状态码。
例如,如果扩展抽象 Request<T>
类
public class GsonRequest<T> extends Request<T>
...
private int mStatusCode;
public int getStatusCode()
return mStatusCode;
...
@Override
protected Response<T> parseNetworkResponse(NetworkResponse response)
mStatusCode = response.statusCode;
try
Log.d(TAG, "[raw json]: " + (new String(response.data)));
Gson gson = new Gson();
String json = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
return Response.success(gson.fromJson(json, mClazz),
HttpHeaderParser.parseCacheHeaders(response));
catch (UnsupportedEncodingException e)
return Response.error(new ParseError(e));
catch (JsonSyntaxException e)
return Response.error(new ParseError(e));
...
或者,如果您正在使用已经扩展了抽象 Request<T>
类的工具箱类之一,并且您不想混淆 parseNetworkResponse(NetworkResponse networkResponse)
的实现,请继续覆盖该方法但通过 @ 返回超级的实现987654329@
例如StringResponse
public class MyStringRequest extends StringRequest
private int mStatusCode;
public MyStringRequest(int method, String url, Listener<String> listener,
ErrorListener errorListener)
super(method, url, listener, errorListener);
public int getStatusCode()
return mStatusCode;
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response)
mStatusCode = response.statusCode;
return super.parseNetworkResponse(response);
用法:
public class myClazz extends FragmentActivity
private Request mMyRequest;
...
public void makeNetworkCall()
mMyRequest = new MyNetworkRequest(
Method.GET,
BASE_URL + Endpoint.USER,
new Listener<String>()
@Override
public void onResponse(String response)
// Success
,
new ErrorListener()
@Override
public void onErrorResponse(VolleyError error)
if (mMyRequest.getStatusCode() == 401)
// HTTP Status Code: 401 Unauthorized
);
MyVolley.getRequestQueue().add(request);
当然,覆盖内联方法的选项也是可用的
public class MyClazz extends FragmentActivity
private int mStatusCode;
...
public void makeNetworkCall()
StringRequest request = new StringRequest(
Method.GET,
BASE_URL + Endpoint.USER,
new Listener<String>()
@Override
public void onResponse(String response)
// Success
,
new ErrorListener()
@Override
public void onErrorResponse(VolleyError error)
if (mStatusCode == 401)
// HTTP Status Code: 401 Unauthorized
)
@Override
protected Response<String> parseNetworkResponse(NetworkResponse response)
mStatusCode = response.statusCode;
return super.parseNetworkResponse(response);
;
MyVolley.getRequestQueue.add(request);
更新:HttpStatus
已弃用。请改用HttpURLConnection
。见Link。
【讨论】:
当服务器发送 statusCode 403 时,从未调用过 parseNetworkResponse 方法。 如果您尝试从内部类访问任何内容,您会被要求将该变量设为 final。不是很好恕我直言【参考方案2】:Volley 不支持 401
事实证明,如果不修改 Google Volley 代码,就不可能保证 error.networkResponse 不为空,因为 Volley 中的一个错误会引发 BasicNetwork 中 Http 状态代码 401 (HttpStatus.SC_UNAUTHORIZED
) 的异常 NoConnectionError
.java (134) 之前设置networkResponse
的值。
解决方法
在这种情况下,我们的解决方案不是修复 Volley 代码,而是修改 Web 服务 API 以针对特定情况发送 Http 错误代码 403 (HttpStatus.SC_FORBIDDEN
)。
对于此 Http 状态代码,error.networkResponse
的值在 Volley 错误处理程序中为非空值:public void onErrorResponse(VolleyError error)
。并且,error.networkResponse.httpStatusCode
正确返回 HttpStatus.SC_FORBIDDEN
。
其他建议
Rperryng 关于扩展Request<T>
类的建议可能提供了一个解决方案,并且是一个有创意的绝妙想法。非常感谢您提供详细的示例。我发现我们案例的最佳解决方案是使用变通方法,因为我们很幸运能够控制 Web 服务 API。
如果我无法在服务器上进行简单更改,我可能会选择在 BasicNetwork.java 中的一个位置修复 Volley 代码。
【讨论】:
【参考方案3】:Volley 支持 HTTP 401 未经授权的响应。但是这个响应必须包含“WWW-Authenticate”头域。
如果没有此标头,401 响应会导致 "com.android.volley.NoConnectionError: java.io.IOException: No authentication challenges found"
错误。
更多详情:https://***.com/a/25556453/860189
如果您使用 3rd 方 API 并且无权更改响应标头,您可以考虑实现自己的 HttpStack,因为 HurlStack 会抛出此异常。或者更好的是,使用 OkHttpStack 作为 HttpStack。
【讨论】:
【参考方案4】:您可以修改 volley 库的 performRequest me(toolbox/BasicNetwork.java) 方法来捕获 401 Unauthorized 响应。 (修改后的代码也将解决volley的http-> https重定向问题)
@Override
public NetworkResponse performRequest(Request<?> request) throws VolleyError
long requestStart = SystemClock.elapsedRealtime();
while (true)
HttpResponse httpResponse = null;
byte[] responseContents = null;
Map<String, String> responseHeaders = Collections.emptyMap();
try
// Gather headers.
Map<String, String> headers = new HashMap<String, String>();
addCacheHeaders(headers, request.getCacheEntry());
httpResponse = mHttpStack.performRequest(request, headers);
StatusLine statusLine = httpResponse.getStatusLine();
int statusCode = statusLine.getStatusCode();
responseHeaders = convertHeaders(httpResponse.getAllHeaders());
// Handle cache validation.
if (statusCode == HttpStatus.SC_NOT_MODIFIED)
Entry entry = request.getCacheEntry();
if (entry == null)
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, null,
responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
// A HTTP 304 response does not have all header fields. We
// have to use the header fields from the cache entry plus
// the new ones from the response.
// http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
entry.responseHeaders.putAll(responseHeaders);
return new NetworkResponse(HttpStatus.SC_NOT_MODIFIED, entry.data,
entry.responseHeaders, true,
SystemClock.elapsedRealtime() - requestStart);
// Handle moved resources
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY || statusCode == HttpStatus.SC_MOVED_TEMPORARILY)
String newUrl = responseHeaders.get("Location");
request.setUrl(newUrl);
// Some responses such as 204s do not have content. We must check.
if (httpResponse.getEntity() != null)
responseContents = entityToBytes(httpResponse.getEntity());
else
// Add 0 byte response as a way of honestly representing a
// no-content request.
responseContents = new byte[0];
// if the request is slow, log it.
long requestLifetime = SystemClock.elapsedRealtime() - requestStart;
logSlowRequests(requestLifetime, request, responseContents, statusLine);
if (statusCode < 200 || statusCode > 299)
throw new IOException();
return new NetworkResponse(statusCode, responseContents, responseHeaders, false,
SystemClock.elapsedRealtime() - requestStart);
catch (SocketTimeoutException e)
attemptRetryOnException("socket", request, new TimeoutError());
catch (ConnectTimeoutException e)
attemptRetryOnException("connection", request, new TimeoutError());
catch (MalformedURLException e)
throw new RuntimeException("Bad URL " + request.getUrl(), e);
catch (IOException e)
int statusCode = 0;
NetworkResponse networkResponse = null;
if (httpResponse != null)
statusCode = httpResponse.getStatusLine().getStatusCode();
else
throw new NoConnectionError(e);
if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY)
VolleyLog.e("Request at %s has been redirected to %s", request.getUrl(), request.getUrl());
else
VolleyLog.e("Unexpected response code %d for %s", statusCode, request.getUrl());
if (statusCode==HttpStatus.SC_FORBIDDEN)
throw new VolleyError("403");
else if (statusCode == HttpStatus.SC_UNAUTHORIZED)
attemptRetryOnException("auth",
request, new AuthFailureError(""));
if (responseContents != null)
networkResponse = new NetworkResponse(statusCode, responseContents,
responseHeaders, false, SystemClock.elapsedRealtime() - requestStart);
if (statusCode == HttpStatus.SC_UNAUTHORIZED)
attemptRetryOnException("auth",
request, new AuthFailureError(networkResponse));
else if (statusCode == HttpStatus.SC_MOVED_PERMANENTLY ||
statusCode == HttpStatus.SC_MOVED_TEMPORARILY)
attemptRetryOnException("redirect",
request, new AuthFailureError(networkResponse));
else
// TODO: Only throw ServerError for 5xx status codes.
throw new ServerError(networkResponse);
else
throw new NetworkError(e);
然后在凌空错误处理程序中使用此代码
@Override
public void onErrorResponse(VolleyError error)
if (error instanceof AuthFailureError)
//handler error 401 unauthorized from here
)
编码愉快:D
【讨论】:
【参考方案5】:可以接收以下格式的网络响应
NetworkResponse response = error.networkResponse;
if(response != null && response.data != null)
switch(response.statusCode)
case 403:
json = new String(response.data);
json = trimMessage(json, "error");
if(json != null) displayMessage(json);
break;
【讨论】:
【参考方案6】:如果设备没有网络连接,error.networkResponse
将是 null
(您可以通过启用飞行模式来证明这一点)。查看 Volley 库中对应的code fragment。
在查找networkResponse
之前,您必须检查错误是否是NoConnectionError
的实例。我不能同意,Volley 不支持 401 错误,我对其进行了测试并得到了一个非空的 networkResponse
对象,并返回了 401 状态码。看对应代码here。
【讨论】:
【参考方案7】:这就是我检查和 grep 错误的方式。
// TimeoutError => most likely server is down or network is down.
Log.e(TAG, "TimeoutError: " + (e instanceof TimeoutError));
Log.e(TAG, "NoConnectionError: " + (e instanceof NoConnectionError));
/*if(error.getCause() instanceof UnknownHostException ||
error.getCause() instanceof EOFException )
errorMsg = resources.getString(R.string.net_error_connect_network);
else
if(error.getCause().toString().contains("Network is unreachable"))
errorMsg = resources.getString(R.string.net_error_no_network);
else
errorMsg = resources.getString(R.string.net_error_connect_network);
*/
Log.e(TAG, "NetworkError: " + (e instanceof NetworkError));
Log.e(TAG, "AuthFailureError: " + (e instanceof AuthFailureError));
Log.e(TAG, "ServerError: " + (e instanceof ServerError));
//error.networkResponse.statusCode
// inform dev
Log.e(TAG, "ParseError: " + (e instanceof ParseError));
//error.getCause() instanceof JsonSyntaxException
Log.e(TAG, "NullPointerException: " + (e.getCause() instanceof NullPointerException));
if (e.networkResponse != null)
// 401 => login again
Log.e(TAG, String.valueOf(e.networkResponse.statusCode));
if (e.networkResponse.data != null)
// most likely JSONString
Log.e(TAG, new String(e.networkResponse.data, StandardCharsets.UTF_8));
Toast.makeText(getApplicationContext(),
new String(e.networkResponse.data, StandardCharsets.UTF_8),
Toast.LENGTH_LONG).show();
else if (e.getMessage() == null)
Log.e(TAG, "e.getMessage");
Log.e(TAG, "" + e.getMessage());
if (e.getMessage() != null && e.getMessage() != "")
Toast.makeText(getApplicationContext(),
e.getMessage(), Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(),
"could not reach server", Toast.LENGTH_LONG).show();
else if (e.getCause() != null)
Log.e(TAG, "e.getCause");
Log.e(TAG, "" + e.getCause().getMessage());
if (e.getCause().getMessage() != null && e.getCause().getMessage() != "")
Toast.makeText(getApplicationContext(),
e.getCause().getMessage(), Toast.LENGTH_LONG).show();
else
Toast.makeText(getApplicationContext(),
"could not reach server", Toast.LENGTH_LONG).show();
【讨论】:
【参考方案8】:我手动处理这个问题:
下载Volley library from github并添加到AndroidStudio项目中
转到com.android.volley.toolbox.HurlStack
类
在performRequest
方法中找到setConnectionParametersForRequest(connection, request);
行
最后在setConnectionParametersForRequest(connection, request);
行添加以下代码:
// for avoiding this exception : No authentication challenges found try connection.getResponseCode(); catch (IOException e) e.printStackTrace();
【讨论】:
以上是关于error.networkResponse 为空时 Android Volley 中的 Http 状态代码的主要内容,如果未能解决你的问题,请参考以下文章
当 listView 为空时,setEmptyView 不显示图像