Android 6.0 中的 Wifi 連接
這幾天在寫一個軟件,結果被其中的 wifi 連接問題困擾了 3 天。
先描述下需求:
- usb 接口接了一根 usb2serial,通過這個接口接收命令
- 當接收到的命令為連接 wifi 時,從命令中讀出要連接的 wifi 名稱,用這個名稱去進行連接
- 返回結果為是否能夠找到這個 wifi,找到這個 wifi 是否能夠連接
起先,我覺得這個問題是很容易的。它不就是:
- 構造出一個
WifiConfiguration
實例; - 將實例傳遞給
WifiManager
對象的addNetwork
,此方法將會返回一個networkId
(這個networkId
其實就是說這個名稱的 Wifi 是第幾個添加的,它從 0 開始); - 在獲得了
networkId
后,就可以使用WifiManager
對象的enableNetwork
方法啟用此網絡(這個方法的大概意思就是,看好了,我想要用這個網絡開始連接了。一個事實是,使用這個方法,即使你之前沒有 disconnect,wifi 也會中斷); - 然后使用
WifiManager
對象的reconnect
重新進行網絡連接。
看着這一切都十分的完美。但是,要是真是如此,那么絕對不會折磨我 3 天的時間了。
第一個坑:權限問題
其實說這個問題是第一個坑,完全是順帶提了一下,實際上,我完全沒有在這個坑中呆過。這個坑是因為 Android 6 引入了一個動態權限問題。需要額外的一些代碼處理。大家可以自己搜索下搞定或者是使用一些額外的庫。
第二個坑:addNetwork 方法的返回值為 -1
這個也是 android 6 之后才有的東西。具體返回值為-1
的情況為指定的 SSID 由其他應用程序連接(包括系統的設置)。對於這一情況,其實一句話形容就是誰拉的屎誰去擦屁股(系統的 Setting 可以擦所有 app 的屁股)。所以,當 APP 本身已經連接 SSID 為 “ABCD” 的熱點一次,第二次連接時,如果依舊以最開始的步驟進行處理,那么,依舊會得到和之前相同的 networkId
,但是,假如系統或者是其他的 APP 已經連接過 “ABCD”,那么,你只能得到一個 -1。
這里,-1
還會引發另一個問題,就是當下一步想要使用 enableNetwork
進行網絡的一個預處理,將會發現,怎么阻塞在這個函數上了。
第三個坑:Android 將會自動連接其他的 Wifi
在看第二個坑的時候,有人一定很好奇,你為什么不使用getConfiguredNetworks
來獲得已經配置過的 Wifi 列表,這樣就不需要再次配置了。理想很好,但是這樣很不好。一個情況就是,假設現在已經存在“ABCD”和“1234”這2個熱點,其中“1234”這個熱點之前已經連接過,且沒有刪除記錄。在這一情況下,連接“ABCD”失敗后,系統會自動連接到"1234"上去。
為什么會這樣子,我是很不解的。這里,看下 SDK 中的內容:
/**
* Allow a previously configured network to be associated with. If
* <code>disableOthers</code> is true, then all other configured
* networks are disabled, and an attempt to connect to the selected
* network is initiated. This may result in the asynchronous delivery
* of state change events.
* <p>
* <b>Note:</b> If an application's target SDK version is
* {@link android.os.Build.VERSION_CODES#LOLLIPOP} or newer, network
* communication may not use Wi-Fi even if Wi-Fi is connected; traffic may
* instead be sent through another network, such as cellular data,
* Bluetooth tethering, or Ethernet. For example, traffic will never use a
* Wi-Fi network that does not provide Internet access (e.g. a wireless
* printer), if another network that does offer Internet access (e.g.
* cellular data) is available. Applications that need to ensure that their
* network traffic uses Wi-Fi should use APIs such as
* {@link Network#bindSocket(java.net.Socket)},
* {@link Network#openConnection(java.net.URL)}, or
* {@link ConnectivityManager#bindProcessToNetwork} to do so.
*
* @param netId the ID of the network in the list of configured networks
* @param disableOthers if true, disable all other networks. The way to
* select a particular network to connect to is specify {@code true}
* for this parameter.
* @return {@code true} if the operation succeeded
*/
public boolean enableNetwork(int netId, boolean disableOthers) {
final boolean pin = disableOthers && mTargetSdkVersion < Build.VERSION_CODES.LOLLIPOP;
if (pin) {
NetworkRequest request = new NetworkRequest.Builder()
.clearCapabilities()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.build();
NetworkPinner.pin(mContext, request);
}
boolean success;
try {
success = mService.enableNetwork(netId, disableOthers);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
if (pin && !success) {
NetworkPinner.unpin();
}
return success;
}
在enableNetwork
函數的開始,是有這樣的一段話的,其中說明這個函數需要兩個參數,一個是需要連接的'networkID',也就是之前通過addNetwork
獲得的返回值,另一個參數是說如果為真,則 disable 其他的網絡,然后讓系統老老實實的連接到指定的網絡。這里 disable 算是一個很有意思的詞,它到底是起到 disconnect 的作用呢,還是 block other 的作用呢。前文也說了,就算沒有調用 disconnect
函數,也會斷開連接,從這一現象推測,它應該是會斷開其他的連接。
最開始的時候我以為這個方法起到了 block other 的作用,但是看結果顯然不是。
另外,也找了 SDK 中所有可能的方法,沒有找到禁止在一個連接失敗的情況下連接其它熱點的方法。那么,最好的辦法就是,在開始使用之前,系統不曾連接過一個熱點,之后每次連接結束后由程序刪除已經配置好的連接。因為手動刪除了,所以getConfiguredNetworks
方法也就用不上了。
到底如何在 Android 6.0 以上的系統中連接指定名稱的 Wifi
根據這幾天的琢磨,以下的流程應該是比較的合適的:
- 1根據得到的 SSID 信息,在已配置的 wifi 列表中進行搜索,后得到 networkid
- 2否則,根據 SSID ,在所有可用 WIFI 列表中進行搜索(非隱藏網絡),如果找到,進行配置后 add,得到對應的 networkid
- 2如果是隱藏網絡,則直接進行配置,然后通過 add 方法獲得對應的 networkid
- 3使用得到的id進行添加網絡,然后嘗試進行連接。
在第三點中,我進行了額外的處理,即當返回值為false時,如果配置文件是 app 生成的,那么將會主動將其刪除。否則的話,這個配置將會存儲於已配置的熱點列表中(考慮下,反正是連接不上,又是自己創建的,那就刪除唄,指不定是手殘 SSID 輸入錯誤呢)。
通過以上的幾個步驟,可以保證對於一個指定的 SSID,如果之前已經配置過那么就可以直接進行連接,否則自己新建一個配置進行連接。如果能夠正常的連接,那么連接信息就存儲於已配置列表中,下次連接就不用再創建配置,否則就手動將配置刪除。以保證已配置的熱點列表處於“干干凈凈”的狀態。
晚些時候給出一個示例......