Android与JNI(一)
Android与JNI(一)
软件版本:
ubuntu10.04
java version "1.6.0_30-ea"
eclipse
android-ndk-r5b
目录:
1. JNI 开发的基本步骤
2. 创建一个 android 工程
3. 生成 jni 的头文件
4. 编写 c/c++ 代码
5. 编译生成动态库
6. 测试
7. 参考资料
1. JNI 开发的基本步骤
在 JAVA 程序中调用 c/c++ 函数的方法一般是:
1、创建一个类(HelloWorld.java)或者在原来的类中声明本地方法。
2、使用 javac 编译源文件 HollowWorld.java,生成 HelloWorld.class。
3、使用 javah –jni 来生成头文件(HelloWorld.h),这个头文件里面包含了本地方法的函数原型。
4、编写 c/c++ 代码(HelloWorld.c)实现头文件中的函数原型。
5、将 HelloWorld.c 编译成一个动态库,生成 Hello-World.dll 或者 libHello-World.so。
6、使用 java 命令运行 HelloWorld 程序,类文件 HelloWorld.class 和本地库(HelloWorld.dll 或者 libHelloWorld.so)在运行时被加载。
如图所示:
2. 创建一个 android 工程
创建一个新的 android 工程 HelloJNI 便于演示:
1 package com.example.hellojni; 2 3 import android.os.Bundle; 4 import android.app.Activity; 5 import android.view.Menu; 6 import android.view.MenuItem; 7 import android.widget.TextView; 8 import android.support.v4.app.NavUtils; 9 10 public class HelloJNI extends Activity { 11 12 @Override 13 public void onCreate(Bundle savedInstanceState) { 14 super.onCreate(savedInstanceState); 15 setContentView(R.layout.hello_jni); 16 getActionBar().setDisplayHomeAsUpEnabled(true); 17 18 /* Create a TextView and set its content. 19 * the text is retrieved by calling a native 20 * function. 21 */ 22 TextView tv = new TextView(this); 23 tv.setText( stringFromJNI() ); 24 setContentView(tv); 25 } 26 27 @Override 28 public boolean onCreateOptionsMenu(Menu menu) { 29 getMenuInflater().inflate(R.menu.hello_jni, menu); 30 return true; 31 } 32 33 34 @Override 35 public boolean onOptionsItemSelected(MenuItem item) { 36 switch (item.getItemId()) { 37 case android.R.id.home: 38 NavUtils.navigateUpFromSameTask(this); 39 return true; 40 } 41 return super.onOptionsItemSelected(item); 42 } 43 44 /* A native method that is implemented by the 45 * 'HelloJNI' native library, which is packaged 46 * with this application. 47 */ 48 public native String stringFromJNI(); 49 50 /* This is another native method declaration that is *not* 51 * implemented by 'HelloJNI'. This is simply to show that 52 * you can declare as many native methods in your Java code 53 * as you want, their implementation is searched in the 54 * currently loaded native libraries only the first time 55 * you call them. 56 * 57 * Trying to call this function will result in a 58 * java.lang.UnsatisfiedLinkError exception ! 59 */ 60 public native String unimplementedStringFromJNI(); 61 62 /* this is used to load the 'HelloJNI' library on application 63 * startup. The library has already been unpacked into 64 * /data/data/com.example.HelloJni/lib/libHelloJNI.so at 65 * installation time by the package manager. 66 */ 67 static { 68 System.loadLibrary("HelloJNI"); 69 } 70 }
代码很简单,主要是调用本地方法返回一个字符串,显示在屏幕上。有两点需要针对说明一下:
1 static { 2 System.loadLibrary("HelloJNI"); 3 }
上面这几行代码是用来加载动态库 libHelloJNI.so 。那么是在什么时候加载呢?当第一次使用到这个类的时候就会加载。
1 public native String stringFromJNI(); 2 public native String unimplementedStringFromJNI();
使用关键字 native 声明本地方法,表明这两个函数需要通过本地代码 c/c++ 实现。
通过 eclipse 编译代码,生成 .class 文件。为生成 jni 的头文件做好准备。
3. 生成 jni 的头文件
现在的问题是要怎么样实现 stringFromJNI 和 unimplementedStringFromJNI 这两个函数呢?这两个函数要怎么命名?直接用这两个名字行不行?要解决这些疑问,就要用到 javah 这个命令。
在你新建成的工程根目录下,键入以下命令:
$javah -classpath bin/classes -d jni com.example.hellojni.HelloJNI
先简单说一下这个命令有什么用。这个命令是用来生成与指定 class 想对应的本地方法的头文件。
-classpath:指定类的路径。
-d:输出目录名。
com.example.hellojni.HelloJNI:完整的类名。
命令的结果是在本地生成一个名为 jni 的目录,里面有一个名为 com_example_hellojni_HelloJNI.h 的头文件。这个文件就是我们所需要的头文件,他声明了两个函数。
$ls AndroidManifest.xml assets bin gen ic_launcher-web.png jni libs obj proguard-project.txt project.properties res src
1 /* DO NOT EDIT THIS FILE - it is machine generated */ 2 #include <jni.h> 3 /* Header for class com_example_hellojni_HelloJNI */ 4 5 #ifndef _Included_com_example_hellojni_HelloJNI 6 #define _Included_com_example_hellojni_HelloJNI 7 #ifdef __cplusplus 8 extern "C" { 9 #endif 10 /* 11 * Class: com_example_hellojni_HelloJNI 12 * Method: stringFromJNI 13 * Signature: ()Ljava/lang/String; 14 */ 15 JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJNI_stringFromJNI 16 (JNIEnv *, jobject); 17 18 /* 19 * Class: com_example_hellojni_HelloJNI 20 * Method: unimplementedStringFromJNI 21 * Signature: ()Ljava/lang/String; 22 */ 23 JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJNI_unimplementedStringFromJNI 24 (JNIEnv *, jobject); 25 26 #ifdef __cplusplus 27 } 28 #endif 29 #endif
上面代码中的 JNIEXPORT 和 JNICALL 是 jni 的宏,在 android 的 jni 中不需要,当然写上去也不会有错。
函数名比较长,不过是有规律的,按照:java_pacakege_class_method 形式来命名。调用 stringFromJNI() 就会执行 JNICALL Java_com_example_hellojni_HelloJNI_stringFromJNI() 。
还有一个地方需要注意一下,那就是第13行
Signature: ()Ljava/lang/String;
()表示函数的参数为空(这里为空是指除了JNIEnv *, jobject 这两个参数之外没有其他参数,JNIEnv* 和 jobject 是所有 jni 函数必有的两个参数,分别表示 jni 环境和对应的 java 类(或对象)本身);
Ljava/lang/String; 表示函数的返回值是 java 的 String 对象。
4. 编写 c/c++ 代码
按照 com_example_hellojni_HelloJNI.h 中声明的函数名,在 jni 目录下建立一个 HelloJNI.c 文件实现其函数体。
1 /* 2 * Copyright (C) 2009 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 #include <string.h> 18 #include <jni.h> 19 #include "com_example_hellojni_HelloJNI.h" 20 21 /* This is a trivial JNI example where we use a native method 22 * to return a new VM String. See the corresponding Java source 23 * file located at: 24 * 25 * apps/samples/hello-jni/project/src/com/example/HelloJni/HelloJni.java 26 */ 27 jstring Java_com_example_hellojni_HelloJNI_stringFromJNI(JNIEnv *env, jobject this) 28 { 29 return (*env)->NewStringUTF(env, "Hello from JNI !"); 30 }
这里只实现了 Java_com_example_hellojni_HelloJNI_stringFromJNI() ,函数很简单,返回一个字符串。但是由于函数定义中的返回值是 java 的 String 类,所以不能简单的返回一个字符串,需要通过 JNI 函数 NewStringUTF 在本地创建一个新的 java.lang.String 对象。这个新创建的字符串对象拥有一个与给定的 UTF-8 编码的 C 类型字符串内容相同的 Unicode 编码字符串。这里抛出了一个问题,就是说我们在写 JNI 的时候,有些数据类型是需要做转换的。
5. 编译生成动态库
如果你还没有下载 ndk 的话,请到这里来下载。ndk 有什么用?就是用来编译 jni 代码的。安装方法很简单,只要把 ndk-build 命令加入到环境变量 PATH 中就可以使用了。验证方法就是键入 ndk-build 命令后,不会出现 command not found 的字样。
在 jni 目录下创建一个名为 Android.mk 的文件,并输入以下内容:
1 # Copyright (C) 2009 The Android Open Source Project 2 # 3 # Licensed under the Apache License, Version 2.0 (the "License"); 4 # you may not use this file except in compliance with the License. 5 # You may obtain a copy of the License at 6 # 7 # http://www.apache.org/licenses/LICENSE-2.0 8 # 9 # Unless required by applicable law or agreed to in writing, software 10 # distributed under the License is distributed on an "AS IS" BASIS, 11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 # See the License for the specific language governing permissions and 13 # limitations under the License. 14 # 15 LOCAL_PATH := $(call my-dir) 16 17 include $(CLEAR_VARS) 18 19 LOCAL_MODULE := HelloJNI 20 LOCAL_SRC_FILES := HelloJNI.c 21 22 include $(BUILD_SHARED_LIBRARY)
然后在 jni 目录下输入 ndk-build 命令就可以编译了。
$ndk-build Compile thumb : HelloJNI <= HelloJNI.c SharedLibrary : libHelloJNI.so Install : libHelloJNI.so => libs/armeabi/libHelloJNI.so
将会在 HelloJNI/libs/armeabi 目录下生成一个名为 libHelloJNI.so 的动态库。编译生成 apk 时会把这个库一起打包。
6. 测试
7. 参考资料
[1]. Android: NDK编程入门笔记
[2]. JNI及Android JNI 开发基本知识和具体操作步骤
[3]. 如何在Android下使用JNI