네이티브 라이브러리에 Gradle 연결

네이티브 라이브러리 프로젝트를 Gradle 빌드 종속 항목으로 포함하려면 Gradle에 CMake 또는 ndk-build 스크립트 파일의 경로를 제공해야 합니다. 앱을 빌드하면 Gradle은 CMake 또는 ndk-build를 실행하고 공유 라이브러리를 앱으로 패키징합니다. Gradle은 또한 빌드 스크립트를 사용하여 Android 스튜디오 프로젝트로 가져올 파일을 알고 있으므로 Project 창에서 이 파일에 액세스할 수 있습니다. 네이티브 소스용 빌드 스크립트가 없다면 계속하기 전에 CMake 빌드 스크립트를 생성해야 합니다.

Android 프로젝트의 각 모듈은 하나의 CMake 또는 ndk-build 스크립트 파일에만 연결할 수 있습니다. 따라서, 예를 들어 여러 CMake 프로젝트의 출력을 빌드하고 패키징하려면 하나의 CMakeLists.txt 파일을 최상위 CMake 빌드 스크립트로 사용하고(그런 다음 Gradle을 연결) 빌드 스크립트의 종속 항목으로 다른 CMake 프로젝트를 추가해야 합니다. 마찬가지로, ndk-build를 사용하는 경우 최상위 Android.mk 스크립트 파일에 다른 Makefile을 포함하면 됩니다.

Gradle을 네이티브 프로젝트에 연결하면 Android 스튜디오는 Project 창을 업데이트하여 cpp 그룹에 소스 파일과 네이티브 라이브러리를 표시하고 External Build Files 그룹에는 외부 빌드 스크립트를 표시합니다.

참고: Gradle 구성을 변경할 때에는 툴바에서 Sync Project 를 클릭하여 변경사항을 적용해야 합니다. 또한 CMake 또는 ndk-build 스크립트 파일을 이미 Gradle에 연결한 후 이 스크립트 파일을 변경한다면 메뉴 바에서 Build > Refresh Linked C++ Projects를 선택하여 Android 스튜디오를 변경사항과 동기화해야 합니다.

Android 스튜디오 UI를 사용하여 Gradle을 외부 CMake 또는 ndk-build 프로젝트에 연결할 수 있습니다.

  1. IDE 왼쪽에서 Project 창을 열고 Android 뷰를 선택합니다.
  2. 모듈과 같이 네이티브 라이브러리에 연결할 모듈을 마우스 오른쪽 버튼으로 클릭한 후 메뉴에서 Link C++ Project with Gradle을 선택합니다. 그림 4와 비슷한 대화상자가 표시됩니다.
  3. 드롭다운 메뉴에서 CMake 또는 ndk-build를 선택합니다.
    1. CMake를 선택한 경우 Project Path 옆에 있는 필드를 사용하여 외부 CMake 프로젝트에 사용할 CMakeLists.txt 스크립트 파일을 지정합니다.
    2. ndk-build를 선택한 경우 Project Path 옆에 있는 필드를 사용하여 외부 ndk-build 프로젝트에 사용할 Android.mk 스크립트 파일을 지정합니다. Application.mk 파일이 Android.mk 파일과 같은 디렉터리에 있다면 이 파일도 Android 스튜디오에 포함됩니다.

    그림 4. Android 스튜디오 대화상자를 이용하여 외부 C++ 프로젝트 연결

  4. OK를 클릭합니다.

수동으로 Gradle 구성

수동으로 Gradle을 구성하여 네이티브 라이브러리에 연결하려면 다음과 같이 모듈 수준 build.gradle 파일에 externalNativeBuild 블록을 추가하고 cmake 또는 ndkBuild 블록을 사용하여 이를 구성해야 합니다.

Groovy

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path "CMakeLists.txt"
    }
  }
}

Kotlin

android {
  ...
  defaultConfig {...}
  buildTypes {...}

  // Encapsulates your external native build configurations.
  externalNativeBuild {

    // Encapsulates your CMake build configurations.
    cmake {

      // Provides a relative path to your CMake build script.
      path = file("CMakeLists.txt")
    }
  }
}

참고: Gradle을 기존 ndk-build 프로젝트에 연결하려면 cmake 블록 대신 ndkBuild 블록을 사용하고 Android.mk 파일의 상대 경로를 제공합니다. Application.mk 파일이 Android.mk 파일과 같은 디렉터리에 있다면 이 파일도 Gradle에 포함됩니다.

선택적 구성 지정

모듈 수준 build.gradle 파일의 defaultConfig 블록 내에 다른 externalNativeBuild 블록을 구성하여 CMake 또는 ndk-build의 선택적 인수 및 플래그를 지정할 수 있습니다. defaultConfig 블록의 다른 속성과 마찬가지로, 빌드 구성에서 각 제품 버전에 맞게 이 속성을 재정의할 수 있습니다.

예를 들어, CMake 또는 ndk-build 프로젝트에서 여러 개의 네이티브 라이브러리와 실행 파일을 정의한다면 targets 속성을 사용하여 지정된 제품 버전의 아티팩트 중 일부만 빌드하고 패키징할 수 있습니다. 다음 코드 샘플에서는 구성할 수 있는 몇 가지 속성을 설명합니다.

Groovy

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"

        // Sets a flag to enable format macro constants for the C compiler.
        cFlags "-D__STDC_FORMAT_MACROS"

        // Sets optional flags for the C++ compiler.
        cppFlags "-fexceptions", "-frtti"
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    demo {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries or executables to build and package
          // for this product flavor. The following tells Gradle to build only the
          // "native-lib-demo" and "my-executible-demo" outputs from the linked
          // CMake project. If you don't configure this property, Gradle builds all
          // executables and shared object libraries that you define in your CMake
          // (or ndk-build) project. However, by default, Gradle packages only the
          // shared libraries in your app.
          targets "native-lib-demo",
                  // You need to specify this executable and its sources in your CMakeLists.txt
                  // using the add_executable() command. However, building executables from your
                  // native sources is optional, and building native libraries to package into
                  // your app satisfies most project requirements.
                  "my-executible-demo"
        }
      }
    }

    paid {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets "native-lib-paid",
                  "my-executible-paid"
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    // This block is different from the one you use to link Gradle
    // to your CMake or ndk-build script.
    externalNativeBuild {

      // For ndk-build, instead use the ndkBuild block.
      cmake {

        // Passes optional arguments to CMake.
        arguments += listOf("-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang")

        // Sets a flag to enable format macro constants for the C compiler.
        cFlags += listOf("-D__STDC_FORMAT_MACROS")

        // Sets optional flags for the C++ compiler.
        cppFlags += listOf("-fexceptions", "-frtti")
      }
    }
  }

  buildTypes {...}

  productFlavors {
    ...
    create("demo") {
      ...
      externalNativeBuild {
        cmake {
          ...
          // Specifies which native libraries or executables to build and package
          // for this product flavor. The following tells Gradle to build only the
          // "native-lib-demo" and "my-executible-demo" outputs from the linked
          // CMake project. If you don't configure this property, Gradle builds all
          // executables and shared object libraries that you define in your CMake
          // (or ndk-build) project. However, by default, Gradle packages only the
          // shared libraries in your app.
          targets += listOf("native-lib-demo",
                  // You need to specify this executable and its sources in your CMakeLists.txt
                  // using the add_executable() command. However, building executables from your
                  // native sources is optional, and building native libraries to package into
                  // your app satisfies most project requirements.
                  "my-executible-demo")
        }
      }
    }

    create("paid") {
      ...
      externalNativeBuild {
        cmake {
          ...
          targets += listOf("native-lib-paid",
                  "my-executible-paid")
        }
      }
    }
  }

  // Use this block to link Gradle to your CMake or ndk-build script.
  externalNativeBuild {
    cmake {...}
    // or ndkBuild {...}
  }
}

제품 버전 및 빌드 변형을 구성하는 방법에 관한 자세한 내용은 빌드 변형 구성을 참고하세요. arguments 속성을 사용하여 CMake를 구성할 수 있는 변수 목록은 CMake 변수 사용을 참고하세요.

미리 빌드된 네이티브 라이브러리 포함

Gradle에서 외부 네이티브 빌드에 사용되지 않은 미리 빌드된 네이티브 라이브러리를 패키징하도록 하려면 모듈의 src/main/jniLibs/ABI 디렉터리에 이 라이브러리를 추가합니다.

4.0 버전 이전의 Android Gradle 플러그인은 jniLibs 디렉터리에 CMake IMPORTED 타겟을 포함해야 앱에 포함됩니다. 이전 버전의 플러그인에서 이전하는 경우 다음과 같은 오류가 발생할 수 있습니다.

* What went wrong:
Execution failed for task ':app:mergeDebugNativeLibs'.
> A failure occurred while executing com.android.build.gradle.internal.tasks.Workers$ActionFacade
   > More than one file was found with OS independent path 'lib/x86/libprebuilt.so'

Android Gradle 플러그인 4.0을 사용한다면 IMPORTED CMake 타겟에서 사용하는 모든 라이브러리를 jniLibs 디렉터리 밖으로 이동하여 이러한 오류를 방지하세요.

ABI 지정

기본적으로 Gradle은 NDK가 지원하는 Application Binary Interface(ABI)의 별도 .so 파일로 네이티브 라이브러리를 빌드하고 이를 모두 앱으로 패키징합니다. Gradle이 네이티브 라이브러리의 특정 ABI 구성만 빌드하고 패키징하도록 하려면 아래와 같이 모듈 수준 build.gradle 파일에서 ndk.abiFilters 플래그를 사용하여 지정하면 됩니다.

Groovy

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your app.
      abiFilters 'x86', 'x86_64', 'armeabi', 'armeabi-v7a',
                   'arm64-v8a'
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

Kotlin

android {
  ...
  defaultConfig {
    ...
    externalNativeBuild {
      cmake {...}
      // or ndkBuild {...}
    }

    // Similar to other properties in the defaultConfig block,
    // you can configure the ndk block for each product flavor
    // in your build configuration.
    ndk {
      // Specifies the ABI configurations of your native
      // libraries Gradle should build and package with your app.
      abiFilters += listOf("x86", "x86_64", "armeabi", "armeabi-v7a",
                   "arm64-v8a")
    }
  }
  buildTypes {...}
  externalNativeBuild {...}
}

대부분의 경우 위와 같이 ndk 블록에 abiFilters를 지정하기만 하면 됩니다. 왜냐하면 이를 통해 Gradle은 이 버전의 네이티브 라이브러리를 빌드하고 패키징하기 때문입니다. 하지만 앱으로 패키징하려는 항목과 별개로 Gradle이 빌드해야 하는 항목을 제어하려면 defaultConfig.externalNativeBuild.cmake 블록(또는 defaultConfig.externalNativeBuild.ndkBuild 블록)에 다른 abiFilters 플래그를 구성합니다. Gradle은 이러한 ABI 구성을 빌드하지만 defaultConfig.ndk 블록에 지정한 구성만 패키징합니다.

Android App Bundle로 게시하여 앱 크기를 더 줄이는 것이 좋습니다. 사용자 기기의 ABI와 일치하는 네이티브 라이브러리만 다운로드와 함께 전송되기 때문입니다.

APK를 사용하여 게시하는 기존 앱(2021년 8월 이전에 생성됨)의 경우 ABI를 기반으로 여러 APK를 구성하는 것이 좋습니다. 모든 버전의 네이티브 라이브러리로 대규모 APK 하나를 생성하는 대신 Gradle은 지원하려는 각 ABI에 별도의 APK를 생성하고 각 ABI에 필요한 파일만 패키징합니다. 위의 코드 샘플에서 보는 바와 같이 abiFilters 플래그를 지정하지 않고 ABI별로 여러 APK를 구성하면 Gradle은 지원되는 모든 ABI 버전의 네이티브 라이브러리를 빌드하고 여러 APK 구성에서 지정한 버전만 패키징합니다. 원하지 않는 버전의 네이티브 라이브러리를 빌드하지 않으려면 abiFilters 플래그와 ABI별 여러 APK 구성 모두에 동일한 ABI 목록을 제공해야 합니다.