先有个概念
JNI,洋名全称是Java Native Interface,翻译过来就是Java本地接口。我们都知道所谓接口就是一套规范,可以用来定义接入方法。那么JNI定义了什么接入方法呢?答案是:它定义了一套Java接入C/C++的方法。
我们知道C/C++语言编译后的文件是可以直接在本地系统中运行的,而Java文件编译后生成的是字节码文件,需要依赖Java虚拟机(JVM)来运行,显然在效率上C/C++更高效;另一方面,我们知道字节码文件是可以很容易反编译的,存在不安全性;再者,硬件相关的驱动、许多知名的音视频解码库也是C/C++编写的。Java程序想要执行高性能的代码、想要高安全性或者需要调用系统驱动和重用已有的音视频解码库,那么就不得不使用JNI了。
看下我当初简陋的笔记:
原理
大致的过程是这样的:
- 我们把C/C++源程序编译打包成动态库(dll或so,具体看运行的环境),存放在我们程序的lib目录下;
- 在java程序中我们调用带native修饰的代理方法,它的实现在C/C++程序中,系统会调用动态库中相应的实现方法,如果需要的话,还会返回处理结果。
JNI开发环境搭建
听你这么一说,原理似乎懂了些了,那现在是不是该开工了??别急,“工欲善其事,必先利其器”,让我们先搭建起开发环境。
1. 下载NDK
What ? NDK是什么鬼?别急嘛,这就说。NDK洋名全称是Native Development Kit,就是Google给咱们开发者提供的一套方便地进行JNI开发的工具集,可以到官网下载,也可以用我准备好的资源(里面还包含了适用于eclipse开发环境的NDK插件,这个在ADT-Bundle中是集成的)。
2. 解压NDK并查看参考文档
将下载后的压缩包解压到任意分区,注意文件路径不能包含中文和空白字符(比如空格、tab),否则会导致NDK不可用。注:可以通过查看参考文档和导入示例代码到工程学习。
3. 安装NDK插件和绑定NDK路径
对Eclipse IDE:放到eclipse安装目录下的plugins目录就对了,然后依次点击Window-->Preference-->Android-->NDK 进行NDK路径的绑定
Hello JNI
如果上面的步骤顺利,下面我们就要正式跟JNI打个招呼了。Hello JNI项目源码:下载
1. 新建一个普通的Android项目
简单,略。
2. 添加本地动态库支持
右击Android项目-->Android Tools-->Add Native Support...,然后输入动态库的名称(系统会自动加上前缀"lib"和后缀".so",以jni为例)
完成后会发现,工程会转到C/C++视图,并且项目中多了一些文件/文件夹
3. 创建两个对应的文件
一个是Java源文件,代理动态库中方法;另一个是C/C++源文件,是对代理方法的实现。
最佳实践是:先创建Java代理文件,静态导入动态库并编写好代理方法,然后利用javah命令生成C语言的方法签名;复制方法签名到C/C++源文件中,补充参数名和方法体就OK了。
3.1 创建Java源文件--Jni.java
package com.example.hello_jni;
public class Jni {
//静态加载我们即将生成的so库
static{
System.loadLibrary("jni");
}
//代理函数,具体实现在C
public native String getMsgFromC();
}
3.2 生成Jni.java的标头文件
复制工程的src目录的位置,当前为:D:\Android\jni\Hello-JNI\src
CMD进入
C:\Users\Lshare>cd /d D:\Android\jni\Hello-JNI\src
D:\Android\jni\Hello-JNI\src>
复制Jni.java的全路径名,当前为:com.example.hello_jni.Jni
javah生成标头文件
D:\Android\jni\Hello-JNI\src>javah com.example.hello_jni.Jni
刷一下工程,可以看到
3.3 复制标头文件内容到jni.cpp,并删除标头文件(com_example_hello_jni_Jni.h)
3.4 绑定头文件路径
4.完成函数体
#include <jni.h>
/**
* env:包含了jni.h提供了许多便捷的函数,查看头文件源码发现
* #if defined(__cplusplus)
* typedef _JNIEnv JNIEnv;
* typedef _JavaVM JavaVM;
* #else
* typedef const struct JNINativeInterface* JNIEnv;
* typedef const struct JNIInvokeInterface* JavaVM;
* thiz:包含对应的代理函数的java类的对象
*/
#ifndef _Included_com_example_hello_jni_Jni
#define _Included_com_example_hello_jni_Jni
#ifdef __cplusplus
extern "C" {
#endif
JNIEXPORT jstring JNICALL Java_com_example_hello_1jni_Jni_getMsgFromC
(JNIEnv * env, jobject thiz){
//当为cpp文件时用“env->”,为c文件时用“(*env)->”
// 该函数用于将“C-String”转换为jstring
return env->NewStringUTF("Hello JNI!");
}
#ifdef __cplusplus
}
#endif
#endif
5. 完善逻辑实现
//activity_main.xml
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:onClick="sayHello"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="C快快请安" />
</RelativeLayout>
//MainActivity.java
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
public void sayHello(View view){
String msg = new Jni().getMsgFromC();
Toast.makeText(this, msg, Toast.LENGTH_SHORT).show();
}
}
6. 修改Android.mk和Application.mk适应需求
Android.mk的任务:描述源文件该如何编译
#包含Android.mk自身的路径
LOCAL_PATH := $(call my-dir)
#清除LOCAL_XXX变量,除了LOCAL_PATH
include $(CLEAR_VARS)
#要生成的动态库名称,系统会自动加上“lib”前缀和“.so”后缀
LOCAL_MODULE := jni
#要编译的C/C++文件
LOCAL_SRC_FILES := jni.cpp
#指向构建脚本,搜集定义的LOCAL_XXX变量信息,生成lib$(LOCAL_MODULE).so
include $(BUILD_SHARED_LIBRARY)
Application.mk的任务:描述哪一个动态库是项目需要的
默认不生成这个文件,我们需要自己在/jni目录下新建一个。一般情况下,我们使用两个变量就够了
#定义要生成的CPU架构的so文件,all代表所有
APP_ABI := all
#目标安卓版本,18表示api level,即Android 4.3
APP_PLATFORM := android-8
7. run一下看看
Java果真通过JNI技术调用了C代码,而C真的返回问候给Java了,nice!!!
最后不妨看看我当初的笔记(仅供参考,估计只有我自己看得懂,捂脸走。。。)