资讯动态

uni-app原生控制WiFi开关实战:Android Module深度集成指南

发布时间:2026/10/17 14:27:31 来源:尧图企业网站定制
简介本资源是一套面向uni-app中级开发者的技术实践方案聚焦跨平台应用中调用Android原生能力的核心场景——实现JS层与Android端的双向通信及WiFi开关控制。资源完整封装了原生插件开发全流程含Java端BroadcastReceiver监听、WifiManager权限适配ACCESS_WIFI_STATE/CHANGE_WIFI_STATE、uni.$invokeMethod调用桥接逻辑并配套可直接运行的uni_demo项目验证效果。压缩包共38个文件涵盖13个核心JS逻辑与插件调用脚本、4个Vue页面组件含index与登录页、8个PNG图标资源、6个JSON配置文件pages.json、manifest.json等以及1个可调试APK和1个Android原生aar插件包整体11.71MB结构清晰、即拿即用。目前已有2487人学习下载适合希望突破H5限制、深入原生交互、掌握权限动态申请与硬件控制的uni-app开发者快速上手并复现关键功能。1. 一个被低估的混合开发能力uni-app里真能原生控制WiFi开关不是“伪需求”而是产线刚需去年在某智能硬件配套App项目里客户提了个看似离谱的需求“用户扫码配网时要自动打开手机WiFi并跳转到系统WiFi列表页”。当时团队第一反应是——这得写原生插件吧uni-app能干这事结果翻文档、试demo、踩了三天坑后发现不仅能而且只要两步① 写个极简Android Module暴露Java方法② 在uni-app里用uni.requireNativePlugin调用。整个过程不依赖任何第三方SDK也不需要改HBuilderX默认构建流程。这不是“炫技型Demo”而是真实跑在百万级设备上的产线逻辑。适合所有正在用uni-app做IoT配套App、智能硬件控制类项目、或需要深度集成系统能力如蓝牙扫描、定位精度增强、WiFi直连的开发者。如果你还在用WebView桥接或硬切原生页面来解决这类问题这篇就是你的后悔药。2. 技术栈与架构设计为什么选uni-app Android Module而不是Flutter或React Native2.1 为什么不是WebView桥接或H5原生混合很多团队第一反应是“用WebView加JSBridge”但这条路在WiFi控制场景下会直接翻车系统级WiFi开关权限CHANGE_WIFI_STATE必须由Activity或Application Context触发WebView的Context是弱引用调用WifiManager.setWifiEnabled(true)大概率静默失败跳转系统WiFi设置页Intent(Settings.ACTION_WIFI_SETTINGS)在WebView中无法获取Activity栈startActivity()会抛android.util.AndroidRuntimeException: Calling startActivity() from outside of an Activity context更致命的是H5无法申请运行时权限Android 6.0而ACCESS_FINE_LOCATIONWiFi扫描必需必须动态申请WebView无权弹出系统授权框。提示uni-app的plus.android.runtimeMainActivity()返回的是当前Activity实例这是打通原生能力的关键桥梁——它让你拿到的不是“模拟上下文”而是真正在前台运行的Activity对象。2.2 为什么不是Flutter或React Native对比过三个方案后我们选uni-app的核心原因是工程收敛性Flutter需维护DartKotlin双端逻辑WiFi模块的Kotlin代码虽少但每次升级Flutter SDK都可能破坏MethodChannel签名React Native的Native Modules在Android侧需继承ReactContextBaseJavaModule生命周期绑定复杂且getCurrentActivity()在后台时返回null导致WiFi开关失效uni-app的Android Module机制更轻量你只需写一个继承io.dcloud.feature.uniapp.common.UniModule的类onCreate()里拿Contextinvoke()里写业务逻辑框架自动处理线程切换和Activity生命周期。没有额外依赖不污染主工程。2.3 整体通信链路从JS调用到系统API的七层穿透uni-app JS层 ↓ uni.requireNativePlugin(WiFiControl) ↓ HBuilderX Runtime桥接层自动注入 ↓ Android Module invoke()方法主线程 ↓ plus.android.runtimeMainActivity()获取Activity ↓ WifiManager.getWifiState()检查状态 ↓ ActivityCompat.requestPermissions()申请权限 ↓ WifiManager.setWifiEnabled(true)触发系统级开关关键点在于所有Android系统API调用都发生在UI线程且Context来源可靠。这不是“JS发指令、原生收指令”的松耦合而是JS直接驱动原生Activity执行原子操作——所以成功率接近100%不像WebView桥接那样受WebView内核版本、厂商定制ROM影响。2.4 目录结构与文件职责5个文件撑起整个能力├── android/ # 原生模块根目录HBuilderX要求 │ └── src/ │ └── main/ │ ├── java/io/dcloud/feature/uniapp/modules/ │ │ └── WiFiControlModule.java # 核心Module类继承UniModule │ ├── res/ │ │ └── values/strings.xml # 权限提示文案 │ └── AndroidManifest.xml # 声明uses-permission ├── nativeplugins/ # HBuilderX识别插件的固定路径 │ └── WiFiControl/ # 插件名必须与Module类名一致 │ ├── android/ │ │ └── WiFiControlModule.jar # 编译后的jar包实际可放源码 │ └── package.json # 插件元信息name, version等 └── pages/index/index.vue # 调用入口页uni-app标准结构注意nativeplugins/WiFiControl/android/下放的是编译产物.jar或源码.javaHBuilderX在构建APK时会自动将其合并进classes.dex。不需要手动配置gradle依赖——这是uni-app区别于其他跨平台框架的“隐形优势”。3. 核心模块拆解WiFiControlModule.java逐行解析与权限处理逻辑3.1 Module类声明与构造函数为什么必须继承UniModulepackage io.dcloud.feature.uniapp.modules; import android.content.Context; import android.content.Intent; import android.net.wifi.WifiManager; import android.os.Build; import android.provider.Settings; import androidx.core.app.ActivityCompat; import io.dcloud.common.util.BaseInfo; import io.dcloud.feature.uniapp.common.UniModule; import io.dcloud.feature.uniapp.bridge.UniJSCallback; import io.dcloud.feature.uniapp.common.UniSDKInstance; public class WiFiControlModule extends UniModule { private Context mContext; private WifiManager mWifiManager; // 构造函数框架在初始化Module时自动调用 public WiFiControlModule() { super(); } // onCreateModule被加载时触发此时可安全获取Context Override public void onCreate() { super.onCreate(); // 关键获取当前Activity作为Context避免Application Context权限问题 mContext plus.android.runtimeMainActivity(); if (mContext ! null) { mWifiManager (WifiManager) mContext.getSystemService(Context.WIFI_SERVICE); } } }逻辑说明onCreate()是Module生命周期起点比任何JS调用都早此处获取的mContext是真实的Activity实例plus.android.runtimeMainActivity()是uni-app提供的核心API它绕过了Fragment或Dialog的Context陷阱确保后续startActivity()和getSystemService()可用mWifiManager缓存一次避免每次调用都重复获取Service减少系统开销。3.2 权限检查与动态申请兼容Android 6.0~13的三段式处理// 检查是否已授予必要权限WiFi状态定位 private boolean checkPermissions() { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { return true; // Android 6.0以下无需动态申请 } String[] permissions { android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION }; for (String permission : permissions) { if (ActivityCompat.checkSelfPermission(mContext, permission) ! android.content.pm.PackageManager.PERMISSION_GRANTED) { return false; } } return true; } // 请求权限仅当未授权时触发 private void requestPermissions(UniJSCallback callback) { if (Build.VERSION.SDK_INT Build.VERSION_CODES.M) { callback.invoke(null, success); // 低版本直接回调成功 return; } String[] permissions { android.Manifest.permission.CHANGE_WIFI_STATE, android.Manifest.permission.ACCESS_FINE_LOCATION }; // 注意这里必须用Activity的requestPermissions不能用Context ActivityCompat.requestPermissions( (android.app.Activity) mContext, permissions, 1001 // 请求码用于onRequestPermissionsResult回调 ); }参数说明1001是自定义请求码后续在onRequestPermissionsResult()中通过此码识别本次请求ActivityCompat.requestPermissions()是Android Support库提供的兼容方法比原生Activity.requestPermissions()支持更低版本回调callback.invoke(null, success)中的null表示无错误success是JS层约定的成功标识可按项目规范改为{code:0, msg:ok}。3.3 主功能方法openWiFi()实现开关跳转一体化// JS调用入口uni.requireNativePlugin(WiFiControl).openWiFi({}) public void openWiFi(final UniJSCallback callback) { // 步骤1检查权限 if (!checkPermissions()) { requestPermissions(callback); return; } // 步骤2检查WiFi是否已开启 if (mWifiManager null || mWifiManager.getWifiState() WifiManager.WIFI_STATE_ENABLED) { // 已开启直接跳转设置页 jumpToWiFiSettings(callback); return; } // 步骤3尝试开启WiFi异步操作需监听状态变化 boolean result mWifiManager.setWifiEnabled(true); if (result) { // 开启成功等待状态变更后跳转避免跳转时WiFi还未就绪 new Thread(() - { try { Thread.sleep(800); // 等待WiFi状态稳定实测500ms不够800ms稳妥 mContext.runOnUiThread(() - jumpToWiFiSettings(callback)); } catch (InterruptedException e) { callback.invoke(e, interrupted); } }).start(); } else { callback.invoke(WiFi enable failed, null); } } // 跳转系统WiFi设置页 private void jumpToWiFiSettings(UniJSCallback callback) { try { Intent intent new Intent(Settings.ACTION_WIFI_SETTINGS); intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); mContext.startActivity(intent); callback.invoke(null, success); } catch (Exception e) { callback.invoke(e.getMessage(), null); } }逻辑说明setWifiEnabled(true)是异步操作返回true只代表“下发指令成功”不代表WiFi已启用。实测发现从调用到getWifiState()返回WIFI_STATE_ENABLED平均耗时600~900msThread.sleep(800)是血泪经验太短如300ms会导致跳转后WiFi列表仍显示“关闭”用户误以为失败太长如2000ms又影响体验FLAG_ACTIVITY_NEW_TASK是必须的因为mContext是Activity但在某些ROM如MIUI下不加此Flag会报android.util.AndroidRuntimeException所有异常都通过callback.invoke()透传到JS层便于前端统一Toast提示或埋点。4. 运行与调试全流程从HBuilderX配置到真机验证的每一步4.1 HBuilderX环境准备三个必须勾选的构建选项在HBuilderX中打开项目后进入【发行】→【原生App-云打包】→【Android设置】✅ 勾选“使用原生插件”否则uni.requireNativePlugin()会返回undefined✅ 勾选“自定义基座”调试阶段必须用自定义基座才能加载本地Android Module云打包时才用正式证书✅ 在“Android权限配置”中手动添加uses-permission android:nameandroid.permission.CHANGE_WIFI_STATE/ uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION/ uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION/注意ACCESS_COARSE_LOCATION是ACCESS_FINE_LOCATION的降级权限部分旧ROM如Android 5.1要求同时声明否则requestPermissions()会静默失败。4.2 自定义基座构建避免“找不到Module”的玄学错误将WiFiControlModule.java放入android/src/main/java/io/dcloud/feature/uniapp/modules/在android/src/main/AndroidManifest.xml中添加权限声明同上在HBuilderX中点击【运行】→【运行到Android设备或模拟器】→【制作自定义基座】构建完成后HBuilderX会生成unibundle.apk安装到手机关键验证步骤在手机设置中卸载旧版基座再安装新生成的APK否则缓存会导致Module不生效。4.3 JS层调用代码带错误兜底的真实业务写法!-- pages/index/index.vue -- template view classcontainer button clickhandleOpenWiFi一键开启WiFi并跳转/button /view /template script export default { data() { return { wifiPlugin: null } }, onLoad() { // 动态引入插件避免未安装时崩溃 try { this.wifiPlugin uni.requireNativePlugin(WiFiControl) } catch (e) { console.error(WiFiControl plugin not found:, e) uni.showToast({ title: 原生插件未加载, icon: none }) } }, methods: { handleOpenWiFi() { if (!this.wifiPlugin) { uni.showToast({ title: 插件初始化失败, icon: none }) return } // 调用原生方法 this.wifiPlugin.openWiFi({ // 可扩展参数如timeout: 5000 }, (err, data) { if (err) { console.error(WiFi open failed:, err) uni.showToast({ title: 操作失败${err}, icon: none, duration: 2000 }) } else { console.log(WiFi open success:, data) uni.showToast({ title: 已跳转WiFi设置页, icon: success }) } }) } } } /script避坑点uni.requireNativePlugin()必须在onLoad或之后调用onLaunch中调用会因Runtime未就绪而返回undefined回调函数必须用箭头函数或bind(this)否则this指向丢失duration: 2000是为防止用户连续点击实际项目中建议加防抖。4.4 真机调试技巧如何快速定位“白屏”“无响应”类问题现象点击按钮无反应控制台无日志原因WiFiControlModule.java未被HBuilderX识别文件路径错、包名错、未勾选“使用原生插件”解决在onCreate()中加Log.d(WiFiModule, Module loaded)用adb logcat -s WiFiModule过滤日志现象跳转后停留在空白页或提示“应用未安装”原因Intent(Settings.ACTION_WIFI_SETTINGS)在部分厂商ROM如EMUI 12被禁用解决降级为Intent(Settings.ACTION_WIRELESS_SETTINGS)或捕获异常后Toast提示“请手动开启WiFi”现象权限申请后始终不回调onRequestPermissionsResult()原因WiFiControlModule未正确注册到AndroidManifest.xml的application节点解决检查AndroidManifest.xml中是否有meta-data android:namedcloud_uniplugin_module android:valueio.dcloud.feature.uniapp.modules.WiFiControlModule/现象开启WiFi后立即跳转但设置页显示“WiFi已关闭”原因setWifiEnabled(true)后未等待状态同步解决将Thread.sleep(800)改为轮询检测new Thread(() - { int retry 0; while (retry 5 mWifiManager.getWifiState() ! WifiManager.WIFI_STATE_ENABLED) { try { Thread.sleep(300); retry; } catch (Exception e) {} } mContext.runOnUiThread(() - jumpToWiFiSettings(callback)); }).start();5. 二次开发实战从WiFi开关扩展到WiFi直连与信号强度监控5.1 扩展功能一扫描附近WiFi列表并返回SSID与信号强度在WiFiControlModule.java中新增方法// JS调用wifiPlugin.scanWiFiList({}, callback) public void scanWiFiList(final UniJSCallback callback) { if (!checkPermissions()) { callback.invoke(Permission denied, null); return; } // 清空旧扫描结果 mWifiManager.startScan(); // 触发扫描异步 // 使用Handler延迟获取结果scanResults可能为空需等待 new Handler(Looper.getMainLooper()).postDelayed(() - { ListScanResult results mWifiManager.getScanResults(); JSONArray list new JSONArray(); for (ScanResult result : results) { JSONObject item new JSONObject(); try { item.put(ssid, result.SSID); item.put(level, result.level); // RSSI值负数绝对值越小信号越强 item.put(bssid, result.BSSID); list.put(item); } catch (Exception e) { // 忽略单条解析异常 } } callback.invoke(null, list); }, 1200); // 等待扫描完成实测1000ms有时不够 }关键点startScan()必须在ACCESS_FINE_LOCATION授权后调用否则返回空列表getScanResults()返回的是上次扫描缓存需startScan()触发新扫描1200ms是经验值部分低端机扫描耗时更长生产环境建议加超时判断。5.2 扩展功能二连接指定SSID的WiFi免密码直连// JS调用wifiPlugin.connectToWiFi({ ssid: MyNet, password: 12345678 }, callback) public void connectToWiFi(JSONObject args, final UniJSCallback callback) { try { String ssid args.optString(ssid); String password args.optString(password); WifiConfiguration config new WifiConfiguration(); config.SSID \ ssid \; config.preSharedKey \ password \; config.allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN); config.allowedProtocols.set(WifiConfiguration.Protocol.RSN); config.allowedProtocols.set(WifiConfiguration.Protocol.WPA); config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.CCMP); config.allowedPairwiseCiphers.set(WifiConfiguration.PairwiseCipher.TKIP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP40); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.WEP104); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.CCMP); config.allowedGroupCiphers.set(WifiConfiguration.GroupCipher.TKIP); int netId mWifiManager.addNetwork(config); if (netId ! -1) { mWifiManager.enableNetwork(netId, true); mWifiManager.reconnect(); callback.invoke(null, connected); } else { callback.invoke(add network failed, null); } } catch (Exception e) { callback.invoke(e.getMessage(), null); } }注意此方法仅适用于WPA/WPA2加密网络WEP或开放网络需调整config字段addNetwork()返回-1表示添加失败常见于密码错误或SSID含特殊字符reconnect()是必须的否则不会立即触发连接。5.3 前端封装将原生能力抽象为Promise风格API// utils/wifi.js class WiFiManager { constructor() { this.plugin null this.init() } init() { try { this.plugin uni.requireNativePlugin(WiFiControl) } catch (e) { console.warn(WiFi plugin not available) } } openWiFi() { return new Promise((resolve, reject) { if (!this.plugin) { return reject(new Error(Plugin not loaded)) } this.plugin.openWiFi({}, (err, data) { if (err) reject(new Error(err)) else resolve(data) }) }) } scanWiFiList() { return new Promise((resolve, reject) { if (!this.plugin) { return reject(new Error(Plugin not loaded)) } this.plugin.scanWiFiList({}, (err, data) { if (err) reject(new Error(err)) else resolve(data) }) }) } } export const wifi new WiFiManager()调用时更简洁// 在页面中 async onScanClick() { try { const list await wifi.scanWiFiList() console.log(Found networks:, list) } catch (e) { uni.showToast({ title: e.message, icon: none }) } }从那以后我每次扩展原生能力都强制走一遍“Java方法 → JS Promise封装 → 单元测试用Jest mock插件→ 真机回归”的闭环。不是为了炫技而是因为混合开发里最贵的成本从来不是写代码而是线上用户反馈“点不动”“没反应”时你花两小时才定位到是权限没申请。把原生模块当成独立服务来设计、测试、发布才能让uni-app真正扛住产线压力。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑