Я пытаюсь понять, в чем разница между конфигурацией api
и implementation
при построении моих зависимостей.
В документации говорится, что implementation
имеет лучшее время сборки, но,
видя это comment в аналогичном вопросе, мне стало интересно, правда ли это.
Поскольку я не эксперт в gradle, я надеюсь, что кто-то может помочь. Я уже читал документацию, но мне было интересно узнать о простом понимании.
Gradle Реализация против конфигурации API
Ответ 1
Ключевое слово Gradle compile
устарело в пользу ключевых слов api
и implementation
для настройки зависимостей.
Использование api
эквивалентно использованию устаревшего compile
, поэтому, если вы замените все compile
на api
, все будет работать как всегда.
Чтобы понять ключевое слово implementation
, рассмотрим следующий пример.
Пример
Предположим, у вас есть библиотека с именем MyLibrary
, которая внутренне использует другую библиотеку с именем InternalLibrary
. Примерно так:
// 'InternalLibrary' module
public class InternalLibrary {
public static String giveMeAString(){
return "hello";
}
}
// 'MyLibrary' module
public class MyLibrary {
public String myString(){
return InternalLibrary.giveMeAString();
}
}
Предположим, что MyLibrary
build.gradle
использует конфигурацию api
в dependencies{}
следующим образом:
dependencies {
api project(':InternalLibrary')
}
Вы хотите использовать MyLibrary
в своем коде, чтобы в приложении build.gradle
вы добавили эту зависимость:
dependencies {
implementation project(':MyLibrary')
}
Используя конфигурацию api
(или устарела compile
), вы можете получить доступ к InternalLibrary
в коде своего приложения:
// 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());
Таким образом, модуль MyLibrary
потенциально "пропускает" внутреннюю реализацию чего-либо. Вы не должны (быть в состоянии) использовать это, потому что оно не импортировано вами напрямую.
Конфигурация implementation
была введена для предотвращения этого.
Так что теперь, если вы используете implementation
вместо api
в MyLibrary
:
dependencies {
implementation project(':InternalLibrary')
}
вы больше не сможете звонить InternalLibrary.giveMeAString()
в коде своего приложения.
Этот вид боксерской стратегии позволяет плагину Android Gradle знать, что если вы редактируете что-то в InternalLibrary
, он должен запускать только перекомпиляцию MyLibrary
, а не перекомпиляцию всего вашего приложения, потому что у вас нет доступа к InternalLibrary
.
Когда у вас много вложенных зависимостей, этот механизм может значительно ускорить сборку. (Смотрите видео в конце, чтобы лучше понять это)
ВЫВОДЫ
Когда вы переключаетесь на новый плагин Android Gradle 3.X.X, вы должны заменить все свои
compile
на ключевое словоimplementation
(1 *). Затем попробуйте скомпилировать и протестировать ваше приложение. Если все в порядке, оставьте код как есть, если у вас есть проблемы, возможно, у вас что-то не так с вашими зависимостями, или вы использовали что-то частное и не более доступное. Предложение от инженера плагинов Gradle для Android Джерома Дочеза (1) *)Если вы являетесь управляющим библиотекой, вы должны использовать
api
для каждой зависимости, которая необходима для общедоступного API вашей библиотеки, в то же время используйтеimplementation
для тестовых зависимостей или зависимостей, которые не должны использоваться конечными пользователями.
Полезная статья, демонстрирующая разницу между реализацией и api
Лит (Это то же самое видео, разделенное для экономии времени)
Google I/O 2017 - Как ускорить сборку Gradle (ПОЛНОЕ ВИДЕО)
Google I/O 2017 - Как ускорить сборку Gradle (ТОЛЬКО ДЛЯ НОВОЙ GRADLE PLUGIN 3.0.0)
Google I/O 2017 - Как ускорить сборку Gradle (ссылка на 1 *)
Ответ 2
Мне нравится думать о зависимости api
как public (видятся другими модулями), тогда как implementation
зависимость как private (видна только этим модулем).
Обратите внимание, что в отличие от переменных и методов public
/private
зависимостей api
/implementation
не выполняются средой выполнения. Это просто оптимизация времени сборки, которая позволяет Gradle
знать, какие модули необходимо перекомпилировать, когда одна из зависимостей изменит свой API.
Ответ 3
lib1
вас есть модуль app
который использует lib1
в качестве библиотеки, а lib1
использует lib2
в качестве библиотеки. Примерно так: app → lib1 → lib2
.
Теперь при использовании api lib2
в lib1
app
может видеть коды lib2
при использовании: api lib1
или implementation lib1
в модуле app
.
НО при использовании implementation lib2
в lib1
, app
не может видеть коды lib2
.
Ответ 4
При использовании Android Gradle плагина 3.0 в вашем проекте вы могли заметить, что ключевое слово компиляции теперь устарело в пользу реализации и api. Давайте рассмотрим их оба примера.
Предположим, проект с четырьмя библиотечными модулями.
- LibraryA
- библиотека B
- LibraryC
- LibraryD
Для чего дерево зависимостей выглядит следующим образом:
Библиотека D
class ClassD {
fun tellMeAJoke():String{
return "You are funny :D"
}
}
Библиотека C
class ClassC {
fun tellMeAJoke(): String {
return "You are funny :C"
}
}
Библиотека B
class ClassB {
val b = ClassD()
fun whereIsMyJoke(): String {
return b.tellMeAJoke()
}
}
Библиотека A
class ClassA {
val c = ClassC()
fun whereIsMyJoke(): String {
return c.tellMeAJoke()
}
}
Из вышеприведенных файлов классов видно, что LibraryA и LibraryB в зависимости от LibraryC и LibraryD соответственно. Поэтому эти зависимости, необходимо добавить в build.gradle файлы. Скомпилируйте (2.0) или Api (3.0):
Новое ключевое слово api точно совпадает с старым ключевым словом компиляции. Таким образом, если весь компилятор заменен на api, он работает отлично. Теперь давайте добавим зависимость LibraryD с помощью ключевого слова api в LibraryB.
dependencies {
. . . .
api project(path: ':libraryD')
}
Аналогично, LibraryB добавляется в модуль приложения.
dependencies {
. . . .
api project(path: ':libraryB')
}
Теперь доступ к библиотекам и библиотекам можно получить из модуля приложения. В примерном приложении обе библиотеки обращаются следующим образом:
Реализация (3.0):
Время, чтобы узнать, как реализация отличается от api. Вернемся к примеру, на этот раз можно импортировать LibraryC в библиотеку с использованием ключевого слова реализации.
dependencies {
. . . .
implementation project(path: ':libraryC')
}
То же самое для модуля App.
dependencies {
. . . .
implementation project(path: ':libraryA')
}
Теперь, если мы попытаемся получить доступ к LibraryC из модуля приложения, студия Android выведет ошибку.
Это означает, что доступ к LibraryC не может быть получен непосредственно из модуля App, если мы используем реализацию вместо api. Итак, в чем польза от этого?
Реализация vs api:
В первом сценарии LibraryD скомпилирован с помощью api. Если какое-либо изменение реализовано внутри LibraryD, Gradle необходимо перекомпилировать библиотечные библиотеки, библиотечные библиотеки и все другие модули, которые импортируют LibraryB, поскольку любой другой модуль может использовать реализацию LibraryD.
Но во втором случае, если какая-либо реализация в LibraryC изменилась, Gradle просто нужно перекомпилировать LibraryC и LibraryA, поскольку любой другой класс, который не импортирует LibraryC напрямую, не может использовать какую-либо его реализацию.
Если вы работаете над проектом с большим количеством модулей (в этом эпизоде фрагментированного подкаста мы слышали какую-то сумасшедшую историю создания), эта стратегия может значительно ускорить процесс сборки. Я попробовал это на нашем примере проекта, и было небольшое улучшение за несколько секунд. Ниже приведены отчеты по сборке для всех сценариев.
Полная сборка:
Изменение в библиотеке D:
Изменение в библиотеке C:
Ответ 5
+--------------------+----------------------+-------------+--------------+-----------------------------------------+
| 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. |
+--------------------+----------------------+-------------+--------------+-----------------------------------------+