Flutter 鸿蒙化 在一起 就可以
相关阅读:
Flutter Love 鸿蒙 - 掘金 (juejin.cn)
不是鸿蒙 ArkUI 不会写,而是 Flutter 更有性价比 - 掘金 (juejin.cn)
前言
鸿蒙生态势如破竹,已有超4000应用加入,实现垂域全覆盖,商店里面的鸿蒙 app
也越来越多,就像余总说的一样,
在一起,就可以 !
OpenHarmony-SIG/flutter_flutter (gitee.com) 社区一直在致力于使用 Flutter 更加快速地适配鸿蒙平台。
而距离 不是鸿蒙 ArkUI 不会写,而是 Flutter 更有性价比 - 掘金 (juejin.cn) 已经有一段时间了,我们来看看 Flutter
鸿蒙化的进展如何了。
重要提示,Flutter 鸿蒙化,需要华为提供的真机和最新的SDK或者自己申请了开发者预览 Beta 招募,没有的,暂时不要尝试。
最近 华为纯血鸿蒙 HarmonyOS NEXT 开发者预览版首批 Beta 招募开启,支持 Mate 60 / Pro、X5 机型, 这给一些个人开发者提前体验鸿蒙 NEXT
的机会。
后续内容全部基于 OpenHarmony-SIG/flutter_flutter (gitee.com) 和 OpenHarmony-SIG/flutter_engine (gitee.com) 的 dev 分支。参考文档也以 dev 分支 的文档为准。另外最新支持的是
ohos api11
。
插件进度
现阶段 Flutter
适配工作主要集中在鸿蒙原生插件的适配。下面介绍一下已知完成适配的插件。
flutter_packages
OpenHarmony-SIG/flutter_packages (gitee.com) 是适配官方 flutter/packages: A collection of useful packages maintained by the Flutter team (github.com) 仓库。
引用方式例子如下:
dependencies:
path_provider:
git:
url: "https://gitee.com/openharmony-sig/flutter_packages.git"
path: "packages/path_provider/path_provider"
path_provider | 2.1.1 | 官方库 | 11月30日 | gitee.com/openharmony… | |
---|---|---|---|---|---|
shared_preferences | 2.2.1 | 官方库 | 11月30日 | gitee.com/openharmony… | |
url_launcher | 6.1.11 | 官方库 | 11月30日 | gitee.com/openharmony… | |
image_picker | 1.0.4 | 官方库 | 12月30日 | gitee.com/openharmony… | |
local_auth | 2.1.6 | 官方库 | 12月30日 | gitee.com/openharmony… | |
pigeon | 11.0.1 | 官方库 | 12月30日 | gitee.com/openharmony… | |
webview_flutter | 4.2.4、4.4.4 | 官方库 | 12月30日 | gitee.com/openharmony… | |
video_player | 2.7.2 | 官方库 | 3月30日 | gitee.com/openharmony… | |
file_selector | 1.0.1 | 官方库 | 12月30日 | gitee.com/openharmony… | |
camera | 0.10.5 | 官方库 | 3月30日 | gitee.com/openharmony… |
plus 插件
[Request]: support HarmonyOS · Issue #2480 · fluttercommunity/plus_plugins (github.com) 作者对于适配鸿蒙平台兴趣不大,所以这里决定 HarmonyCandies (github.com) 来维护。
wakelock_plus_ohos
引用:
dependencies:
wakelock_plus: 1.1.4
wakelock_plus_ohos: any
device_info_plus_ohos
引用:
dependencies:
device_info_plus: any
device_info_plus_ohos: any
注意,有 2
个 uid
是系统级别的,需要应用单独申请。
/// Requires permission: ohos.permission.sec.ACCESS_UDID (System permission, only open to system apps).
/// Device serial number.
/// 设备序列号。
final String serial;
/// Requires permission: ohos.permission.sec.ACCESS_UDID (System permission, only open to system apps).
/// Device Udid.
/// 设备Udid。
final String udid;
使用
import 'package:device_info_plus_ohos/device_info_plus_ohos.dart';
final DeviceInfoOhosPlugin deviceInfoOhosPlugin = DeviceInfoOhosPlugin();
OhosDeviceInfo deviceInfo = await deviceInfoOhosPlugin.ohosDeviceInfo;
// Requires permission: ohos.permission.sec.ACCESS_UDID (System permission, only open to system apps).
OhosAccessUDIDInfo accessUDIDInfo = await deviceInfoOhosPlugin.ohosAccessUDIDInfo;
network_info_plus_ohos
引用:
dependencies:
network_info_plus: any
network_info_plus_ohos: any
在你的项目的 module.json5
文件中增加以下权限设置。
requestPermissions: [
{"name" : "ohos.permission.INTERNET"},
{"name" : "ohos.permission.GET_WIFI_INFO"},
],
sensors_plus_ohos
引用:
dependencies:
sensors_plus: 4.0.2
sensors_plus_ohos: any
在你的项目的 module.json5
文件中增加以下权限设置。
requestPermissions: [
{"name" : "ohos.permission.ACCELEROMETER"},
{"name" : "ohos.permission.GYROSCOPE"},
],
connectivity_plus_ohos
引用:
dependencies:
connectivity_plus: 5.0.2
connectivity_plus_ohos: any
在你的项目的 module.json5
文件中增加以下权限设置。
requestPermissions: [
{"name" : "ohos.permission.INTERNET"},
{"name" : "ohos.permission.GET_NETWORK_INFO"},
],
battery_plus_ohos
引用:
dependencies:
battery_plus: 5.0.3
battery_plus_ohos: any
package_info_plus_ohos
引用:
dependencies:
package_info_plus: 4.2.0
package_info_plus_ohos: any
糖果插件
flutter_image_compress
引用:
dependencies:
flutter_image_compress: ^2.2.0
Feature | Android | iOS | Web | macOS | OpenHarmony |
---|---|---|---|---|---|
method: compressWithList | ✅ | ✅ | ✅ | ✅ | ✅ |
method: compressAssetImage | ✅ | ✅ | ✅ | ✅ | ✅ |
method: compressWithFile | ✅ | ✅ | ❌ | ✅ | ✅ |
method: compressAndGetFile | ✅ | ✅ | ❌ | ✅ | ✅ |
format: jpeg | ✅ | ✅ | ✅ | ✅ | ✅ |
format: png | ✅ | ✅ | ✅ | ✅ | ✅ |
format: webp | ✅ | ✅ | [🌐][webp-compatibility] | ❌ | ✅ |
format: heic | ✅ | ✅ | ❌ | ✅ | ✅ |
param: quality | ✅ | ✅ | [🌐][webp-compatibility] | ✅ | ✅ |
param: rotate | ✅ | ✅ | ❌ | ✅ | ✅ |
param: keepExif | ✅ | ✅ | ❌ | ✅ | ❌ |
flutter_image_editor
引用:
dependencies:
image_editor: ^2.2.0
Feature | Android | iOS | OpenHarmony |
---|---|---|---|
flip | ✅ | ✅ | ✅ |
crop | ✅ | ✅ | ✅ |
rotate | ✅ | ✅ | ✅ |
scale | ✅ | ✅ | ✅ |
matrix | ✅ | ✅ | ❌ |
mix image | ✅ | ✅ | ✅ |
merge multi image | ✅ | ✅ | ✅ |
draw point | ✅ | ✅ | ✅ |
draw line | ✅ | ✅ | ✅ |
draw rect | ✅ | ✅ | ✅ |
draw circle | ✅ | ✅ | ✅ |
draw path | ✅ | ✅ | ✅ |
draw Bezier | ✅ | ✅ | ✅ |
Gaussian blur | ❌ | ❌ | ❌ |
flutter_photo_manager
引用:
注意 photo_manager_image_provider
需要限制一下版本。
dependencies:
photo_manager: ^3.1.0
dependency_overrides:
photo_manager_image_provider: ^1.1.1
暂时支持下面的功能,目前鸿蒙只支持图片和视频 2 种资源类型。
Feature | OpenHarmony |
---|---|
getAssetPathList | ✅ |
getAssetCountFromPath | ✅ |
fetchPathProperties | ✅ |
getAssetCount | ✅ |
getAssetListPaged | ✅ |
getOriginBytes | ✅ |
getThumb | ✅ |
getAssetListRange | ✅ |
getAssetsByRange | ✅ |
deleteWithIds | ✅ |
getColumnNames | ✅ |
saveImage | ✅ |
saveImageWithPath | ✅ |
saveVideo | ✅ |
requestPermissionExtend | ✅ |
ignorePermissionCheck | ✅ |
log | ✅ |
notify | ✅ |
其他插件
permission_handler_ohos
引用:
dependencies:
permission_handler_ohos: any
权限列表来自: gitee.com/openharmony…
注意
由于 OpenHarmony
和 HarmonyOS
的权限差异以及鸿蒙版本的高速迭代,检查请求权限的 api
是传递的权限的字符串全称,如果你发现 PermissionOhos
枚举中没有某个权限,你可以直接传递权限的字符串全称。等鸿蒙版本稳定下来了,会再同步权限列表到枚举中。
权限枚举列表是由文档自动生成的。
// GENERATED CODE - DO NOT MODIFY MANUALLY
// **************************************************************************
// Auto generated by https://github.com/HarmonyCandies/permission_handler_ohos/bin/main.dart
// **************************************************************************
// https://gitee.com/openharmony/docs/blob/OpenHarmony-4.1-Release/zh-cn/application-dev/security/AccessToken/permissions-for-all.md
// ignore_for_file: constant_identifier_names,slash_for_doc_comments
/// The Permissions of OpenHarmony
/// total: 44
enum PermissionOhos {
/// ohos.permission.USE_BLUETOOTH
///
/// 允许应用查看蓝牙的配置。
///
/// 权限级别:normal
///
/// 授权方式:system_grant
///
/// ACL使能:true
///
/// 起始版本:8
use_bluetooth(
name: 'ohos.permission.USE_BLUETOOTH',
permissionLevel: 'normal',
grantType: 'system_grant',
aclEnabled: true,
startVersion: 8,
),
使用
请认真阅读官方关于权限的文档 gitee.com/openharmony…
在你的项目的 module.json5
文件中增加对应需要权限设置,比如:
requestPermissions: [
{ name: "ohos.permission.READ_CALENDAR" },
{ name: "ohos.permission.WRITE_CALENDAR" },
],
例子
检查权限状态
import 'package:device_info_plus_ohos/device_info_plus_ohos.dart';
final PermissionStatusOhos status =
await PermissionHandlerOhos.checkPermissionStatus(
PermissionOhos.read_calendar.name);
请求单个权限
final PermissionStatusOhos status =
await PermissionHandlerOhos.requestPermission(
PermissionOhos.read_calendar.name,
);
请求多个权限
final Map<String, PermissionStatusOhos> statusMap =
await PermissionHandlerOhos.requestPermissions([
PermissionOhos.read_calendar.name,
PermissionOhos.write_calendar.name,
]);
打开设置页面
PermissionHandlerOhos.openAppSettings();
audio_streamer_ohos
引用:
dependencies:
audio_streamer: 4.1.1
audio_streamer_ohos: any
audio_streamer 在 OpenHarmony 平台上的实现
在 OpenHarmony 项目的 module.json
文件中添加 ohos.permission.MICROPHONE
权限
{
"module": {
"requestPermissions": [
{
"name": "ohos.permission.MICROPHONE",
"reason": "Microphone permission is required to record audio."
}
]
}
}
geolocator
地址: HarmonyCandies/geolocator_ohos: The OpenHarmony implementation of geolocator. (github.com)
引用:
dependencies:
geolocator: any
geolocator_ohos: ^0.0.1
在你的项目的 module.json5
文件中增加以下权限设置。
"requestPermissions": [
{"name" : "ohos.permission.KEEP_BACKGROUND_RUNNING"},
{
"name": "ohos.permission.LOCATION",
"reason": "$string:EntryAbility_label",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.APPROXIMATELY_LOCATION",
"reason": "$string:EntryAbility_label",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
{
"name": "ohos.permission.LOCATION_IN_BACKGROUND",
"reason": "$string:EntryAbility_label",
"usedScene": {
"abilities": [
"EntryAbility"
],
"when": "inuse"
}
},
]
鸿蒙特有的方法
CountryCode? countryCode= await geolocatorOhos.getCountryCode();
(逆)地理编码转化
final position = await geolocatorOhos.getCurrentPosition(
locationSettings: const CurrentLocationSettingsOhos(
priority: LocationRequestPriority.firstFix,
scenario: LocationRequestScenario.unset,
),
);
// ohos only
if (await geolocatorOhos.isGeocoderAvailable()) {
//
var addresses = await geolocatorOhos.getAddressesFromLocation(
ReverseGeoCodeRequest(
latitude: position.latitude,
longitude: position.longitude,
locale: 'zh',
maxItems: 1,
),
);
for (var address in addresses) {
if (kDebugMode) {
print('ReverseGeoCode address:$address');
}
var position = await geolocatorOhos.getAddressesFromLocationName(
GeoCodeRequest(description: address.placeName ?? ''),
);
if (kDebugMode) {
print('geoCode position:$position');
}
}
}
vibration
地址:flutter_vibration/vibration_ohos at master · benjamindean/flutter_vibration (github.com)
引用:
dependencies:
vibration: any
vibration_ohos: any
在你的项目的 module.json5
文件中增加以下权限设置。
"requestPermissions": [
{"name" : "ohos.permission.VIBRATE"},
]
vibrateEffect
and vibrateAttribute
are only exist in VibrationOhos
.
(VibrationPlatform.instance as VibrationOhos).vibrate(
vibrateEffect: const VibratePreset(count: 100),
vibrateAttribute: const VibrateAttribute(
usage: 'alarm',
),
);
sqflite
引用:
dependencies:
sqflite:
git:
url: "https://gitee.com/openharmony-sig/flutter_sqflite.git"
path: "sqflite"
fluttertoast
引用:
dependencies:
fluttertoast:
git:
url: "https://gitee.com/openharmony-sig/flutter_fluttertoast.git"
audio_session
引用:
dependencies:
audio_session:
git:
url: "https://gitee.com/openharmony-sig/flutter_audio_session.git"
flutter_sound
引用:
dependencies:
flutter_sound:
git:
url: "https://gitee.com/openharmony-sig/flutter_sound.git"
path: "flutter_sound"
image_gallery_saver
引用:
dependencies:
image_gallery_saver:
git:
url: "https://gitee.com/openharmony-sig/flutter_image_gallery_saver.git"
location
引用:
dependencies:
location:
git:
url: "https://gitee.com/openharmony-sig/flutter_location.git"
path: "location"
power_image
引用:
dependencies:
power_image:
git:
url: "https://gitee.com/openharmony-sig/flutter_power_image.git"
flutter_native_image
引用:
dependencies:
flutter_native_image:
git:
url: "https://gitee.com/openharmony-sig/flutter_native_image.git"
audioplayers
引用:
dependencies:
audioplayers:
git:
url: "https://gitee.com/openharmony-sig/flutter_audioplayers.git"
image_crop
引用:
dependencies:
image_crop:
git:
url: "https://gitee.com/openharmony-sig/flutter_image_crop.git"
bitmap
引用:
dependencies:
bitmap:
git:
url: "https://gitee.com/openharmony-sig/flutter_bitmap.git"
leak_detector
引用:
dependencies:
leak_detector:
git:
url: "https://gitee.com/openharmony-sig/flutter_leak_detector.git"
flutter_contacts
引用:
dependencies:
flutter_contacts:
git:
url: "https://gitee.com/openharmony-sig/flutter_contacts.git"
纯 Flutter 库
extended_text
dependencies:
extended_text: 10.0.1-ohos
extended_text_field
dependencies:
extended_text_field: 11.0.1-ohos
flutter_platform_utils
HarmonyCandies/flutter_platform_utils: A utility to check the platform for ohos (github.com)
如果您的库支持 OpenHarmony
平台,并且有 Platform.isOhos
的判断,那么建议换成 PlatformUtils.isOhos
避免对其他非鸿蒙用户在非鸿蒙分支编译的影响。
一些注意事项
关于鸿蒙的 context
在制作插件中,你可能需要用到 2
种 context
。
ApplicationContex
你可以直接从 onAttachedToEngine
方法中获取。
private context: Context | null = null;
onAttachedToEngine(binding: FlutterPluginBinding): void {
this.context = binding.getApplicationContext();
}
onDetachedFromEngine(binding: FlutterPluginBinding): void {
this.context = null;
}
该 context
可以用于获取 applicationInfo
等属性。
let applicationInfo = this.context.applicationInfo;
UIAbilityContext
插件继承 AbilityAware
并且在 onAttachedToAbility
方法中获取。
export default class XXXPlugin implements FlutterPlugin, MethodCallHandler, AbilityAware {
private _uiContext: common.UIAbilityContext | null = null;
onAttachedToAbility(binding: AbilityPluginBinding): void {
this._uiContext = binding.getAbility().context;
}
onDetachedFromAbility(): void {
this._uiContext = null;
}
}
该 uiContext
可以用于获取 applicationInfo
等属性。
photoAccessHelper.getPhotoAccessHelper(PhotoManagerPlugin.uiContext);
关于插件参数传递
按照以前的习惯,dart
端传递 map
参数,原生端根据 map
解析参数。
但由于 ts
支持将字符串直接转换成对应的 interface
,那么我们可以将 dart
的端的参数。
参数定义
比如 geolocator_ohos
中的 CurrentLocationSettingsOhos
在 dart
端的实现为如下:
Map<String, dynamic> toMap() {
return {
if (priority != null) 'priority': priority?.toInt(),
if (scenario != null) 'scenario': scenario?.toInt(),
if (maxAccuracy != null) 'maxAccuracy': maxAccuracy,
if (timeoutMs != null) 'timeoutMs': timeoutMs,
};
}
@override
String toString() {
return jsonEncode(toMap());
}
而在鸿蒙原生端,对于的 interface
是 CurrentLocationRequest
export interface CurrentLocationRequest {
priority?: LocationRequestPriority;
scenario?: LocationRequestScenario;
maxAccuracy?: number;
timeoutMs?: number;
}
值得注意的是,如果参数为 null
,不要传递过去,比如 'priority': null
, 如果传递过去,鸿蒙原生端会解析错误。不传递过去的话,会解析为 undefined
,这也对应了 priority?: LocationRequestPriority
可选的意思。
可以使用
chatgpt
直接将鸿蒙的interface
转换成dart
的类,并且增加toMap
,fromMap
,和注释。
插件传递
dart
端,将参数类以字符串的方式传递过去,并且用字符串的方式接受返回值。
@override
Future<Position> getCurrentPosition({
LocationSettings? locationSettings,
String? requestId,
}) async {
assert(
locationSettings == null ||
locationSettings is CurrentLocationSettingsOhos,
'locationSettings should be CurrentLocationSettingsOhos',
);
try {
final Duration? timeLimit = locationSettings?.timeLimit;
Future<dynamic> positionFuture =
GeolocatorOhos._methodChannel.invokeMethod(
'getCurrentPosition',
locationSettings?.toString(),
);
if (timeLimit != null) {
positionFuture = positionFuture.timeout(timeLimit);
}
return PositionOhos.fromString(await positionFuture);
}
}
在鸿蒙端, 将字符串直接转换成鸿蒙对应的 interface
。
let request: geoLocationManager.CurrentLocationRequest = JSON.parse(args);
并且将要返回的 interface
转换成字符串。
result.success(JSON.stringify(location));
当然了,这样有个问题,就是如果鸿蒙端修改了 interface
的属性名字,插件很难感知到(当然会报错)。
关于做 Flutter 鸿蒙化的一些环境要求
重要提示,Flutter 鸿蒙化,需要华为提供的真机和最新的SDK或者自己申请了开发者预览 Beta 招募,没有的,暂时不要尝试。
Xcode15
如果你的电脑升级了 Xcode15
,在做编译引擎的时候,也许会遇到下面的错误。
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX14.4.sdk/System/Library/Frameworks/Foundation.framework/Headers/NSObjCRuntime.h:657:37: error: use of undeclared identifier 'NSIntegerMax'
static const NSInteger NSNotFound = NSIntegerMax;
或者
../../third_party/dart/runtime/bin/security_context_macos.cc:188:17: error: use of undeclared identifier 'noErr'
if (status != noErr) {
^
../../third_party/dart/runtime/bin/security_context_macos.cc:196:19: error: use of undeclared identifier 'noErr'
if (status != noErr) {
^
../../third_party/dart/runtime/bin/security_context_macos.cc:205:17: error: use of undeclared identifier 'noErr'
if (status != noErr) {
^
../../third_party/dart/runtime/bin/security_context_macos.cc:303:21: error: use of undeclared identifier 'noErr'
OSStatus status = noErr;
^
../../third_party/dart/runtime/bin/security_context_macos.cc:319:23: error: use of undeclared identifier 'noErr'
status == noErr && (trust_result == kSecTrustResultProceed ||
^
解决办法是从下面地址选择 13.3 sdk
中的 TargetConditionals.h
替换掉你本地的,注意做备份。
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/TargetConditionals.h
说点其他
关于 Google 裁员
最近很多人都在问,传着传着就变成 Google
要解散 Flutter
团队。不管是哪个公司都会有裁员的哪一天,与其犹豫不决,还不如笃定前行。
总有人年轻
没有人永远年轻,但总有人年轻。最近 NBA
季后赛,你不得不承认,他们都老了。过年的时候出去玩,把娃放肩膀上面驮着,脖子肩膀酸痛了,2天才恢复。有那么一刻,确实感觉自己也不再年轻了。
尽管时间会在我们身上留下痕迹,但当我们投身于自己热爱的事业或兴趣时,心态和精神永远年轻。
扶我起来,我还能写代码。那你,是什么时候发现自己不再年轻的?
结语
跟当年 Flutter
社区一样,我们也是从一点点慢慢变好的。5年前,Flutter Candies 一桶天下 - 掘金 (juejin.cn),社区开始慢慢壮大。现在我们也将继续在新的领域汇集在一起 不是鸿蒙 ArkUI 不会写,而是 Flutter 更有性价比 - 掘金 (juejin.cn)。
如果你是喜欢分享的,请加入我们;如果你需要分享的,也请加入我们。
爱 鸿蒙
,爱糖果
,欢迎加入Harmony Candies,一起生产可爱的鸿蒙小糖果QQ群:981630644
来源:juejin.cn/post/7364698043910930443