使用本机活动时关闭软键盘时崩溃

时间:2013-04-09 21:40:17

标签: android android-layout android-ndk native-activity

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

键盘遇到的第一个问题是函数ANativeActivity_showSoftInput()似乎什么都不做(如here所述),所以我们使用JNI调用函数调出键盘:< / p>

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]

我们做的事情明显错了吗?或者这可能是一个错误?像this one这样的问题似乎表明键盘功能从未在原生胶中正确实现。

另一方面,我们尚未在许多设备上进行过测试,但它没有崩溃的是那些拥有较旧Android操作系统的设备。此外,在它崩溃的那些,当键盘出现时,它会将后退按钮从看起来像backward arrow shaped button的按钮更改为看起来像这样的按钮 V shaped button。也许这对应于一个不同的输入事件,当他们第一次开发原生胶时没有考虑到这些事件?我只是在猜测。

无论如何,如果有人在使用原生活动时使用软键盘,请告诉我们您是如何做到的。

干杯

更新

据报道,这是Android here中的一个错误,但我们仍然很乐意听到有关解决方法的信息。如果您也受到影响,您可能希望对该问题进行投票(通过按星号)。

2 个答案:

答案 0 :(得分:3)

彼得的解决方案运作良好。但是,如果您不想修改native_app_glue文件:请注意process_input被指定为函数指针。在您的实现文件中,按照Peter所描述的那样创建您自己的process_input函数:

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-&gt; inputPollSource.process

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

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

中解决

答案 1 :(得分: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_keyboard hide_keyboard ,请使用来自this帖子的Ratamovics答案中的信息。

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

LOCAL_STATIC_LIBRARIES := android_native_app_glue
$(call import-module,android/native_app_glue)