NDK交叉编译及so库导入Android项目

2,598 阅读3分钟

个人博客

www.milovetingting.cn

前言

记录NDK交叉编译及so库导入Android项目的简单步骤,以备后续用到时查看。

环境

在Linux和Mac环境下,分别编译输出so库。

Red Hat Enterprise Linux 8 64 位 使用GCC编译(也可以用CLANG,这里演示用GCC)

macOS Big Sur 11.3.1 使用CLANG编译(也可以用GCC,这里演示用CLANG)

下载NDK

这里只演示下载NDK17,项目中Mac用到的NDK版本为NDK21

下载NDK

wget https://dl.google.com/android/repository/android-ndk-r17c-linux-x86_64.zip

NDK18及之后的NDK版本,建议使用CLANG编译。

解压NDK

unzip android-ndk-r17c-linux-x86_64.zip

解压后得到android-ndk-r17c文件夹

编写头文件及c文件

GCC编译

#include "get.h"

int get(){
  return 666;
}
#include "get.h"

int get(){
  return 666;
}

CLANG编译

#include <stdio.h>

int hi();

#include "hi.h"

int hi(){
  return 888;
}

配置NDK

Linux(使用GCC编译)

编辑Home/用户 目录下的.bashrc

vim /home/wangyz/.bashrc

添加以下内容

# 配置NDK的目录
export NDK_HOME=/home/wangyz/NDK/android-ndk-r17c
# 将NDK目录加入PATH中
export PATH=$PATH:$NDK_HOME

# x86 CPU架构的gcc
export NDK_GCC_x86=$NDK_HOME/toolchains/x86-4.9/prebuilt/linux-x86_64/bin/i686-linux-android-gcc

# x86_64 CPU架构的gcc
export NDK_GCC_x64=$NDK_HOME/toolchains/x86_64-4.9/prebuilt/linux-x86_64/bin/x86_64-linux-android-gcc

# ARM CPU架构的gcc
export NDK_GCC_ARM=$NDK_HOME/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc

# ARM64 CPU架构的gcc
export NDK_GCC_ARM_64=$NDK_HOME/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc

# x86 CPU架构 配置sysroot,isystem,否则会找不到头文件
export NDK_GCC_CONFIG_x86="--sysroot=$NDK_HOME/platforms/android-21/arch-x86 -isystem $NDK_HOME/sysroot/usr/include -isystem $NDK_HOME/sysroot/usr/include/i686-linux-android"

# x86_64 CPU架构 配置sysroot,isystem,否则会找不到头文件
export NDK_GCC_CONFIG_x64="--sysroot=$NDK_HOME/platforms/android-21/arch-x86_64 -isystem $NDK_HOME/sysroot/usr/include -isystem $NDK_HOME/sysroot/usr/include/x86_64-linux-android"

# ARM CPU架构 配置sysroot,isystem,否则会找不到头文件
export NDK_GCC_CONFIG_ARM="--sysroot=$NDK_HOME/platforms/android-21/arch-arm -isystem $NDK_HOME/sysroot/usr/include -isystem $NDK_HOME/sysroot/usr/include/arm-linux-androideabi"

# ARM64 CPU架构 配置sysroot,isystem,否则会找不到头文件
export NDK_GCC_CONFIG_ARM_64="--sysroot=$NDK_HOME/platforms/android-21/arch-arm64 -isystem $NDK_HOME/sysroot/usr/include -isystem $NDK_HOME/sysroot/usr/include/aarch64-linux-android"

Mac(使用CLANG编译)

修改~/.bash_profile

vim ~/.bash_profile

添加以下内容

# NDK目录
export NDK_HOME=/Users/ringle/Library/Android/sdk/ndk/21.1.6352462

# CLANG目录
export CLANG=${NDK_HOME}/toolchains/llvm/prebuilt/darwin-x86_64/bin

# 添加到PATH中
export PATH=${PATH}:${NDK_HOME}:${CLANG}

编译

这里编译ARM64构架的so

GCC

$NDK_GCC_ARM_64 $NDK_GCC_CONFIG_ARM_64 -fPIC -shared get.c -o libndk-linux.so

CLANG

aarch64-linux-android21-clang -fPIC -shared hi.c -o libndk-mac.so

导入Android Studio

复制so到项目中

在app/src/main 目录下新建jniLibs目录,再新建arm64-v8a目录,将编译生成的libndk-linux.so及libndk-mac.so复制到目录下

配置cmake

在app/src/main 目录下新建cpp目录,新建CMakeLists.txt,配置如下:


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

# Declares and names the project.

project("ndk")

# 包含所有CPP文件
file(GLOB allCPP *.cpp)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.

add_library( # Sets the name of the library.
        native-lib

        # Sets the library as a shared library.
        SHARED

        # Provides a relative path to your source file(s).
        ${allCPP})

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
        log-lib

        # Specifies the name of the NDK library that
        # you want CMake to locate.
        log)

set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -L${CMAKE_SOURCE_DIR}/../jniLibs/${CMAKE_ANDROID_ARCH_ABI}")

# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
        native-lib

        # Links the target library to the log library
        # included in the NDK.
        ${log-lib}
        # 链接libndk-mac
        ndk-mac
        # 链接libndk-linux
        ndk-linux
        )

配置gradle

配置app模块下的build.gralde文件


android {

    defaultConfig {
        //...
        
        externalNativeBuild {
            cmake {
                abiFilters "arm64-v8a"
            }
        }
        ndk {
            abiFilters "arm64-v8a"
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.10.2"
        }
    }
}

引用so中的方法

在cpp目录下,新建native-lib.cpp

#include <jni.h>
#include <string>
#include <android/log.h>

#define TAG "Wangyz"

#define LOG_I(...) __android_log_print(ANDROID_LOG_INFO, TAG,  __VA_ARGS__);

extern "C" int get();

extern "C" int hi();

extern "C" JNIEXPORT jstring JNICALL
Java_com_wangyz_ndk_MainActivity_stringFromJNI(
        JNIEnv *env,jobject /* this */) {
    int a = get();
    LOG_I("hello:%d", a);

    int b = hi();
    LOG_I("hi:%d", b);

    return env->NewStringUTF("hello");
}

Activity中调用


public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("ndk-mac");
        System.loadLibrary("ndk-linux");
        System.loadLibrary("native-lib");
    }

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

        // Example of a call to a native method
        TextView tv = findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * A native method that is implemented by the 'native-lib' native library,
     * which is packaged with this application.
     */
    public native String stringFromJNI();
}