uni-app-x开发安卓app的wifi监听器实战

UniApp X 安卓App的Wi-Fi 状态监听器实战指南

前言

在移动应用开发中,实时监听 Wi-Fi 连接状态变化是一个常见需求。本文将详细介绍如何使用 UniApp X (UTS) 开发一个健壮的 Wi-Fi 状态监听器,帮助开发者快速实现网络状态检测功能。

功能特性

  • 监听 Wi-Fi 开关状态变化(开启/关闭)
  • 监听网络连接状态变化(连接/断开)
  • 自动获取当前连接的 Wi-Fi 名称(SSID)
  • 单例模式设计,避免重复注册监听器
  • 自动释放资源,防止内存泄漏

权限配置

在使用 Wi-Fi 功能之前,需要在 AndroidManifest.xml 中配置相应权限:

xml 复制代码
<!-- Wi-Fi 权限 -->
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE"/>

<!-- 获取 SSID 所需的位置权限 -->
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION"/>
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION"/>

注意:在 Android 8.0 及以上版本,获取 Wi-Fi SSID 名称需要位置权限。即使应用本身不需要位置功能,也必须申请此权限才能获取到真实的 SSID。

核心代码实现

必须先申请android.permission.ACCESS_FINE_LOCATION 权限 再进行下一步 这里就不写了

typescript 复制代码
// wifiListener.uts
import Context from "android.content.Context";
import Intent from "android.content.Intent";
import IntentFilter from "android.content.IntentFilter";
import BroadcastReceiver from "android.content.BroadcastReceiver";
import WifiManager from "android.net.wifi.WifiManager";
import WifiInfo from "android.net.wifi.WifiInfo";
import NetworkInfo from "android.net.NetworkInfo";

type WifiStateData = {
	isConnected : boolean;
	ssid ?: string;
}

// 获取SSID 需要配置权限 android.permission.ACCESS_FINE_LOCATION 即app的位置权限开启 才能获取到 必须先申请权限 

class WifiBroadcastReceiver extends BroadcastReceiver {
	private cb : (data : WifiStateData) => void;

	constructor(callback : (data : WifiStateData) => void) {
		super();
		this.cb = callback;
	}

	override onReceive(context : Context, intent : Intent) {
		const action = intent.getAction();

		if (action === WifiManager.WIFI_STATE_CHANGED_ACTION) {
			const state = intent.getIntExtra(
				WifiManager.EXTRA_WIFI_STATE,
				WifiManager.WIFI_STATE_UNKNOWN
			);
			if (state === WifiManager.WIFI_STATE_ENABLED) {
				console.log('[Wi-Fi] 已开启');
				const ssid = getCurrentSSID();
				const hasSsid = ssid != null;
				if (hasSsid) {
					this.cb({ isConnected: true, ssid: ssid });
				} else {
					this.cb({ isConnected: false });
				}
			} else if (state === WifiManager.WIFI_STATE_DISABLED) {
				console.log('[Wi-Fi] 已关闭');
				this.cb({ isConnected: false });
			}
		}
		else if (action === WifiManager.NETWORK_STATE_CHANGED_ACTION) {
			const networkInfo = intent.getParcelableExtra<NetworkInfo>(
				WifiManager.EXTRA_NETWORK_INFO
			);
			if (networkInfo != null && networkInfo.isConnected()) {
				const wifiInfo = intent.getParcelableExtra<WifiInfo>(
					WifiManager.EXTRA_WIFI_INFO
				);
				let ssid : string | null = null;
				if (wifiInfo != null) {
					const raw = wifiInfo.getSSID();
					if (raw != null && raw !== '<unknown ssid>') {
						ssid = raw.replace(/^"|"$/g, '');
					}
				}
				if (ssid == null) {
					ssid = getCurrentSSID();
				}
				console.log('[Wi-Fi] 已连接:', ssid != null ? ssid : '未知网络');
				if (ssid != null) {
					this.cb({ isConnected: true, ssid: ssid });
				} else {
					this.cb({ isConnected: true });
				}
			} else {
				console.log('[Wi-Fi] 已断开');
				this.cb({ isConnected: false });
			}
		}
	}
}

/**
 * Wi-Fi 状态监听器
 * 使用方法:
 *   const listener = WifiListener.getInstance();
 *   listener.startListening((state) => { console.log(state); });
 *   // 页面销毁时:listener.stopListening();
 */
class WifiListener {
	private static instance : WifiListener | null = null;
	private receiver : BroadcastReceiver | null = null;
	private context : Context | null = null;

	private constructor() { }

	public static getInstance() : WifiListener {
		if (WifiListener.instance == null) {
			WifiListener.instance = new WifiListener();
		}
		return WifiListener.instance;
	}

	private getContext() : Context | null {
		if (this.context == null) {
			this.context = UTSAndroid.getAppContext();
		}
		return this.context;
	}

	/**
	 * 开始监听 Wi-Fi 状态变化
	 * @param callback 回调函数,参数为 { isConnected: boolean, ssid?: string }
	 */
	public startListening(callback : (data : WifiStateData) => void) : void {
		const context = this.getContext();
		if (context == null) {
			console.error('无法获取 Context');
			return;
		}

		this.stopListening();

		this.receiver = new WifiBroadcastReceiver(callback);

		const filter = new IntentFilter();
		filter.addAction(WifiManager.WIFI_STATE_CHANGED_ACTION);
		filter.addAction(WifiManager.NETWORK_STATE_CHANGED_ACTION);

		try {
			context.registerReceiver(this.receiver, filter);
			console.log('[Wi-Fi] 监听器已注册');
		} catch (e) {
			console.error('[Wi-Fi] 注册失败:', e);
		}
	}

	/**
	 * 停止监听,取消注册广播接收器
	 */
	public stopListening() : void {
		if (this.receiver != null) {
			const context = this.getContext();
			if (context != null) {
				try {
					context.unregisterReceiver(this.receiver);
					console.log('[Wi-Fi] 监听器已取消注册');
				} catch (e) {
				}
			}
			this.receiver = null;
		}
	}
}

function getCurrentSSID() : string | null {
	const context = UTSAndroid.getAppContext();
	if (context == null) return null;
	const wifiManager = context.getSystemService(Context.WIFI_SERVICE) as WifiManager;
	if (wifiManager == null) return null;
	const wifiInfo = wifiManager.getConnectionInfo();
	if (wifiInfo == null) return null;
	if (wifiInfo.getNetworkId() === -1) return null;
	let ssid = wifiInfo.getSSID();
	if (ssid != null && ssid !== '<unknown ssid>') {
		ssid = ssid.replace(/^"|"$/g, '');
		return ssid;
	}
	return null;
}

export { WifiListener, WifiStateData };

使用示例

在页面中使用

vue 复制代码
<template>
    <view>
        <view>Wi-Fi 状态: {{ isConnected ? '已连接' : '未连接' }}</view>
        <view v-if="ssid">网络名称: {{ ssid }}</view>
    </view>
</template>

<script setup>
    import { ref, onMounted, onUnmounted } from 'vue';
    import { WifiListener } from '@/utils/wifiHelper.uts';

    const isConnected = ref(false);
    const ssid = ref('');

    const wifiListener = WifiListener.getInstance();

    onMounted(() => {
        wifiListener.startListening((state) => {
            isConnected.value = state.isConnected;
            ssid.value = state.ssid || '';
        });
    });

    onUnmounted(() => {
        wifiListener.stopListening();
    });
</script>

技术要点解析

1. 为什么需要继承 BroadcastReceiver

Android 的 Wi-Fi 状态变化通过广播机制传递。BroadcastReceiver 是 Android 四大组件之一,用于接收系统广播。要监听 Wi-Fi 状态,必须:

  • 创建 BroadcastReceiver 的子类
  • 重写 onReceive 方法处理广播
  • AndroidManifest.xml 或代码中注册接收器

2. 为什么要使用单例模式

typescript 复制代码
private static instance: WifiListener | null = null;

public static getInstance(): WifiListener {
    if (WifiListener.instance == null) {
        WifiListener.instance = new WifiListener();
    }
    return WifiListener.instance;
}
  • 避免重复创建监听器实例
  • 确保全局只有一个广播接收器
  • 防止多次注册导致的资源浪费

3. SSID 获取的注意事项

typescript 复制代码
function getCurrentSSID(): string | null {
    const wifiInfo = wifiManager.getConnectionInfo();
    if (wifiInfo.getNetworkId() === -1) return null;
    let ssid = wifiInfo.getSSID();
    if (ssid != null && ssid !== '<unknown ssid>') {
        ssid = ssid.replace(/^"|"$/g, '');
        return ssid;
    }
    return null;
}
  • SSID 返回值可能包含引号,需要去除
  • <unknown ssid> 表示无法获取真实的 SSID
  • getNetworkId() === -1 表示未连接到任何网络

常见问题

Q: 为什么 SSID 返回 ""?

A: 这是 Android 的安全机制。在 Android 8.0 及以上版本,第三方应用获取 SSID 需要满足以下条件:

  1. 位置权限已授予
  2. 定位服务已开启
  3. 应用具有 ACCESS_FINE_LOCATION 权限

Q: 如何处理权限申请?

参考 https://blog.csdn.net/a3212768093/article/details/161656845?spm=1001.2014.3001.5501

总结

本文详细介绍了使用 UniApp X 的 UTS 开发 Android 原生 Wi-Fi 监听器的方法。通过封装单例模式的 WifiListener 类,开发者可以方便地在应用任何页面监听 Wi-Fi 状态变化,无需关心广播接收器的注册和注销细节。

关键技术点:

  • 使用 BroadcastReceiver 接收系统广播
  • 单例模式确保全局唯一监听器
  • 正确处理权限和边界情况
  • 记得在页面销毁时停止监听