緣起
最近有個(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è):
- 掃描WiFi列表
- 匹配到相關(guān)的熱點(diǎn)后連接
- 返回連接結(jié)果
核心API
- 參考Android API 文檔,掃描獲取掃描結(jié)果也需要三步
- 注冊(cè) WifiManager.SCAN_RESULTS_AVAILABLE_ACTION廣播監(jiān)聽(tīng)
- 調(diào)用 WifiManager.startScan()方法掃描
- 調(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作用不同,看下源碼:
- Wifimanager.updateNetwork(config)
public int updateNetwork(WifiConfiguration config) {
if (config == null || config.networkId < 0) {
return -1;
}
return addOrUpdateNetwork(config);
}
- 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" />
文檔里都有解釋,這里就不一一列舉了。
核心代碼
- 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();
}
}
- 連接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;
}
- 廣播監(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)題留言一起探討。