目錄:
1 JNI基礎知識
2 Android中的JNI
3 我們在寫JNI時候,一些容易出現的問題
4 Android中一個簡單的例子
5 Android中交叉編譯最新版libjpeg-8d的例子
6 C++編寫JNI的不同之處
7 后記
8 參考文章
1 JNI基礎知識
JNI是Java native interface的簡寫,可以譯作Java原生接口。Java可以通過JNI調用C/C++的庫,這對於那些對性能要求比較高的Java程序無疑是一個福音。
在Sun的官方提供了一個詳細的文檔,關於Java中JNI的基本對象方法以及使用說明。英文好的兄弟可以下載下來直接看。從開發的角度,基本差不多,問題的關鍵是語法、資源的釋放、復雜對象的傳遞問題。
中文版相關下載地址:http://download.csdn.net/detail/ZFZF294990051/3477828
本文附上本地下載:
jni.doc.tar
JNI開發的基本步驟見下圖(請單獨點擊打開):
2 Android中的JNI
Android中使用JNI,是Android NDK編程的一個媒介,在開發中,Android的版本必須高於1.5才可以使用JNI編程。當然,現在市面上的機器都滿足。
其基本的使用方式為:
static { System.loadLibrary("HelloWorld"); } private native String Hello();
使用上面的定義后,我們就可一像使用本地的java類函數一樣調用Hello了,這個函數返回String對象。
這里需要提到的一點就是在JNI里面,有兩個很重要的函數
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
void *venv;
LOGI("JNI_OnLoad!");
if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
return -1;
}
return JNI_VERSION_1_4;
}
jint JNI_OnUnload(JavaVM* vm,void* reserved)
{
void *venv;
LOGI("JNI_OnUnload!");
if ((*vm)->GetEnv(vm, (void**)&venv, JNI_VERSION_1_4) != JNI_OK) {
LOGE("ERROR: GetEnv failed");
return -1;
}
return JNI_VERSION_1_4;
}
上面的函數是指如果我們需要在加載SO庫或者釋放SO庫的時候,需要額外處理,可以加在這里。
在Android中,我們如果編譯了一個APK,可以使用javah 自動生成相應的JNI的C實現頭文件。但是,跟單獨使用javac編譯文件不同的時候,我們需要額外指定android.jar庫的路徑。否則會出現奇怪的錯誤。下面是兩個典型的錯誤。
當我們編譯完畢后,使用 javah com.jouhu.HelloWorld,會出現如下錯誤:
Error: Could not find class file for ‘com.jouhu.HelloWorld’.
解決辦法:
javah -classpath /root/workspace/HelloWorld/bin/classes com.jouhu.HelloWorld
請注意,由於我們Android編譯后,會將.class文件放到classes目錄下面,如果我們在bin目錄下,執行,還會報上面的錯誤的。這里非常關鍵。
當我們使用上面的方法編譯后,出現如下錯誤:
Error: cannot access android.app.Activity
class file for android.app.Activity not found
解決方法,附加android.jar庫的地址:
javah -classpath /opt/android-sdk-linux/platforms/android-8/android.jar:/root/workspace/HelloWorld/bin/classes com.jouhu.HelloWorld
至此,我們就會生成文件com_jouhu_HelloWorld.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_jouhu_HelloWorld */
#ifndef _Included_com_jouhu_HelloWorld
#define _Included_com_jouhu_HelloWorld
#ifdef __cplusplus
extern "C" {
#endif
#undef com_jouhu_HelloWorld_MODE_PRIVATE
#define com_jouhu_HelloWorld_MODE_PRIVATE 0L
#undef com_jouhu_HelloWorld_MODE_WORLD_READABLE
#define com_jouhu_HelloWorld_MODE_WORLD_READABLE 1L
#undef com_jouhu_HelloWorld_MODE_WORLD_WRITEABLE
#define com_jouhu_HelloWorld_MODE_WORLD_WRITEABLE 2L
#undef com_jouhu_HelloWorld_MODE_APPEND
#define com_jouhu_HelloWorld_MODE_APPEND 32768L
#undef com_jouhu_HelloWorld_BIND_AUTO_CREATE
#define com_jouhu_HelloWorld_BIND_AUTO_CREATE 1L
#undef com_jouhu_HelloWorld_BIND_DEBUG_UNBIND
#define com_jouhu_HelloWorld_BIND_DEBUG_UNBIND 2L
#undef com_jouhu_HelloWorld_BIND_NOT_FOREGROUND
#define com_jouhu_HelloWorld_BIND_NOT_FOREGROUND 4L
#undef com_jouhu_HelloWorld_CONTEXT_INCLUDE_CODE
#define com_jouhu_HelloWorld_CONTEXT_INCLUDE_CODE 1L
#undef com_jouhu_HelloWorld_CONTEXT_IGNORE_SECURITY
#define com_jouhu_HelloWorld_CONTEXT_IGNORE_SECURITY 2L
#undef com_jouhu_HelloWorld_CONTEXT_RESTRICTED
#define com_jouhu_HelloWorld_CONTEXT_RESTRICTED 4L
#undef com_jouhu_HelloWorld_RESULT_CANCELED
#define com_jouhu_HelloWorld_RESULT_CANCELED 0L
#undef com_jouhu_HelloWorld_RESULT_OK
#define com_jouhu_HelloWorld_RESULT_OK -1L
#undef com_jouhu_HelloWorld_RESULT_FIRST_USER
#define com_jouhu_HelloWorld_RESULT_FIRST_USER 1L
#undef com_jouhu_HelloWorld_DEFAULT_KEYS_DISABLE
#define com_jouhu_HelloWorld_DEFAULT_KEYS_DISABLE 0L
#undef com_jouhu_HelloWorld_DEFAULT_KEYS_DIALER
#define com_jouhu_HelloWorld_DEFAULT_KEYS_DIALER 1L
#undef com_jouhu_HelloWorld_DEFAULT_KEYS_SHORTCUT
#define com_jouhu_HelloWorld_DEFAULT_KEYS_SHORTCUT 2L
#undef com_jouhu_HelloWorld_DEFAULT_KEYS_SEARCH_LOCAL
#define com_jouhu_HelloWorld_DEFAULT_KEYS_SEARCH_LOCAL 3L
#undef com_jouhu_HelloWorld_DEFAULT_KEYS_SEARCH_GLOBAL
#define com_jouhu_HelloWorld_DEFAULT_KEYS_SEARCH_GLOBAL 4L
/*
* Class: com_jouhu_HelloWorld
* Method: printJNI
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jouhu_HelloWorld_printJNI
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
接下來,我們就可以根據定義,去實現printJNI函數了。
JNIEXPORT jstring JNICALL Java_com_jouhu_HelloWorld_printJNI(JNIEnv *env, jobject obj)
{
LOGI("Hello World From libhelloworld.so!");
return (*env)->NewStringUTF(env, "Hello World!");
}
這里還有一個問題需要注意的就是:
我們使用NDK-Build生成的文件是libhelloworld.so,我們在使用System.loadLibrary的時候,參數只需要到helloworld,而不需要把lib和.so加上。
編寫Android.mk文件
//一般都是這行
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
//代碼文件
LOCAL_SRC_FILES:=com_jouhu_HelloWorld.c
//包含的頭文件
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
//生成的so庫名稱
LOCAL_MODULE := libhelloworld
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
3 我們在寫JNI時候,一些容易出現的問題,參考文章
內存釋放,各種類型數據的轉換
jbytearray轉c++byte數組
jbyte * arrayBody = env->GetByteArrayElements(data,0);
jsize theArrayLengthJ = env->GetArrayLength(data);
BYTE * starter = (BYTE *)arrayBody;
jbyteArray 轉 c++中的BYTE[]
//jbytearray strIn
jbyte * olddata = (jbyte*)env->GetByteArrayElements(strIn, 0);
jsize oldsize = env->GetArrayLength(strIn);
BYTE* bytearr = (BYTE*)olddata;
int len = (int)oldsize;
char* 轉jstring
jstring WindowsTojstring(JNIEnv* env, char* str_tmp)
{
jstring rtn=0;
int slen = (int)strlen(str_tmp);
unsigned short* buffer=0;
if(slen == 0)
{
rtn = env->NewStringUTF(str_tmp);
}
else
{
int length = MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, NULL, 0);
buffer = (unsigned short*)malloc(length*2+1);
if(MultiByteToWideChar(CP_ACP, 0, (LPCSTR)str_tmp, slen, (LPWSTR)buffer, length) > 0)
{
rtn = env->NewString((jchar*)buffer, length);
}
}
if(buffer)
{
free(buffer);
}
return rtn;
}
4 Android中一個簡單的例子
4.1 使用Eclipse創建一個HelloWorld的Activity的程序。
package com.jouhu;
import android.app.Activity;
import android.os.Bundle;
import android.util.Log;
public class HelloWorld extends Activity {
private static final String TAG = "HelloWorld";
static {
System.loadLibrary("helloworld");
}
private native String printJNI();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Log.d(TAG, "Activity call JNI: " + printJNI());
}
}
4.2 在虛擬機中運行一次,系統會在/root/workspace/HelloWorld/bin/classes/com/jouhu目錄生成相應的class文件
4.3 進入命令行的/root/workspace/HelloWorld/bin目錄
執行
javah -classpath /opt/android-sdk-linux/platforms/android-8/android.jar:/root/workspace/HelloWorld/bin/classes com.jouhu.HelloWorld
4.4 將會在/bin目錄生成一個com_jouhu_HelloWorld.h文件
4.5 在/root/workspace/HelloWorld目錄建立jni目錄
4.6 將剛才生成的com_jouhu_HelloWorld.h拷貝到jni目錄下
4.7 創建com_jouhu_HelloWorld.cpp文件
#include "com_jouhu_HelloWorld.h"
JNIEXPORT jstring JNICALL Java_com_jouhu_HelloWorld_printJNI
(JNIEnv *env, jobject)
{
return env->NewStringUTF("Hello World!");
}
4.8 創建 Android.mk文件
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES:=com_jouhu_HelloWorld.cpp
LOCAL_C_INCLUDES := $(JNI_H_INCLUDE)
LOCAL_MODULE := libhelloworld
LOCAL_SHARED_LIBRARIES := libutils
LOCAL_PRELINK_MODULE := false
include $(BUILD_SHARED_LIBRARY)
4.9 打開命令行 進入目錄/root/workspace/HelloWorld/jni
4.10 執行ndk-build
4.11 結果
root@ubuntu:~/workspace/HelloWorld/jni# ndk-build
Compile++ thumb : helloworld libs/armeabi/libhelloworld.so
4.12 adb install HelloWorld.apk 即可看到結果了。
5 Android中交叉編譯libjpeg的例子
5.1 下載最新的jpeg-8d源代碼
5.2 創建Android Project
5.3 編寫native加載函數代碼
package com.jouhu;
import android.app.Activity;
import android.os.Bundle;
public class AndroidJpeg8DActivity extends Activity {
static
{
System.loadLibrary("jpegtest");
}
public native String jpegTest();
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
}
}
5.4 虛擬機中執行程序(主要目的是為了生成class文件)
5.5 使用javah生成.h文件
javah -classpath /opt/android-sdk-linux/platforms/android-8/android.jar:/root/workspace/AndroidJpeg8D/bin/classes com.jouhu.AndroidJpeg8DActivity
5.6 拷貝生成的com_jouhu_AndroidJpeg8DActivity.h文件到項目的jni目錄,並編寫.cpp文件
#include "com_jouhu_AndroidJpeg8DActivity.h"
/*
* Class: com_jouhu_AndroidJpeg8DActivity
* Method: jpegTest
* Signature: ()Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_com_jouhu_AndroidJpeg8DActivity_jpegTest
(JNIEnv * env, jobject)
{
return env->NewStringUTF("Hello World!");
}
5.7 編寫Android.mk文件(在jni目錄一個Android.mk以及jpeg-8d目錄的Android.mk)
jni目錄的Android.mk文件
# Copyright (C) 2009 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_SRC_FILES := com_jouhu_AndroidJpeg8DActivity.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/jpeg8d
LOCAL_STATIC_LIBRARIES := libjpeg
LOCAL_MODULE := jpegtest
#LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/jpeg-8d/Android.mk
jpeg-8d/Android.mk
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
LOCAL_ARM_MODE := arm
LOCAL_SRC_FILES := \
jaricom.c jcapimin.c jcapistd.c jcarith.c jccoefct.c jccolor.c \
jcdctmgr.c jchuff.c jcinit.c jcmainct.c jcmarker.c jcmaster.c \
jcomapi.c jcparam.c jcprepct.c jcsample.c jctrans.c jdapimin.c \
jdapistd.c jdarith.c jdatadst.c jdatasrc.c jdcoefct.c jdcolor.c \
jddctmgr.c jdhuff.c jdinput.c jdmainct.c jdmarker.c jdmaster.c \
jdmerge.c jdpostct.c jdsample.c jdtrans.c jerror.c jfdctflt.c \
jfdctfst.c jfdctint.c jidctflt.c jidctfst.c jidctint.c jquant1.c \
jquant2.c jutils.c jmemmgr.c
# the assembler is only for the ARM version, don't break the Linux sim
ifneq ($(TARGET_ARCH),arm)
ANDROID_JPEG_NO_ASSEMBLER := true
endif
LOCAL_MODULE:= libjpeg
include $(BUILD_STATIC_LIBRARY)
注意上面LOCAL_SRC_FILES的所有文件從jpeg-8d目錄下的Makefile中拷貝而來,不是自己敲上去的,這樣可以保證不會又問題。
5.8 ndk-build編譯
root@ubuntu:~/workspace/AndroidJpeg8D/jni# ndk-build
Compile arm : jpeg <= jaricom.c
Compile arm : jpeg <= jcapimin.c
Compile arm : jpeg <= jcapistd.c
Compile arm : jpeg <= jcarith.c
Compile arm : jpeg <= jccoefct.c
Compile arm : jpeg <= jccolor.c
Compile arm : jpeg <= jcdctmgr.c
Compile arm : jpeg <= jchuff.c
Compile arm : jpeg <= jcinit.c
Compile arm : jpeg <= jcmainct.c
Compile arm : jpeg <= jcmarker.c
Compile arm : jpeg <= jcmaster.c
Compile arm : jpeg <= jcomapi.c
Compile arm : jpeg <= jcparam.c
Compile arm : jpeg <= jcprepct.c
Compile arm : jpeg <= jcsample.c
Compile arm : jpeg <= jctrans.c
Compile arm : jpeg <= jdapimin.c
Compile arm : jpeg <= jdapistd.c
Compile arm : jpeg <= jdarith.c
Compile arm : jpeg <= jdatadst.c
Compile arm : jpeg <= jdatasrc.c
Compile arm : jpeg <= jdcoefct.c
Compile arm : jpeg <= jdcolor.c
Compile arm : jpeg <= jddctmgr.c
Compile arm : jpeg <= jdhuff.c
Compile arm : jpeg <= jdinput.c
Compile arm : jpeg <= jdmainct.c
Compile arm : jpeg <= jdmarker.c
Compile arm : jpeg <= jdmaster.c
Compile arm : jpeg <= jdmerge.c
Compile arm : jpeg <= jdpostct.c
Compile arm : jpeg <= jdsample.c
Compile arm : jpeg <= jdtrans.c
Compile arm : jpeg <= jerror.c
Compile arm : jpeg <= jfdctflt.c
Compile arm : jpeg <= jfdctfst.c
Compile arm : jpeg <= jfdctint.c
Compile arm : jpeg <= jidctflt.c
Compile arm : jpeg <= jidctfst.c
Compile arm : jpeg <= jidctint.c
Compile arm : jpeg <= jquant1.c
Compile arm : jpeg <= jquant2.c
Compile arm : jpeg <= jutils.c
Compile arm : jpeg <= jmemmgr.c
StaticLibrary : libjpeg.a
SharedLibrary : libjpegtest.so
Install : libjpegtest.so => libs/armeabi/libjpegtest.so
5.9 執行
5.10 可以使用libjpeg庫了
6 C++編寫JNI的不同之處
在Android自身的調用中,大都采用這種方式。
7 后記
很早就想寫這篇文章,礙於一些事情,耽擱了很久,另外,本文也僅僅是涵蓋了一些基本和簡單的方面。很多深入的地方,還有待繼續探討,如果有什么更好的建議,歡迎提出。本文很多知識也是來自網絡整理,這里需要特別感謝simon兄的文章。另外要說明的就是,在我們的ndk目錄里面有一些例子,大家可以參考。本文對應的目錄為/opt/android-ndk-r7b/samples
參考文章:
1 http://my.unix-center.net/~Simon_fu/?p=833
2 http://my.unix-center.net/~Simon_fu/?p=836
3 http://my.unix-center.net/~Simon_fu/?p=849
4 http://my.unix-center.net/~Simon_fu/?p=856
5 http://my.unix-center.net/~Simon_fu/?p=359
6 http://my.unix-center.net/~Simon_fu/?page_id=724
7 http://stackoverflow.com/questions/4273168/javah-not-able-to-find-android-classes
8 http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javah.html
9 http://blog.csdn.net/brince101/article/details/6736105
10 http://hi.baidu.com/beibei245/blog/item/ffb709efc7877622acafd5da.html
11 http://provista.iteye.com/blog/839703
12 http://android.wooyd.org/JNIExample/
13 http://hi.baidu.com/zhlg_hzh/blog/item/f0d782081f2f45d963d986f5.html
-END-
本文同發地址:http://doandroid.info/2012/03/19/jni_and_android_jni_details/
歡迎轉載,注明出處。