有效果的Android编译so文件及使用
0.前言
根据不成文的规则,这里还是要废话几句的。
看标题,我特意加上了“有效果”三字,没错,今天我来给大伙填坑了。
因公司业务需求,与第三方商家合作,需要调用商家提供的so库,对此知识一无所知的我踏上了一条踩坑之路。
1.导入so库
废话不多说,直接切入正题。
网上的方法,只要新建jni包然后将so库放入其中即可使用。但是,本文说的是编译,如果你要编译一个so库,这个库又需要引用本地so库,这个方法是不行的
2.编译so库
编译so库的方法主要分为mk编译和cmake编译这两种方式。
(1)mk方式编译独立的so库
先来看看mk编译的方式。
首先要在gradle.properties文件里面添加
android.useDeprecatedNdk=true
然后在build之中添加代码:
android{
` ` `
sourceSets {
main{
jni.srcDirs=[]; //禁用as自动生成mk
jniLibs.srcDirs = ['src/main/jniLibs']//设置目标的so存放路径
}
}
//设置编译任务,编译ndkBuild
tasks.withType(JavaCompile) {
compileTask -> compileTask.dependsOn 'ndkBuild'
}
` ` `
}
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
//ndk路径,一定要加上这部分
commandLine "C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",
'NDK_PROJECT_PATH=build/intermediates/ndk',
'NDK_LIBS_OUT=src/main/jniLibs',
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}
然后新建一个jni目录(相信大伙都懂,这里就不罗嗦怎么建立),在jni目录下添加Android.mk和Application.mk这两个文件,编写代码如下
- Android.mk:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := dp
LOCAL_SRC_FILES := dp.cpp
LOCAL_LDLIBS :=-llog
include $(BUILD_SHARED_LIBRARY)
dp即自己编写的cpp文件,位于jni目录下,名字可以随意取,但要对得上。
然在Application.mk文件里写明要编译的平台。比如
#模拟器是 x86_64 的
APP_ABI := x86_64
这里的x86_64指编译出x86_64平台的so库,如果选择all,即编译出所有的平台的so库。
然后运行一下,即可编译出对应的libdp.so库文件,放在['src/main/jniLibs']目录下。
这里特别指出重点:
task ndkBuild(type: Exec, description: 'Compile JNI source via NDK') {
//ndk路径,一定要加上这部分
commandLine "C:\\Users\\Administrator\\AppData\\Local\\Android\\Sdk\\ndk-bundle\\ndk-build.cmd",
'NDK_PROJECT_PATH=build/intermediates/ndk',
'NDK_LIBS_OUT=src/main/jniLibs',
'APP_BUILD_SCRIPT=src/main/jni/Android.mk',
'NDK_APPLICATION_MK=src/main/jni/Application.mk'
}
这段代码一定要加上去,不如编译不过。博主曾在网上搜到过片段代码与此相似,奈何其博主没有详细说明也没有完整说明,无法理解。此处给出完整例子希望有所帮助。(至于dp.cpp的代码,先不给出,这不是本文重点)
(2)mk方式引入第三方so库
为了便于说明,这里给第三方库取个名,就叫sb库好了,完整名是libsb.so
。
导入第三方so库,必须要注意他支持的平台,需要与你的Application.mk中设置的相对应(如果对不上,改为相对应的),否则会编译不通过的(重点,小编在这问题上被坑得死死的,希望读者能吸取小编的教训)。
接下来讲重点:
- 1.将libsb.so库放在jni目录下,然后重新编写Android.mk文件代码:
LOCAL_PATH := $(call my-dir)
#引入第三方 so
include $(CLEAR_VARS)
LOCAL_MODULE := sb
LOCAL_SRC_FILES := sb.so
LOCAL_EXPORT_C_INCLUDES := include
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
LOCAL_MODULE := dp
LOCAL_SRC_FILES := dp.cpp
LOCAL_LDLIBS :=-llog
#引入第三方编译模块
LOCAL_SHARED_LIBRARIES := \sb
include $(BUILD_SHARED_LIBRARY)
这样一来(只有这样),你就可以在dp.cpp代码里面编写调用libsb.so库里面的方法了。如果你只是单纯把它放到jniLibs目录下,那是不行的。
- 2.你还是得老老实实的把它放到jniLibs目录下,因为你的业务代码里面要用到它必然需要把它导进去,需要用到哪些库就放到jniLibs目录下,别忘了在这些so的上一层加上他的平台文件包。
如此一来,再run一下就没问题了。
(3)cmake方式编译
使用cmake方式编译,那就真的是太即便简便太简单了。我这么说读者可能会不服气,眼见为实吧。
- 1.编写cpp文件
正常操作,编写cpp文件,相信这一步不是问题,这里就取名为mb.cpp吧,此处就放在main/cpp/目录下好了。然后再编写cmake的代码(语法在你新建支持c++文件的项目时产生的cmakeLists.txt中有介绍)
这里直接给出代码:
add_library(mb SHARED src/main/cpp/mb.cpp)
target_link_libraries( # Specifies the target library
mb
${log-lib} )
先用add_library将mb添加进去,再在target_link_libraries里关联即可。
如果你的mb.cpp文件里面include了其他头文件,你还需加上
include_directories(scr/main/cpp/include/ )
声明头文件的部分,否则会编译失败找不到头文件的(坑)。
注意:习惯使用mk编译的童鞋这里注意了,使用cmake的方式编译是不会生成so文件的(坑),所以你不用去找libmb.so文件了=-=。
但是你还是要在你的代码里面导入库时把它倒进去:
System.loadLibrary("mb");
(4)cmake引入第三方so库
这里就给第三方so取名为nb好了(具体的当然要与读者的相同啦),完整名字libnb.so。
不废话,上代码:
#导入第三方so包
#IMPORTED 指只是把 so 导入到项目中
add_library( nb
SHARED
IMPORTED )
#指明 so 库的路径
set_target_properties( # Specifies the target library.
nb
# Specifies the parameter you want to define.
PROPERTIES IMPORTED_LOCATION
# Provides the path to the library you want to import.
${CMAKE_SOURCE_DIR}/src/main/jniLibs/${ANDROID_ABI}/libvvw.so )
#头文件路径
include_directories(scr/main/cpp/include/ )
add_library(mb SHARED src/main/cpp/mb.cpp)
target_link_libraries( # Specifies the target library.
mb
#关联第三方 so
nb
${log-lib} )
ANDROID_ABI指平台,同样,你需要将你的第三方so库放入到可识别的路径文件下(这里放在jniLibs目录下),上一层需加上平台的文件夹,比如/jniLibs/x86/libnb.so这样子,如果你这里选ANDROID_ABI,则会默认编译你在build中设定的平台,比如在build中加入:
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
defaultConfig {
applicationId "com.zhengsr.jnidemo_camke"
minSdkVersion 19
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
externalNativeBuild {
cmake {
cppFlags "-frtti -fexceptions"
}
}
ndk{
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'//重点
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
externalNativeBuild {
cmake {
path "CMakeLists.txt"
}
}
}
重点看:
ndk{
abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'//重点
}
那他就会默认编译 'x86', 'x86_64', 'armeabi', 'armeabi-v7a', 'arm64-v8a'平台的第三方so,如果你在这里使用了ANDROID_ABI,而你的jniLibs路径下恰好没有以上全部平台的libnb.so库,则会编译失败,这时你就需要将ANDROID_ABI改为你所拥有的平台或补充相应的so文件。
说到底,这一步,路径很重要,必须配置正确,否则会有一堆问题,然后网上一堆无关说法、解决方法等你去踩坑.
通过以上配置,就可以愉快的引用你的so库了,想怎么用就怎么用,不怕报在cpp文件里面找不到库里面的方法(某方法未声明)的错误
总结还是得要有的
3.总结
不管是mk编译方法还是cmake编译方法,都是可取的方法。但是相较而言,cmake编译的方法明显胜于mk编译的方法,不用生成so库文件即可使用。
二者的使用方法上有共通之处,需要注意的是路径一定要配置正确,
由于cpp的文件书写以及utils的代码相关部分本文并不给出,需要读者自行搜索了。希望此文对你有所帮助。
如有问题和错误的地方,欢迎留言提出!
原创文章,转载请附上