适用于:Android 10 及以上(API 29+) 解决问题:在不修改系统 WiFi 配置的前提下,App 临时连接指定 WiFi
一、背景说明
从 Android 10(API 29) 开始:
- ❌ 不允许 App 直接添加 / 修改系统 WiFi 配置
- ❌ 不允许静默切换 WiFi
- ✅ 只能使用 WifiNetworkSpecifier + ConnectivityManager
该方式特点:
| 特性 | 说明 |
|---|---|
| 是否需要用户确认 | ❌ 不需要弹系统连接确认 |
| 是否保存到系统WiFi列表 | ❌ 不保存 |
| 是否影响系统默认网络 | ❌ 仅 App 内生效 |
| 适合场景 | IoT 配网、设备直连、局域网通信 |
二、核心原理
整体流程如下:
WifiNetworkSpecifier 指定 SSID + 密码
↓
NetworkRequest 请求 WiFi 网络
↓
ConnectivityManager.requestNetwork()
↓
NetworkCallback.onAvailable()
↓
bindProcessToNetwork(network)
关键点:
⚠️ 必须调用 bindProcessToNetwork(),否则 App 网络仍走原来的网络。
三、所需权限
AndroidManifest.xml
bash
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>
运行时要求
- 必须开启:📍定位服务
- 必须授予:定位权限
否则: - 扫描不到 WiFi
- 连接会失败
四、完整工具类封装
✅ WifiConnectionHelper.java
bash
import android.annotation.SuppressLint;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.wifi.ScanResult;
import android.net.wifi.WifiInfo;
import android.net.wifi.WifiManager;
import android.net.wifi.WifiNetworkSpecifier;
import android.os.Build;
import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi;
import java.util.List;
/**
* Author: Su
* Date: 2025/8/16
* WifiConnectionHelper
* 封装 WifiNetworkSpecifier 功能:
* - 连接指定 WiFi
* - 断开 WiFi
* - 获取当前 WiFi 信息
* - 扫描 WiFi 列表
* 适用 Android 10+ (API 29+)
*/
@RequiresApi(api = Build.VERSION_CODES.Q)
public class WifiConnectionHelper {
private static final String TAG = "WifiConnectionHelper";
private final Context context;
private final ConnectivityManager connectivityManager;
private ConnectivityManager.NetworkCallback networkCallback;
private Network currentNetwork;
private WifiCallback wifiCallback;
private final WifiManager wifiManager;
public WifiConnectionHelper(Context context) {
this.context = context.getApplicationContext();
this.connectivityManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
this.wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
}
/**
* 设置 WiFi 回调监听
*/
public void setWifiCallback(WifiCallback callback) {
this.wifiCallback = callback;
}
/**
* 连接指定 WiFi
*
* @param ssid WiFi 名称
* @param password WiFi 密码
*/
public void connectToWifi(String ssid, String password) {
if (TextUtils.isEmpty(ssid)) {
LogUtils.e(TAG, "SSID 不能为空");
if (wifiCallback != null) wifiCallback.onConnectFailed("SSID 不能为空");
return;
}
WifiNetworkSpecifier.Builder builder = new WifiNetworkSpecifier.Builder().setSsid(ssid);
if (!TextUtils.isEmpty(password)) {
builder.setWpa2Passphrase(password);
}
WifiNetworkSpecifier specifier = builder.build();
NetworkRequest request = new NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.setNetworkSpecifier(specifier)
.build();
// 清理之前回调
if (networkCallback != null) {
connectivityManager.unregisterNetworkCallback(networkCallback);
}
networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(@NonNull Network network) {
super.onAvailable(network);
LogUtils.i(TAG, "已连接到 WiFi: " + ssid);
currentNetwork = network;
connectivityManager.bindProcessToNetwork(network);
if (wifiCallback != null) wifiCallback.onConnectSuccess(ssid);
}
@Override
public void onUnavailable() {
super.onUnavailable();
LogUtils.w(TAG, "WiFi 连接失败: " + ssid);
if (wifiCallback != null) wifiCallback.onConnectFailed("WiFi 连接失败");
}
@Override
public void onLost(@NonNull Network network) {
super.onLost(network);
LogUtils.w(TAG, "WiFi 连接丢失: " + ssid);
if (currentNetwork != null && currentNetwork.equals(network)) {
connectivityManager.bindProcessToNetwork(null);
currentNetwork = null;
if (wifiCallback != null) wifiCallback.onDisconnectSuccess(ssid);
}
}
};
connectivityManager.requestNetwork(request, networkCallback);
}
/**
* 断开当前 WiFi 连接
*/
public void disconnect() {
if (networkCallback != null) {
try {
connectivityManager.unregisterNetworkCallback(networkCallback);
connectivityManager.bindProcessToNetwork(null);
currentNetwork = null;
LogUtils.i(TAG, "已断开 WiFi 连接");
if (wifiCallback != null) wifiCallback.onDisconnectSuccess(null);
} catch (Exception e) {
LogUtils.e(TAG, "断开 WiFi 失败");
if (wifiCallback != null) wifiCallback.onDisconnectFailed(e.getMessage());
}
}else {
LogUtils.e(TAG, "断开 WiFi 失败");
if (wifiCallback != null) wifiCallback.onDisconnectFailed("NetworkCallback = null");
}
}
/**
* 判断权限,断开当前 WiFi 连接
*/
public boolean callBackState() {
return networkCallback != null;
}
/**
* 获取当前连接的 WiFi 信息
*/
@SuppressLint("MissingPermission")
public void getCurrentWifiInfo() {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = wifiManager.getConnectionInfo();
if (wifiCallback != null) wifiCallback.onCurrentWifiInfo(wifiInfo);
}
/**
* 扫描 WiFi 列表(需要 ACCESS_FINE_LOCATION 权限 & 定位开启)
*/
@SuppressLint("MissingPermission")
public void scanWifiList() {
WifiManager wifiManager = (WifiManager) context.getSystemService(Context.WIFI_SERVICE);
boolean success = wifiManager.startScan();
if (!success) {
LogUtils.w(TAG, "WiFi 扫描启动失败");
}
List<ScanResult> results = wifiManager.getScanResults();
if (wifiCallback != null) wifiCallback.onScanResults(results);
}
/**
* WiFi 回调接口
*/
public interface WifiCallback {
void onConnectSuccess(String ssid); // 连接成功
void onConnectFailed(String reason); // 连接失败
void onDisconnectSuccess(String ssid); // 断开成功
void onDisconnectFailed(String reason); // 断开失败
void onCurrentWifiInfo(WifiInfo wifiInfo); // 当前 WiFi 信息
void onScanResults(List<ScanResult> results);// WiFi 列表扫描结果
}
/**
* 判断当前 WiFi 是否开启
*
* @return true 表示已开启 WiFi,false 表示关闭
*/
public boolean isWifiEnabled() {
return wifiManager.isWifiEnabled();
}
/** 设置 WiFi 开关(Android 10 及以上会引导用户手动操作) */
public void setWifiEnabled(Activity activity, boolean enabled) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10 及以上,跳系统 WiFi 面板
Intent mIntent = new Intent();
mIntent.setAction(Settings.ACTION_WIFI_SETTINGS);
activity.startActivity(mIntent);
} else {
// Android 9 及以下,可以直接开关
wifiManager.setWifiEnabled(enabled);
}
}
}
五、使用示例
1. 初始化
bash
wifiHelper = new WifiConnectionHelper(this);
wifiHelper.setWifiCallback(new WifiConnectionHelper.WifiCallback() {
@Override
public void onConnectSuccess(String ssid) {
Log.i("WiFi", "连接成功:" + ssid);
}
@Override
public void onConnectFailed(String reason) {
Log.e("WiFi", "连接失败:" + reason);
}
@Override
public void onDisconnectSuccess(String ssid) {
Log.i("WiFi", "已断开");
}
@Override
public void onDisconnectFailed(String reason) {}
@Override
public void onCurrentWifiInfo(WifiInfo wifiInfo) {}
@Override
public void onScanResults(List<ScanResult> results) {}
});
2. 连接 WiFi
bash
wifiHelper.connectToWifi("MyWifi", "12345678");
- 断开 WiFi
bash
wifiHelper.disconnect();
4. 扫描 WiFi
bash
wifiHelper.scanWifiList();
六、常见坑位总结(非常重要)
❗1. 没有调用 bindProcessToNetwork
后果:
- 表面显示已连接
- 实际请求仍走 4G / 原WiFi
必须:
bash
connectivityManager.bindProcessToNetwork(network);
❗2. Android 10+ 无法静默开关 WiFi
系统限制:
- setWifiEnabled() 失效
- 必须跳转系统设置页
正确做法:
bash
startActivity(new Intent(Settings.ACTION_WIFI_SETTINGS));
❗3. 扫描不到 WiFi
必须满足:
- 已授权定位权限
- 系统定位已开启
否则:
bash
getScanResults() == 空
❗4. 连接成功但网络断流
常见原因:
- 路由器无外网
- DNS 异常
- 设备限制局域网通信
建议:
bash
request.addCapability(NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED)
七、适用业务场景
该方案非常适合:
- ✅ 智能硬件配网
- ✅ 设备热点直连
- ✅ 局域网通信
- ❌ 不适合长期联网
因为:
网络只对当前 App 生效,退出 App 即失效
八、总结
| 项目 | 结论 |
|---|---|
| 推荐系统 | Android 10+ |
| 连接方式 | WifiNetworkSpecifier |
| 是否保存WiFi | ❌ |
| 是否影响系统网络 | ❌ |
| 是否需要权限 | ✅ 定位 |
这是目前 Android 官方唯一推荐的直连指定 WiFi 方案。