Jaka jest różnica między implementacją a kompilacją w Gradle?

1027

Po aktualizacji do Android Studio 3.0 i utworzeniu nowego projektu zauważyłem, że build.gradleistnieje nowy sposób dodawania nowych zależności zamiast compiletam implementationi zamiast testCompiletam testImplementation.

Przykład:

 implementation 'com.android.support:appcompat-v7:25.0.0'
 testImplementation 'junit:junit:4.12'

zamiast

 compile 'com.android.support:appcompat-v7:25.0.0'
 testCompile 'junit:junit:4.12'

Jaka jest różnica między nimi a tym, czego powinienem używać?

humazed
źródło

Odpowiedzi:

1279

tl; dr

Po prostu zamień:

  • compilez implementation(jeśli nie potrzebujesz przechodniości) lub api(jeśli potrzebujesz przechodniości)
  • testCompile z testImplementation
  • debugCompile z debugImplementation
  • androidTestCompile z androidTestImplementation
  • compileOnlyjest ciągle ważny. Został dodany w wersji 3.0 w celu zastąpienia pod warunkiem, a nie kompilacji. ( providedwprowadzony, gdy Gradle nie miał nazwy konfiguracji dla tego przypadku użycia i nazwał ją na podstawie zakresu podanego przez Mavena).

Jest to jedna z przełomowych zmian wprowadzonych wraz z Gradle 3.0 ogłoszonym przez Google na IO17 .

compileKonfiguracja jest teraz przestarzała i powinna zostać zastąpiona implementationlubapi

Z dokumentacji Gradle :

dependencies {
    api 'commons-httpclient:commons-httpclient:3.1'
    implementation 'org.apache.commons:commons-lang3:3.5'
}

Zależności pojawiające się w apikonfiguracjach zostaną tranzytowo udostępnione konsumentom biblioteki i jako takie pojawią się na ścieżce kompilacji konsumentów.

implementationZ drugiej strony zależności znalezione w konfiguracji nie będą narażone na konsumentów, a zatem nie będą przeciekać ścieżki kompilacji klientów. Ma to kilka zalet:

  • zależności nie przeciekają już do ścieżki kompilacji konsumentów, więc nigdy nie będziesz przypadkowo polegać na zależności przechodniej
  • szybsza kompilacja dzięki zmniejszonemu rozmiarowi ścieżki klas
  • mniej ponownych kompilacji, gdy zmieniają się zależności implementacyjne: konsumenci nie będą musieli ponownie kompilować
  • czystsze publikowanie: w połączeniu z nową wtyczką maven-publishing biblioteki Java wytwarzają pliki POM, które dokładnie odróżniają to, co jest wymagane do skompilowania z biblioteką od tego, co jest wymagane do korzystania z biblioteki w czasie wykonywania (innymi słowy, nie wymieszać to, co jest potrzebne do skompilowania samej biblioteki i co jest potrzebne do skompilowania z biblioteką).

Konfiguracja kompilacji nadal istnieje, ale nie należy jej używać, ponieważ nie zapewnia ona gwarancji zapewnianych przez konfiguracje apii implementation.


Uwaga: jeśli używasz tylko biblioteki w module aplikacji - częsty przypadek - nie zauważysz żadnej różnicy.
zobaczysz różnicę tylko wtedy, gdy masz złożony projekt z modułami zależnymi od siebie lub tworzysz bibliotekę.

humazed
źródło
137
Kim są „konsumenci”?
Suragch
34
konsument to moduł korzystający z biblioteki. w przypadku Androida jest to aplikacja na Androida. Myślę, że to jasne i nie jestem pewien, czy o to prosisz.
humazed
21
Tak też dla mnie to brzmiało. Ale jeśli tworzę bibliotekę, oczywiście chcę, aby jej interfejs API był udostępniany aplikacji. W przeciwnym razie, w jaki sposób twórca aplikacji wykorzystałby moją bibliotekę? Dlatego nie rozumiem sensu implementationukrywania zależności. Czy moje pytanie ma sens?
Suragch,
235
tak, ma to teraz sens, jeśli Twoja aplikacja zależy od biblioteki x, która sama zależy od y, z. jeśli użyjesz implementationtylko x api, zostanie odkryte, ale jeśli użyjesz apiy, z również będzie widoczne.
humazed
36
Rozumiem! To ma teraz większy sens. Możesz dodać to wyjaśnienie do swojej odpowiedzi. Jest to bardziej przejrzyste niż cytowana dokumentacja.
Suragch,
378

Ta odpowiedź będzie wykazać różnicę między implementation, apioraz compilenad projektem.


Powiedzmy, że mam projekt z trzema modułami Gradle:

  • aplikacja (aplikacja na Androida)
  • myandroidlibrary (biblioteka Androida)
  • myjavalibrary (biblioteka Java)

appma myandroidlibraryjako zależności. myandroidlibraryma myjavalibrary jako zależności.

Zależność 1

myjavalibraryma MySecretklasę

public class MySecret {

    public static String getSecret() {
        return "Money";
    }
}

myandroidlibraryma MyAndroidComponentklasę, która manipuluje wartością z MySecretklasy.

public class MyAndroidComponent {

    private static String component = MySecret.getSecret();

    public static String getComponent() {
        return "My component: " + component;
    }    
}

Wreszcie appinteresuje się tylko wartością zmyandroidlibrary

TextView tvHelloWorld = findViewById(R.id.tv_hello_world);
tvHelloWorld.setText(MyAndroidComponent.getComponent());

Porozmawiajmy teraz o zależnościach ...

apptrzeba konsumować :myandroidlibrary, więc w appbuild.gradle użyj implementation.

( Uwaga : możesz również użyć interfejsu API / kompilacji. Ale przytrzymaj tę myśl przez chwilę.)

dependencies {
    implementation project(':myandroidlibrary')      
}

Zależność 2

Jak myślisz, jak myandroidlibrarypowinna wyglądać build.gradle? Z jakiego zakresu powinniśmy korzystać?

Mamy trzy opcje:

dependencies {
    // Option #1
    implementation project(':myjavalibrary') 
    // Option #2
    compile project(':myjavalibrary')      
    // Option #3
    api project(':myjavalibrary')           
}

Zależność 3

Jaka jest różnica między nimi a tym, czego powinienem używać?

Kompiluj lub API (opcja nr 2 lub nr 3) Zależność 4

Jeśli używasz compilelub api. Nasza aplikacja na Androida ma teraz dostęp do myandroidcomponentzależności, która jest MySecretklasą.

TextView textView = findViewById(R.id.text_view);
textView.setText(MyAndroidComponent.getComponent());
// You can access MySecret
textView.setText(MySecret.getSecret());

Realizacja (opcja nr 1)

Zależność 5

Jeśli używasz implementationkonfiguracji, MySecretnie jest narażony.

TextView textView = findViewById(R.id.text_view);
textView.setText(MyAndroidComponent.getComponent());
// You can NOT access MySecret
textView.setText(MySecret.getSecret()); // Won't even compile

Którą konfigurację wybrać? To naprawdę zależy od twoich wymagań.

Jeśli chcesz ujawnić zależności, użyj apilub compile.

Jeśli nie chcesz ujawniać zależności (ukrywanie modułu wewnętrznego), użyj implementation.

Uwaga:

To tylko streszczenie konfiguracji Gradle, patrz Tabela 49.1. Wtyczka Java Library - konfiguracje używane do deklarowania zależności w celu uzyskania bardziej szczegółowych wyjaśnień.

Przykładowy projekt dla tej odpowiedzi jest dostępny na https://github.com/aldoKelvianto/ImplementationVsCompile

aldok
źródło
1
Dodałem zależność do jednego pliku jar przy użyciu implementacji, jeśli nie ujawnia on dostępu do niego, dlaczego nadal mogę go uzyskać, a mój kod działa poprawnie?
smkrn110
Implementacja @ smkrn110 ujawni bibliotekę jar, ale nie biblioteki zależności jar.
aldok
2
@WijaySharma zaakceptowana odpowiedź stwierdza, że compilenie gwarantuje tych samych apigwarancji.
Sub 6 Zasoby
9
Myślę, że to powinna być zaakceptowana odpowiedź. Dobrze wyjaśnione!
Shashank Kapsime,
9
@ StevenW.Klassen to najbardziej niezasłużony głos, o jakim kiedykolwiek słyszałem. Jeśli uważasz, że kolejność informacji nie jest optymalna, zasugeruj zmianę zamiast narzekać na nią
Tim
65

Compilekonfiguracja była przestarzała i powinna zostać zastąpiona przez implementationlub api.

Dokumenty możesz przeczytać na https://docs.gradle.org/current/userguide/java_library_plugin.html#sec:java_library_separation .

Krótka część jest-

Kluczową różnicą między standardową wtyczką Java a wtyczką Java Library jest to, że ta ostatnia wprowadza koncepcję interfejsu API udostępnianego konsumentom. Biblioteka jest komponentem Java przeznaczonym do wykorzystania przez inne komponenty. Jest to bardzo częsty przypadek użycia w kompilacjach obejmujących wiele projektów, ale także w przypadku zewnętrznych zależności.

Wtyczka udostępnia dwie konfiguracje, których można używać do deklarowania zależności: interfejs API i implementacja. Konfiguracja interfejsu API powinna być używana do deklarowania zależności, które są eksportowane przez API biblioteki, podczas gdy konfiguracja implementacji powinna być używana do deklarowania zależności wewnętrznych dla komponentu.

Dalsze objaśnienia znajdują się w tym obrazie. Krótkie wyjaśnienie

Riszaw
źródło
46

Krótkie rozwiązanie:

Lepszym rozwiązaniem jest zastąpienie wszystkich compilezależności implementationzależnościami. I tylko w przypadku wycieku z interfejsu modułu, powinieneś użyć api. To powinno spowodować znacznie mniejszą rekompilację.

 dependencies {
         implementation fileTree(dir: 'libs', include: ['*.jar'])

         implementation 'com.android.support:appcompat-v7:25.4.0'
         implementation 'com.android.support.constraint:constraint-layout:1.0.2'
         // …

         testImplementation 'junit:junit:4.12'
         androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
             exclude group: 'com.android.support', module: 'support-annotations'
         })
 }

Wyjaśnij więcej:

Przed Androidem Gradle plugin 3.0 : mieliśmy duży problem - jedna zmiana kodu powoduje rekompilację wszystkich modułów. Główną przyczyną tego jest to, że Gradle nie wie, czy przeciekasz interfejs modułu przez inny, czy nie.

Po Androidzie Gradle plugin 3.0 : najnowsza wtyczka Android Gradle wymaga teraz jawnego zdefiniowania, czy nieszczelność interfejsu modułu. Na tej podstawie może dokonać właściwego wyboru na podstawie tego, co powinien ponownie skompilować.

W związku z tym compilezależność została uznana za przestarzałą i zastąpiona dwoma nowymi:

  • api: przeciekasz interfejs tego modułu przez własny interfejs, co oznacza dokładnie to samo, co stara compilezależność

  • implementation: używasz tego modułu tylko wewnętrznie i nie przeciekasz go przez interfejs

Teraz możesz teraz wyraźnie powiedzieć Gradleowi, aby ponownie skompilował moduł, jeśli interfejs używanego modułu zmieni się, czy nie.

Dzięki uprzejmości bloga Jeroen Mols

Shayan Amani
źródło
2
Czyste i zwięzłe wyjaśnienie. Dzięki!
LeOn - Han Li
20
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| Name               | Role                 | Consumable? | Resolveable? | Description                             |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| api                | Declaring            |      no     |      no      | This is where you should declare        |
|                    | API                  |             |              | dependencies which are transitively     |
|                    | dependencies         |             |              | exported to consumers, for compile.     |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| implementation     | Declaring            |      no     |      no      | This is where you should                |
|                    | implementation       |             |              | declare dependencies which are          |
|                    | dependencies         |             |              | purely internal and not                 |
|                    |                      |             |              | meant to be exposed to consumers.       |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| compileOnly        | Declaring compile    |     yes     |      yes     | This is where you should                |
|                    | only                 |             |              | declare dependencies                    |
|                    | dependencies         |             |              | which are only required                 |
|                    |                      |             |              | at compile time, but should             |
|                    |                      |             |              | not leak into the runtime.              |
|                    |                      |             |              | This typically includes dependencies    |
|                    |                      |             |              | which are shaded when found at runtime. |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| runtimeOnly        | Declaring            |      no     |      no      | This is where you should                |
|                    | runtime              |             |              | declare dependencies which              |
|                    | dependencies         |             |              | are only required at runtime,           |
|                    |                      |             |              | and not at compile time.                |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testImplementation | Test dependencies    |      no     |      no      | This is where you                       |
|                    |                      |             |              | should declare dependencies             |
|                    |                      |             |              | which are used to compile tests.        |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testCompileOnly    | Declaring test       |     yes     |      yes     | This is where you should                |
|                    | compile only         |             |              | declare dependencies                    |
|                    | dependencies         |             |              | which are only required                 |
|                    |                      |             |              | at test compile time,                   |
|                    |                      |             |              | but should not leak into the runtime.   |
|                    |                      |             |              | This typically includes dependencies    |
|                    |                      |             |              | which are shaded when found at runtime. |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| testRuntimeOnly    | Declaring test       |      no     |      no      | This is where you should                |
|                    | runtime dependencies |             |              | declare dependencies which              |
|                    |                      |             |              | are only required at test               |
|                    |                      |             |              | runtime, and not at test compile time.  |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
Wajid Ali
źródło
Nie odpowiada bezpośrednio na pytanie
skryvets,
1
Istnieje również rozwój
Tylko
Czego powinienem użyć, jeśli potrzebuję zarówno czasu działania, jak i czasu kompilacji? Obecnie implementationśledzę runtime.
Maroun
8

Krótka różnica w terminach laika to:

  • Jeśli pracujesz nad interfejsem lub modułem, który zapewnia obsługę innych modułów, ujawniając członków podanej zależności, powinieneś używać „api”.
  • Jeśli tworzysz aplikację lub moduł, który zamierza zaimplementować wewnętrzną deklarowaną zależność lub użyć jej, użyj „implementacji”.
  • „kompilacja” działała tak samo jak „api”, jednak jeśli implementujesz lub używasz tylko biblioteki, „implementacja” będzie działać lepiej i oszczędzać zasoby.

przeczytaj odpowiedź @aldok, aby uzyskać wyczerpujący przykład.

Rushabh Agarwal
źródło
Ale chodzi o to, że jeśli ktoś celowo przybył tutaj, szukając odpowiedzi na te pytania, to wcale nie jest laikiem.
Rishav
6

Od wersji 5.6.3 dokumentacji Gradle zapewnia prostych zasad kciuk do identyfikacji czy starą compilezależność (lub nowy) należy zastąpić implementationlub apizależność:

  • Preferuj implementationkonfigurację, apijeśli to możliwe

Dzięki temu zależności od ścieżki klas kompilacji konsumenta są wyłączone. Ponadto konsumenci natychmiast nie skompilują się, jeśli jakiekolwiek typy implementacji przypadkowo wyciekną do publicznego interfejsu API.

Kiedy więc należy użyć apikonfiguracji? Zależność API to taka, która zawiera co najmniej jeden typ, który jest ujawniany w binarnym interfejsie biblioteki, często nazywanym jego interfejsem ABI (Application Binary Interface). Obejmuje to między innymi:

  • typy używane w superklasach lub interfejsach
  • typy używane w parametrach metod publicznych, w tym ogólne typy parametrów (gdzie public jest czymś, co jest widoczne dla kompilatorów. Tj. public, chronione i pakują prywatnych członków w świecie Java)
  • typy używane w polach publicznych
  • typy adnotacji publicznych

Z drugiej strony, każdy typ użyty na poniższej liście nie ma znaczenia dla ABI i dlatego powinien zostać zadeklarowany jako implementationzależność:

  • typy stosowane wyłącznie w obiektach metod
  • typy używane wyłącznie przez członków prywatnych
  • typy znajdujące się wyłącznie w klasach wewnętrznych (przyszłe wersje Gradle pozwolą ci zadeklarować, które pakiety należą do publicznego API)
Pom12
źródło
6

Gradle 3.0 wprowadzono kolejne zmiany:

  • compile -> api

    api słowo kluczowe jest takie samo jak przestarzałe compile

  • compile -> implementation

    Jest to preferowany sposób, ponieważ ma pewne zalety. implementationujawniają zależność tylko o jeden poziom wyżej w czasie kompilacji (zależność jest dostępna w czasie wykonywania). W rezultacie masz szybszą kompilację (nie ma potrzeby ponownej kompilacji klientów, którzy są wyżej niż o 1 poziom wyżej)

  • provided -> compileOnly

    Ta zależność jest dostępna tylko w czasie kompilacji (zależność nie jest dostępna w czasie wykonywania). Ta zależność nie może być przechodnia i nią być .aar. Może być używany z procesorem adnotacji czasu kompilacji i pozwala zmniejszyć końcowy plik wyjściowy

  • compile -> annotationProcessor

    Bardzo podobny, compileOnlyale także gwarantuje, że zależność przechodnia nie będzie widoczna dla konsumenta

  • apk -> runtimeOnly

    Zależność nie jest dostępna w czasie kompilacji, ale jest dostępna w czasie wykonywania.

yoAlex5
źródło
Więc innymi słowy api = public, implementation = internali compileOnly = private- trzeba utworzyć takie aliasy dla tych funkcji, ponieważ są one bardzo mylące.
t3chb0t