@[toc]
在 React Native 项目里,只要碰到"图片处理"四个字,十有八九跑不掉一句话:卡。尤其是裁剪、压缩、批量处理大图的时候,JS 线程基本"当场升天",UI 直接卡住不动。
为什么 React Native 在图片处理上这么吃力?原因其实很简单:图像处理本质上是计算密集型任务,而 JS 线程天生不适合干这种"搬砖"活。
这篇文章我会从原理讲起,再一步步带你写一个能跑的原生图片压缩模块(含 Android + iOS),同时聊聊 JSI、FastImage、大图策略等实战经验。
一、为什么图片处理必须走原生?
图片处理涉及几个特点:
-
需要大量 CPU 运算
比如图片压缩、滤镜、像素矩阵操作,这些都需要不断遍历像素,JS 做不了。
-
JS 线程要留给 UI
JS 一卡,UI 就卡,用户体验直接崩。
-
React Native 的 Bridge 本身很慢
JS ↔ 原生的通信是 JSON 序列化/反序列化,数据越大就越慢。
大图动不动就 3MB、5MB,甚至十几兆,发送一次就能让 RN 痛不欲生。
所以原则很明确:
图片处理不要用 JS,全部交给 Native。
二、JSI:彻底绕开 Bridge 的终极方案
React Native 新架构之后,最推荐的方式是用 JSI 写 C++ 的图片处理逻辑。
JSI 的好处是什么?
- 不走 Bridge
- 不序列化大数据
- 执行在原生线程
- 调用速度接近原生
非常适合图像处理这种重任务。
如果项目允许迁移新架构,图像处理优先用 JSI。当然如果你只是要压缩图片、裁剪这些功能,那传统 NativeModule 也完全够用。
接下来我们用 NativeModule 写一个基础的"图片压缩模块",你可以按需求扩展滤镜、裁剪等能力。
三、为什么不要盲目用 JS 库处理图片?
常见库:
- react-native-image-editor(依赖原生,有限)
- react-native-image-resizer(可以但老旧)
- react-native-compressor(封装不错,但有些场景不够灵活)
如果项目对性能要求高(比如 IM、短视频、电商多图上传),最好自己写。
原因:
- 你可以选择最优压缩算法(JPEG/WEBP/HEIC)
- 你可以跟业务结合(上传前批量压缩)
- 性能好得多(避免重复 decode/encode)
四、实战 Demo:用原生实现图片压缩模块
下面我们写一个可以直接跑的原生模块:
- Android:Kotlin + BitmapFactory
- iOS:Swift + UIImage JPEG 压缩
- JS:Promise 化封装 + 调用示例
文件结构
bash
/android
/ios
src/native/NativeImageCompressor.ts
src/ImageCompressor.ts
五、Android 原生压缩实现(Kotlin)
创建文件:android/app/src/main/java/com/yourapp/ImageCompressorModule.kt
kotlin
package com.yourapp
import android.graphics.BitmapFactory
import android.graphics.Bitmap
import android.util.Base64
import com.facebook.react.bridge.*
import java.io.ByteArrayOutputStream
class ImageCompressorModule(reactContext: ReactApplicationContext) :
ReactContextBaseJavaModule(reactContext) {
override fun getName(): String {
return "ImageCompressor"
}
@ReactMethod
fun compress(path: String, quality: Int, promise: Promise) {
try {
val bitmap = BitmapFactory.decodeFile(path)
val baos = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, baos)
val base64 = Base64.encodeToString(baos.toByteArray(), Base64.DEFAULT)
promise.resolve(base64)
} catch (e: Exception) {
promise.reject("COMPRESS_ERROR", e)
}
}
}
然后注册模块(ImageCompressorPackage.kt):
kotlin
class ImageCompressorPackage : ReactPackage {
override fun createNativeModules(reactContext: ReactApplicationContext)
= listOf(ImageCompressorModule(reactContext))
override fun createViewManagers(reactContext: ReactApplicationContext)
= emptyList<ViewManager<*, *>>()
}
在 MainApplication.java 加入:
java
packages.add(new ImageCompressorPackage());
六、iOS 原生压缩实现(Swift)
文件:ios/ImageCompressor.swift
swift
@objc(ImageCompressor)
class ImageCompressor: NSObject {
@objc(compress:quality:resolve:reject:)
func compress(path: String,
quality: NSNumber,
resolve: RCTPromiseResolveBlock,
reject: RCTPromiseRejectBlock) {
guard let image = UIImage(contentsOfFile: path) else {
reject("IMAGE_ERROR", "Cannot load image", nil)
return
}
guard let data = image.jpegData(compressionQuality: CGFloat(truncating: quality) / 100) else {
reject("COMPRESS_ERROR", "JPEG encode failed", nil)
return
}
let base64 = data.base64EncodedString()
resolve(base64)
}
}
注册模块(ImageCompressor.m):
objc
#import <React/RCTBridgeModule.h>
@interface RCT_EXTERN_MODULE(ImageCompressor, NSObject)
RCT_EXTERN_METHOD(compress:(NSString *)path
quality:(nonnull NSNumber *)quality
resolve:(RCTPromiseResolveBlock)resolve
reject:(RCTPromiseRejectBlock)reject)
@end
七、JS 层封装(Promise 化)
创建 src/native/NativeImageCompressor.ts:
ts
import { NativeModules } from 'react-native';
const { ImageCompressor } = NativeModules;
export default {
compress: (path: string, quality: number = 80): Promise<string> => {
return ImageCompressor.compress(path, quality);
},
};
再封装一层业务 API(可加超时、可加批量处理):
ts
import NativeImageCompressor from './native/NativeImageCompressor';
export async function compressImage(path: string) {
const base64 = await NativeImageCompressor.compress(path, 80);
return `data:image/jpeg;base64,${base64}`;
}
JS 代码完全不会被阻塞,压缩在原生线程进行。
八、react-native-fast-image 的原理解析
FastImage 解决的是"图片显示卡顿",不是"处理性能问题"。
它为什么更快?
- 使用 Fresco / Glide(Android)、SDWebImage(iOS)
- 原生层并行加载图片
- 有内存缓存和磁盘缓存策略
- 避免 React Native Image 的多次解码
适用场景:
- 列表大量图(电商、资讯)
- 高速滑动
- 需要缓存策略
不适用:
- 压缩、裁剪、滤镜(这些需要自己做)
九、大图处理策略(非常重要)
如果你处理用户上传图片(电商、IM、社交),下面这些经验直接帮你省很多坑。
1. 先压缩宽高,再压缩质量
流程:
bash
decode → scale to maxWidth → jpeg compress
比直接 JPEG 压缩效率高得多。
2. 移动端图片建议不超过 2~3MB
太大会上传失败。
3. 不要一次处理多张图
建议:
- 批量压缩时串行压缩
- 每张压缩后释放 bitmap
4. 避免在 UI 交互期间处理大图
比如拍照后立即压缩,要放到后台线程执行并给用户 loading。
十、如何把图像处理迁移到 JSI(进阶)
大图处理(如逐像素滤镜),用 Java/Kotlin 或 Swift 其实也够,但如果你想做到极致:
- C++ 实现算法
- 直接 Buffer ↔ JS TypedArray
- 不走 Bridge
- 性能提升至少 5~10 倍
JSI 的典型用途:
- 滤镜(黑白、模糊、锐化)
- 像素处理(亮度、饱和度)
- 视频帧预处理
- 多图批量处理
十一、调试三方图像库的技巧
-
Android 打开 Bitmap decode 日志
bashadb shell setprop log.tag.BitmapFactory VERBOSE -
iOS 查看内存峰值
Xcode → Debug → Memory Graph
-
监控压缩耗时
JS + Native 双端打点
-
对比不同压缩格式
- JPG:通用、兼容好
- WEBP:尺寸小、Android 强
- HEIC:最省空间(iOS)
十二、总结
React Native 做图片处理时,你要记住一件事:
JS 只负责业务流程,所有计算密集逻辑必须走原生。
最推荐的处理方式:
- 简单压缩 → 原生模块(Kotlin/Swift)
- 复杂像素运算 → JSI(C++)
- 显示优化 → FastImage
- 多图上传 → 串行压缩 + 大图限制
整套方案下来,基本能把"图片导致卡顿"这个问题彻底根治。