Flutter 鸿蒙化 在一起 就可以

发布于:2024-05-06 ⋅ 阅读:(45) ⋅ 点赞:(0)

相关阅读:

前言

,商店里面的鸿蒙 app 也越来越多,就像余总说的一样,

在一起,就可以 !

社区一直在致力于使用 Flutter 更加快速地适配鸿蒙平台。

而距离 已经有一段时间了,我们来看看 Flutter 鸿蒙化的进展如何了。

重要提示,Flutter 鸿蒙化,需要华为提供的真机和最新的SDK或者自己申请了开发者预览 Beta 招募,没有的,暂时不要尝试。

最近 , 这给一些个人开发者提前体验鸿蒙 NEXT 的机会。

后续内容全部基于 和 的 dev 分支。参考文档也以 dev 分支 的文档为准。另外最新支持的是 ohos api11

插件进度

现阶段 Flutter 适配工作主要集中在鸿蒙原生插件的适配。下面介绍一下已知完成适配的插件。

flutter_packages

是适配官方 仓库。

引用方式例子如下:

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日
shared_preferences 2.2.1 官方库 11月30日
url_launcher 6.1.11 官方库 11月30日
image_picker 1.0.4 官方库 12月30日
local_auth 2.1.6 官方库 12月30日
pigeon 11.0.1 官方库 12月30日
webview_flutter 4.2.4、4.4.4 官方库 12月30日
video_player 2.7.2 官方库 3月30日
file_selector 1.0.1 官方库 12月30日
camera 0.10.5 官方库 3月30日

plus 插件

作者对于适配鸿蒙平台兴趣不大,所以这里决定 来维护。

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

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

  /// 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

权限列表来自:

注意

由于 OpenHarmonyHarmonyOS 的权限差异以及鸿蒙版本的高速迭代,检查请求权限的 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,
  ),
使用

请认真阅读官方关于权限的文档

在你的项目的 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();

geolocator

地址:

引用:

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

地址:

引用:

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 文件中增加以下权限设置。

    "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"   

flutter_native_image

地址:

引用:

dependencies:
  flutter_native_image:
    git:
      url: "https://gitee.com/openharmony-sig/flutter_native_image.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

如果您的库支持 OpenHarmony 平台,并且有 Platform.isOhos 的判断,那么建议换成 PlatformUtils.isOhos 避免对其他非鸿蒙用户在非鸿蒙分支编译的影响。

一些注意事项

关于鸿蒙的 context

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

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 中的 在 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

export interface CurrentLocationRequest {
    priority?: LocationRequestPriority;
    scenario?: LocationRequestScenario;
    maxAccuracy?: number;
    timeoutMs?: number;
}

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

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

插件传递

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 裁员

QQ图片20240504115445.jpg

最近很多人都在问,传着传着就变成 Google 要解散 Flutter 团队。不管是哪个公司都会有裁员的哪一天,与其犹豫不决,还不如笃定前行。

总有人年轻

没有人永远年轻,但总有人年轻。最近 NBA 季后赛,你不得不承认,他们都老了。过年的时候出去玩,把娃放肩膀上面驮着,脖子肩膀酸痛了,2天才恢复。有那么一刻,确实感觉自己也不再年轻了。 尽管时间会在我们身上留下痕迹,但当我们投身于自己热爱的事业或兴趣时,心态和精神永远年轻。

扶我起来,我还能写代码。那你,是什么时候发现自己不再年轻的?

结语

跟当年 Flutter 社区一样,我们也是从一点点慢慢变好的。5年前,,社区开始慢慢壮大。现在我们也将继续在新的领域汇集在一起 。

如果你是喜欢分享的,请加入我们;如果你需要分享的,也请加入我们。

鸿蒙,爱糖果,欢迎加入,一起生产可爱的鸿蒙小糖果

harmony_candies.png