Android 6.0 中的 Wifi 連接


Android 6.0 中的 Wifi 連接

這幾天在寫一個軟件,結果被其中的 wifi 連接問題困擾了 3 天。

先描述下需求:

  • usb 接口接了一根 usb2serial,通過這個接口接收命令
  • 當接收到的命令為連接 wifi 時,從命令中讀出要連接的 wifi 名稱,用這個名稱去進行連接
  • 返回結果為是否能夠找到這個 wifi,找到這個 wifi 是否能夠連接

起先,我覺得這個問題是很容易的。它不就是:

  1. 構造出一個 WifiConfiguration實例;
  2. 將實例傳遞給WifiManager對象的addNetwork,此方法將會返回一個networkId(這個networkId其實就是說這個名稱的 Wifi 是第幾個添加的,它從 0 開始);
  3. 在獲得了 networkId后,就可以使用 WifiManager 對象的 enableNetwork 方法啟用此網絡(這個方法的大概意思就是,看好了,我想要用這個網絡開始連接了。一個事實是,使用這個方法,即使你之前沒有 disconnect,wifi 也會中斷);
  4. 然后使用 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,如果之前已經配置過那么就可以直接進行連接,否則自己新建一個配置進行連接。如果能夠正常的連接,那么連接信息就存儲於已配置列表中,下次連接就不用再創建配置,否則就手動將配置刪除。以保證已配置的熱點列表處於“干干凈凈”的狀態。

晚些時候給出一個示例......


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM