You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

React项目hex转二进制传Android/iOS原生写文件异常求助

问题背景
  • 业务流程:下载以十六进制(hex)格式存储的文件,整体链路为读取hex格式文件内容→转换为二进制(bin)格式→写入Android/iOS设备。前端基于React(非React Native)实现文件读取、hex转bin逻辑,转换完成后将内容传递给原生层执行文件写入操作。
  • 异常现象:前端viewFile方法内打印待传递的content参数时内容完全正常,但原生层接收到的内容存在两类异常:一是无故新增大量十六进制值为0xC2的字符,二是内容在非末尾位置出现截断。
核心实现代码

文件读取逻辑

async readFileContent(path: string, isNotBin = true): Promise<string> {
     if (this._isDownloadingFile) {
         return Promise.reject();
     }

     let attempts = 0;
     let pos = 0;
     let fileContent = '';
     let fileDownloadCompleted = false;
     this.stopped = false;

     let contentRead = '';
     const chunkRead = [];

     while (!fileDownloadCompleted && !this.stopped && attempts < 3) {
         try {
             const command = this.createReadFileCommand(path, pos);
              const response: CommandResponse<FileReadResponse> = await utilityService.timeoutPromise(
                 this.bridgeService.sendCommand<
                     FileReadCommand,
                     FileReadResponse
                 >(command),
                 5000
             );

             if (response.retval == CommandResponseCode.OK) {
                 let newContent = response.data.data;
                 contentRead += newContent;
                 chunkRead.push(response.data);
                 const bytesCount = parseInt(response.data.count, 10);

                 const charsCount = bytesCount * 2;
                 if (response.data.count === "0") {
                     fileDownloadCompleted = true;
                 } else if (charsCount < newContent.length) {
                     newContent = newContent.substr(0, charsCount);
                 }

                 pos += bytesCount;
                 fileContent += this.hex2bin(newContent);
                 attempts = 0;
             } else {
                 attempts++;
             }
         } catch {
             attempts++;
         }
     }

     this._isDownloadingFile = false;

     if (fileDownloadCompleted) {
         return Promise.resolve(fileContent);
     } else {
         return Promise.reject();
     }
}

hex转二进制转换逻辑

private hex2bin(hex: string): string | string[] | Uint8Array {
     const view = new Uint8Array(hex.length / 2);
     for (let i = 0; i < hex.length; i += 2) {
         view[i / 2] = parseInt(hex.substring(i, i + 2), 16);
     }
     const bin = String.fromCharCode.apply(String, view);
     return bin;
}

向原生层传递文件内容逻辑

viewFile(content: string, fileName: string): Promise<ViewFileResult> {
     return new Promise<ViewFileResult>((resolve, reject) => {
         this.sendNativeCommand('viewFile', [content, fileName]);
         this.viewFileResultChange.subscribe(response =>
             response !== undefined && response !== null
                 ? resolve(response)
                 : reject()
         );
     });
}
问题根因
  • 编码不匹配是核心原因:JS字符串默认使用UTF-16编码,hex2bin方法中通过String.fromCharCode将0-255范围的单字节值拼接为JS字符串的操作,本质是把原始二进制字节强行映射为UTF-16码元。JSBridge在跨上下文传递字符串参数时,会默认以UTF-8规则序列化字符串:
    • 所有值≥0x80的单字节,在UTF-8编码时会被转成2字节序列,其中首字节固定为0xC2(对应码元范围0x800xBF)或`0xC3`(对应码元范围0xC00xFF),这就是原生层收到大量0xC2字符的直接来源。
    • 当二进制内容中出现不符合UTF-8编码规则的字节序列(比如孤立的UTF-16代理对码元),序列化过程会直接判定为无效编码,终止后续内容解析,最终表现为内容非末尾截断。
  • 前端控制台打印正常是误导项:浏览器控制台渲染JS字符串时直接读取UTF-16码元输出,不会执行跨上下文传递时的UTF-8编码步骤,因此控制台看不到编码转换引入的异常,不代表字符串可以安全跨层传递。
修复方案
  • 禁止用普通JS字符串承载二进制跨层传递:普通字符串存在默认编码转换逻辑,不适合传输原始二进制数据,二选一即可:
    • 传递Base64编码字符串:前端将二进制内容转成纯ASCII范围的Base64字符串后传递,原生层收到后解码Base64得到原始字节再写入文件,全程无编码转义风险。
    • 直接透传二进制类型:如果所用JSBridge支持传递ArrayBuffer/Uint8Array类型参数,直接传递原始二进制对象即可,完全跳过字符串编码环节。
  • 修正hex2bin方法逻辑:现有逻辑最后将Uint8Array转成String的操作无意义且是问题根源,修改后代码如下:
private hex2bin(hex: string): Uint8Array {
     const view = new Uint8Array(hex.length / 2);
     for (let i = 0; i < hex.length; i += 2) {
         view[i / 2] = parseInt(hex.substring(i, i + 2), 16);
     }
     // 直接返回二进制数组,不做字符串转换
     return view;
}
  • 调整跨层传参逻辑:如果采用Base64传递方案,需要同步修改readFileContent的返回值类型、viewFile的入参类型,将原有的字符串拼接逻辑改为TypedArray合并逻辑,传参前增加Base64编码步骤,参考代码如下:
// readFileContent 返回值类型改为Promise<Uint8Array>
// fileContent 类型改为Uint8Array,不再做字符串拼接
viewFile(content: Uint8Array, fileName: string): Promise<ViewFileResult> {
     return new Promise<ViewFileResult>((resolve, reject) => {
         // 二进制转Base64
         const rawBinStr = String.fromCharCode.apply(String, content);
         const base64Content = btoa(rawBinStr);
         this.sendNativeCommand('viewFile', [base64Content, fileName]);
         this.viewFileResultChange.subscribe(response =>
             response !== undefined && response !== null
                 ? resolve(response)
                 : reject()
         );
     });
}

原生层收到Base64字符串后,调用平台自带的Base64解码接口得到原始字节数组,再执行文件写入即可。如果JSBridge支持直接传二进制类型,跳过Base64转换步骤,直接传入hex2bin返回的Uint8Array对象即可。


内容的提问来源于stack exchange,提问作者Fenopiù

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.28 13:33:25