当前位置: 首页 > article >正文

Android NDK 集成调用第三方SO库(标准SO库和非标准SO库)

想要进阶高级Android开发工程师,NDK开发是必须要掌握的技能之一,本文以集成第三方SO库为开端,带大家入门NDK开发,主要包含一下内容:

1、NDK工程创建;

2、编写和打包Android标准SO库;

3、集成Android标准SO库;

4、集成Android非标准SO库。

一、NDK工程创建

1、什么是NDK(Native Development Kit)?

NDK是一个为Android平台提供支持的开发工具包,专门用于开发本地(Native)代码的。通过NDK,开发者可以编写C、C++代码并将其编译成可以在Android设备上运行的共享库(.so文件)。简单来说,就是通过NDK让Android可以使用C、C++代码。

2、什么是JNI(Java Native Interface)?

JNI是Java与其他编程语言(如C、C++)之间的接口。它允许Java代码和本地代码(通常是C或C++)进行互相调用。
当开发者需要在Android应用中使用Java与C/C++代码交互时,就需要使用JNI。它提供了一种桥接机制,让Java代码能够调用C/C++库。

3、什么是CMake?

CMake 是一个跨平台的自动化构建系统工具,它主要用于管理和生成项目的构建过程。可以简单的把CMake理解Android工程的Gradle。

4、创建Native Android工程

 Native工程创建好后,会自动生成CMake文件,在cpp文件目录想编写C/C++代码;在java目录下编写java代码。

1)简单的了解一下CMake文件代码:

# 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)

#当前工程名
project("nativet0224")

#添加一个库(动态库SHARED,静态库STATIC)
add_library(
        nativet0224 #库的名字--->nativet0224.so
        SHARED #动态库

        #cpp的源文件:把cpp源文件编译成libnative-lib.so库
        native-lib.cpp)

#查找一个NDK工具中的动态库(liblog.so)
find_library(
        log-lib
        log)

#nativet0224是我们的总库,也就是我们在apk/lib/nativet0224.so
#然后把log库链接到总库中去,总库的cpp代码就可以使用android/log.h的库实现代码了
target_link_libraries(
        nativet0224 #被链接的总库

        #链接的具体库
        ${log-lib})

2)native-lib代码

native-lib.cpp主要是起到一个桥接作用,使用Java层连接native调用C++层。当我们需要让java层调用C++代码时,需要先在java层定义一个Native方法,让后通过快捷键,在native-lib.cpp自动生成桥接方法。

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

//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

3)Java层定义Native方法

Native工程创建好后,会在MainActivity里面自动生成调用Native层的默然方法。

public class MainActivity extends AppCompatActivity {

    // 加载nativet0224.so库,项目编译后apk的lib目录下会自动打包生成nativet0224.so库
    static {
        System.loadLibrary("nativet0224");
    }

    private ActivityMainBinding binding;

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

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());
        
        TextView tv = binding.sampleText;
        // 调用native方法
        tv.setText(stringFromJNI());
    }

    //声明一个native方法,供Java层调用
    public native String stringFromJNI();
}

4)自定义NativeLib.java

真实的项目开发中,我们会将Java层调用Native方法的定义统一封装到NativeLib.java

public class NativeLib {
    // 加载nativet0224.so库,项目编译后apk的lib目录下会自动打包生成nativet0224.so库
    static {
        System.loadLibrary("nativet0224");
    }

    //声明一个native方法,供Java层调用
    public native String testCallNative();
}

定义testCallNative()方法后,将鼠标放到 testCallNative()方法上,Androidstudio自动提示在native层native-lib.cpp创建桥接方法。

简单的写个字符串“Hello my Native.”返回,这里用的是C++ 语言开发,需要花点时间入门一下,或者直接AI帮写。

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

//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_nativet0224_NativeLib_testCallNative(JNIEnv *env, jobject thiz) {
    // TODO: implement testCallNative()
    std::string hello = "Hello my Native.";
    return env->NewStringUTF(hello.c_str());
}

5)Java层调用

public class MainActivity extends AppCompatActivity {

    private ActivityMainBinding binding;

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

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


        NativeLib nativeLib = new NativeLib();
        String result = nativeLib.testCallNative();
        Log.d("NDK", result);
    }
}

至此,一个简单的Android NDK工程已完成,实现了Java层与C++层的交互。

二、编写和打包Android标准SO库

将上面的Android NDK工程打包,即可生成Android标准SO库。

编译出的 .so 文件位于以下目录: 

app/build/intermediates/cmake/debug/obj/<architecture>/lib<library_name>.so

三、集成Android标准SO库

重新创建一个Android工程,集成我们打包出来的libnativet0224.so;

1)在新的Android工程的main目录下,创建jniLibs文件夹,将打包好的libnativet0224.so复制到该目录下。

2)在build.gradle文件中声明使用

android {
    //.....

    sourceSets {
        main {
            // 指定 jniLibs 路径
            jniLibs.srcDirs = ['src/main/jniLibs']
        }
    }
}

3) 自定义NativeLib.java

将Java层调用Native方法的定义统一封装到NativeLib.java,同Native工程的NativeLib.java。

但是这里要注意了,NativeLib.java的包名要同打包SO库的NativeLib.java的包名一致,不让对应的Native方法会对应不上。

我们先做一个错误的示范:

将NativeLib.java放在新项目的包名下,

在MainActivity调用:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        NativeLib nativeLib = new NativeLib();
        String result = nativeLib.testCallNative();
        Log.d("NDK", result);
    }
}

 执行报错:

java.lang.UnsatisfiedLinkError: No implementation found for java.lang.String com.nativet0224java.NativeLib.testCallNative() (tried Java_com_nativet0224java_NativeLib_testCallNative and Java_com_nativet0224java_NativeLib_testCallNative__)

 原因是,新项目的NativeLib.java是在com.nativet0224java包名下的,故新项目的NativeLib.java的public native String testCallNative()方法生成的Native方法命名是:Java_com_nativet0224java_NativeLib_testCallNative

而SO库中testCallNative()方法生成的Native方法命名是:

Java_com_nativet0224_NativeLib_testCallNative,

导致新项目无法在SO库中找到对应的Native方法,需要将新项目的NativeLib.java写在com.nativet0224包名下。

修改后重新编译,项目正常运行:

四、集成Android非标准SO库

然而在真实项目开发过程中,我们集成的SO库往往是第三方开发的,很大可能并不是标准的SO库(即不存在native-lib.cpp桥接文件,或native-lib.cpp桥接文件里面没有定义符合Android命名(Java_com_nativet0224_NativeLib_testCallNative)的桥接方法)。

这时候我们就需要自己创建一个Native Module去集成非标准的SO库。

1)在上面NDK工程增加test C++文件,并创建一个test方法

test.cpp

//
// Created by mypc on 2025-02-24.
//

#include "test.h"

Text0224::Text0224() {}

Text0224::~Text0224() {}

int Text0224::test(int a, int b) {
    return a + b;
}

test.h

//
// Created by mypc on 2025-02-24.
//

#ifndef NATIVET0224_TEST_H
#define NATIVET0224_TEST_H


class Text0224 {
public:
    Text0224();

    ~Text0224();

    int test(int a, int b);

};


#endif //NATIVET0224_TEST_H

2)在上面NDK工程的CMake中配置将text.cpp打包到SO库中

重新打包,生成的新的libnativet0224.so就会包含test.cpp在里面

3)在Java工程创建一个Native Module

4)在nativelib模块的main目录下,创建jniLibs文件夹,将新打包好的包含text.cpp文件的libnativet0224.so复制到该目录下。

5)在 nativelib模块的CMake文件中添加nativet0224动态库

# 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)

project("nativelib")

#添加nativet0224动态库
add_library(nativet0224 SHARED IMPORTED)
#指定nativet0224动态库的路径
set_target_properties(nativet0224  PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libnativet0224.so)

add_library(
        nativelib
        SHARED
        nativelib.cpp)

find_library(
        log-lib
        log)

target_link_libraries(
        nativelib

        #链接的nativet0224库到打包出来的新的nativelib.so库里面
        nativet0224

        ${log-lib})

6)在nativelib模块自动生成的NativeLib.java文件中增加调用Native层text.cpp类的test方法的桥接方法。参数和返回值要对应上text.cpp类的test方法。

public native int callTest(int a, int b);

package com.nativelib;

public class NativeLib {

    // Used to load the 'nativelib' library on application startup.
    static {
        System.loadLibrary("nativelib");
    }

    /**
     * A native method that is implemented by the 'nativelib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
    
    
    public native int callTest(int a, int b);
}

 快捷键自动创建Native层的桥接方法

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

extern "C" JNIEXPORT jstring JNICALL
Java_com_nativelib_NativeLib_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jint JNICALL
Java_com_nativelib_NativeLib_callTest(JNIEnv *env, jobject thiz, jint a, jint b) {
    // TODO: implement callTest()
}

7)在nativelib模块中cpp目录下创建text.h文件,可以直接复制上面的text.h文件

text.h

//
// Created by mypc on 2025-02-24.
//

#ifndef NATIVET0224_TEST_H
#define NATIVET0224_TEST_H


class Text0224 {
public:
    Text0224();

    ~Text0224();

    int test(int a, int b);

};


#endif //NATIVET0224_TEST_H

8)在 nativelib模块的Native层的桥接方法中谁用text.cpp的test方法

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

//Java层连接native调用C++层
extern "C" JNIEXPORT jstring JNICALL
Java_com_nativet0224_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}
extern "C"
JNIEXPORT jstring JNICALL
Java_com_nativet0224_NativeLib_testCallNative(JNIEnv *env, jobject thiz) {
    // TODO: implement testCallNative()
    std::string hello = "Hello my Native.";
    return env->NewStringUTF(hello.c_str());
}

9)Java层调用NativeLib.java的callTest桥接方法

在app模块的build.gradle中增加对native模块的依赖

implementation project(path: ':nativelib')

MainActivity中调用

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        com.nativelib.NativeLib nativeLib = new com.nativelib.NativeLib();
        int result = nativeLib.callTest(2, 3);
        Log.d("NDK", "result = " + result);
    }
}

至此,实现了Android集成非标准的SO库。 

注意:我这里演示的集成Android标准SO库和集成Android非标准SO库,用的是同一个工程,所以在集成Android非标准SO库时,需要将集成Android标准SO库时在app模块main目录下,创建jniLibs文件夹和libnativet0224.so删除,避免存在来个同名libnativet0224.so。


http://www.kler.cn/a/562373.html

相关文章:

  • java23种设计模式-工厂方法模式
  • 《Flink学习攻略:从入门到进阶的奇妙之旅》
  • MySQL 中表和视图的关系
  • 2025年2月最新SCI-中华穿山甲优化算法Chinese Pangolin Optimizer-附Matlab免费代码
  • P8665 [蓝桥杯 2018 省 A] 航班时间
  • Android开发奇葩bug:布局宽高不自动自适应了
  • 显式指定 ChromeDriver 路径
  • python安装matplotlib库报错
  • 分布式锁实现(数据库+Redis+Zookeeper)
  • ref和reactive的区别 Vue3
  • 好用的Docker项目:本地部署IOPaint打造专属在线图片处理工作站
  • 京东web 详情 cfe滑块分析
  • 【Elasticsearch】同一台服务器部署集群
  • 从零开始用react + tailwindcs + express + mongodb实现一个聊天程序(三) 实现注册 登录接口
  • HarmonyOS学习第7天: 文本组件点亮界面的文字魔法棒
  • 2025年第16届蓝桥杯嵌入式竞赛学习笔记(十):ADC测量电压
  • 如何实现日志采集以及存储以及问题排查
  • 面试-JVM:JVM的组成及作用
  • 三品PDM管理系统:企业产品数据管理的“智慧大脑”,如何破解安全隐私难题?
  • 微信小程序开发中CSS书写常见问题及最佳实践