You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多
文档控制台
免费开始使用

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)**作为终止标记,原子变量本身就是线程安全的,不需要额外加锁,完美适配跨线程(JS线程→C任务线程、native线程→C++任务线程)的信号传递:

  • 在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倍,所以除了协作式取消,还要注意这几点:

  1. 绝对隔离线程:长任务必须跑在独立的C++线程(用std::thread或者Android原生的ThreadPoolExecutor),哪怕是JSI函数也不能在JS线程执行,一跑就ANR
  2. 高频检查信号:任务拆分得越细,检查点越密,取消响应越快,也越不容易触发Android的ANR检测。比如ML推理可以按每层神经网络、图像处理按每100x100像素块来拆分
  3. 及时通知JS:不管任务是正常完成还是被取消,必须通过JSI的callFunction或者TurboModule的Promise,给JS侧一个明确的结果,别让JS侧一直挂着等
  4. 线程优先级合理: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的绘图上下文资源

四、几个避坑的关键注意事项

  1. 绝对别用抢占式终止:比如pthread_cancel这种,C++标准库完全不保证这种方式的安全性,容易导致内存泄漏、资源锁死,甚至直接崩溃
  2. 资源清理必须到位:取消任务后,一定要释放所有分配的C++资源——比如图像缓冲区、ML模型内存、文件句柄、摄像头帧等,否则Android 15的内存检测会直接杀你的APP
  3. 多任务场景要做任务隔离:如果同时跑多个长任务,一定要给每个任务分配独立的原子变量标记,别用全局单例的标记,不然会出现一取消就全取消的乌龙
  4. Promise任务要正确处理:如果你的TurboModule方法返回的是Promise,取消后一定要reject一个明确的错误(比如new Error('Task cancelled')),别让JS侧的Promise一直处于pending状态

最后再补一句:如果你的任务是特别密集的计算(比如大模型推理),可以把任务拆成更小的块,每块执行完就检查一次取消标记,这样不仅取消响应快,还能让Android的调度器认为你的线程是“非阻塞”的,不会触发ANR。

火山引擎 最新活动