2013年12月14日 星期六

使用 C++ 改寫 HelloJNI++

在《從 HelloJNI 開始 Android NDK 的使用》這篇文章裡,JNI 裡的 Native Library 是以 C 完成的。接著,我們試著將它改成由 C++ 來完成。

首先,將 jni/hellojni.c 改名稱為 jni/hellojni.cpp,然後同時修改 jni/Android.mk 的內容:

LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := hellojni
LOCAL_SRC_FILES := hellojni.cpp

include $(BUILD_SHARED_LIBRARY) 

接著,修改 jni/hellojni.cpp 的內容:

#include <jni.h>
#include <string.h>
#include <android/log.h>

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
 return env->NewStringUTF("Hello from JNI !");
} 

簡單說,請是將原本的 return (*env)->NewStringUTF(env, "Hello from JNI !"); 改成 return env->NewStringUTF("Hello from JNI !");

接著,執行 Build Project。但,此時可能會出現這樣的錯誤:

這是因為在 AndroidManifest.xml 指定的 minSdkVersion 和使用的 SDK 版本不一樣。

    <uses-sdk
        android:minSdkVersion="8"
        android:targetSdkVersion="18" /> 

解決的方法很簡單,新建一個 jni/Application.mk,然後填入內容:

APP_PLATFORM := android-8 

這裡使用的版本必須和 minSdkVersion 相同。

最後,將編譯出來的 HelloJNI++.apk 下載到模擬器執行,卻出現了錯誤的狀況。

而,從 Logcat 也可以查看到以下的錯誤訊息:

E/AndroidRuntime(1365): FATAL EXCEPTION: main
E/AndroidRuntime(1365): Process: demo.example.hellojni, PID: 1365
E/AndroidRuntime(1365): java.lang.UnsatisfiedLinkError: Native method not found: demo.example.hellojni.HelloActivity.stringFromJNI:()Ljava/lang/String;
E/AndroidRuntime(1365):     at demo.example.hellojni.HelloActivity.stringFromJNI(Native Method)
E/AndroidRuntime(1365):     at demo.example.hellojni.HelloActivity.onCreate(HelloActivity.java:20)
E/AndroidRuntime(1365):     at android.app.Activity.performCreate(Activity.java:5243)
E/AndroidRuntime(1365):     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1087)
E/AndroidRuntime(1365):     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2140)
E/AndroidRuntime(1365):     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2226)
E/AndroidRuntime(1365):     at android.app.ActivityThread.access$700(ActivityThread.java:135)
E/AndroidRuntime(1365):     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1397)
E/AndroidRuntime(1365):     at android.os.Handler.dispatchMessage(Handler.java:102)
E/AndroidRuntime(1365):     at android.os.Looper.loop(Looper.java:137)
E/AndroidRuntime(1365):     at android.app.ActivityThread.main(ActivityThread.java:4998)
E/AndroidRuntime(1365):     at java.lang.reflect.Method.invokeNative(Native Method)
E/AndroidRuntime(1365):     at java.lang.reflect.Method.invoke(Method.java:515)
E/AndroidRuntime(1365):     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:777)
E/AndroidRuntime(1365):     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:593)
E/AndroidRuntime(1365):     at dalvik.system.NativeStart.main(Native Method) 

在網路上可以找到一份《JNI Examples for Android》的文件,參考它的內容實作 JNI OnLoad() 的部份能處理這個問題。至於實作的內容,在 Android 的原始碼裡可以找到一份 SimpleJNI 的範例,在 https://github.com/android/platform_development/tree/master/samples/SimpleJNI 這裡可以查看程式碼。根據範例裡的 jni/native.cpp,我們可以修改 HelloJNI++ 的 jni/hellojni.cpp 的內容:

#include <jni.h>
#include <string.h>
#include <android/log.h>
#include <stdio.h>

#define LOG_TAG "HelloJNI++: hellojni.cpp"

jstring
Java_demo_example_hellojni_HelloActivity_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return env->NewStringUTF("Hello from JNI !");
}

static const char *classPathName = "demo/example/hellojni/HelloActivity";

static JNINativeMethod methods[] = {
  {"stringFromJNI", "()Ljava/lang/String;", (void*)Java_demo_example_hellojni_HelloActivity_stringFromJNI },
};

/*
 * Register several native methods for one class.
 */
static int registerNativeMethods(JNIEnv* env, const char* className,
    JNINativeMethod* gMethods, int numMethods)
{
    jclass clazz;

    clazz = env->FindClass(className);
    if (clazz == NULL) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Native registration unable to find class '%s'", className);
        return JNI_FALSE;
    }
    if (env->RegisterNatives(clazz, gMethods, numMethods) < 0) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "RegisterNatives failed for '%s'", className);
        return JNI_FALSE;
    }

    return JNI_TRUE;
}

/*
 * Register native methods for all classes we know about.
 *
 * returns JNI_TRUE on success.
 */
static int registerNatives(JNIEnv* env)
{
  if (!registerNativeMethods(env, classPathName,
                 methods, sizeof(methods) / sizeof(methods[0]))) {
    return JNI_FALSE;
  }

  return JNI_TRUE;
}


// ----------------------------------------------------------------------------

/*
 * This is called by the VM when the shared library is first loaded.
 */

typedef union {
    JNIEnv* env;
    void* venv;
} UnionJNIEnvToVoid;

jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
    UnionJNIEnvToVoid uenv;
    uenv.venv = NULL;
    jint result = -1;
    JNIEnv* env = NULL;

    __android_log_print(ANDROID_LOG_INFO, LOG_TAG, "JNI_OnLoad");

    if (vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: GetEnv failed");
        goto bail;
    }
    env = uenv.env;

    if (registerNatives(env) != JNI_TRUE) {
        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "ERROR: registerNatives failed");
        goto bail;
    }

    result = JNI_VERSION_1_4;

bail:
    return result;
} 

此外,這裡用了 __android_log_print ,請再參考《在 JNI 程式碼使用 Logcat》修改 Android.mk,如此就完成了。

沒有留言: