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

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

本文使用开发环境:

Android Studio 4.0+

compileSDK 33

gradle 7.2.1

一、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。请在 ndk块内部使用 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;
    @Override
    protected 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);
    }
}

如果你使用的是比较老版本的Gradle,那么在app模块的build.gradle中需要添加以下配置,才能链接so库(生成的so库需放在libs目录下):

apply plugin: 'com.android.application'

android {
    compileSdkVersion 28
   
    //...其他省略

    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }
}

dependencies {

    //...其他省略
}

 二、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} PROPERTIES
        LIBRARY_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 33

    defaultConfig {
        applicationId "com.example.jni"
        minSdk 21
        targetSdk 32
        versionCode 1
        versionName "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 开发中,你可以根据需求选择使用动态库或静态库。动态库适用于多个应用程序共享库文件、便于更新和维护的场景,而静态库适用于需要独立的可执行文件、性能优化和不需要依赖外部库的场景。

猜你喜欢

转载自blog.csdn.net/gs12software/article/details/131636204