Android Jni开发-生成so库和aar详解(一)

3,105 阅读5分钟

注:默认你以及安装好NDK开发的所有环境,包括cmake插件,没配置的点这

一.Android studio自动创建demo分析

1.新建一个Project,选择Native C++项目类创建;

image.png

2.项目会默认创建出3个关键文件,cpp目录native-lib.cppCMakeList.txt、以及入口类 MainActivity.

3.分析一下这三个类的关键代码,理解了再进行模仿

1.MainActivity.java入口类

package com.android.projectforblog;

import androidx.appcompat.app.AppCompatActivity;

import android.os.Bundle;
import android.widget.TextView;

import com.android.projectforblog.databinding.ActivityMainBinding;

public class MainActivity extends AppCompatActivity {
    // Used to load the 'projectforblog' library on application startup.
    static {
        System.loadLibrary("projectforblog");
    }
    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'projectforblog' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}
  • System.loadLibrary("projectforblog") 加载so库,这个库是项目build或者Run起来后在 “app -> build -> intermediates -> cmake -> debug/release -> obj -> (具体架构)arm64-v8a/armeabi-v7a/x86/x86_64” 目录下生成的, 名字是在开发者自己在CMakeLists.txt中定义,如果在当前项目直接引用则是定义的名字,但是系统在build生成的so文件时会给你文件名字默认加上前缀lib

tempsnip.png

  • stringFromJNI是本地方法的声明,java代码可以直接调用它,它调用projectforblog库中对应的头文件声明,头文件声明方法再调用对应的c文件;如果c库中没有对应的.h头文件,则会直接调用对应的.c/.cpp文件

2.native-lib.cpp

    #include <jni.h>
    #include <string>

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_android_projectforblog_MainActivity_stringFromJNI(
            JNIEnv* env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        return env->NewStringUTF(hello.c_str());
    }
  • extern "C"一方面extern 是 C/C++ 语言中表明函数和全局变量作用范围(可见性)的关键字,该关键字告诉编译器,其声明的函数和变量可以在本模块或其它模块中使用;另一方面,这是一个C++语言的特殊语法,用于指定函数的名称不需要进行C++的命名规则修饰。被 extern "C" 修饰的变量和函数是按照 C 语言方式编译和连接的

  • JNIEXPORT 是jni.h中的一个被定义的宏,用于指定函数的可见性和调用约定。 它会根据操作系统的不同,展开为不同的关键字。例如,在 Windows 平台上为__declspec(dllexport) 关键字,用于指定函数的导出;在 Linux 平台上,它会展开为 attribute((visibility("default"))) 关键字,用于指定函数的可见性

  • jstring 是jni.h中对应JAVA语言String基本结构的类型,更多内容看文章三

  • JNICALL 是jni.h中的一个被定义的宏。意思为JNI调用某方法

  • Java_com_android_projectforblog_MainActivity_stringFromJNI指JNICALL调用的方法名,也是java中的native方法名。命名规则:"Java"+包名+类名+方法名


3.CMakeLists.txt

# 设置cmake版本,得看anroid studio配置cmake创建是什么版本,创建会自动添加。
cmake_minimum_required(VERSION 3.22.1)

# 项目名字,注意,不是so库名字,NDK这个项目有时候会独立一个module,可以配置版本。
project(projectforblog VERSION 1.0)

add_library( # Sets the name of the library.
        # 设置库名字
        projectforblog
        # 设置为共享库
        SHARED
        # 填写的是库源代码的路径
        native-lib.cpp)
        
find_library(
        #如果要打印log,这段代码照搬就好了,这个lib库是ndk默认提供的。
        # 要寻找的库,路径存储位置
        log-lib
        # 要寻找的库的名字
        log)
        
target_link_libraries( 
        # 定义最终要链接的库名字
        projectforblog
        # log-lib不是库的名字,而是log库的位置路径。
        ${log-lib})

CMakeLists.txt 是一个 CMake 工程的配置文件,用于描述项目的编译、链接和安装等相关信息。以下是一些常用的参数说明:

  • CMAKE_MINIMUM_REQUIRED:指定 CMake 的最低版本要求,填你配置的cmake的版本号

  • PROJECT:指定项目的名称,还可以设置项目的版本号、类型(例如可执行文件、静态库或动态库)、使用的语言(例如C或C++)。

  • INCLUDE_DIRECTORIES:指定头文件搜索路径,例如: include_directories("${CMAKE_CURRENT_SOURCE_DIR}/include")

  • ADD_LIBRARY:可以用来添加 normal libraryimported library 两种类型的库文件。 Normal library 是指在当前 CMake 项目中编译生成的库文件,可以使用 add_library 命令来创建。它可以是静态库(.a 或 .lib)或动态库(.so 或 .dll)。静态库是在编译时链接到目标程序中的库,而动态库是在运行时动态加载的库。

    假设我们有一个名为 mylib 的 C++ 库,包含两个源文件 foo.cpp 和 bar.cpp,需要编译成静态库和动态库。可以使用以下 CMakeLists.txt 文件来创建 normal library,CMakeLists.txt

    # SHARED意味着编译生成静态库
    add_library(mylib_static STATIC foo.cpp bar.cpp)
    # SHARED意味着编译生成动态库
    add_library(mylib_shared SHARED foo.cpp bar.cpp)
    
  • Imported library 是指在当前 CMake 项目外部编译生成的库文件,可以使用 add_library 命令的 IMPORTED 参数来创建。在使用时,需要通过 target_link_libraries 命令将其链接到目标文件中。

    假设我们有一个名为 libcurl 的第三方库,已经编译成了 libcurl.a 静态库和 libcurl.so 动态库。现在我们需要在我们的 C++ 项目中链接这个库。可以使用以下 CMakeLists.txt 文件来创建 imported library:

    # CMakeLists.txt
    add_library(curl STATIC IMPORTED)
    set_target_properties(
        curl 
        PROPERTIES IMPORTED_LOCATION 
        ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libcurl.a
    )
    target_link_libraries(curl)
    

注:上面只写了静态库.a的规则写法,动态库的直接改成.so就好了,使用 set_target_properties 命令设置其 IMPORTED_LOCATION 属性为 libcurl.a 的路径

  • TARGET_LINK_LIBRARIES:指定链接的库文件。

  • SET:设置变量的值,例如#set(CMAKE_ANDROID_ARCH_ABI arm64-v8a)

  • LINK_DIRECTORIES:指定库文件搜索路径。

  • INSTALL:指定安装规则。

  • FIND_PACKAGE:查找外部库文件的位置和版本信息。

  • MESSAGE:输出消息。

  • ADD_EXECUTABLE:添加可执行文件。 以上是一些常用的 CMakeLists.txt 参数说明。在实际使用中,可以根据项目的需要来选择和配置相应的参数。


4.build之后生成的so库,理论上这个so库也是可以提供给其他应用进行调用

二.总结

1.展示了如何通过Android Studio自己创建一个so库,官方默认demo在build之后就可以看到有so库

2.解释了核心的3个文件的作用和So库的引用方式

3.展示了自定义so库引入第三方so库的方法。

4.由于篇幅问题,上面只讲述了基础的demo,实际开发中不可能只是获取一个String就可以了的,可能要获取int,float,array等数据类型,此外,也有可能要提供对象等数据才能返回结果等,因此下章节讲述完整版的实际开发,比如作为提供So库的项目,应该是app_moudule + so_library_moudule的形似,在so_library_module中提供so库和java调用管理类,在app_module中进行自我测试,测试通过再打包为aar提供给第三方开发者

5.Android Jni开发-生成so库和aar详解(二)