热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

AsyncHttpClient关于失败响应和网络超时的响应的处理

安卓平台的开源网络通信框架android-async-http是当前比较流行的网络通信框架。它简洁易用,功能强大。这段时间在使用过程中遇到网络超时的情况,需要对超时做出响应处理。为了厘清处理

安卓平台的开源网络通信框架android-async-http是当前比较流行的网络通信框架。它简洁易用,功能强大。这段时间在使用过程中遇到网络超时的情况,需要对超时做出响应处理。为了厘清处理流程,只好走读了一下部分源码。

我们直接入正题,发起请求的动作是AsyncHttpRequest类的run方法:

@Override
    public void run() {
        if (isCancelled()) {
            return;
        }

        if (responseHandler != null) {
            responseHandler.sendStartMessage();
        }

        if (isCancelled()) {
            return;
        }

        try {
            makeRequestWithRetries();
        } catch (IOException e) {
            if (!isCancelled() && responseHandler != null) {
                responseHandler.sendFailureMessage(0, null, null, e);
            } else {
                Log.e("AsyncHttpRequest", "makeRequestWithRetries returned error, but handler is null", e);
            }
        }

        if (isCancelled()) {
            return;
        }

        if (responseHandler != null) {
            responseHandler.sendFinishMessage();
        }

        isFinished = true;
    }

不算太复杂,请求发起时,首先是调用 makeRequestWithRetries(); 继续看该方法的源码

private void makeRequestWithRetries() throws IOException {
        boolean retry = true;
        IOException cause = null;
        HttpRequestRetryHandler retryHandler = client.getHttpRequestRetryHandler();
        try {
            while (retry) {
                try {
                    makeRequest();
                    return;
                } catch (UnknownHostException e) {
                    // switching between WI-FI and mobile data networks can cause a retry which then results in an UnknownHostException
                    // while the WI-FI is initialising. The retry logic will be invoked here, if this is NOT the first retry
                    // (to assist in genuine cases of unknown host) which seems better than outright failure
                    cause = new IOException("UnknownHostException exception: " + e.getMessage());
                    retry = (executionCount > 0) && retryHandler.retryRequest(cause, ++executionCount, context);
                } catch (NullPointerException e) {
                    // there's a bug in HttpClient 4.0.x that on some occasions causes
                    // DefaultRequestExecutor to throw an NPE, see
                    // http://code.google.com/p/android/issues/detail?id=5255
                    cause = new IOException("NPE in HttpClient: " + e.getMessage());
                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
                } catch (IOException e) {
                    if (isCancelled()) {
                        // Eating exception, as the request was cancelled
                        return;
                    }
                    cause = e;
                    retry = retryHandler.retryRequest(cause, ++executionCount, context);
                }
                if (retry && (responseHandler != null)) {
                    responseHandler.sendRetryMessage(executionCount);
                }
            }
        } catch (Exception e) {
            // catch anything else to ensure failure message is propagated
            Log.e("AsyncHttpRequest", "Unhandled exception origin cause", e);
            cause = new IOException("Unhandled exception: " + e.getMessage());
        }

        // cleaned up to throw IOException
        throw (cause);
    }
顾名思义,这里包含了请求和重试两个操作。所谓重试,就是服务器在一定时间内没有对请求作出响应时,客户端会根据配置进行一定次数的重试。重试这部分我们先不探讨。注意到这段源码中调用的请求:
 
 
makeRequest();
private void makeRequest() throws IOException {
        if (isCancelled()) {
            return;
        }
        // Fixes #115
        if (request.getURI().getScheme() == null) {
            // subclass of IOException so processed in the caller
            throw new MalformedURLException("No valid URI scheme was provided");
        }

        HttpResponse respOnse= client.execute(request, context);

        if (!isCancelled() && responseHandler != null) {
            responseHandler.sendResponseMessage(response);
        }
    }

我们知道在android代码中,大部分的通知处理都是通过Handler来实现的。android-async-http框架也不例外。当服务端对网络请求的响应回来时,客户端是通过Handler的消息机制来通知代码做相应的处理的。

显而易见,makeRequest()方法执行,在请求返回的结果 HttpResponse到达客户端时,AsyncHttpRequest会以消息的方式通知responseHandler对象进行后续处理。

private final ResponseHandlerInterface responseHandler;

实际的应答,则是由ResponseHandlerInterface接口的一个实现类AsyncHttpResponseHandler来进行处理的。没错,其实本文的第一段代码,AsyncHttpRequest类的run方法中发送失败消息的这句

responseHandler.sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable error)
就是在给这个AsyncHttpResponseHandler类的对象发消息了。

先不跑题,继续追踪这句代码

responseHandler.sendResponseMessage(response);
@Override
    public void sendResponseMessage(HttpResponse response) throws IOException {
        // do not process if request has been cancelled
        if (!Thread.currentThread().isInterrupted()) {
            StatusLine status = response.getStatusLine();
            byte[] responseBody;
            respOnseBody= getResponseData(response.getEntity());
            // additional cancellation check as getResponseData() can take non-zero time to process
            if (!Thread.currentThread().isInterrupted()) {
                if (status.getStatusCode() >= 300) {
                    sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()));
                } else {
                    sendSuccessMessage(status.getStatusCode(), response.getAllHeaders(), responseBody);
                }
            }
        }
    }

可以看到,android-async-http框架认为,只要返回的状态码是300或者更大的数,那么就认为本次请求是失败的。(本文末尾我们附上一段http各种主要状态码的说明)
 
 

接下来开始走请求失败的处理流程。首先是发消息

sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase()));

继续往下看:

final public void sendFailureMessage(int statusCode, Header[] headers, byte[] responseBody, Throwable throwable) {
        sendMessage(obtainMessage(FAILURE_MESSAGE, new Object[]{statusCode, headers, responseBody, throwable}));
    }

消息发出去之后,接下来就是处理的过程了。还在AsyncHttpResponseHandler.java 里面,
我们看它的这段源码:

protected void handleMessage(Message message) {
        Object[] response;

        switch (message.what) {
            case SUCCESS_MESSAGE:
                respOnse= (Object[]) message.obj;
                if (response != null && response.length >= 3) {
                    onSuccess((Integer) response[0], (Header[]) response[1], (byte[]) response[2]);
                } else {
                    Log.e(LOG_TAG, "SUCCESS_MESSAGE didn't got enough params");
                }
                break;
            case FAILURE_MESSAGE:
                respOnse= (Object[]) message.obj;
                if (response != null && response.length >= 4) {
                    onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]);
                } else {
                    Log.e(LOG_TAG, "FAILURE_MESSAGE didn't got enough params");
                }
                break;
            case START_MESSAGE:
                onStart();
                break;
            case FINISH_MESSAGE:
                onFinish();
                break;
            case PROGRESS_MESSAGE:
                respOnse= (Object[]) message.obj;
                if (response != null && response.length >= 2) {
                    try {
                        onProgress((Integer) response[0], (Integer) response[1]);
                    } catch (Throwable t) {
                        Log.e(LOG_TAG, "custom onProgress contains an error", t);
                    }
                } else {
                    Log.e(LOG_TAG, "PROGRESS_MESSAGE didn't got enough params");
                }
                break;
            case RETRY_MESSAGE:
                respOnse= (Object[]) message.obj;
                if (response != null && response.length == 1)
                    onRetry((Integer) response[0]);
                else
                    Log.e(LOG_TAG, "RETRY_MESSAGE didn't get enough params");
                break;
            case CANCEL_MESSAGE:
                onCancel();
                break;
        }
    }

这是一个常规的Handle对消息的处理模式。

我们今天讨论的主题是失败和超时的处理。注意网络超时发生时的状态码是4xx或者5xx,对android-async-http来说,都是走请求失败的流程: 

if (status.getStatusCode() >= 300) {
sendFailureMessage(status.getStatusCode(), response.getAllHeaders(), responseBody, new HttpResponseException(status.getStatusCode(), status.getReasonPhrase())); }
 通过对sendFailureMessage()的追踪,我们已经知道发送的消息状态码是 FAILURE_MESSAGE。这是一个常量 
 

protected static final int FAILURE_MESSAGE = 1;

在handleMessage()方法中,对这个消息状态的处理是调用了

onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]);

public abstract void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error);

发现这是一个未实现的抽象方法。我们把注意力转移到AsyncHttpResponseHandler类的子类中去。

这里涉及到具体项目中使用哪种数据格式来传递服务器和客户端的通信。我们用当前使用比较广泛多Json格式来作为讨论的例子。android-async-http框架实现了继承AsyncHttpResponseHandler的一个使用Json格式数据的子类:JsonHttpResponseHandler 当然该类首先继承了TextHttpResponseHandler,接着TextHttpResponseHandler再继承AsyncHttpResponseHandler。考虑到中间的这一层TextHttpResponseHandler实际使用中关注的比较少,我们一笔带过不做讨论。

主要还是看JsonHttpResponseHandler:



/**
     * Returns when request failed
     *
     * @param statusCode    http response status line
     * @param headers       response headers if any
     * @param throwable     throwable describing the way request failed
     * @param errorResponse parsed response if any
     */
    public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse) {

    }

    /**
     * Returns when request failed
     *
     * @param statusCode    http response status line
     * @param headers       response headers if any
     * @param throwable     throwable describing the way request failed
     * @param errorResponse parsed response if any
     */
    public void onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse) {

    }

    @Override
    public void onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable) {

    }
 @Override
    public final void onFailure(final int statusCode, final Header[] headers, final byte[] responseBytes, final Throwable throwable) {
        if (responseBytes != null) {
    Runnable parser = new Runnable() {
                @Override
                public void run() {
                    try {
                        final Object jsOnResponse= parseResponse(responseBytes);
                        postRunnable(new Runnable() {
                            @Override
                            public void run() {
                                if (jsonResponse instanceof JSONObject) {
                                    onFailure(statusCode, headers, throwable, (JSONObject) jsonResponse);
                                } else if (jsonResponse instanceof JSONArray) {
                                    onFailure(statusCode, headers, throwable, (JSONArray) jsonResponse);
                                } else if (jsonResponse instanceof String) {
                                    onFailure(statusCode, headers, (String) jsonResponse, throwable);
                                } else {
                                    onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
                                }
                            }
                        });


                    } catch (final JSONException ex) {
                        postRunnable(new Runnable() {
                            @Override
                            public void run() {
                                onFailure(statusCode, headers, ex, (JSONObject) null);
                            }
                        });


                    }
                }
    };
    if (!getUseSynchronousMode())
	new Thread(parser).start();
    else // In synchronous mode everything should be run on one thread
	parser.run();
        } else {
            Log.v(LOG_TAG, "response body is null, calling onFailure(Throwable, JSONObject)");
            onFailure(statusCode, headers, throwable, (JSONObject) null);
        }
    }
一共有4个方法,前面3个空方法,最后一个做了实现。我们再重温一下之前在其父类(其实是父类的父类)AsyncHttpResponseHandler当中看到对onFailure的调用:

onFailure((Integer) response[0], (Header[]) response[1], (byte[]) response[2], (Throwable) response[3]);
实际上调用的,就是JsonHttpResponseHandler中已经实现了的这个onFailure()。

在这个方法中,先对返回的数据进行组装:

final Object jsOnResponse= parseResponse(responseBytes);
转成 JSONObject 或者 JSONArray。根据数据格式,调用相应的

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse)

或者

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse)

如果无法转化为上述两种格式,则直接调用接收String类型的

onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable)

而在整个过程中,一旦出现异常情况抛出异常,则会默认调用

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse)

而这3onFailure()方法在JsonHttpResponseHandler类中都是没有实现的,我们在继承JsonHttpResponseHandler的子类,实现自己的Handler来处理网络请求结果时,就需要重写onFailure()了。

另外,同样在JsonHttpResponseHandler类中,我们还看到了网络请求调用成功时的处理方法onSuccess(),我们不妨看一下这个方法:

@Override
    public final void onSuccess(final int statusCode, final Header[] headers, final byte[] responseBytes) {
        if (statusCode != HttpStatus.SC_NO_CONTENT) {
	    Runnable parser = new Runnable() {
                @Override
                public void run() {
                    try {
                        final Object jsOnResponse= parseResponse(responseBytes);
                        postRunnable(new Runnable() {
                            @Override
                            public void run() {
                                if (jsonResponse instanceof JSONObject) {
                                    onSuccess(statusCode, headers, (JSONObject) jsonResponse);
                                } else if (jsonResponse instanceof JSONArray) {
                                    onSuccess(statusCode, headers, (JSONArray) jsonResponse);
                                } else if (jsonResponse instanceof String) {
                                    onFailure(statusCode, headers, (String) jsonResponse, new JSONException("Response cannot be parsed as JSON data"));
                                } else {
                                    onFailure(statusCode, headers, new JSONException("Unexpected response type " + jsonResponse.getClass().getName()), (JSONObject) null);
                                }

                            }
                        });
                    } catch (final JSONException ex) {
                        postRunnable(new Runnable() {
                            @Override
                            public void run() {
                                onFailure(statusCode, headers, ex, (JSONObject) null);
                            }
                        });
                    }
                }
	    };
	    if (!getUseSynchronousMode())
		new Thread(parser).start();
	    else // In synchronous mode everything should be run on one thread
		parser.run();
        } else {
            onSuccess(statusCode, headers, new JSONObject());
        }
    }

聪明的你一定发现了,当网络处理请求成功返回时,如果对结果的转换失败,结果数据格式不符合Json结构时,将会默认调用String类型入参的onFailure():

onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable)


而如果结果连String类型都不满足,或者直接发生异常抛出时,则默认调用JSONObject类型入参的onFailure():

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse).


分析到这里,我们的结论就出来了:在android-async-http框架中,如果需要对网络请求超时或者请求失败的情况发生时,系统将会调用onFailure()方法进行响应。因此我们需要重写onFailure()方法,并且至少需要重写

onFailure(int statusCode, Header[] headers, String responseString, Throwable throwable)

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse)

因为这两个方法可能会在不同情况下被系统调用。至于JSONArray类型入参的onFailure()方法,则视你的代码而定。如果你通信数据中有返回JSONArray类型的数据,那么你还需要重写

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONArray errorResponse)

来进行你所需要的处理。

当然了,最简单的处理方式,就是直接提示一句话,例如:

onFailure(int statusCode, Header[] headers, Throwable throwable, JSONObject errorResponse)

Toast.makeText(context, "网络超时,请稍候重试。", Toast.LENGTH_LONG);

这里只是举个简单例子,你可以根据项目实际需要,进行恰当的超时处理或者失败处理。


附录:常见网络响应状态码的含义:

HTTP: Status200– 服务器成功返回网页

HTTP: Status404– 请求的网页不存在

HTTP: Status503– 服务不可用

———————————————————————————————— 

HTTP: Status 1xx(临时响应)

->表示临时响应并需要请求者继续执行操作的状态代码。 

详细代码及说明:

HTTP: Status 100(继续)

-> 请求者应当继续提出请求。 服务器返回此代码表示已收到请求的第一部分,正在等待其余部分。

HTTP: Status 101(切换协议)

-> 请求者已要求服务器切换协议,服务器已确认并准备切换。 

—————————————————————————————————— 

HTTP Status 2xx(成功)

->表示成功处理了请求的状态代码; 

HTTP Status 200(成功)

-> 服务器已成功处理了请求。 通常,这表示服务器提供了请求的网页。

HTTP Status 201(已创建)

-> 请求成功并且服务器创建了新的资源。

HTTP Status 202(已接受)

-> 服务器已接受请求,但尚未处理。

HTTP Status 203(非授权信息)

-> 服务器已成功处理了请求,但返回的信息可能来自另一来源。

HTTP Status 204(无内容)

-> 服务器成功处理了请求,但没有返回任何内容。

HTTP Status 205(重置内容)

-> 服务器成功处理了请求,但没有返回任何内容。

HTTP Status 206(部分内容)

-> 服务器成功处理了部分 GET 请求。

HTTP Status 3xx(重定向)

->这要完成请求,需要进一步操作。通常,这些状态码用来重定向。

——————————————————————————————————

HTTP Status 300(多种选择) 

->针对请求,服务器可执行多种操作。服务器可根据请求者 (user agent) 选择一项操作,或提供操作列表供请求者选择。

HTTP Status 301(永久移动) 

->请求的网页已永久移动到新位置。服务器返回此响应(对 GET 或 HEAD 请求的响应)时,会自动将请求者转到新位置。您应使用此代码告诉 Googlebot 某个网页或网站已永久移动到新位置。

HTTP Status 302(临时移动) 

->服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个网页或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

HTTP Status 303(查看其他位置)

-> 请求者应当对不同的位置使用单独的 GET 请求来检索响应时,服务器返回此代码。对于除 HEAD 之外的所有请求,服务器会自动转到其他位置。

HTTP Status 304(没有修改) 

->自从上次请求后,请求的网页未修改过。服务器返回此响应时,不会返回网页内容。如果网页自请求者上次请求后再也没有更改过,您应将服务器配置为返回此响应(称为 If-Modified-Since HTTP 标头)。服务器可以告诉 Googlebot 自从上次抓取后网页没有变更,进而节省带宽和开销。

HTTP Status 305(使用代理)

-> 请求者只能使用代理访问请求的网页。如果服务器返回此响应,还表示请求者应使用代理。 

HTTP Status 307(使用代理)

-> 服务器目前从不同位置的网页响应请求,但请求者应继续使用原有位置来响应以后的请求。此代码与响应 GET 和 HEAD 请求的 301 代码类似,会自动将请求者转到不同的位置,但您不应使用此代码来告诉 Googlebot 某个页面或网站已经移动,因为 Googlebot 会继续抓取原有位置并编制索引。

————————————————————————————————————

HTTP Status 4xx(请求错误)

->这些状态代码表示请求可能出错,妨碍了服务器的处理。 

详细代码说明:

HTTP Status 400(错误请求) 

->服务器不理解请求的语法。

HTTP Status 401(未授权) 

->请求要求身份验证。 对于需要登录的网页,服务器可能返回此响应。

HTTP Status 403(禁止)

-> 服务器拒绝请求。

HTTP Status 404(未找到) 

->服务器找不到请求的网页。

HTTP Status 405(方法禁用) 

->禁用请求中指定的方法。

HTTP Status 406(不接受) 

->无法使用请求的内容特性响应请求的网页。

HTTP Status 407(需要代理授权) 

->此状态代码与 401(未授权)类似,但指定请求者应当授权使用代理。

HTTP Status 408(请求超时) 

->服务器等候请求时发生超时。

HTTP Status 409(冲突) 

->服务器在完成请求时发生冲突。 服务器必须在响应中包含有关冲突的信息。

HTTP Status 410(已删除)

-> 如果请求的资源已永久删除,服务器就会返回此响应。

HTTP Status 411(需要有效长度) 

->服务器不接受不含有效内容长度标头字段的请求。

HTTP Status 412(未满足前提条件) 

->服务器未满足请求者在请求中设置的其中一个前提条件。

HTTP Status 413(请求实体过大) 

->服务器无法处理请求,因为请求实体过大,超出服务器的处理能力。

HTTP Status 414(请求的 URI 过长) 请求的 URI(通常为网址)过长,服务器无法处理。

HTTP Status 415(不支持的媒体类型) 

->请求的格式不受请求页面的支持。

HTTP Status 416(请求范围不符合要求) 

->如果页面无法提供请求的范围,则服务器会返回此状态代码。

HTTP Status 417(未满足期望值) 

->服务器未满足”期望”请求标头字段的要求。 

———————————————————————————————————

HTTP Status 5xx(服务器错误)

->这些状态代码表示服务器在尝试处理请求时发生内部错误。 这些错误可能是服务器本身的错误,而不是请求出错。

代码详细及说明:

HTTP Status 500(服务器内部错误) 

->服务器遇到错误,无法完成请求。

HTTP Status 501(尚未实施) 

->服务器不具备完成请求的功能。 例如,服务器无法识别请求方法时可能会返回此代码。

HTTP Status 502(错误网关) 

->服务器作为网关或代理,从上游服务器收到无效响应。

HTTP Status 503(服务不可用)

-> 服务器目前无法使用(由于超载或停机维护)。 通常,这只是暂时状态。

HTTP Status 504(网关超时) 

->服务器作为网关或代理,但是没有及时从上游服务器收到请求。

HTTP Status 505(HTTP 版本不受支持)

-> 服务器不支持请求中所用的 HTTP 协议版本。








推荐阅读
author-avatar
hytyj_989
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有