Andorid——ubuntu下的 NDK / JNI

来源:互联网 发布:微信扫码群发营销软件 编辑:程序博客网 时间:2024/06/08 08:25

     之前一直有接触源码里面的JNI体系,知道个大概,只管调进了哪个C/C++的接口,现在记录学习下。

                  

                                                 撰写不易,转载请注明出处:http://blog.csdn.net/jscese/article/details/39645485


概念:

NDK - Native Development Kit ,类似SDK性质,可以看作为一个编译工具的集合,

在android开发中常用于将C/C++代码打包编译成android 应用程序能够加载使用的模块,像动态静态库 .a ,.so.

来自百科,NDK包括了
  • 从C / C++生成原生代码库所需要的工具和build files。
  • 将一致的原生库嵌入可以在Android设备上部署的应用程序包文件(application packages files ,即.apk文件)中。
  • 支持所有未来Android平台的一些列原生系统头文件和库

JNI - Java Native Interface , android的应用层都是java写的,都是交给dalvik进行转码成二进制再运行的,在执行效率上远低与C/C++程序,

所以就有了JNI机制,通过JNI 我们可以把一些复杂讲究效率的操作用C/C++语言来实现,让 java层来调用执行,提高效率!JNI 有一套自己的代码写法。

我们一般使用NDK编译的动态库来为JNI服务的。


NDK:

        可以到google上去下载NDK包,也可在http://www.androiddevtools.cn/中选择下载,下载样式类似android-ndk-r9d-linux-x86_64.tar.bz2

r9 代表版本,下载之后 解压到想安装的位置,ubuntu下 直接解压之后,配置环境变量即可,我的如下:

#set android NDK environmentexport NDK_HOME=/usr/local/android-ndk-r9dexport PATH=$PATH:$NDK_HOME

source 之后,可在终端运行 ndk-build,出现如下:

jscese@jscese-H61M-S2P:~$ ndk-buildAndroid 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.

就代表已经安装成功,可以用它进行编译了!


JNI:

      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;@Overridepublic 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);}@Overridepublic 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)。

很明了,进入到 bin/class目录 ,那么当前目录就是 classpath ,执行:

javah -classpath . -d ../../jni com.jscese.test.JNI

会在工程目录下的jni目录下生成 com_jscese_test_JNI.h  命令规则是 包名+类名

内容如下:

/* 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 __cplusplusextern "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

函数定义比较奇怪,刚开始接触肯定不适应,这就是JNI 机制的特殊写法了! 以 JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_add
  (JNIEnv *, jobject, jint, jint); 为例, 其中

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:= libjscesejniLOCAL_SRC_FILES:= com_jscese_test_JNI.cLOCAL_SHARED_LIBRARIES := \libutilsLOCAL_LDLIBS :=-llog# Also need the JNI headers.LOCAL_C_INCLUDES += \$(JNI_H_INCLUDES)include $(BUILD_SHARED_LIBRARY)

不加JNI_ONLoad函数,默认会认为JNI 版本为1.1 ,少了一些特性,就这个例子不影响,这样code 已经完成了,

接下来就是编译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

可以看到生成了 mk中定义的 libjscesejni.so 并且安装到了libs/armeabi 目录下,最后再次eclipse编译整个 工程,apk运行应该是OK 的。


常用办法:

    避过上面的 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类型本地类型描述booleanjbooleanC/C++8位整型bytejbyteC/C++带符号的8位整型charjcharC/C++无符号的16位整型shortjshortC/C++带符号的16位整型intjintC/C++带符号的32位整型longjlongC/C++带符号的64位整型efloatjfloatC/C++32位浮点型doublejdoubleC/C++64位浮点型Objectjobject任何Java对象,或者没有对应java类型的对象ClassjclassClass对象Stringjstring字符串对象Object[]jobjectArray任何对象的数组boolean[]jbooleanArray布尔型数组byte[]jbyteArray比特型数组char[]jcharArray字符型数组short[]jshortArray短整型数组int[]jintArray整型数组long[]jlongArray长整型数组float[]jfloatArray浮点型数组double[]jdoubleArray双浮点型数组                                          ※     JNI类型映射


JNI通过JNIEnv提供的操作Java数组的功能。它提供了两个函数:一个是操作java的简单型数组的,另一个是操作对象类型数组的。


函数Java数组类型本地类型GetBooleanArrayElementsjbooleanArrayjbooleanGetByteArrayElementsjbyteArrayjbyteGetCharArrayElementsjcharArrayjcharGetShortArrayElementsjshortArrayjshortGetIntArrayElementsjintArrayjintGetLongArrayElementsjlongArrayjlongGetFloatArrayElementsjfloatArrayjfloatGetDoubleArrayElementsjdoubleArrayjdouble

                                               ※   JNI数组存取函数



JNI提供的另外一个功能是在本地代码中使用Java对象。通过使用合适的JNI函数,你可以创建Java对象,get、set 静态(static)和实例(instance)的域,调用静态(static)和实例(instance)函数。JNI通过ID识别域和方法,一个域或方法的ID是任何处理域和方法的函数的必须参数


函数描述GetFieldID得到一个实例的域的IDGetStaticFieldID得到一个静态的域的IDGetMethodID得到一个实例的方法的IDGetStaticMethodID得到一个静态方法的ID

                                            ※   域和方法的函数



Java 类型符号booleanZbyteBcharCshortSintIlongLfloatFdoubleDvoidVobjects对象Lfully-qualified-class-name;L类名Arrays数组[array-type [数组类型methods方法(argument-types)return-type(参数类型)返回类型

                                           ※   确定域和方法的符号






7 0
原创粉丝点击