在网络(移动 -> Wifi)更改时维护套接字连接的最佳实践



我有一个android应用程序,它连接到一个服务器使用套接字连接保持开放,而应用程序是活跃的。如果手机处于非活动状态(锁屏)或用户按下home键,应用程序将关闭套接字连接,并在应用程序再次可见时重新打开该连接。

这种模式在我们拥有的大多数安卓手机(大约15台设备)上都很有效,但摩托罗拉Milestone、Defy、SE Xperia Arc和LG Optimus One需要很长时间(>10秒)才能检测到Wifi是否可用并连接到它。因此,为了等待最佳的网络连接,我使用以下代码(在打开到服务器的套接字之前):

public static boolean waitNetworkConnection(Context context, int retries) {
    ConnectivityManager cm = (ConnectivityManager) 
                       context.getSystemService(Context.CONNECTIVITY_SERVICE);
    NetworkInfo ni = getNetworkToTest(cm);
    if (ni == null || !ni.isConnected()) {
        // sleep a short while to allow system to switch connecting networks.
        Tools.sleep(1000);
        int counter = 0;
        while (counter < retries && (ni == null || (ni.isAvailable() && 
               !ni.isConnected()))) {
            Tools.sleep(500);
            ni = getNetworkToTest(cm);
            counter++;
        }
    }
    return (cm.getActiveNetworkInfo() != null && 
            cm.getActiveNetworkInfo().isConnected());
}

和这个方法(由上面的一个使用)来获得要测试的连接,如果有一个(不一定连接)可用,它更喜欢wifi连接:

private static NetworkInfo getNetworkToTest(ConnectivityManager cm) {
    NetworkInfo[] nis = cm.getAllNetworkInfo();
    NetworkInfo ni = cm.getActiveNetworkInfo();
    for (int i = 0; i < nis.length; i++) {
        if (nis[i].getType() == 1 /* Wifi */ && nis[i].isAvailable()) {
            ni = nis[i];
            return(ni);
        }
    }
    return(ni);
}

这适用于大多数设备,但对于提到的那些,这经常失败,这个方法告诉我使用移动网络连接,设备切换连接类型,而我打开一个套接字连接,导致SocketException非常通用的错误消息,所以我无法确定套接字连接是由这个问题引起的,还是因为其他一些网络错误。

简单地重试也不能解决这个问题,因为这会破坏对其他网络错误的处理,因为它需要很长时间才能检测到套接字超时(因为它被检查了两次)。

有其他人遇到这个问题(非常慢连接到Wifi),并有解决方案吗?

是的,这是一个棘手的问题。一种选择是使用BroadcastReceiver等待正确的网络状态广播。

如下所述:如何在Android中检测WIFI连接是否已经建立?

这里:如何在Android中监控网络连接状态?

有一个叫做droidfu的项目,它有一个HTTP包装器,可以绕过wi-fi/3g问题。

下面是BetterHttpRequestBase类的代码片段:
public BetterHttpResponse send() throws ConnectException {
    BetterHttpRequestRetryHandler retryHandler = new BetterHttpRequestRetryHandler(maxRetries);
    // tell HttpClient to user our own retry handler
    httpClient.setHttpRequestRetryHandler(retryHandler);
    HttpContext context = new BasicHttpContext();
    // Grab a coffee now and lean back, I'm not good at explaining stuff. This code realizes
    // a second retry layer on top of HttpClient. Rationale: HttpClient.execute sometimes craps
    // out even *before* the HttpRequestRetryHandler set above is called, e.g. on a
    // "Network unreachable" SocketException, which can happen when failing over from Wi-Fi to
    // 3G or vice versa. Hence, we catch these exceptions, feed it through the same retry
    // decision method *again*, and align the execution count along the way.
    boolean retry = true;
    IOException cause = null;
    while (retry) {
        try {
            return httpClient.execute(request, this, context);
        } catch (IOException e) {
            cause = e;
            retry = retryRequest(retryHandler, cause, 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 = retryRequest(retryHandler, cause, context);
        } finally {
            // if timeout was changed with this request using withTimeout(), reset it
            if (oldTimeout != BetterHttp.getSocketTimeout()) {
                BetterHttp.setSocketTimeout(oldTimeout);
            }
        }
    }
    // no retries left, crap out with exception
    ConnectException ex = new ConnectException();
    ex.initCause(cause);
    throw ex;
}

最新更新