AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包

AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包


版本说明

版本 作者 日期 备注
0.1 loon 2020.8.28 初稿

目录

文章目录

    • AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包
    • 版本说明
    • 目录
      • 一、创建项目
      • 二、创建java类
      • 三、创建jni文件夹
        • 1、创建jni文件夹
        • 2、创建cpp文件夹及CMakeLists.txt等
      • 四、生成c接口头文件
      • 五、完成c/c++代码
      • 六、配置CMake
      • 七、关联Gradle
      • 八、编译so
      • 九、生成jar包
      • 十、最后

总共分如下几步:

  • 1、创建项目,将测试module和生成so和jar的module分开;
  • 2、创建java类,外部直接调用时的接口;
  • 3、创建jni文件夹,并在其下创建CMakeLists.txt文件、cpp文件夹以及include文件夹等,这样结构更清晰;
  • 4、利用命令生成.h文件获取jni层的c接口;这样生成的接口不容易出问题;
  • 5、完成C接口;
  • 6、配置CMake;(用于编译c程序,链接第三方库等)
  • 7、关联Gradle;(指定CMakeLists路径,ndk编译的abi等)
  • 8、生成so;
  • 9、生成jar包;

一、创建项目

创建一个默认的empty activity模块一会用于测试我们编译好的so和jar包:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第1张图片
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第2张图片
然后我们再单独创建一个module用于编译生成so和jar(先切换成Project,然后创建module):
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第3张图片
这里选择Android Library:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第4张图片
我们直接叫sdklibrary:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第5张图片
项目结构如下,后续的so和jar包的生成在sdk module下,生成后在app module中进行调用测试:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第6张图片
注意: 下面的这些操作都是基于sdk module的,下一篇我们再说如何调用so和jar,后续不再赘述。

二、创建java类

这里创建SDK类,后续我们的库也叫sdk,然后写一个getString和add的简单接口:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第7张图片
SDK类的内容:

package com.knowyou.sdklibrary;

public class SDK {
    //调用sdk库
    static {
        System.loadLibrary("sdk");
    }

    //调用sdk原生接口getString
    public static native String getString();

    //调用原生接口getAdd
    public static native int getAdd(int numOne, int numTwo);
}

三、创建jni文件夹

1、创建jni文件夹

AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第8张图片

2、创建cpp文件夹及CMakeLists.txt等

创建cpp、include等,这样结构更清晰一些(new 普通的文件夹及文件即可):
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第9张图片

四、生成c接口头文件

在terminal中进入到java目录下,输入javah -jni “包名.类名”,即会在java目录下生成头文件:

javah -jni com.knowyou.sdklibrary.SDK

AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第10张图片
上面的错误是由于中文注释引起的,不用管,或者可以删除中文注释后重新执行即可。

头文件中有准确的jni c接口名称,这样我们再去实现c接口不容易写错接口名称,实在要自己写的话则命名规则为:Java_包名_类名_接口名

五、完成c/c++代码

将刚才生成的头文件移动到我们创建的include文件夹下,之后在cpp文件夹下创建SDK.cpp复制过来刚才生成的头文件中的接口,然后实现,并包含头文件即可(做了一些简单的修改):

#include "com_knowyou_sdklibrary_SDK.h"

JNIEXPORT jstring JNICALL Java_com_knowyou_sdk_SDK_getString(JNIEnv *env, jclass cls)
{
    char *str = "String from native cpp";

    return (*env).NewStringUTF(str);
}

JNIEXPORT jint JNICALL Java_com_knowyou_sdk_SDK_getAdd(JNIEnv *env, jclass cls, jint numOne, jint numTwo)
{
    return (numOne + numTwo);
}

之后的jni目录结构如下:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第11张图片

六、配置CMake

已经完成了c/c++ 的代码,那么我们需要完成CMakeLists.txt的编写,以此编译c/c++代码,其实还有一些其它方法,但是这里使用CMake进行跨平台编译是非常方便的,而且之前也用到CMake,算是对CMake有一些了解,所以我们直接使用CMake:
目前Android Studio编写CMakeLists.txt还不能自动补全,高亮也要整体build时才能触发,写的还是有些难受,但是基本是模板,可以自己收集一下自己常用的(或者在外部使用notepad++等其它工具编写好后导入即可):

# 指定最小cmake版本
cmake_minimum_required(VERSION 3.0.0)

# 设置so输出位置
# 这里从CMakeLists.txt的当前位置开始的,会自动创建libs以及abi分类文件夹
set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/libs/${ANDROID_ABI})

# 设置cpp源文件位置及源文件
# 这里的相对路径从CMakeLists.txt文件位置开始
file(GLOB CPP_FILES "cpp/*.cpp")

# 这里指定头文件的位置
include_directories(include/)

# 设置生成的so名称及库类型
add_library(
    sdk
	SHARED
	${CPP_FILES}
)

执行编译前(CMakeLists.txt未高亮):
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第12张图片

七、关联Gradle

这个是要将c/c++的编译关联到整个模块的编译中,所以修改该module的build.gradle这个脚本,修改后如下:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.knowyou.ndkcmakecompilecall"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/jni/CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

主要增加了abi过滤:

ndk {
    abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
}

以及这里的CMakeLists.txt的相对路径:

externalNativeBuild {
    cmake {
        path "src/main/jni/CMakeLists.txt"
    }
}

八、编译so

上面的配置完成后在sdk module上右键run all Tests即可:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第13张图片
常见问题,提示ndk未设置则在项目结构中设置ndk即可:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第14张图片
编译成功后结果如下(在cmake指定的位置生成过滤abi的so库):
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第15张图片

九、生成jar包

在模块的build.gradle中添加如下代码:

//设置sdk名称、版本、生成位置,解压的classes.jar包
def SDK_NAME = "SDK";
def SDK_VERSION = "_V1.0";
def sdkDestinationPath = "build";
def zipFile = file('build/intermediates/aar_main_jar/release/classes.jar')

//删除已编译的旧jar包
task deleteBuild(type: Delete) {
    delete sdkDestinationPath + SDK_NAME + SDK_VERSION + ".jar"
}

//编译jar包
task makeJar(type: Jar) {
    from zipTree(zipFile)
    from fileTree(dir: 'src/main',includes: ['assets/**'])
    baseName = SDK_NAME + SDK_VERSION
    destinationDir = file(sdkDestinationPath)
}

//执行上面的两个任务
makeJar.dependsOn(deleteBuild, build)

最终的build.gradle:

apply plugin: 'com.android.application'

android {
    compileSdkVersion 29
    buildToolsVersion "29.0.3"

    defaultConfig {
        applicationId "com.knowyou.ndkcmakecompilecall"
        minSdkVersion 19
        targetSdkVersion 29
        versionCode 1
        versionName "1.0"

        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"

        ndk {
            abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
        }
    }

    externalNativeBuild {
        cmake {
            path "src/main/jni/CMakeLists.txt"
        }
    }
}

dependencies {
    implementation fileTree(dir: "libs", include: ["*.jar"])
    implementation 'androidx.appcompat:appcompat:1.2.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

}

//设置sdk名称、版本、生成位置,解压的classes.jar包
def SDK_NAME = "SDK";
def SDK_VERSION = "_V1.0";
def sdkDestinationPath = "build";
def zipFile = file('build/intermediates/aar_main_jar/release/classes.jar')

//删除已编译的旧jar包
task deleteBuild(type: Delete) {
    delete sdkDestinationPath + SDK_NAME + SDK_VERSION + ".jar"
}

//编译jar包
task makeJar(type: Jar) {
    from zipTree(zipFile)
    from fileTree(dir: 'src/main',includes: ['assets/**'])
    baseName = SDK_NAME + SDK_VERSION
    destinationDir = file(sdkDestinationPath)
}

//执行上面的两个任务
makeJar.dependsOn(deleteBuild, build)

常见问题(找不到classes.jar):
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第16张图片
指定的classes.jar的位置不对,使用搜索工具在module目录下搜索classes.jar的位置后更改即可,比如我这里的位置:

def zipFile = file('build/intermediates/aar_main_jar/release/classes.jar')

在4.0的as中可以直接执行该打包功能,执行成功后在我们指定的build目录下生成了SDK_V1.0.jar:
AndroidStudio-NDK开发(一)cmake编译c代码提供so库和jar包_第17张图片

十、最后

生成的so和jar包都成功了,下一次我们试一下看看ndk开发生成so和jar包如何使用。

你可能感兴趣的:(android开发)