Implementacja stopniowa a konfiguracja interfejsu API

231

Staram się zrozumieć to, co jest różnica między apii implementationkonfiguracja budując moje zależności.
W dokumentacji napisano, że implementationma to lepszy czas kompilacji, ale widząc ten komentarz w podobnym pytaniu, zastanawiałem się, czy to prawda.
Ponieważ nie jestem ekspertem od stopni, mam nadzieję, że ktoś może pomóc. Przeczytałem już dokumentację, ale zastanawiałem się nad łatwym do zrozumienia wyjaśnieniem.

reinaldomoreira
źródło
1
Czytałeś tutaj ?
MatPag,
tak zresztą zrobiłem, ale, jak powiedziałem, ten komentarz go zastanawiał. więc jestem teraz trochę zagubiony
reinaldomoreira
Prawdopodobnie zmienisz zależności bibliotek z compilena api. Biblioteki, których używasz wewnętrznie, mogą wykorzystywać niektóre prywatne implementacje, które nie są ujawniane w bibliotece końcowej, więc są dla Ciebie przezroczyste. Te zależności „wewnętrzne-prywatne” można przełączyć na, implementationa gdy wtyczka stopniowania Androida skompiluje twoją aplikację, pominie kompilację tych zależności, co skróci czas kompilacji (ale te zależności będą dostępne w czasie wykonywania). Oczywiście możesz zrobić to samo, jeśli masz lokalne biblioteki modułów
MatPag
1
Oto krótkie objaśnienie graficzne „api” i „implementacja”: jeroenmols.com/blog/2017/06/14/androidstudio3
albert c braun
1
to niesamowity post! dziękuję @albertbraun
reinaldomoreira

Odpowiedzi:

418

Słowo compilekluczowe Gradle zostało wycofane na rzecz słów kluczowych apii, implementationaby skonfigurować zależności.

Korzystanie apijest równoznaczne z wykorzystaniem przestarzałe compile, więc jeśli zastąpić wszystko compileze apiwszystko będzie działa jak zawsze.

Aby zrozumieć implementationsłowo kluczowe, rozważ następujący przykład.

PRZYKŁAD

Załóżmy, że masz bibliotekę o nazwie, MyLibraryktóra wewnętrznie korzysta z innej biblioteki o nazwie InternalLibrary. Coś takiego:

    // 'InternalLibrary' module
    public class InternalLibrary {
        public static String giveMeAString(){
            return "hello";
        }
    }
    // 'MyLibrary' module
    public class MyLibrary {
        public String myString(){
            return InternalLibrary.giveMeAString();
        }
    }

Załóżmy, że konfiguracja MyLibrary build.gradlezastosowań wygląda następująco:apidependencies{}

dependencies {
    api project(':InternalLibrary')
}

Chcesz użyć MyLibraryw swoim kodzie, więc w swojej aplikacji build.gradledodajesz tę zależność:

dependencies {
    implementation project(':MyLibrary')
}

Za pomocą apikonfiguracji (lub przestarzałej compile) możesz uzyskać dostęp InternalLibrarydo kodu aplikacji:

// Access 'MyLibrary' (granted)
MyLibrary myLib = new MyLibrary();
System.out.println(myLib.myString());

// Can ALSO access the internal library too (and you shouldn't)
System.out.println(InternalLibrary.giveMeAString());

W ten sposób moduł MyLibrarypotencjalnie „przecieka” wewnętrzną implementację czegoś. Nie powinieneś (być w stanie) tego używać, ponieważ nie jest on bezpośrednio importowany przez Ciebie.

implementationKonfiguracja została wprowadzona, aby temu zapobiec. Więc teraz, jeśli użyjesz implementationzamiast apiw MyLibrary:

dependencies {
    implementation project(':InternalLibrary')
}

nie będziesz już mógł wywoływać InternalLibrary.giveMeAString()kodu aplikacji.

Ten rodzaj strategii bokserskiej pozwala wtyczce Android Gradle wiedzieć, że jeśli coś edytujesz InternalLibrary, musi ona tylko wyzwalać ponowną kompilację, MyLibrarya nie ponowną kompilację całej aplikacji, ponieważ nie masz do niej dostępu InternalLibrary.

Gdy masz wiele zagnieżdżonych zależności, ten mechanizm może znacznie przyspieszyć kompilację. (Obejrzyj film na końcu, aby w pełni to zrozumieć)

WNIOSKI

  • Po przełączeniu na nowy Android Gradle wtyczki 3.xx, należy wymienić wszystkie Twoje compileze implementationsłów kluczowych (1 *) . Następnie spróbuj skompilować i przetestować aplikację. Jeśli wszystko jest w porządku, pozostaw kod bez zmian, jeśli masz problemy, prawdopodobnie masz coś nie tak z zależnościami lub użyłeś czegoś, co teraz jest prywatne i nie jest bardziej dostępne. Sugestia inżyniera wtyczki Android Gradle Jerome Dochez (1 ) * )

  • Jeśli jesteś mantainerem biblioteki, powinieneś używać go apido każdej zależności, która jest potrzebna dla publicznego interfejsu API twojej biblioteki, podczas gdy implementationdo testowania zależności lub zależności, które nie mogą być używane przez użytkowników końcowych.

Przydatny artykuł Przedstawienie różnicy między implementacją a interfejsem API

REFERENCJE (To samo wideo podzielone w celu zaoszczędzenia czasu)

Google I / O 2017 - Jak przyspieszyć kompilację Gradle (PEŁNE WIDEO)

Google I / O 2017 - Jak przyspieszyć kompilację Gradle (TYLKO NOWA GRADLE PLUGIN 3.0.0

Google I / O 2017 - Jak przyspieszyć kompilacje Gradle (odniesienie do 1 * )

Dokumentacja Androida

MatPag
źródło
4
Zauważyłem, że API nie działa dobrze w modułach bibliotecznych. Jeśli go użyję, nadal nie mogę uzyskać dostępu do zależności z mojego projektu aplikacji. Mogę uzyskać dostęp do kodu tylko w tej bibliotece.
Allan W
1
Jest to w porządku i działa w przypadku kompilacji debugowania, ale podczas korzystania z ProGuard (w wersjach wydania) MyLibrary#myString()ulegnie awarii, ponieważ ProGuard zostanie InternalLibraryusunięty. Jaka jest najlepsza praktyka dla bibliotek z systemem Android w aplikacjach ProGuard?
hardysim
3
Myślę, że odpowiedź nie jest dokładna, aplikacja może używać dowolnego zakresu dla MyLibrary. Zobaczy, czy nie, InternalLibrary w zależności od tego, czy MyLibrary korzysta z interfejsu API / implementacji.
Snicolas,
2
dzięki. niesamowite wyjaśnienie, znacznie lepsze niż to podane w oficjalnych dokumentach Androida
Henry
2
to piękne wytłumaczenie. Teoria i beton świetnie się wymieszały. Dobra robota. Dzięki za to
Peter Kahn,
133

Lubię myśleć o apizależności jako publicznej (widzianej przez inne moduły), podczas gdy implementationzależność jako prywatnej (widzianej tylko przez ten moduł).

Zauważ, że w przeciwieństwie do public/ privatezmiennych i metod, api/ implementationzależności nie są wymuszane przez środowisko wykonawcze. Jest to jedynie optymalizacja czasu kompilacji, która pozwala Gradledowiedzieć się, które moduły należy ponownie skompilować, gdy jedna z zależności zmieni interfejs API.

dev.bmax
źródło
15
Uwielbiam prostotę tej odpowiedzi, dziękuję bardzo
Kevin Gilles
2
Prawdziwa różnica (AFAICT) polega na tym, że wygenerowany plik pom umieszcza apizależności w zakresie „kompilacji” (zostaną one uwzględnione jako zależności w bibliotece i wszystko, co zależy od biblioteki) oraz implementationzależności w zakresie „środowiska wykonawczego” (lepiej być w ścieżka klasy, gdy kod jest uruchomiony, ale nie są one potrzebne do kompilacji innego kodu korzystającego z biblioteki).
Shadow Man
@ShadowMan Jest to szczegół implementacji wtyczki, odpowiedzialny za generowanie pliku POM, jak mapuje zakresy Gradle na zakresy Maven .
dev.bmax
1
Powinieneś używać implementationdo wszelkich zależności, które są wymagane do uruchomienia (i do skompilowania biblioteki), ale nie powinno się to automatycznie przeciągać do projektów korzystających z twojej biblioteki. Przykładem może być jax-rs, twoja biblioteka może korzystać z RESTeasy, ale nie powinna wciągać tych bibliotek do żadnego projektu korzystającego z twojej biblioteki, ponieważ zamiast tego mogą chcieć użyć Jersey.
Shadow Man
1
w ten sposób wiesz, że ktoś dostaje to: D dzięki za prostą i jasną odpowiedź
Elias Fazel
12

Rozważ, że masz appmoduł, który używa lib1jako biblioteki i lib1używa lib2jako biblioteki. Coś takiego: app -> lib1 -> lib2.

Teraz podczas używania api lib2w lib1, a następnie app można zobaczyć lib2 kody przy użyciu: api lib1lub implementation lib1w appmodule.

Jednak przy użyciu implementation lib2w lib1, to app nie widzę tych lib2kodów.

Ehsan Mashhadi
źródło
5

Odpowiedzi z @matpag i @ dev-bmax są wystarczająco jasne, aby ludzie zrozumieli różne zastosowania między implementacją a interfejsem API. Chcę tylko zrobić dodatkowe wyjaśnienie z innej strony, mam nadzieję pomóc ludziom, którzy mają to samo pytanie.

Stworzyłem dwa projekty do testowania:

  • projekt A jako projekt biblioteki Java o nazwie „frameworks-web-gradle-plugin” zależy od „org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE”
  • projekt B zależy od projektu A według implementacji „com.example.frameworks.gradle: frameworks-web-gradle-plugin: 0.0.1-SNAPSHOT”

Hierarchia zależności opisana powyżej wygląda następująco:

[project-b] -> [project-a] -> [spring-boot-gradle-plugin]

Następnie przetestowałem następujące scenariusze:

  1. Sprawdź, czy projekt A zależy od „org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE” według implementacji .

    Uruchom gradle dependenciespolecenie w terminalu w katalogu głównym obiektu B po wyrzuceniu, po następującym zrzucie ekranu możemy zobaczyć, że „wiosna-boot-gradle-plugin” pojawia się w drzewie zależności runtimeClasspath, ale nie w compileClasspath, myślę, że właśnie dlatego nie możemy zrobić korzystanie z biblioteki, która zadeklarowała użycie implementacji, po prostu nie przejdzie kompilacji.

    wprowadź opis zdjęcia tutaj

  2. Utwórz projekt A w zależności od 'org.springframework.boot: spring-boot-gradle-plugin: 1.5.20.RELEASE' przez api

    gradle dependenciesPonownie uruchom polecenie w terminalu w katalogu głównym katalogu B obiektu. Teraz „Spring-boot-gradle-plugin” pojawia się zarówno w drzewie zależności compileClasspath, jak i runtimeClasspath.

    wprowadź opis zdjęcia tutaj

Znaczącą różnicę, którą zauważyłem, jest to, że zależność w projekcie producenta / biblioteki zadeklarowana w sposób implementacji nie pojawi się w ścieżce compileClas projektów konsumenckich, więc nie możemy korzystać z odpowiedniej biblioteki lib w projektach konsumenckich.

Rong.l
źródło
2

Z dokumentacji stopnia :

Rzućmy okiem na bardzo prosty skrypt kompilacji dla projektu opartego na JVM.

plugins {
    id 'java-library'
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.hibernate:hibernate-core:3.6.7.Final'
    api 'com.google.guava:guava:23.0'
    testImplementation 'junit:junit:4.+'
}

realizacja

Zależności wymagane do skompilowania źródła produkcyjnego projektu, które nie są częścią interfejsu API udostępnionego przez projekt. Na przykład projekt wykorzystuje Hibernację do implementacji wewnętrznej warstwy trwałości.

api

Zależności wymagane do skompilowania źródła produkcyjnego projektu, które są częścią interfejsu API udostępnionego przez projekt. Na przykład projekt wykorzystuje Guava i ujawnia publiczne interfejsy z klasami Guava w swoich sygnaturach metod.

Camilo Silva
źródło