編輯:關於Android編程
WifiConfigStore在Android的無線網絡部分,主要負責網絡配置信息的管理工作,包括保存、讀取配置信息等。當我們在Settings中觸發一個保存網絡、連接網絡或者auto_connect自動重連動作時,都會調用到WifiConfigStore中的方法。
public class WifiConfigStore extends IpConfigStoreWifiConfigStore繼承自IpConfigStore,它提供了一套API去管理用戶配置過的網絡。下面介紹一些framework中經常調用到的API接口。
WifiStateMachine中,WifiConfigStore對象的創建發生在其構造函數中:
mWifiConfigStore = new WifiConfigStore(context,this, mWifiNative);我們傳入了Context、當前的WifiStateMachine對象和一個WifiNative對象。通過mWifiNative對象可以向wpa_s下發一系列連接、選擇的命令。
我們在連接一個網絡的時候,會先保存該網絡的配置信息,調用:
/** * Add/update the specified configuration and save config * * @param config WifiConfiguration to be saved * @return network update result */ NetworkUpdateResult saveNetwork(WifiConfiguration config, int uid) { WifiConfiguration conf; // A new network cannot have null SSID if (config == null || (config.networkId == INVALID_NETWORK_ID && config.SSID == null)) { return new NetworkUpdateResult(INVALID_NETWORK_ID); } if (VDBG) localLog("WifiConfigStore: saveNetwork netId", config.networkId); if (VDBG) { loge("WifiConfigStore saveNetwork, size=" + mConfiguredNetworks.size() + " SSID=" + config.SSID + " Uid=" + Integer.toString(config.creatorUid) + "/" + Integer.toString(config.lastUpdateUid)); } if (mDeletedEphemeralSSIDs.remove(config.SSID)) { if (VDBG) { loge("WifiConfigStore: removed from ephemeral blacklist: " + config.SSID); } // NOTE: This will be flushed to disk as part of the addOrUpdateNetworkNative call // below, since we're creating/modifying a config. } boolean newNetwork = (config.networkId == INVALID_NETWORK_ID); NetworkUpdateResult result = addOrUpdateNetworkNative(config, uid); int netId = result.getNetworkId(); if (VDBG) localLog("WifiConfigStore: saveNetwork got it back netId=", netId); /* enable a new network */ if (newNetwork && netId != INVALID_NETWORK_ID) { if (VDBG) localLog("WifiConfigStore: will enable netId=", netId); mWifiNative.enableNetwork(netId, false); conf = mConfiguredNetworks.get(netId); if (conf != null) conf.status = Status.ENABLED; } conf = mConfiguredNetworks.get(netId); if (conf != null) { if (conf.autoJoinStatus != WifiConfiguration.AUTO_JOIN_ENABLED) { if (VDBG) localLog("WifiConfigStore: re-enabling: " + conf.SSID); // reenable autojoin, since new information has been provided conf.setAutoJoinStatus(WifiConfiguration.AUTO_JOIN_ENABLED); enableNetworkWithoutBroadcast(conf.networkId, false); } if (VDBG) { loge("WifiConfigStore: saveNetwork got config back netId=" + Integer.toString(netId) + " uid=" + Integer.toString(config.creatorUid)); } } mWifiNative.saveConfig(); sendConfiguredNetworksChangedBroadcast(conf, result.isNewNetwork() ? WifiManager.CHANGE_REASON_ADDED : WifiManager.CHANGE_REASON_CONFIG_CHANGE); return result; }saveNetwork()主要負責根據WifiConfiguration對象更新、保存網絡的各配置信息;WifiConfiguration代表一個配置過的網絡,主要包括該網絡的加密方式、SSID、密鑰等等信息。重要的一個操作是調用addOrUpdateNetworkNative()來更新配置信息、並保存到本地;該函數的函數實現雖然較多,看起來復雜,但實際處理卻還是較為簡單的:
首先從mConfiguredNetworks中根據傳入的config對象獲取到先前保存過的同netId的savedConfig對象;mConfiguredNetworks是一個HasMap結構,它以某個網絡的netId為key,以對應的WifiConfiguration對象作為value,由此可知它以鍵值對的形式保存了當前所有配置過的網絡信息。後續的操作都是比對config和savedConfig直接的差異,保存到wpa_s配置文件中並進行更新,最後再將更新過的WifiConfiguration對象保存到mConfiguredNetworks中。調用writeIpAndProxyConfigurationsOnChange()將新的配置信息保存到本地文件/misc/wifi/ipconfig.txt中。後面會說到,當我們重新打開Wifi時,會從該文件中讀取我們所配置過的網絡信息,並進行重連。
/* Compare current and new configuration and write to file on change */ private NetworkUpdateResult writeIpAndProxyConfigurationsOnChange( WifiConfiguration currentConfig, WifiConfiguration newConfig) { boolean ipChanged = false; boolean proxyChanged = false; if (VDBG) { loge("writeIpAndProxyConfigurationsOnChange: " + currentConfig.SSID + " -> " + newConfig.SSID + " path: " + ipConfigFile); } switch (newConfig.getIpAssignment()) { case STATIC: if (currentConfig.getIpAssignment() != newConfig.getIpAssignment()) { ipChanged = true; } else { ipChanged = !Objects.equals( currentConfig.getStaticIpConfiguration(), newConfig.getStaticIpConfiguration()); } break; case DHCP: if (currentConfig.getIpAssignment() != newConfig.getIpAssignment()) { ipChanged = true; } break; case UNASSIGNED: /* Ignore */ break; default: loge("Ignore invalid ip assignment during write"); break; } switch (newConfig.getProxySettings()) { case STATIC: case PAC: ProxyInfo newHttpProxy = newConfig.getHttpProxy(); ProxyInfo currentHttpProxy = currentConfig.getHttpProxy(); if (newHttpProxy != null) { proxyChanged = !newHttpProxy.equals(currentHttpProxy); } else { proxyChanged = (currentHttpProxy != null); } break; case NONE: if (currentConfig.getProxySettings() != newConfig.getProxySettings()) { proxyChanged = true; } break; case UNASSIGNED: /* Ignore */ break; default: loge("Ignore invalid proxy configuration during write"); break; } if (ipChanged) { currentConfig.setIpAssignment(newConfig.getIpAssignment()); currentConfig.setStaticIpConfiguration(newConfig.getStaticIpConfiguration()); log("IP config changed SSID = " + currentConfig.SSID); if (currentConfig.getStaticIpConfiguration() != null) { log(" static configuration: " + currentConfig.getStaticIpConfiguration().toString()); } } if (proxyChanged) { currentConfig.setProxySettings(newConfig.getProxySettings()); currentConfig.setHttpProxy(newConfig.getHttpProxy()); log("proxy changed SSID = " + currentConfig.SSID); if (currentConfig.getHttpProxy() != null) { log(" proxyProperties: " + currentConfig.getHttpProxy().toString()); } } if (ipChanged || proxyChanged) { writeIpAndProxyConfigurations(); sendConfiguredNetworksChangedBroadcast(currentConfig, WifiManager.CHANGE_REASON_CONFIG_CHANGE); } return new NetworkUpdateResult(ipChanged, proxyChanged); }函數中涉及到IpAssignment和ProxySettings兩個枚舉類型:
public enum IpAssignment { /* Use statically configured IP settings. Configuration can be accessed * with staticIpConfiguration */ STATIC, /* Use dynamically configured IP settigns */ DHCP, /* no IP details are assigned, this is used to indicate * that any existing IP settings should be retained */ UNASSIGNED } public enum ProxySettings { /* No proxy is to be used. Any existing proxy settings * should be cleared. */ NONE, /* Use statically configured proxy. Configuration can be accessed * with httpProxy. */ STATIC, /* no proxy details are assigned, this is used to indicate * that any existing proxy settings should be retained */ UNASSIGNED, /* Use a Pac based proxy. */ PAC }IpAssignment代表當前獲取IP使用的方式,我們可以根據自己的需求在裡面添加自定義的方式,比如PPPoE;同理,ProxySettings表示當前網絡使用的代理方式。IpAssignment類型的值一般由設置根據用戶選擇的IP模式來賦值,並傳遞給framework,以讓底層可以知道該使用什麼方式去獲取IP地址。例如,如果用戶選擇Static IP,則在WifiStateMachine::ObtainingIpState中會有:
if (!mWifiConfigStore.isUsingStaticIp(mLastNetworkId)) { if (isRoaming()) { renewDhcp(); } else { // Remove any IP address on the interface in case we're switching from static // IP configuration to DHCP. This is safe because if we get here when not // roaming, we don't have a usable address. clearIPv4Address(mInterfaceName); startDhcp(); } obtainingIpWatchdogCount++; logd("Start Dhcp Watchdog " + obtainingIpWatchdogCount); // Get Link layer stats so as we get fresh tx packet counters getWifiLinkLayerStats(true); sendMessageDelayed(obtainMessage(CMD_OBTAINING_IP_ADDRESS_WATCHDOG_TIMER, obtainingIpWatchdogCount, 0), OBTAINING_IP_ADDRESS_GUARD_TIMER_MSEC); } else { // stop any running dhcp before assigning static IP stopDhcp(); StaticIpConfiguration config = mWifiConfigStore.getStaticIpConfiguration( mLastNetworkId); if (config.ipAddress == null) { logd("Static IP lacks address"); sendMessage(CMD_STATIC_IP_FAILURE); } else { InterfaceConfiguration ifcg = new InterfaceConfiguration(); ifcg.setLinkAddress(config.ipAddress); ifcg.setInterfaceUp(); try { mNwService.setInterfaceConfig(mInterfaceName, ifcg); if (DBG) log("Static IP configuration succeeded"); DhcpResults dhcpResults = new DhcpResults(config); sendMessage(CMD_STATIC_IP_SUCCESS, dhcpResults); } catch (RemoteException re) { loge("Static IP configuration failed: " + re); sendMessage(CMD_STATIC_IP_FAILURE); } catch (IllegalStateException e) { loge("Static IP configuration failed: " + e); sendMessage(CMD_STATIC_IP_FAILURE); } } }通過WifiConfigStore.isUsingStaticIp(mLastNetworkId)方法獲知當前用戶使用的獲取IP地址類型,具體方法定義:
/** * Return if the specified network is using static IP * @param netId id * @return {@code true} if using static ip for netId */ boolean isUsingStaticIp(int netId) { WifiConfiguration config = mConfiguredNetworks.get(netId); if (config != null && config.getIpAssignment() == IpAssignment.STATIC) { return true; } return false; }根據傳入的netId,從mConfiguredNetworks集合中獲取對應網絡的WifiConfiguration對象,再獲取該對象配置的IpAssignment值,來區分不用的網絡方式,進而控制流程走不同的分支。如果我們有加入別的方式,可以仿照這個原生例子,寫出自己的程序。
riteIpAndProxyConfigurationsOnChange()中會根據IpAssignment、ProxySettings的類型是否改變,去更新currentConfig對象,並writeIpAndProxyConfigurations()方法寫入到本地磁盤文件:
private void writeIpAndProxyConfigurations() { final SparseArraynetworks = new SparseArray (); for(WifiConfiguration config : mConfiguredNetworks.values()) { if (!config.ephemeral && config.autoJoinStatus != WifiConfiguration.AUTO_JOIN_DELETED) { networks.put(configKey(config), config.getIpConfiguration()); } } super.writeIpAndProxyConfigurations(ipConfigFile, networks);//in IpConfigStore }
public void IpConfigStore::writeIpAndProxyConfigurations(String filePath, final SparseArraynetworks) { mWriter.write(filePath, new DelayedDiskWrite.Writer() { public void onWriteCalled(DataOutputStream out) throws IOException{ out.writeInt(IPCONFIG_FILE_VERSION); for(int i = 0; i < networks.size(); i++) { writeConfig(out, networks.keyAt(i), networks.valueAt(i)); } } }); }
private boolean writeConfig(DataOutputStream out, int configKey, IpConfiguration config) throws IOException { boolean written = false; try { switch (config.ipAssignment) { case STATIC: out.writeUTF(IP_ASSIGNMENT_KEY); out.writeUTF(config.ipAssignment.toString()); StaticIpConfiguration staticIpConfiguration = config.staticIpConfiguration; if (staticIpConfiguration != null) { if (staticIpConfiguration.ipAddress != null) { LinkAddress ipAddress = staticIpConfiguration.ipAddress; out.writeUTF(LINK_ADDRESS_KEY); out.writeUTF(ipAddress.getAddress().getHostAddress()); out.writeInt(ipAddress.getPrefixLength()); } if (staticIpConfiguration.gateway != null) { out.writeUTF(GATEWAY_KEY); out.writeInt(0); // Default route. out.writeInt(1); // Have a gateway. out.writeUTF(staticIpConfiguration.gateway.getHostAddress()); } for (InetAddress inetAddr : staticIpConfiguration.dnsServers) { out.writeUTF(DNS_KEY); out.writeUTF(inetAddr.getHostAddress()); } } written = true; break; case DHCP: out.writeUTF(IP_ASSIGNMENT_KEY); out.writeUTF(config.ipAssignment.toString()); written = true; break; case UNASSIGNED: /* Ignore */ break; default: loge("Ignore invalid ip assignment while writing"); break; } switch (config.proxySettings) { case STATIC: ProxyInfo proxyProperties = config.httpProxy; String exclusionList = proxyProperties.getExclusionListAsString(); out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); out.writeUTF(PROXY_HOST_KEY); out.writeUTF(proxyProperties.getHost()); out.writeUTF(PROXY_PORT_KEY); out.writeInt(proxyProperties.getPort()); if (exclusionList != null) { out.writeUTF(EXCLUSION_LIST_KEY); out.writeUTF(exclusionList); } written = true; break; case PAC: ProxyInfo proxyPacProperties = config.httpProxy; out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); out.writeUTF(PROXY_PAC_FILE); out.writeUTF(proxyPacProperties.getPacFileUrl().toString()); written = true; break; case NONE: out.writeUTF(PROXY_SETTINGS_KEY); out.writeUTF(config.proxySettings.toString()); written = true; break; case UNASSIGNED: /* Ignore */ break; default: loge("Ignore invalid proxy settings while writing"); break; } if (written) { out.writeUTF(ID_KEY); out.writeInt(configKey); } } catch (NullPointerException e) { loge("Failure in writing " + config + e); } out.writeUTF(EOS); return written; }最終寫入文件的操作是父類IpConfigStore::writeConfig()方法處理的。在WifiConfigStore::writeIpAndProxyConfigurations()中,我們會將所有保存的網絡配置信息從mConfiguredNetworks集合中取出,重新按照<一個唯一int值,IpConfiguration>的形式保存到一個SparseArray
在IpConfigStore::writeIpAndProxyConfigurations和IpConfigStore::writeConfig()中,我們會遍歷networks集合,並按照
switch (config.ipAssignment) switch (config.proxySettings)的分類,將信息寫入ipconfig.txt文件中;這裡的寫入也是有一定的規則的,每一個標簽後面跟一個該標簽對應的值。這樣做方法後面的數據讀取。定義的標簽值有:
/* IP and proxy configuration keys */ protected static final String ID_KEY = "id"; protected static final String IP_ASSIGNMENT_KEY = "ipAssignment"; protected static final String LINK_ADDRESS_KEY = "linkAddress"; protected static final String GATEWAY_KEY = "gateway"; protected static final String DNS_KEY = "dns"; protected static final String PROXY_SETTINGS_KEY = "proxySettings"; protected static final String PROXY_HOST_KEY = "proxyHost"; protected static final String PROXY_PORT_KEY = "proxyPort"; protected static final String PROXY_PAC_FILE = "proxyPac"; protected static final String EXCLUSION_LIST_KEY = "exclusionList"; protected static final String EOS = "eos"; protected static final int IPCONFIG_FILE_VERSION = 2;從這裡我們可以看到一些可以定制的地方。現在,有一部分Android手機上的Wifi功能是支持無線PPPoE的;要使用PPPoE,就要用到賬戶信息;此時,我們是否可以在WifiConfiguration或IpConfiguration中添加對應的賬戶屬性字段,在保存網絡時,加入對賬戶密碼字段的寫入保存動作;同時,在從ipconfig.txt讀取信息時,將該信息重新封裝到WifiConfiguration或IpConfiguration對象中,供無線PPPoE獲取IP時使用。
最後還會涉及到writeKnownNetworkHistory()的調用,它會向/misc/wifi/networkHistory.txt中寫入每個WifiConfiguration對象中的一些字段值,包括優先級、SSID等等;寫入方式跟前面相同。這裡,saveNetwork()的處理就結束了。
selectNetwork()的作用是選擇一個特定的網絡去准備連接,這裡會涉及到網絡優先級更新和enable網絡的部分。
/** * Selects the specified network for connection. This involves * updating the priority of all the networks and enabling the given * network while disabling others. * * Selecting a network will leave the other networks disabled and * a call to enableAllNetworks() needs to be issued upon a connection * or a failure event from supplicant * * @param config network to select for connection * @param updatePriorities makes config highest priority network * @return false if the network id is invalid */ boolean selectNetwork(WifiConfiguration config, boolean updatePriorities, int uid) { if (VDBG) localLog("selectNetwork", config.networkId); if (config.networkId == INVALID_NETWORK_ID) return false; // Reset the priority of each network at start or if it goes too high. if (mLastPriority == -1 || mLastPriority > 1000000) { for(WifiConfiguration config2 : mConfiguredNetworks.values()) { if (updatePriorities) { if (config2.networkId != INVALID_NETWORK_ID) { config2.priority = 0; setNetworkPriorityNative(config2.networkId, config.priority); } } } mLastPriority = 0; } // Set to the highest priority and save the configuration. if (updatePriorities) { config.priority = ++mLastPriority; setNetworkPriorityNative(config.networkId, config.priority); buildPnoList(); } if (config.isPasspoint()) { /* need to slap on the SSID of selected bssid to work */ if (getScanDetailCache(config).size() != 0) { ScanDetail result = getScanDetailCache(config).getFirst(); if (result == null) { loge("Could not find scan result for " + config.BSSID); } else { log("Setting SSID for " + config.networkId + " to" + result.getSSID()); setSSIDNative(config.networkId, result.getSSID()); config.SSID = result.getSSID(); } } else { loge("Could not find bssid for " + config); } } if (updatePriorities) mWifiNative.saveConfig(); else mWifiNative.selectNetwork(config.networkId); updateLastConnectUid(config, uid); writeKnownNetworkHistory(false); /* Enable the given network while disabling all other networks */ enableNetworkWithoutBroadcast(config.networkId, true); /* Avoid saving the config & sending a broadcast to prevent settings * from displaying a disabled list of networks */ return true; }mLastPriority是一個int類型的整數值,它代表當前網絡中的優先級的最大值。越是最近連接過的網絡,它的priority優先級值就越大。updatePriorities代表是否需要更新優先級。當當前的最大優先級值為-1或1000000時,都會重新設置mLastPriority值;如果updatePriorities為true,也會將更改更新到wpa_s.conf文件中。
// Set to the highest priority and save the configuration. if (updatePriorities) { config.priority = ++mLastPriority; setNetworkPriorityNative(config.networkId, config.priority); buildPnoList(); }從這可以看出,每個當前正在連接的網絡,都具有最高的優先級。最後enableNetworkWithoutBroadcast()中,會在mConfiguredNetworks將選中網絡的status屬性設為Status.ENABLED,其他的設置為Status.DISABLED:
/* Mark all networks except specified netId as disabled */ private void markAllNetworksDisabledExcept(int netId) { for(WifiConfiguration config : mConfiguredNetworks.values()) { if(config != null && config.networkId != netId) { if (config.status != Status.DISABLED) { config.status = Status.DISABLED; config.disableReason = WifiConfiguration.DISABLED_UNKNOWN_REASON; } } }
}
mWifiConfigStore.loadAndEnableAllNetworks();
/** * Fetch the list of configured networks * and enable all stored networks in supplicant. */ void loadAndEnableAllNetworks() { if (DBG) log("Loading config and enabling all networks "); loadConfiguredNetworks(); enableAllNetworks(); }看loadConfiguredNetworks():
void loadConfiguredNetworks() { mLastPriority = 0; mConfiguredNetworks.clear(); int last_id = -1; boolean done = false; while (!done) { String listStr = mWifiNative.listNetworks(last_id); if (listStr == null) return; String[] lines = listStr.split("\n"); if (showNetworks) { localLog("WifiConfigStore: loadConfiguredNetworks: "); for (String net : lines) { localLog(net); } } // Skip the first line, which is a header for (int i = 1; i < lines.length; i++) { String[] result = lines[i].split("\t"); // network-id | ssid | bssid | flags WifiConfiguration config = new WifiConfiguration(); try { config.networkId = Integer.parseInt(result[0]); last_id = config.networkId; } catch(NumberFormatException e) { loge("Failed to read network-id '" + result[0] + "'"); continue; } if (result.length > 3) { if (result[3].indexOf("[CURRENT]") != -1) config.status = WifiConfiguration.Status.CURRENT; else if (result[3].indexOf("[DISABLED]") != -1) config.status = WifiConfiguration.Status.DISABLED; else config.status = WifiConfiguration.Status.ENABLED; } else { config.status = WifiConfiguration.Status.ENABLED; } readNetworkVariables(config); Checksum csum = new CRC32(); if (config.SSID != null) { csum.update(config.SSID.getBytes(), 0, config.SSID.getBytes().length); long d = csum.getValue(); if (mDeletedSSIDs.contains(d)) { loge(" got CRC for SSID " + config.SSID + " -> " + d + ", was deleted"); } } if (config.priority > mLastPriority) { mLastPriority = config.priority; } config.setIpAssignment(IpAssignment.DHCP);//默認設置DHCP config.setProxySettings(ProxySettings.NONE);//默認設置NONE if (mConfiguredNetworks.getByConfigKey(config.configKey()) != null) { // That SSID is already known, just ignore this duplicate entry if (showNetworks) localLog("discarded duplicate network ", config.networkId); } else if(WifiServiceImpl.isValid(config)){ mConfiguredNetworks.put(config.networkId, config); if (showNetworks) localLog("loaded configured network", config.networkId); } else { if (showNetworks) log("Ignoring loaded configured for network " + config.networkId + " because config are not valid"); } } done = (lines.length == 1); } readPasspointConfig(); readIpAndProxyConfigurations();//讀取ipconfig.txt readNetworkHistory();//讀取networkHistory.txt readAutoJoinConfig(); buildPnoList(); sendConfiguredNetworksChangedBroadcast(); if (showNetworks) localLog("loadConfiguredNetworks loaded " + mConfiguredNetworks.size() + " networks"); if (mConfiguredNetworks.isEmpty()) { // no networks? Lets log if the file contents logKernelTime(); logContents(SUPPLICANT_CONFIG_FILE); logContents(SUPPLICANT_CONFIG_FILE_BACKUP); logContents(networkHistoryConfigFile); } }函數開始就會清空mConfiguredNetworks集合:
從wp_s讀取保存的網絡配置列表,並保存到mConfiguredNetworks中調用readIpAndProxyConfigurations()方法,從ipconfig.txt中讀取保存的IpConfiguration對象,更新到mConfiguredNetworks保存的各WifiConfiguration對象中調用mConfiguredNetworks()方法,從/misc/wifi/networkHistory.txt文件中讀取保存的信息,更新到mConfiguredNetworks保存的各WifiConfiguration對象中
讀取的方式跟前面介紹的寫入的方式基本相似。經過上所述的兩次讀取操作,我們持有的WifiConfiguration對象的信息就是比較完整的了。
如果有我們前面說過的無線PPPoE的場景,readIpAndProxyConfigurations()方法中就會把我們事先寫入的賬號密碼信息也讀取出來,存到mConfiguredNetworks中。走auto_connect流程時,獲取到最近一次連接的網絡netId,從mConfiguredNetworks中取出的對應的WifiConfiguration對象中就保存有PPPoE的賬號密碼,這樣我們在PPPoE獲取IP時,就有可用的賬戶信息了。
WifiConfigStore中定義了一個比較有意義的默認變量值:
/** * The maximum number of times we will retry a connection to an access point * for which we have failed in acquiring an IP address from DHCP. A value of * N means that we will make N+1 connection attempts in all. *
* See {@link Settings.Secure#WIFI_MAX_DHCP_RETRY_COUNT}. This is the default * value if a Settings value is not present. */ private static final int DEFAULT_MAX_DHCP_RETRIES = 9;
從原生注釋中,我們得知這個變量控制了當一個網絡獲取IP失敗時,之後會繼續重試的次數;如果值定義為9,那麼實際的重連次數將是9+1,為10。如果我們有定制這個值,那麼重連次數將以我們自定義配置的值為准:
int WifiConfigStore::getMaxDhcpRetries() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT, DEFAULT_MAX_DHCP_RETRIES); }我們可以配置Settings.Global.WIFI_MAX_DHCP_RETRY_COUNT這個字段值來定制這部分:
/** * The maximum number of times we will retry a connection to an access * point for which we have failed in acquiring an IP address from DHCP. * A value of N means that we will make N+1 connection attempts in all. */ public static final String WIFI_MAX_DHCP_RETRY_COUNT = "wifi_max_dhcp_retry_count";getMaxDhcpRetries()在WifiConfigStore中只在handleSSIDStateChange()函數中有使用:
void handleSSIDStateChange(int netId, boolean enabled, String message, String BSSID) { WifiConfiguration config = mConfiguredNetworks.get(netId); if (config != null) { if (enabled) { loge("Ignoring SSID re-enabled from supplicant: " + config.configKey() + " had autoJoinStatus=" + Integer.toString(config.autoJoinStatus) + " self added " + config.selfAdded + " ephemeral " + config.ephemeral); //We should not re-enable the BSSID based on Supplicant reanable. // Framework will re-enable it after its own blacklist timer expires } else { loge("SSID temp disabled for " + config.configKey() + " had autoJoinStatus=" + Integer.toString(config.autoJoinStatus) + " self added " + config.selfAdded + " ephemeral " + config.ephemeral); if (message != null) { loge(" message=" + message); } if (config.selfAdded && config.lastConnected == 0) { // This is a network we self added, and we never succeeded, // the user did not create this network and never entered its credentials, // so we want to be very aggressive in disabling it completely. removeConfigAndSendBroadcastIfNeeded(config.networkId); } else { if (message != null) { if (message.contains("no identity")) { config.setAutoJoinStatus( WifiConfiguration.AUTO_JOIN_DISABLED_NO_CREDENTIALS); if (DBG) { loge("no identity blacklisted " + config.configKey() + " to " + Integer.toString(config.autoJoinStatus)); } } else if (message.contains("WRONG_KEY") || message.contains("AUTH_FAILED")) { // This configuration has received an auth failure, so disable it // temporarily because we don't want auto-join to try it out. // this network may be re-enabled by the "usual" // enableAllNetwork function config.numAuthFailures++; if (config.numAuthFailures > maxAuthErrorsToBlacklist) { config.setAutoJoinStatus (WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE); disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE); loge("Authentication failure, blacklist " + config.configKey() + " " + Integer.toString(config.networkId) + " num failures " + config.numAuthFailures); } } else if (message.contains("DHCP FAILURE")) { config.numIpConfigFailures++; config.lastConnectionFailure = System.currentTimeMillis(); int maxRetries = getMaxDhcpRetries(); // maxRetries == 0 means keep trying forever if (maxRetries > 0 && config.numIpConfigFailures > maxRetries) { /** * If we've exceeded the maximum number of retries for DHCP * to a given network, disable the network */ config.setAutoJoinStatus (WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE); disableNetwork(netId, WifiConfiguration.DISABLED_DHCP_FAILURE); loge("DHCP failure, blacklist " + config.configKey() + " " + Integer.toString(config.networkId) + " num failures " + config.numIpConfigFailures); } // Also blacklist the BSSId if we find it ScanResult result = null; String bssidDbg = ""; if (getScanDetailCache(config) != null && BSSID != null) { result = getScanDetailCache(config).get(BSSID); } if (result != null) { result.numIpConfigFailures ++; bssidDbg = BSSID + " ipfail=" + result.numIpConfigFailures; if (result.numIpConfigFailures > 3) { // Tell supplicant to stop trying this BSSID mWifiNative.addToBlacklist(BSSID); result.setAutoJoinStatus(ScanResult.AUTO_JOIN_DISABLED); } } if (DBG) { loge("blacklisted " + config.configKey() + " to " + config.autoJoinStatus + " due to IP config failures, count=" + config.numIpConfigFailures + " disableReason=" + config.disableReason + " " + bssidDbg); } } else if (message.contains("CONN_FAILED")) { config.numConnectionFailures++; if (config.numConnectionFailures > maxConnectionErrorsToBlacklist) { config.setAutoJoinStatus (WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE); disableNetwork(netId, WifiConfiguration.DISABLED_ASSOCIATION_REJECT); loge("Connection failure, blacklist " + config.configKey() + " " + config.networkId + " num failures " + config.numConnectionFailures); } } message.replace("\n", ""); message.replace("\r", ""); config.lastFailure = message; } } } } }在WifiStateMachine中,如果一個網絡DHCP獲取IP失敗、或STATIC IP配置失敗、或網絡的配置信息丟失,都會間接調用到handleSSIDStateChange()函數,在配置的次數內嘗試網絡重連。我們看一個例子:
private void WifiStateMachine::handleIpConfigurationLost() { mWifiInfo.setInetAddress(null); mWifiInfo.setMeteredHint(false); mWifiConfigStore.handleSSIDStateChange(mLastNetworkId, false, "DHCP FAILURE", mWifiInfo.getBSSID());//函數調用 /* DHCP times out after about 30 seconds, we do a * disconnect thru supplicant, we will let autojoin retry connecting to the network */ mWifiNative.disconnect(); }這裡調用時,netId為當前使用的網絡的netId,用以在WifiConfigStore獲取到對應的WifiConfiguration,enabled為false,message為DHCP_FALURE;對照handleSSIDStateChange()實現,我們可以分析得出:
根據傳入的message,實現中會根據message的內容,判斷當前網絡發生的錯誤是什麼,並記錄相應錯誤的次數;比如是WRONG_KEY、還是AUTH_FAILED、還是DHCP FAILURE;然後會更新WifiConfiguration對象中各個錯誤對應的字段值,例如:WRONG_KEY和AUTH_FAILED對應的字段就是numAuthFailures,它記錄了授權失敗的次數,如果授權失敗的次數大於一定的值,就會將該網絡的config.autoJoinStatus設為AUTO_JOIN_DISABLED_ON_AUTH_FAILURE,並disable當前網絡。那麼在之後的auto_connect流程中,判斷autoJoinStatus不合法,就不會去繼續重連流程。
if (config.numAuthFailures > maxAuthErrorsToBlacklist) { config.setAutoJoinStatus (WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE);//設置autostatus disableNetwork(netId, WifiConfiguration.DISABLED_AUTH_FAILURE);//disable網絡 loge("Authentication failure, blacklist " + config.configKey() + " " + Integer.toString(config.networkId) + " num failures " + config.numAuthFailures); }
else if (message.contains("DHCP FAILURE")) { config.numIpConfigFailures++; config.lastConnectionFailure = System.currentTimeMillis(); int maxRetries = getMaxDhcpRetries(); // maxRetries == 0 means keep trying forever if (maxRetries > 0 && config.numIpConfigFailures > maxRetries) { /** * If we've exceeded the maximum number of retries for DHCP * to a given network, disable the network */ config.setAutoJoinStatus (WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE);//設置autostatus disableNetwork(netId, WifiConfiguration.DISABLED_DHCP_FAILURE);//disable網絡 loge("DHCP failure, blacklist " + config.configKey() + " " + Integer.toString(config.networkId) + " num failures " + config.numIpConfigFailures); } // Also blacklist the BSSId if we find it ScanResult result = null; String bssidDbg = ""; if (getScanDetailCache(config) != null && BSSID != null) { result = getScanDetailCache(config).get(BSSID); } if (result != null) { result.numIpConfigFailures ++; bssidDbg = BSSID + " ipfail=" + result.numIpConfigFailures; if (result.numIpConfigFailures > 3) { // Tell supplicant to stop trying this BSSID mWifiNative.addToBlacklist(BSSID);//也有可能將當前網絡加入到blacklist中 result.setAutoJoinStatus(ScanResult.AUTO_JOIN_DISABLED);//設置autostatus } } if (DBG) { loge("blacklisted " + config.configKey() + " to " + config.autoJoinStatus + " due to IP config failures, count=" + config.numIpConfigFailures + " disableReason=" + config.disableReason + " " + bssidDbg); } }首先numIpConfigFailures自增1,該字段代表當前網絡DHCP失敗的次數。如果當前網絡的DHCP失敗次數numIpConfigFailures大於配置的DHCP最大重連次數;則會將config的autoJoinStatus設為WifiConfiguration.AUTO_JOIN_DISABLED_ON_AUTH_FAILURE;並disable當前的網絡。這時,除非手動觸發連接,否則都不會自動重連了。
Activity在inflate layout時,通過DataBindingUtil來生成綁定,從代碼看,是遍歷contentView得到View數組對象,然後通過數據綁
第一種: 使用SharedPreferences存儲數據適用范圍:保存少量的數據,且這些數據的格式非常簡單:字符串型、基本類型的值。比如應用程序的各種配置信息(如是否打開
前言Android中有許多的數據存儲方式,如果我們有少量的數據需要存儲,那麼使用:SharedPreferences、文件存儲就可以了。但是如果有大量數據需要進行讀寫,那
上一篇文章介紹了MediaPlayer相關內容,這次用兩篇文章來介紹SurfaceView的用法。網上介紹SurfaceView的用法有很多,寫法也層出不同,例如繼承Su