flutter 使用archive压缩与解压文件时出现中文乱码的问题

archive 是 flutter 上的一个文件压缩与解压的类库,支持 zip,tar,zlip,gzip,zip2,xz 格式的压缩与解压。

archive 的使用

archive 主要通过 Archive,ArchiveFile,ZipEncoder,ZipDecoder 三个类来实现文件的压缩和解压。
ArchiveFile 表示压缩包内的一个文件。
Archive 表示一个压缩包。
ZipEncoder 表示 zip 编码器。
ZipDecoder 表示 zip 解码器。

文件压缩:

final zipFile = File("text.zip");   
final archive = Archive();  
var str="中文";
final archiveFile=ArchiveFile.string("test.txt", str); //将压缩内容添加到ArchiveFile
archive.addFile(archiveFile);  //将ArchiveFile添加到Archive
final zipData = ZipEncoder().encode(archive);  //使用zip编码
zipFile.writeAsBytesSync(zipData!);  //写入到压缩文件

文件解压:

final zipFile = File("text.zip");
var archive=ZipDecoder().decodeBytes(zipFile.readAsBytesSync()); //读取文件内容,使用ZipDecoder解码
ArchiveFile? f=archive.findFile("test.txt");  //从压缩包内寻找文件
var content=f?.content;  //获取内容

乱码分析

对于非中文内容来说,直接使用以上代码来实现压缩和解压是不会导致乱码,但是对于中文来说却会导致乱码。
首先来分析 ArchiveFile.string() 方法做了什么。

ArchiveFile.string(this.name, String content,  
    [this._compressionType = STORE]) {  
  size = content.length;  
  _content = Uint8List.fromList(content.codeUnits);  
  _rawContent = InputStream(_content);  
}

能看到,该方法首先获取传入字符串的 utf-16 编码的列表,再将其转为 uint8 列表。uint8 在 flutter 中表示 8 位比特(bit)的无符号数,范围为 [0,256)。因为文件就是按字节(Byte)存储的,一个字节由 8 位比特组成,范围跟 uint8 一致。
但是对于中文来说,大小是超过 256 的,所以当使用 Uint8List.fromList() 将中文从 utf-16 强转为 uint8,会导致其被截断,从而导致乱码。
从下面测试例子中也能看出来:

String str="中文";  
var list=str.codeUnits;  
print(list);  
var list2=Uint8List.fromList(list);  
print(list2);

//运行结果为:
[20013, 25991]
[45, 135]

所以对于中文来说,并不能直接强制为 uint8,需要对其进行编码再进行存储,中文编码方式存在 utf-8,gbk 等,而 flutter 支持 utf-8 编码,但不支持 gbk 编码。对于 utf-8 编码来说,中文需要 3 个字节来存储。

utf8.encode(); //编码
utf8.decode(); //解码

我们再来看,ArchiveFile 的构造方法除了 ArchiveFile.string() 外,还有

ArchiveFile(this.name, this.size, dynamic content,  
    [this._compressionType = STORE]) {  
  name = name.replaceAll('\\', '/');  
  if (content is Uint8List) {  
    _content = content;  
    _rawContent = InputStream(_content);  
    if (size <= 0) {  
      size = content.length;  
    }  
  } else if (content is InputStream) {  
    _rawContent = InputStream.from(content);  
    if (size <= 0) {  
      size = content.length;  
    }  
  } else if (content is InputStreamBase) {  
    _rawContent = content;  
    if (size <= 0) {  
      size = content.length;  
    }  
  } else if (content is TypedData) {  
    _content = Uint8List.view(content.buffer);  
    _rawContent = InputStream(_content);  
    if (size <= 0) {  
      size = (_content as Uint8List).length;  
    }  
  } else if (content is String) {  
    _content = content.codeUnits;  
    _rawContent = InputStream(_content);  
    if (size <= 0) {  
      size = content.codeUnits.length + 1;  
    }  
  } else if (content is List<int>) {  
    // Legacy  
    // This expects the list to be a list of bytes, with values [0, 255].  
    _content = content;  
    _rawContent = InputStream(_content);  
    if (size <= 0) {  
      size = content.length;  
    }  
  } else if (content is FileContent) {  
    _content = content;  
  }  
}

当传入的 content 类型为 List 时,直接赋值为 _content,没有进行额外的操作。另外该方法还有另外一个参数 _compressionType,该参数需要传入 ArchiveFile.STORE,否则在进行解压时,archiveFile.content 会报错:Unhandled Exception: RangeError: Value not in range: -6128

解决办法

所以最后处理中文乱码的解决办法就是先对其进行 utf-8 编码,再压缩。解压后,对其进行 utf-8 解码,获取中文内容。
代码如下:

//压缩
final zipFile = File("text.zip");  
final archive = Archive();  
var str="中文";
//这步是必须的
var encodeStr=utf8.encode(str);   
//必须要为ArchiveFile.STORE
final archiveFile=ArchiveFile("test.txt",str.length,encodeStr,ArchiveFile.STORE); 
archive.addFile(archiveFile);  
final zipData =  ZipEncoder().encode(archive);  
zipFile.writeAsBytesSync(zipData!);  

//解压
ArchiveFile? 
f=ZipDecoder().decodeBytes(zipFile.readAsBytesSync()).findFile("test.txt");  
var content=f?.content;  
// 解码
print(utf8.decode(content));

你可能感兴趣的:(flutter)