使用NDK 13编译独立二进制文件

7
使用NDK 10版本时,我用ndk-build编译了许多不同ABIs和多个API级别的独立二进制文件,并将这些二进制文件包含在应用程序中。然而,在新的开发机器上安装了NDK,如本文所述,这导致Android SDK目录下出现了一个ndk-bundle文件夹。我曾经通过命令行编译代码,然后将二进制文件复制到Android Studio项目的资源中,但是我无法弄清楚如何在NDK 13中实现这一点,因此我尝试遵循将本地代码包含在Android Studio项目中的教程。然而,几乎所有最近的说明都假设用户想要构建库而不是独立的二进制文件,所以我没有进展。

如果我能找出如何使用CMake,我就会转向它。我的本地项目有以下(简化)结构:

  • native

    • Android.mk

      LOCAL_PATH := $(call my-dir)/my_tool/src
      include $(CLEAR_VARS)
      LOCAL_MODULE    := my_tool
      LOCAL_SRC_FILES := main.c
      include $(BUILD_EXECUTABLE)
      
    • Application.mk

      APP_ABI := all
      APP_PLATFORM := android-21
      
    • my_tool

      • src
        • main.c
我应该如何在我们的Windows 10开发机上使用Android Studio或NDK从命令行编译它? 编辑: 我在build.gradle中使用了这个:
externalNativeBuild {
    ndkBuild {
        path "../native/Android.mk"
    }
}

Gradle创建了一个名为.externalNativeBuild的目录,其中包含构建配置,但是我不知道如何实际构建本地代码。运行gradle时没有生成任何二进制文件。 我找不到有关ndk-build的gradle配置的任何信息。

1
构建 Android shell 可执行文件(包括 ndk-buildCMake)。 - Onik
2个回答

5

我尽可能地按照你的简化结构进行了跟进。

这里是文件 app/build.gradle:

apply plugin: 'com.android.library'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.1"
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 24
        externalNativeBuild {
            ndkBuild {
                targets "my_tool"
                abiFilters "armeabi-v7a"
            }
        }
    }
    externalNativeBuild {
        ndkBuild {
            path "../native/Android.mk"
        }
    }
}

native/Android.mk 文件与您的文件完全相同:

LOCAL_PATH := $(call my-dir)/my_tool/src
include $(CLEAR_VARS)
LOCAL_MODULE    := my_tool
LOCAL_SRC_FILES := main.c
include $(BUILD_EXECUTABLE)

我还有文件 native/main.c 和一个最小的 app/src/main/AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
  package="my.tool" />

我没有修改Android Studio向导生成的根目录build.gradle脚本:

// Top-level build file where you can add configuration options common to all sub-projects/modules.

buildscript {
    repositories {
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:2.3.0-alpha3'

        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}

现在我可以构建项目,这是我的结果:
$> file ./app/build/intermediates/ndkBuild/debug/obj/local/armeabi-v7a/my_tool
ELF 32-bit LSB shared object, ARM, version 1 (SYSV), dynamically linked (uses shared libs), not stripped

Android Studio默认视图中将我的main.c显示在cpp文件夹中:

enter image description here

更新: 为了使可执行文件被剥离并打包到APK中,必须更改native/Android.mk文件:

LOCAL_PATH := $(call my-dir)/my_tool/src

install: LIB_PATH := $(call my-dir)/libs

include $(CLEAR_VARS)
LOCAL_MODULE    := my_tool
LOCAL_SRC_FILES := main.c
include $(BUILD_EXECUTABLE)

install: $(LOCAL_INSTALLED)
    -mkdir $(LIB_PATH)
    -rm -r $(LIB_PATH)
    mv $< $(<:my_tool=lib-my_tool-.so)
    mv $(realpath $(dir $<)..) $(LIB_PATH)

.PHONY: install

此外,app/build.gradle 需要进行一些微调:
apply plugin: 'com.android.application'

android {
    compileSdkVersion 24
    buildToolsVersion "25.0.1"
    defaultConfig {
        minSdkVersion 21
        targetSdkVersion 24
        externalNativeBuild {
            ndkBuild {
                targets "my_tool"
                abiFilters "armeabi-v7a"
                arguments 'V=1', 'install'
            }
        }
    }
    externalNativeBuild {
        ndkBuild {
            path "../native/Android.mk"
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['../native/libs']
        }
    }
}

这依赖于旧的黑科技,它依赖NDK的未记录行为,并且在将来的NDK升级中可能会突然失效。


太好了!生成的二进制文件会自动包含在APK中吗? - just.me
我认为它们不会自动添加到APK中。实际上,二进制文件不会自动从APK中提取出来。我曾经提出过一个技巧,但我从未在Android 5或6上测试过它。 - Alex Cohn
1
提取不是问题,我们已经有了相关的代码。这只意味着我们需要手动添加一个步骤来将二进制文件添加到APK中,但这之前也是一样的,所以没有变化。 - just.me
是的,将二进制文件添加到APK的最简单方法是劫持jniLibs,但Gradle提供了无限的替代方案。请注意,ndkBuild/debug/obj中的可执行文件没有被剥离,因此在打包之前需要进行额外的步骤。 - Alex Cohn
1
谢谢你的回答,我已经接受并奖励了你。strip工具是NDK捆绑包的一部分吗?你能否为我提供一个剥离和打包所有架构可执行文件的示例,以便我稍后可以提取它们? - just.me
1
我已更新答案以强制执行strip。为了实现打包,似乎不可避免要采用旧的破解方法 - Alex Cohn

2
如果您只是在更改NDK版本,那么我认为这非常简单,我们可能错过了一些简单的东西。我认为您需要更新PATH环境变量以指向新的ndk-bundle路径,然后尝试再次运行ndk-build。
从我的经验来看,我无法让Cmake为Android生成可执行文件(独立二进制文件)或静态库作为最终输出。因为我希望最终输出是一个可执行文件,所以我不得不重新使用NDK并忘记Cmake。
更新
以下是使用Android Studio和NDK构建C++可执行文件的方法。这是不使用CMake的方法,即使您没有JNI代码也可以使用。
app/build.gradle
sourceSets.main {
    // Do not run default ndkbuild
    jni.srcDirs = []
    // Place where .so libs are available
    jniLibs.srcDir 'src/main/libs'
}

task buildNative(type: Exec, description: 'Compile JNI source via NDK') {
    def ndkDir = android.ndkDirectory
    commandLine "$ndkDir/ndk-build.cmd", 'V=1',
            '-C', file('src/main/jni').absolutePath,
            '-j', Runtime.runtime.availableProcessors(),
            'all',
            'NDK_DEBUG=1'
}

task cleanNative(type: Exec, description: 'Clean JNI object files') {
    def ndkDir = android.ndkDirectory
    commandLine "$ndkDir/ndk-build.cmd",
            '-C', file('src/main/jni').absolutePath,
            'clean'
}

目录结构

你需要创建一个名为 app/src/main/jni 的文件夹。正如你所看到的,上面的 gradle 代码只是针对该目录调用了 ndk-build 命令。以下是我建议你设置目录结构的方式:

  • jni

    • Android.mk

      LOCAL_PATH := $(call my-dir)
      include $(call all-subdir-makefiles)
      
    • Application.mk

      APP_ABI := all
      APP_PLATFORM := android-21
      
    • native

      • my_tool
        • src
          • main.c
      • Android.mk

        include $(CLEAR_VARS)
        LOCAL_C_INCLUDES := my_tool/src
        LOCAL_PATH := $(call my-dir)
        LOCAL_MODULE := my_tool
        LOCAL_SRC_FILES := my_tool/src/main.c
        include $(BUILD_EXECUTABLE)
        

构建

要进行构建,您可以从app/src/main/jni目录运行ndk-build,或者您可以从项目的根目录运行gradle buildNative。您可以看到在我上面展示的gradle代码中,有一个名为buildNative的任务,因此这就是将要执行的gradle代码。

顶层的Android.mk文件有助于在您需要在app/src/main/jni下添加更多模块进行构建时,遍历该目录中的所有子目录并调用任何其他找到的Android.mk文件。


我们无法访问旧的工具链,我们只在新的设置中获得了代码。这就是为什么我正在寻找如何编译代码的具体说明,最好是通过Android Studio,但命令行解决方案也可以接受。我们不仅更改了NDK版本,而且安装了全新的机器。 - just.me
不,我没有这样的文件夹,因为我没有使用JNI。JNI仅支持本地库代码,而我没有编译库。或者jni文件夹是否应该还包含其他本地代码?我将编辑我的问题以回答另一个部分。 - just.me
我更新了我的回答,而不是添加另一个回答。如果这有帮助,请告诉我。 - Martin
我会尝试这个。使用更现代的externalNativeBuild任务也可以实现吗? - just.me
@AlexCohn 你能解释一下怎么做吗? - just.me
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接