Как правильно обращаться с зависимостями классов, чтобы использовать инкрементную компиляцию в Java/Android с Gradle?
Я улучшил нашу систему сборки и активировал инкрементные сборки и компиляцию, как описано в этом вопросе. К моему разочарованию, инкрементная компиляция не улучшила время сборки так сильно, как я ожидала от прочтения поста Gradles.
После некоторого исследования я понял, что проблема в том, что, хотя я добавляю комментарий только к небольшому классу где-то глубоко в приложении, очевидно, почти вся база кода перестраивается. На самом деле, неважно, к какому классу я прикасаюсь, Gradles --debug
Вывод показывает, что он в основном всегда перекомпилирует 476 классов.
Инкрементная компиляция 476 классов завершена за 12,51 с.
Пока я так понимаю public static
константы в измененном файле вызывают полную перекомпиляцию (которая только немного медленнее), я не понимаю, как правильно разбить зависимости класса так, чтобы инкрементная компиляция действительно работала. Каковы точные правила для определения зависимостей классов, которые влияют на добавочную компиляцию? Я мог бы прочитать о некоторых примерах здесь, но это не относится к нашему (довольно стандартному) проекту вообще.
Некоторые из моих собственных испытаний дали следующее:
// One of my main classes that has lots of class dependencies
public class A{
public void foo() {
// This line produces a dependency between A and B. So changing just
// a comment in B triggers recompilation of all classes attached to A
B b1 = new B();
}
}
// A small helper class that I want to change
public class B {
public void bar() {
// This line does not create a dependency, so B can still be compiled by
// itself. But usually, that's not the "common" direction you have.
A a1 = new A();
// I make the change here and then trigger a new build
}
}
Зачем нужна перекомпиляция A, когда изменилась деталь реализации, а не интерфейс B?
Я также попытался "спрятать" B за интерфейсом C. Я подумал, что это будет правильным (хотя зачастую очень громоздким) способом разбить зависимости. Но оказывается, что это совсем не помогло.
public class A{
public void foo() {
C c1 = C.cFactory();
}
}
public class B implements C {
public void bar() {
// I make the change here and then trigger a new build
}
}
public interface C {
void bar();
public static C cFactory() {
return new B();
}
}
Мне кажется, что у нас есть этот большой блок зависимостей, и я не уверен, что его можно было бы разумно изменить, даже если бы я утверждал, что у нас есть разумно разработанная база кода. Существуют ли лучшие практики, рекомендации или шаблоны проектирования, которые распространены среди разработчиков Android, которые могли бы эффективно улучшить инкрементные компиляции?
Мне интересно, есть ли у других такая же проблема, как у нас, и если нет, то что вы делали?
1 ответ
Проблема в том, что все наши классы являются частью большого графа зависимостей. Это показывает в цитате
Инкрементная компиляция 476 классов завершена за 12,51 с.
По сути, любой класс, к которому я прикасаюсь, вызывает перекомпиляцию 476 классов. В нашем конкретном случае это вызвано двумя закономерностями. Мы используем Explicit Intents в Android, с чем я не уверен, как лучше справиться. Кроме того, мы используем Dagger таким образом, чтобы все классы были соединены в круг.
Что касается проблематичного интерфейса, я допустил довольно очевидную ошибку с cFactory()
, Поскольку это создает зависимость класса от C
в B
и, таким образом, переходно от A
в C
,
Следующий фрагмент прерывает зависимость от A
в B
но создает один из B
в A
,
public class A{
public static void foo(C input) {
input.foo();
}
}
public class B implements C {
public void bar() {
// I make the change here and then trigger a new build
A.foo(this);
}
}
public interface C {
void bar();
}