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 美颜相机】第十二天:GPUImageFilterGroup 源码解析
android·数码相机
_李小白2 小时前
【Android GLSurfaceView源码学习】第三天:GLSurfaceView的Surface、GLES与EGLSurface的关联
android·学习
技术摆渡人2 小时前
专题三:【Android 架构】全栈性能优化与架构演进全书
android·性能优化·架构
前端世界2 小时前
鸿蒙系统中时间与日期的国际化实践:一次把不同文化显示问题讲清楚
android·华为·harmonyos
木卫四科技2 小时前
【Claude Agent - 入门篇】:从原生 SDK 到自主智能体
android
2501_915918412 小时前
Mac 抓包软件有哪些?Charles、mitmproxy、Wireshark和Sniffmaster哪个更合适
android·ios·小程序·https·uni-app·iphone·webview
2501_915106322 小时前
iOS 抓包绕过 SSL 证书认证, HTTPS 暴力抓包、数据流分析
android·ios·小程序·https·uni-app·iphone·ssl
2501_9159214310 小时前
iOS App 电耗管理 通过系统电池记录、Xcode Instruments 与克魔(KeyMob)组合使用
android·ios·小程序·https·uni-app·iphone·webview
June bug12 小时前
【配环境】安卓项目开发环境
android