资讯动态

Android扫码连Wi-Fi:WIFI串解析与三条连接分支

发布时间:2026/10/26 3:53:08 来源:尧图企业网站定制
简介面向Android开发者的WiFi二维码扫描与自动连接实战项目围绕扫码解析网络名称、密码和加密方式再借助系统无线管理接口完成配置保存与连接反馈的完整链路展开。压缩包共141个文件、约2.16MB主要由20个Java源码、41个编译类文件、13个XML布局与配置、6个Jar依赖组成并配有PNG界面素材、APK安装包和工程属性文件目录结构便于导入开发工具后对照调试。已有2015人学习下载适合希望掌握二维码识别、无线配置对象构造、动态权限申请和连接异常处理的初中级开发者。示例将扫码、配置解析、网络添加、保存连接及结果提示串联为可运行流程可帮助读者快速排查权限声明、网络名称引号处理、加密类型设置等常见问题并改造成自有应用中的便捷联网入口。1. 扫码连 Wi-Fi真正难的是后半段Android 扫描 Wi-Fi 二维码自动连接这件事拆开看是两个完全独立的工程问题。前半段是把一张二维码还原成 SSID、加密类型和密码纯字符串处理一个下午就能写完后半段是让系统真的去连这张网而这一段从 Android 10 开始被系统权限和 API 重构过两轮静默连接的口子被彻底关死。我见过太多演示工程扫码解析一秒出结果点“连接”之后就是转圈、闪退或者连上了但应用流量还走蜂窝。这份资源值不值得拿取决于它有没有把后半段的三条分支写全——WifiNetworkSpecifier、WifiNetworkSuggestion和 API 29 以下的addNetwork——以及有没有处理转义、权限、路由这些“看起来能跑、换台机型就废”的地方。它适合已经能写基础 Android 界面、需要做配网引导页或入网激活页的人如果你只想找个扫码控件贴上去这里面的坑会比你预想的多。2. 拆解WIFI:串字段、转义和解析容错2.1 一张 Wi-Fi 二维码里到底装了什么绝大多数手机相机认的 Wi-Fi 二维码内容是一条以WIFI:开头的分号分隔串。它没有正式 RFC但事实标准在几大平台上是一致的字段就四个字段含义常见取值是否必填T认证类型WPA、WEP、SAE、nopass建议必填缺省按nopass处理SSSID132 字节的 UTF-8 字符串必填P密码明文nopass时为空视T而定H是否为隐藏网络true/false可省略缺省false一条典型的串长这样WIFI:T:WPA;S:Office-5G;P:abc12345;;。注意结尾是两个分号第一个结束最后一个字段第二个是整串的终止符。字段顺序不固定我见过S写在T前面的生成器也见过省略H直接以;;收尾的。所以解析时不能按位置取值必须按 key 建映射。真正的麻烦在转义。规则是反斜杠、分号、逗号、冒号、双引号这五个字符如果出现在 SSID 或密码里前面要加一个反斜杠。SSID 里带分号的情况在真实环境里不算罕见——有些路由器默认 SSID 就带标点。如果解析器只是split(;)这类网络会直接解析失败而且失败得很隐蔽你会拿到一个被截断的 SSID然后连接时一直超时日志里什么都看不出来。2.2 手写解析器转义符是最容易翻车的地方不依赖任何条码库自带的解析工具自己写一个状态机是最稳的做法因为不同版本的库对WIFI:串的兼容程度不一样而这段逻辑总共不到四十行。// WifiQr.kt —— 纯字符串解析不依赖任何第三方库 enum class WifiSec { OPEN, WEP, WPA2, WPA3 } data class WifiQr( val ssid: String, val password: String, val sec: WifiSec, val hidden: Boolean ) fun parseWifiQr(raw: String?): WifiQr? { if (raw.isNullOrBlank()) return null // 前缀大小写都兼容有些生成器会输出小写 wifi: if (!raw.startsWith(WIFI:, ignoreCase true)) return null val fields HashMapChar, String() var i 5 // 跳过 WIFI: while (i raw.length) { if (raw[i] ;) break // 遇到结尾的 ;; 就收工 // key 必须是单个字符紧跟冒号否则认为整串非法 if (i 1 raw.length || raw[i 1] ! :) return null val key raw[i].uppercaseChar() i 2 val sb StringBuilder() while (i raw.length) { val c raw[i] if (c \\ i 1 raw.length) { // 转义吃掉反斜杠原样保留下一个字符 sb.append(raw[i 1]); i 2; continue } if (c ;) { i; break } // 字段结束 sb.append(c); i } fields[key] sb.toString() } val ssid fields[S] ?: return null val pwd fields[P].orEmpty() val type fields[T].orEmpty() val sec when { type.equals(SAE, true) - WifiSec.WPA3 // WPA3-Personal type.equals(WEP, true) - WifiSec.WEP type.equals(nopass, true) || type.isBlank() - WifiSec.OPEN else - WifiSec.WPA2 // WPA / WPA2 / WPA2-WPA3 混合一律先按 WPA2 试 } return WifiQr(ssid, pwd, sec, fields[H].equals(true, true)) }这段代码的关键点有三个。第一转义处理放在内层循环最前面先判断反斜杠再判断分号顺序反了就会把\;当成字段分隔符。第二i是手动推进的游标不能用for循环加split否则转义字符会被当成分隔符切碎。第三;之后的continue不写死而是靠外层判断raw[i] ;提前 break这样结尾是一个分号还是两个分号都能兼容。很多现成实现死在这一步遇到;;结尾的串会多解析出一个空字段。T字段的值映射要留一手WPA这个名字被滥用得最厉害。有些生成器把 WPA3 网络也标成WPA有些把 WPA2/WPA3 混合模式标成WPA。先按 WPA2 处理是成功率最高的默认值连不上再降级到 WPA3 重试一次这个兜底逻辑后面第 3 章会用到。2.3 解析结果的合法性校验与降级策略解析出对象不等于能连。要在进入连接层之前把明显不可能成功的输入挡掉否则错误会被系统吞掉排查成本翻倍。先做长度校验收窄SSID 按 UTF-8 编码后的字节数必须在 132 之间注意是字节不是字符中文 SSID 一个汉字占 3 字节10 个汉字就超了。WPA2 的密码长度是 863 个 ASCII 字符。OPEN类型直接忽略P字段哪怕它非空——有些二维码会带上占位密码照着传进去反而会让连接失败。再做一次归一化把 SSID 首尾的不可见字符U200B、UFEFF 这类剔掉。这个坑我踩过某个路由器管理页导出的二维码里 SSID 尾部带了一个零宽空格肉眼完全看不出来连接时系统一直报“网络未找到”。提示校验失败时给用户的提示要具体到字段比如“这串二维码里的网络名超过 32 字节”而不是笼统的“二维码无效”。配网场景的用户往往就在路由器旁边具体提示能省掉一轮沟通。还有一种降级情况要处理部分二维码不是标准WIFI:串而是纯文本两行——第一行 SSID、第二行密码。这种格式在部分老设备的管理页上存在。我一般会在parseWifiQr返回 null 后再尝试一次“两行文本”的启发式解析成立就按 WPA2 处理同时在界面上明确告诉用户这是推测结果让他确认。3. 连接层的三条分支Specifier、Suggestion 与老 addNetwork3.1 API 29 是分水岭先把行为差异固定下来从 Android 10API 29开始面向 29 及以上编译的应用调用WifiManager.addNetwork()会直接返回-1而且不抛异常只是静默失败。这个改动把“填个密码后台连上”的整套玩法废掉了也把连接 Wi-Fi 这件事拆成了两条官方路径WifiNetworkSpecifier一次性、弹窗、应用内可见和WifiNetworkSuggestion持久、走通知栏、用户点一下才生效。先把三条分支的行为差异列清楚选型时对着表看就行能力API ≤28 的addNetworkWifiNetworkSpecifier29WifiNetworkSuggestion29用户确认方式无静默连接系统弹窗“连接到网络”通知栏提示点击后连网络是否持久保存是写进系统配置否unregister后即释放是长期保留连接对其他应用可见是否仅本应用可用是全局可用是否影响默认网络是否是隐藏网络支持支持不支持无对应 setterAPI 30 支持setIsHiddenSsid选型结论其实很直接如果是“扫完码当场连上、验证设备可用”的配网流程用WifiNetworkSpecifier如果是“把家里网络保存下来以后自动连”的场景用WifiNetworkSuggestion。前者体验顺滑但退出页面就断后者体验绕但结果持久。做配网引导页的一般两个都要实现让用户选“仅本次连接”还是“保存到系统”。另外提前说一句面向 API 29 及以上编译后WifiManager.setWifiEnabled()也无法调用返回false。应用没法替用户打开 Wi-Fi 开关。常见做法是拉起系统面板// 引导用户自己去打开 Wi-Fi 开关 startActivity(Intent(Settings.Panel.ACTION_WIFI))这个Intent在 API 29 及以上可用不会跳出应用堆栈用户下拉点一下 Wi-Fi 就回去了比跳到设置首页体验好很多。3.2WifiNetworkSpecifier弹窗式一次性连接这是配网场景用得最多的一条路。构造一个NetworkRequest把 SSID 和密码塞进WifiNetworkSpecifier交给ConnectivityManager.requestNetwork()系统会弹出一个选择框用户点“连接”之后网络就通了而且这个网络只在当前应用内可用。RequiresApi(Build.VERSION_CODES.Q) class SpecifierConnector(private val ctx: Context) { private val cm ctx.getSystemService(ConnectivityManager::class.java) private var callback: ConnectivityManager.NetworkCallback? null fun connect(qr: WifiQr, timeoutMs: Int 30_000, onResult: (Boolean, Network?) - Unit) { release() // 先释放上一次避免回调重复触发 val specBuilder WifiNetworkSpecifier.Builder().setSsid(qr.ssid) when (qr.sec) { WifiSec.WPA2 - specBuilder.setWpa2Passphrase(qr.password) WifiSec.WPA3 - specBuilder.setWpa3Passphrase(qr.password) WifiSec.WEP - specBuilder.setWepKey(qr.password) WifiSec.OPEN - Unit // 开放网络一个 setXxxPassphrase 都不能调 } val request NetworkRequest.Builder() .addTransportType(NetworkCapabilities.TRANSPORT_WIFI) // 刻意不加 NET_CAPABILITY_INTERNET加上就要求该网络必须真正可上外网 // 内网设备摄像头、打印机会被判成不可用直接 onUnavailable .setNetworkSpecifier(specBuilder.build()) .build() val cb object : ConnectivityManager.NetworkCallback() { override fun onAvailable(network: Network) onResult(true, network) override fun onUnavailable() onResult(false, null) override fun onLost(network: Network) { /* 网络断开按需通知 UI */ } } callback cb cm.requestNetwork(request, cb, timeoutMs) } fun release() { callback?.let { runCatching { cm.unregisterNetworkCallback(it) } } callback null } }参数上有几个必须说清楚的地方。setSsid传的是裸 SSID不要自己加引号——老WifiConfiguration那套才需要引号混用是最常见的错误来源。加密类型的四个 setter 互斥开放式网络一个都不能调调了系统会认为你要求特定的加密方式而开放热点不匹配于是直接弹框失败。timeoutMs建议给 30 秒太短用户还没看清弹窗就超时了太长会把 UI 卡在“连接中”的状态。requestNetwork的超时不同于onUnavailable超时后会回调onUnavailable两者在业务上可以用同一个失败分支处理。拿到Network对象之后如果只想让某个请求走这张网比如连接设备的本地 HTTP 接口正确的做法是用network.getSocketFactory()建 socket或者在协程里用network.openConnection(url)。全局改路由要用cm.bindProcessToNetwork(network)但记得在页面销毁时bindProcessToNetwork(null)恢复否则退出后整个应用的流量都会卡在这张不可用的网络上——这个黑匣子现象后面第 5 章会展开。还有一个容易忽略的行为如果手机系统里已经保存过一个同名 SSID 的网络requestNetwork可能不走弹窗直接匹配到那条已保存的配置上。这意味着用户明明改了密码扫码之后却连到了旧密码的网络表现为一连就断。处理方式是在发起连接前先检查系统的已保存配置发现同名就提示用户“系统中已存在同名网络请先忘记”。3.3WifiNetworkSuggestion走通知栏的持久建议这条路径适合“保存网络”的场景。应用把建议提交给系统系统在通知栏挂一条提示用户点击确认后才真正加入已保存网络列表之后开机自动重连都由系统负责。RequiresApi(Build.VERSION_CODES.Q) fun suggestNetwork(ctx: Context, qr: WifiQr): Int { val wm ctx.getSystemService(WifiManager::class.java) val builder WifiNetworkSuggestion.Builder().setSsid(qr.ssid) when (qr.sec) { WifiSec.WPA2 - builder.setWpa2Passphrase(qr.password) WifiSec.WPA3 - builder.setWpa3Passphrase(qr.password) WifiSec.WEP - builder.setWepKey(qr.password) WifiSec.OPEN - Unit } // 隐藏网络只在 API 30 有开关低版本只能引导用户手动添加 if (qr.hidden Build.VERSION.SDK_INT Build.VERSION_CODES.R) { builder.setIsHiddenSsid(true) } val status wm.addNetworkSuggestions(listOf(builder.build())) return status // 0 表示成功非 0 见下面的错误码 }返回值是几个固定常量必须逐一处理不能只判断“成功/失败”STATUS_NETWORK_SUGGESTIONS_SUCCESS表示提交成功STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_DUPLICATE表示已经有相同的建议这时候再提交也没用直接提示用户“已保存过该网络”STATUS_NETWORK_SUGGESTIONS_ERROR_ADD_EXCEEDS_MAX_PER_APP表示单个应用的建议数量超限上限是 50 条说明历史建议没有清理需要先removeNetworkSuggestionsSTATUS_NETWORK_SUGGESTIONS_ERROR_APP_DISALLOWED一般是用户在系统设置里关掉了该应用的“附近设备”能力。用户点击通知确认之后系统会发一个WifiManager.ACTION_WIFI_NETWORK_SUGGESTION_POST_CONNECTION广播但注意这个广播只能在运行期动态注册接收写在清单里是收不到的。我的习惯是在提交建议后注册一次收到广播就提示“网络已保存”。3.4 低于 API 29 的兼容分支老系统上仍然可以用WifiConfiguration那套虽然代码长一点但胜在完全静默。这段逻辑只对 API ≤28 生效不要在更高版本上保留入口。Suppress(DEPRECATION) fun legacyConnect(ctx: Context, qr: WifiQr): Boolean { val wm ctx.getSystemService(WifiManager::class.java) // 已保存过同名网络先删掉否则新密码不会生效 wm.configuredNetworks?.firstOrNull { it.SSID \${qr.ssid}\ }?.let { wm.removeNetwork(it.networkId) } val cfg WifiConfiguration().apply { SSID \${qr.ssid}\ // 老接口要求双引号包裹漏了会返回 -1 hiddenSSID qr.hidden when (qr.sec) { WifiSec.OPEN - allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) WifiSec.WEP - { wepKeys[0] \${qr.password}\ allowedAuthAlgorithms.set(WifiConfiguration.AuthAlgorithm.OPEN) allowedKeyManagement.set(WifiConfiguration.KeyMgmt.NONE) } else - { preSharedKey \${qr.password}\ allowedKeyManagement.set(WifiConfiguration.KeyMgmt.WPA_PSK) } } } val netId wm.addNetwork(cfg) if (netId -1) return false wm.disconnect() wm.enableNetwork(netId, true) return wm.reconnect() }这里的引号是刚性要求SSID和preSharedKey都要用双引号包起来这是老接口的约定不是可选项。另外别忘了removeNetwork那一步老系统上同名网络会覆盖不掉导致改了密码之后永远连不上用户以为二维码是坏的。注意这段代码必须在Build.VERSION.SDK_INT Build.VERSION_CODES.Q的分支里调用在 API 29 及以上执行会拿到-1而不报错排查起来很浪费时间。4. 相机与权限把扫码链路接完整4.1 CameraX 取帧与解码的旋转问题扫码部分用 CameraX 的ImageAnalysis做连续取帧解码用条码库的MultiFormatReader。这里不需要接任何系统级扫码服务本地解码离线可用也避开了部分定制系统上没有相应服务的问题。private val reader MultiFormatReader().apply { setHints(mapOf(DecodeHintType.POSSIBLE_FORMATS to listOf(BarcodeFormat.QR_CODE))) } androidx.camera.core.ExperimentalGetImage fun bindAnalyzer(useCase: ImageAnalysis, onQr: (String) - Unit) { val executor Executors.newSingleThreadExecutor() useCase.setAnalyzer(executor) { proxy - try { val media proxy.image ?: returnsetAnalyzer // 用 toBitmap 而不是手撸 YUVrowStride 通常大于 width // 直接按 width 拷贝会让整张图斜切解码率断崖下跌 val bmp media.toBitmap() val rotated if (proxy.imageInfo.rotationDegrees ! 0) { rotate(bmp, proxy.imageInfo.rotationDegrees) } else bmp val source RGBLuminanceSource(rotated) val bitmap BinaryBitmap(HybridBinarizer(source)) reader.decodeWithState(bitmap)?.let { onQr(it.text) } } catch (_: NotFoundException) { // 一帧里没有码正常现象不要打日志刷屏 } finally { reader.reset() proxy.close() // 不 close 会在第三帧左右彻底卡死 } } }几个参数要交代清楚。STRATEGY_KEEP_ONLY_LATEST是必须的用STRATEGY_BLOCK_PROCESSING在低端机上会把预览拖到个位数帧率。分辨率建议设成 1280×720 这一档太低了小码识别不出来太高了解码一帧要几十毫秒反而丢帧。rotationDegrees不能省——竖屏持机时传感器的图像是横的不旋转的话二维码在图像里躺倒 90 度解码器照样认不出来表现为“对着码半天没反应”非常劝退。proxy.close()放在finally里是硬性要求。ImageAnalysis的缓冲区数量有限默认 2 个漏掉一次close()就会导致后续帧拿不到日志里也没有任何报错就是画面不动、码不识别。另外要做去重。同一个码在视场内会连续几十帧都识别成功如果每次都触发一次连接用户会看到系统弹窗反复出现。常见的做法是加一个时间戳门限成功解码后记录内容哈希三秒内相同内容直接忽略。4.2NEARBY_WIFI_DEVICES与neverForLocation权限这块从 Android 13 起发生了变化。早期版本把 Wi-Fi 相关的接口都挂在位置权限下从 13 开始独立出NEARBY_WIFI_DEVICES并且允许声明“我不拿它推导位置”这样用户可以只授予这个权限而不用交出位置信息。清单要这么写uses-permission android:nameandroid.permission.CAMERA / uses-permission android:nameandroid.permission.CHANGE_WIFI_STATE / uses-permission android:nameandroid.permission.ACCESS_WIFI_STATE / !-- 1012 上系统仍沿用位置权限做前置校验加 maxSdkVersion 避免 13 以上重复申请 -- uses-permission android:nameandroid.permission.ACCESS_FINE_LOCATION android:maxSdkVersion32 / !-- 13 起改用附近设备权限neverForLocation 声明不用于推导位置 -- uses-permission android:nameandroid.permission.NEARBY_WIFI_DEVICES android:usesPermissionFlagsneverForLocation tools:targetApitiramisu /tools:targetApi只是给 Lint 看的需要根节点声明xmlns:tools否则编译期就会报未解析的命名空间。neverForLocation这个标记的作用是让系统在申请时不再强制要求位置权限但也意味着你承诺不通过 Wi-Fi 扫描结果推断用户位置——如果后续要读扫描列表做网络选择就不该加这个标记否则部分机型上getScanResults会返回空列表。这是取舍不是纯优化。运行时申请按版本分流private val launcher registerForActivityResult( ActivityResultContracts.RequestMultiplePermissions() ) { result - val ok result.values.all { it } if (ok) startScan() else showPermissionGuide() } fun requestWifiPermission() { val perms buildList { add(Manifest.permission.CAMERA) if (Build.VERSION.SDK_INT Build.VERSION_CODES.TIRAMISU) { add(Manifest.permission.NEARBY_WIFI_DEVICES) } else { add(Manifest.permission.ACCESS_FINE_LOCATION) } } launcher.launch(perms.toTypedArray()) }注意这里用RequestMultiplePermissions而不是单权限申请因为相机和 Wi-Fi 权限要一次问完。分两次问在部分定制系统上会出现第二次弹窗被拦截的情况用户只点了第一次后面一直失败。用户拒绝后不要再反复弹——系统在拒绝两次之后会直接返回拒绝且不显示弹窗此时正确做法是引导到应用设置页。4.3 版本行为对照表排查问题时经常需要快速确认“这个版本上到底该走哪条路”把关键差异整理成一张表放在手边系统版本APIWi-Fi 权限可用的连接方式备注6.08.12327ACCESS_FINE_LOCATIONaddNetwork需要位置权限才能拿到扫描结果928ACCESS_FINE_LOCATIONaddNetwork同上行为基本一致10122932ACCESS_FINE_LOCATIONSpecifier/SuggestionaddNetwork返回 -113 及以上33NEARBY_WIFI_DEVICESSpecifier/Suggestion未申请会抛SecurityException表里最后一行是升级targetSdk之后最容易炸的地方应用在低版本上跑得好好的改成 33 编译之后一调requestNetwork就崩堆栈指向ConnectivityManager看不出和权限有什么关系。5. 避坑与排查五个真实翻车现场5.1 现象二维码扫出来了SSID 却是半截原因解析器用split(;)切分SSID 里的分号被当成了字段分隔符。这类 SSID 在真实环境里不算少路由器后台默认生成的名称就带标点。解决换成 2.2 节的状态机写法把转义处理放在判断分隔符之前同时在上线前用一批“带分号、带冒号、带反斜杠、中文、带零宽字符”的 SSID 做回归这五类覆盖了绝大多数生成器的输出差异。5.2 现象系统弹窗点了“连接”回调却是onUnavailable这个现象最费时间因为原因有三个得逐个排除。第一手机里已经保存过同名 SSID 但密码不同requestNetwork匹配到旧配置后握手失败。第二NetworkRequest里加了NET_CAPABILITY_INTERNET而目标是内网设备摄像头、打印机系统判定该网络不可上网直接判失败。第三开放式网络错误地调用了某个setXxxPassphrase。解决先查已保存配置并提示用户忘记网络把INTERNET能力去掉开放网络分支里一个 passphrase setter 都不调。排查时把onUnavailable和超时分开打日志能快速定位是哪一个。5.3 现象升级targetSdk到 33 后连接直接抛SecurityException原因从 Android 13 起WifiNetworkSpecifier、addNetworkSuggestions这类接口要求NEARBY_WIFI_DEVICES而清单里只有老的ACCESS_FINE_LOCATION。更隐蔽的是部分机型在缺失权限时不抛异常只是requestNetwork静默不弹窗用户以为按钮没反应。解决清单按 4.2 节补齐运行时按版本分流申请在调用连接前统一做一次checkSelfPermission前置判断缺权限就走引导页不要等系统抛异常。5.4 现象网络连上了App 的请求还是走蜂窝原因WifiNetworkSpecifier创建的网络默认不是进程的默认网络onAvailable回调给了你Network对象但没告诉你“后面的请求该用它”。这个设计是刻意的因为这条网络只对本应用可见。解决要么用network.getSocketFactory()显式建连要么调cm.bindProcessToNetwork(network)把整个进程绑过去。用后者的务必在页面销毁时bindProcessToNetwork(null)否则退出页面后应用的所有请求都卡在这张已经释放的网络上表现为“退出配网页之后应用整体没网了”用户只能重启应用——这就是那个最典型的黑匣子故障。5.5 现象相机预览卡住扫码再也不出结果原因ImageAnalysis的ImageProxy没有close()缓冲区被占满之后分析器拿不到新帧。另一种情况是解码成功后在主线程直接改 UI帧率被拖垮看起来像卡死。解决close()一律放finally解码回调切到主线程再更新 UI加三秒内容去重避免同一个码连续触发几十次连接流程。如果还卡把setTargetResolution降到 1280×720 以下试一次能立刻判断是不是解码耗时的问题。6. 进阶用自家二维码做闭环验证调试配网功能最大的麻烦是手边没有可控的热点。我的做法是先写一个生成端把当前网络信息编成标准WIFI:串再出图然后拿另一台设备扫——这样 SSID 和密码都是自己定的改一个字符就能复现某类边界条件比拿真实路由器试快得多。生成端只要注意两件事字符串里的五个特殊字符要转义结尾补两个分号。fun buildWifiQrString(qr: WifiQr): String { fun esc(s: String) s.replace(\\, \\\\) .replace(;, \\;).replace(,, \\,) .replace(:, \\:).replace(\, \\\) val t when (qr.sec) { WifiSec.OPEN - nopass; WifiSec.WEP - WEP WifiSec.WPA3 - SAE; else - WPA } return WIFI:T:$t;S:${esc(qr.ssid)};P:${esc(qr.password)};H:${qr.hidden};; }转义顺序不能乱反斜杠必须第一个替换否则后面替换出来的反斜杠会被二次转义生成一张自己都解不开的码。这个函数建议直接放进调试菜单里改完 SSID 立刻出图形成“生成扫描连接回读”的闭环。验证连接是否真的成功不要只看onAvailable。可靠的判定是连接后回读一次WifiManager.getConnectionInfo()比对SSID注意返回值带引号要剥掉和networkId再发一个目标网段的探测请求。onAvailable只表示链路层可用DHCP 还没走完的情况很常见此时立刻发业务请求会拿到UnknownHostException。对于需要“连上之后马上访问设备本地接口”的场景我给每个连接流程都加了一个超时兜底requestNetwork的超时设 30 秒业务请求本身再加 5 秒超时两层都超时就走失败分支并提示用户手动去系统设置里连接。这个兜底看起来很保守但配网场景的用户容忍度其实很高——他们怕的是转圈没有尽头而不是多等一会儿。从那以后我每接一个配网需求都会先把这张版本对照表和三条连接分支的失败返回码打印出来贴在屏幕边上代码跑通之前不碰界面。这套流程救过我好几次有一次线上反馈某型号连不上照着表一看是客户把targetSdk提到了 33 而清单里还留着带maxSdkVersion的老权限五分钟就定位了。这份资源里的三条分支和避坑清单拿去对着自己的工程过一遍基本能绕开上面这些坑。希望帮到你。本文还有配套的精品资源点击获取

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

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

免费获取报价 →
↑