转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119005718
本文出自【赵彦军的博客】

文章目录

  • (一)什么是Android NDK
  • (二)下载NDK
  • (三)什么JNI?
  • (四) JNI的三个角色
  • (五) JNI的命名规则
  • (六) 如何实现JNI
  • (七)JNI原理
    • JNIEnv是什么?
    • JNIEnv和JavaVM的区别
    • JNIEnv的作用
    • JNIEnv与线程
    • JNIEnv结构
    • 创建Java类中的String对象
  • (八)ABI
  • (九)JNI的引用
    • 1、局部引用(Local Reference)
    • 2、全局引用(Global Reference)
    • 3、弱全局引用(Weak Global Reference)
  • (十)示例
  • (十一)查看Android 项目so
  • (十二)CMakeLists.txt
  • (十三)如何编译多个平台so

(一)什么是Android NDK

NDK 官网:https://developer.android.google.cn/ndk/index.html

Android NDK 是一个工具集,可让您使用 C 和 C++ 等语言以原生代码实现应用的各个部分。对于特定类型的应用,这可以帮助您重复使用以这些语言编写的代码库。

(二)下载NDK

1、下载 NDK ,下载完成后,下载的目录在 android sdk 目录里面

/Users/xmly/Library/Android/sdk/ndk


除此之外,你还可以自由下载其他版本的 ndk ,https://developer.android.com/ndk/downloads/revision_history

2、下载 CMake 工具

3、在 Android Studio local.properties 配置 ndk 目录,示例如下:

sdk.dir=/Users/xmly/Library/Android/sdk
ndk.dir=/Users/xmly/Library/Android/ndk-r19c

(三)什么JNI?

Java调用C/C++在Java语言里面本来就有的,并非Android自创的,即JNI。JNI就是Java调用C++的规范。当然,一般的Java程序使用的JNI标准可能和android不一样,Android的JNI更简单。

JNI,全称为Java Native Interface,即Java本地接口,JNI是Java调用Native 语言的一种特性。通过JNI可以使得Java与C/C++机型交互。即可以在Java代码中调用C/C++等语言的代码或者在C/C++代码中调用Java代码。

由于JNI是JVM规范的一部分,因此可以将我们写的JNI的程序在任何实现了JNI规范的Java虚拟机中运行。

同时,这个特性使我们可以复用以前用C/C++写的大量代码JNI是一种在Java虚拟机机制下的执行代码的标准机制。

代码被编写成汇编程序或者C/C++程序,并组装为动态库。也就允许非静态绑定用法。这提供了一个在Java平台上调用C/C++的一种途径,反之亦然。

PS:
开发JNI程序会受到系统环境限制,因为用C/C++ 语言写出来的代码或模块,编译过程当中要依赖当前操作系统环境所提供的一些库函数,并和本地库链接在一起。而且编译后生成的二进制代码只能在本地操作系统环境下运行,因为不同的操作系统环境,有自己的本地库和CPU指令集,而且各个平台对标准C/C++的规范和标准库函数实现方式也有所区别。这就造成了各个平台使用JNI接口的Java程序,不再像以前那样自由的跨平台。

如果要实现跨平台, 就必须将本地代码在不同的操作系统平台下编译出相应的动态库。比如在Android环境下,编译成Android平台的 .so 库。在 Ios 环境下,需要编译成 Ios 平台的动态库。

(四) JNI的三个角色

三者的关系

JNI下一共涉及到三个角色:C/C++代码、本地方法接口类、Java层中具体业务类。

JNI简要流程

(五) JNI的命名规则

随便举例如下:

JNIExport jstring JNICALL Java_com_example_hellojni_MainActivity_stringFromJNI( JNIEnv* env,jobject thiz )
  • jstring 是返回值类型
  • Java_com_example_hellojni 是包名
  • MainActivity 是类名
  • stringFromJNI 是方法名

其中JNIExportJNICALL是不固定保留的关键字不要修改

定义 native 方法:

java 类

native String stringFromJNI();

kotlin 类

external fun stringFromJNI(): String

external 相当于 native

(六) 如何实现JNI

JNI开发流程的步骤:

  • 第1步:在Java中先声明一个native方法
  • 第2步:编译Java源文件javac得到.class文件
  • 第3步:通过javah -jni命令导出JNI的.h头文件
  • 第4步:使用Java需要交互的本地代码,实现在Java中声明的Native方法(如果Java需要与C++交互,那么就用C++实现Java的Native方法。)
  • 第5步:将本地代码编译成动态库(Windows系统下是.dll文件,如果是Linux系统下是.so文件,如果是Mac系统下是.jnilib)
  • 第6步:通过Java命令执行Java程序,最终实现Java调用本地代码。

(七)JNI原理

JNIEnv是什么?

JNIEnv是一个线程相关的结构体,该结构体代表了Java在本线程的执行环境

JNIEnv和JavaVM的区别

  • JavaVM:JavaVM是Java虚拟机在JNI层的代表,JNI全局仅仅有一个
  • JNIEnv:JavaVM 在线程中的代码,每个线程都有一个,JNI可能有非常多个JNIEnv;

JNIEnv的作用

  • 调用Java 函数:JNIEnv代表了Java执行环境,能够使用JNIEnv调用Java中的代码
  • 操作Java代码:Java对象传入JNI层就是jobject对象,需要使用JNIEnv来操作这个Java对象

JNIEnv与线程

JNIEnv是线程相关的,即在每一个线程中都有一个JNIEnv指针,每个JNIEnv都是线程专有的,其他线程不能使用本线程中的JNIEnv,即线程A不能调用线程B的JNIEnv。所以JNIEnv不能跨线程。

  • JNIEnv只在当前线程有效:JNIEnv仅仅在当前线程有效,JNIEnv不能在线程之间进行传递,在同一个线程中,多次调用JNI层方便,传入的JNIEnv是同样的
  • 本地方法匹配多个JNIEnv:在Java层定义的本地方法,能够在不同的线程调用,因此能够接受不同的JNIEnv

JNIEnv结构

JNIEnv是一个指针,指向一个线程相关的结构,线程相关结构,线程相关结构指向JNI函数指针数组,这个数组中存放了大量的JNI函数指针,这些指针指向了详细的JNI函数。

创建Java类中的String对象

jstring NewString(JNIEnv *env, const jchar *unicodeChars,jsize len):

通过Unicode字符的数组来创建一个新的String对象。
envJNI接口指针;unicodeChars是指向Unicode字符串的指针;lenUnicode字符串的长度。返回值是Java字符串对象,如果无法构造该字符串,则为null

那有没有一个直接直接new一个utf-8的字符串的方法呢?答案是有的

就是jstring NewStringUTF(JNIEnv *env, const char *bytes)这个方法就是直接new一个编码为utf-8的字符串。

其他方法:

  • NewArray Routines 返回值:Array Type
  • NewBooleanArray() 返回值:jbooleanArray
  • NewByteArray() 返回值:jbyteArray
  • NewCharArray() 返回值:jcharArray
  • NewShortArray() 返回值:jshortArray
  • NewIntArray() 返回值:jintArray
  • NewLongArray() 返回值:jlongArray
  • NewFloatArray() 返回值:jfloatArray
  • NewDoubleArray() 返回值:jdoubleArray

(八)ABI

(九)JNI的引用

Java内存管理这块是完全透明的,new一个实例时,只知道创建这个类的实例后,会返回这个实例的一个引用,然后拿着这个引用去访问它的成员(属性、方法),完全不用管JVM内部是怎么实现的,如何为新建的对象申请内存,使用完之后如何释放内存,只需要知道有个垃圾回收器在处理这些事情就行了,然而,从Java虚拟机创建的对象传到C/C++代码就会产生引用,根据Java的垃圾回收机制,只要有引用存在就不会触发该该引用所指向Java对象的垃圾回收。

在JNI规范中定义了三种引用:局部引用(Local Reference)、全局引用(Global Reference)、弱全局引用(Weak Global Reference)。区别如下:

1、局部引用(Local Reference)

局部引用,也成本地引用,通常是在函数中创建并使用。会阻止GC回收所有引用对象。

最常见的引用类型,基本上通过JNI返回来的引用都是局部引用,例如使用NewObject,就会返回创建出来的实例的局部引用,局部引用值在该native函数有效,所有在该函数中产生的局部引用,都会在函数返回的时候自动释放(freed),也可以使用DeleteLocalRef函数手动释放该应用。

之所以使用DeleteLocalRef函数:实际上局部引用存在,就会防止其指向对象被垃圾回收期回收,尤其是当一个局部变量引用指向一个很庞大的对象,或是在一个循环中生成一个局部引用,最好的做法就是在使用完该对象后,或在该循环尾部把这个引用是释放掉,以确保在垃圾回收器被触发的时候被回收。在局部引用的有效期中,可以传递到别的本地函数中,要强调的是它的有效期仍然只是在第一次的Java本地函数调用中,所以千万不能用C++全部变量保存它或是把它定义为C++静态局部变量。

2、全局引用(Global Reference)

全局引用可以跨方法、跨线程使用,直到被开发者显式释放。类似局部引用,一个全局引用在被释放前保证引用对象不被GC回收。和局部应用不同的是,没有那么多函数能够创建全局引用。能创建全部引用的函数只有NewGlobalRef,而释放它需要使用ReleaseGlobalRef函数

3、弱全局引用(Weak Global Reference)

JDK 1.2新增加的功能,与全局引用类似,创建跟删除都需要由编程人员来进行,这种引用与全局引用一样可以在多个本地方法有效,不一样的是,弱引用将不会阻止垃圾回收期回收这个引用所指向的对象,所以在使用时需要多加小心,它所引用的对象可能是不存在的或者已经被回收。

引用比较

在给定两个引用,不管是什么引用,我们只需要调用IsSameObject函数来判断他们是否是指向相同的对象。代码如下:

(*env)->IsSameObject(env, obj1, obj2)

如果obj1obj2指向相同的对象,则返回JNI_TRUE(或者1),否则返回JNI_FALSE(或者0)

(十)示例

Java代码

package com.example.myapplication;public class JniBridge {native String stringFromJNI();native String stringFromJNI2(String message);native double doubleFromJNI(double value);native int sumFromJNI(int value);
}

native-lib.cpp

#include <jni.h>
#include <string>
#include <float.h>extern "C" JNIEXPORT jstring JNICALL
Java_com_example_myapplication_JniBridge_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}extern "C"
JNIEXPORT jstring JNICALL
Java_com_example_myapplication_JniBridge_stringFromJNI2(JNIEnv *env, jobject thiz,jstring _message) {//获取 c 语言层面的字符串,会分配内存const char * str = env->GetStringUTFChars(_message, JNI_FALSE);//释放c风格的对象内存,一旦释放,str将没有用env->ReleaseStringUTFChars(_message,str);//获取字符串的长度int length = env->GetStringLength(_message);//获取一段字符串,类似于java的截取char buffer[128];env->GetStringUTFRegion(_message,0,length-1,buffer);//c风格的字符串转换成jstringreturn env->NewStringUTF("");
}extern "C"
JNIEXPORT jdouble JNICALL
Java_com_example_myapplication_JniBridge_doubleFromJNI(JNIEnv *env, jobject thiz, jdouble value) {return value * 3;
}extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_JniBridge_sumFromJNI(JNIEnv *env, jobject thiz, jint value) {return value * 2;
}

(十一)查看Android 项目so

如何查看编译出来的 so 文件,有两种方式

第一种在 build 文件夹中查看

第二种反编译 .apk 文件

(十二)CMakeLists.txt

当你创建了一个 JNI Android 工程的时候,会自带创建一个 CMakeLists.txt 文件

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.10.2)# Declares and names the project.project("myapplication")# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp )# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log )# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.native-lib# Links the target library to the log library# included in the NDK.${log-lib} )

ADD_LIBRARY()语法

add_library(<name> [STATIC | SHARED | MODULE][EXCLUDE_FROM_ALL]source1 [source2 ...])

:库的名字,直接写名字即可。
[STATIC | SHARED | MODULE] :类型有三种。

SHARED,动态库
STATIC,静态库
source, 资源的相对路径

当一个类中方法被一个 cpp类实现,source 选项只写一个就行。如果是被 两个 cpp 实现,就需要添加多个 source

举例:

public class JniBridge {//native2.cpp 实现native String stringFromJNI();native String stringFromJNI2(String message);native double doubleFromJNI(double value);native int sumFromJNI(int value);//native2.cpp 实现native int sum2(int value);
}

添加多个 source

add_library( # Sets the name of the library.native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp native2.cpp )

(十三)如何编译多个平台so

 defaultConfig {applicationId "com.example.myapplication"minSdkVersion 16targetSdkVersion 30versionCode 1versionName "1.0"externalNativeBuild {ndk {abiFilters 'armeabi-v7a', 'arm64-v8a'}}}

Android NDK学习笔记1:基础相关推荐

  1. Android NDK学习笔记(转)

    前言 Android系统中的应用程序都是用Java开发的.Android NDK使我们能够在android上使用C/C++开发的原生代码.有两个理由使用NDK: 一是合理的重用现有的代码:二是在程序中 ...

  2. Android NDK学习笔记3:JNI访问Java属性、方法

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119209444 本文出自[赵彦军的博客] 文章目录 Java 类型和JNI符号对比 ...

  3. Android NDK学习笔记6:异常处理

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119547007 本文出自[赵彦军的博客] 文章目录 JNI捕获异常 JNI抛出异常 ...

  4. Android Ormlite 学习笔记1 -- 基础

    Ormlite 是一个开源Java数据实体映射框架.其中依赖2个核心类库: 1.ormlite-android-4.48.jar 2.ormlite-core-4.48.jar 新建项目,引用上面2个 ...

  5. Android NDK学习笔记5:引用类型管理

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119545225 本文出自[赵彦军的博客] 文章目录 局部引用 全局引用 弱引用 总 ...

  6. Android NDK学习笔记4:JNI访问Java构造函数

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119348263 本文出自[赵彦军的博客] 文章目录 方式一:NewObject 方 ...

  7. Android NDK学习笔记2:数组

    转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/119152933 本文出自[赵彦军的博客] 文章目录 java数组和jni数组对应关 ...

  8. Android:日常学习笔记(6)——探究活动(3)

    Android:日常学习笔记(6)--探究活动(3) 活动的生命周期 返回栈 Android中的活动是可以叠加的,我们每启动一个新活动,就会覆盖在原来的活动上,点击Back以后销毁最上面的活动,下面的 ...

  9. Android动画学习笔记

    Android实战经验之图像处理及特效处理的集锦 https://www.oschina.net/question/231733_44154 Android动画学习笔记 3.0以前,android支持 ...

最新文章

  1. 倒序输出单链表的内容
  2. Jzoj4458 密钥破解——Pollard-rho
  3. 在wsl中运行开源项目tinyhttpd遇到的问题
  4. 《Java 核心技术卷1 第10版》学习笔记------ Object类的 hashCode 方法
  5. C语言入门题-7-1 最大和最小 (10分)
  6. Linux下配置和安装VNCServer远程服务
  7. ubuntu 开启 rewrite 模块
  8. 【笔记3】二维码扫码数据埋点
  9. java如何实现动态时钟_java中的动态时钟
  10. IntelliJ IDEA 2019.3 首个最新访问版本发布,新特性抢先看
  11. LINUX SHELL命令ls只列出目录名
  12. oracle查询undo表空间使用率,检查Undo表空间使用情况
  13. 【原】如何免财富值下载百度文库文档?如何下载百度文库源文档?
  14. 【华为云技术分享】“技术-经济范式”视角下的开源软件演进剖析-part 2
  15. 单例模式之懒汉式(三种代码实现)
  16. 一个exfat U盘引起的systen crash
  17. 在matlab中如何求偏导数,求解 PDE 并计算偏导数
  18. 《深入理解计算机系统》实验四Architecture Lab
  19. 你知道低代码BPM平台吗?
  20. 计算机一级基础及msoffice应用,全国计算机等级考试教程一级计算机基础及MS Office应用...

热门文章

  1. mysql scws_php利用scws实现mysql全文搜索功能的方法,_PHP教程
  2. 10 windows 启动虚拟机报错_Windows 系统如何安装 Docker
  3. form select multiple 某个字段是数组_Hive取非Group by字段数据的方法
  4. 博士申请 | 北京大学AI院杨耀东老师招收强化学习博弈论实习生/博士生
  5. 全面涵盖传统方法和深度学习方法:3D人脸识别的十年发展及未来趋势
  6. 第五届“达观杯”自然语言处理文本分类竞赛开启报名,丰厚奖金等你来战!...
  7. ​ICML 2021 Long Oral | 顺序不可知的交叉熵函数
  8. @请注意查收:《史上最全的AI论文资料》.pdf
  9. WSDM 2020 | RMRN:社区问答中的深度关联推理模型
  10. SIGIR 2019 | 基于人类阅读行为模式的机器阅读理解