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 方案。

相关推荐
工程师老罗3 小时前
如何在Android工程中配置NDK版本
android
Libraeking6 小时前
破壁行动:在旧项目中丝滑嵌入 Compose(混合开发实战)
android·经验分享·android jetpack
市场部需要一个软件开发岗位7 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
JMchen1239 小时前
Android后台服务与网络保活:WorkManager的实战应用
android·java·网络·kotlin·php·android-studio
crmscs9 小时前
剪映永久解锁版/电脑版永久会员VIP/安卓SVIP手机永久版下载
android·智能手机·电脑
localbob9 小时前
杀戮尖塔 v6 MOD整合版(Slay the Spire)安卓+PC端免安装中文版分享 卡牌肉鸽神作!杀戮尖塔中文版,电脑和手机都能玩!杀戮尖塔.exe 杀戮尖塔.apk
android·杀戮尖塔apk·杀戮尖塔exe·游戏分享
机建狂魔9 小时前
手机秒变电影机:Blackmagic Camera + LUT滤镜包的专业级视频解决方案
android·拍照·摄影·lut滤镜·拍摄·摄像·录像
hudawei9969 小时前
flutter和Android动画的对比
android·flutter·动画
lxysbly11 小时前
md模拟器安卓版带金手指2026
android
儿歌八万首12 小时前
硬核春节:用 Compose 打造“赛博鞭炮”
android·kotlin·compose·春节