从设备存储中读取图像文件并转换为 Base64 会导致 Flutter 中的 base64 无效

Reading an image file from device storage and converting to Base64 results in an invalid base64 in Flutter

我正在尝试从已知路径读取文件,从图像中获取 base64 字符串。我仍然不明白为什么 base64 坏了。这只发生在 flutter 代码中。

我确实尝试使用简单的飞镖程序进行相同的转换,我得到了所需的 base64 字符串。这个问题发生在 flutter 中。

onPressed: () async {
  File imageFile =
      await ImagePicker.pickImage(source: ImageSource.camera);
  if (imageFile != null) {
    Uint8List bytes = await imageFile.readAsBytes();
    String base64data = base64.encode(bytes);
    print(base64data);
  }
}

以下是我的控制台输出。

W/ExifInterface(15331): Skip the tag entry since tag number is not defined: 2
W/ExifInterface(15331): Stop reading file since a wrong offset may cause an infinite loop: 0
I/chatty  (15331): uid=10271(com.crowdanalytix.datax_app) Binder:15331_5 identical 2 lines
W/ExifInterface(15331): Stop reading file since a wrong offset may cause an infinite loop: 0
I/flutter (15331): /9j/4QGLRXhpZgAATU0AKgAAAAgACAEAAAQAAAABAAAKMgEQAAIAAAAOAAAAbgEBAAQAAAABAAASIAEPAAIAAAAIAAAAfIdpAAQAAAABAAAAmAESAAMAAAABAAAAAAEyAAIAAAAUAAAAhIglAAQAAAABAAABCgAAAABPTkVQTFVTIEEzMDAzAE9uZVBsdXMAMjAxOTowOTowMiAxOTo0MDo1MgAAB6QDAAMAAAABAAAAAIgnAAMAAAABD6AAAJIKAAUAAAABAAAA8oKaAAUAAAABAAAA+pIJAAMAAAABABAAAJIIAAQAAAABAAAAAIKdAAUAAAABAAABAgAAAAAAAAGqAAAAZAAAAkwAACcQAABOIAAAJxAAAQAFAAEAAAAHAAABHAAAAAAyMDAvMTAwAAQBEAACAAAADgAAAVkBDwACAAAACAAAAWcBEgADAAAAAQAAAAABMgACAAAAFAAAAW8AAAAAT05FUExVUyBBMzAwMwBPbmVQbHVzADIwMTk6MDk6MDIgMTk6NDA6NTIA/+AAEEpGSUYAAQEAAAEAAQAA/9sAQwABAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB/9sAQwEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEBAQEB/8AAEQgSIAoyAwEiAAIRAQMRAf/EAB8AAAIDAQEBAQEBAQAAAAAAAAQFAwYHAggAAQkKC//EAF0QAAEBBAYHCAIBAwMDAQECHwIBAAMREgQhIjFBUQUyYXGBkfAGE0KhscHR4VLxIwcUYjNDchVTgghjkhYkc4OiNJOjF0RUstIlZISzwsPilMTTGHSkNVW01PP0/8QAHAEAAwEBAQEBAQAAAAAAAAAAAgMEAAEFBgcI/8QAQhEAAAQDBgUFAQEAAQQBAgENAAECEQMhMRJBUWFx8IG

Base64 我得到的字符串无效。有人可以试试这个吗?

我正在使用 OnePlus3t (ONEPLUS A3003)

print 函数没有打印所有内容。
你可以在调试模式下看到完整的结果

代码片段

    List<int> imageBytes = await _imageFile.readAsBytes();
    String base64Image = base64Encode(imageBytes);
    print(base64Image);

从调试模式复制base64字符串并粘贴到在线转换器,你可以看到结果是正确的。

来自官方演示的完整代码并添加 base64 转换

// Copyright 2019 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:async';
import 'dart:io';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:image_picker/image_picker.dart';
import 'package:video_player/video_player.dart';
import 'dart:typed_data';
import 'package:image/image.dart' as ImageProcess;

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Image Picker Demo',
      home: MyHomePage(title: 'Image Picker Example'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  File _imageFile;
  dynamic _pickImageError;
  bool isVideo = false;
  VideoPlayerController _controller;
  String _retrieveDataError;

  Future<void> _playVideo(File file) async {
    if (file != null && mounted) {
      await _disposeVideoController();
      _controller = VideoPlayerController.file(file);
      await _controller.setVolume(1.0);
      await _controller.initialize();
      await _controller.setLooping(true);
      await _controller.play();
      setState(() {});
    }
  }

  void _onImageButtonPressed(ImageSource source) async {
    if (_controller != null) {
      await _controller.setVolume(0.0);
    }
    if (isVideo) {
      final File file = await ImagePicker.pickVideo(source: source);
      await _playVideo(file);
    } else {
      try {
        _imageFile = await ImagePicker.pickImage(source: source);

        List<int> imageBytes = await _imageFile.readAsBytes();
        String base64Image = base64Encode(imageBytes);
        print(base64Image);

        setState(() {});
      } catch (e) {
        _pickImageError = e;
      }
    }
  }

  @override
  void deactivate() {
    if (_controller != null) {
      _controller.setVolume(0.0);
      _controller.pause();
    }
    super.deactivate();
  }

  @override
  void dispose() {
    _disposeVideoController();
    super.dispose();
  }

  Future<void> _disposeVideoController() async {
    if (_controller != null) {
      await _controller.dispose();
      _controller = null;
    }
  }

  Widget _previewVideo() {
    final Text retrieveError = _getRetrieveErrorWidget();
    if (retrieveError != null) {
      return retrieveError;
    }
    if (_controller == null) {
      return const Text(
        'You have not yet picked a video',
        textAlign: TextAlign.center,
      );
    }
    return Padding(
      padding: const EdgeInsets.all(10.0),
      child: AspectRatioVideo(_controller),
    );
  }

  Widget _previewImage() {
    final Text retrieveError = _getRetrieveErrorWidget();
    if (retrieveError != null) {
      return retrieveError;
    }
    if (_imageFile != null) {
      return Image.file(_imageFile);
    } else if (_pickImageError != null) {
      return Text(
        'Pick image error: $_pickImageError',
        textAlign: TextAlign.center,
      );
    } else {
      return const Text(
        'You have not yet picked an image.',
        textAlign: TextAlign.center,
      );
    }
  }

  Future<void> retrieveLostData() async {
    final LostDataResponse response = await ImagePicker.retrieveLostData();
    if (response.isEmpty) {
      return;
    }
    if (response.file != null) {
      if (response.type == RetrieveType.video) {
        isVideo = true;
        await _playVideo(response.file);
      } else {
        isVideo = false;
        setState(() {
          _imageFile = response.file;
        });
      }
    } else {
      _retrieveDataError = response.exception.code;
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Platform.isAndroid
            ? FutureBuilder<void>(
          future: retrieveLostData(),
          builder: (BuildContext context, AsyncSnapshot<void> snapshot) {
            switch (snapshot.connectionState) {
              case ConnectionState.none:
              case ConnectionState.waiting:
                return const Text(
                  'You have not yet picked an image.',
                  textAlign: TextAlign.center,
                );
              case ConnectionState.done:
                return isVideo ? _previewVideo() : _previewImage();
              default:
                if (snapshot.hasError) {
                  return Text(
                    'Pick image/video error: ${snapshot.error}}',
                    textAlign: TextAlign.center,
                  );
                } else {
                  return const Text(
                    'You have not yet picked an image.',
                    textAlign: TextAlign.center,
                  );
                }
            }
          },
        )
            : (isVideo ? _previewVideo() : _previewImage()),
      ),
      floatingActionButton: Column(
        mainAxisAlignment: MainAxisAlignment.end,
        children: <Widget>[
          FloatingActionButton(
            onPressed: () {
              isVideo = false;
              _onImageButtonPressed(ImageSource.gallery);
            },
            heroTag: 'image0',
            tooltip: 'Pick Image from gallery',
            child: const Icon(Icons.photo_library),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              onPressed: () {
                isVideo = false;
                _onImageButtonPressed(ImageSource.camera);
              },
              heroTag: 'image1',
              tooltip: 'Take a Photo',
              child: const Icon(Icons.camera_alt),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              backgroundColor: Colors.red,
              onPressed: () {
                isVideo = true;
                _onImageButtonPressed(ImageSource.gallery);
              },
              heroTag: 'video0',
              tooltip: 'Pick Video from gallery',
              child: const Icon(Icons.video_library),
            ),
          ),
          Padding(
            padding: const EdgeInsets.only(top: 16.0),
            child: FloatingActionButton(
              backgroundColor: Colors.red,
              onPressed: () {
                isVideo = true;
                _onImageButtonPressed(ImageSource.camera);
              },
              heroTag: 'video1',
              tooltip: 'Take a Video',
              child: const Icon(Icons.videocam),
            ),
          ),
        ],
      ),
    );
  }

  Text _getRetrieveErrorWidget() {
    if (_retrieveDataError != null) {
      final Text result = Text(_retrieveDataError);
      _retrieveDataError = null;
      return result;
    }
    return null;
  }
}

class AspectRatioVideo extends StatefulWidget {
  AspectRatioVideo(this.controller);

  final VideoPlayerController controller;

  @override
  AspectRatioVideoState createState() => AspectRatioVideoState();
}

class AspectRatioVideoState extends State<AspectRatioVideo> {
  VideoPlayerController get controller => widget.controller;
  bool initialized = false;

  void _onVideoControllerUpdate() {
    if (!mounted) {
      return;
    }
    if (initialized != controller.value.initialized) {
      initialized = controller.value.initialized;
      setState(() {});
    }
  }

  @override
  void initState() {
    super.initState();
    controller.addListener(_onVideoControllerUpdate);
  }

  @override
  void dispose() {
    controller.removeListener(_onVideoControllerUpdate);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    if (initialized) {
      return Center(
        child: AspectRatio(
          aspectRatio: controller.value?.aspectRatio,
          child: VideoPlayer(controller),
        ),
      );
    } else {
      return Container();
    }
  }
}