Andorid——ubuntu下的 NDK / JNI
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Andorid——ubuntu下的 NDK / JNI相关的知识,希望对你有一定的参考价值。
之前一直有接触源代码里面的JNI体系,知道个大概,仅仅管调进了哪个C/C++的接口,如今记录学习下。
撰写不易,转载请注明出处:
http://blog.csdn.net/jscese/article/details/39645485
概念:
NDK - Native Development Kit ,类似SDK性质,能够看作为一个编译工具的集合。
在android开发中经常使用于将C/C++代码打包编译成android 应用程序可以载入使用的模块。像动态静态库 .a ,.so.
-
从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 environment export NDK_HOME=/usr/local/android-ndk-r9d export PATH=$PATH:$NDK_HOME
source 之后,可在终端执行 ndk-build,出现例如以下:
[email protected]:~$ 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.
就代表已经成功安装。能够用它进行编译了!
JNI:
java本地的接口,能够用C 写 也能够用C++ 。仅仅是会有一些细微的区别,在C 或者 C++ 本地接口函数中是有 JNIEnv *env 这个指针參数的。
C 调用;(*env)->
C++ 调用: env->
由于 jni.h 定义的不同,
还有就是C++ 的源文件是须要引入相应的头文件的。
从我写的一个样例来分析记录,一个 实现加减法的apk。算术操作放到C程序中运行。
eclipse 中新建 androidproject,主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); }
非常easy的一个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 里面编译这个androidproject。终端进入project文件夹bin/classes下。这个文件夹往下就是编译的java文件的class文件。层次是从包名到类名
我这里就是 com/jscese/test/JNI.class 这是通过eclipse编译好的,也能够手动 javac JNI.java编译。
生成 jni 类型的 头文件:
须要用到 javah 命令,这个环节easy出错,由于路径參数等原因,贴出 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
会在project文件夹下的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 __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
函数定义比較奇怪。刚開始接触肯定不适应,这就是JNI 机制的特殊写法了! 以 JNIEXPORT jint JNICALL Java_com_jscese_test_JNI_add
(JNIEnv *, jobject, jint, jint); 为例。 当中
JNIEXPORT JNICALL keyword 代表这是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)
不加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编译整个 project,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类型 | 本地类型 | 描写叙述 |
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(參数类型)返回类型 |
※ 确定域和方法的符号
以上是关于Andorid——ubuntu下的 NDK / JNI的主要内容,如果未能解决你的问题,请参考以下文章
Ubuntu+NDK编译openssl(为了Android上使用libcurl且支持HTTPS协议)