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

  1. 告诉编译器 headers 在哪里(在 android.ndk{} 中):

    CFlags += "-I${file("path/to/headers")}".toString() cppFlags += CFlags

  2. 告诉链接器 .a 文件在哪里(在 android.ndk{} 或哪里 定义口味 - 确保添加 abiFilter - 例如 abiFilters += "armeabi-v7")

    ldFlags += "-L${file(path/to/library.a)}".toString() ldLibs += ["nameOfLibrary"]

    注意库的名字按照约定是后面的字符串 "lib" 在 .a 文件名中。例如,对于名为 libNative.a 的文件 您应该将 ldLibs += ["native"] 添加到 gradle.

  3. 创建一个新模块并使用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")
        }
      }
    }
  }