【问题标题】:Crash when closing soft keyboard while using native activity使用本机活动时关闭软键盘时崩溃
【发布时间】:2013-04-01 12:58:36
【问题描述】:

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

我们遇到的第一个键盘问题是函数 ANativeActivity_showSoftInput() 似乎什么都不做(如 here 所描述的那样),所以我们使用 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]

我们做错了什么吗?或者这可能是一个错误? this one 之类的问题似乎表明键盘功能从未在本机胶水中正确实现。

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

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

干杯

更新

它已被报告为 Android here 中的一个错误,但我们仍然很乐意听到解决方法。如果您也受到它的影响,您可能想对该问题进行投票(按星号)。

【问题讨论】:

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


    【解决方案1】:

    Peter 的解决方案效果很好。但是,如果您不想修改 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->inputPollSource.process

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

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

    【讨论】:

    • 我完全错过了,比修补 c 文件要好得多,谢谢。
    • 偶然地,我今天收到了我的 Nexus 升级,看来问题已在 Android 4.3 中得到解决。您的建议仅在 Android 版本为 4.2 时才可以轻松应用修补的 process_input 函数。如果您愿意,您可以结合您和我的答案为其他人提供最终解决方案。然后我将其标记为最终答案。否则,如果我记得的话,我会在下周的某个时候尝试这样做。干杯。
    • 当然 - 已更新。我还注意到这个问题在 4.3 中得到了修复!太糟糕了,它会在 4.1 和 4.2 手机市场上持续一段时间......
    • @krsteeve 这应该是一个while循环,见developer.nvidia.com/content/…
    【解决方案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 答案的 this 帖子中的信息。

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

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

    【讨论】:

    • 我在装有 Android 4.2 的 Nexus 4 上遇到了这个问题。感谢您 [A LOT] 记录此问题!
    猜你喜欢
    • 2016-02-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-09
    • 2015-12-09
    • 1970-01-01
    • 2011-08-17
    • 1970-01-01
    相关资源
    最近更新 更多