JNI及Android JNI 开发基本知识和具体操作步骤

转自:http://doandroid.info/jni_and_android_jni_details/

目录:
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编程。当然,现在市面上的机器都满足。
其基本的使用方式为:

1
2
3
4
5
6
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
/* 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文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//一般都是这行
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;
}

jbyteArray 转 char *

char * data = ( char *)env->GetByteArrayElements(strIn, 0 );

更多的请参考原文,本文仅摘录部分用的比较多的。

4 Android中一个简单的例子

4.1 使用Eclipse创建一个HelloWorld的Activity的程序。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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文件

1
2
3
4
5
6
7
8
9
10
11
12
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 结果

1
2
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加载函数代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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的不同之处

具体可以参考Simon的文章这篇

在Android自身的调用中,大都采用这种方式。

7 后记

很早就想写这篇文章,碍于一些事情,耽搁了很久,另外,本文也仅仅是涵盖了一些基本和简单的方面。很多深入的地方,还有待继续探讨,如果有什么更好的建议,欢迎提出。本文很多知识也是来自网络整理,这里需要特别感谢simon兄的文章。另外要说明的就是,在我们的ndk目录里面有一些例子,大家可以参考。本文对应的目录为/opt/android-ndk-r7b/samples


  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值