在android应用开发过程中,从Java调用C/C++函数主要经历以下几个步骤:
1. 必须在java代码中声明本地方法。
2.需要实现java本地接口(JNI)粘合层。
3.必须创建Android makefile文件。
4.必须用C/C++实现本地方法。
5.必须编译本地库。
6.必须加载本地库。
android-NDK为我们提供了一整套的机制来完成这些步骤。我采用的是NDKr8版本,完全不用Cygwin(貌似从NDKr7开始就可以完全不用Cygwin了)。
前篇:新建android工程MyJNITest,步骤省略,示例代码如下:
代码清单1:activity_main.xml
/>
代码清单2:MainActivity.java
package com.example.myjnitest;
import android.app.Activity;
import android.os.Bundle;
import android.view.View;
import android.widget.EditText;
public class MainActivity extends Activity {
private EditText additionResult,multiplicationResult;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
additionResult=(EditText)findViewById(R.id.addtionResult);
multiplicationResult=(EditText)findViewById(R.id.multiplicationResult);
}
public void calculate(View view)
{
int id=view.getId();
switch (id) {
case R.id.addition:
long addResult=addition(20, 70);
additionResult.setText("20+70="+addResult);
break;
case R.id.multiplication:
long mulResult=multiplication(20, 70);
multiplicationResult.setText("20*70="+mulResult);
break;
}
}
//本地方法,由java调用
private native long addition(int i,int j);
public static native long multiplication(int a,int b);
}
示例代码2中有两个方法,addition()和multiplication()方法。这就是java中声明的本地方法,只是比普通的方法多了native关键字(native关键字不可省略),这些方法并没有具体的实现。从调用者角度来看,一旦声明了本地方法就可以在java代码中调用,会顺利通过编译。不过,如果应用运行并调用这两个本地方法的话,就会蹦出IllegalStateException异常,这很好理解,因为本地方法还没有具体实现。
本地方法可以是公共的,也可以是受保护的、私有的或包作用域的,如同任何其他java方法一样。同样,本地方法不一定必须是静态的,也不是必须使用基本类型。
2.实现JNI粘合层:
java通过JNI框架调用库里的C/C++方法。可以利用JDK(java开发工具包)建立JNI粘合层。首先,需要在C/C++头文件中声明要实现的函数。建议使用JDK的javah工具自动生成头文件。
在cmd中,进入MyJNITest工程根目录,例如我的工程根目录为 E:\workSpace\MyJNITest,然后在此目录下使用javah命令:
将本地方法放在了MainActivity.java类中,使用javah命令:
javah -classpath ./bin/classes -d jni com.example.myjnitest.MainActivity
会蹦出如此错误,找不到android.app.Activity的类文件。跳出该错误,猜想可能得原因是由于没有找到android.jar包,可能得在某个参数中指定下路径,在此不纠结其原因,以后的文章中再详细解释吧。
避免该问题有两种解决办法,仅是避免而已:
方法一: 在cmd中进入工程的src目录下,执行javah命令:
javah -d jni com.example.myjnitest.MainActivity
这样会在src目录下成功生成一个jni文件夹,头文件就在该jni文件夹中。
方法二:不要将本地方法写在MainActivity.java类中,而是单独放在一个普通类中
代码清单3:NativeMethodsClass.java
package com.example.myjnitest;
public class NativeMethodsClass {
//本地方法,由java调用
private native long addition(int i,int j);
public static native long multiplication(int a,int b);
}
再执行javah命令:
javah -classpath ./bin/classes -d jni com.example.myjnitest.NativeMethodsClass
就会在工程的根目录下生成jni文件夹,该文件夹中包含头文件。
采用方法一,生成头文件,
代码清单4:com_example_myjnitest_MainActivity.h
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* Header for class com_example_myjnitest_MainActivity */
#ifndef _Included_com_example_myjnitest_MainActivity
#define _Included_com_example_myjnitest_MainActivity
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_example_myjnitest_MainActivity
* Method: addition
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_example_myjnitest_MainActivity
* Method: multiplication
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
(JNIEnv *, jclass, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
不要
直接修改该文件,若需要添加本地方法或者其他操作的话,可以使用javah命令重新生成一次。
C头文件本身做不了任何事情。需要创建com_example_myjnitest_MainActivity.c文件实现其中的函数。
代码清单5:com_example_myjnitest_MainActivity.c
#include "com_example_myjnitest_MainActivity.h"
/*
* Class: com_example_myjnitest_MainActivity
* Method: addition
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
(JNIEnv * env, jobject clazz, jint a, jint b)
{
return 0;
}
/*
* Class: com_example_myjnitest_MainActivity
* Method: multiplication
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
(JNIEnv * env, jobject clazz, jint a, jint b);
{
return 0;
}
JNI层的所有函数有一个共同点:他们的第一个参数都是JNIEnv*类型(JNIEnv对象的指针)。JNIEnv的对象是JNI环境本身,使用它可以与虚拟机交互。第二个参数,当方法被声明为静态时为jclass类型,否则为jobject类型。
在此之前,请先将src目录下的jni文件夹迁移到MyJNITest工程跟目录下,然后在该jni目录下创建mk文件。否则,后面使用NDK编译so库的时候,会编译不成功。
代码清单6:Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := myjnitest
LOCAL_SRC_FILES := com_example_myjnitest_MainActivity.c
include $(BUILD_SHARED_LIBRARY)
4. 实现本地函数
一般情况下,需要新建C/C++源文件以及相应的头文件来完成本地函数的具体实现,然后从JNI粘合层调用函数,而不是直接在com_example_myjnitest_MainActivity.c文件中实现,若直接将实现代码在com_example_myjnitest_MainActivity.c文件中实现,虽然在技术上可行,但不推荐这么做。这样会将粘合层与本地函数实现捆绑在一起,使得本地函数代码难以在非java程序中重要,如,可能需要在IOS程序中重用相同的C/C++头文件和实现。
由于该工程代码功能比较简单,因此没有将方法实现放在单独的C源文件中,而是在粘合层进行了实现。
代码清单7:com_example_myjnitest_MainActivity.c
#include "com_example_myjnitest_MainActivity.h"
/*
* Class: com_example_myjnitest_MainActivity
* Method: addition
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_addition
(JNIEnv * env, jobject clazz, jint a, jint b)
{
return a+b;
}
/*
* Class: com_example_myjnitest_MainActivity
* Method: multiplication
* Signature: (II)J
*/
JNIEXPORT jlong JNICALL Java_com_example_myjnitest_MainActivity_multiplication
(JNIEnv * env, jobject clazz, jint a, jint b);
{
return a*b;
}
使用Eclipse工具与android NDK生成so动态库。注意,需要将NDK的路径添加到系统环境变量path中。
新建并配置一个新的Builder
1) 点击Project->Properties->Builders->New,新建立一个Builder。在弹出的对话框上面点击Program,点击OK;
2) 在弹出的对话框【Edit Configuration】中,配置选项卡【Main】:
Location中需要填入nkd-build.cmd的路径(NDK安装目录下)。
WorkingDiretcoty中需要填入MyJNITest的工程根目录。
3) 在【EditConfiguration】中,配置选项卡【Refresh】:
勾选“Refresh resources upon completion”,
勾选“The entire workspace”,
勾选“Recuresively include sub-folders”。
4)在【EditConfiguration】中,配置选项卡【Build Options】:
勾选“After a “Clean””,
勾选“During manual builds”,
勾选“During auto builds”,
勾选“Specify working set of relevant resources”。
点击“Specify Resources…”勾选MyJNITest工程的“jni“目录,Finish!
保存设置,点击OK。
由于我们勾选了 “During auto builds” ,所以在工程有所改变的时候,so文件便会自动编译,正确生成以后就能在工程目录下发现多了两个文件夹,文件夹libs\armeabi目录下生成了一个叫 libhello-jni.so 的文件。至此,使用NDK生成so文件的工作就完成了。6. 加载本地库
在静态初始化块中调用System.loadLibrary加载本地库,是加载库的最简单方法。静态代码块中的代码是在虚拟机加载类时执行的,此时还没调用过任何方法。静态初始化块可能会显著增加开销,这是在用某些特定函数时需要避免的。
代码清单8:加载so库
//加载本地库
static
{
System.loadLibrary("myjnitest");
}
注意加载本地库时,本地库名称不能带前缀lib,也不能带后缀.so,否则都将蹦出错误信息。
可以运行程序了,试试看吧。
补充: 关于上面的javah命令执行, 本地代码放在MainActivity.java中,就蹦出找不到android.app.Activity类文件的错误解决:
之所以跳出该错误信息,是因为MainActivity继承了Activity,是引导类的问题,就是javah -help 中所提到的“-bootclasspath”的作用了。在cmd中,进入到MyJNITest工程根目录下,执行javah命令:
E:\workSpace\MyJNITest\src>javah -classpath bin/classes -bootclasspath E:\android-sdk-windows\android-sdk-windows\platforms\android-8\android.jar -d jni com.example.myjnitest.MainActivity
好了,在根目录下成功生成jni目录和头文件了,省了需要在src目录下往外复制啦。
源码工程下载:点击打开链接 http://download.csdn.net/detail/u010978228/6969263