Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 处理 system_settings_2
Flutter 3.47 鸿蒙三方库适配实战:从 0 到 1 处理 system_settings_2
应用里「跳到系统设置」几乎是个必做功能:引导用户开通知权限、开蓝牙、连 WLAN、看应用详情。安卓有 Intent(Settings.ACTION_*) 一整套,iOS 只能打开本应用的设置页。system_settings_2 把这两套差异收进 25 个同签名方法里,本批里它和 airplane_mode_checker 都属于「系统设置」这条能力线。
本文以 system_settings_2 3.0.2 为例,记录我如何保留原有 Dart API、补齐 Flutter OH 插件入口、在鸿蒙端用 startAbility + Want 接入真实系统设置页,并在 HarmonyOS API 26 真机上完成构建、安装、启动与跳转验证。
适配仓库: oh-flutter/system_settings_2
适配分支: feat/ohos_system_settings_2_3.0.2
适配 TAG: 3.0.2-ohos-1.0.0-beta.1
一、先看最终效果

图 1:HarmonyOS API 26 真机启动自检台。页面顶部显示「已成功 0 次 · 失败 0 次」,下方是 25 个与上游方法一一对应的按钮。
| 验证点 | 实测结果 | 证据 |
|---|---|---|
| Flutter OH 3.47 工程构建 | 通过 | entry-default-signed.hap |
| 安装、启动(真机) | 通过 | 图 1 |
SystemSettings.wifi() | 成功跳到系统 WLAN 页 | 图 2 |
| 返回后调用计数 | 已成功 1 次 · 失败 0 次 | 图 3 |

图 2:点击「WLAN · wifi」按钮后,系统真的打开了设置里的 WLAN 页面——左上角标题是「WLAN」,带返回箭头和开关,说明 startAbility + uri="wifi_entry" 生效。

图 3:按返回键回到自检台,顶部统计变成「已成功 1 次 · 失败 0 次」,「最近一次」显示 wifi → 成功。
二、这个库原来是怎么工作的
system_settings_2 的接口面出奇地整齐——25 个方法,全部无参数、无返回值,每个方法在 Dart 侧就是一次 invokeMethod:
Dart SystemSettings
-> MethodChannel('system_settings_2')
-> Android: Intent(Settings.ACTION_WIFI_SETTINGS) 等 25 个 ACTION
-> iOS: UIApplication.openSettingsURLString(只能开本应用设置页)
覆盖的能力包括:设置首页、无线与网络、WLAN、蓝牙、NFC、移动数据、数据漫游、飞行模式、显示、声音、日期、语言、定位、隐私、安全、无障碍、存储、电量、电源、应用列表、通知管理、默认应用、关于本机,以及「本应用详情」和「本应用通知」两个针对自身应用的入口。
正因为返回类型是 void,鸿蒙侧的实现目标很明确:把调用方送到正确的设置页面,不需要回传任何数据。
三、实际环境
Flutter 鸿蒙环境搭建不在本文重复展开,直接参考 Flutter OH 环境搭建指南。
| 组件 | 版本 |
|---|---|
| Flutter OH | 3.47.4-ohos-1.0.4 |
| Dart | 3.13.3 |
| DevEco Studio | 26.0.0 Release |
| HarmonyOS SDK | API 26 |
| 测试设备 | 华为真机 6UMBB26319007180,HarmonyOS 7.0.0.105(API 26) |
| 目标库 | system_settings_2 3.0.2 |
flutter --version
hdc list targets
四、确认上游版本并同步到 AtomGit
上游包地址是 https://github.com/timmaffett/system_settings_2,版本 3.0.2,使用 MIT License。适配前我在 AtomGit 目标组织中搜索了同名库,没有找到可以直接使用的 OHOS 实现。
基线核对:上游
master(db8faac,pubspec 写3.0.2)与 pub.dev 上的 3.0.2 逐文件一致(81 个文件相同,仓库仅多出.github/.gitignore/.vscode等工程文件)。
git clone https://atomgit.com/oh-flutter/system_settings_2.git
cd system_settings_2
git switch feat/ohos_system_settings_2_3.0.2
git log --oneline -5
五、创建适配分支并检查工程结构
git switch -c feat/ohos_system_settings_2_3.0.2
关键目录如下:
system_settings_2/
├── lib/system_settings_2.dart # 上游唯一的 Dart 文件,一行未改
├── ohos/
│ ├── index.ets # OHOS 插件导出入口
│ └── src/main/ets/components/plugin/
│ └── SystemSettingsPlugin.ets # 通道实现(约 200 行)
├── example/lib/main.dart # 真机验证自检台
├── example/ohos/ # Flutter OH 宿主工程
├── pubspec.yaml # 声明 ohos 平台
└── README.OpenHarmony_CN.md # 鸿蒙适配说明
pubspec.yaml 中的关键声明是:
flutter:
plugin:
platforms:
ohos:
pluginClass: SystemSettingsPlugin
pluginClass 必须和 ohos/index.ets 导出的类名一致,否则 Dart 端能编译,运行时却不会注册插件。
六、适配过程:注册通道并对齐语义
6.1 通道注册
本库只有一个方法通道,没有事件通道:
const METHOD_CHANNEL: string = 'system_settings_2';
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.channel = new MethodChannel(binding.getBinaryMessenger(), METHOD_CHANNEL);
this.channel.setMethodCallHandler(this);
Log.i(TAG, 'system_settings_2 channel registered');
}
6.2 核心:25 个方法映射成「bundleName + uri」
鸿蒙没有「Settings.ACTION_*」这种枚举,但系统设置应用接受 Want 里的 uri 参数来定位子页面。所以实现里建了一张「Dart 方法名 → 子页面 uri」的映射表:
/** 鸿蒙"设置"应用:所有跳转都落到它身上。 */
const SETTINGS_BUNDLE: string = 'com.huawei.hmos.settings';
const SETTINGS_ABILITY: string = 'com.huawei.hmos.settings.MainAbility';
function buildPageUris(): Map<string, string> {
const pages: Map<string, string> = new Map();
pages.set('system', ''); // 空串 = 设置首页
pages.set('wireless', 'wifi_entry');
pages.set('wifi', 'wifi_entry');
pages.set('bluetooth', 'bluetooth_entry');
pages.set('nfc', 'more_connections_entry');
pages.set('data-usage', 'mobile_network_entry');
pages.set('display', 'display_entry');
pages.set('sound', 'sound_entry');
pages.set('date', 'date_and_time_entry');
pages.set('locale', 'language_and_region_entry');
pages.set('apps', 'application_info_entry');
// ... 共 25 项
return pages;
}
onMethodCall 就退化成一次查表 + 一次 startAbility:
onMethodCall(call: MethodCall, result: MethodResult): void {
const uri: string | undefined = PAGE_URIS.get(call.method);
if (uri === undefined) {
result.notImplemented();
return;
}
this.openSettings(call.method, uri)
.then((): void => { result.success(null); })
.catch((error: BusinessError): void => {
result.error('ERROR', `Failed to open system settings: ${error.message}`, null);
});
}
6.3 「本应用」的两个页面靠参数定位
app 和 app-notifications 不是独立 uri,而是同一个页面 + 带上自己的包名:
const PARAM_PUSH: string = 'pushParams';
if (method === 'app' || method === 'app-notifications') {
const params: Record<string, Object> = {};
params[PARAM_PUSH] = ability.context.abilityInfo.bundleName;
want.parameters = params;
}
6.4 插件必须实现 AbilityAware
这是本库与「纯数据类插件」最大的区别:startAbility 是 UIAbilityContext 的能力,而插件默认拿不到 UIAbility。
export default class SystemSettingsPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
private ability: UIAbility | null = null;
onAttachedToAbility(binding: AbilityPluginBinding): void {
this.ability = binding.getAbility();
}
onDetachedFromAbility(): void {
this.ability = null;
}
}
如果没有 UIAbility 就跳不了页面,所以 openSettings 里先做严格判断:
if (ability === null) {
throw new Error('UIAbility is not attached, cannot start settings ability');
}
宁可报错也不静默失败——这比返回一个假的成功值更好排查。
6.5 子页面 uri 是系统约定,必须有兜底
这些 uri 不属于公开 API,不同系统版本或机型上可能存在差异。所以每个方法都做了「打不开就退回首页」的兜底:
try {
await context.startAbility(want);
Log.i(TAG, `${method} -> ${SETTINGS_BUNDLE} uri="${uri}" (ok)`);
} catch (error) {
const err: BusinessError = error as BusinessError;
Log.w(TAG, `${method} -> uri="${uri}" failed code=${err.code} ${err.message}, ` +
'falling back to settings home');
await context.startAbility({
bundleName: SETTINGS_BUNDLE,
abilityName: SETTINGS_ABILITY,
} as Want);
}
这样即使某个子页面在特定设备上不存在,用户也至少会落在设置首页,而不是什么都不发生。
| 方法 | OpenHarmony 实现 |
|---|---|
system() | startAbility,不带 uri → 设置首页 |
wifi() / wireless() | uri="wifi_entry" |
bluetooth() | uri="bluetooth_entry" |
nfc() | uri="more_connections_entry" |
airplaneMode() / dataRoaming() / dataUsage() | uri="mobile_network_entry" |
app() / appNotifications() | uri="application_info_entry" / "notification_entry" + pushParams=<本应用包名> |
| 其余 19 个 | 各自对应的 *_entry |
七、构建配置与签名
这里踩了两个坑,都值得记下来。
7.1 compatibleSdkVersion 必须写 "26.0.0"
上游示例工程沿用旧模板,写的是 '5.1.0(18)'。用本机 Flutter OH 3.47.4 构建时直接失败:
00306004 Specification Limit Violation
The project's compatibleSdkVersion: 18 cannot be lower than
the minimum compatible version 24 required by the dependencies: @ohos/flutter_ohos.
但改成 '6.0.0(20)' 仍报同样的错,改成 '26.0.0(26)' 则报 00308018 Unknown Error。正确写法是不带括号的纯版本号:
{
"products": [
{
"name": "default",
"signingConfig": "default",
"compatibleSdkVersion": "26.0.0",
"targetSdkVersion": "26.0.0",
"runtimeOS": "HarmonyOS"
}
]
}
7.2 签名证书绑定了 bundleName
配置好签名后仍然失败:
00303074 Configuration Error
The bundleName in app.json5/hvigorfile.ts does not match the bundleName
in the generated SigningConfigs.
原因是调试证书是按包名签发的。本库示例的 app.json5 写的是 xyz.hiveright.system_settings_2_example,而证书对应的是 com.example.flutter_oh_demo。把 AppScope/app.json5 的 bundleName 改成证书对应的值即可:
{
"app": {
"bundleName": "com.example.flutter_oh_demo",
...
}
}
证书、p12、p7b 和密码没有提交到仓库,读者应在自己的 DevEco Studio 中生成调试签名。
八、通过 AtomGit 引入适配版
在另一个 Flutter OH 应用中使用时,建议锁定 TAG:
dependencies:
system_settings_2:
git:
url: https://atomgit.com/oh-flutter/system_settings_2.git
ref: 3.0.2-ohos-1.0.0-beta.1
flutter pub get
然后在 pubspec.lock 检查 source: git、AtomGit 地址和 resolved-ref,确认没有解析到 pub.dev 的同名版本。
本库不需要任何权限声明——startAbility 打开系统设置应用属于正常的应用间跳转,不涉及敏感权限。
九、构建、安装和真机验证
cd example
flutter pub get
flutter analyze
flutter build hap --debug
hdc install -r build/ohos/hap/entry-default-signed.hap
hdc shell aa start -a EntryAbility -b com.example.flutter_oh_demo
构建输出为 ✓ Built build/ohos/hap/entry-default-signed.hap.,hdc 报告 install bundle successfully 与 start ability successfully。
真机上按本文截图顺序操作:启动自检台(图 1)→ 点击「WLAN · wifi」→ 系统打开 WLAN 设置页(图 2)→ 按返回键回到自检台,统计变为「已成功 1 次 · 失败 0 次」(图 3)。
排查时按 SystemSettingsPlugin 过滤 hilog,可以看到每个方法到底是「落到目标页」还是「退回了首页」:
hdc shell hilog -x | grep SystemSettingsPlugin
SystemSettingsPlugin --> wifi -> com.huawei.hmos.settings uri="wifi_entry" (ok)
十、适配结果和当前边界
25 个方法全部接通,Dart 层一行未改。真机上点击 WLAN 确实打开了系统设置里的 WLAN 页面,说明 startAbility + uri 这条路径是真实生效的。
已知边界:
- 子页面 uri 依赖系统实现:表里未覆盖或某版本不存在的页面会退回设置首页(日志会写明
falling back); - 部分安卓概念在鸿蒙没有独立页面:NFC 归在「更多连接」下,数据漫游/飞行模式归在「移动网络」下,因此共用同一个 uri;
app/app-notifications依赖pushParams:设置应用是否支持该参数由系统决定,不支持时同样退回首页;- 没有模拟 iOS 的「只能开本应用设置页」限制:
system()等方法照常打开对应页面,行为比 iOS 更接近安卓; - 示例的 Dart SDK 约束被放宽:上游示例写
sdk: '>=2.15.1 <3.0.0'(Dart 2),鸿蒙工具链是 Dart 3.13,无法直接构建,因此改成>=2.15.1 <4.0.0(与主包一致)。
十一、FAQ
Q1:为什么不用 settings.getValueSync 之类的接口?
- 现象:
@ohos.settings确实是「读写系统设置项」的接口,但它面向的是读取配置值(如飞行模式状态),不是「打开设置页面」。 - 解决方法: 本库的语义是「把用户送到设置页」,属于应用间跳转,必须用
startAbility+Want。 - 验证结果: 真机点击 WLAN 后系统设置被拉起。
Q2:没有 UIAbility 会怎样?
- 现象:
startAbility是UIAbilityContext的能力,纯引擎插件拿不到。 - 解决方法: 实现
AbilityAware,在onAttachedToAbility里保存UIAbility;未附着时抛出明确错误。 - 验证结果: 正常宿主下可跳转;异常情况下 Dart 侧收到
Failed to open system settings而不是静默无响应。
Q3:某个子页面打不开怎么办?
- 现象: 子页面 uri 属于系统约定,某些版本/机型上可能不存在。
- 解决方法: 实现里对每个方法都做了兜底——打不开具体页面就退回设置首页,并在 hilog 记录失败码。
- 验证结果: 兜底路径可用,用户不会落到「什么都没发生」的状态。
Q4:和 airplane_mode_checker 有什么区别?
- 现象: 两者都跟系统设置有关,但
airplane_mode_checker是读状态 + 监听变化,本库是打开设置页。 - 解决方法: 按需要选择;如果只是想让用户自己去开飞行模式,用本库的
airplaneMode();如果要读当前状态,用airplane_mode_checker。 - 验证结果: 两库在同一台设备上均可用。
Q5:构建时报 compatibleSdkVersion 冲突怎么解决?
- 现象:
00306004 Specification Limit Violation,提示当前版本低于@ohos/flutter_ohos要求的 24。 - 解决方法: 写成纯版本号
"26.0.0",不要写'5.1.0(18)'或'26.0.0(26)'。 - 验证结果: 改完后
MergeProfile通过。
十二、总结
system_settings_2 3.0.2 已完整接入鸿蒙:25 个「打开设置页」方法统一用 startAbility + Want 实现,靠 uri 定位子页面、靠 pushParams 定位本应用页面,并对打不开的子页面做了退回首页的兜底。真机上点击 WLAN 真实拉起了系统 WLAN 设置页,Dart 层一行未改。
这个库最值得记的一条经验是:鸿蒙的「打开某页」不是枚举映射,而是 bundleName + uri 的约定。uri 不属于公开 API,所以兜底逻辑不是可选项而是必需品——否则换个系统版本,用户点了没反应,排查起来会很痛苦。
十三、参考资料和仓库地址
- 适配仓库主页
- 适配分支
- 适配 TAG
- ArkTS 通道实现
- 鸿蒙适配说明
- pub.dev 包页面
- pub.dev 3.0.2 版本页
- 上游仓库
- Flutter 官方插件与平台通道文档
- Flutter OH 鸿蒙 SDK 仓库
- HarmonyOS 开发者官方文档
欢迎加入CPF-Flutter 鸿蒙社区:https://atomgit.com/CPF-Flutter
更多推荐

所有评论(0)