前言:
本文介绍了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.javaprivate 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.javapublic 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 requestlong requestTimestamp = SystemClock.elapsedRealtime();urlConnection.getInputStream();// Time how long it takes to get a response to our requestlong 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 requestlong fakerequestTimestamp = SystemClock.elapsedRealtime();try{Thread.currentThread().sleep(200);}catch(Exception e){}// Time how long it takes to get a response to our requestlong 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 requestlong requestTimestamp = SystemClock.elapsedRealtime();urlConnection.getInputStream();// Time how long it takes to get a response to our requestlong 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不回连问题相关推荐

  1. Android网络请求加密机制详解

    Android开发中,难免会遇到需要加解密一些数据内容存到本地文件.或者通过网络传输到其他服务器和设备的问题,但并不是使用了加密就绝对安全了,如果加密函数使用不正确,加密数据很容易受到逆向破解攻击.还 ...

  2. Android网络类型判断(2g、3g、wifi)

    2019独角兽企业重金招聘Python工程师标准>>> 判断网络类型是wifi,还是3G,还是2G网络,对不同 的网络进行不同的处理,现将判断方法整理给大家,以供参考 说明:下面用到 ...

  3. android 连接eap wifi,在Android中以编程方式连接WPA2企业WiFi连接

    因为我的AP具有相同的SSID,所以我想通过使用连接正确的网络奥克.现在我用过这个answer但我不需要澄清,因为这个回答是非常古老的.这里我附上一些关于连接说明的屏幕截图. 在这你可以看到身份和密码 ...

  4. Android P WiFi自动连接评分机制

    1.WifiConnectivityManager的初始化 frameworks/opt/net/wifi/service/java/com/android/server/wifi/WifiState ...

  5. 【安卓Framework学习】Wifi框架学习之热点评分机制

    系列文章目录 [安卓Framework学习]Wifi框架学习之核心类 [安卓Framework学习]Wifi框架学习之wifi状态机 [安卓Framework学习]Wifi框架学习之连接与断开流程 [ ...

  6. Android wifi列表扫描 密码连接 多个wifi切换登录 广播状态等都在这里

    Wi-Fi 前言 需求 实现 扫描wifi列表 wifi广播 wifi连接登录 总结 前言 app里有个需求就是在应用内部开发一个wifi设置的功能,避免用户跳到手机wifi设置界面操作,之前没开发过 ...

  7. Android连接管理的评分机制(WIFI,Ethernet,BT,移动数据

    Android可以支持:移动数据网络.WIFI.蓝牙.网线等,这些连接本身都可以独立使用,连接管理通过一个评分机制来实现不同接入方式的选择.具体来说就是,每一种上网方式在初始化时,都向Connecti ...

  8. 【Android】Android网络评分机制简单总结

    文章参考于: 三.Android 网络评分机制 - 简书在前两节简单介绍了连接管理的大致框架,数据链接的准备工作,包括APN的初始化与默认APN使能,DcTracker的构造,包括各种事件的注册等工作 ...

  9. Android 8.0/9.0 wifi 自动连接评分机制

    今天了解了一下Wifi自动连接时的评分机制,总结如下: WifiConnectivityManager的初始化: /frameworks/opt/net/wifi/service/java/com/a ...

最新文章

  1. SBIO | 西农韦革宏组-大豆土壤细菌门间负向互作影响群落的动态变化和功能
  2. 如何查看和停止Linux启动的服务
  3. MySQL5.7 : 对隐式锁转换的优化
  4. Kafka 客户端实现逻辑分析
  5. 海园帮忙写的JQUERY功能,实现了我们想要的,我觉得有点屌哟~~
  6. P1600 [NOIP2016 提高组] 天天爱跑步
  7. mysql phpmyadmin 安装_phpMyAdmin 安装
  8. 如何追踪App安装来源
  9. HTML的style属性(替代font等标签)
  10. 基于ARM的智能灯光控制系统(4)数据结构
  11. android硬解码x265,Android 设置硬解码 h265 失败
  12. selenium操作各种浏览器
  13. 山东大学软件学院计算机组成原理课程设计整机实验(2)
  14. Caliburn.Micro学习笔记目录——其他
  15. windows系统重装(安装)第一篇——老毛桃本地PE环境的安装
  16. svn 项目文件出现左边红色箭头,右边绿色箭头的双箭头的解决方法
  17. 校尉羽书飞瀚海,顺序表中增删改(Pt.2)
  18. 电脑在线如何一键重装win10系统?电脑在线一键重装系统win10步骤
  19. 洞若观火 - 让ZONE做你数据库运维的眼睛
  20. [翻译] 更改 PDF 浏览器 - Changing PDF viewer

热门文章

  1. idea使用Protobuf插件
  2. DSP在线升级方案:TMS320C6678使用网络或者串口输出升级数据进行固件升级
  3. 释放/清理 VMware 虚拟磁盘空间
  4. 数据安全法(草案)概述
  5. RxSwift系列—Driver
  6. 10月25日 c语言 打印所有水仙花数
  7. Node.js:fs文件模块的替代品fs-extra
  8. 《ImageNet Classification with Deep Convolutional Neural Networks》翻译
  9. dijkstra习题集
  10. Fiddler中Response 的Raw乱码问题解决