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包:
然后我们再单独创建一个module用于编译生成so和jar(先切换成Project,然后创建module):
这里选择Android Library:
我们直接叫sdklibrary:
项目结构如下,后续的so和jar包的生成在sdk module下,生成后在app module中进行调用测试:
注意: 下面的这些操作都是基于sdk module的,下一篇我们再说如何调用so和jar,后续不再赘述。
二、创建java类
这里创建SDK类,后续我们的库也叫sdk,然后写一个getString和add的简单接口:
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文件夹
2、创建cpp文件夹及CMakeLists.txt等
创建cpp、include等,这样结构更清晰一些(new 普通的文件夹及文件即可):
四、生成c接口头文件
在terminal中进入到java目录下,输入javah -jni “包名.类名”,即会在java目录下生成头文件:
javah -jni com.knowyou.sdklibrary.SDK
上面的错误是由于中文注释引起的,不用管,或者可以删除中文注释后重新执行即可。
头文件中有准确的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目录结构如下:
六、配置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未高亮):
七、关联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即可:
常见问题,提示ndk未设置则在项目结构中设置ndk即可:
编译成功后结果如下(在cmake指定的位置生成过滤abi的so库):
九、生成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):
指定的classes.jar的位置不对,使用搜索工具在module目录下搜索classes.jar的位置后更改即可,比如我这里的位置:
def zipFile = file('build/intermediates/aar_main_jar/release/classes.jar')
在4.0的as中可以直接执行该打包功能,执行成功后在我们指定的build目录下生成了SDK_V1.0.jar:
十、最后
生成的so和jar包都成功了,下一次我们试一下看看ndk开发生成so和jar包如何使用。