調用 `WifiManager.addNetwork()` 返回 `true` 只表示「配置下發成功」,並不代表 WiFi 實際已關聯、獲取 IP、可通訊。直接比對 SSID 並在網路請求時仍會失敗。
Android 系統在 WiFi 連接狀態真正變化時,會發送有序廣播:
WifiManager.NETWORK_STATE_CHANGED_ACTION:標準、推薦,攜帶 `NetworkInfo` 詳情。WifiManager.SUPPLICANT_STATE_CHANGED_ACTION:較底層,Supplicant 狀態機變化(ASSOCIATING、ASSOCIATED、COMPLETED、DISCONNECTED)。public class WifiConnectVerifier {
private final Context context;
private final String targetSSID; // 目標 SSID(含雙引號,如 "\"MyWiFi\"")
private final VerificationCallback callback;
private BroadcastReceiver receiver;
public interface VerificationCallback {
void onConnected(); // 真正連上、有 IP
void onFailed(int reason); // 連接失敗/超時
}
public WifiConnectVerifier(Context context, String targetSSID, VerificationCallback callback) {
this.context = context;
this.targetSSID = targetSSID;
this.callback = callback;
}
public void startListening() {
IntentFilter filter = new IntentFilter();
filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);
// 可選:同時監聽 SUPPLICANT_STATE_CHANGED_ACTION 獲取更細粒度狀態
receiver = new BroadcastReceiver() {
@Override
public void onReceive(Context ctx, Intent intent) {
if (!WifiManager.NETWORK_STATE_CHANGED_ACTION.equals(intent.getAction())) return;
NetworkInfo info = intent.getParcelableExtra(WifiManager.EXTRA_NETWORK_INFO);
if (info == null) return;
// 只關心目標 SSID
WifiManager wm = (WifiManager) ctx.getApplicationContext().getSystemService(Context.WIFI_SERVICE);
WifiInfo current = wm.getConnectionInfo();
if (current == null || !targetSSID.equals(current.getSSID())) return;
switch (info.getDetailedState()) {
case CONNECTED:
// 關鍵:再次確認有 IP 地址
if (info.isConnected() && current.getIpAddress() != 0) {
stopListening();
callback.onConnected();
}
break;
case DISCONNECTED:
case FAILED:
case BLOCKED:
stopListening();
callback.onFailed(info.getDetailedState().ordinal());
break;
default:
// OBTAINING_IPADDR、VERIFYING_POOR_LINK 等中間態,繼續等待
break;
}
}
};
context.registerReceiver(receiver, filter);
}
public void stopListening() {
if (receiver != null) {
try { context.unregisterReceiver(receiver); } catch (IllegalArgumentException ignored) {}
receiver = null;
}
}
}
WifiManager wm = (WifiManager) getApplicationContext().getSystemService(WIFI_SERVICE);
WifiConfiguration conf = new WifiConfiguration();
conf.SSID = "\"TargetSSID\"";
conf.preSharedKey = "\"password\"";
int netId = wm.addNetwork(conf);
wm.enableNetwork(netId, true); // 觸發連接
new WifiConnectVerifier(this, "\"TargetSSID\"", new WifiConnectVerifier.VerificationCallback() {
@Override public void onConnected() {
runOnUiThread(() -> Toast.makeText(MainActivity.this, "WiFi 真正連接成功", Toast.LENGTH_SHORT).show());
// 此處發起網路請求安全可靠
}
@Override public void onFailed(int reason) {
Log.w("WiFi", "連接失敗,狀態碼: " + reason);
}
}).startListening();
// 記得在 onDestroy() 呼叫 stopListening() 避免洩漏
| 誤區 | 正確做法 |
|---|---|
| 信任 `addNetwork`/`enableNetwork` 返回值 | 只表示配置下發,不代表關聯成功 |
| 輪詢 `WifiInfo.getSSID()` 比對 | 競態條件、無法感知 IP 獲取完成 |
| 只檢查 `NetworkInfo.State.CONNECTED` | 需結合 `DetailedState.CONNECTED` + `getIpAddress() != 0` |
| 註冊廣播用 Application Context | ✅ 正確,避免 Activity 銷毀導致洩漏/崩潰 |