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ù
相关产品推荐
相关产品推荐

