【问题标题】:Receive complete android unicode input in C/C++在 C/C++ 中接收完整的 android unicode 输入
【发布时间】:2014-02-03 03:11:08
【问题描述】:

(Android、NDK、C++、OpenGL ES)

我需要一种方法来可靠地接收来自(软)键盘的文本输入。 解决方案可以通过 Java 使用 NativeActivity 子类,或者任何可行的方法。 最后,我需要输入任何文本,因此我可以使用 OpenGL 自己渲染它

一些背景: 到目前为止,我一直通过调用 showSoftInput 或 hideSoftInputFromWindow 来触发软键盘,我认为是 JNI。到目前为止,这从未失败过。 但是,问题是本机活动不会发送所有字符。尤其是一些超出 ASCII 范围的 unicode 字符,或者某些运动软键盘不起作用(AKeyEvent_getKeyCode)

以前可以获取一些其他 unicode 字符,为什么要检查 KeyEvent.ACTION_MULTIPLE 并读取字符串。 但即使这样也不再可靠。

到目前为止,我还没有找到替代方法。 我尝试以编程方式添加 EditText,但从未让它工作。即使尝试添加一个简单的 Button 也会导致 OpenGL 视图不再被渲染。

在 iOS 上,我通过隐藏编辑框来解决这个问题,我只是激活它以显示键盘。然后我会读出编辑框并使用字符串在 OpenGL 中呈现自己。

【问题讨论】:

  • 感谢您的询问,但我无法关注您。您指的是什么变量,与它有什么相关性?

标签: android c++ unicode opengl-es android-ndk


【解决方案1】:

我也有同样的问题,我已经使用与 InputEvent 分开处理的“Character”事件解决了它。

问题是这样的:AKeyEvent_getKeyCode 不返回某些软键事件的 KeyCode,尤其是当您按住某个键时扩展的“unicode/latin”字符。这会阻止 @Shammi 和 @eozgonul 方法工作,因为在 Java 端重构的 KeyEvent 没有足够的信息来获取 unicode 字符。

另一个问题是 InputQueue 在触发 dispatchKeyEvent 事件之前在 C++/Native 端耗尽。这意味着 KEYDOWN/KEYUP 事件在 Java 代码可以处理事件之前全部触发。 (它们没有交错)。

我的解决方案是通过覆盖 dispatchKeyEvent 并将字符发送到 Queue<Integer> queueLastInputCharacter = new ConcurrentLinkedQueue<Integer>(); 来捕获 Java 端的 unicode 字符

// [JAVA]
@Override
public boolean dispatchKeyEvent (KeyEvent event)
{
    int metaState = event.getMetaState(); 
    int unichar = event.getUnicodeChar(metaState);

    // We are queuing the Unicode version of the characters for
    // sending to the app during processEvents() call.

    // We Queue the KeyDown and ActionMultiple Event UnicodeCharacters
    if(event.getAction()==KeyEvent.ACTION_DOWN){
        if(unichar != 0){
            queueLastInputCharacter.offer(Integer.valueOf(unichar));
        }
        else{
            unichar = event.getUnicodeChar(); 

            if(unichar != 0){
                queueLastInputCharacter.offer(Integer.valueOf(unichar));
            }
            else if (event.getDisplayLabel() != 0){
                String aText = new String();
                aText = "";
                aText += event.getDisplayLabel();
                queueLastInputCharacter.offer(Integer.valueOf(Character.codePointAt(aText, 0)));
            }
            else
                queueLastInputCharacter.offer(Integer.valueOf(0));
        }
    }
    else if(event.getAction()==KeyEvent.ACTION_MULTIPLE){
        unichar = (Character.codePointAt(event.getCharacters(), 0));
        queueLastInputCharacter.offer(Integer.valueOf(unichar));
    }


    return super.dispatchKeyEvent(event);
}

并发队列会让线程一起玩得很好。

我有一个返回最后一个输入字符的 Java 端方法:

// [JAVA]
public int getLastUnicodeChar(){
    if(!queueLastInputCharacter.isEmpty())
        return queueLastInputCharacter.poll().intValue();
    return 0;
}

在我的 looper 代码的最后,我添加了一个额外的检查来查看队列是否保留了任何 unicode 字符:

// [C++]
int ident;
int events;
struct android_poll_source* source;

// If not rendering, we will block 250ms waiting for events.
// If animating, we loop until all events are read, then continue
// to draw the next frame of animation.
while ((ident = ALooper_pollAll(((nv_app_status_focused(_lpApp)) ? 1 : 250),
                                NULL,
                                &events,
                                (void**)&source)) >= 0)
{
    // Process this event.
    if (source != NULL)
        source->process(_lpApp, source);

    // Check if we are exiting.  If so, dump out
    if (!nv_app_status_running(_lpApp))
        return;
}

static int modtime = 10; // let's not run on every call
if(--modtime == 0) {
    long uniChar = androidUnicodeCharFromKeyEvent();
    while (uniChar != 0) {
        KEvent kCharEvent; // Game engine event
        kCharEvent.ptkKey = K_VK_ERROR;
        kCharEvent.unicodeChar = uniChar;
        kCharEvent.character = uniChar;

        /* Send unicode char */
        kCharEvent.type = K_EVENT_UNICHAR;
        _lpPortableHandler(&kCharEvent);

        if (kCharEvent.character < 127) {
            /* Send ascii char for source compatibility as well */
            kCharEvent.type = K_EVENT_CHAR;
            _lpPortableHandler(&kCharEvent);
        }

        uniChar = androidUnicodeCharFromKeyEvent();
    }
    modtime = 10;
}

androidUnicodeCharFromKeyEvent 函数与@Shammi 的GetStringFromAInputEvent 方法非常相似,只是使用CallIntMethod 来返回jint

注意事项 这确实需要修改您的引擎以处理与键事件分开的角色事件。 Android 仍然有像 AKEYCODE_BACKAKEYCODE_ENTER 这样的键码,它们不是字符事件,仍然需要处理(并且可以在主输入循环器上处理)。

编辑框、控制台等...可以修改期望用户输入的内容,以接收构建字符串的单独字符事件。如果您在多个平台上工作,那么除了正常的按键输入事件之外,您还需要生成这些新的字符事件。

【讨论】:

    【解决方案2】:

    我希望这对你有用,到目前为止对我有用。

    int GetUnicodeChar(struct android_app* app, int eventType, int keyCode, int metaState)
    {
    JavaVM* javaVM = app->activity->vm;
    JNIEnv* jniEnv = app->activity->env;
    
    JavaVMAttachArgs attachArgs;
    attachArgs.version = JNI_VERSION_1_6;
    attachArgs.name = "NativeThread";
    attachArgs.group = NULL;
    
    jint result = javaVM->AttachCurrentThread(&jniEnv, &attachArgs);
    if(result == JNI_ERR)
    {
        return 0;
    }
    
    jclass class_key_event = jniEnv->FindClass("android/view/KeyEvent");
    int unicodeKey;
    
    if(metaState == 0)
    {
        jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "()I");
        jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
        jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
    
        unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char);
    }
    
    else
    {
        jmethodID method_get_unicode_char = jniEnv->GetMethodID(class_key_event, "getUnicodeChar", "(I)I");
        jmethodID eventConstructor = jniEnv->GetMethodID(class_key_event, "<init>", "(II)V");
        jobject eventObj = jniEnv->NewObject(class_key_event, eventConstructor, eventType, keyCode);
    
        unicodeKey = jniEnv->CallIntMethod(eventObj, method_get_unicode_char, metaState);
    }
    
    javaVM->DetachCurrentThread();
    
    LOGI("Unicode key is: %d", unicodeKey);
    return unicodeKey;
    }
    

    只需从您的输入处理程序中调用它,我的结构大致如下:

    switch (AInputEvent_getType(event))
        {
            case AINPUT_EVENT_TYPE_KEY:
              switch (AKeyEvent_getAction(event))
              {
                case AKEY_EVENT_ACTION_DOWN:
                  int key = AKeyEvent_getKeyCode(event);
                  int metaState = AKeyEvent_getMetaState(event);
                  int uniValue;
                  if(metaState != 0)
                      uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, metaState);
                  else
                      uniValue = GetUnicodeChar(app, AKEY_EVENT_ACTION_DOWN, key, 0);
    

    既然你说你已经打开了软键盘,我就不多说了,但代码有点简单。我基本上使用具有 GetUnicodeChar 函数的 KeyEvent 类的 Java 函数。

    【讨论】:

    • 感谢您分享您的代码。它不能解决问题。您的代码确实有效,实际上它与我已经做的非常相似。问题是,它不适用于所有键。例子。触摸键盘上的软键并按住它,一些键会与其他键一起打开选择。在这里,一些键根本不起作用。输入处理程序使用 AINPUT_EVENT_TYPE_KEY 触发,但 unicode 结果将为“0”。这一切在 Android 4.1 之前都很好,并且自 4.3 以来似乎发生了根本性的变化(类似的东西)
    • 这太棒了。它的工作原理是,作为一个警告,我尝试简化代码以不对 metaState 做任何奇怪的事情,而且它似乎用更少的代码工作。
    【解决方案3】:

    Eozgonul 的解决方案对我有用。我采用了它并对其进行了修改,以便在 Java 和本机端之间拆分工作。基本上,我扩展 NativeActivity 以派生我自己的类,它允许我尽可能多地迁移到 Java。我最终还传递了输入事件中的所有数据。我想确保在创建的 KeyEvent 对象中尽可能多地捕获。

    package com.MyCompany.MyApp;
    
    import android.os.Bundle;
    import android.view.inputmethod.InputMethodManager;
    import android.content.Context;
    import android.view.KeyEvent;
    
    public class MyNativeActivity extends android.app.NativeActivity
    {
    
        // Need this for screen rotation to send configuration changed callbacks to native
        @Override
        public void onConfigurationChanged( android.content.res.Configuration newConfig )
        {
            super.onConfigurationChanged( newConfig );
        }
    
        public void showKeyboard()
        {
            InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
            imm.showSoftInput( this.getWindow().getDecorView(), InputMethodManager.SHOW_FORCED );
        }
    
    
        public void hideKeyboard()
        {
            InputMethodManager imm = ( InputMethodManager )getSystemService( Context.INPUT_METHOD_SERVICE );
            imm.hideSoftInputFromWindow( this.getWindow().getDecorView().getWindowToken(), 0 );
        }
    
        public String stringFromKeyCode( long downTime, long eventTime, 
                int eventAction, int keyCode, int repeatCount, int metaState, 
                int deviceId, int scanCode, int flags, int source )
        {
            String strReturn;
    
            KeyEvent keyEvent = new KeyEvent( downTime, eventTime, eventAction, keyCode, repeatCount, metaState, deviceId, scanCode, flags, source );
    
            if ( metaState == 0 )
            {
                int unicodeChar = keyEvent.getUnicodeChar();
                if ( eventAction == KeyEvent.ACTION_MULTIPLE && unicodeChar == keyEvent.KEYCODE_UNKNOWN )
                {
                    strReturn = keyEvent.getCharacters();
                }
                else
                {
                    strReturn = Character.toString( ( char )unicodeChar );
                }
            }
            else
            {
                strReturn = Character.toString( ( char )( keyEvent.getUnicodeChar( metaState ) ) );
            }
    
            return strReturn;
        }
     }
    

    在原生端...

    std::string GetStringFromAInputEvent( android_app* pApp, AInputEvent* pInputEvent )
    {
        std::string strReturn;
    
        JavaVM* pJavaVM = pApp->activity->vm;
        JNIEnv* pJNIEnv = pApp->activity->env;
    
        JavaVMAttachArgs javaVMAttachArgs;
        javaVMAttachArgs.version = JNI_VERSION_1_6;
        javaVMAttachArgs.name = "NativeThread";
        javaVMAttachArgs.group = NULL;
    
        jint jResult;
        jResult = pJavaVM->AttachCurrentThread( &pJNIEnv, &javaVMAttachArgs );
        if ( jResult != JNI_ERR )
        {
            // Retrieves NativeActivity.
            jobject nativeActivity = pNativeActivity->clazz;
            jclass ClassNativeActivity = pJNIEnv->GetObjectClass( nativeActivity );
    
            jmethodID MethodStringFromKeyCode = pJNIEnv->GetMethodID( ClassNativeActivity, "stringFromKeyCode", "(JJIIIIIIII)Ljava/lang/String;" );
            jlong jDownTime = AKeyEvent_getDownTime( pInputEvent );
            jlong jEventTime = AKeyEvent_getEventTime( pInputEvent );
            jint jEventAction = AKeyEvent_getAction( pInputEvent );
            jint jKeyCode = AKeyEvent_getKeyCode( pInputEvent );
            jint jRepeatCount = AKeyEvent_getRepeatCount( pInputEvent );
            jint jMetaState = AKeyEvent_getMetaState( pInputEvent );
            jint jDeviceID = AInputEvent_getDeviceId( pInputEvent );
            jint jScanCode = AKeyEvent_getScanCode( pInputEvent );
            jint jFlags = AKeyEvent_getFlags( pInputEvent );
            jint jSource = AInputEvent_getSource( pInputEvent );
    
            jstring jKeyCodeString = ( jstring )pJNIEnv->CallObjectMethod( nativeActivity, MethodStringFromKeyCode, 
                jDownTime, jEventTime, jEventAction, 
                jKeyCode, jRepeatCount, jMetaState,
                jDeviceID, jScanCode, jFlags, jSource );
    
            const char* keyCodeString = pJNIEnv->GetStringUTFChars( keyCodeString, nullptr );
            strReturn = std::string( keyCodeString );
            pJNIEnv->ReleaseStringUTFChars( jKeyCodeString, keyCodeString );
    
            // Finished with the JVM.
            pJavaVM->DetachCurrentThread();
        }
    
        return strReturn;
    }
    

    我采用这种方法的两个原因..

    • 通过将代码移至 java 并仅让您在本机端调用一个 jni 包装器方法来降低代码语法复杂性。

    • Java 是首选的 Android 语言,这使我能够快速迭代基于 Java 的解决方案。此外,大多数现有的解决方案都是用 java 编写的。

    【讨论】:

    • 您的回复让我重新审视我的代码。我注意到您正在使用“AKeyEvent_getEventAction”,但我没有找到这样的功能。要么我在旧的 NDK 上,要么你的意思是“AKeyEvent_getAction”
    • 感谢您指出这一点。你说的对。没有 AKeyInput_getEventAction。当我重构代码以将所有内容放在这篇文章中时,这可能是一个错字。
    【解决方案4】:

    基本上这会解决问题。
    NativeActivity override onKeyDown()

    但您必须实现除 NDK 键输入之外的其他方式,才能将 onKeyMultiple 的 event.getCharacters() 字符串放入您的代码中。

    【讨论】:

      猜你喜欢
      • 2020-11-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-07-11
      • 2018-01-30
      相关资源
      最近更新 更多