Some Android Issues

最近写了些代码,遇到一些Android的坑…真的坑

1.Android HttpURLConnection response code 204/200

Android 中 HttpURLConnection 的 response code 并不可靠,譬如服务器明明返回了HTTP 204,然而你调用 urlConnection.getResponseCode 得到的却是 HTTP 200…(如果用Java SE运行得到的是正常的204)

查看了一下 Android 源码中 isCaptivePortal 函数(/frameworks/base/services/core/java/com/android/server/connectivity/NetworkMonitor.java),其中处理到了 HTTP response code,

580    /**
581     * Do a URL fetch on a known server to see if we get the data we expect.
582     * Returns HTTP response code.
583     */
584    private int isCaptivePortal() {
585        if (!mIsCaptivePortalCheckEnabled) return 204;
586
587        HttpURLConnection urlConnection = null;
588        int httpResponseCode = 599;
589        try {
590            URL url = new URL("http", mServer, "/generate_204");
591            if (DBG) {
592                log("Checking " + url.toString() + " on " +
593                        mNetworkAgentInfo.networkInfo.getExtraInfo());
594            }
595            urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url);
596            urlConnection.setInstanceFollowRedirects(false);
597            urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS);
598            urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS);
599            urlConnection.setUseCaches(false);
600
601            // Time how long it takes to get a response to our request
602            long requestTimestamp = SystemClock.elapsedRealtime();
603
604            urlConnection.getInputStream();
605
606            // Time how long it takes to get a response to our request
607            long responseTimestamp = SystemClock.elapsedRealtime();
608
609            httpResponseCode = urlConnection.getResponseCode();
610            if (DBG) {
611                log("isCaptivePortal: ret=" + httpResponseCode +
612                        " headers=" + urlConnection.getHeaderFields());
613            }
614            // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive
615            // portal.  The only example of this seen so far was a captive portal.  For
616            // the time being go with prior behavior of assuming it's not a captive
617            // portal.  If it is considered a captive portal, a different sign-in URL
618            // is needed (i.e. can't browse a 204).  This could be the result of an HTTP
619            // proxy server.
620
621            // Consider 200 response with "Content-length=0" to not be a captive portal.
622            // There's no point in considering this a captive portal as the user cannot
623            // sign-in to an empty page.  Probably the result of a broken transparent proxy.
624            // See http://b/9972012.
625            if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) {
626                if (DBG) log("Empty 200 response interpreted as 204 response.");
627                httpResponseCode = 204;
628            }
629
630            sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode == 204,
631                    requestTimestamp, responseTimestamp);
632        } catch (IOException e) {
633            if (DBG) log("Probably not a portal: exception " + e);
634            if (httpResponseCode == 599) {
635                // TODO: Ping gateway and DNS server and log results.
636            }
637        } finally {
638            if (urlConnection != null) {
639                urlConnection.disconnect();
640            }
641        }
642        return httpResponseCode;
643    }

 
从第 625 行可以看出,Android 源码中判断当 response code=200 且 content-length 为 0 时,手动将 response code 置为 204。嗯,我也这样做,然后该问题就解决了。

总之,Android 中 HttpURLConnection 和 Java SE 中运行不大一致,除此之外,譬如如果你是 POST 请求,却设置了 urlConnection.setDoOutput(false),将会影响响应头。

但是,至于为什么出现这些情况,暂时并没细究,改日有空再看看 Android 相关源码。

2. Android 21+ Network bind with using vpn cause operating not permitted exception

Android 21+ 因为开始支持网络多连接,因此在 android.net.ConnectivityManager 中新增了绑定网络到当前线程或者绑定到具体 socket 的方法,

API21 connectivityManager.setProcessDefaultNetwork(network)
API23 connectivityManager.bindProcessToNetwork(network)

然而使用时,对于某 type 为 ConnectivityManager.TYPE_WIFI 的 network 调用以上方法绑定后,手机再连接 VPN 时,网络请求将会出现 java.net.SocketException "operating not permitted" 翻阅 Android API 文档发现,Android 21 中新增了一种网络类型VPN,想必与此有关,继续翻阅 Android 源码,发现了Android 在处理网络类型时,把 vpn 作为优先处理的网络类型,一旦连接了 vpn,系统将会将其作为当前活跃网络。此时类型为 wifi 的 network 对象将会被限制 socket 创建。

共有 0 条评论

发表回复

您的电子邮箱地址不会被公开。 必填项已用 * 标注