安卓JNI从0到1入门教程(二)

经过上一篇《安卓JNI从0到1入门教程(一)》介绍,我们对JNI有了初步认识,接下来我会从ndk-build方式和cmake方式分别来介绍怎么构建native库:

一、ndk-build

ndk-build依赖配置文件Android.mk,存放代码的位置通常是jni目录

1.新建一个java测试类

package com.example.jni;public class JNIDemo {static {//这个库名必须跟Android.mk的LOCAL_MODULE对应System.loadLibrary("JniDemo");}public static native String test();
}

2.创建jni目录

3.生成.h文件

打开命令行,切换到项目的java目录,然后使用以下命令生成.h头文件。(没有javah命令的请先配置jdk环境变量)

javah -d ../jni com.example.jni.JNIDemo

生成的文件会在jni目录下出现

 4.实现具体C++函数

在jni目录下新建一个.cpp文件,内容如下

#include "com_example_jni_JNIDemo.h" //引入刚刚生成的头文件extern "C"
//实现test函数实际功能
JNIEXPORT jstring JNICALL Java_com_example_jni_JNIDemo_test(JNIEnv * env, jclass clz){return env->NewStringUTF("hello world");
}

 5.配置Android.mk和Application.mk

在jni目录下新建这两个文件,文件内容如下:

Android.mk:

LOCAL_PATH := $(call my-dir)include $(CLEAR_VARS)#定义要构建生成的本地库的名称,以下示例会生成名为 libJniDemo.so 的库。
LOCAL_MODULE := JniDemo#指定源代码文件
LOCAL_SRC_FILES := \
JNITest.cpp \include $(BUILD_SHARED_LIBRARY)

 Application.mk

#指定用于此应用的 C++ 标准库,默认情况下使用 system STL。其他选项包括 c++_shared、c++_static 和 none
APP_STL := c++_shared
APP_CPPFLAGS := -frtti -fexceptions
# APP_ABI:指定生成哪些cpu类型的so, all代表全部 常用的有:armeabi-v7a,arm64-v8a,x86,x86_64
APP_ABI := armeabi-v7a arm64-v8a
#APP_PLATFORM 会声明构建此应用所面向的 Android API 级别,并对应于应用的 minSdkVersion
APP_PLATFORM := android-21

注意:Gradle 的 externalNativeBuild 会忽略 APP_ABI。请在 splits 块内部使用 abiFilters 块或abi 块。

6.生成.so文件

你可以直接使用ndk-build命令构建,也可以在AndroidStudio中配置快捷方式。(本示例使用ndk版本为21.4.7075529)

(1)用构建命令构建
控制台切换到jni目录下,也就是包含Android.mk和Application.mk的目录,执行ndk-build命令,成功后可以在libs文件夹下找到。(没有ndk-build命令的需要先配置ndk环境变量)

(2)配置快捷工具

打开File-Settings-Tools-External Tools,添加新的工具,命名为ndk-build(随意命名),Program配置选择你的ndk所在的目录下的ndk-build.cmd,这个通常在你的AndroidStudio的安装目录下的ndk目录,Working directory填写项目的jni目录

点击执行ndk-build即可生成.so文件

 7.在代码中使用native方法

public class MainActivity extends AppCompatActivity {private TextView tvMsg;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);tvMsg = findViewById(R.id.tv_msg);//调用native方法String msg = JNIDemo.test();tvMsg.setText(msg);}
}

二、CMake

上面讲述了ndk-build,现在讲讲CMake怎么配置

1.添加CMakeLists.txt文件

在项目的app模块根目录新建CMakeLists.txt文件,填写如下内容:

#指定CMake的最低版本要求
cmake_minimum_required(VERSION 3.18.1)# 定义本地库的名称
set(my_lib_name JniDemo)#添加库配置,如果有多个库,可以添加多个add_library方法
add_library( # 指定生成库的名称,使用上面定义的变量${my_lib_name}# 标识是静态库还是动态库 STATIC:静态库 SHARED:动态库SHARED# C/C++源代码文件路径src/main/cpp/JNITest.cpp)#指定.h头文件的目录
include_directories(src/main/cpp/)# 指定构建输出路径
set_target_properties(${my_lib_name} PROPERTIESLIBRARY_OUTPUT_DIRECTORY "${CMAKE_SOURCE_DIR}/libs/${ANDROID_ABI}")# 链接外部静态库(如果你的静态库或动态库依赖于其他库或依赖项)
target_link_libraries(MyStaticLibrary PRIVATE MyExternalStaticLibrary)

示例中的add_library函数用于指定要构建的本地库以及其源代码文件。target_link_libraries函数用于指定要链接的系统库,例如log库,它用于在本地代码中使用Android的日志功能。

最后,通过set_target_properties函数,可以指定生成的共享库的输出路径,将其放置在libs/${ANDROID_ABI}目录下,其中${ANDROID_ABI}是一个CMake变量,表示当前目标平台的ABI(应用二进制接口)

更多CMakeLists.txt的配置项请参考配置Cmake

2.在app模块下的build.gradle中添加externalNativeBuild和ndk配置

android {compileSdk 33defaultConfig {applicationId "com.example.jni"minSdk 21targetSdk 32versionCode 1versionName "1.0"testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"ndk {//指定编译的ABI平台abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'}}externalNativeBuild {cmake {//指定CMakeLists文件所在位置path "CMakeLists.txt"}}//...省略
}

3.添加C++文件

CMake通常使用cpp目录而不是jni目录,所以在src/main的目录下新建一个cpp目录,然后把.h和.cpp文件放进去

 4.生成.so文件

使用AS的Rebuild Project构建项目,即可以在libs目录生成对应so文件。

 好了,至此两种方式介绍已经讲完。

更多关于其中的配置可参考谷歌官方的文档。

后记

上述过程中,我们涉及了2个概念:动态库静态库

在NDK编译过程中,这是两个不一样库文件生成方式。

1.动态库(Dynamic Library):

  • 动态库是一种在运行时被加载和链接到应用程序中的库文件。在 Android NDK 中,动态库的文件扩展名通常为 .so(Shared Object)。
  • 动态库可以被多个应用程序或模块共享,以减少磁盘空间和内存占用。多个应用程序可以同时加载和使用同一个动态库。
  • 动态库的加载和链接是在运行时进行的,这意味着库文件可以在应用程序运行期间动态加载和替换,从而实现更新和维护的方便性。
  • 在 Android NDK 中,动态库使用 C/C++ 编译器生成,并使用 ndk-build 或 CMake 等构建工具进行编译和构建。

2.静态库(Static Library):

  • 静态库是一种在编译时被链接到应用程序中的库文件。在 Android NDK 中,静态库的文件扩展名通常为 .a(Archive)。
  • 静态库会在应用程序编译时被复制到可执行文件中,每个使用了静态库的应用程序都包含了库的完整副本。
  • 静态库的优点是可以提供独立的可执行文件,不需要依赖外部的库文件。它在性能上可能略优于动态库,因为静态库的函数调用是直接的,无需动态链接过程。
  • 在 Android NDK 中,静态库也使用 C/C++ 编译器生成,并使用 ndk-build 或 CMake 等构建工具进行编译和构建。

在 Android NDK 开发中,你可以根据需求选择使用动态库或静态库。动态库适用于多个应用程序共享库文件、便于更新和维护的场景,而静态库适用于需要独立的可执行文件、性能优化和不需要依赖外部库的场景。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.hqwc.cn/news/20510.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

感受C++模版的所带来的魅力

一、泛型编程思想 首先我们来看一下下面这三个函数,如果学习过了 C函数重载 和 C引用 的话,就可以知道下面这三个函数是可以共存的,而且传值会很方便 void Swap(int& left, int& right) {int temp left;left right;right temp; }…

HarmonyOS/OpenHarmony应用开发-Stage模型UIAbility组件使用(四)

UIAbility组件与UI的数据同步 基于HarmonyOS的应用模型,可以通过以下两种方式来实现UIAbility组件与UI之间的数据同步。 1.EventHub:基于发布订阅模式来实现,事件需要先订阅后发布,订阅者收到消息后进行处理。 2.globalThis&…

macOS Sonoma 14beta 3 (23A5286i)第二个更新「附黑/白苹果镜像下载」

系统镜像下载: 系统介绍 黑果魏叔 7 月12 日消息,苹果今天发布 macOS Sonoma 14.0 Beta 3(内部版本号:23A5286i)第二个更新。 目前尚不清楚苹果为什么要发布 macOS Sonoma Beta 3 的第二个版本,但它可能…

MQ的优劣势及RabbitMQ相关概念

一,MQ 1,MQ 的概念 MQ 全称 Message Queue(消息队列),是用来存储消息数据的容器(是一个中间件),一般用于分布式系统间的通信;MQ主要介于生产者和消费者之间&#xff0c…

微服务系列文章之 Redisson实现分布式锁

一、高效分布式锁 当我们在设计分布式锁的时候,我们应该考虑分布式锁至少要满足的一些条件,同时考虑如何高效的设计分布式锁,这里我认为以下几点是必须要考虑的。 1、互斥 在分布式高并发的条件下,我们最需要保证,同…

Redis_设置密码

目录 一、临时设置密码 二、永久设置密码 源码等资料获取方法 一、临时设置密码 # 获取密码 config get requirepass # 设置密码为123456 config set requirepass 123456 # 验证密码。当设置密码后,进入redis就要验证 auth 密码 # 取消密码 config set requirep…

vscode插件开发之终端那些事儿

在开发vscode插件的时候,好几个设计都需要集成终端。 查资料后发现vsocd为开发者提供了丰富的终端API。 结合我自己的需求来展开终端的那些事儿吧: 从treeview中点击触发打开一个终端 无关的代码省略: vscode.window.createTerminal({name…

【JavaEE进阶】使用注解存储对象

使用注解存储对象 之前我们存储Bean时,需要在spring-config 中添加一行 bean注册内容才行,如下图所示: 问题引入:如果想在Spring 中能够更简单的进行对象的存储和读取,该怎么办呢? 问题解答:实…

Tomcat工作原理

一、Tomcat架构 ### 说明: Server:表示整个 Tomcat Catalina servlet 容器,Server 中可以有多个 Service。(可以通过telenet 8005后连接后输入“SHUTDOWN” 注意这里是大写,来关闭服务)Service&#xff1…

JavaScript中的 map, forEach 无法跳出循环, return和 break不起作用,可以使用every 和 some方法

在我们平时使用习惯中,for循环里要跳出整个循环是使用break,但在数组中用forEach循环或者map如要退出整个循环使用break会报错,使用return也不能跳出循环,以下我们就来探索一下正确的跳出循环方案 一、先看一下案例 1、forEach函…

通信算法之176: 基于Matlab的OFDM通信系统关键基带算法设计6-流程

一. 接收算法流程 粗同步(分组检测) 载波同步(精细频偏估计) 精同步(OFDM起始,符号同步) 1.4 信道估计(长序列) 1.5 信道均衡(所有数据OFDM符号&#xff…

存css实现动态时钟背景

代码实现 <!DOCTYPE html> <html lang"en"> <head><meta http-equiv"Content-Type" content"text/html; charsetUTF-8"><title>Title</title><meta name"referrer" content"no-referrer…