Cmake 之Android库编译

一 检测库和执行程序能否在Android上用

1.1 我们知道Cmake不止能编译Linux库程序,也能编译出其它系统的库,如windows,ios和android等,那么上一篇生成的Linux的库程序能否直接用于Android上呢,下面先来做个测试。

1.2 我们先在Linux平台生成一个最简单的C语言可执行程序,main.c

创建文件

bash 复制代码
touch main.c

修改文件,文本编辑器(如vi/vim、nano等)或图形界面编辑器(如gedit、kate等)打开并修改文件内容,ctrl+x保存退出

bash 复制代码
nano main.c
cpp 复制代码
#include <stdio.h>
int main(){
	printf("Hello Word\n");
	return 0;
}

生成执行程序

bash 复制代码
gcc main.c -o appexe

运行程序,可以看到正常输出文字

bash 复制代码
$ ./appexe
Hello Word

1.3 看执行程序能否在Android上运行。Android程序需要用到ADB命令,所以使用ADB工具来测试,提前先在Linux或者Windows上安装好ADB工具

1.4 如果ADB安装在Windows,那么可以把库复制到windows上来测试,下面以Windows为例,我现在D盘已经有了个Linux生成的appexe执行程序

1.5 adb调试,先Andoird机打开开发者选项来连接工具,用adb devices查看是否连接成功。出现设备说明连接成功

bash 复制代码
>adb devices
List of devices attached
10AC741Q7K000NG device

1.6 Win+R打开终端,用adb push命令来把文件移到Android系统上,因为Andoird其它也是Linux内核,所以是可以直接执行该程序的

bash 复制代码
adb push D:\VSProject\cmaketest4\appexe /sdcard

1.7 用shell工具来测试,打开shell

bash 复制代码
adb shell

1.8 进入sdcard 目录

bash 复制代码
$ cd sdcard/

1.9 根linux一样执行程序方式一样

bash 复制代码
./appexe

1.10 先报没有权限访问,chmod 需改权限也是不行的

bash 复制代码
chmod 777 appexe
bash 复制代码
/sdcard $ ./appexe
/system/bin/sh: ./appexe: can't execute: Permission denied

1.11 需要修改放执行程序的目录为临时目录

bash 复制代码
 adb push D:/VSProject/cmaketest4/appexe /data/local/tmp/

1.12 再次执行上面步骤

bash 复制代码
/data/local/tmp $ chmod 777 appexe
/data/local/tmp $ ./appexe

11.12 错误一,这时候看到错误信息变了,说不能执行ELF文件(这是因为非交叉编译的执行文件,不能用在Android系统上),那么下面就看怎样交叉编译

/data/local/tmp $ ./appexe
/system/bin/sh: ./appexe: not executable: 64-bit ELF file

二 AndroidNDK生成可执行程序

2.1 上面已经测试Linux生成的库程序是不能在Android上用的,那么怎样才能让Android也能使用呢,这就需要用到CMake交叉编译,交叉编译可以理解为跨平台编译,来实现库的跨平台使用。

2.2 由于C++编译器的不同,所以生成的库不能跨平台。想要生成Andorid能用的库就需要Andorid平台的C++编译工具。这个工具就是AndroidNDK,NDK下载官网链接:

https://developer.android.google.cn/ndk/downloads?hl=zh-cn

下载得到压缩包zip或tar或tar.gz

首先进入要解压的文件夹,我这是/home

cd /home

我压缩文件放在/mnt/d 目录下,所以解压命令是

tar -xvf /mnt/d/android-ndk-r17c.tar

这时候NDK的完整安装路径就是 /home/android-ndk-r17c,下面就配置NDK环境变量。

打开vim编辑器,输入命令字母"o",切换为插入模式(--INSERT--),可以在光标末尾插入新行对文件进行修改

sudo vim /etc/profile

在末尾添加NDK的安装路径

export ANDROID_NDK=/home/android-ndk-r17c

export PATH=PATH:ANDROID_NDK

保存并退出,按 esc 键,退出插入模式即可进入命令模式。在末尾输入 :wq ,回车即可写入保存并离开 。或者按大写ZZ也可以快捷保存退出

刷新使配置生效

source /etc/profile

. /etc/profile

source ~/.bashrc

查看环境变量是否有NDK路径

echo $PATH

验证配置是否成功

ndk-build -v

成功会打印如下信息:

bash 复制代码
$ ndk-build -v
GNU Make 3.81
Copyright (C) 2006  Free Software Foundation, Inc.
This is free software; see the source for copying conditions.
There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE.

This program built for x86_64-pc-linux-gnu

2.3 我们知道CMake只是构建工具,真正执行编译的是C++编译器,Linux平台的C++编译器是gcc,所以Linux的编译命令是:

bash 复制代码
gcc main.c -o appexe 

那么Android平台的编译工具在哪呢,就在NDK的toochins目录下

cpp 复制代码
 /home/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc main.c -o appexe

这个路径从哪来的呢,那就从NDK里面找支持的C++编译器,我的NDK安装在/home/android-ndk-r17c目录下,那么就一级一级找,如下所示

上面路径太长了,每次输入容易出错,那么就可以定义一个环境变量,怎样定义参考NDK环境变量设置

cpp 复制代码
export ANDROID_NDK_GCC="/home/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc"

简化后的指令就变为,后面CMake也要用到了

bash 复制代码
$ANDROID_NDK_GCC main.c -o appexe

2.4 错误二,提示找不到文件头

$ $ANDROID_NDK_GCC main.c -o appexe
main.c:1:19: fatal error: stdio.h: No such file or directory
#include <stdio.h>
^
compilation terminated.

配置 --sysroot 环境变量(--sysroot 自动寻找头文件,库文件)。

寻找库文件位置 ,可以看到里面全部的库

寻找头文件位置 ,可以看到里面全部的头文件

那配置库和头文件环境变量的路径就是

bash 复制代码
export SYSROOT="--sysroot=$ANDROID_NDK/platforms/android-21/arch-arm -isystem $ANDROID_NDK/sysroot/usr/include"

查看是否设置成功

bash 复制代码
$ echo $SYSROOT
--sysroot=/home/android-ndk-r17c/platforms/android-21/arch-arm -isystem /home/android-ndk-r17c/sysroot/usr/include

再次编译

bash 复制代码
$ANDROID_NDK_GCC $SYSROOT main.c -o appexe

2.5 出现第三个错误,意思执行过程中找不到 ams(汇编)支持

$ $ANDROID_NDK_GCC $SYSROOT main.c -o appexe
In file included from /home/android-ndk-r17c/sysroot/usr/include/sys/types.h:36:0,
from /home/android-ndk-r17c/sysroot/usr/include/stdio.h:42,
from main.c:1:
/home/android-ndk-r17c/sysroot/usr/include/linux/types.h:21:23: fatal error: asm/types.h: No such file or directory
#include <asm/types.h>
^
compilation terminated.

寻找asm路径

再次配置$SYSROOT的路径

bash 复制代码
$ export SYSROOT="--sysroot=$ANDROID_NDK/platforms/android-21/arch-arm -isystem $ANDROID_NDK/sysroot/usr/include -isystem $ANDROID_NDK/sysroot/usr/include/arm-linux-androideabi"

再次执行编译,不再报错,可以成功生成appex执行程序

bash 复制代码
$ $ANDROID_NDK_GCC $SYSROOT main.c -o appexe
$ ls
appexe  main.c

2.6 把文件按开头方法导入Android手机运行测试

bash 复制代码
C:\Users\dong>adb push D:/VSProject/cmaketest4/appexe /data/local/tmp/
D:/VSProject/cmaketest4/appexe: 1 file pushed, 0 skipped. 5.8 MB/s (6436 bytes in 0.001s)

C:\Users\dong>adb shell
PD2219:/ $ cd /data/local/tmp/
PD2219:/data/local/tmp $ ls
appexe                         device-explorer                                    sky.com.example.flutter_app.sha1
com.wnxds.tataim-build-id.txt  lldb-server                                        start_lldb_server.sh
databasesfinance.db            perfd
databasesfinance.db-wal        sky.com.dongfangdashu.tangshu.flutterproject.sha1
PD2219:/data/local/tmp $ chmod 777 appexe
PD2219:/data/local/tmp $ ./appexe
"./appexe": error: Android 5.0 and later only support position-independent executables (-fPIE).

2.7 adb运行最后一个错误,需要pie文件

/data/local/tmp $ ./appexe
"./appexe": error: Android 5.0 and later only support position-independent executables (-fPIE).

那就在末尾添加pie重新生成执行程序

bash 复制代码
$ $ANDROID_NDK_GCC $SYSROOT -pie main.c -o appexe

再次用adb测试,终于成功了,记录这一重要时刻

bash 复制代码
PD2219:/data/local/tmp $ rm -f appexe
PD2219:/data/local/tmp $ exit;

C:\Users\dong>adb push D:/VSProject/cmaketest4/appexe /data/local/tmp/
D:/VSProject/cmaketest4/appexe: 1 file pushed, 0 skipped. 14.1 MB/s (6488 bytes in 0.000s)

C:\Users\dong>adb shell
PD2219:/ $ cd /data/local/tmp/
PD2219:/data/local/tmp $  chmod 777 appexe
PD2219:/data/local/tmp $ ./appexe
Hello Word

2.8 NDK环境变量配置总结,要完成NDK对C/C++的编译至少需要配置以下环境变量

bash 复制代码
#NDK环境变量
export ANDROID_NDK=/home/android-ndk-r17c
#GCC环境变量
export ANDROID_NDK_GCC="/home/android-ndk-r17c/toolchains/arm-linux-androideabi-4.9/prebuilt/linux-x86_64/bin/arm-linux-androideabi-gcc"
#头文件,库文件环境变量
export SYSROOT="--sysroot=$ANDROID_NDK/platforms/android-21/arch-arm -isystem $ANDROID_NDK/sysroot/usr/include -isystem $ANDROID_NDK/sysroot/usr/include/arm-linux-androideabi"
#so库架构环境变量
export ANDROID_NDK_AR="/home/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"

三 Android 三方so库的导入

3.1 先用androidStuiod的Native项目生成一个so库

创建所需文件

calc.h

cpp 复制代码
#include "include/calc.h"

int Calc::add(int a, int b) {
    return a+b;
}

calc.cpp

cpp 复制代码
#include "include/calc.h"

int Calc::add(int a, int b) {
    return a+b;
}

calclib.cpp

cpp 复制代码
#include <jni.h>
#include <string>
#include <filesystem>
#include "calc.h"

extern "C" JNIEXPORT jstring JNICALL
Java_com_bob_nativelib_CalcTools_stringFromJNI(
        JNIEnv* env,
        jobject) {
    Calc calcclass;
    int value = (calcclass.add(10, 10));
    std::string valueString = std::to_string(value);
    std::string hello = "相加的值是:" + valueString;
//    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

CMakeLists.txt

cpp 复制代码
cmake_minimum_required(VERSION 3.10.2)
project("calclib")


#导入头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/)
add_library(
        calclib
        SHARED
        
        #所有源文件
        calc.cpp
        calcjni.cpp)
target_link_libraries(
        calclib)

CalcTools.java

java 复制代码
package com.bob.nativelib;


public class CalcTools {
    static {
        System.loadLibrary("calclib");
    }

    public native String stringFromJNI();
}

MainActivity.java

java 复制代码
public class MainActivity extends AppCompatActivity {
    private TextView tvText;

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

        //调用函数库
        CalcTools calcTools=new CalcTools();
        Log.e("EEE", calcTools.stringFromJNI() );

    }
}

成功会输出

java 复制代码
相加的值是:20

此时在build - cmake - debug - obj 目录下会生成对应架构的so库文件

3.2 上面我们已经生成了so库,那么我们怎样引用这些so库,来进行JNI交互呢。

先新建一个module,导入该so库和头文件到我们java项目里面

3.2 在build.gradle里面增加相关配置

java 复制代码
plugins {
    id 'com.android.library'
}

android {
    namespace 'com.xixia.jincmakelib'
    compileSdk 33

    defaultConfig {
        minSdk 21
        targetSdk 33

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        consumerProguardFiles "consumer-rules.pro"
        externalNativeBuild {
            cmake {
                cppFlags ""
            }
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }
    externalNativeBuild {
        cmake {
            path "src/main/cpp/CMakeLists.txt"
            version "3.22.1"
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {

    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.5.0'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
}

4.3 CMakeLists.txt 配置修改如下:

bash 复制代码
cmake_minimum_required(VERSION 3.22.1)

project("jincmakelib")

#导入库文件开始---------------------------------------------

#导入头文件
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/include/)


#导入库文件
add_library(
        calclib

        SHARED

        IMPORTED)
# 指定库的路径
set_target_properties(
        calclib

        PROPERTIES IMPORTED_LOCATION

        ${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_ANDROID_ARCH_ABI}/libcalclib.so
)
#导入库文件结束---------------------------------------------



add_library(
        jincmakelib

        SHARED

        jincmakelib.cpp)

find_library(
        log-lib

        log)

target_link_libraries(
        jincmakelib
        calclib
        ${log-lib})

3.4 编写JNI文件 jnicmaketest.cpp,调用动态库函数calc.so

cpp 复制代码
#include <jni.h>
#include <string>
#include <calc.h>

extern "C" JNIEXPORT jstring JNICALL
Java_com_xixia_jincmakelib_NativeCmakeLib_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    Calc calcclass;
    int value = (calcclass.add(10, 10));
    std::string valueString = std::to_string(value);
    std::string hello = "相加的值是:" + valueString;
    //std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

3.5 编写Java层接口NativeCmakeLib.java,加载jni库

java 复制代码
package com.xixia.jincmakelib;

public class NativeCmakeLib {

    static {
        System.loadLibrary("jincmakelib");
        System.loadLibrary("calclib");

    }

    public native String stringFromJNI();
}

至此JNI层就写好了,下面写java层来调用native层,注意so库名字一定要在前面加上lib,不然会报找不到so库错误

比如源来是这样写的

set_target_properties(

calclib

PROPERTIES IMPORTED_LOCATION

${CMAKE_CURRENT_SOURCE_DIR}/x86_64/calc.so

)

编译可以通过,但运行可能就会报错

java.lang.UnsatisfiedLinkError: dlopen failed: library "libcalclib.so" not found
at java.lang.Runtime.loadLibrary0(Runtime.java:1016)
at java.lang.System.loadLibrary(System.java:1669)
at com.xixia.jincmakelib.NativeCmakeLib.<clinit>(NativeCmakeLib.java:6)

按照提示改为libcalclib.so,当然也可能与编译有关,按提示名字改就行了

set_target_properties(

calclib

PROPERTIES IMPORTED_LOCATION

${CMAKE_CURRENT_SOURCE_DIR}/x86_64/libcalclib.so

)

3.6 java层调用JNI层,MainActivity.java

java 复制代码
public class MainActivity extends AppCompatActivity {
    private TextView tvText;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        tvText = (TextView) findViewById(R.id.tv_text);


        //Cmake测试so库
        NativeCmakeLib testCallBack = new NativeCmakeLib();

        String cpuArchitecture = Build.CPU_ABI; // 获取当前设备的 CPU 架构
        StringBuilder stringBuilder=new StringBuilder();
        stringBuilder.append("CPU架构:"+cpuArchitecture).append("\n");
        stringBuilder.append(testCallBack.stringFromJNI());
        tvText.setText(stringBuilder);
    }
}

能输出信息说明就成功了

java 复制代码
CPU架构:x86_64
相加的值是:20

3.7 编译过程可能遇到的错误:

错误1:

Build command failed.
Error while executing process D:\AndroidSdk\cmake\3.22.1\bin\ninja.exe with arguments {-C D:\AndroidProject\MyApplication\jincmakelib\.cxx\Debug\4r5q3z6v\x86_64 jincmakelib}
ninja: Entering directory `D:\AndroidProject\MyApplication\jincmakelib\.cxx\Debug\4r5q3z6v\x86_64'

ninja: error: 'src/x86_64/libcalclib.so', needed by 'D:/AndroidProject/MyApplication/jincmakelib/build/intermediates/cxx/Debug/4r5q3z6v/obj/x86_64/libjincmakelib.so', missing and no known rule to make it

报这样错误可能就是so文件路径没写对,比如我随便写个路径,找不断该路径就会报这错误

bash 复制代码
# 指定库的路径
set_target_properties(
        calclib

        PROPERTIES IMPORTED_LOCATION

        src/x86_64/libcalclib.so
)

这种把路径写对就可以了,相对路径,绝对路径都可以

bash 复制代码
# 指定库的路径
set_target_properties(
        calclib

        PROPERTIES IMPORTED_LOCATION

        ${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_ANDROID_ARCH_ABI}/libcalclib.so
)

错误2:

2 files found with path 'lib/x86_64/libcalclib.so' from inputs:

  • D:\AndroidProject\MyApplication\jincmakelib\build\intermediates\merged_jni_libs\debug\out\x86_64\libcalclib.so
  • D:\AndroidProject\MyApplication\jincmakelib\build\intermediates\cxx\Debug\4r5q3z6v\obj\x86_64\libcalclib.so
    If you are using jniLibs and CMake IMPORTED targets, see
    https://developer.android.com/r/tools/jniLibs-vs-imported-targets

会提示重复so库文件,这是因为我们可能放so库在jniLibs目录下,如下

bash 复制代码
# 指定库的路径
set_target_properties(
        calclib

        PROPERTIES IMPORTED_LOCATION

        ../jniLibs/x86_64/libcalclib.so
)

这个目录是自动加载so库的,我们在CMakelist.txt 里面又导入了一个,就会重复,所以我们可以放在另一个任意地方,不要放在jniLibs目录下。

bash 复制代码
# 指定库的路径
set_target_properties(
        calclib

        PROPERTIES IMPORTED_LOCATION

        ${CMAKE_CURRENT_SOURCE_DIR}/${CMAKE_ANDROID_ARCH_ABI}/libcalclib.so
)

四 AndroidNDK编译静态动态库

4.1 上面用androidStudio环境生成so库可以正常调用,那在linux环境编译出的库文件能否被android使用呢,下面来测试下

4.2 同样在cmaketest2文件夹下创建test.h和test.cpp文件

test.h

cpp 复制代码
#ifndef HEAD_H
#define HEAD_H

int add(int a,int b);


#endif

test.cpp

cpp 复制代码
#include "test.h"

int add(int a, int b) {
    return a+b;
}

main.cpp测试程序

bash 复制代码
#include <string>
#include "test.h"
int main(){
    int value = add(10, 10);
    printf("%d\n",value);
	return 0;
}

先用gcc编译器测试main.cpp是否运行正常,看到可以正常输出20

cpp 复制代码
cmaketest2$ gcc test.cpp main.cpp -o mainexe
cmaketest2$ ./mainexe
20

4.3 用AndroidNDK编译动态库,编译文成会在cmaketest2文件夹里面生成一个libcalc.so库文件

bash 复制代码
$ANDROID_NDK_GCC $SYSROOT -shared -fPIC test.cpp -o libcalc.so

4.4 用AndroidNDK编译静态库

静态库需要用到另一个编译类型,所以需要再配置一个环境变量

查找 arm-linux-androideabi-ar 位置

配置环境变量

bash 复制代码
export ANDROID_NDK_AR="/home/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-ar"

将这源文件编译成目标文件,生成test.o文件

bash 复制代码
$ANDROID_NDK_GCC -c test.cpp

接下来使用ar命令将目标文件打包成一个静态库文件calctlib.a

bash 复制代码
$ANDROID_NDK_AR rcs -o libcalc.a test.o

把编译出来的so库放进androdStudio里面,按上面第二标题方式编译运行,如果能成功输出信息,说明编译成功

cpp 复制代码
相加的值是:20

五 CMake配置交叉编译

5.1 上面已经用C++编译工具直接编译后的可执行程序演示,那么怎样用CMake进行交叉编译呢?来看下CMake需要增加的配置:

cpp 复制代码
cmake .. -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_NDK=$ANDROID_NKD \
-DANDROID_ABI=arm64-v8a \
-DANDROID_TOOLCHAIN=gcc \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=debug \
  • DCMAKE_TOOLCHAIN_FILE:指定ndk交叉编译链工具的路径,在ndk16版本以上,ndk自带cmake交叉编译工具链。
  • DANDROID_NDK:NDK的安装跟目录
  • DANDROID_ABI:各大平台:如armeabi-v7a、x86、mips等。android下基本编译一个armeabi-v7a就可以了,当然也可以加一个x86,方便在虚拟机上调试。
  • DANDROID_TOOLCHAIN:表示交叉编译链类型,取值gcc或者clang,clang++;gcc已经被废弃
  • DANDROID_PLATFORM:定义最低api版本
  • DCMAKE_BUILD_TYPE:定义构建类型,取值Debug或Release,Release

执行cmake后编译如下:

bash 复制代码
$ cmake .. -DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_NDK=$ANDROID_NKD \
-DANDROID_ABI=arm64-v8a \
-DANDROID_TOOLCHAIN=gcc \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=debug \
>
CMake Warning at /home/android-ndk-r17c/build/cmake/android.toolchain.cmake:185 (message):
  GCC is deprecated and will be removed in the next release.  See
  https://android.googlesource.com/platform/ndk/+/master/docs/ClangMigration.md.
Call Stack (most recent call first):
  /usr/share/cmake-3.22/Modules/CMakeDetermineSystem.cmake:124 (include)
  CMakeLists.txt:2 (project)


-- The C compiler identification is GNU 4.9.0
-- The CXX compiler identification is GNU 4.9.0
-- Detecting C compiler ABI info
CMake Warning at /home/android-ndk-r17c/build/cmake/android.toolchain.cmake:185 (message):
  GCC is deprecated and will be removed in the next release.  See
  https://android.googlesource.com/platform/ndk/+/master/docs/ClangMigration.md.
Call Stack (most recent call first):
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/3.22.1/CMakeSystem.cmake:6 (include)
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/CMakeTmp/CMakeLists.txt:3 (project)


-- Detecting C compiler ABI info - done
-- Check for working C compiler: /home/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-gcc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
CMake Warning at /home/android-ndk-r17c/build/cmake/android.toolchain.cmake:185 (message):
  GCC is deprecated and will be removed in the next release.  See
  https://android.googlesource.com/platform/ndk/+/master/docs/ClangMigration.md.
Call Stack (most recent call first):
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/3.22.1/CMakeSystem.cmake:6 (include)
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/CMakeTmp/CMakeLists.txt:3 (project)


-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /home/android-ndk-r17c/toolchains/aarch64-linux-android-4.9/prebuilt/linux-x86_64/bin/aarch64-linux-android-g++ - skipped
-- Detecting CXX compile features
CMake Warning at /home/android-ndk-r17c/build/cmake/android.toolchain.cmake:185 (message):
  GCC is deprecated and will be removed in the next release.  See
  https://android.googlesource.com/platform/ndk/+/master/docs/ClangMigration.md.
Call Stack (most recent call first):
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/3.22.1/CMakeSystem.cmake:6 (include)
  /mnt/d/VSProject/cmaketest/build/CMakeFiles/CMakeTmp/CMakeLists.txt:3 (project)


-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/d/VSProject/cmaketest/build

然后再执行make,可以成功生成so库

bash 复制代码
build$ make
[ 25%] Building C object CMakeFiles/calc.dir/src/main.c.o
[ 50%] Building C object CMakeFiles/calc.dir/src/test1.c.o
[ 75%] Building C object CMakeFiles/calc.dir/src/test2.c.o
[100%] Linking C shared library ../outpath/lib/libcalc.so
[100%] Built target calc

5.2 脚本进行cmake编译

上面步骤可以写在一个脚本里面,增加可读性,简化控制台流程

第一步放头文件,源文件,脚本文件在一个文件夹里面,如下结构

cmaketest2

├── CMakeLists.txt

├── include

│ └── test.h

└── src

├── test.cpp

└── main.cpp

CMakeList.txt 内容:

bash 复制代码
cmake_minimum_required(VERSION 3.0)
project(cmaketest2)

# 输出库文件目录
set(MYPATH /mnt/d/VSProject/cmaketest2/outpath)
set(LIBRARY_OUTPUT_PATH ${MYPATH}/lib)

# 示例:
set(SOURCE_DIR /mnt/d/VSProject/cmaketest2)
# 包含头文件
include_directories(${SOURCE_DIR}/include)
file(GLOB SRC_LIST ${SOURCE_DIR}/src/*.cpp)


# 包含库路径
# link_directories(${MYPATH}/lib)

# 链接静态库
# link_libraries(calc)

# 启动程序
# add_executable(apptest  ${SRC_LIST})
# add_library(calc STATIC ${SRC_LIST})
add_library(calc SHARED ${SRC_LIST})

# 程序启动后链接动态库
# target_link_libraries(apptest calc)


# 输出一般日志信息
# message(STATUS "STATUS Message")
# 输出警告信息
# message(WARNING "WARNING Message")
# 输出错误信息
# message(FATAL_ERROR "FATAL_ERROR Message")

第二步cmaketest项目目录,touch命令 新建 myscript.sh 脚本

bash 复制代码
touch myscript.sh

第三步打开编辑器

bash 复制代码
nano myscript.sh

第四步编写脚本内容

bash 复制代码
#!/bin/bash
rm -r build
mkdir build
cd build
cmake -DCMAKE_SYSTEM_NAME=Android \
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
-DANDROID_NDK=$ANDROID_NKD \
-DANDROID_ABI=x86_64 \
-DANDROID_TOOLCHAIN=gcc \
-DANDROID_PLATFORM=android-21 \
-DCMAKE_BUILD_TYPE=debug \
..
make
cd ..

第五步编辑好后保存退出

bash 复制代码
Ctrl + O   # 保存文件
Enter      # 确认保存
Ctrl + X   # 退出编辑器或者关闭当前的shell会话 

第六步查看文件权限

bash 复制代码
ls -l myscript.sh 

第七步添加脚本文件权限

bash 复制代码
chmod +x myscript.sh 

第八步执行该脚本

bash 复制代码
./myscript.sh

注意,可能会报换行错误,找不到文件

$ ./myscript.sh
rm: cannot remove 'build'$'\r': No such file or directory

这时候需要设置编码方式,打开vim文件

vim myscript.sh

设置格式,输入以下指令

: set fileformat=unix

再次输入保存退出指令

: wq

再次执行脚本就可以成功了

bash 复制代码
$ ./myscript.sh
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
-- Build files have been written to: /mnt/d/VSProject/cmaketest/build
[ 25%] Building C object CMakeFiles/calc.dir/src/main.c.o
[ 50%] Building C object CMakeFiles/calc.dir/src/test1.c.o
[ 75%] Building C object CMakeFiles/calc.dir/src/test2.c.o
[100%] Linking C static library ../outpath/lib/libcalc.a
[100%] Built target calc

5.3 编译后会生成以下目录

cmaketest2

├── build

├── CMakeLists.txt

├── include

│ └── test.h

├── outpath

│ └── lib

│ └── libcalc.so # 动态库的名字

└── src

├── test.cpp

└── main.cpp

把编译出来的so库放进androdStudio里面,按上面第二标题方式编译运行

可以看到也能成功输出

cpp 复制代码
相加的值是:20

六 总结

编译C/C++到Android平台的so库的三种方式:

1,源码编译,把C/C++源码完成的复制到Android的项目里面,Android用jni接口来调用你C/C++代码,这种是最原始最简单的方式,但也伴随着兼容性,安全性,维护性等方面问题,所以一半不采用这种方案。

2,C/C++编译器,在Linux平台通过GCC编译器把C/C++程序编译为目标架构的so库,然后Android导入so库编译运行。

3,Cmake构建编译器脚本,通过简单配置把C/C++编译为so库,供android使用。

相关推荐
闲暇部落23 分钟前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX2 小时前
Android 分区相关介绍
android
大白要努力!3 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee3 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip
Winston Wood4 小时前
Perfetto学习大全
android·性能优化·perfetto
Dnelic-7 小时前
【单元测试】【Android】JUnit 4 和 JUnit 5 的差异记录
android·junit·单元测试·android studio·自学笔记
Eastsea.Chen9 小时前
MTK Android12 user版本MtkLogger
android·framework
长亭外的少年16 小时前
Kotlin 编译失败问题及解决方案:从守护进程到 Gradle 配置
android·开发语言·kotlin
建群新人小猿19 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
1024小神20 小时前
tauri2.0版本开发苹果ios和安卓android应用,环境搭建和最后编译为apk
android·ios·tauri