7. 连接WiFi(推荐方式):NetworkSpecifier与WifiNetworkSpecifier.Builder(Android 10+)、ConnectivityManager.requestNetwork()、自动连接流程
说实话,Android 10 之前的 WiFi 连接方式,我一直觉得有点「拧巴」。你要去操作 WifiManager,还要处理各种隐式广播,稍不留神就踩坑。我自己早期做项目时,就因为 addNetwork 和 enableNetwork 的调用时序问题,在测试机上反复断连,差点被测试小姐姐追着打。
好在 Android 10 之后,Google 终于给出了一个更优雅的方案——NetworkSpecifier。说白了,它让你像「点菜」一样描述你要连的 WiFi,然后系统帮你搞定剩下的活。我个人习惯把这个流程叫做「声明式连接」。
7.1 核心思路:从「操作硬件」到「声明需求」
老方式里,你得自己管 WifiConfiguration,自己调 connect。新方式呢?你只需要告诉系统:
- 我要连哪个 SSID?
- 用什么加密方式?
- 密码是多少?
剩下的扫描、认证、分配 IP——全交给 ConnectivityManager 去协调。你想想看,这就像你去餐厅吃饭,以前你得自己进厨房炒菜,现在你只管点单,厨师(系统)帮你做。
7.2 主角登场:WifiNetworkSpecifier.Builder
这个 Builder 是 Android 10 新增的。它的作用就是构建一个「网络描述符」。我一般这样用:
// 构建一个针对特定 SSID 的请求
val specifier = WifiNetworkSpecifier.Builder()
.setSsid("MyHomeWiFi")
.setWpa2Passphrase("my_password_123")
.build()
嗯,这里要注意:setWpa2Passphrase 只适用于 WPA2 加密。如果你遇到的是企业级加密(比如 802.1X),那就得用 setWpa2EnterpriseConfig 或者 setWpa3EnterpriseConfig。我在项目中遇到过一家商场的热点,用的就是 WPA3-Enterprise,当时没注意,折腾了半天才发现是加密类型不匹配。
7.3 发起请求:ConnectivityManager.requestNetwork()
有了描述符,下一步就是把它扔给 ConnectivityManager。这里有个关键点:你需要创建一个 NetworkRequest,然后把 specifier 塞进去。
val request = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(specifier)
.build()
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val callback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
// 网络可用!可以开始传输数据了
Log.d("WiFiDemo", "网络已连接: $network")
// 记得绑定网络,否则可能走蜂窝数据
connectivityManager.bindProcessToNetwork(network)
}
override fun onUnavailable() {
// 超时或无法找到匹配网络
Log.w("WiFiDemo", "网络不可用,请检查 SSID 或密码")
}
override fun onLost(network: Network) {
// 连接断开
Log.d("WiFiDemo", "网络丢失: $network")
}
}
connectivityManager.requestNetwork(request, callback)
你可能会问:为什么要 bindProcessToNetwork?我记得第一次写这个代码时,发现 WiFi 连上了,但 App 还是走蜂窝流量。后来查文档才明白——onAvailable 只是告诉你网络可用,但系统不会自动把你的进程流量切过去。你得手动绑定,或者用 Network.openConnection() 来指定网络。
7.4 自动连接流程:系统帮你做了什么?
当你调用 requestNetwork 后,系统内部会走一套完整的流程。我画了一张图,方便你理解:
从图上你能看到,整个流程是线性的。但有一个细节容易被忽略:系统会弹一个用户确认对话框。没错,即使你代码里写好了 SSID 和密码,Android 10+ 仍然会要求用户点击「连接」来确认。这是隐私和安全方面的考虑——防止 App 在后台偷偷连 WiFi。
onUnavailable 会被触发。如果用户同意,后续系统会自动重连,不再弹框。
7.5 避坑指南:我曾经踩过的三个坑
讲几个我实际项目中遇到的坑,希望能帮你省点时间:
- 坑一:忘记释放 Callback ——
requestNetwork返回的是一个NetworkRequest对象,如果你在 Activity 销毁时没有调用unregisterNetworkCallback,会导致内存泄漏。我建议在onStop或onDestroy里做清理。 - 坑二:bindProcessToNetwork 的副作用 —— 绑定后,所有 socket 都会走这个网络。如果你同时需要蜂窝和 WiFi,建议用
Network.openConnection()按需绑定,而不是全局绑定。 - 坑三:SSID 带引号 —— 有些设备返回的 SSID 带双引号,比如
"MyWiFi"。但setSsid方法要求传入不带引号的原始字符串。我曾经因为这个 bug 排查了一下午。
7.6 什么时候用这个方式?
我个人建议:如果你的 App 只支持 Android 10+(API 29+),那就无脑用 NetworkSpecifier。它比老方式更安全、更稳定。但如果你的 App 还要兼容老设备,那就得同时保留 WifiManager 的 fallback 方案。
Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q 来做版本判断,分别走新旧两套逻辑。
7.7 总结一下
说白了,NetworkSpecifier + ConnectivityManager.requestNetwork() 就是 Android 官方推荐的 WiFi 连接方式。它把复杂的底层操作封装起来,让你专注于「连哪个网」而不是「怎么连」。你只需要构建好描述符,发起请求,然后在回调里处理结果就行。
嗯,这一节的内容就到这里。代码不多,但背后的流程值得你多跑几遍试试。尤其是那个用户确认对话框,不同厂商的 ROM 表现可能不一样——我在小米和华为的设备上就遇到过不同的 UI 风格。