Połącz Gradle z biblioteką natywną

Aby uwzględnić projekt biblioteki natywnej jako zależność kompilacji Gradle, musisz podać Gradle ścieżkę do pliku skryptu kompilacji CMake lub ndk. Gdy tworzysz aplikację, Gradle uruchamia CMake lub ndk-build i pakuje biblioteki udostępnione Twojej aplikacji. Gradle korzysta również ze skryptu kompilacji, aby wiedzieć, które pliki pobrać do projektu Android Studio, dzięki czemu masz do nich dostęp w oknie Project (Projekt). Jeśli nie masz skryptu kompilacji dla źródeł natywnych, musisz najpierw utworzyć skrypt kompilacji CMake.

Każdy moduł w projekcie na Androida może być połączony tylko z jednym plikiem skryptu CMake lub ndk-build. Jeśli na przykład chcesz kompilować i spakować dane wyjściowe z wielu projektów CMake, musisz użyć 1 pliku CMakeLists.txt jako skryptu kompilacji CMake najwyższego poziomu (który następnie połączysz z Gradle) i dodać inne projekty CMake jako zależności tego skryptu. Podobnie, jeśli używasz ndk-build, możesz dołączyć inne pliki Makefiles w pliku skryptu Android.mk najwyższego poziomu.

Gdy połączysz Gradle z projektem natywnym, Android Studio zaktualizuje panel Projekt, aby wyświetlać pliki źródłowe i biblioteki natywne w grupie cpp, a zewnętrzne skrypty kompilacji w grupie Zewnętrzne pliki kompilacji.

Uwaga: gdy wprowadzasz zmiany w konfiguracji Gradle, pamiętaj, aby zastosować zmiany, klikając Synchronizuj projekt na pasku narzędzi. Dodatkowo w przypadku wprowadzania zmian w pliku skryptu CMake lub ndk-build już po połączeniu go z Gradle, należy zsynchronizować Android Studio ze zmianami, wybierając Build > Odśwież połączone projekty C++ z paska menu.

Możesz połączyć Gradle z zewnętrznym projektem CMake lub ndk-build za pomocą interfejsu Android Studio:

  1. Otwórz panel Projekt z lewej strony IDE i wybierz widok Android.
  2. Kliknij prawym przyciskiem myszy moduł, który chcesz połączyć z biblioteką natywną, na przykład moduł aplikacji, i z menu wybierz Połącz projekt C++ z Gradle. Powinno wyświetlić się okno podobne do pokazanego na rys. 4.
  3. Z menu wybierz CMake lub ndk-build.
    1. Jeśli wybierzesz CMake, w polu obok opcji Ścieżka projektu określ plik skryptu CMakeLists.txt dla zewnętrznego projektu CMake.
    2. Jeśli wybierzesz ndk-build, w polu obok opcji Ścieżka projektu podaj plik skryptu Android.mk dla zewnętrznego projektu ndk-build. Android Studio zawiera też plik Application.mk, jeśli znajduje się w tym samym katalogu co plik Android.mk.

    Rysunek 4. Połączenie zewnętrznego projektu C++ w oknie Android Studio.

  4. Kliknij OK.

Ręczne konfigurowanie Gradle

Aby ręcznie skonfigurować połączenie Gradle z biblioteką natywną, musisz dodać blok externalNativeBuild do pliku build.gradle na poziomie modułu i skonfigurować go za pomocą bloku cmake lub ndkBuild:

Odlotowy

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")
    }
  }
}

Uwaga: jeśli chcesz połączyć Gradle z istniejącym projektem kompilacji ndk, użyj bloku ndkBuild zamiast bloku cmake i podaj względną ścieżkę do pliku Android.mk. Gradle zawiera też plik Application.mk, jeśli znajduje się w tym samym katalogu co plik Android.mk.

Podaj konfiguracje opcjonalne

Możesz określić opcjonalne argumenty i flagi dla CMake lub ndk-build, konfigurując kolejny blok externalNativeBuild w bloku defaultConfig w pliku build.gradle na poziomie modułu. Podobnie jak w przypadku innych właściwości w bloku defaultConfig, możesz zastąpić te właściwości dla każdego rodzaju usługi w konfiguracji kompilacji.

Jeśli na przykład Twój projekt CMake lub ndk-build definiuje wiele bibliotek natywnych i plików wykonywalnych, możesz użyć właściwości targets, aby skompilować i spakować tylko podzbiór tych artefaktów dla danego rodzaju usługi. Ten przykładowy kod opisuje niektóre właściwości, które możesz skonfigurować:

Odlotowy

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

Więcej informacji o konfigurowaniu rodzajów usług i wersji kompilacji znajdziesz w artykule o konfigurowaniu wersji kompilacji. Listę zmiennych, które możesz skonfigurować pod kątem CMake, za pomocą właściwości arguments znajdziesz w artykule Używanie zmiennych CMake.

Uwzględnij gotowe biblioteki natywne

Jeśli chcesz, by Gradle umieszczała w pakiecie gotowe biblioteki natywne, które nie są używane w żadnej zewnętrznej kompilacji natywnej, dodaj je do katalogu src/main/jniLibs/ABI swojego modułu.

Aby można było uwzględnić je w aplikacji, wymagane są wersje wtyczki Androida do obsługi Gradle starsze niż 4.0, w tym cele CMake IMPORTED w katalogu jniLibs. Jeśli przeprowadzasz migrację z wcześniejszej wersji wtyczki, może pojawić się następujący błąd:

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

Jeśli używasz wtyczki Androida do obsługi Gradle w wersji 4.0, przenieś wszystkie biblioteki używane przez IMPORTED elementy docelowe CMake poza katalog jniLibs, aby uniknąć tego błędu.

Określ interfejsy ABI

Domyślnie Gradle kompiluje bibliotekę natywną w osobne pliki .so dla interfejsów binarnych aplikacji (ABI) obsługiwanych przez NDK i pakuje je wszystkie do aplikacji. Jeśli chcesz, by Gradle skompilowała i spakowała tylko określone konfiguracje ABI bibliotek natywnych, możesz je określić za pomocą flagi ndk.abiFilters w pliku build.gradle na poziomie modułu, jak pokazano poniżej:

Odlotowy

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

W większości przypadków wystarczy podać w bloku ndk tylko abiFilters, jak pokazano powyżej, ponieważ informuje to Gradle o kompilowanie i pakowanie tych wersji bibliotek natywnych. Jeśli jednak chcesz kontrolować, co ma kompilować Gradle, niezależnie od tego, co ma zostać spakowane do aplikacji, skonfiguruj kolejną flagę abiFilters w bloku defaultConfig.externalNativeBuild.cmake (lub bloku defaultConfig.externalNativeBuild.ndkBuild). Gradle kompiluje te konfiguracje ABI, ale pakuje tylko te określone w bloku defaultConfig.ndk.

Zalecamy publikowanie aplikacji za pomocą pakietów Android App Bundle, aby jeszcze bardziej zmniejszyć jej rozmiar, ponieważ pobierane są tylko biblioteki natywne pasujące do interfejsu ABI urządzenia użytkownika.

W przypadku starszych aplikacji publikowanych za pomocą plików APK (utworzonych przed sierpniem 2021 r.) rozważ skonfigurowanie wielu plików APK opartych na interfejsie ABI. Zamiast tworzyć jeden duży plik ze wszystkimi wersjami bibliotek natywnych, Gradle tworzy oddzielny plik APK dla każdego obsługiwanego interfejsu ABI i pakuje tylko te pliki, których potrzebuje dany interfejs ABI. Jeśli skonfigurujesz wiele plików APK dla jednego interfejsu ABI bez określania flagi abiFilters, jak pokazano w przykładzie powyżej, Gradle skompiluje wszystkie obsługiwane wersje ABI bibliotek natywnych, ale pakuje tylko te określone w konfiguracji z wieloma plikami APK. Aby uniknąć tworzenia wersji bibliotek natywnych, które są niepotrzebne, podaj tę samą listę interfejsów ABI dla flagi abiFilters i konfiguracji wielu plików APK dla poszczególnych interfejsów ABI.