ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

uni-app调用Android原生WiFi插件开发实战

uni-app调用Android原生WiFi插件开发实战 简介面向uni-app开发者的原生插件通信实战资源重点演示通过uni.$invokeMethod调用Android原生能力实现打开/关闭WiFi、广播通信及动态权限申请。资源包共38个文件约11.71MB涵盖13个js、4个vue、6个json等前端逻辑与页面配置同时包含aar原生插件、apk安装包和html调试页面可直观对照学习JS与Java/Kotlin的交互流程。压缩包内uni_demo示例结构完整包含pages目录、App.vue、main.js、manifest.json等标准uni-app工程文件以及nativeplugins/BluetoothAndWifiPlugin插件工程便于在HBuilderX中直接运行和二次修改。另有png图片资源和scss样式文件辅助界面理解。目前已有2474人学习下载适合希望突破纯前端限制、掌握混合开发技能的uni-app开发者。通过该资源可快速搭建原生通信桥梁理解WifiManager、BroadcastReceiver、权限管理等关键知识点并在实际项目中复用这套插件封装思路。 做移动端混开开发的朋友应该都碰过这类需求项目里用的是uni-app方便一套代码跑三端但业务上突然要操作系统级别的能力——比如扫描并连接WiFi、读取设备网络状态或者和硬件模块做原生通信。uni-app的JS API覆盖面确实广但到了系统能力这个层面就有点力不从心了。这时候官方给出的路径就是写原生插件让JS层和Android原生代码互相通信。这篇文章我会拆一个实际能落地的场景——用uni-app调用Android原生打开WiFi并返回状态把整套流程从工程搭建、原生代码编写、JS侧调用到常见的坑全部走一遍。内容适合两类人一类是uni-app为主、需要补原生能力的团队另一类是刚接触离线打包、搞不清uni-app和Android Studio怎么协作的开发者。看完之后你至少能独立写完一个带原生交互的插件而不是卡在“为什么我调不到原生方法”这个坎上。1. 整体设计思路先搞清楚这几种通信方式1.1 为什么非得走原生而不是直接调JS APIuni-app官方API里确实提供了一些WiFi相关的接口但实际查一下会发现uni.getNetworkType只能拿当前网络类型uni.connectSocket是走WebSocket的跟系统WiFi管理器压根不是一回事。你要做的是“扫描附近的WiFi列表并连接某个热点”这属于系统级API的范畴JS引擎根本碰不到。另外还有一种常见误区以为H5的plus API能覆盖所有原生能力。plus确实封装了不少原生能力但它是DCloud维护的通用封装不可能为每个厂商的特殊硬件接口都提供JS桥接。遇到这种场景原生插件几乎是唯一干净的路子——既不用放弃uni-app的跨端优势又能拿到完整的Android SDK能力。1.2 几种通信方案的取舍我梳理过比较主流的几种做法简单列一下优缺点方案原理优点缺点uni原生插件UniModule通过官方SDK封装Android代码暴露方法给JS层规范、完整支持双向通信、可发布插件市场需要离线打包或云打包调试稍麻烦HTML5扩展利用plus的扩展机制编写原生代码实现较快适合功能简单的小插件文档较少版本兼容性一般Webview URL拦截JS请求自定义scheme原生拦截解析不需要SDK纯Webview方案传输大数据效率低回调链路脆弱不推荐本地Socket通信JS通过socket与原生服务通信极端情况下可用极其繁琐几乎没有必要我推荐直接用uni原生插件方案。原因很简单这是DCloud官方维护的规范uni.requireNativePlugin的调用方式写起来顺手双向回调机制也成熟以后插件要上架插件市场或者交给别的团队维护成本都最低。1.3 通信模型先画在脑子里整个通信链路其实就三层JS层uni-app页面 → 桥接层uni-app SDK → Android原生模块。JS层调用uni.requireNativePlugin拿到插件实例然后像调普通JS方法一样调用原生方法原生方法执行完通过回调把结果传回JS层。这里有个容易踩的思维误区很多人把“原生插件”理解为必须弹出一个Activity才能干活。实际上负责通信的UniModule是运行在宿主进程里的一个普通Java类不需要UI。只有当你需要操作界面时才需要跳转原生Activity这部分和通信逻辑可以分离。2. 环境准备离线打包才是绕不开的第一步2.1 为什么本地调试一定要走Android Studio用uni-app开发时很多人习惯直接使用HBuilderX的“运行到浏览器”或者“标准基座”。但是标准基座里根本没有你的原生插件所以在浏览器和标准基座里调试插件是无效的。唯一的办法就是使用自定义基座而自定义基座本质上就是一次离线打包——把你的插件代码和uni-app的SDK一起编译成一个Android工程。我这里的建议是尽早把离线打包环境搭起来。第一次配置会花一些时间但之后每次改动原生代码只需要在Android Studio里rebuild一次再重新运行自定义基座就行比云端打包的迭代速度快得多。2.2 Android Studio工程的几个关键配置新建工程时选“Empty Activity”即可包名最好和uni-app项目的AppID对应尤其是已上架的应用包名不能乱改。然后把HBuilderX安装目录下SDK/libs里的uniapp-v8-release.aar等依赖引入工程并把assets里的data目录拷到工程assets下。Manifest文件里有一个容易漏的地方必须加uni-app的UniApplication作为Application的子类。如果这一步漏了运行时会直接报“UniApp框架初始化失败”之类的错误。具体代码大概是这样的application android:nameio.dcloud.application.DCloudApplication android:iconmipmap/ic_launcher android:labelstring/app_name /application工程跑通了之后你会发现自己拿到了一个“自定义基座”。HBuilderX里运行到手机时选择这个基座就可以在真机上调试原生了。3. 核心原生代码实现一个WiFi控制模块3.1 新建插件类并声明方法在Android工程里新建一个Java类继承UniModule。UniModule是uni-app提供的基础类它内部已经做了和JS层的桥接我们只需要通过注解暴露方法给JS调用即可。打开WiFi、扫描、连接这些功能Android里的核心是WifiManager。下面是这个插件类的骨架package com.example.myplugin; import android.content.Context; import android.net.wifi.WifiManager; import android.net.wifi.ScanResult; import android.net.wifi.WifiConfiguration; import java.util.List; import io.dcloud.feature.uniapp.common.UniModule; import io.dcloud.feature.uniapp.annotation.UniJSMethod; import io.dcloud.feature.uniapp.bridge.Callback; import com.alibaba.fastjson.JSONObject; public class WifiModule extends UniModule { private WifiManager getWifiManager() { if (mUniSDKInstance null) return null; Context context mUniSDKInstance.getContext(); if (context null) return null; return (WifiManager) context.getApplicationContext() .getSystemService(Context.WIFI_SERVICE); } UniJSMethod(uiThread false) public void openWifi(JSONObject options, Callback callback) { WifiManager wifiManager getWifiManager(); JSONObject result new JSONObject(); if (wifiManager null) { result.put(code, -1); result.put(message, wifiManager is null); callback.invoke(result); return; } boolean success wifiManager.setWifiEnabled(true); result.put(code, success ? 0 : -1); result.put(message, success ? wifi enabled : enable failed); callback.invoke(result); } }注意UniJSMethod(uiThread false)这个注解。UI操作需要主线程但网络和耗时操作要放后台。打开WiFi本身不慢放到uiThread true也能接受但如果你后面要加扫描WiFi列表的逻辑就必须用false了否则会卡界面。3.2 加入扫描和连接指定WiFi的功能扫描WiFi需要startScan()与getScanResults()配合。这里有个实际开发中非常容易踩的坑在Android 10以上扫描WiFi不仅需要WiFi权限还必须申请定位权限并用GPS打开。不少新手机即使WiFi权限都开了仍然扫不到列表十有八九就是定位权限没给。所以我在代码里特意写了一段检查定位权限的逻辑UniJSMethod(uiThread false) public void scanWifi(JSONObject options, Callback callback) { WifiManager wifiManager getWifiManager(); JSONObject result new JSONObject(); if (wifiManager null) { result.put(code, -1); callback.invoke(result); return; } // 检查定位权限不满足则先返回错误码 if (!checkPermission()) { result.put(code, -100); result.put(message, need location permission); callback.invoke(result); return; } boolean scanStarted wifiManager.startScan(); if (!scanStarted) { result.put(code, -2); result.put(message, startScan failed); callback.invoke(result); return; } ListScanResult scanResults wifiManager.getScanResults(); result.put(code, 0); result.put(wifiList, filterScanResults(scanResults)); callback.invoke(result); }filterScanResults是做数据裁剪把SSID、BSSID、信号强度这些有用的字段提取出来因为ScanResult里还带着一些内核层的数据直接序列化回JS层会让包体变大也没必要。连接指定WiFi是老生常谈的addNetworkenableNetwork或者用connect到WifiNetworkSpecifier仅Android 10。我这里用的还是兼容性更好的传统方案UniJSMethod(uiThread false) public void connectWifi(JSONObject options, Callback callback) { String ssid options.getString(ssid); String password options.getString(password); WifiConfiguration config new WifiConfiguration(); config.SSID \ ssid \; if (password null || password.isEmpty()) { config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE); } else { config.preSharedKey \ password \; config.allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK); } WifiManager wifiManager getWifiManager(); int netId wifiManager.addNetwork(config); if (netId -1) { // addNetwork返回-1可能表示网络已存在可以尝试获取现有配置 ListWifiConfiguration configuredNetworks wifiManager.getConfiguredNetworks(); // 这里做个兜底优先复用已有配置 ... } boolean enabled wifiManager.enableNetwork(netId, true); ... }关于addNetwork返回-1我得提醒一句在部分国产ROM里因为系统对WiFi配置的管理策略不同这个方法可能经常返回-1而WiFi其实是可以连的。所以我一般会先遍历getConfiguredNetworks找相同SSID的配置找到就直接enableNetwork找不到再addNetwork。这个逻辑虽然啰嗦但实测能少一半连接失败的问题。3.3 权限清单与Android版本适配Manifest里需要申请的权限有这些uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / uses-permission android:nameandroid.permission.CHANGE_WIFI_STATE / uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION / uses-permission android:nameandroid.permission.ACCESS_COARSE_LOCATION /Android 13以上还推荐声明android.permission.NEARBY_WIFI_DEVICES但这个是“不需要用户弹窗”的权限属于普通权限加了不冲突。定位权限则属于危险权限需要在页面层用uni.authorize或者原生动态申请我一般在插件里直接返回code: -100让JS层感知再由前端引导用户去系统设置里打开定位服务。这样职责更清晰——原生负责“能不能做”前端负责“提示用户怎么做”。4. JS侧调用与通信细节4.1 如何拿到插件实例在uni-app页面中通过uni.requireNativePlugin加载插件模块名是离线打包时在dcloud_uniplugins.json里注册的名字。比如注册为WifiModule那么JS里这样拿const wifiModule uni.requireNativePlugin(WifiModule)这个requireNativePlugin返回的是一个Proxy对象你可以直接调插件里暴露的任意UniJSMethod方法。注意调用时机最好放在onReady之后因为插件实例需要与uni-app的SDK实例绑定过早调用可能拿到null。4.2 回调参数的传输规则原生回调里我传的是一个JSONObjectJS层收到后就是一个对象。这里有一个很容易忽略的约定Callback.invoke可以只调一次也可以多次调。比如一个“开始连接WiFi并持续上报进度”的方法原生侧可以在开始、配置成功、连接成功三个阶段分别调callback.invokeJS侧每次都会收到。这种设计很适合做长任务状态回传不用自己再搞WebSocket或者事件总线。JS侧的处理示例const wifiModule uni.requireNativePlugin(WifiModule) wifiModule.connectWifi({ ssid: MyHomeWiFi, password: 12345678 }, (result) { if (result.code 0) { // 连接成功可继续查询状态 } else { uni.showToast({ title: result.message || 连接失败, icon: none }) } })4.3 耗时操作的线程问题很多初学者会在原生方法里做一个无线循环等待扫描完成或是在回调前Thread.sleep这会把JS和原生之间的调用阻塞住。正确做法是耗时操作放到原生线程池或协程完成后回到主线程再调Callback。上面的scanWifi里startScan()是异步的扫描结果需要等100-200毫秒才更新所以实际项目中我会在scanWifi里轮询几次getScanResults或者通过BroadcastReceiver监听WifiManager.SCAN_RESULTS_AVAILABLE_ACTION等广播到达后再取结果回传。后者是更优雅的方案代码复杂度也更高但正因如此才显得原生封装的必要。5. 常见问题与排查技巧实录5.1 插件加载不到报“Module not found”这个九成是离线打包时的插件注册没配对。检查dcloud_uniplugins.json里的name是否和uni.requireNativePlugin里的字符串一致且大小写敏感。还有就是要确认这个json文件放在assets目录下并且plugins数组里的class路径写的是完整类名包括包名。5.2 调用了方法但没有任何反应也不报错先检查原生代码里方法上有没有UniJSMethod注解。这个注解是必须的没有它JS层找不到对应方法。另一个坑是方法参数类型要和JS传入的一致。我遇到过JS传的是数字原生用String接结果回调直接被吞掉。最好统一用JSONObject作为入参避免类型匹配问题。5.3 WiFi扫描结果为空出现这种情况严格按这个顺序排查定位权限是否授予、定位开关是否打开、Android版本是否大于10、ROM是否有WiFi扫描频次限制。其中定位开关这一项最容易被人忽略很多国产手机在权限设置里给了“精确定位”和“模糊定位”你选了“模糊定位”WiFi扫描也会失败。5.4 常见问题速查表现象最常见原因解决方案requireNativePlugin返回null插件未注册或基座未更新检查dcloud_uniplugins.json、重编自定义基座方法调不到缺少UniJSMethod注解补上注解并重编回调无响应参数类型不匹配统一使用JSONObject传参能连WiFi但无法上网未做连通性验证、或热点无外网原生侧增加ping网关逻辑、前端给提示扫描列表为空定位权限或GPS开关未开引导用户开启定位服务Android 14上旧代码失效新版本收紧附近设备权限增加NEARBY_WIFI_DEVICES权限声明5.5 调试时的保命技巧原生代码改动后如果没有自动同步到自定义基座建议在HBuilderX里重新“制作自定义调试基座”。另外原生插件里的日志可以用Log.e先打个标记然后在Android Studio的Logcat里过滤自己的包名这样报错时定位能快很多。JS侧的报错无法直接映射到原生代码所以插件里一定要对每个异常分支都给出明确错误码回传后前端再展示成可理解的文案。结尾我在实际项目里做完这套WiFi模块之后最大的体会是uni-app和Android原生之间的通信没有想象中那么玄乎本质就是注册一个类、暴露几个方法、回来几个回调。真正花时间的地方反而在权限适配、不同Android版本的行为差异、以及各种国产ROM的“个性化策略”上。建议你自己动手做的时候先从打开WiFi这个最小功能跑通再逐步加扫描、连接每一步都验证回调正常再往深走。另外如果你后续还要在这个插件里做蓝牙、串口之类的能力这套UniModule的架构完全可以直接复用——把各个功能拆成独立的方法就行没必要重新搭一套通信体系。本文还有配套的精品资源点击获取
返回列表