JNI的意思是Java Native Interface(java本地接口),它是为了方便java调用C,C++等本地代码所封装的一层接口,我们都知道,JAVA的优点是跨平台,但是作为有蒂娜的同时,其在本地交互的时候出现了短板,java的跨平台性导致了本地交互的能力不够强大,一些和操作系统相关的特性无法满足,这才出现了java JNI

NDK是android所提供的一个工具借,通过NDK可以在android中更加方便的通过jni来访问本地代码。比如c/c++,ndk还提供了交叉编译器,开发人员只需要简单的修改mk文件就可以生成特定的CPU动态库:

  • 1.提高了代码的安全性,由于so库反编译比较困难,因此NDK提高了Android程序的安全性
  • 2.可以很方便的使用目前已有的C/C++开源库
  • 3.由于平台间的移植,通过C/C++实现的动态库可以很方便的在其他平台使用
  • 4.提高程序在某些特定情形下的执行效率,但是并不能明显提升Android程序的性能

由于jni和ndk比较适合在linux环境下开发,这里也同样用ubuntu来说明

一.JNI的开发流程

JNI的开发流程有如下几个步骤,首先需要在JAVA中声明native方法,接着用C或者C++实现native方法,然后编译运行

1.在java中声明native方法

public class JniTest {static {System.loadLibrary("jni-test");}public static void main(String [] args){JniTest jniTest = new JniTest();System.out.print(jniTest.get());jniTest.set("Hello Jni");}public native String get();public native void set(String str);
}

可以看到代码,声明了两个native方法,get和set(string),这两个需要在JNI中实现,jniTest的头部有一个加载动态库的过程,其中jni-test是so库的标识,so库完整的名称为libjni-test.so,这是加载so库的规范。

2.编译Java源文件得到class文件,然后通过javah命令到处JNI的头文件

具体的命令

javac jniTest.java
javah jniTest

这样就会自动生成一个头文件,这里注意下,函数名的规则是:Java_包名类名方法名,比如jniTest中的set方法,到这里就变成 JNIEXPORT void JNICALL Java_com.liuguilin_jniTest_set(JNIEnv*,jobject,jstring),关于Java和Jni的数据类型之间的关系会在后面介绍,这里只需要知道Java的String对应的JNI的jstring,JNIEXPORT,JNICALL,JNIEnv和jobject都是JNI标准定义的类型或者宏,他们的含义:

  • JNIEnv*:表示一个指向JNI环境的指针,可以通过他来访问JNI提供的接口方法
  • jobjct:表示Java对象的this
  • JNIEXPORT,JNICALL:他们是JNI所定义的宏,可以在jni.h这个头文件中查看

下面的宏定义是必须的,他指定extern “C”内部的函数采用C语言的命名规范来编译,否则当JNI采用C++来实现时,由于命名风格不同,这将导致JNI在链接时无法根据函数名查找到具体的函数,那么JNI调用就无法完成,更多的细节实际上有关C和C++编译时的一些问题,这里就不展开了

#ifdef _cplusplus
extern "C"{
#endif

3.实现JNI方法

JNI方法是指Java中声明的native方法,这里可以选择C或者C++来实现,他们的实现过程都类似,只有少量的区别,下面分别使用C和C++来实现JNI的方法,首先,在工程里创建一个子目录,这里叫做jni,然后将之前通过javah生成的头文件复制尽力啊,接着创建test.cpp和test.c文件

test.cpp和test.c很类似,但是他们对env的操作方法有所不同,

C++:env->NewStringUTF("Hello from JNI!");
C:(*env)->NewStringUTF("Hello from JNI!");

4.编译so库并在Java中调用

so库的编译这里采用gcc,切换到jni目录中,对于test.cpp和test.c来说,他们的编译指令如下

C++:gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.cpp -o libjni-test.so
C:gcc -shared -I /usr/lib/jvm/java-7-openjdk-amd64/include -fPIC test.c -o libjni-test.so

上面的语句中,/usr/lib/jvm/java-7-openjdk-amd64是本地的JDK安装路径,在其他环境编译时也可以将其指向本机的jdk路径即可,而libjni-test.so则是生成so库的名字,在JAVA中可以通过如下方式加载System.loadLibaray(“jni-test”),其中so库名字中的“lib”和“.so”是不需要明确指明的,so编译后,就可以调用了

首先采用C++产生so库的日志

invoke get in C++
Hello from JNI!
invoke set from C++
hello world

然后是C

invoke get from C
Hello from JNI!
invoke set from C
hello world

通过上面的例子可以方便的调用C、C++代码

二.NDK的开发流程

NDK的开发是基于JNI的

1.下载和配置NDK

首先要从Android官网下载NDK,然后配置环境变量

2.创建一个Android项目,声明native方法

public class JniActivity extends AppCompatActivity{static {System.loadLibrary("jni-test");}@Overrideprotected void onCreate( Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_jni);TextView textView = findViewById(R.id.mTextView);textView.setText(get());set("Hello JNI");}public native String get();public native void set(String str);
}

3.实现Android项目中所声明的native方法

在的外部创建jni目录,然后再jni目录下创建三个文件,test.cpp,Android.mk,Application.mk

//test-cpp#include <jni.h>
#include <stdio.h>#ifdef _cplusplus
extern "C"{
#endifjstring Java_com_liuguilin_androidsample_JniTestApp_JniActivity_get(JNIEnv*env,jobject thiz){printf("invoke get in c++ \n");return env->NewStringUTF("Hello JNI");}void Java_com_liuguilin_androidsample_JniActivity_set(JNIEnv*env,jobject thiz,jstring string){printf("invoke get in c++ \n");char* str = env -> GetStringUTFChars(string,NULL);printf("%s\n",str);env->ReleaseStringUTFChars(string,str);}#ifdef _cplusplus
}
#endif#Android.mk
LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)LOCAL_MODULE := jni-test
LOCAL_SRC_FILES := test.cppinclude $(BUILD_SHARED_LIBRARY)#Application.mk
APP_ABI := armeabi

这里对Android.mk和Application.mk做了简单的介绍,在Android.mk中,LOCAL_MODULE表示模块的名称,LOCAL_SRC_FILES表示需要参与编译的源文件,Application.mk中常用的配置项是APP_ABI,他表示CPU的加载平台的类型,目前市面上常见的架构平台是armeabi,x86,mips,其中在移动设备中有主要地位的是armeabi,这也是大部分apk中只包含armeabi平台的原因

4.切换到jni目录的父目录,然后通过ndk-build命令编译产生的so

这个时候NDK回切换一个和jni目录平级的目录,libs,在这面放置so,需要主要的是,ndk-build命令会默认jni目录为本地源码的目录,如果庅存放的目录名字不是jni则编译不通过

然后再创建一个jniLibs放进去即可

当然,我们需要配置一下build.gradle

    sourceSets.main{jniLibs.srcDir 'src/main/jniLibs'}

除了手动使用ndk-build命令创建so库,还可以通过AS来自动编译,不过就比较复杂了,为了让AS自动编译JNI,我们需要配置NDK的选项

  ndk{modelName "jni-test"}

这样就可以自动编译JNI代码了,但是这个时候AS会把所有平台的so都打包到apk中,我们一般要配置一下的

    productFlavors{arm{ndk{abiFilter "armeabi"}}x86{ndk{abiFilter "x86"}}

三.JNI的数据类型和类型签名

JNI的数据类型包含两种,基本类型和引用类型,基本类型主要有jboolean,jchar,jint等,他们和Java中的数据类型对应关系

JNI中的引用类型主要有类,对象和数组,他们在java中的引用类型的对应关系如图:

JNI的类型签名标识了一个特定的JAVA类型,这个类型即可是方法,也可以是数据类型

类的签名比较简单,采用了L+包名+类名+.的形式,只需要将其中的.替换为/即可,比如java.lang.String,他的签名是Ljava/lang/String,注意末尾也是签名的一部分

基本数据类型采用的是一系列大写字母表示:

从表可以看出,基本数据类型的签名是有规律的,一般是首字母的大写,但是boolean除外,因为B已经被byte占用,而long的签名之所以不是L,那是因为L表示的是类的签名

对象和数组的签名稍微复杂一些,对于对象来说,他的签名就是对象所属的类的签名,比如String对象,他的签名是Ljava/lang/String,对于数组来说,他的签名为【+类型签名,比如int数组,其类型为int,而int的签名为I,所以int数组的签名为【I,同理就是可以得出如下的签名对应关系

char[] [C
float[] [F
double[] [D
long[] [J
String[] [Ljava/lang/String
Object[] [Ljava/lang/Object

对于多维数组来说,他的签名为n+[+类型签名,其中n表示数组的维度,比兔int[][]的签名[[I

方法的签名为(参数类型签名)+返回值类型签名,这有点不好理解,举个例子,如下方法:boolean fun1(int a,double b,int[]c),根据签名的规定可以知道,他的参数类型的签名连在一起读ID[I,返回值类型的签名为Z,所以整个办法的签名就是(ID[I)Z。再举例子:

int fun1()  签名为()I
void fun1(int i) 签名为(I)V

四.JNI调用JAVA方法的流程

JNI调用Java方法的流程是闲通过类名找到类,然后再根据方法名找到方法的id,最后就可以调用这个方法了,如果调用JAVA中的费静态方法,那么需要构造出类的对象后才能调用
,下面的例子演示了如何在JNI中调用java的静态方法,至于非静态的只是多了异步构造队形的过程

首先需要在java中定义一个静态方法供JNI调用

    public static void methodCalledByJni(String msg) {Log.i(TAG, "methodCalledByJni:" + msg);}
    void callJavaMethod(JNIEnv * ev,jobject thiz){jclass clszz = env->FindClass("com/liuguilin/androidsample/JniActivity");if(clazz == NULL){return;}jmethodID id = env->GetStaticMethodID(clszz,"methodCalledByJni","(LJava/lang/String;)V");if(id == NULL){printf("error");}jstring msg = env->NewStringUTF("msg send by callJavaMethod in test.cpp");env->CallStaticVoidMethod(clazz,id,msg);}

从callJavaMethod的实现可以看出,程序会根据类名找到类,然后再去找这个方法,接着完成最终的调用,最后在get中的使用:

    jstring Java_com_liuguilin_androidsample_JniTestApp_JniActivity_get(JNIEnv*env,jobject thiz){printf("invoke get in c++ \n");callJavaMethod(env,thiz);return env->NewStringUTF("Hello JNI");}

由于MainActivity会调用JNI中的get方法,set方法优惠调用callJavaMethod方法,而callJavaMethod方法又会反过来调用,这样就完成了一次从Java调用JNI然后从JNI中调用Java的方法,安装运行程序,可以看出,已经调用成功了

我们可以发现,JNI调用java过程和java方法的定义有很大的关联,针对不同类型的java方法,JNIEnv提供了不同的接口和调用,这只是一个初步的介绍,更多的需要读者自己去查阅

Android开发艺术探索——第十四章:JNI和NDK编程相关推荐

  1. Android开发艺术探索学习笔记 第二章IPC

    最近将之前工作做本地的学习笔记上传一下 这里是Android艺术开发探索的前三章内容 文章目录 1. android的多进程模式 2. IPC基础概念介绍 2.1 Serializable 2.2Pa ...

  2. 《Android开发艺术探索》第12章- Bitmap 的加载和 Cache 读书笔记

    目录 1. 前言 2. 正文 2.1 Bitmap 的高效加载 2.1.1 说一下对于Android 中的 Bitmap 的理解 2.1.2 内存中存储的 Bitmap 对象和本地图片有什么区别? 2 ...

  3. 《Android开发艺术探索》第9章-四大组件的工作过程读书笔记

    目录 1 四大组件的运行状态 2 Activity 的工作过程 2.1 Activity 的启动过程 3 Service 的工作过程 3.1 Service 有哪两种工作状态?这两种状态可以共存吗? ...

  4. Android开发艺术探索完结篇——天道酬勤

    这片文章发布,代表着我已经把本书和看完并且笔记也发布完成了,回忆了一下我看Android群英传,只用了两个月,但是看本书却花了2016年05月04日 - 2018年07月16日,整整两年多,真是惭愧 ...

  5. 《Android开发艺术探索》图书勘误

    第一章 在13页提到"系统只在Activity异常终止的时候才会调用onSaveInstanceState与onRestoreInstanceState来储存和恢复数据,其他情况不会触发这个 ...

  6. Android开发艺术探索——第七章:Android动画深入分析

    Android开发艺术探索--第七章:Android动画深入分析 Android的动画可以分成三种,view动画,帧动画,还有属性动画,其实帧动画也是属于view动画的一种,,只不过他和传统的平移之类 ...

  7. Android开发艺术探索--第二章IPC机制(2)之Binder

    最近在拜读任主席的Android开发艺术探索,现在看了一半,再回头看前面的,感觉跟没有看一样,所以还是把知识点总结一下吧,这一节咱们来讲一下IPC中的Binder 直观来说,Binder是Androi ...

  8. 《android开发艺术探索》笔记之Bitmap的加载和Cache

    <Android开发艺术探索>笔记之Bitmap的加载和Cache<一> 我放暑假前,就在图书馆借了一本<Android开发艺术探索>,这也是我看到很多人推荐的.之 ...

  9. Android开发艺术探索读书笔记(一)

    首先向各位严重推荐主席这本书<Android开发艺术探索>. 再感谢主席邀请写这篇读书笔记 + 书评.书已经完整的翻完一遍了,但是还没有细致的品读并run代码,最近有时间正好系统的把整本书 ...

最新文章

  1. udp重发机制_UDP 协议
  2. mac pdf去水印_今天才知道,Word、PDF文档去水印这么简单!一键水印说拜拜
  3. AD账号创建日期、最近一次登录时间、最近一次重置密码时间查询
  4. 动态规划 53:Maximum Subarray,152:Maximum Subarray,266. Palindrome Permutation 回文全排列...
  5. 算法试题 - 找出字符流中第一个不重复的元素
  6. RStudio-Desktop与RStudio-Server的启动方式
  7. 杂牌手柄模拟xboxone手柄_手机就能玩Switch游戏,蛋蛋模拟器+盖世小鸡X2手柄体验...
  8. (转)Linux下的输入/输出重定向
  9. java 实现二分法
  10. C++实验课任务(多态--容器--算法)
  11. datagrid不显示 easy_[Easy UI ]DataGrid 首次进入页面时,不加载任何数据
  12. 数据增强在贝壳找房文本分类中的应用
  13. java日期格式化返回date_Java日期时间格式化操作DateUtils 的整理
  14. MDI-jade化工软件的安装
  15. matlab 将路径靠左,latex 图片位置靠左
  16. AdaBoost 自适应增强 简单易懂 by hch
  17. java smb删除指定文件,java 利用SMB向远道机器写文件
  18. 设计素材|最流行的抽象流体彩色渐变海报,艺术感爆棚
  19. Android APK安装常见错误列表
  20. Access denied for user ‘user‘@‘%‘ to database 可能的原因

热门文章

  1. 华硕主板如何设置开机自启_华硕主板如何设置开机第一启动项方法大全
  2. 4A服务按库拆分|组件、服务|合并打包、独立打包(进行中)
  3. 在phpMyAdmin使用用户口令登陆(转)
  4. CSS样式:渐变色圆角边框
  5. 生日蛋糕-python实现
  6. 布朗大学计算机科学博士怎样,2020年布朗大学博士含金量
  7. 链栈的创建,入栈,出栈,获取栈顶元素
  8. 论文阅读:A Survey of Open Domain Event Extraction 综述:开放域事件抽取
  9. 手机无网状态下获取经纬度,离线定位的方法。
  10. java毕业设计牙科诊所管理系统Mybatis+系统+数据库+调试部署