android网络的评分机制、连接国内ap wifi不回连问题
来源:互联网 发布:php qrcode生成二维码 编辑:程序博客网 时间:2024/05/16 01:18
前言:
本文介绍了android下网络的评分机制,同时分析wifi连接国内ap时,重新打开wifi后,wifi不回连ap的问题,并提供解决方法。
android下可以有多种网络存在,如:wifi、mobile network、ethernet、bt-pan。而对于上层应用来说,只会看到一个连通的网络,在多个网络同时存在的情况下,android就需要一套评分机制来选择一个当前使用的网络,当那个网络的分值高时,就优先使用那个网络。Android下各种网络的分值在NetworkAgentInfo.java中管理,保存在currentScore中,各种网络初始化时会设置自己的分值。
Wifi初始分值为60(WifiStateMachine.java);
Ethernet初始分值为70(EthernetNetworkFactory.java);
Mobile network初始分值为50(DataConnection.java);
bt-pan初始分值为69(BluetoothTetheringNetworkFactory.java):
bt-pan的分值比wifi还高,这比较奇怪,已知的bt-pan网速都比较慢,google出于什么原因设计成这样?就不清楚了。
在实际运行中,还会根据网络的实时状态调整分值。
ethernet根据网卡的up和down状态,把分值设置为70(NETWORK_SCORE)或0。
(EthernetNetworkFactory.java)mNetworkAgent.sendNetworkScore(mLinkUp? NETWORK_SCORE : 0);
而wifi的分值还跟信号状态、当前数据速率等一系列因素有关:
Wifi的分值计算在WifiStateMachine.java的calculateWifiScore函数中进行,初始计算的基础分值为:int score = 56;根据wifi网络的状态,进行小的加减,最后,如果分值大于60(NetworkAgent.WIFI_BASE_SCORE),就把分值设置为60。
上面设置的分值计算,只考虑网络是否连接好,至于连接的网络是否能连接上internet,还没加入考虑。如wifi已经连接上ap,而该ap是否能连接上internet,就没在这里考虑。
上面设置的网络分值,是最终保存在NetworkAgentInfo类中的分值,而在获取网络分值时,还会根据网络是否连接上internet,是否用户指定使用的网络,返回经过计算后的分值。
NetworkAgentInfo.java private int getCurrentScore(boolean pretendValidated) { // TODO: We may want to refactor this into a NetworkScore class that takes a base score from // the NetworkAgent and signals from the NetworkAgent and uses those signals to modify the // score. The NetworkScore class would provide a nice place to centralize score constants // so they are not scattered about the transports. int score = currentScore; if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY; if (score < 0) score = 0; if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE; return score; }
如果需要根据网络是否连通internet,就进行if (!everValidated && !pretendValidated) score -= UNVALIDATED_SCORE_PENALTY(40);处理,当网络与internet不通时,分值减去40。如果是用户指定使用的网络,直接返回分值if (networkMisc.explicitlySelected) score = EXPLICITLY_SELECTED_NETWORK_SCORE;(100)。pretendValidated参数确定是否认为当前网络就是与internet连通的。everValidated表示当前网络与internet是否连通的标志。networkMisc.explicitlySelected为用户是否指定使用当前网络的标志,在用户手动连接ap的时候,该标志就会被设置,所以这时候的分值比ethernet还高,就会优先选择wifi作为首选网络。但在开关wifi后,自动连接上ap时,该标志就不会设置。最后,分析一下everValidated标志是由哪里设置的,这里以wifi作为例子分析。在连接wifi的过程中,当WifiStateMachine进入L2ConnectedState时,就会创建:
mNetworkAgent = new WifiNetworkAgent(getHandler().getLooper(), mContext, "WifiNetworkAgent", mNetworkInfo, mNetworkCapabilitiesFilter, mLinkProperties, 60);
在WifiNetworkAgent初始化时,把everValidated设置为false,而当网络断开连接时,就会注销WifiNetworkAgent:
if (mNetworkAgent != null) { mNetworkAgent.sendNetworkInfo(mNetworkInfo); mNetworkAgent = null; }
所以在每次连接网络后,都会重新设置everValidated,断开网络时就会清除。创建WifiNetworkAgent时,在WifiNetworkAgent内创建了NetworkMonitor, NetworkMonitor就是一个检测网络的状态机,状态机包含下面状态,初始状态为mDefaultState,检测网络是否与internet连通就是在该状态机中实现。
addState(mDefaultState); addState(mOfflineState, mDefaultState); addState(mValidatedState, mDefaultState); addState(mMaybeNotifyState, mDefaultState); addState(mEvaluatingState, mMaybeNotifyState); addState(mCaptivePortalState, mMaybeNotifyState); addState(mLingeringState, mDefaultState); setInitialState(mDefaultState);
当网络与internet连通时,NetworkMonitor所走的状态机如下图:
当进入mValidatedState时,就会给connectivity发送消息: mConnectivityServiceHandler.sendMessage(obtainMessage(EVENT_NETWORK_TESTED,
NETWORK_TEST_RESULT_VALID, mNetwork3gTestResultIsFake,
mNetworkAgentInfo));
connectivity对NETWORK_TEST_RESULT_VALID消息进行处理时,就会设置everValidated为true。
检测网络是否连通的代码如下:
android\frameworks\base\services\core\java\com\android\server\connectivity\ NetworkMonitor.java public boolean processMessage(Message message) { if (DBG) log(getName() + message.toString()); switch (message.what) { case CMD_REEVALUATE: if (message.arg1 != mReevaluateToken) return HANDLED; // Don't bother validating networks that don't satisify the default request. // This includes: // - VPNs which can be considered explicitly desired by the user and the // user's desire trumps whether the network validates. // - Networks that don't provide internet access. It's unclear how to // validate such networks. // - Untrusted networks. It's unsafe to prompt the user to sign-in to // such networks and the user didn't express interest in connecting to // such networks (an app did) so the user may be unhappily surprised when // asked to sign-in to a network they didn't want to connect to in the // first place. Validation could be done to adjust the network scores // however these networks are app-requested and may not be intended for // general usage, in which case general validation may not be an accurate // measure of the network's quality. Only the app knows how to evaluate // the network so don't bother validating here. Furthermore sending HTTP // packets over the network may be undesirable, for example an extremely // expensive metered network, or unwanted leaking of the User Agent string. if (!mDefaultRequest.networkCapabilities.satisfiedByNetworkCapabilities( mNetworkAgentInfo.networkCapabilities)) { transitionTo(mValidatedState); return HANDLED; } // Note: This call to isCaptivePortal() could take up to a minute. Resolving the // server's IP addresses could hit the DNS timeout, and attempting connections // to each of the server's several IP addresses (currently one IPv4 and one // IPv6) could each take SOCKET_TIMEOUT_MS. During this time this StateMachine // will be unresponsive. isCaptivePortal() could be executed on another Thread // if this is found to cause problems. int httpResponseCode = isCaptivePortal(); if (httpResponseCode == 204) { transitionTo(mValidatedState); } else if (httpResponseCode >= 200 && httpResponseCode <= 399) { transitionTo(mCaptivePortalState); } else if (++mAttempt > mMaxAttempts) { transitionTo(mOfflineState); } else if (mReevaluateDelayMs >= 0) { Message msg = obtainMessage(CMD_REEVALUATE, ++mReevaluateToken, 0); sendMessageDelayed(msg, mReevaluateDelayMs); } return HANDLED;android\frameworks\base\services\core\java\com\android\server\connectivity\ NetworkMonitor.java /** * Do a URL fetch on a known server to see if we get the data we expect. * Returns HTTP response code. */ private int isCaptivePortal() { HttpURLConnection urlConnection = null; int httpResponseCode = 599; try { URL url = new URL("http", mServer, "/generate_204"); // On networks with a PAC instead of fetching a URL that should result in a 204 // reponse, we instead simply fetch the PAC script. This is done for a few reasons: // 1. At present our PAC code does not yet handle multiple PACs on multiple networks // until something like https://android-review.googlesource.com/#/c/115180/ lands. // Network.openConnection() will ignore network-specific PACs and instead fetch // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with // NO_PROXY is the fetch of the PAC itself. // 2. To proxy the generate_204 fetch through a PAC would require a number of things // happen before the fetch can commence, namely: // a) the PAC script be fetched // b) a PAC script resolver service be fired up and resolve mServer // Network validation could be delayed until these prerequisities are satisifed or // could simply be left to race them. Neither is an optimal solution. // 3. PAC scripts are sometimes used to block or restrict Internet access and may in // fact block fetching of the generate_204 URL which would lead to false negative // results for network validation. boolean fetchPac = false; { final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy(); if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { url = new URL(proxyInfo.getPacFileUrl().toString()); fetchPac = true; } } if (DBG) { log("Checking " + url.toString() + " on " + mNetworkAgentInfo.networkInfo.getExtraInfo()); } urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url); urlConnection.setInstanceFollowRedirects(fetchPac); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); // Time how long it takes to get a response to our request long requestTimestamp = SystemClock.elapsedRealtime(); urlConnection.getInputStream(); // Time how long it takes to get a response to our request long responseTimestamp = SystemClock.elapsedRealtime(); httpResponseCode = urlConnection.getResponseCode(); if (DBG) { log("isCaptivePortal: ret=" + httpResponseCode + " headers=" + urlConnection.getHeaderFields()); } // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive // portal. The only example of this seen so far was a captive portal. For // the time being go with prior behavior of assuming it's not a captive // portal. If it is considered a captive portal, a different sign-in URL // is needed (i.e. can't browse a 204). This could be the result of an HTTP // proxy server. // Consider 200 response with "Content-length=0" to not be a captive portal. // There's no point in considering this a captive portal as the user cannot // sign-in to an empty page. Probably the result of a broken transparent proxy. // See http://b/9972012. if (httpResponseCode == 200 && urlConnection.getContentLength() == 0) { if (DBG) log("Empty 200 response interpreted as 204 response."); httpResponseCode = 204; } if (httpResponseCode == 200 && fetchPac) { if (DBG) log("PAC fetch 200 response interpreted as 204 response."); httpResponseCode = 204; } sendNetworkConditionsBroadcast(true /* response received */, httpResponseCode != 204 /* isCaptivePortal */, requestTimestamp, responseTimestamp); } catch (IOException e) { if (DBG) log("Probably not a portal: exception " + e); if (httpResponseCode == 599) { // TODO: Ping gateway and DNS server and log results. } } finally { if (urlConnection != null) { urlConnection.disconnect(); } } return httpResponseCode;}
当网络与internet不连通时,NetworkMonitor所走状态机如下:
当与internet不通时,开始进行间隔5秒(每次检测超时为10(SOCKET_TIMEOUT_MS)秒,总的来说就是15秒检测一次)的检测,当大于3(INITIAL_ATTEMPTS)次检测都不通时,就会转到mOfflineState状态,并发送一个10分钟后的CMD_FORCE_REEVALUATION消息,10分钟后再进入mEvaluatingState状态进行检测,这次检测如果失败,就会再次进入mOfflineState状态,不停的循环检测。但有时上层会发送一个CMD_FORCE_REEVALUATIO消息过来,这时马上就进入mEvaluatingState状态进行检测,如果失败就继续进入mOfflineState状态并发送一个10分钟的CMD_FORCE_REEVALUATION消息。
从android代码看,在进入mOfflineState状态时,会给connectivity发送NETWORK_TEST_RESULT_INVALID消息,但connectivity中没有对该消息进行处理,也就是说,只要有一次检测到网络与internet连通后,everValidated设置为true,即使后面因为其他原因与internet不通了(与ap连接还是正常的),即使用户发送CMD_FORCE_REEVALUATIO消息进行网络检测,everValidated也不会被设置为false。
在android中,判断网络是否连接,是通过连接google服务器是否有回应判断的,在isCaptivePortal函数中实现,所以在大陆连接wifi的时候,由于无法与google服务器通信,所以在机子看来网络也是不通的。一个明显的标志是连接大陆wifi,在状态栏的wifi图标旁边有一个“感叹号”,如果连接的wifi能与google服务器通信,wifi图标旁就不会有“感叹号”。Wifi在检测到多次不能连接google服务器后,会在/data/misc/wifi/ networkHistory.txt 中记录:
!NO_INTERNET_ACCESS_REPORTS : 1
“VALIDATED_INTERNET_ACCESS: false
当VALIDATED_INTERNET_ACCESS为false,numNoInternetAccessReports大于0时,wifi AP名的下面就会出现提示“未检测到任何互联网连接,因此不会自动重新连接”,这就是连接大陆wifi时,会遇到的不会回连ap的情况。
这个问题,可以通过修改代码来规避:
如修改下面代码:
private int isCaptivePortal() { /* 增加代码,不配置该属性,默认为1,直接返回检测网络成功 */ if (SystemProperties.get("ro.isCaptivePortal", ”1”).equals("1")) return 204;
上面的修改,对于原生的检查代码,还缺少一个发送网络检测时间的广播,下面的修改,增加上广播网络回应时间。
protected int isCaptivePortal() { if (SystemProperties.get("ro.isCaptivePortal", ”1”).equals("1")) { /* make fake return */ int fakehttpResponseCode = 204; // Time how long it takes to get a response to our request long fakerequestTimestamp = SystemClock.elapsedRealtime(); try { Thread.currentThread().sleep(200); } catch(Exception e){} // Time how long it takes to get a response to our request long fakeresponseTimestamp = SystemClock.elapsedRealtime(); sendNetworkConditionsBroadcast(true /* response received */, fakehttpResponseCode != 204 /* isCaptivePortal */, fakerequestTimestamp, fakeresponseTimestamp); return fakehttpResponseCode; }
甚至我们可以重定向检测网络的地址。
private int isCaptivePortal() { HttpURLConnection urlConnection = null; int httpResponseCode = 599; try { URL url = new URL("http", "www.baidu.com", "/more/index.html"); /* 确保能成功获取该内容 *///URL url = new URL("http", "www.baidu.com", "/img/baidu_jgylogo3.gif"); /* 确保能成功获取该内容 */ // On networks with a PAC instead of fetching a URL that should result in a 204 // reponse, we instead simply fetch the PAC script. This is done for a few reasons: // 1. At present our PAC code does not yet handle multiple PACs on multiple networks // until something like https://android-review.googlesource.com/#/c/115180/ lands. // Network.openConnection() will ignore network-specific PACs and instead fetch // using NO_PROXY. If a PAC is in place, the only fetch we know will succeed with // NO_PROXY is the fetch of the PAC itself. // 2. To proxy the generate_204 fetch through a PAC would require a number of things // happen before the fetch can commence, namely: // a) the PAC script be fetched // b) a PAC script resolver service be fired up and resolve mServer // Network validation could be delayed until these prerequisities are satisifed or // could simply be left to race them. Neither is an optimal solution. // 3. PAC scripts are sometimes used to block or restrict Internet access and may in // fact block fetching of the generate_204 URL which would lead to false negative // results for network validation. boolean fetchPac = false; { final ProxyInfo proxyInfo = mNetworkAgentInfo.linkProperties.getHttpProxy(); if (proxyInfo != null && !Uri.EMPTY.equals(proxyInfo.getPacFileUrl())) { url = new URL(proxyInfo.getPacFileUrl().toString()); fetchPac = true; } } if (DBG) { log("Checking " + url.toString() + " on " + mNetworkAgentInfo.networkInfo.getExtraInfo()); } urlConnection = (HttpURLConnection) mNetworkAgentInfo.network.openConnection(url); urlConnection.setInstanceFollowRedirects(fetchPac); urlConnection.setConnectTimeout(SOCKET_TIMEOUT_MS); urlConnection.setReadTimeout(SOCKET_TIMEOUT_MS); urlConnection.setUseCaches(false); // Time how long it takes to get a response to our request long requestTimestamp = SystemClock.elapsedRealtime(); urlConnection.getInputStream(); // Time how long it takes to get a response to our request long responseTimestamp = SystemClock.elapsedRealtime(); httpResponseCode = urlConnection.getResponseCode(); if (DBG) { log("isCaptivePortal: ret=" + httpResponseCode + " headers=" + urlConnection.getHeaderFields()); } // NOTE: We may want to consider an "HTTP/1.0 204" response to be a captive // portal. The only example of this seen so far was a captive portal. For // the time being go with prior behavior of assuming it's not a captive // portal. If it is considered a captive portal, a different sign-in URL // is needed (i.e. can't browse a 204). This could be the result of an HTTP // proxy server. // Consider 200 response with "Content-length=0" to not be a captive portal. // There's no point in considering this a captive portal as the user cannot // sign-in to an empty page. Probably the result of a broken transparent proxy. // See http://b/9972012. if (httpResponseCode == 200) { if (DBG) log("www.baidu.com 200 response interpreted as 204 response."); httpResponseCode = 204; }
- android网络的评分机制、连接国内ap wifi不回连问题
- android网络的评分机制、连接国内ap wifi不回连问题
- android wifi开发 连接wifi 创建AP
- 网络连接评分机制之再谈WIFI与数据切换过程(原)
- 网络连接评分机制之再谈WIFI与数据切换过程
- 网络连接评分机制之再谈WIFI与数据切换过程
- 网络连接评分机制之再谈WIFI与数据切换过程
- 三、Android 网络评分机制
- android wifi不能连接中文AP
- Android网络连接Wifi和cmnet及cmwap的问题
- 网络连接评分机制之NetworkMonitor
- 网络连接评分机制之NetworkAgent
- 网络连接评分机制之NetworkFactory
- 网络连接评分机制之NetworkFactory
- 网络连接评分机制之NetworkAgent
- 网络连接评分机制之NetworkMonitor
- 网络连接评分机制之NetworkFactory
- 网络连接评分机制之NetworkAgent
- 应用内存泄露问题分析实例
- a day of no significance
- [深入理解Android卷一全文-第七章]深入理解Audio系统
- phpstudy+Notepad+DBGp xdebug 调试php
- 计算机的三大原则(《计算机是怎样运行起来的》笔记)
- android网络的评分机制、连接国内ap wifi不回连问题
- [深入理解Android卷一全文-第六章]深入理解Binder
- S3C2440冒泡排序(汇编)
- 【算法】二进制中1的个数
- [深入理解Android卷一全文-第五章]深入理解常见类
- Debug中URLClassPath.class File not found问题
- 《cuda实战》 笔记
- 优质App特性
- 常用命令4