标签:android ndk native jni javah classpath
之前一直有接触源码里面的JNI体系,知道个大概,只管调进了哪个C/C++的接口,现在记录学习下。
撰写不易,转载请注明出处:
http://blog.csdn.net/jscese/article/details/39645485
NDK - Native Development Kit ,类似SDK性质,可以看作为一个编译工具的集合,
在android开发中常用于将C/C++代码打包编译成android 应用程序能够加载使用的模块,像动态静态库 .a ,.so.
JNI - Java Native Interface , android的应用层都是java写的,都是交给dalvik进行转码成二进制再运行的,在执行效率上远低与C/C++程序,
所以就有了JNI机制,通过JNI 我们可以把一些复杂讲究效率的操作用C/C++语言来实现,让 java层来调用执行,提高效率!JNI 有一套自己的代码写法。
我们一般使用NDK编译的动态库来为JNI服务的。
可以到google上去下载NDK包,也可在http://www.androiddevtools.cn/中选择下载,下载样式类似 android-ndk-r9d-linux-x86_64.tar.bz2 ,
r9 代表版本,下载之后 解压到想安装的位置,ubuntu下 直接解压之后,配置环境变量即可,我的如下:
#set android NDK environment export NDK_HOME=/usr/local/android-ndk-r9d export PATH=$PATH:$NDK_HOME
source 之后,可在终端运行 ndk-build,出现如下:
jscese@jscese-H61M-S2P:~$ ndk-build Android NDK: Could not find application project directory ! Android NDK: Please define the NDK_PROJECT_PATH variable to point to it. /usr/local/android-ndk-r9d/build/core/build-local.mk:148: *** Android NDK: Aborting . Stop.
java本地的接口,可以用C 写 也可以用C++ ,只是会有一些细微的差别,在C 或者 C++ 本地接口函数中是有 JNIEnv *env 这个指针参数的,
C 调用;(*env)->
C++ 调用: env->
因为 jni.h 定义的不同,
还有就是C++ 的源文件是需要引入对应的头文件的。
从我写的一个例子来分析记录,一个 实现加减法的apk,算术操作放到C程序中执行。
eclipse 中新建 android工程,主java 文件:
package com.jscese.test;
import android.R.integer;
import android.app.Activity;
import android.os.Bundle;
import android.os.RemoteException;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
public class JNI extends Activity implements OnClickListener {
private static final String LOG_TAG = "JSCESE_JNI";
/** Called when the activity is first created. */
static {
// The runtime will add "lib" on the front and ".o" on the end of
// the name supplied to loadLibrary.
System.loadLibrary("jscesejni");
}
private EditText valueText1, valueText2 = null;
private Button addButton = null;
private Button subButton = null;
private Button clearButton = null;
private TextView vlaueText = null;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
valueText1 = (EditText) findViewById(R.id.edit_value1);
valueText2 = (EditText) findViewById(R.id.edit_value2);
addButton = (Button) findViewById(R.id.button_add);
subButton = (Button) findViewById(R.id.button_sub);
clearButton = (Button) findViewById(R.id.button_clear);
vlaueText = (TextView) findViewById(R.id.value);
addButton.setOnClickListener(this);
subButton.setOnClickListener(this);
clearButton.setOnClickListener(this);
}
@Override
public void onClick(View v) {
String text1 = valueText1.getText().toString();
String text2 = valueText2.getText().toString();
if (v.equals(addButton)) {
int value = add(Integer.parseInt(text1), Integer.parseInt(text2));
vlaueText.setText(String.valueOf(value));
} else if (v.equals(subButton)) {
int value = sub(Integer.parseInt(text1), Integer.parseInt(text2));
vlaueText.setText(String.valueOf(value));
} else if (v.equals(clearButton)) {
String text = "";
valueText1.setText(text);
valueText2.setText(text);
vlaueText.setText(text);
}
}
public native int add(int a, int b);
public native int sub(int a, int b);
}很简单的一个apk 主Activity,需要注意的是这个apk启动的时候 会调用开头的 static中的 System.loadLibrary("jscesejni");
这就是加载C/C++的动态库了,动态库原型应该是 libjscesejni.so 这里只要写名字就好!
另外还定义了加减法的两个本地方法,以关键词 native 修饰,将在 libjscesejni.so中实现。
资源文件main.xml:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
android:weightSum="1" >
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:orientation="vertical" >
<EditText
android:id="@+id/edit_value1"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:hint="@string/hint" >
</EditText>
<EditText
android:id="@+id/edit_value2"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:hint="@string/hint" >
</EditText>
<TextView
android:id="@+id/value"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="10dp"
android:gravity="center_horizontal"
android:hint="@string/value"
android:textSize="25sp" />
</LinearLayout>
<LinearLayout
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_marginTop="20dp"
android:gravity="center"
android:orientation="horizontal" >
<Button
android:id="@+id/button_add"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add" >
</Button>
<Button
android:id="@+id/button_sub"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="@string/sub" >
</Button>
<Button
android:id="@+id/button_clear"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="20dp"
android:text="@string/clear" >
</Button>
</LinearLayout>
</LinearLayout>首先在eclipse 里面编译这个android工程,终端进入工程目录bin/classes下,这个目录往下就是编译的java文件的class文件,层次是从包名到类名
我这里就是 com/jscese/test/JNI.class 这是通过eclipse编译好的,也可以手动 javac JNI.java编译。
生成 jni 类型的 头文件:
需要用到 javah 命令,这个环节容易出错,因为路径参数等原因,贴出 javah 的help:
用法:javah [选项] <类> 其中 [选项] 包括: -help 输出此帮助消息并退出 -classpath <路径> 用于装入类的路径 -bootclasspath <路径> 用于装入引导类的路径 -d <目录> 输出目录 -o <文件> 输出文件(只能使用 -d 或 -o 中的一个) -jni 生成 JNI样式的头文件(默认) -version 输出版本信息 -verbose 启用详细输出 -force 始终写入输出文件 使用全限定名称指定 <类>(例 如,java.lang.Object)。
javah -classpath . -d ../../jni com.jscese.test.JNI
内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com_jscese_test_JNI */
#ifndef _Included_com_jscese_test_JNI
#define _Included_com_jscese_test_JNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: com_jscese_test_JNI
* Method: add
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_add
(JNIEnv *, jobject, jint, jint);
/*
* Class: com_jscese_test_JNI
* Method: sub
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_sub
(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif
JNIEXPORT JNICALL 关键字 代表这是JNI 调用的函数,
jint 代表返回值,int 整型的变形
函数名以Java开头然后是包名+类名+native方法名
我们的C/C++的实现必须根据这个头文件定义的来实现。
jni下新建一个 com_jscese_test_JNI.c 这个名字随意,写入内容:
#include <jni.h>
#include <assert.h>
JNIEXPORT jint JNICALL Java_com_jscese_test_Jscese_add
(JNIEnv *env, jobject thiz, jint a, jint b)
{
return (a+b);
}
JNIEXPORT jint JNICALL Java_com_jscese_test_Jscese_sub
(JNIEnv *env, jobject thiz, jint a, jint b)
{
return (a-b);
}同目录下新建Android.mk :
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE:= libjscesejni LOCAL_SRC_FILES:= com_jscese_test_JNI.c LOCAL_SHARED_LIBRARIES := libutils LOCAL_LDLIBS :=-llog # Also need the JNI headers. LOCAL_C_INCLUDES += $(JNI_H_INCLUDES) include $(BUILD_SHARED_LIBRARY)
接下来就是编译so 然后打包进apk 里面了,在Android.mk 目录下 使用 ndk-build 命令编译
正确如下:
Android NDK: WARNING: APP_PLATFORM android-17 is larger than android:minSdkVersion 8 in /home/jscese/product_code/Mstar_Android/android/JB4.2/jb4.2/device/mstar/common/apps/Jscese_Jni/AndroidManifest.xml [armeabi] Compile thumb : jscesejni <= com_jscese_test_JNI.c [armeabi] SharedLibrary : libjscesejni.so [armeabi] Install : libjscesejni.so => libs/armeabi/libjscesejni.so
避过上面的 javah 命令的执行,不参照生成的 jni 头文件来写 C/C++
com_jscese_test_JNI.c内容如下:
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <jni.h>
#include <assert.h>
#include<android/log.h>
#define TAG "JsceseDemo-jni"
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,TAG ,__VA_ARGS__) // 定义LOGW类型
int Jscese_add(JNIEnv *env, jobject thiz, jint a, jint b)
{
return (a+b);
}
int Jscese_sub(JNIEnv *env, jobject thiz, jint a, jint b)
{
return (a-b);
}
#define JNIREG_CLASS "com/jscese/test/JNI"
/**
* Table of methods associated with a single class.
*/
static JNINativeMethod gMethods[] = {
{ "add", "(II)I", (void*)Jscese_add },
{ "sub", "(II)I", (void*)Jscese_sub },
};
/*
* Register several native methods for one class.
*/
static int registerNativeMethods(JNIEnv* env, const char* className,
JNINativeMethod* gMethods, int numMethods)
{
LOGW("jscese test jni in registerNativeMethods 0 ");
jclass clazz;
clazz = (*env)->FindClass(env, className);
if (clazz == NULL) {
LOGW("clazz NULL 0 ");
return JNI_FALSE;
}
if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) {
LOGW("RegisterNatives error 0 ");
return JNI_FALSE;
}
return JNI_TRUE;
}
/*
* Register native methods for all classes we know about.
*/
static int registerNatives(JNIEnv* env)
{
LOGW("jscese test jni in registerNatives ");
if (!registerNativeMethods(env, JNIREG_CLASS, gMethods,
sizeof(gMethods) / sizeof(gMethods[0])))
return JNI_FALSE;
return JNI_TRUE;
}
jint JNI_OnLoad(JavaVM* vm, void* reserved)
{
JNIEnv* env = NULL;
jint result = -1;
if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_4) != JNI_OK) {
return -1;
}
assert(env != NULL);
if (!registerNatives(env)) {//注册
return -1;
}
/* success -- return valid version number */
result = JNI_VERSION_1_4;
return result;
}
JNI_OnLoad函数作为动态库的入口函数,所以很多初始化的操作都可以在这里,返回JNI 版本,目前基本都是1.4
因为不采用标准方法 的头文件形式的函数命名,所以需要另外注册映射关系,将java本地的接口函数跟 C/C++中的实现函数一一对应上。
可以看到 gMethods 数组中的对应关系,中间的为参数说明。
引入了 log.h 定义了LOGW 所以需要在Android.mk 中添加 include : LOCAL_LDLIBS :=-llog
以下为网上对类型的一些资料
参数类型的转换和表述方法如下:
| Java类型 | 本地类型 | 描述 |
| boolean | jboolean | C/C++8位整型 |
| byte | jbyte | C/C++带符号的8位整型 |
| char | jchar | C/C++无符号的16位整型 |
| short | jshort | C/C++带符号的16位整型 |
| int | jint | C/C++带符号的32位整型 |
| long | jlong | C/C++带符号的64位整型e |
| float | jfloat | C/C++32位浮点型 |
| double | jdouble | C/C++64位浮点型 |
| Object | jobject | 任何Java对象,或者没有对应java类型的对象 |
| Class | jclass | Class对象 |
| String | jstring | 字符串对象 |
| Object[] | jobjectArray | 任何对象的数组 |
| boolean[] | jbooleanArray | 布尔型数组 |
| byte[] | jbyteArray | 比特型数组 |
| char[] | jcharArray | 字符型数组 |
| short[] | jshortArray | 短整型数组 |
| int[] | jintArray | 整型数组 |
| long[] | jlongArray | 长整型数组 |
| float[] | jfloatArray | 浮点型数组 |
| double[] | jdoubleArray | 双浮点型数组 |
JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。
| 函数 | Java数组类型 | 本地类型 |
| GetBooleanArrayElements | jbooleanArray | jboolean |
| GetByteArrayElements | jbyteArray | jbyte |
| GetCharArrayElements | jcharArray | jchar |
| GetShortArrayElements | jshortArray | jshort |
| GetIntArrayElements | jintArray | jint |
| GetLongArrayElements | jlongArray | jlong |
| GetFloatArrayElements | jfloatArray | jfloat |
| GetDoubleArrayElements | jdoubleArray | jdouble |
※ JNI数组存取函数
JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数
| 函数 | 描述 |
| GetFieldID | 得到一个实例的域的ID |
| GetStaticFieldID | 得到一个静态的域的ID |
| GetMethodID | 得到一个实例的方法的ID |
| GetStaticMethodID | 得到一个静态方法的ID |
※ 域和方法的函数
| Java 类型 | 符号 |
| boolean | Z |
| byte | B |
| char | C |
| short | S |
| int | I |
| long | L |
| float | F |
| double | D |
| void | V |
| objects对象 | Lfully-qualified-class-name;L类名 |
| Arrays数组 | [array-type [数组类型 |
| methods方法 | (argument-types)return-type(参数类型)返回类型 |
※ 确定域和方法的符号
标签:android ndk native jni javah classpath
原文地址:http://blog.csdn.net/jscese/article/details/39645485