Android Gradle 添加静态库
Android Gradle Adding static library
在传统的 android ndk 中,我们将在 Android.mk 文件中指定要 linked 的静态库。
Android.mk
PLATFORM_PREFIX := /opt/android-ext/
LOCAL_PATH := $(PLATFORM_PREFIX)/lib
include $(CLEAR_VARS)
LOCAL_MODULE := library
LOCAL_SRC_FILES := library.a
include $(PREBUILT_STATIC_LIBRARY)
LOCAL_STATIC_LIBRARIES := android_native_app_glue library
这是我的问题
切换到 NDK 的 Gradle 实验性插件时,我有点困惑。分享您关于如何 link 应用 build.gradle 文件中的静态库的想法。
我已经关注了最新的 gradle 实验插件文档 given here.
看看这个 sample。
告诉编译器 headers 在哪里(在 android.ndk{}
中):
CFlags += "-I${file("path/to/headers")}".toString()
cppFlags += CFlags
告诉链接器 .a 文件在哪里(在 android.ndk{} 或哪里
定义口味 - 确保添加 abiFilter - 例如
abiFilters += "armeabi-v7"
)
ldFlags += "-L${file(path/to/library.a)}".toString()
ldLibs += ["nameOfLibrary"]
注意库的名字按照约定是后面的字符串
"lib" 在 .a 文件名中。例如,对于名为 libNative.a 的文件
您应该将 ldLibs += ["native"] 添加到 gradle.
- 创建一个新模块并使用
apply plugin: 'java'
应用java插件。在 build.gradle 中编写必要的代码以获取 .a 文件并将其放置在适当的目录中(您将从使用它的模块中获取它)。不要忘记使用库(compile project(':libraryModule')
in dependencies{}
)在模块中添加依赖项,并将其包含在 settings.gradle 的项目中文件 include ':libraryModule'
。如果您想将模块放在您指定的文件夹中(例如,当前 Android.mk 文件所在的位置),只需添加 project(':libraryModule').projectDir = new File(settingsDir, 'path/to/module')
.
应该可以了。
从概念上讲,apk 是清单、本机文件和 class 库的 zip。
因此,如果您将静态库复制到输出,它将起作用。
所以在 gradle 你应该使用复制任务并将这些库作为输出的一部分。
我只是在下面使用了我的 so 文件并且它有效。
task copyNativeLibs2h(type: Copy) {
from(new File(getProjectDir(), 'libs')) { include '**/*.so' }
into new File(buildDir, 'native-libs')
}
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyNativeLibs2h }
即使使用实验性插件,您也无法使用 gradle 构建静态库。您可以使用预建库,或使用 ndk-build
和 link 将其构建到带有 gradle 插件的共享对象中。
这是一个例子:
假设我们有这样的目录结构:
- project (build.gradle, gradle.properties, etc.)
- jni_static (Application.mk, Android.mk, cpp files for the static lib)
- app (build.gradle, src/main, etc.)
- jni_shared (cpp files for the shared lib)
这里是project/app/build的相关片段。gradle:
// look for NDK directory
import org.apache.tools.ant.taskdefs.condition.Os
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkBuild = properties.getProperty('ndk.dir') + '/ndk-build'
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
ndkBuild += '.cmd'
}
apply plugin: 'com.android.model.application'
... 依赖项、模型 { compileOptions 等
// static lib is built with ndk-build
def LOCAL_MODULE = "static"
def appAbi = "armeabi-v7a"
def ndkOut = "build/intermediates/$LOCAL_MODULE"
def staticLibPath = "$ndkOut/local/$appAbi/lib${LOCAL_MODULE}.a"
// To guarantee that the intermediates shared library is always refreshed,
// we delete it in gradle task rmSO.
task rmSO(type: Delete) {
delete 'build/intermediates/binaries/debug/lib/armeabi-v7a', 'libshared.so'
delete 'build/intermediates/binaries/debug/obj/armeabi-v7a', 'libshared.so'
}
// in file jni/Android.mk there is a section for LOCAL_MODULE=static
// which builds the static library
task buildStaticLib(type: Exec, description: 'Compile Static lib via NDK') {
commandLine "$ndkBuild", "$staticLibPath", "NDK_APPLICATION_MK=../jni_static/Application.mk",
"NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
dependsOn rmSO
}
task cleanNative(type: Exec, description: 'Clean JNI object files') {
commandLine "$ndkBuild", "clean", "NDK_APPLICATION_MK=../jni_static/Application.mk",
"NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
}
clean.dependsOn cleanNative
tasks.all {
task ->
// link of the shared library depends on build of the static lib
if (task.name.startsWith('link')) {
task.dependsOn buildStaticLib
}
// before build, make sure the intermediate so is not stuck there
if (task.name.startsWith('package')) {
task.dependsOn rmSO
}
}
// build the wrapper shared lib around the static lib using the experimental plugin
model {
android.ndk {
moduleName = "shared"
cppFlags += "-std=c++11"
ldFlags += "$staticLibPath".toString()
ldLibs += "log"
stl = "gnustl_static"
abiFilters += "$appAbi".toString()
}
android.sources {
main.jni.source {
srcDirs = ["jni_shared"]
}
}
}
重要:共享库的源码应该在单独的目录下,这样静态库的源码就不会在里面或下面。这是因为实验插件无法排除srcDirs
.
下的部分文件
以上答案解决了 gradle 之前 NDK 集成不足的问题。此答案说明了与 NDK 的新 gradle 集成。
看看这个针对 gradle 2.9 和 android 插件 0.6.0-alpha1 编写的建议 sample。与问题的提出方式相反,此答案包含一个单独的图书馆项目。可以探索此功能以允许 gradle 在应用程序项目使用该库之前构建该库。其他答案依赖于图书馆已经建成的假设。
:secondlib com.android.model.application
构建 libsecondlib.so(在 Java 代码中加载 System.loadLibrary("secondlib")。名称 'secondlib' 是命名不当。我喜欢将其视为所有其他本机库 link 的 .so "wrapper" 供应用程序使用。
该共享库是针对 firstlib.a 静态 link 编辑的,由 :firstlib com.android.model.native
.
构建
根据 exportedHeaders
子句,headers 从 :firstlib 导出到任何依赖项目(本例中为:secondlib)。这样依赖项目知道如何 link 反对 .so/.a。这取代了先前答案中的 CFlags+="-I/path/to/headers" 语法。
:secondlib links 根据以下子句静态地反对 :firstlib:
android.sources {
main {
jni {
dependencies {
project ":firstlib" buildType "debug" linkage "static"
}
}
// TODO(proppy): show jniLibs dependencies on .so
}
}
如评论所示,示例不完整。完成语法显示在 experimental android plugin documentation 的 'NDK Dependencies' 部分。在该文档中,关于如何静态 link 而不是动态 link 的语法应该很清楚。
目前存在一些缺点(例如,上面显示的依赖项的 buildType 默认为 'debug' 而不是当前正在构建的 buildType)。
编辑:这是 app/build 中新依赖语法的 work-in-progress 示例,gradle 从我的一个项目中提取:
android.sources {
main {
jni {
//for exportedHeaders
dependencies { project ":libfoo" linkage "shared" }
}
jniLibs {
//Where the swig wrapped library .so is. I use swig to create code to interface with libfoo.so within :app
source { srcDirs 'libs' }
//for file in $(model.repositories.libs.libfoo)
dependencies { library "libfoo" }
}
}
}
repositories {
libs(PrebuiltLibraries) {
libevdev {
//headers already available from our libfoo project via exportedHeaders
//headers.srcDir "../libfoo/src/main/jni/"
binaries.withType(SharedLibraryBinary) {
sharedLibraryFile = file("../libfoo/build/intermediates/binaries/debug/lib/${targetPlatform.getName()}/libfoo.so")
}
}
}
}
在传统的 android ndk 中,我们将在 Android.mk 文件中指定要 linked 的静态库。
Android.mk
PLATFORM_PREFIX := /opt/android-ext/
LOCAL_PATH := $(PLATFORM_PREFIX)/lib
include $(CLEAR_VARS)
LOCAL_MODULE := library
LOCAL_SRC_FILES := library.a
include $(PREBUILT_STATIC_LIBRARY)
LOCAL_STATIC_LIBRARIES := android_native_app_glue library
这是我的问题
切换到 NDK 的 Gradle 实验性插件时,我有点困惑。分享您关于如何 link 应用 build.gradle 文件中的静态库的想法。
我已经关注了最新的 gradle 实验插件文档 given here.
看看这个 sample。
告诉编译器 headers 在哪里(在
android.ndk{}
中):CFlags += "-I${file("path/to/headers")}".toString() cppFlags += CFlags
告诉链接器 .a 文件在哪里(在 android.ndk{} 或哪里 定义口味 - 确保添加 abiFilter - 例如
abiFilters += "armeabi-v7"
)ldFlags += "-L${file(path/to/library.a)}".toString() ldLibs += ["nameOfLibrary"]
注意库的名字按照约定是后面的字符串 "lib" 在 .a 文件名中。例如,对于名为 libNative.a 的文件 您应该将 ldLibs += ["native"] 添加到 gradle.
- 创建一个新模块并使用
apply plugin: 'java'
应用java插件。在 build.gradle 中编写必要的代码以获取 .a 文件并将其放置在适当的目录中(您将从使用它的模块中获取它)。不要忘记使用库(compile project(':libraryModule')
independencies{}
)在模块中添加依赖项,并将其包含在 settings.gradle 的项目中文件include ':libraryModule'
。如果您想将模块放在您指定的文件夹中(例如,当前 Android.mk 文件所在的位置),只需添加project(':libraryModule').projectDir = new File(settingsDir, 'path/to/module')
.
应该可以了。
从概念上讲,apk 是清单、本机文件和 class 库的 zip。 因此,如果您将静态库复制到输出,它将起作用。 所以在 gradle 你应该使用复制任务并将这些库作为输出的一部分。 我只是在下面使用了我的 so 文件并且它有效。
task copyNativeLibs2h(type: Copy) {
from(new File(getProjectDir(), 'libs')) { include '**/*.so' }
into new File(buildDir, 'native-libs')
}
tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn copyNativeLibs2h }
即使使用实验性插件,您也无法使用 gradle 构建静态库。您可以使用预建库,或使用 ndk-build
和 link 将其构建到带有 gradle 插件的共享对象中。
这是一个例子:
假设我们有这样的目录结构:
- project (build.gradle, gradle.properties, etc.)
- jni_static (Application.mk, Android.mk, cpp files for the static lib)
- app (build.gradle, src/main, etc.)
- jni_shared (cpp files for the shared lib)
这里是project/app/build的相关片段。gradle:
// look for NDK directory
import org.apache.tools.ant.taskdefs.condition.Os
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def ndkBuild = properties.getProperty('ndk.dir') + '/ndk-build'
if (Os.isFamily(Os.FAMILY_WINDOWS)) {
ndkBuild += '.cmd'
}
apply plugin: 'com.android.model.application'
... 依赖项、模型 { compileOptions 等
// static lib is built with ndk-build
def LOCAL_MODULE = "static"
def appAbi = "armeabi-v7a"
def ndkOut = "build/intermediates/$LOCAL_MODULE"
def staticLibPath = "$ndkOut/local/$appAbi/lib${LOCAL_MODULE}.a"
// To guarantee that the intermediates shared library is always refreshed,
// we delete it in gradle task rmSO.
task rmSO(type: Delete) {
delete 'build/intermediates/binaries/debug/lib/armeabi-v7a', 'libshared.so'
delete 'build/intermediates/binaries/debug/obj/armeabi-v7a', 'libshared.so'
}
// in file jni/Android.mk there is a section for LOCAL_MODULE=static
// which builds the static library
task buildStaticLib(type: Exec, description: 'Compile Static lib via NDK') {
commandLine "$ndkBuild", "$staticLibPath", "NDK_APPLICATION_MK=../jni_static/Application.mk",
"NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
dependsOn rmSO
}
task cleanNative(type: Exec, description: 'Clean JNI object files') {
commandLine "$ndkBuild", "clean", "NDK_APPLICATION_MK=../jni_static/Application.mk",
"NDK_PROJECT_PATH=../jni_static", "NDK_OUT=$ndkOut"
}
clean.dependsOn cleanNative
tasks.all {
task ->
// link of the shared library depends on build of the static lib
if (task.name.startsWith('link')) {
task.dependsOn buildStaticLib
}
// before build, make sure the intermediate so is not stuck there
if (task.name.startsWith('package')) {
task.dependsOn rmSO
}
}
// build the wrapper shared lib around the static lib using the experimental plugin
model {
android.ndk {
moduleName = "shared"
cppFlags += "-std=c++11"
ldFlags += "$staticLibPath".toString()
ldLibs += "log"
stl = "gnustl_static"
abiFilters += "$appAbi".toString()
}
android.sources {
main.jni.source {
srcDirs = ["jni_shared"]
}
}
}
重要:共享库的源码应该在单独的目录下,这样静态库的源码就不会在里面或下面。这是因为实验插件无法排除srcDirs
.
以上答案解决了 gradle 之前 NDK 集成不足的问题。此答案说明了与 NDK 的新 gradle 集成。
看看这个针对 gradle 2.9 和 android 插件 0.6.0-alpha1 编写的建议 sample。与问题的提出方式相反,此答案包含一个单独的图书馆项目。可以探索此功能以允许 gradle 在应用程序项目使用该库之前构建该库。其他答案依赖于图书馆已经建成的假设。
:secondlib com.android.model.application
构建 libsecondlib.so(在 Java 代码中加载 System.loadLibrary("secondlib")。名称 'secondlib' 是命名不当。我喜欢将其视为所有其他本机库 link 的 .so "wrapper" 供应用程序使用。
该共享库是针对 firstlib.a 静态 link 编辑的,由 :firstlib com.android.model.native
.
根据 exportedHeaders
子句,headers 从 :firstlib 导出到任何依赖项目(本例中为:secondlib)。这样依赖项目知道如何 link 反对 .so/.a。这取代了先前答案中的 CFlags+="-I/path/to/headers" 语法。
:secondlib links 根据以下子句静态地反对 :firstlib:
android.sources {
main {
jni {
dependencies {
project ":firstlib" buildType "debug" linkage "static"
}
}
// TODO(proppy): show jniLibs dependencies on .so
}
}
如评论所示,示例不完整。完成语法显示在 experimental android plugin documentation 的 'NDK Dependencies' 部分。在该文档中,关于如何静态 link 而不是动态 link 的语法应该很清楚。
目前存在一些缺点(例如,上面显示的依赖项的 buildType 默认为 'debug' 而不是当前正在构建的 buildType)。
编辑:这是 app/build 中新依赖语法的 work-in-progress 示例,gradle 从我的一个项目中提取:
android.sources {
main {
jni {
//for exportedHeaders
dependencies { project ":libfoo" linkage "shared" }
}
jniLibs {
//Where the swig wrapped library .so is. I use swig to create code to interface with libfoo.so within :app
source { srcDirs 'libs' }
//for file in $(model.repositories.libs.libfoo)
dependencies { library "libfoo" }
}
}
}
repositories {
libs(PrebuiltLibraries) {
libevdev {
//headers already available from our libfoo project via exportedHeaders
//headers.srcDir "../libfoo/src/main/jni/"
binaries.withType(SharedLibraryBinary) {
sharedLibraryFile = file("../libfoo/build/intermediates/binaries/debug/lib/${targetPlatform.getName()}/libfoo.so")
}
}
}
}