React Native 0.76新架构下Android 15平台JSI长时任务安全终止/中断的方案咨询
React Native 0.76新架构下Android 15平台JSI长时任务安全终止/中断的方案咨询
刚好在最近的项目里踩过几乎一模一样的坑——RN 0.76新架构+Android 15,JSI长任务动不动就触发ANR,还没法中途取消。结合自己的实践和对业内成熟库的研究,给你梳理下目前最靠谱的现代方案,全是针对JSI/新架构的,和老桥接那套完全不沾边:
首先得明确一个核心前提:JSI本身完全没有内置的取消/中断机制,所以所有可行的方案都是「协作式取消」——也就是说,你得让长任务自己定期检查「终止信号」,然后主动退出,绝对不能搞抢占式的强制终止(比如直接杀线程),那在C++里纯属找死,内存泄漏、资源锁死分分钟找上门。
一、线程安全的终止信号怎么实现?
最稳妥的就是用**C原子变量(std::atomic
- 在TurboModule的类成员或者JSI函数的全局上下文里,声明一个
std::atomic<bool> shouldCancel = false; - JS侧通过暴露的native方法(比如
cancelTask()),触发native侧把这个原子变量设为true - 如果是多任务场景,可以用
std::unordered_map<taskId, std::atomic<bool>>来维护每个任务的独立标记
举个简化的TurboModule侧代码例子:
class ImageProcessorModule : public facebook::react::TurboModule { private: std::atomic<bool> shouldCancel = false; std::optional<std::thread> processingThread; public: // JS侧调用的启动方法 jsi::Value startProcessing(jsi::Runtime& rt, const jsi::Value* args, size_t count) { shouldCancel = false; // 把任务丢到独立C++线程,绝对不能碰JS/UI线程 processingThread = std::thread([this, &rt]() { while (!shouldCancel) { // 执行一小段任务(比如处理100px的图像块) processImageChunk(); // 关键:每完成一小步就检查终止信号 if (shouldCancel) { // 必须做资源清理:释放图像缓冲区、关闭文件句柄等 cleanupProcessingResources(); // 通知JS侧任务已取消 notifyJS(rt, "cancelled"); break; } } // 正常结束也要通知JS if (!shouldCancel) notifyJS(rt, "completed"); }); return jsi::Value::undefined(); } // JS侧调用的取消方法 jsi::Value cancelProcessing(jsi::Runtime& rt, const jsi::Value* args, size_t count) { shouldCancel = true; // 等待线程结束,避免内存泄漏 if (processingThread.has_value() && processingThread->joinable()) { processingThread->join(); processingThread.reset(); } return jsi::Value::undefined(); } };
二、适配Android 15严格调度的取消实现要点
Android 15对主线程/JS线程的阻塞限制比之前严了N倍,所以除了协作式取消,还要注意这几点:
- 绝对隔离线程:长任务必须跑在独立的C++线程(用
std::thread或者Android原生的ThreadPoolExecutor),哪怕是JSI函数也不能在JS线程执行,一跑就ANR - 高频检查信号:任务拆分得越细,检查点越密,取消响应越快,也越不容易触发Android的ANR检测。比如ML推理可以按每层神经网络、图像处理按每100x100像素块来拆分
- 及时通知JS:不管任务是正常完成还是被取消,必须通过JSI的
callFunction或者TurboModule的Promise,给JS侧一个明确的结果,别让JS侧一直挂着等 - 线程优先级合理:C++任务线程别设太高优先级,避免抢占UI线程的资源,Android 15的调度器对前台UI线程的优先级保护很强,瞎改优先级反而容易出问题
三、业内成熟库的现成参考方案
你提到的Reanimated、VisionCamera、Skia都是JSI新架构的标杆,它们的取消逻辑都是协作式的,核心思路和上面一致,但有各自的细节优化:
- Reanimated:Worklet的
cancelAnimation内部是在C++侧维护一个取消标记,并且会自动在worklet的字节码里插入检查点——每执行几个字节码指令就检查一次标记,一旦触发就主动退出worklet。如果你自己写JSI函数,可以参考这个思路,手动在关键步骤插入检查点 - VisionCamera:Frame Processor的取消是通过
removeFrameProcessor,内部会把当前的frame processor指针置空,同时用原子变量标记取消。在每帧处理的循环里,会先检查指针是否有效+取消标记,一旦触发就停止处理当前帧,立刻清理摄像头帧资源,避免内存泄漏 - Skia:Skia的绘图任务取消是通过
SkCanvas::cancel(),内部也是在绘图的各个关键步骤(比如绘制路径、填充颜色)检查取消标记,主动中断绘图,同时释放Skia的绘图上下文资源
四、几个避坑的关键注意事项
- 绝对别用抢占式终止:比如
pthread_cancel这种,C++标准库完全不保证这种方式的安全性,容易导致内存泄漏、资源锁死,甚至直接崩溃 - 资源清理必须到位:取消任务后,一定要释放所有分配的C++资源——比如图像缓冲区、ML模型内存、文件句柄、摄像头帧等,否则Android 15的内存检测会直接杀你的APP
- 多任务场景要做任务隔离:如果同时跑多个长任务,一定要给每个任务分配独立的原子变量标记,别用全局单例的标记,不然会出现一取消就全取消的乌龙
- Promise任务要正确处理:如果你的TurboModule方法返回的是Promise,取消后一定要reject一个明确的错误(比如
new Error('Task cancelled')),别让JS侧的Promise一直处于pending状态
最后再补一句:如果你的任务是特别密集的计算(比如大模型推理),可以把任务拆成更小的块,每块执行完就检查一次取消标记,这样不仅取消响应快,还能让Android的调度器认为你的线程是“非阻塞”的,不会触发ANR。




