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

不依赖android_native_app_glue.h的Android Native单线程输入处理方案

单线程Android Native应用输入处理ANR问题求解

我正在开发一款不依赖NDK glue代码的Android Native应用,目标是避免创建新线程,保持与普通应用一致的单线程运行模式。官方文档提及过该模式,但未找到相关示例。参考glue代码实现输入处理方案后,慢滑动和点击操作均正常,但快速滑动松手时会触发ANR(应用无响应)。求单线程场景下处理输入的最佳方式。

当前代码

static int on_input_queue_event(int fd, int events, void* data) {
  LOGI("got events: %d", events);
  App* app = data;
  for (int i=0; i<events; i++) {
    uint64_t stuff;
    size_t count = read(fd, &stuff, 8);
    LOGI("%llu <-%d->", stuff, count);
    AInputEvent* event;
    int result = AInputQueue_getEvent(app->input_queue, &event);
    if (result < 0) {
      LOGE("something went wrong");
      break;
    }
    AInputQueue_finishEvent(app->input_queue, event, 1);
  }
  // 0: unregister callback; 1: keep doing work
  return 1;
}

static void on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) {
  LOGI("on_input_queue_created");
  App* app = activity->instance;
  app->input_queue = queue;
  ALooper* looper = ALooper_prepare(0);
  AInputQueue_attachLooper(queue, looper, 1, on_input_queue_event, app);
}

static void on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) {
  LOGI("on_input_queue_destroyed");
  App* app = activity->instance;
  app->input_queue = NULL;
  AInputQueue_detachLooper(queue);
}

Logcat日志

got events: 1
0 <-1->
[...]
got events: 1
0 <-1->
[mouse button up while still swiping]
[new events not popping up any more]

线程栈追踪

DALVIK THREADS (14):
"main" prio=5 tid=1 Native
  | group="main" sCount=1 ucsCount=0 flags=1 obj=0x71d9bcd8 self=0x777a3acd66f0
  | sysTid=11513 nice=-10 cgrp=top-app sched=0/0 handle=0x777b071c24f8
  | state=S schedstat=( 488289842 15752972 1719 ) utm=36 stm=12 core=1 HZ=100
  | stack=0x7fff6020b000-0x7fff6020d000 stackSize=8188KB
  | held mutexes=
  native: #00 pc 000b947a  /apex/com.android.runtime/lib64/bionic/libc.so (__epoll_pwait+10) (BuildId: 3b932b1f8a953967762bc3b7184185e5)
  native: #01 pc 0001780b  /system/lib64/libutils.so (android::Looper::pollOnce+315) (BuildId: 624a8b7c0eabca99824bda237d4324ab)
  ...[后续栈信息保留原样]

问题根源

当前代码中**直接读取AInputQueue的文件描述符(fd)**是核心错误:AInputQueue的fd仅用于系统通知事件到来,事件数据由其内部API管理,直接read会破坏队列的内部状态,导致后续事件无法被正确获取,最终引发输入阻塞和ANR。此外,循环依赖events参数(Looper传递的是事件标志位,不是事件数量)也会导致事件处理不完整。

修正后的实现方案

以下是单线程场景下输入处理的正确实现:

static int on_input_queue_event(int fd, int events, void* data) {
  LOGI("input queue event triggered");
  App* app = data;
  AInputEvent* event;

  // 循环获取所有待处理事件,直到无更多事件
  while (true) {
    int result = AInputQueue_getEvent(app->input_queue, &event);
    if (result == AINPUT_EVENT_RESULT_WOULD_BLOCK) {
      // 没有更多事件,退出循环
      break;
    } else if (result < 0) {
      LOGE("Failed to retrieve input event: %d", result);
      break;
    }

    // 这里添加自定义事件处理逻辑,例如判断事件类型、分发到业务逻辑
    int32_t eventType = AInputEvent_getType(event);
    LOGI("Received input event type: %d", eventType);

    // 必须调用finishEvent通知系统事件已处理,否则会阻塞后续输入
    AInputQueue_finishEvent(app->input_queue, event, 1);
  }

  return 1; // 保持回调注册,持续监听输入事件
}

static void on_input_queue_created(ANativeActivity* activity, AInputQueue* queue) {
  LOGI("input queue created");
  App* app = activity->instance;
  app->input_queue = queue;

  // 使用主线程已有的Looper,避免重复初始化
  ALooper* looper = ALooper_forThread();
  if (!looper) {
    looper = ALooper_prepare(ALOOPER_PREPARE_ALLOW_NON_CALLBACKS);
  }

  // 正确关联Looper与输入队列,使用ALOOPER_POLL_CALLBACK标志
  AInputQueue_attachLooper(queue, looper, ALOOPER_POLL_CALLBACK, on_input_queue_event, app);
}

static void on_input_queue_destroyed(ANativeActivity* activity, AInputQueue* queue) {
  LOGI("input queue destroyed");
  App* app = activity->instance;
  
  // 先解绑Looper再清空引用
  AInputQueue_detachLooper(queue);
  app->input_queue = NULL;
}

关键注意事项

  1. 禁止直接操作AInputQueue的fd:所有输入事件必须通过AInputQueue_getEvent和AInputQueue_finishEvent接口处理,fd仅用于Looper监听事件通知。
  2. 正确的事件循环逻辑:通过循环调用AInputQueue_getEvent直到返回AINPUT_EVENT_RESULT_WOULD_BLOCK,确保一次性处理所有待处理事件,避免遗漏。
  3. 复用主线程Looper:ANativeActivity的主线程默认已初始化Looper,直接使用ALooper_forThread()获取即可,无需重复调用ALooper_prepare(0)。
  4. 必须调用finishEvent:处理完每个事件后必须通知系统,否则系统会认为事件未处理,持续等待导致输入阻塞或ANR。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.17 13:02:18