Android JNI编程指南及模拟器配置问题

目前正在学习JNI,从一开始的一无所知,到现在的略知一二,走了不少弯路,为了让有兴趣的同行少走弯路,下面把我的经验记录下来,给大家一个参考:
1、先从SimpleJNI说起:
在Android SDK的源码目录下./development/samples/SimpleJNI可以找到一个最简单的JNI例子,其文件树如下
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
.
|-- AndroidManifest.xml
|-- Android.mk
|-- jni
| |-- Android.mk
| `--native.cpp
`-- src
`-- com
`-- example
`-- android
`-- simplejni
`-- SimpleJNI.java


该例子的主要思想是用JNI生成一个本地库libsimplejni.so,实现一个add(a,b)功能,然后通过SimpleJNI.java调用该库输出显示信息

此例子的Android.mk文件如下:

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
# This makefile shows how to build a shared library and an activity that
# bundles the shared library and calls it using JNI.
 
TOP_LOCAL_PATH:= $(call my-dir)
 
# Build activity
LOCAL_PATH:= $(TOP_LOCAL_PATH)
include $(CLEAR_VARS)
 
LOCAL_MODULE_TAGS := samples
LOCAL_SRC_FILES := $(call all-subdir-java-files) #查找当前目录下所有的java文件
LOCAL_PACKAGE_NAME := SimpleJNI                #编译一个java包:SimpleJNI.apk
LOCAL_JNI_SHARED_LIBRARIES := libsimplejni #编译一个动态库:libsimplejni.so
LOCAL_PROGUARD_ENABLED := disabled
 
include $(BUILD_PACKAGE)
 
# ============================================================
 
# Also build all of the sub-targets underthisone: the shared library.
include $(call all-makefiles-under,$(LOCAL_PATH))


在Android SDK的根目录下面运行终端,输入如下编译命令:
?
代码片段,双击复制
01
make SimpleJNI libsimplejni


将得到如下两个文件:
?
代码片段,双击复制
01
02
out/target/product/sdkDemo/system/app/SimpleJNI.apk
out/target/product/sdkDemo/system/lib/libsimplejni.so


JNI代码的目录为jni/vative.cpp,其内容如下:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
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
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
View Code
#define LOG_TAG"simplejni native.cpp"
#include <utils/Log.h>
 
#include <stdio.h>
 
#include"jni.h"//JNI相关的头文件
   
  staticjint add(JNIEnv *env, jobject thiz, jint a, jint b) {   /*定义Java方法add(),具有两个整数类型的参数和一个整数类型的返回值,由本地代码add函数实现*/
int result = a + b;
     LOGI("%d + %d = %d", a, b, result);
return result;
}
 
staticconstchar*classPathName ="com/example/android/simplejni/Native";   //类的路径名
 
static JNINativeMethod methods[ ] = {     //本地方法列表
   {"add", "(II)I", (void*)add },
};
 
/*使用JNI的核心是JNINativeMethod结构体,这个结构体在jni.h中定义
 
typedef struct {
 
        const char* name;           /*JNI函数的名称*/
 
constchar* signature;     /*描述JNI函数的参数和返回值*/
 
void*           fnPtr;            /*JNI函数对应的C(C++)语言函数指针*/
 
}JNINativeMethod;
 
关于参数和返回值的类型如下表:
 
Java 类型 JNI类型 对应字母
Java 布尔类型(boolean) jboolean(8位无符号) Z
Java字节(byte) jbyte(8位有符号) B
Java字符(char) jchar(16位无符号) C
Java短整型(short) jshort(16位有符号) S
Java整型(int) jint(32位有符号) I
Java长整型(long) jlong(64位有符号) J
Java单精度浮点(folat) jfloat(IEEE754,32位) F
Java双精度浮点(double) jdouble(IEEE754,64位) D
Java对象 jobject L
Java无返回值 void V
 
 
该例子里"(II)I代表的是,有两个整型参数和一个整"
 
*/
/*
  * Register several native methods for one class.
 
*/
staticint registerNativeMethods(JNIEnv* env, constchar* className,
     JNINativeMethod* gMethods, int numMethods)
{
     jclass clazz;
 
     clazz = env->FindClass(className);
if (clazz == NULL) {
         LOGE("Native registration unable to find class '%s'", className);
return JNI_FALSE;
     }
if (env->RegisterNatives(clazz, gMethods, numMethods) <0) {
         LOGE("RegisterNatives failed for '%s'", className);
return JNI_FALSE;
     }
 
return JNI_TRUE;
}
 
/*
  * Register native methods for all classes we know about.
  *以下是注册JNI方法,它又调用registerNativeMethods()函数
  * returns JNI_TRUE on success.
*/
staticint 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.
 
*在加载库的过程中调用registerNatives()函数实现方法注册
*/
   
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;
    
     LOGI("JNI_OnLoad");
 
if(vm->GetEnv(&uenv.venv, JNI_VERSION_1_4) != JNI_OK) {
         LOGE("ERROR: GetEnv failed");
gotobail;
     }
     env = uenv.env;
 
if(registerNatives(env) != JNI_TRUE) {
         LOGE("ERROR: registerNatives failed");
gotobail;
     }
    
     result = JNI_VERSION_1_4;
    
bail:
returnresult;
}


编译此JNI代码所需要的Android.mk如下:
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
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
# This makefile supplies the rulesforbuilding a library of JNI codefor
#use by our example of how to bundleashared library with an APK.
 
LOCAL_PATH:= $(call my-dir)
include $(CLEAR_VARS)
 
LOCAL_MODULE_TAGS := samples
 
# This is the target being built.
LOCAL_MODULE:= libsimplejni
 
# All of the source files that we will compile.
LOCAL_SRC_FILES:= \
   native.cpp
 
# All of the shared libraries we link against.
LOCAL_SHARED_LIBRARIES := \
  libutils
 
# Nostaticlibraries.
LOCAL_STATIC_LIBRARIES :=
 
# Also need the JNI headers.
LOCAL_C_INCLUDES += \
  $(JNI_H_INCLUDE)
 
# No special compiler flags.
LOCAL_CFLAGS +=
 
# Don't prelinkthislibrary.  For more efficient code, you may want
# to addthislibrary to the prelink map and setthis totrue. However,
# it's difficult todothisforapplications that are not supplied as
# part of a system image.
 
LOCAL_PRELINK_MODULE :=false     #不需要重新链接此库
 
include $(BUILD_SHARED_LIBRARY)


应用部分的代码目录为/src/com/example/android/simplejni/SimpleJNI.java,在这个类中Native类是对本地方法的封装,内容如下:

?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
classNative {         
  //定义Java的封装类
static{
// The runtime will add "lib" on the front and ".o" on the end of
// the name supplied to loadLibrary.
         System.loadLibrary("simplejni");           //加载本地库
     }
 
staticnativeint add(inta,intb);              //调用本地方法
}


在这个类中调用的过程如下: 
?
代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
publicclass SimpleJNIextendsActivity {
/** Called when the activity is first created. */
     @Override
publicvoid onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
         TextView tv =newTextView(this);                                    //建立一个UI中的类TextView
intsum = Native.add(2,3);                                                //通过封装类调用本地方法
         tv.setText("2 + 3 = "+ Integer.toString(sum));                  //设置显示内容
         setContentView(tv);
     }
}


通常JNI的使用自下而上有4个层次:本地库、JNI库、声明本地接口的Java类,Java调用者。在本例中,本地库和JNI库合二为一,声明本地接口的Java类和Java调用者合二为一。

2、将以上所得到的libsimplejni.so与SimpleJNI.apk两个文件从Ubuntu中拷贝出来,放置在windows C盘的根目录下,

运行Android模拟器

在windows的“运行”中输入cmd打开windows的命令窗口

输入cd c:\命令切换到C盘根目录下

然后输入adb version确实系统是否已经安装了adb工具,如果已经安装将得到如下内容

Android Debug Bridge version 1.0.26

如果没有安装,可以到\android-sdk-windows\tools目录下将adb.exe和AdbWinApi.dll两个文件拷贝到windows C盘的system32目录下即可

然后输入如下命令将libsamplejni.so拷贝到模拟器的system/lib目录下



?
代码片段,双击复制
01
adb push libsamplejni.so /system/lib


再输入如下命令把SampleJNI.apk拷贝到模拟器的system/app目录下
?
代码片段,双击复制
01
adb push SampleJNI.apk


上面可能遇到的问题解决办法:

(1)、提示failed to copy 'libsimplejni.so'to'/system/lib/libsimplejni.so':Read-only file system
这是因为当前状态下,此目录是一个只读目录,输入如下命令就可以获得写的权限

?
代码片段,双击复制
01
adb remount


(2)、提示failed to copy 'libsimplejni.so'to'/system/lib/libsimplejni.so':Out of memory

这是因为建议模拟器的时候默认的系统memory太小了,关闭当前模拟器,输入如下命令就可以解决此问题
?
代码片段,双击复制
01
emulator -avd Android2.2-partition-size128


说明:其中Android2.2是我当前所建AVD的名称,128代表的是设置的系统memory的大小,输入此命令之后将会自动打开模拟器

一切正常后,输入相应命令后将得到:
?
代码片段,双击复制
01
02
03
04
05
C:\>adb push libsimplejni.so /system/lib
40KB/s (5188bytes in0.125s)
 
C:\>adb push SimpleJNI.apk /system/app
52KB/s (5064bytes in0.093s)


在模拟器中,我们将看到已经安装好了的Simple JNI运行它之后

将得到我们所期望的结果


2+3=5

你可能感兴趣的:(Android JNI编程指南及模拟器配置问题)