5

我们正在为安卓开发一款独立游戏,并希望用户选择他的昵称。我们选择使用 NDK 提供的 Native Activity,因为这似乎是最简单的方法。

我们在键盘上遇到的第一个问题是函数 ANativeActivity_showSoftInput() 似乎什么都不做(如此处所述,所以我们使用 JNI 调用函数来调出键盘:

static void showKeyboard(Activity activity) {
  String s = Context.INPUT_METHOD_SERVICE;
  InputMethodManager m = (InputMethodManager)activity.getSystemService(s);
  View w = activity.getWindow().getDecorView();
  m.showSoftInput(w, 0);
}

这可以很好地调出键盘,并且在某些设备上都可以正常工作。但是在其他设备(例如 Nexus 7)上,当用户尝试通过点击“隐藏键盘”按钮来关闭键盘时,应用程序会冻结并显示以下调试输出:

I/InputDispatcher(  453): Application is not responding: AppWindowToken{429b54a8 token=Token{42661288 ActivityRecord{41bb0b00 u0 com.example.project/android.app.NativeActivity}}} - Window{420d6138 u0 com.example.project/android.app.NativeActivity}.  It has been 5006.7ms since event, 5005.6ms since wait started.  Reason: Waiting because the focused window has not finished processing the input events that were previously delivered to it.
I/WindowManager(  453): Input event dispatching timed out sending to com.example.project/android.app.NativeActivity

然后用户会看到一个对话框,上面写着:

Project isn't responding. Do you want to close it? [Wait]/[OK]

有什么我们做的明显错误的事情吗?或者这可能是一个错误?像这样的问题似乎表明键盘功能从未在本机胶水中正确实现。

附带说明一下,我们还没有在许多设备上进行测试,但不会崩溃的设备是具有较旧 Android 操作系统的设备。此外,在它确实崩溃的地方,当键盘出现时,它会将后退按钮从一个看起来像这样后退箭头形按钮变成一个看起来像这样 V形按钮。也许这对应于他们第一次开发原生胶水时没有考虑的不同输入事件?我只是猜测。

无论如何,如果有人在使用本机活动时让软键盘工作,请告诉我们你是如何做到的。

干杯

更新

它已在此处报告为 Android 中的错误,但我们仍然很高兴听到有关解决方法的信息。如果您也受到它的影响,您可能想对该问题进行投票(按星号)。

4

2 回答 2

3

彼得的解决方案效果很好。但是,如果您不想修改 native_app_glue 文件:请注意 process_input 被分配为函数指针。在您的实现文件中,创建您自己的 process_input 函数,如 Peter 所述:

static void process_input( struct android_app* app, struct android_poll_source* source) {
    AInputEvent* event = NULL;
    if (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
        int type = AInputEvent_getType(event);
        LOGV("New input event: type=%d\n", AInputEvent_getType(event));

        bool skip_predispatch
              =  AInputEvent_getType(event)  == AINPUT_EVENT_TYPE_KEY
              && AKeyEvent_getKeyCode(event) == AKEYCODE_BACK;

        // skip predispatch (all it does is send to the IME)
        if (!skip_predispatch && AInputQueue_preDispatchEvent(app->inputQueue, event)) {
            return;
        }

        int32_t handled = 0;
        if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
        AInputQueue_finishEvent(app->inputQueue, event, handled);
    } else {
        LOGE("Failure reading next input event: %s\n", strerror(errno));
    }
}

在您的android_main函数的开头,将您的process_input版本分配给android_app->inputPollSource.process

在您的事件处理程序中,确保您检查后退键(AKEYCODE_BACK)并拦截它以隐藏您的键盘(如果可见)。

请注意,此问题似乎存在于 Android 4.1 和 4.2 中 - 在 4.3 中已解决

于 2013-07-30T18:50:22.457 回答
2

好的,正如我在原始问题的更新中提到的,这是 Android 操作系统内部的一个错误。我们找到了一种解决方法,它真的很难看,但它确实有效,所以有人可能会觉得它有用。

首先你需要修改文件

<NDK>/sources/android/native_app_glue/android_native_app_glue.c

通过将函数process_input更改为如下所示:

// When user closes the software keyboard, this function is normally not
// called at all. On the buggy devices, it is called as if AKEYCODE_BACK
// was pressed. This event then gets consumed by the
// AInputQueue_preDispatchEvent. There should be some mechanism that then
// calls the process_input again to finish processing the input.
// But it never does and AInputQueue_finishEvent is never called, the OS
// notices this and closes our app.
static void process_input( struct android_app* app
                         , struct android_poll_source* source) {
    AInputEvent* event = NULL;
    if (AInputQueue_getEvent(app->inputQueue, &event) >= 0) {
        int type = AInputEvent_getType(event);
        LOGV("New input event: type=%d\n", AInputEvent_getType(event));

        int skip_predispatch
              =  AInputEvent_getType(event)  == AINPUT_EVENT_TYPE_KEY
              && AKeyEvent_getKeyCode(event) == AKEYCODE_BACK;

        // TODO: Not sure if we should skip the predispatch all together
        //       or run it but not return afterwards. The main thing
        //       is that the code below this 'if' block will be called.
        if (!skip_predispatch) {
          if (AInputQueue_preDispatchEvent(app->inputQueue, event)) {
              return;
          }
        }

        int32_t handled = 0;
        if (app->onInputEvent != NULL) handled = app->onInputEvent(app, event);
        AInputQueue_finishEvent(app->inputQueue, event, handled);
    } else {
        LOGE("Failure reading next input event: %s\n", strerror(errno));
    }
}

然后你应该在你自己的输入事件处理程序中有一个看起来像这样的代码:

static int32_t handle_input(android_app* app, AInputEvent* event) {
  int32_t handled = 0;

  struct engine* engine = (struct engine*) app->userData;
  switch (AInputEvent_getType(event)) {
    case AINPUT_EVENT_TYPE_KEY:
      switch (AKeyEvent_getAction(event)) {
        case AKEY_EVENT_ACTION_DOWN:
          int key = AKeyEvent_getKeyCode(event);
          if (os_version_major == 4 && os_version_minor == 2) {
            if (m_keyboard_is_visible && key == AKEYCODE_BACK) {
              // You should set this to true when showing the keyboard.
              m_keyboard_is_visible = false;
              hide_keyboard();
              handled = 1;
              break;
            }
          }
          ... // your own "key down" event handling code.
          break;
      }
      break;
    ...
  }
  return handled;  
}

要获取操作系统版本号,我们使用另一个 JNI 调用从android.os.Build.VERSION.RELEASE android.os.Build.VERSION.SDK_INT获取它。要实现show_keyboardhide_keyboard ,请使用本文中 Ratamovics 答案中的信息。

注意要自动编译 android_native_app_glue.c 并避免直接对 NDK 树进行更改,您可能需要将文件复制到项目的 jni/ 目录并从 Android.mk 中删除这两行

LOCAL_STATIC_LIBRARIES := android_native_app_glue
$(call import-module,android/native_app_glue)
于 2013-04-12T01:59:41.727 回答