Flutter怎么在HarmonyOS鸿蒙Next中把图片保存到手机相册

Flutter怎么在HarmonyOS鸿蒙Next中把图片保存到手机相册 我做了一个定制侠的APP,用户定制完需要把效果图保存到手机相册。

但是,申请权限不给过

申请权限:ohos.permission.WRITE_IMAGEVIDEO

申请原因:因为用户上传照片设计好手机壳后,需要将效果图保存本地相册

谁有能跑起来的flutter示例代码能保存图片到相册的,不能使用ohos.permission.WRITE_IMAGEVIDEO权限,又要实现写图片到相册。

8 回复

【解决方案】

image_gallery_saver是一个Flutter插件,用于将图片或者视频保存到设备相册。由于Flutter的image_picker插件只能选择图片而无法保存图片到相册,此插件提供了该功能。以下提供4种方式保存图片或者视频到设备相册。

  @override
  Widget build(BuildContext context) {
    return Scaffold(
        appBar: AppBar(
          title: Text("Save image to gallery"),
        ),
        body: Center(
          child: Column(
            children: <Widget>[
              SizedBox(height: 15),
              // `RepaintBoundary` 的作用是将这个蓝色矩形的绘制区域隔离出来,以便在需要时可以独立处理它的绘制和重绘。`_globalKey` 则用于在代码的其他部分访问这个 `RepaintBoundary` 小部件。
              RepaintBoundary(
                key: _globalKey,
                child: Container(
                  alignment: Alignment.center,
                  width: 300,
                  height: 300,
                  color: Colors.blue,
                ),
              ),
              Container(
                padding: EdgeInsets.only(top: 15),
                child: ElevatedButton(
                  onPressed: _saveLocalImage,
                  child: Text("Save Local Image"),
                ),
                width: 300,
                height: 44,
              ),
              Container(
                padding: EdgeInsets.only(top: 15),
                child: ElevatedButton(
                  onPressed: _saveNetworkImage,
                  child: Text("Save Network Image"),
                ),
                width: 300,
                height: 44,
              ),
              Container(
                padding: EdgeInsets.only(top: 15),
                child: ElevatedButton(
                  onPressed: _saveNetworkGifFile,
                  child: Text("Save Network Gif Image"),
                ),
                width: 300,
                height: 44,
              ),
              Container(
                padding: EdgeInsets.only(top: 15),
                child: ElevatedButton(
                  onPressed: _saveNetworkVideoFile,
                  child: Text("Save Network Video"),
                ),
                width: 300,
                height: 44,
              ),
            ],
          ),
        ));
  }

保存本地图片到相册,参考代码如下:

  GlobalKey _globalKey = GlobalKey();  // 创建一个`GlobalKey`类型的全局键对象,并将其赋值给变量 `_globalKey`
  // 从指定的Widget中获取渲染对象(Render Object),然后将该Widget的内容转换成一个图像(Image)
  _saveLocalImage() async {
    RenderRepaintBoundary boundary =
        _globalKey.currentContext!.findRenderObject() as RenderRepaintBoundary;
    ui.Image image = await boundary.toImage();
    ByteData? byteData =
        await (image.toByteData(format: ui.ImageByteFormat.png));
    if (byteData != null) {
      final result =
          await ImageGallerySaver.saveImage(byteData.buffer.asUint8List());
      print(result);
      Utils.toast(result.toString());
    }
  }

保存网络图片到相册,参考代码如下:

  _saveNetworkImage() async {
    var response = await Dio().get(
        "https://example.jpg",
        options: Options(responseType: ResponseType.bytes));
    final result = await ImageGallerySaver.saveImage(
        Uint8List.fromList(response.data),
        quality: 60,
        name: "hello");
    print(result);
    Utils.toast("$result");
  }

保存网络gif图到相册,参考代码如下:

  _saveNetworkGifFile() async {
    var appDocDir = await getTemporaryDirectory();
    String savePath = appDocDir.path + "/temp.gif";
    String fileUrl =
        "https://example.gif";
    await Dio().download(fileUrl, savePath);
    final result =
        await ImageGallerySaver.saveFile(savePath, isReturnPathOfIOS: true);
    print(result);
    Utils.toast("$result");
  }

保存网络video到相册,参考代码如下:

  _saveNetworkVideoFile() async {
    var appDocDir = await getTemporaryDirectory();
    String savePath = appDocDir.path + "/temp.mp4";
    String fileUrl =
        "https://example.mp4";
    await Dio().download(fileUrl, savePath, onReceiveProgress: (count, total) {
      print((count / total * 100).toStringAsFixed(0) + "%");
    });
    final result = await ImageGallerySaver.saveFile(savePath);
    print(result);
    Utils.toast("$result");
  }

更多关于Flutter怎么在HarmonyOS鸿蒙Next中把图片保存到手机相册的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


用已经适配过鸿蒙那个 image_gallery_saver 版本 https://gitcode.com/openharmony-sig/flutter_image_gallery_saver

但这个版本没有支持 gradle8 ,在安卓上可能会编译失败,如果要修复安卓的编译失败,可以按照 https://github.com/hui-z/image_gallery_saver/issues/400 这个修改源码。

实际上调用 image_gallery_saver 保存图片的代码就一行,我最近刚刚测试过保存单张照片是不需要请求权限的。

cke_929.png

找HarmonyOS工作还需要会Flutter的哦,有需要Flutter教程的可以学学大地老师的教程,很不错,B站免费学的哦:https://www.bilibili.com/video/BV1S4411E7LY/?p=17

好的 我抽空试试你的代码方案,

用 image_gallery_saver 这个库啊,保存单张图片都不用申请权限的。

我使用了,但是没有跑通。你有flutter demo不,可以提供一个只带这个image_gallery_saver的,

在HarmonyOS鸿蒙Next中,Flutter通过image_gallery_saver插件保存图片到手机相册。首先在pubspec.yaml中添加依赖,然后使用saveImage方法将图片数据写入相册。需要确保应用已获取相册访问权限。

在HarmonyOS Next中,由于系统架构和安全策略的升级,传统的WRITE_IMAGEVIDEO权限已不再适用于直接写入相册的场景。你需要使用HarmonyOS提供的媒体库管理接口来实现图片保存,这不需要申请敏感的文件写入权限。

核心方法是使用PhotoAccessHelper(媒体库管理模块)的createAsset接口,将图片数据写入媒体库,系统会自动将其纳入相册管理。

以下是关键步骤和Flutter插件侧代码思路:

  1. 环境与权限

    • 确保你的Flutter项目已正确集成HarmonyOS Next的Native能力(通过FFI或Platform Channel)。
    • module.json5配置文件中声明媒体库只读权限即可,无需声明WRITE_IMAGEVIDEO
      "requestPermissions": [
        {
          "name": "ohos.permission.READ_IMAGEVIDEO"
        }
      ]
      
  2. Native侧(ArkTS)核心代码示例: 你需要通过Flutter的MethodChannel调用以下HarmonyOS Native代码。

    import photoAccessHelper from '@ohos.file.photoAccessHelper';
    import fs from '@ohos.file.fs';
    
    // 1. 获取媒体库管理实例
    let phAccessHelper = photoAccessHelper.getPhotoAccessHelper(context);
    
    // 2. 准备要保存的图片数据(这里假设从Flutter侧接收图片的Uint8Array数据)
    // imageData: Uint8Array 类型,由Flutter侧传入
    // fileName: 自定义文件名,如 "custom_design_20240520.jpg"
    
    // 3. 创建媒体资源选项
    let createOption: photoAccessHelper.PhotoCreateOptions = {
      title: fileName // 保存到相册中显示的文件名
    };
    
    // 4. 执行创建(保存)操作
    try {
      let assetUri = await phAccessHelper.createAsset(photoAccessHelper.PhotoType.IMAGE, createOption);
      // assetUri 示例: file://media/Photo/1/IMG_20240520_123456.jpg
    
      // 5. 打开该URI的文件描述符,并写入图片数据
      let file = await fs.open(assetUri, fs.OpenMode.READ_WRITE | fs.OpenMode.CREATE);
      await fs.write(file.fd, imageData);
      await fs.close(file.fd);
    
      // 保存成功,可返回URI或成功信息给Flutter侧
      console.info('Image saved successfully: ' + assetUri);
    } catch (err) {
      console.error('Failed to save image: ' + err.message);
      // 处理错误
    }
    
  3. Flutter侧(Dart)调用示例

    import 'package:flutter/services.dart';
    
    // 初始化MethodChannel
    const _channel = MethodChannel('your_channel_name');
    
    Future<void> saveImageToGallery(Uint8List imageData, String fileName) async {
      try {
        // 调用Native侧方法,传递图片数据和文件名
        final result = await _channel.invokeMethod('saveImage', {
          'imageData': imageData,
          'fileName': fileName,
        });
        print('Save result: $result');
      } on PlatformException catch (e) {
        print('Failed to save image: ${e.message}');
      }
    }
    

关键点总结

  • 无需WRITE_IMAGEVIDEO权限:通过PhotoAccessHelper的系统接口写入,符合HarmonyOS Next的安全规范。
  • 数据格式:确保从Flutter传递到Native的图片数据是Uint8Array(ArkTS)或Uint8List(Dart)格式。
  • 文件命名:通过createOption.title指定文件名,系统可能会自动添加后缀或调整名称以避免冲突。
  • 异步处理:整个过程是异步的,注意在UI线程中处理好加载状态。

这种方案完全遵循HarmonyOS Next的设计规范,能稳定地将图片保存到系统相册,且能通过应用商店的权限审核。

回到顶部