之前一直有接触源代码里面的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 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.

就代表已经成功安装。能够用它进行编译了!

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类型映射

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的更多相关文章

  1. Ubuntu下编译Android JNI最靠谱的方法...

    网上资料太杂乱,搞了大半天都还是没搞懂怎么系统的调用NDK.最后干脆放弃了Win改用Ubuntu编译JNI,虽然编译环境简单了,但是资料却少了不少.几乎没有一篇完整的文章.我想或许是能在Ubuntu下 ...

  2. ffmpeg2.2在ubuntu下使用NDK编译——并在android工程下测试使用

    作者:wainiwann 出处:http://www.cnblogs.com/wainiwann/ 本文版权归作者所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则 ...

  3. Ubuntu下编译Android JNI实例全过程

    第一步:保证make和gcc可用 在shell中输入make-v.不报错就是对的.(可參考http://wenku.baidu.com/view/d87586c24028915f804dc24a.ht ...

  4. ubuntu下编译android jni到so库的mk文件配置

    项目根目录下的Android.mk文件 LOCAL_PATH:= $(call my-dir)include $(CLEAR_VARS) LOCAL_MODULE_TAGS := optional L ...

  5. ubuntu下Java通过JNI调用C

    下面看一个实例,如下: public class TestJNI { static { System.loadLibrary("diaoyong"); // 程序在加载时,自动加载 ...

  6. Linux下使用NDK编译FFMPEG(libstagefright)

    这个月要负责一个项目,使用FFMPEG渲染视频,主要是Android端的,由于性能要求,要使用硬解码,但网上大多数教程都是没有libstagefright的,所以个人觉得,生成的so库文件也是没有开启 ...

  7. 【android 开 发 】 - Android studio 下 NDK Jni 开发 简单例子

    Android 开发了一段时间,一方面 ,感觉不留下点什么.有点对不起自己, 另一方面,好记性不如烂笔头,为了往后可以回头来看看,就当做是笔记,便决定开始写博客.废话不多说 ! 今天想搞一搞 ndk ...

  8. ubuntu下整合eclipse和javah生成jni头文件开发android的native程序(转)

    本文介绍两种利用javah命令生成jni头文件的方法,第一种为大众所知的javah命令,第二种为整合javah到eclipse里面.推荐第二种方式,方便快捷,随时修改随时生成 0:前提和条件: 1:u ...

  9. 如何在Ubuntu下搭建Android NDK开发环境

    1 搭建Android SDK开发环境 参考在在Ubuntu下搭建Android SDK开发环境(图文)首先在Ubuntu下搭建Android SDK开发环境. 2 下载NDK开发包 打开官网: ht ...

随机推荐

  1. javascript中 for循环的一些写法 for length 以及for in 还有 for of 的区别

    最近在写一些前端的代码,遇到一个产品列表遍历的问题,正好使用到for 的几种用法,于是研究了下. 代码如下,先说明下goodslist 是一个产品列表 形如这样的数据格式 { ‘types’:1, ' ...

  2. 容器 What, Why, How

    学习任何东西都可以按照3W的框架进行,容器技术也是一样,先回答 What.Why 和 How 这三个问题. What - 什么是容器? 容器是一种轻量级.可移植.自包含的软件打包技术,使应用程序可以在 ...

  3. windows8.1如何分盘

    磁盘分区首先要弄明白磁盘物理顺序与逻辑顺序的区别,在[磁盘管理]界面,所显示的前后顺序为物理顺序,这是磁盘上实实在在的物理位置,如下图2的电脑磁盘物理顺序为CFDE.在[资源管理器]界面,所显示的顺序 ...

  4. 转 如何在C++中调用C程序

    如何在C++中调用C程序?   C++和C是两种完全不同的编译链接处理方式,如果直接在C++里面调用C函数,会找不到函数体,报链接错误.要解决这个问题,就要在 C++文件里面显示声明一下哪些函数是C写 ...

  5. PE+ 1.0 ( Pump Express Plus 1.0 )

    Dialog iW1788 for PE+ 1.0 Dialog iW1680 for PE PE+1.0 for up to 15W battery, improve charging time, ...

  6. AC日记——[HAOI2015]树上操作 洛谷 P3178

    题目描述 有一棵点数为 N 的树,以点 1 为根,且树点有边权.然后有 M 个操作,分为三种:操作 1 :把某个节点 x 的点权增加 a .操作 2 :把某个节点 x 为根的子树中所有点的点权都增加 ...

  7. html-禁用右键、键盘F12、网页上选取内容、复制、粘贴

    摘要 为了保护我们的代码,我们需要想些办法禁止复制. css: body{     -webkit-touch-callout: none;    -webkit-user-select: none; ...

  8. 在js中为对象添加和删除属性

    对于一个普通的js对象: var obj = { name:"mary", age:21 } 如果我们要对它添加新属性的话可以使用下列方式: obj.address = " ...

  9. 最简单的window下使用Jenkins来做自动化部署的教程

    今天我们来说一下,如何使用Jenkins+powershell脚本,将我们的.NET CORE的脚本部署到对应的服务器上. 这里我们使用的源码管理工具是TFS.虽然源码管理器比较老旧,但是原理都差不多 ...

  10. 【APIO2015】Palembang Bridges

    题目描述 一条东西走向的穆西河将巴邻旁市一分为二,分割成了区域 $A$ 和区域 $B$. 每一块区域沿着河岸都建了恰好 $1000000001$ 栋的建筑,每条岸边的建筑都从 $0$ 编号到 $100 ...