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) 仓库。

引用方式例子如下:

yaml 复制代码
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

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  wakelock_plus: 1.1.4
  wakelock_plus_ohos: any

device_info_plus_ohos

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  device_info_plus: any
  device_info_plus_ohos: any

注意,有 2uid 是系统级别的,需要应用单独申请。

dart 复制代码
  /// 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;

使用

dart 复制代码
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

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  network_info_plus: any
  network_info_plus_ohos: any

在你的项目的 module.json5 文件中增加以下权限设置。

json 复制代码
    requestPermissions: [
      {"name" :  "ohos.permission.INTERNET"},
      {"name" :  "ohos.permission.GET_WIFI_INFO"},
    ],

sensors_plus_ohos

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  sensors_plus: 4.0.2
  sensors_plus_ohos: any

在你的项目的 module.json5 文件中增加以下权限设置。

json 复制代码
    requestPermissions: [
      {"name" :  "ohos.permission.ACCELEROMETER"},
      {"name" :  "ohos.permission.GYROSCOPE"},
    ],

connectivity_plus_ohos

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  connectivity_plus: 5.0.2
  connectivity_plus_ohos: any

在你的项目的 module.json5 文件中增加以下权限设置。

json 复制代码
    requestPermissions: [
      {"name" :  "ohos.permission.INTERNET"},
      {"name" :  "ohos.permission.GET_NETWORK_INFO"},
    ],

battery_plus_ohos

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  battery_plus: 5.0.3
  battery_plus_ohos: any

package_info_plus_ohos

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
  package_info_plus: 4.2.0
  package_info_plus_ohos: any

糖果插件

flutter_image_compress

地址:github.com/fluttercand...

引用:

yaml 复制代码
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

地址:github.com/fluttercand...

引用:

yaml 复制代码
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

地址:github.com/fluttercand...

引用:

注意 photo_manager_image_provider 需要限制一下版本。

yaml 复制代码
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

地址:github.com/HarmonyCand...

引用:

yaml 复制代码
dependencies:
 permission_handler_ohos: any

权限列表来自: gitee.com/openharmony...

注意

由于 OpenHarmonyHarmonyOS 的权限差异以及鸿蒙版本的高速迭代,检查请求权限的 api 是传递的权限的字符串全称,如果你发现 PermissionOhos 枚举中没有某个权限,你可以直接传递权限的字符串全称。等鸿蒙版本稳定下来了,会再同步权限列表到枚举中。

权限枚举列表是由文档自动生成的。

dart 复制代码
// 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 文件中增加对应需要权限设置,比如:

json 复制代码
    requestPermissions: [
      { name: "ohos.permission.READ_CALENDAR" },
      { name: "ohos.permission.WRITE_CALENDAR" },
    ],
例子

检查权限状态

dart 复制代码
import 'package:device_info_plus_ohos/device_info_plus_ohos.dart';
 
    final PermissionStatusOhos status =
        await PermissionHandlerOhos.checkPermissionStatus(
            PermissionOhos.read_calendar.name);      

请求单个权限

dart 复制代码
    final PermissionStatusOhos status =
        await PermissionHandlerOhos.requestPermission(
      PermissionOhos.read_calendar.name,
    );

请求多个权限

dart 复制代码
    final Map<String, PermissionStatusOhos> statusMap =
        await PermissionHandlerOhos.requestPermissions([
      PermissionOhos.read_calendar.name,
      PermissionOhos.write_calendar.name,
    ]);

打开设置页面

dart 复制代码
   PermissionHandlerOhos.openAppSettings();

geolocator

地址:Support OpenHarmony by zmtzawqlp · Pull Request #1499 · Baseflow/flutter-geolocator (github.com)

引用:

yaml 复制代码
dependencies:
  geolocator: any
  geolocator_ohos: ^0.0.1

在你的项目的 module.json5 文件中增加以下权限设置。

json 复制代码
    "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"
        }
      },                  
    ]

鸿蒙特有的方法

dart 复制代码
CountryCode? countryCode= await geolocatorOhos.getCountryCode();

(逆)地理编码转化

dart 复制代码
    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

地址:Support OpenHarmony by zmtzawqlp · Pull Request #100 · benjamindean/flutter_vibration (github.com)

引用:

yaml 复制代码
dependencies:
  vibration:
    git:
      url: "https://github.com/zmtzawqlp/flutter_vibration.git"
      path: "vibration"
  vibration_ohos:
    git:
      url: "https://github.com/zmtzawqlp/flutter_vibration.git"
      path: "vibration_ohos"   

在你的项目的 module.json5 文件中增加以下权限设置。

json 复制代码
    "requestPermissions": [
         {"name" :  "ohos.permission.VIBRATE"},                
    ]

vibrateEffect and vibrateAttribute are only exist in VibrationOhos.

dart 复制代码
 (VibrationPlatform.instance as VibrationOhos).vibrate(
   vibrateEffect: const VibratePreset(count: 100),
   vibrateAttribute: const VibrateAttribute(
     usage: 'alarm',
   ),
 );

sqflite

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  sqflite:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_sqflite.git"
      path: "sqflite"

fluttertoast

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  fluttertoast:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_fluttertoast.git"

audio_session

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  audio_session:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_audio_session.git"

flutter_sound

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  flutter_sound:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_sound.git"
      path: "flutter_sound"      

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  image_gallery_saver:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_image_gallery_saver.git"

location

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  location:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_location.git"
      path: "location"      

power_image

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  power_image:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_power_image.git"    

flutter_native_image

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  flutter_native_image:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_native_image.git"   

flutter_native_image

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  flutter_native_image:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_native_image.git"   

image_crop

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  image_crop:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_image_crop.git"   

bitmap

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  bitmap:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_bitmap.git"   

leak_detector

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  leak_detector:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_leak_detector.git"   

flutter_contacts

地址:gitee.com/openharmony...

引用:

yaml 复制代码
dependencies:
  flutter_contacts:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_contacts.git"   

纯 Flutter 库

extended_text

yaml 复制代码
dependencies:
  extended_text: 10.0.1-ohos

extended_text_field

yaml 复制代码
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

在制作插件中,你可能需要用到 2context

ApplicationContex

你可以直接从 onAttachedToEngine 方法中获取。

js 复制代码
  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 方法中获取。

js 复制代码
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 中的 CurrentLocationSettingsOhosdart 端的实现为如下:

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());
  }

而在鸿蒙原生端,对于的 interfaceCurrentLocationRequest

js 复制代码
export interface CurrentLocationRequest {
    priority?: LocationRequestPriority;
    scenario?: LocationRequestScenario;
    maxAccuracy?: number;
    timeoutMs?: number;
}

值得注意的是,如果参数为 null,不要传递过去,比如 'priority': null , 如果传递过去,鸿蒙原生端会解析错误。不传递过去的话,会解析为 undefined,这也对应了 priority?: LocationRequestPriority 可选的意思。

可以使用 chatgpt 直接将鸿蒙的 interface 转换成 dart 的类,并且增加 toMapfromMap,和注释。

插件传递

dart 端,将参数类以字符串的方式传递过去,并且用字符串的方式接受返回值。

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,在做编译引擎的时候,也许会遇到下面的错误。

objectivec 复制代码
/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; 

或者

lua 复制代码
../../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

macos-sdk/MacOSX13.3.sdk/usr/include/TargetConditionals.h at main · alexey-lysiuk/macos-sdk (github.com)

替换掉你本地的,注意做备份。

/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

相关推荐
我命由我123457 分钟前
Vue 开发问题:Missing required prop: “value“
开发语言·前端·javascript·vue.js·前端框架·ecmascript·js
16年上任的CTO7 分钟前
一文讲清楚React中的key值作用与原理
前端·javascript·react.js·react key
快起来别睡了13 分钟前
Vue 中组件的生命周期与 v-if、v-show 的区别详解
前端·vue.js
阳火锅16 分钟前
在生产环境下,你真的有考虑到使用数组方法的健壮性吗?
前端·javascript·面试
孤月葬花魂27 分钟前
JavaScript 中的 Promise API 全面解析
前端·javascript
几道之旅28 分钟前
Electron 应用打包全指南
前端·javascript·electron
Keya29 分钟前
在HarmonyOS(鸿蒙)中H5页面中的视频不会自动播放
app·harmonyos·arkts
shushushu32 分钟前
Web如何自动播放音视频
前端·javascript
帅夫帅夫37 分钟前
前端存储入门:Cookie 与用户登录状态管理
前端·架构
陈随易40 分钟前
程序员的新玩具,MoonBit(月兔)编程语言科普
前端·后端·程序员