Liên kết Gradle với thư viện gốc của bạn

Để đưa dự án thư viện gốc của bạn vào với vai trò là một phần phụ thuộc của bản dựng Gradle, bạn cần cung cấp cho Gradle đường dẫn đến tệp tập lệnh CMake hoặc ndk-build. Khi bạn xây dựng ứng dụng, Gradle sẽ chạy CMake hoặc ndk-build và các gói thư viện chia sẻ. Gradle cũng sử dụng tập lệnh bản dựng để biết cần kéo những tệp nào vào dự án Android Studio của bạn. Nhờ đó, bạn có thể truy cập các tệp đó từ cửa sổ Dự án. Nếu không có tập lệnh bản dựng cho mã nguồn gốc, bạn cần tạo tập lệnh bản dựng CMake trước khi tiếp tục.

Mỗi mô-đun trong dự án Android của bạn chỉ có thể liên kết với một tệp tập lệnh CMake hoặc ndk-build. Ví dụ: nếu muốn tạo và đóng gói kết quả đầu ra từ nhiều dự án CMake, bạn cần sử dụng một tệp CMakeLists.txt làm tập lệnh bản dựng CMake cấp cao nhất (sau đó bạn có thể liên kết Gradle) và thêm các dự án CMake khác làm phần phụ thuộc của tập lệnh bản dựng đó. Tương tự, nếu đang dùng ndk-build, bạn có thể thêm vào các Makefiles khác trong tệp tập lệnh Android.mk cấp cao nhất.

Sau khi bạn liên kết Gradle với một dự án gốc, Android Studio sẽ cập nhật ngăn Project (Dự án) để hiển thị các tệp nguồn và thư viện gốc của bạn trong nhóm cpp, và tập lệnh bản dựng bên ngoài trong nhóm Tệp bản dựng ngoài.

Lưu ý: Khi thay đổi cấu hình Gradle, bạn hãy áp dụng các thay đổi của mình bằng cách nhấp vào Sync Project (Đồng bộ dự án) trên thanh công cụ. Ngoài ra, khi thay đổi tệp tập lệnh CMake hoặc ndk-build sau khi đã liên kết tệp đó với Gradle, bạn nên đồng bộ hoá Android Studio với các thay đổi của mình bằng cách chọn Build (Tạo) > Refresh Linked C++ Projects (Làm mới đường liên kết C++ của Dự án) từ thanh trình đơn.

Bạn có thể liên kết Gradle với một dự án CMake hoặc ndk-build bên ngoài bằng giao diện người dùng Android Studio:

  1. Mở ngăn Project (Dự án) nằm bên trái IDE rồi chọn chế độ xem Android.
  2. Nhấp chuột phải vào mô-đun mà bạn muốn liên kết với thư viện gốc, chẳng hạn như mô-đun app (ứng dụng) rồi chọn Link C++ Project with Gradle (Liên kết dự án C++ với Gradle) trên trình đơn. Bạn sẽ thấy một hộp thoại tương tự như hộp thoại hiển thị trong hình 4.
  3. Từ trình đơn thả xuống, hãy chọn CMake hoặc ndk-build.
    1. Nếu bạn chọn CMake, hãy sử dụng trường bên cạnh Project Path (Đường dẫn dự án) để xác định tệp tập lệnh CMakeLists.txt cho dự án CMake bên ngoài của bạn.
    2. Nếu bạn chọn ndk-build, hãy sử dụng trường bên cạnh Đường dẫn dự án để xác định tệp tập lệnh Android.mk cho dự án bản dựng bên ngoài của bạn. Android Studio cũng thêm vào tệp Application.mk nếu tệp đó nằm trong cùng thư mục với tệp Android.mk.

    Hình 4. Liên kết dự án C++ bên ngoài bằng hộp thoại Android Studio.

  4. Nhấp vào OK.

Định cấu hình Gradle thủ công

Để định cấu hình Gradle nhằm liên kết thủ công với thư viện gốc, bạn cần thêm khối externalNativeBuild vào tệp cấp mô-đun build.gradle rồi định cấu hình tệp đó thành khối cmake hoặc 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")
    }
  }
}

Lưu ý: Nếu bạn muốn liên kết Gradle với một dự án ndk-build sẵn có, hãy sử dụng khối ndkBuild thay vì cmake và cung cấp đường dẫn tương đối đến tệp Android.mk. Gradle cũng thêm vào tệp Application.mk nếu tệp đó nằm trong cùng thư mục với tệp Android.mk.

Xác định các cấu hình tuỳ chọn

Bạn có thể chỉ định các đối số và cờ tuỳ chọn cho CMake hoặc ndk-build bằng cách định cấu hình một khối externalNativeBuild khác trong defaultConfig tệp cấp mô-đun build.gradle. Tương tự như các thuộc tính khác trong khối defaultConfig, bạn có thể ghi đè các thuộc tính này cho mỗi phiên bản sản phẩm trong cấu hình bản dựng riêng.

Ví dụ: nếu dự án CMake hoặc ndk-build định nghĩa nhiều thư viện gốc và tệp thực thi, bạn có thể sử dụng targets để chỉ tạo và đóng gói một tập hợp con của các cấu phần mềm đó cho một phiên bản sản phẩm cụ thể. Mã mẫu sau đây mô tả một số thuộc tính mà bạn có thể định cấu hình:

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 {...}
  }
}

Để tìm hiểu thêm về cách định cấu hình phiên bản sản phẩm và biến thể xây dựng, hãy chuyển đến phần Định cấu hình biến thể bản dựng. Để biết danh sách các biến mà bạn có thể định cấu hình cho CMake bằng thuộc tính arguments, hãy xem phần Sử dụng biến CMake.

Thêm vào các thư viện gốc tạo sẵn

Nếu bạn muốn Gradle gói các thư viện gốc tạo sẵn mà bạn không dùng trong bất kỳ bản dựng bên ngoài nào, hãy thêm các thư viện đó vào thư mục src/main/jniLibs/ABI của mô-đun.

Các phiên bản của trình bổ trợ Android cho Gradle trước 4.0 bắt buộc phải có cả các mục tiêu CMake IMPORTED trong thư mục jniLibs để đưa vào ứng dụng. Nếu đang chuyển từ phiên bản plugin cũ hơn, bạn có thể gặp lỗi này:

* 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'

Nếu bạn đang sử dụng trình bổ trợ Android cho Gradle 4.0, hãy chuyển mọi thư viện màIMPORTED CMake nhắm mục tiêu ra khỏi thư mục jniLibs của bạn đang sử dụng để tránh gặp lỗi này.

Xác định ABI

Theo mặc định, Gradle sẽ tách thư viện gốc của bạn thành các tệp .so riêng biệt cho Giao diện nhị phân của ứng dụng (ABI) mà NDK hỗ trợ và đóng gói tất cả vào ứng dụng. Nếu muốn Gradle chỉ xây dựng và đóng gói một số cấu hình ABI nhất định từ thư viện gốc, bạn có thể chỉ định những mã đó bằng cờ ndk.abiFilters trong tệp cấp mô-đun build.gradle, như minh hoạ dưới đây:

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 {...}
}

Trong hầu hết các trường hợp, bạn chỉ cần chỉ định abiFilters trong khối ndk, như minh hoạ ở trên, vì khối này cho Gradle biết để xây dựng đồng thời gói các phiên bản của thư viện gốc của bạn. Tuy nhiên, nếu bạn muốn kiểm soát những nội dung mà Gradle sẽ tạo, độc lập với những gì bạn muốn gói đó đưa vào ứng dụng của mình, hãy định cấu hình một cờ abiFilters trong khối defaultConfig.externalNativeBuild.cmake (hoặc khối defaultConfig.externalNativeBuild.ndkBuild). Gradle tạo các cấu hình ABI đó nhưng chỉ gói những cấu hình ABI mà bạn chỉ định trong khối defaultConfig.ndk.

Bạn nên phát hành với Android App Bundle để tối đa giảm kích thước ứng dụng, vì chỉ những thư viện gốc khớp với ABI của thiết bị người dùng mới được phân phối cùng nội dung tải xuống.

Đối với các ứng dụng cũ phát hành bằng tệp APK (được tạo trước tháng 8 năm 2021), hãy cân nhắc việc định cấu hình nhiều tệp APK dựa trên ABI – thay vì tạo một tệp APK lớn có tất cả các phiên bản thư viện gốc, Gradle sẽ tạo một tệp APK riêng cho mỗi ABI mà bạn muốn hỗ trợ và chỉ gói các tệp mà mỗi ABI cần. Nếu bạn định cấu hình nhiều tệp APK cho mỗi ABI mà không chỉ định cờ abiFilters như hiển thị trong mã mẫu ở trên, thì Gradle sẽ xây dựng tất cả các phiên bản ABI được hỗ trợ của thư viện gốc, nhưng chỉ đóng gói các phiên bản mà bạn chỉ định trong cấu hình nhiều APK của bạn. Để tránh tạo các phiên bản của các thư viện gốc không mong muốn, hãy cung cấp cùng một danh sách ABI cho cả cờ abiFilters và cấu hình nhiều tệp APK cho mỗi ABI.