Android Studio 14原生开发+CrystaX NDK

3
我正在尝试将一款跨平台的C++11(iOS+Android)大型应用迁移到Android Studio 14。我使用CrystaX NDK提供的boost和C++14。他们的网站博客上有官方的Android Studio + CrystaX教程,但是已经过时了。
我已经下载了官方的hello-jni示例,并尝试将其适应CrystaX NDK的工作。以下是我的Gradle 2.5设置: 项目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-experimental:0.2.0'
    }
}

allprojects {
    repositories {
        jcenter()
    }
}

模块 build.gradle

apply plugin: 'com.android.model.application'

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.1"

        defaultConfig.with {
            applicationId = "com.tableair.app"
            minSdkVersion.apiLevel = 4
            targetSdkVersion.apiLevel = 23
        }
    }

    compileOptions.with {
        sourceCompatibility=JavaVersion.VERSION_1_7
        targetCompatibility=JavaVersion.VERSION_1_7
    }

    /*
     * native build settings
     */
    android.ndk {
        moduleName = "tableair-framework"
        cppFlags += "-std=c++11"
        cppFlags += "-Werror"
        ldLibs = ["android", "log", "GLESv2", "crystax"]
        stl = "gnustl_static"
        /*
         * Other ndk flags configurable here are
         * cppFlags += "-fno-rtti"
         * cppFlags += "-fno-exceptions"
         * ldLibs    = ["android", "log"]
         * stl       = "system"
         */
    }
    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles  += file('proguard-rules.txt')
        }
    }
    android.productFlavors {
        // for detailed abiFilter descriptions, refer to "Supported ABIs" @
        // https://developer.android.com/ndk/guides/abis.html#sa
        create("arm") {
            ndk.abiFilters += "armeabi"
        }
        create("arm7") {
            ndk.abiFilters += "armeabi-v7a"
        }
        create("arm8") {
            ndk.abiFilters += "arm64-v8a"
        }
        create("x86") {
            ndk.abiFilters += "x86"
        }
        create("x86-64") {
            ndk.abiFilters += "x86_64"
        }
        create("mips") {
            ndk.abiFilters += "mips"
        }
        create("mips-64") {
            ndk.abiFilters += "mips64"
        }
        // To include all cpu architectures, leaves abiFilters empty
        create("all")
    }
}

local.properties

ndk.dir=/Users/vilius/Software/Android/crystax-ndk-10.2.1
sdk.dir=/Users/vilius/Library/Android/sdk

当我尝试运行应用程序时,出现了以下错误:
....
:app:generateAllDebugAndroidTestSources UP-TO-DATE
:app:copyArm64-v8aDebugAllTableair-frameworkSharedLibraryGdbServer UP-TO-DATE
:app:createArm64-v8aDebugAllTableair-frameworkSharedLibraryGdbsetup
:app:compileArm64-v8aDebugAllTableair-frameworkSharedLibraryTableair-frameworkMainC UP-TO-DATE
:app:linkArm64-v8aDebugAllTableair-frameworkSharedLibrary
/Users/vilius/Software/Android/crystax-ndk-10.2.1/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/../lib/gcc/aarch64-linux-android/4.9/../../../../aarch64-linux-android/bin/ld:     cannot find -lcrystax
/Users/vilius/Software/Android/crystax-ndk-10.2.1/toolchains/aarch64-linux-android-4.9/prebuilt/darwin-x86_64/bin/../lib/gcc/aarch64-linux-android/4.9/../../../../aarch64-linux-android/bin/ld:     cannot find -lcrystax
Error:error: ld returned 1 exit status
Error:Execution failed for task ':app:linkArm64-v8aDebugAllTableair-frameworkSharedLibrary'.
> A build operation failed.
      Linker failed while linking libtableair-framework.so.
  See the complete log at: file:///Users/vilius/TableAir/Mobile/app/android-studio-project/app/build/tmp/linkArm64-v8aDebugAllTableair-frameworkSharedLibrary/output.txt    

文件output.txt显示的信息与消息窗口中完全相同。

有人对这个问题有想法吗?


请检查我为 define LOCAL_SRC_FILES in ndk{} DSL 提出的混合解决方案。 - Alex Cohn
2个回答

8

是的,这个示例有点过时了,所以我们计划很快发布新文章,更新如何使用CrystaX NDK和新实验性Gradle插件的描述。在此期间,您可以查看我推送到GitHub上的示例。那个示例最有趣的部分是应用程序的build.gradle文件,因此我在这里复制它以方便:

import org.gradle.internal.os.OperatingSystem;

apply plugin: 'com.android.model.application'

final APP_ABIS = ["armeabi", "armeabi-v7a", "x86"]
final BOOST_SHARED_LIBS = ["boost_serialization"]

model {
    android {
        compileSdkVersion = 23
        buildToolsVersion = "23.0.1"

        defaultConfig.with {
            applicationId = "net.crystax.testboost2"
            minSdkVersion.apiLevel = 15
            targetSdkVersion.apiLevel = compileSdkVersion.asType(Integer)
            versionCode = 1
            versionName = "1.0"
        }

    }

    compileOptions.with {
        sourceCompatibility = JavaVersion.VERSION_1_7
        targetCompatibility = JavaVersion.VERSION_1_7
    }

    android.ndk {
        moduleName = "test-boost2"
        cppFlags += "-std=c++11"
        cppFlags += "-fexceptions"
        cppFlags += "-frtti"
        cppFlags += "-Werror"
        cppFlags += "-I" + getBoostIncDir()
        ldLibs.addAll BOOST_SHARED_LIBS
        ldLibs += "log"
        stl = "gnustl_shared"
    }

    android.buildTypes {
        release {
            minifyEnabled = false
            proguardFiles += file('proguard-rules.pro')
        }
    }

    android.productFlavors {
        APP_ABIS.each { abi ->
            create(getFlavorName(abi)) {
                ndk.with {
                    abiFilters += abi
                    getPrebuiltLibPaths(abi).each { path ->
                        ldFlags += "-L" + path
                    }
                }
            }
        }
    }

}

tasks.all {
    task ->
        if (task.name.startsWith('link')) {
            task.dependsOn copyNativeLibs, stripNativeLibs
        }
}

task copyNativeLibs {
    ["debug", "release"].each { buildType ->
        APP_ABIS.each { abi ->
            def libs = [:]
            BOOST_SHARED_LIBS.each { name ->
                libs[name] = "${getBoostLibDir(abi)}/lib${name}.so"
            }
            libs.crystax = getLibCrystax(abi)

            libs.each { name, file ->
                dependsOn tasks.create(name: "copy-native-library-${name}-${abi}-${buildType}", type: Copy) {
                    from file
                    into getTargetLibDir(abi, buildType)
                }
            }
        }
    }
}

task stripNativeLibs(dependsOn: copyNativeLibs) {
    ["debug", "release"].each { buildType ->
        APP_ABIS.each { abi ->
            def libs = []
            libs.addAll(BOOST_SHARED_LIBS)
            libs += "crystax"

            libs.each { name ->
                dependsOn tasks.create(name: "strip-native-library-${name}-${abi}-${buildType}", type: Exec) {
                    commandLine getStripExecutable(abi), "--strip-unneeded", "${getTargetLibDir(abi, buildType)}/lib${name}.so"
                }
            }

        }
    }
}

dependencies {
    compile fileTree(dir: 'libs', include: ['*.jar'])
    compile 'com.android.support:appcompat-v7:23.0.1'
    compile 'com.android.support:design:23.0.1'
}

def getNdkDir() {
    if (System.env.ANDROID_NDK_ROOT != null)
        return System.env.ANDROID_NDK_ROOT

    Properties properties = new Properties()
    properties.load(project.rootProject.file('local.properties').newDataInputStream())
    def ndkdir = properties.getProperty('ndk.dir', null)
    if (ndkdir == null)
        throw new GradleException("""\
                NDK location not found.
                Define location with ndk.dir in the local.properties file
                or with an ANDROID_NDK_ROOT environment variable.""")

    return ndkdir
}

def getCrystaxNdkDir() {
    def ndkDir = getNdkDir()
    if (!(new File(ndkDir, "sources/crystax").exists()))
        throw new GradleException("""\
            '${ndkDir}' is not a CrystaX NDK.
            Edit ndk.dir in local.properties or set ANDROID_NDK_ROOT
            environment variable pointing to CrystaX NDK""")

    return ndkDir
}

def getFlavorName(abi) {
    switch (abi) {
        case "armeabi":
            return "arm";
        case "armeabi-v7a":
            return "arm7"
        case "arm64-v8a":
            return "arm64"
        default:
            return abi.replaceAll('-', '_')
    }
}

def getToolchainName(abi) {
    switch (abi) {
        case ~/^armeabi.*/:
            return "arm-linux-androideabi"
        case ~/^arm64.*/:
            return "aarch64-linux-android"
        case "mips":
            return "mipsel-linux-android"
        case "mips64":
            return "mips64el-linux-android"
        case ["x86", "x86_64"]:
            return abi
        default:
            throw new GradleException("Unsupported ABI: '${abi}'")
    }
}

def getToolchainPrefix(abi) {
    switch (abi) {
        case ~/^armeabi.*/:
            return "arm-linux-androideabi"
        case ~/^arm64.*/:
            return "aarch64-linux-android"
        case "mips":
            return "mipsel-linux-android"
        case "mips64":
            return "mips64el-linux-android"
        case "x86":
            return "i686-linux-android"
        case "x86_64":
            return "x86_64-linux-android"
        default:
            throw new GradleException("Unsupported ABI: '${abi}'")
    }
}

def getHostOS() {
    if (OperatingSystem.current().isLinux())
        return "linux"
    if (OperatingSystem.current().isMacOsX())
        return "darwin"
    if (OperatingSystem.current().isWindows())
        return "windows"
    throw new GradleException("Unsupported host OS")
}

def getHostArch() {
    def arch = System.getProperty("os.arch")
    switch (arch) {
        case ["x86_64", "amd64"]:
            return "x86_64"
        case ~/^i[3456]86/:
        case "x86":
            return "x86"
        default:
            throw new GradleException("Can't detect host's CPU architecture: '${arch}'")
    }
}

def getHostTag() {
    def tag = getHostOS()
    def arch = getHostArch()
    if (tag != "windows" || arch != "x86")
        tag += "-${arch}"
    return tag
}

def getStripExecutable(abi) {
    def ndk = getCrystaxNdkDir()
    def toolchainName = getToolchainName(abi)
    def toolchainPrefix = getToolchainPrefix(abi)
    def hostTag = getHostTag()
    def strip = "${ndk}/toolchains/${toolchainName}-4.9/prebuilt/${hostTag}/bin/${toolchainPrefix}-strip"
    if (OperatingSystem.current().isWindows())
        strip = strip.replaceAll('/', '\\\\') + '.exe'
    return strip
}

def getPrebuiltLibPaths(abi) {
    def paths = []
    paths += getBoostLibDir(abi)
    paths += getLibCrystaxDir(abi)
    return paths
}

def getTargetLibDir(abi, buildType) {
    return "${buildDir}/intermediates/binaries/${buildType}/${getFlavorName(abi)}/lib/${abi}"
}

def getLibCrystaxDir(abi) {
    return "${getCrystaxNdkDir()}/sources/crystax/libs/${abi}"
}

def getLibCrystax(abi) {
    return "${getLibCrystaxDir(abi)}/libcrystax.so"
}

def getBoostDir() {
    return "${getCrystaxNdkDir()}/sources/boost/1.58.0"
}

def getBoostIncDir() {
    return "${getBoostDir()}/include"
}

def getBoostLibDir(abi) {
    return "${getBoostDir()}/libs/${abi}"
}

我已经使用Android Studio 1.4和gradle实验插件0.2.0进行了测试。
实际上,只有在将共享库包含在apk中时才需要自定义copyNativeLibs任务;如果您链接静态库,则根本不需要copyNativeLibs。
更新:我添加了stripNativeLibs任务,因为我意识到APK包含未剥离的带有调试信息的库,使其大小过大,而实际上并没有真正需要这样做。

你觉得这种方法需要自定义的“清理”任务吗? - Alex Cohn
1
不需要单独的clean任务,因为我们在这里进行的唯一定制是将本机共享库复制到buildDir中,而且当执行gradle :app:clean任务时,这些buildDir会被完全删除。 - Dmitry Moskalchuk
它正常工作了!谢谢。只是一个简单的问题,你如何明确地将构建类型更改为x86?在构建模拟器时似乎没有推断出来。 - Crossfire
只需点击菜单“Build -> Select Build Variant”,然后选择“x86Debug”或“x86Release”。 - Dmitry Moskalchuk
@DmitryMoskalchuk 我应该使用哪个Gradle任务来构建? - Boris Rozhkovsky
如何生成一个包含所有ABI架构的APK?(就像在生成APK时选择“全部”一样) - user3896501

0

@dmitry-moskalchuk 你好Dmitry,我使用了你的build.gradle代码,并在local.properties中定义了crystax-ndk-10.2.1,如下所示: ndk.dir=/opt/android-sdk-linux/crystax-ndk-10.2.1

但我一直收到以下错误信息: Error:A problem occurred configuring project ':app'.

android.compileSdkVersion缺失!

尽管它已经在android部分以及androidmanifest.xml中定义了。

谢谢。


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