Android NDK基础介绍及例子

Android开发中的NDK到底是什么?(详细解析+案例) - 知乎 (zhihu.com)

NDK介绍

(1)简介

定义:Native Development Kit,是 Android的一个工具开发包

  • NDK是属于 Android 的,与Java并无直接关系

作用:快速开发CC++的动态库,并自动将so和应用一起打包成 APK

  • 即可通过NDKAndroid中 使用JNI与本地代码(如C、C++)交互

应用场景:Android的场景下 使用JNI

  • Android开发的功能需要本地代码(C/C++)实现

java调用c的步骤

  1. package com.fs.test;
    public class HelloWorld {
    //本地方法
    	private native void show();
    	public static void main(String[] args)
    	{
    	System.loadL ibrary("wgr");
    	new HelloWorld().show();
    	}
    }
    

    1.System.loadL ibrary("wgr");加载动态库

    2.private native void show();声明一个动态库对应的本地方法调用libwgr.so中的函数
    *void Java_ com_ fs_ test HelloWorld_ show(JNIEnv env, jobject thiz)

    3.直接调用show函数(动态库中与show对应的函数)

一、NDK/JNI

NDK

NDK(Native Development Kit)-原生开发工具包,使得能够在Android上
去使用C/C++代码;

JNI

JNI即Java Native Interface,Java和Native接口,就是Java和C/C++之间通讯的桥梁; 为什么要有JNI,因为Java和C/C++之间是无法直接相互调用的,也就是无法直接通讯,就和Java和JS之间也不能直接相互调用,中间需要翻译者来处理,JNI就是为了实现Java和C/C++之间这两种不同语言之间的通讯的;
JNI并不是Android中的技术,它是Java本身就具有的功能,是由JVM支持JNI功能

-那么为什么需要在Android上去使用C/C++代码?

因为我们知道开发Android我们是使用Java/Kotlin语言进行开发的,那么不论是Java还是Kotlin,最终都是编译成字节码文件.dex文件,然而字节码文件是Java/Kotlin经过一定语义编译之后的产物,并不是完全可以运行的机器码指令;所以运行应用时还是需要进一步的编译成机器码才能够执行;
而C/C++是完全的编译型语言,编译之后是机器码文件,运行应用时,可以直接执行的,所以效率肯定是要比Java/Kotlin要高的;所以一些对一些比较耗性能的操作,如音视频编解码,图像处理等操作,最好还是交由C/C++代码中去执行,这样能够大大提高应用的执行效率;

NDK开发

新建一个Native项目

Android Studio 新建一个项目,新建项目时,项目模板选择Native C++
新建项目成功后,我们可以看一下项目的目录结构;

在src/main目录下 有一个cpp目录,与java目录同级,cpp目录下有一个 CMakeLists.txt文件和native-lib.cpp
CMkeLists.txt就是CMake脚本文件
native-lib.cpp 是C++源文件

app Module build.gradle中会有一些NDk相关的配置:

plugins {
 id 'com.android.application'
}
android {
 namespace 'com.example.nativec'
 compileSdk 33
 defaultConfig {
 applicationId "com.example.nativec"
 minSdk 24
 targetSdk 33
 versionCode 1
 versionName "1.0"
 testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
 }
 buildTypes {
 release {
 minifyEnabled false
 proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
 }
 }
 compileOptions {
 sourceCompatibility JavaVersion.VERSION_1_8
 targetCompatibility JavaVersion.VERSION_1_8
 }
 externalNativeBuild {
 cmake {
 path file('src/main/cpp/CMakeLists.txt')
 version '3.22.1'
 }
 }
 buildFeatures {
 viewBinding true
 }
}
dependencies {
 implementation 'androidx.appcompat:appcompat:1.4.1'
 implementation 'com.google.android.material:material:1.5.0'
 implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
 testImplementation 'junit:junit:4.13.2'
 androidTestImplementation 'androidx.test.ext:junit:1.1.3'
 androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

设置调试模式

在Run/Edit Configuration/Debug Configurations中 Debugger 调试有四个选项
Auto、 自动检测
Java、 只调试Java代码
Native、 只调试Native代码
Dual、 Java+Native两种都用
注意不要选择Java和Native,选择Auto或者Dual

编写编译脚本文件CMakeLists.txt

CMake是一个跨平台的C++编译工具,所谓编译就是把C++代码编译成静态库或者动态库
CMakeLists.txt就是CMake的编译脚本文件,
CMake编译C++是通过CMakeLists.txt编译脚本来进行编译的;
CMakeLists.txt常用编译脚本

 (1)include_directories(dir_path) 
 //设置头文件的查找目录,可以使用多次include_directories设置多个头文件
 //查找目录
 举例: include_directories(${CMAKE_CURRENT_SOURCE_DIR}/giflib)
 
 (2)add_library(编译生成的库名,
 编译生成库的类型(STATIC/SHARED),
 编译的库所使用的C/C++源文件(.c/.cpp文件)(可能会有很多) 
 .
 .
 .
 ) //指定编译生成库
 举例:add_library(
 native-lib
 SHARED
 native-lib.cpp) 
 
 上面这个add_library就指定了编译生成的库的名字为native-lib,库的类型为
 动态库,库所使用或者依赖的所以C/C++源文件只有一个native-lib.cpp
 这里编译native-lib库只使用了一个C/C++源文件native-lib.cpp,但是大多情况下,
 
 编译库,可能会使用依赖到很多C/C++源文件; 
 
 如:
 add_library(
 native-lib
 SHARED
 native-lib.cpp,
 a.cpp,
 b.cpp
 ....
 ) 
 这样把所有使用到的C/C++源文件一个个写进来是可以的,但是当非常多的时候,就很容易写漏,
 而且也很麻烦; 可以使用file() 这个脚本,来设置C/C++的查找路径的别名,最终使用这个查找路径
 别名,就可以替代写这个路径下的所有C/C++源文件了
 
 (3) file(GLOB 别名 某路径下某种类型文件) 查找某路径下所有某种类型文件
 
 举例:
 #设置查找GifLib库实现源文件(.c)文件目录
 file(GLOB giflib_C_DIR ${CMAKE_CURRENT_SOURCE_DIR}/giflib/*.c)
 #设置自己编写的C++实现源文件(.CPP)查找目录
 file(GLOB CSUTOM_CPP_DIR ${CMAKE_CURRENT_SOURCE_DIR}/*.cpp)
 然后上面说到的add_library()设置编译生成库依赖很多C/C++源文件的是偶,就可以使用
 file()设置C/C++源文件查找别名
 add_library(
 native-lib
 SHARED
 ${CSUTOM_CPP_DIR}
 
 ${giflib_C_DIR}
 ) 
 (4) target_link_libraries(生成库的名字,
 生成库所链接的或者说是使用到的其他动态库或者静态库
 ) //设置生成库使用到的所有其他动态库或者静态库
 举例:
 
 target_link_libraries(
 
 native-lib
 a
 b
 ....
 
 ) 
 
 生成库使用到了哪些其他动态库或者静态库都要写进来,如果很多的情况下一个个写就很麻烦了
 和add_library()写使用到的所有C/C++源文件一样,也可以借助file()来设置使用到的动态库或者
 静态库文件查找路径别名;
 #查找所有so库
 file(GLOB SO_DIR ../../../libs/${CMAKE_ANDROID_ARCH_ABI}/*.so)
 target_link_libraries( # Specifies the target library.
 native-lib
 ${SO_DIR}
 
 )
 (5) set(变量名 变量值) //用于定义一个变量指代后面的变量值
 比如但我们需要使用一个目录路径的时候,这个目录路径很长,并且在CMakeLists.txt中
 多个地方使用到,我们可以通过set()来定义一个变量指代这个目录路径,后面在使用到这个
 路径的时候,使用这个简单的变量来替代;
 
 set(LINK_DIR ../../../../libs/${CMAKE_ANDROID_ARCH_ABI})
 file(GLOB SO_DIR ${LINK_DIR}/*.so)
 (6)find_library(库的别名 库名) //从系统查找依赖库
 find_library( 
 log-lib
 log)
 find_library(
 jnigraphics-lib
 jnigraphics
 ) 
 target_link_libraries( # Specifies the target library.
 native-lib
 ${SO_DIR}
 ${log-lib}
 
 ${jnigraphics}
 
 )
 log和jnigraphics都是NDK中自带的动态链接库

新建Native项目后,CMakeLists.txt默认已经有了一些一些基本配置,可以让我们正常编译使用native-lib.cpp了;

# 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.22.1)
# Declares and names the project.
project("nativec")
# 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.
 nativec
 # 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.
 nativec
 # Links the target library to the log library
 # included in the NDK.
 ${log-lib})

加载C/C++库

Android 系统 API提供了两种加载C/C++库的方式:
一种是直接加载APK中的C/C++库文件;
第二种是通过文件地址来加载C/C++库文件;

(1)加载APK中的C/C++库文件

库文件可能是动态库so库,或者是静态库.a,或者是C/C++源文件
加载方式:

 static {
 System.loadLibrary("native-lib"); //注意不要写库文件扩展名
 }

(2)加载外部的C/C++ so库

如SD卡下的C/C++库 ,这里要注意的是,如果是加载外部的库文件,只能加载动态库so库,并且这个库文件的位置只能在/system/lib目录下,或者是在应用的安装包目录下/data/data/packagename/,
而且这两个路劲又是有权限保护的不能直接访问,所以一般我们是将库文件复制到我们应用自己的应用目录下/data/data/packagename/com.xxx.xxx

 System.load(" C/C++库文件的绝对路径(注意不需要写库文件扩展名)");

实现Java和C++互相调用

因为我们新建的项目模板是Native C++,所以项目中已经自动为我们创建了一个native-lib.cpp,C++源代码文件,MainActivtiy中也声明了一个native方法与native-lib.cpp中对应的native方法对应,并且在MainActivtiy中调用了native方法;
来演示Java中调用C/C++方法

下面我们来分析一下native方法:

Native方法中的包名要和Java方法中的包名对应上,Native层中JNI方法命名格式

为Java_包名类名方法名 之间全部是下划线分隔 :

Java_com_nado_jniproject_MainActivity_stringFromJNI

参数:JNIEnv *env和jobject thiz

这个jobject类型的thiz就是对应外层的Java对象 jobject就是Java中的Object

Java类型 C类型 以及 JNI别名 对应如下:

Java 类型	JNI 别名 	 C 类型
boolean	 jboolean	 unsigned char
byte	 jbyte	 signed char
char	 jchar	 unsigned short
short	 jshort	 short
int	 jint int
long	 jlong	 long
float	 jfloat	 float
double	 jdouble	 double
String	 jstring	 char*
Class	 jclass	 /
Object	 jobject	 /

由于Java和C语言之间是无法直接调用的,两种语言的基本类型是不一样的,例如Java中有boolean类型,而在C中就没有这种类型,但是C语言还是有if-else判断的,那怎么判断true或者false? C中使用char类型,当char的值是0就是false非0就是true;基于这种情况,JNI重新定义了一些类型,以便C和Java对应上;

运行应用,我们可以在MainActivity中成功的显示从调用C/C++方法获取到的字符串

上面我们学习了Java中调用C/C++方法,为了清楚的了解Java和C之间的互相调用,还需要学习一下Native中调用Java方法

生成so文件

build一下,在这里找到生成的so文件

参考

https://blog.csdn.net/mq2856992713/article/details/118090046

作者:张少春02原文地址:https://www.cnblogs.com/zsc02/p/17654719.html

%s 个评论

要回复文章请先登录注册