Android 10+ 使用 WifiNetworkSpecifier 连接指定 WiFi(完整封装 + 实战)

适用于: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");
  1. 断开 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 方案。

相关推荐
李堇1 小时前
android滚动列表VerticalRollingTextView
android·java
lxysbly3 小时前
n64模拟器安卓版带金手指2026
android
游戏开发爱好者85 小时前
日常开发与测试的 App 测试方法、查看设备状态、实时日志、应用数据
android·ios·小程序·https·uni-app·iphone·webview
王码码20356 小时前
Flutter for OpenHarmony 实战之基础组件:第三十一篇 Chip 系列组件 — 灵活的标签化交互
android·flutter·交互·harmonyos
黑码哥6 小时前
ViewHolder设计模式深度剖析:iOS开发者掌握Android列表性能优化的实战指南
android·ios·性能优化·跨平台开发·viewholder
亓才孓6 小时前
[JDBC]元数据
android
独行soc6 小时前
2026年渗透测试面试题总结-17(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
金融RPA机器人丨实在智能6 小时前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿6 小时前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc7 小时前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮