在《從 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,如此就完成了。