Android Wifi 掃描及自動(dòng)連接

緣起

最近有個(gè)需求,要求App能夠自動(dòng)掃描到某個(gè)熱點(diǎn)然后自動(dòng)連接上熱點(diǎn)。背景是我們公司屬于IoT行業(yè),這個(gè)熱點(diǎn)是設(shè)備發(fā)出的,有固定的前綴(比如設(shè)備熱點(diǎn)名為SmartLife-xx),可以直接連接無(wú)需密碼。

本來(lái)覺(jué)得這個(gè)需求非常簡(jiǎn)單,然鵝GitHub上僅有的幾個(gè)相關(guān)Demo并不能滿足需求,只好自己擼起。

拆解需求

核心的動(dòng)作有三個(gè):

  1. 掃描WiFi列表
  2. 匹配到相關(guān)的熱點(diǎn)后連接
  3. 返回連接結(jié)果

核心API

  1. 注冊(cè) WifiManager.SCAN_RESULTS_AVAILABLE_ACTION廣播監(jiān)聽(tīng)
  2. 調(diào)用 WifiManager.startScan()方法掃描
  3. 調(diào)用 WifiManager.getScanResults()獲取掃描結(jié)果
    其中:WifiManager.startScan()方法被標(biāo)記為@deprecated ,文檔提示"The ability for apps to trigger scan requests will be removed in a future release".結(jié)合Stack Overflow這篇回復(fù),估計(jì)Android以后會(huì)閹割這個(gè)功能,好在文檔中說(shuō)API 29還能使用,Android 10 release 后允許繼續(xù)使用。如果以后真閹割這個(gè)API了我們?cè)僬医鉀Q方案。
  • 自動(dòng)連接到熱點(diǎn)也有2個(gè)API:Wifimanager.updateNetwork(config) 和Wifimanager.addNetwork(config)。這2個(gè)API作用不同,看下源碼:
  1. Wifimanager.updateNetwork(config)
 public int updateNetwork(WifiConfiguration config) {
        if (config == null || config.networkId < 0) {
            return -1;
        }
        return addOrUpdateNetwork(config);
    }
  1. Wifimanager.addNetwork(config)
public int addNetwork(WifiConfiguration config) {
        if (config == null) {
            return -1;
        }
        config.networkId = -1;
        return addOrUpdateNetwork(config);
    }

顯然,2個(gè)API的區(qū)別是config.networkId是否等于 -1;如果連接過(guò)指定的WiFi,系統(tǒng)會(huì)有記錄,config.networkId > 0,否則config.networkId = -1;源碼注釋對(duì)Wifimanager.addNetwork的說(shuō)法是

Add a new network description to the set of configured networks.
     * The {@code networkId} field of the supplied configuration object
     * is ignored.
     * <p/>
     * The new network will be marked DISABLED by default. To enable it,
     * called {@link #enableNetwork}.

對(duì)某個(gè)WiFi調(diào)用這個(gè)API后,這個(gè)WiFi就會(huì)被標(biāo)記為DISABLED,如果想要連接必須再調(diào)用enableNetwork方法。杯具的是自動(dòng)連接到熱點(diǎn)也有2個(gè)API在API 29會(huì)過(guò)期,文檔推薦使用WifiNetworkSpecifier.Builder相關(guān)方法代替,后續(xù)會(huì)繼續(xù)研究更新。

  • 返回連接結(jié)果,成功或者失敗,我注冊(cè)了廣播WifiManager.NETWORK_STATE_CHANGED_ACTION來(lái)監(jiān)聽(tīng)。在連接target WiFi之前需要斷開(kāi)當(dāng)前連接WiFi,連接成功之后會(huì)收到WiFi變化的廣播,然后獲取當(dāng)前連接的WiFi和target WiFi進(jìn)行對(duì)比,就可以知道是否連接成功。

權(quán)限

如果想要正常使用以上核心API,除了常規(guī)的

<uses-permission android:name="android.permission.INTERNET"/>

權(quán)限外,還需要以下權(quán)限

    <uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
    <uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
    <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
    <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

文檔里都有解釋,這里就不一一列舉了。

核心代碼

  1. WiFi掃描核心代碼
public void wifiScan(final AppCompatActivity mActivity) {
        index = 0;
        if (rxPermissions == null) {
            rxPermissions = new RxPermissions(mActivity);
            pool = Executors.newScheduledThreadPool(1);
        }
        rxPermissions.request(Manifest.permission.ACCESS_COARSE_LOCATION, Manifest.permission.ACCESS_FINE_LOCATION).
                subscribe(new Consumer<Boolean>() {
                    @Override
                    public void accept(Boolean aBoolean) {
                        if (aBoolean) {
                            final WifiManager wifimanager = (WifiManager) mActivity.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
                            pool.scheduleAtFixedRate(new Runnable() {
                                @Override
                                public void run() {
                                    if (index < TOTAL_TIMES) {

                                        wifimanager.startScan();
                                        Log.i("WIFI_LIST", "1 :  wifimanager.startScan()" + " index = " + index);
                                        index++;
                                    }
                                }
                            }, 0, DELAYT_TIME, TimeUnit.MILLISECONDS);

                        } else {
                            Log.i("WIFI_LIST", "1 :  no permission");
                        }

                    }
                });


    }

簡(jiǎn)單解釋下,使用了線程池循環(huán)掃描。如果沒(méi)有掃描到target WiFi 就每隔2s掃描一次,最多掃描20次。如果掃描到target WiFi就停止掃描,調(diào)用以下方法

public void stopScan() {
        if (pool != null) {
            pool.shutdown();
        }
    }
  1. 連接target WiFi 核心 代碼
 public static boolean addNetWork(WifiConfiguration config, Context context) {

        WifiManager wifimanager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);

        WifiInfo wifiinfo = wifimanager.getConnectionInfo();

        if (null != wifiinfo) {
            wifimanager.disableNetwork(wifiinfo.getNetworkId());
        }

        boolean result = false;

        if (config.networkId > 0) {
            result = wifimanager.enableNetwork(config.networkId, true);
            wifimanager.updateNetwork(config);
        } else {

            int i = wifimanager.addNetwork(config);
            result = false;

            if (i > 0) {

                wifimanager.saveConfiguration();
                return wifimanager.enableNetwork(i, true);
            }
        }

        return result;

    }
  1. 廣播監(jiān)聽(tīng)
@Override
    public void onReceive(Context context, Intent intent) {
        if(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION.equals(intent.getAction())){ //掃描到WiFi列表
            if(delegate != null){
                List<ScanResult> results =  delegate.getWifiScanResult(context);
                Log.i("WIFI_LIST", "2 :  WifiBroadcastReceiver#onReceive" + " currentIndex = " + delegate.getCurrentIndex() + " results.size = " + results.size());
                if(listener != null){
                    if(delegate.getCurrentIndex() == TOTAL_TIME){
                        listener.resultSuc(results,true);
                    }else {
                        listener.resultSuc(results,false);
                    }
                }
            }
        }else if(WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())){ //連接的WiFi變化了
            WifiInfo connectedWifiInfo = ((WifiManager) context.getSystemService(Context.WIFI_SERVICE)).getConnectionInfo();
            if(listener != null){
                listener.connectedWifiCallback(connectedWifiInfo);
            }
        }
    }

完整Demo

Github地址
歡迎star ,歡迎fork。有問(wèn)題留言一起探討。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 網(wǎng)絡(luò)連接處理 在說(shuō)WiFi之前,先來(lái)說(shuō)說(shuō)網(wǎng)絡(luò)連接處理。在Android開(kāi)發(fā)過(guò)程中,對(duì)于一個(gè)需要連接網(wǎng)絡(luò)的Andro...
    Reathin閱讀 39,607評(píng)論 19 71
  • 最近有個(gè)需求,需要做一個(gè)掃描WiFi列表的功能,也在網(wǎng)上找了一些資料,但有些資料是有問(wèn)題的,然后自己摸索了下,總結(jié)...
    snowyeti閱讀 6,541評(píng)論 0 2
  • Swift1> Swift和OC的區(qū)別1.1> Swift沒(méi)有地址/指針的概念1.2> 泛型1.3> 類型嚴(yán)謹(jǐn) 對(duì)...
    cosWriter閱讀 11,631評(píng)論 1 32
  • 前言 博主又來(lái)更新文章了,有點(diǎn)墨跡哈,很久才來(lái)一篇文章,不講究文章量的大小,只在乎內(nèi)容的實(shí)用性,幫助每一個(gè)開(kāi)發(fā)者,...
    cuieney閱讀 23,445評(píng)論 3 17
  • 前言:作為一名系統(tǒng)開(kāi)發(fā)者, 對(duì)應(yīng)用上 WiFi 的研發(fā)是一個(gè) "門(mén)外漢" 的角色, 通過(guò)閱讀 Android 源碼...
    迷你小豬閱讀 27,320評(píng)論 31 23

友情鏈接更多精彩內(nèi)容