在《從 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,如此就完成了。
沒有留言:
張貼留言