在C / C ++中接收完整的android unicode输入

时间:2014-01-14 21:07:09

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

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

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

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

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

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

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

4 个答案:

答案 0 :(得分:4)

我有同样的问题,我使用'Character'事件解决了它,我将事件与InputEvent分开处理。

问题是: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

备注 这确实需要修改引擎以处理与Key事件分开的字符事件。 Android仍然具有AKEYCODE_BACKAKEYCODE_ENTER等关键代码,这些代码不是字符事件,仍然需要处理(并且可以在主输入循环器上处理)。

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

答案 1 :(得分:3)

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

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函数。

答案 2 :(得分:1)

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。

答案 3 :(得分:0)

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

但是你必须实现除NDK键输入以外的其他方式来将onKeyMultiple的event.getCharacters()字符串放入代码中。