Jni是java用来和其他语言交互的一套规范,有了它java可以调用动态链接库中的方法,或者让其他语言掉用java方法。具体介绍和Demo可以浏览这篇博客。
基础
NDK
Native Development Kit(NDK)是一系列工具的集合。它提供了一系列的工具,帮助开发者快速开发C/C++的动态库,并能自动将so和Java一起打包成apk。
JNI与NDK的关系
NDK可以为我们生成了C/C++的动态链接库,JNI是java和C/C++沟通的接口,两者与Android没有半毛钱关系,只因为安卓是java程序语言开发,然后通过JNI又能与C/C++沟通,所以我们可以使用NDK+JNI来实现“Java+C”的开发方式。
NDK开发优点
- 项目需要调用底层的一些C/C++的一些东西(java无法直接访问到操作系统底层(如系统硬件等)),或者已经在C/C++环境下实现了功能代码(大部分现存的开源库都是用C/C++代码编写的。),直接使用即可。NDK开发常用于驱动开发、无线热点共享、数学运算、实时渲染的游戏、音视频处理、文件压缩、人脸识别、图片处理等。
- 为了效率更加高效些。将要求高性能的应用逻辑使用C/C++开发,从而提高应用程序的执行效率。但是C/C++代码虽然是高效的,在java与C/C++相互调用时却增大了开销;
- 基于安全性的考虑。防止代码被反编译,为了安全起见,使用C/C++语言来编写重要的部分以增大系统的安全性,最后生成so库(用过第三方库的应该都不陌生)便于给人提供方便。(任何有效的代码混淆对于会smail语法反编译你apk是分分钟的事,即使你加壳也不能幸免高手的攻击)
- 便于移植。用C/C++写得库可以方便在其他的嵌入式平台上再次使用。
NDK配置
首先友情提醒摒弃Eclipse拥抱AndroidStudio吧。AS给了android程序员极大的支持,大大提高了效率。如果你在Eclipse+ADT下开发过NDK就能体会到要么是配置NDK还要下载Cygwin,配置Cygwin ,然后需要编译生成,相当的蛋疼。要么是直接用Eclipse开发,但是前期配置也是一堆;真心蛋疼。
首先下载NDK,在AS上点击File–>Project Structure,如果你已经下载,直接选择你 ndk 的位置;如果没有下载,在红色框这个位置会有位置提示你下载,点击下载就行。(不用翻墙哦,速度杠杠的)
添加 ndk 后你会在 local.properties 这个文件看到(路径取决于你 ndk 的位置):
最后在此处加上这句代码:android.useDeprecatedNdk=true
好了,ndk 环境搭建完毕!
实例
生成so文件
新建一个android项目,在项目中新建一个类,类中调用动态链接库“Jary”,并声明了一个本地方法“getString”,返回一个字符串。
package com.example.nickolas.jnitest3;
public class JniTest{
static {
System.loadLibrary("Jary");
}
public native String getString();
}
然后重新编译下你的 Project: Build–>Make Project
重新编译之后就可以在对应的文件夹看到编译后的 JniTest.class
下一步来看怎么生成 .h 的文件
在 studio 打开 Terminal 命令行工具,打开步骤是 View–>Tool Windows–>Terminal
默认进入到该项目的app文件夹下。输入如下命令跳转到class中间文件生成路径:
xxxxx\app> cd app/build/intermediates/classes/debug/
然后执行如下javah命令生成h文件。
xxxxx\debug> javah -jni icom.example.nickolas.jnitest3.JniTest
执行完之后你可以在文件夹app\build\intermediates\classes\debug下看见生成的 .h头文件为:
com_example_nickolas_jnitest3_JniTest.h
开始编写C/C++源文件。在 main 目录下新建“jni” 文件夹,把上一步生成的.h文件复制到“jni”文件夹中,并在该文件夹中新建C语言源文件jary.c。
C语言源代码如下:
#include "com_example_nickolas_jnitest3_JniTest.h"
JNIEXPORT jstring JNICALL Java_com_example_nickolas_jnitest3_JniTest_getString
(JNIEnv *env, jobject jobject1){
return (*env)->NewStringUTF(env, "hello nick");
}
依赖刚才生成的 .h 头文件 ; 方法名与 .h 里面方法名保持一致 ;
最后在 build.gradle defaultConfig 中添加如下代码
ndk {
moduleName "Jary" //生成的so名字
abiFilters "armeabi", "armeabi-v7a", "x86" //输出指定三种abi体系结构下的so库。
}
到这里,jni 调 C 就完成了,现在我们来测试一下,写个 TextView 显示一下调用的 C:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
TextView tvJni = (TextView) findViewById(R.id.tvJni);
tvJni.setText(new JniTest().getString());
}
效果如下图 :
使用so文件
要使用so文件首先得把so文件导入到项目。
倒入so文件有两种方法,一种直接在main文件夹下新建文件夹jniLibs把so文件放入该文件夹。
第二种方法在我们的Module的根目录中建立libs目录,把so文件放入该文件夹。同时需要在build.gradle 添加如下配置:
最后有个大坑敲黑板。调用so库的时候,将调用其中方法的类的包名改成so文件中写的包名。