再看!NDK交叉编译动态库并在Android中调用

一、前言

前面文章写过了,使用NDK交叉编译C/C++文件为静态库和动态库,在安卓中调用,当时环境在windows上,不知道是不是环境原因导致动态库无法使用,这次电脑环境为纯Ubuntu系统,再来重走一下来时路...

二、编译动态库

方式一(只在Ubuntu上使用)

假设,你的电脑上已经安装了NDK了

查看NDK版本

bash 复制代码
ls ~/Android/Sdk/ndk/

这个是AS默认创建的SDK路径,如果你电脑上有NDK的话,会有以下输出:21.4.7075529 23.1.7779620

1. 选择NDK版本

建议使用较新的

bash 复制代码
export NDK_HOME=~/Android/Sdk/ndk/23.1.7779620  

2. 创建项目目录结构

bash 复制代码
mkdir -p native-lib/jni
cd native-lib

3. 准备源文件

test.c文件放入jni目录,内容如下:

bash 复制代码
 #include <stdio.h>
 #include <android/log.h>

 int test_function() {
     __android_log_print(ANDROID_LOG_INFO, "NativeLib", "Hello from C!");
     return 20250429;
 }

4. 创建编译配置文件

创建jni/Android.mk

bash 复制代码
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)
LOCAL_MODULE    := testlib
LOCAL_SRC_FILES := test.c
LOCAL_LDLIBS    := -llog

include $(BUILD_SHARED_LIBRARY)

创建jni/Application.mk

bash 复制代码
APP_ABI := armeabi-v7a arm64-v8a 
APP_PLATFORM := android-21

5. 编译动态库

bash 复制代码
$NDK_HOME/ndk-build

编译会有以下内容输出:

bash 复制代码
xaye@orange:~/dev/native-lib$ $NDK_HOME/ndk-build
[armeabi-v7a] Compile thumb  : testlib <= test.c
[armeabi-v7a] SharedLibrary  : libtestlib.so
[armeabi-v7a] Install        : libtestlib.so => libs/armeabi-v7a/libtestlib.so
[arm64-v8a] Compile        : testlib <= test.c
[arm64-v8a] SharedLibrary  : libtestlib.so
[arm64-v8a] Install        : libtestlib.so => libs/arm64-v8a/libtestlib.so

此时的目录结构为:

bash 复制代码
libs/
├── arm64-v8a/
│   └── libtestlib.so
├── armeabi-v7a/
│   └── libtestlib.so
obj/ (中间文件)

方式一,编译动态库结束!

方式二(Ubuntu和Windows上都可)

在安卓项目中编译

项目目录结构通常如下

bash 复制代码
你的Android项目/
├── app/
│   ├── src/
│   │   ├── main/
│   │   │   ├── java/        # Java代码
│   │   │   ├── cpp/         # C/C++代码(可选)
│   │   │   └── jniLibs/     # 预编译的.so文件(如果不用CMake直接编译)
│   ├── build.gradle         # 模块配置
│   └── CMakeLists.txt       # (推荐位置)CMake配置文件
└── ...
  • app/目录下创建CMakeLists.txt(如果不存在)

  • 将你的test.c文件放在app/src/main/cpp/目录下

  • 修改app/build.gradle

bash 复制代码
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags ""
                // 指定ABI(可选)
                abiFilters "arm64-v8a", "armeabi-v7a"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "CMakeLists.txt"
        }
    }
}
  • CMakeLists.txt内容:
bash 复制代码
cmake_minimum_required(VERSION 3.4.1)
add_library(testlib SHARED src/main/cpp/test.c)
find_library(log-lib log)
target_link_libraries(testlib ${log-lib})

然后编译项目 生成debug apk,在下面目录就可以找到生成的动态库

方式二,编译动态库结束!

三、验证动态库在安卓项目中使用

写一个 .cpp(JNI桥接层),在 .cppextern "C" 引用 .so 里面的函数,然后正常JNI导出给Java调用!

将编译好的动态库,放到如下目录中:

bash 复制代码
app/src/main/jniLibs/arm64-v8a/libtestlib.so
app/src/main/jniLibs/armeabi-v7a/libtestlib.so

在cpp 目录下,创建 native_bridge.cpp 文件,内容如下:

bash 复制代码
#include <jni.h>

extern "C"
// 声明你.so里暴露的C函数
int test_function();


extern "C"
JNIEXPORT jint JNICALL
Java_com_xaye_testcompiler_NativeLib_test_1function(JNIEnv *env, jclass thiz) {
    // 调用.so里的原生函数
    return test_function();
}
  • 这里 Java_com_xaye_testcompiler_NativeLib_test_1function 是标准 JNI 规范。

  • 写一个JNI包装方法,在里面调用 .so 里的 test_function()

配置 CMakeLists.txt 来链接你的 .so

在你的 CMakeLists.txt 里:

bash 复制代码
cmake_minimum_required(VERSION 3.4.1)

# 指向你的 .so 和头文件
add_library(testlib SHARED IMPORTED)
set_target_properties(testlib PROPERTIES IMPORTED_LOCATION
        ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}/libtestlib.so
)

# 编译自己的 native_bridge.cpp
add_library(native-bridge SHARED native_bridge.cpp)

# 链接你的 native-bridge 依赖 testlib.so
target_link_libraries(native-bridge testlib log)
  • testlib.so放在 app/src/main/jniLibs/armeabi-v7a/arm64-v8a/ 下。

  • native-bridge 是你自己编译出来的新 .so,Android系统加载的是你这层。

  • 修改app/build.gradle

bash 复制代码
android {
    defaultConfig {
        externalNativeBuild {
            cmake {
                cppFlags ""
                // 指定ABI(可选)
                abiFilters "arm64-v8a", "armeabi-v7a"
            }
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
        }
    }
}

注意,CMakeLists.txt 放在了src/main/cpp/下,这里的路径要和实际的一样!

Java 层使用方式:

java 复制代码
package com.xaye.testcompiler;

public class NativeLib {
    static {
        System.loadLibrary("native-bridge"); // 只加载你编译的这个桥接so!
    }

    public static native int test_function();
}

调用:

java 复制代码
int result = NativeLib.test_function();
Log.d("TEST", "C 函数返回值: " + result); 

结果:

最后

回头看下,也没有那么复杂,大致流程就是正确编译出动态库,由于安卓调用C/C++代码,需要JNI,所以再写个.cpp文件,链接动态库和安卓代码,这个桥接作用的.cpp文件,就是使用JNI技术,桥接代码就是CMakeLists.txt,结束!

相关推荐
百锦再2 小时前
Android Studio开发中Application和Activity生命周期详解
android·java·ide·app·gradle·android studio·studio
移动开发者1号3 小时前
Android现代进度条替代方案
android·app
万户猴3 小时前
【Android蓝牙开发实战-11】蓝牙BLE多连接机制全解析1
android·蓝牙
RichardLai883 小时前
[Flutter 基础] - Flutter基础组件 - Icon
android·flutter
前行的小黑炭3 小时前
Android LiveData源码分析:为什么他刷新数据比Handler好,能更节省资源,解决内存泄漏的隐患;
android·kotlin·android jetpack
清霜之辰3 小时前
安卓 Compose 相对传统 View 的优势
android·内存·性能·compose
一杯凉白开3 小时前
Android View 事件的分发机制 四句口诀 先问拦截再派送,子不处理父兜底, 一旦消费无后续, 滑动冲突靠逻辑。
android
冬田里的一把火34 小时前
[Android]导航栏中插入电源菜单
android
星途码客4 小时前
SQLyog中DELIMITER执行存储过程时出现的前置缩进问题
android