Android 獲取 Wifi 列表、鏈接 Wifi 及登陸驗證

1、獲取 WIFI 列表

1. 開啓 WIFI

1.1 添加權限

<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
複製代碼

1.2 使用 WifiManager 開啓 Wifi

經過 Application 的 Context 獲取到 WifiManager,調用 setWifiEnable(true) 開啓 wifi。html

public class MainActivity extends AppCompatActivity {
    private WifiManager wifiManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        wifiManager = (WifiManager) getApplicationContext().getSystemService(Context.WIFI_SERVICE);
        wifiManager.setWifiEnabled(true);
    }
}
複製代碼

2. 掃描 WIFI 並獲取結果

2.1 添加權限

<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
複製代碼

2.2 開始掃描

使用 startScan() 方法開始掃描附近的 WIFI 信號。java

wifiManager.startScan();
複製代碼

2.3 獲取掃描結果

啓動掃描後並不能當即獲取到掃描結果,系統在掃描結果準備好以後會發送 WifiManager.SCAN_RESULTS_AVAILABLE_ACTION 廣播,能夠經過監聽該廣播,並在收到廣播後調用 getScanResults() 獲取到掃描結果。android

注意:getScanResults() 在 Android 6.0 以上的設備返回爲空列表的解決辦法。git

  1. 將 targetSdkVersion 設置爲 22 便可獲取到有效結果。
  2. 添加 ACCESS_COARSE_LOCATION 或 ACCESS_FINE_LOCATION 權限而且開啓 GPS 定位,才能獲取到有效結果。
@Override
protected void onStart() {
    super.onStart();
    IntentFilter filter = new IntentFilter();
    filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    registerReceiver(broadcastReceiver, filter);
}

@Override
protected void onStop() {
    super.onStop();
    unregisterReceiver(broadcastReceiver);
}

private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        List<ScanResult> scanResults = wifiManager.getScanResults();
    }
};
複製代碼

ScanResult 中保存了 WIFI 的名稱、加密方式、信號強度等信息。github

3. 獲取已保存的 WIFI 列表

使用 WifiManager 的 getConfiguredNetworks() 方法獲取設備中已保存的 WIFI 列表的配置信息。網絡

List<WifiConfiguration> wifiConfigurations = WifiUtils.getInstance().getConfiguredNetworks();
複製代碼

WifiConfiguration 中除了有 WIFI 的名稱、加密方式外,還有 networkId、狀態及相關配置信息。app

4. 獲取當前鏈接的 WIFI 信息和狀態

經過 ConnectivityManager 的 getActiveNetworkInfo() 方法獲取到當前鏈接的網絡信息;經過 WifiManager 的 getConnectionInfo() 方法獲取當前鏈接的 WIFI 信息;根據當前 WIFI 的 networkId 獲取到當前 WIFI 的配置和狀態。ide

private NetworkInfo lastNetworkInfo;
private WifiInfo lastWifiInfo;
private WifiConfiguration lastWifiConfiguration;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    ...
    lastNetworkInfo = getActiveNetworkInfo();
    lastWifiInfo = wifiManager.getConnectionInfo();
    if (lastWifiInfo != null && lastWifiInfo.getNetworkId() != -1) {
        lastWifiConfiguration = getWifiConfigurationForNetworkId(lastWifiInfo.getNetworkId());
    }
}

/** * 根據 NetworkId 獲取 WifiConfiguration 信息 * * @param networkId 須要獲取 WifiConfiguration 信息的 networkId * @return 指定 networkId 的 WifiConfiguration 信息 */
private WifiConfiguration getWifiConfigurationForNetworkId(int networkId) {
    final List<WifiConfiguration> configs = wifiManager.getConfiguredNetworks();
    if (configs != null) {
        for (WifiConfiguration config : configs) {
            if (lastWifiInfo != null && networkId == config.networkId) {
                return config;
            }
        }
    }
    return null;
}

/** * 獲取當前網絡信息 */
public NetworkInfo getActiveNetworkInfo() {
    ConnectivityManager cm = (ConnectivityManager) this.getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm != null) {
        return cm.getActiveNetworkInfo();
    }
    return null;
}
複製代碼

5. 建立 AccessPoint 來保存 WIFI 的信息和狀態

分別獲取到附近的 WIFI 列表和已保存的 WIFI 列表後,須要匹配附近的 WIFI 和已保存的 WIFI,對已匹配上的 WIFI 須要同事保存其信息和狀態。經過查看源碼能夠知道,Settings 中 WIFI 設置的列表是將 WIFI 信息和狀態保存到了 AccessPoint 中。可是 AccessPoint 沒法直接使用,因此須要本身建立一個 AccessPoint 類。ui

AccessPoint

經過 ScanResult 列表和 WifiConfiguration 列表獲得 AccessPoint 列表this

private NetworkInfo lastNetworkInfo;
private WifiInfo lastWifiInfo;
private WifiConfiguration lastWifiConfiguration;
private List<AccessPoint> lastAccessPoints = new CopyOnWriteArrayList<>();

private void updateAccessPoints() {
    Single.create((SingleOnSubscribe<List<AccessPoint>>) emitter -> {
        List<AccessPoint> accessPoints = new ArrayList<>();
        List<ScanResult> scanResults = wifiManager.getScanResults();
        if (lastWifiInfo != null && lastWifiInfo.getNetworkId() != AccessPoint.INVALID_NETWORK_ID) {
            lastWifiConfiguration = getWifiConfigurationForNetworkId(lastWifiInfo.getNetworkId());
        }
        if (scanResults != null) {
            for (ScanResult scanResult : scanResults) {
                if (TextUtils.isEmpty(scanResult.SSID)) {
                    continue;
                }
                AccessPoint accessPoint = new AccessPoint(this.getApplicationContext(), scanResult);
                if (accessPoints.contains(accessPoint)) {
                    continue;
                }
                List<WifiConfiguration> wifiConfigurations = wifiManager.getConfiguredNetworks();
                if (wifiConfigurations != null) {
                    for (WifiConfiguration config : wifiConfigurations) {
                        if (accessPoint.getQuotedSSID().equals(config.SSID)) {
                            accessPoint.setWifiConfiguration(config);
                        }
                    }
                }
                if (lastWifiInfo != null && lastNetworkInfo != null) {
                    accessPoint.update(lastWifiConfiguration, lastWifiInfo, lastNetworkInfo);
                }
                accessPoints.add(accessPoint);
            }
        }
        Collections.sort(accessPoints);
        emitter.onSuccess(accessPoints);
    })
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new SingleObserver<List<AccessPoint>>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onSuccess(List<AccessPoint> accessPoints) {
                    // 在界面上展現
                }
                @Override
                public void onError(Throwable e) {
                }
            });
}
複製代碼

2、WIFI 列表刷新

獲取到 WIFI 列表後,須要定時刷新,而且在 WIFI 鏈接發生變化時也須要刷新列表

1. 定時刷新

每隔 10 秒鐘調用一次 startScan() 方法,而後在廣播接收者中更新列表

private Disposable scanWifi;

@Override
protected void onStart() {
    super.onStart();
    scanWifi = Observable.interval(0, 10, TimeUnit.SECONDS)
            .observeOn(Schedulers.io())
            .doOnNext(aLong -> wifiManager.startScan())
            .subscribe();
    IntentFilter filter = new IntentFilter();
    filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    registerReceiver(broadcastReceiver, filter);
}

@Override
protected void onStop() {
    super.onStop();
    if (scanWifi != null && scanWifi.isDisposed()) {
        scanWifi.dispose();
    }
    unregisterReceiver(broadcastReceiver);
}

private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        updateAccessPoints();
    }
};
複製代碼

2. 監聽網絡變化

網絡變化的廣播:

  • ConnectivityManager.CONNECTIVITY_ACTION

    "android.net.conn.CONNECTIVITY_CHANGE",網絡鏈接發生變化時的廣播,如:創建鏈接或斷開鏈接等

  • WifiManager.CONFIGURED_NETWORKS_CHANGED_ACTION

    "android.net.wifi.CONFIGURED_NETWORKS_CHANGE",已配置的 WIFI 網絡發生變化時的廣播,如:添加,更新或刪除等

  • WifiManager.LINK_CONFIGURATION_CHANGED_ACTION

    "android.net.wifi.LINK_CONFIGURATION_CHANGED",WIFI 連接配置發生變化時的廣播

  • WifiManager.NETWORK_STATE_CHANGED_ACTION

    "android.net.wifi.STATE_CHANGE",WIFI 的鏈接狀態發生變化時的廣播

  • WifiManager.SUPPLICANT_STATE_CHANGED_ACTION

    "android.net.wifi.supplicant.STATE_CHANGE",與 WIFI 創建鏈接的狀態發生變化時的廣播,如:密碼錯誤等

註冊廣播

@Override
protected void onStart() {
    super.onStart();
    ...
    IntentFilter filter = new IntentFilter();
    filter.addAction(ConnectivityManager.CONNECTIVITY_ACTION);
    filter.addAction(WifiManager.SCAN_RESULTS_AVAILABLE_ACTION);
    filter.addAction("android.net.wifi.CONFIGURED_NETWORKS_CHANGE");
    filter.addAction("android.net.wifi.LINK_CONFIGURATION_CHANGED");
    filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
    filter.addAction(WifiManager.SUPPLICANT_STATE_CHANGED_ACTION);
    registerReceiver(broadcastReceiver, filter);
}
複製代碼

接收廣播並更新 wifi 列表

private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (action != null) {
            switch (action) {
                case ConnectivityManager.CONNECTIVITY_ACTION:
                case WifiManager.SCAN_RESULTS_AVAILABLE_ACTION:
                case "android.net.wifi.CONFIGURED_NETWORKS_CHANGE":
                case "android.net.wifi.LINK_CONFIGURATION_CHANGED":
                    updateAccessPoints();
                    break;
                case WifiManager.NETWORK_STATE_CHANGED_ACTION:
                    updateAccessPoints();
                    NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
                    updateNetworkInfo(info);
                    break;
                case WifiManager.SUPPLICANT_STATE_CHANGED_ACTION:
                    int error = intent.getIntExtra(WifiManager.EXTRA_SUPPLICANT_ERROR, -1);
                    if (error == WifiManager.ERROR_AUTHENTICATING) {
                        // 處理密碼錯誤
                        handlePasswordError();
                    }
                    break;
            }
        }
    }
};
複製代碼

註冊網絡回調

@Override
protected void onStart() {
    super.onStart();
    ...
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm != null) {
        // 只關心 WIFI
        NetworkRequest.Builder request = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_WIFI);
        cm.registerNetworkCallback(request.build(), callback);
    }
}

@Override
protected void onStop() {
    super.onStop();
    ...
    ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE);
    if (cm != null) {
        cm.unregisterNetworkCallback(callback);
    }
}

private ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {

    @Override
    // 網絡可用時
    public void onAvailable(Network network) {
        super.onAvailable(network);
        setCurrentNetwork(network);
        portalCurrentWifi();
    }

    @Override
    // 聯網能力發生變化時
    public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
        super.onCapabilitiesChanged(network, networkCapabilities);
        if (network.equals(getCurrentNetwork())) {
            updateNetworkInfo(null);
        }
    }
};
複製代碼

更新當前鏈接 wifi 的狀態

private NetworkInfo lastNetworkInfo;
private WifiInfo lastWifiInfo;
private WifiConfiguration lastWifiConfiguration;
private List<AccessPoint> lastAccessPoints = new CopyOnWriteArrayList<>();
private int lastPortalNetworkId = AccessPoint.INVALID_NETWORK_ID;

public void updateNetworkInfo(NetworkInfo networkInfo) {
    Single.create((SingleOnSubscribe<List<AccessPoint>>) emitter -> {
        if (networkInfo != null) {
            lastNetworkInfo = networkInfo;
        }
        lastWifiInfo = wifiManager.getConnectionInfo();
        if (lastWifiInfo.getNetworkId() == AccessPoint.INVALID_NETWORK_ID) {
            // 表示沒有 wifi 鏈接,lastPortalNetworkId 置爲無效
            lastPortalNetworkId = AccessPoint.INVALID_NETWORK_ID;
        }
        if (lastWifiInfo != null && lastWifiInfo.getNetworkId() != AccessPoint.INVALID_NETWORK_ID) {
            lastWifiConfiguration = getWifiConfigurationForNetworkId(lastWifiInfo.getNetworkId());
        }
        boolean reorder = false;
        for (AccessPoint accessPoint : lastAccessPoints) {
            if (accessPoint.update(lastWifiConfiguration, lastWifiInfo, lastNetworkInfo)) {
                reorder = true;
            }
        }
        if (reorder) {
            Collections.sort(lastAccessPoints);
        }
        emitter.onSuccess(lastAccessPoints);
    }).subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(new SingleObserver<List<AccessPoint>>() {
                @Override
                public void onSubscribe(Disposable d) {
                }
                @Override
                public void onSuccess(List<AccessPoint> accessPoints) {
                    // 更新列表
                    adapter.setAccessPoints(accessPoints);
                }
                @Override
                public void onError(Throwable e) {
                    e.printStackTrace();
                }
            });
}
複製代碼

3、WIFI 鏈接

須要用到的 API

  • addNetwork(WifiConfiguration config)
  • enableNetwork(int netId, boolean attemptConnect)
private void connect(AccessPoint accessPoint) {
    // 建立 WifiConfiguration
    accessPoint.generateNetworkConfig();
    // 添加 WifiConfiguration
    int networkId = wifiManager.addNetwork(accessPoint.wifiConfiguration);
    // 啓用並嘗試鏈接到 wifi
    wifiManager.enableNetwork(networkId, true);
}
複製代碼
/** * 生成 wifiConfiguration */
public void generateNetworkConfig() {
    if (wifiConfiguration != null)
        return;
    wifiConfiguration = new WifiConfiguration();
    wifiConfiguration.SSID = getQuotedSSID();
    switch (security) {
        case SECURITY_NONE:
            wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            break;
        case SECURITY_WEP:
            wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE);
            wifiConfiguration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN);
            wifiConfiguration.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.SHARED);
            int length = password.length();
            if ((length == 10 || length == 26 || length == 58) &&
                    password.matches("[0-9A-Fa-f]*")) {
                wifiConfiguration.wepKeys[0] = password;
            } else {
                wifiConfiguration.wepKeys[0] = '"' + password + '"';
            }
            break;
        case SECURITY_PSK:
            wifiConfiguration.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK);
            if (password.matches("[0-9A-Fa-f]{64}")) {
                wifiConfiguration.preSharedKey = password;
            } else {
                wifiConfiguration.preSharedKey = '"' + password + '"';
            }
            break;
        case SECURITY_EAP:
            // 暫時忽略
            break;
    }
}
複製代碼

判斷 wifi 是否須要登陸

網絡可用時,鏈接指定網址根據返回碼是不是 204 判斷 wifi 是否須要登陸

private ConnectivityManager.NetworkCallback callback = new ConnectivityManager.NetworkCallback() {

    @Override
    // 網絡可用時
    public void onAvailable(Network network) {
        super.onAvailable(network);
        setCurrentNetwork(network);
        portalCurrentWifi();
    }
};

public void portalCurrentWifi() {
    if (lastWifiInfo.getNetworkId() != lastPortalNetworkId) {
        lastPortalNetworkId = lastWifiInfo.getNetworkId();
        Single.create((SingleOnSubscribe<Boolean>) emitter -> {
            Network currentNetwork = getCurrentNetwork();
            HttpURLConnection urlConnection = null;
            try {
                // 使用當前的網絡打開連接
                urlConnection = (HttpURLConnection) currentNetwork.openConnection(new URL("http://connect.rom.miui.com/generate_204"));
                urlConnection.setInstanceFollowRedirects(false);
                urlConnection.setConnectTimeout(10000);
                urlConnection.setReadTimeout(10000);
                urlConnection.setUseCaches(false);
                urlConnection.getInputStream();
                int responseCode = urlConnection.getResponseCode();
                if (responseCode == 200 && urlConnection.getContentLength() == 0) {
                    responseCode = 204;
                }
                emitter.onSuccess(responseCode != 204 && responseCode >= 200 && responseCode <= 399);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (urlConnection != null) {
                    urlConnection.disconnect();
                }
            }
        })
                .retry(throwable -> throwable instanceof UnknownHostException)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new SingleObserver<Boolean>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                    }
                    @Override
                    public void onSuccess(Boolean aBoolean) {
                        if (aBoolean) {
                            // 調用網絡登陸界面
                        }
                    }
                    @Override
                    public void onError(Throwable e) {
                        e.printStackTrace();
                    }
                });
    }
}
複製代碼

4、取消保存

須要用到的 API

  • removeNetwork(int netId)
public void forgetWifi(AccessPoint accessPoint) {
    wifiManager.removeNetwork(accessPoint.wifiConfiguration.networkId);
}
複製代碼

PS 權限問題:對於普通應用只能取消保存本身添加的 wifi,沒法取消保存其它應用添加的 wifi;若是須要取消保存其它應用添加的 wifi,須要添加以下權限,並做爲系統應用。

<!--覆蓋wifi配置 須要是 system app-->
<uses-permission android:name="android.permission.OVERRIDE_WIFI_CONFIG" />
複製代碼

Demo:github.com/zly394/Wifi…

相關文章
相關標籤/搜索