В последней части этой серии « Введение в компоненты архитектуры Android» мы рассказали о новой архитектуре Android и о том, почему она была разработана. По сути, новая архитектура решает некоторые известные проблемы Android, предлагая набор компонентов, специально предназначенных для системы. Это строительные блоки архитектуры. Мы уже взглянули на эти компоненты, и теперь пришло время углубиться в них.
В этом руководстве мы LiveModel
компоненты Lifecycle
и LiveModel
. Пока мы их изучаем, мы также собираемся проверить некоторые фрагменты кода из примера приложения. Поскольку мы говорим о новых парадигмах Android, все фрагменты кода сделаны с использованием потрясающего Kotlin .
Если вы еще не знаете Kotlin, пожалуйста, не бойтесь следовать; реализация очень близка к Java, и я уверен, что вы сможете это понять. Если вы хотите узнать больше о Kotlin, Джессика Торнсби написала отличную серию здесь на Tuts + о кодировании приложений Android в Kotlin . Вы должны взглянуть!
-
Создание функциональных приложений для Android в Kotlin: начало работы
-
Функциональные приложения для Android в Котлине: Lambdas, Null Safety и многое другое
1. Пример проекта
Мы предоставили небольшое приложение, демонстрирующее концепции, о которых мы говорим в этом руководстве. Название приложения — MyWeatherApp , и оно позволяет пользователю выбирать погоду на день, используя название города или текущее местоположение пользователя. Логика приложения довольно проста, но вы можете улучшить ее, создав собственное приложение.
Как вы можете видеть на диаграмме ниже, архитектура соответствует архитектуре, предложенной Android, и мы максимально использовали новый пакет Компоненты архитектуры, упрощая простоту для базового анализа. В качестве бонуса мы используем Dagger 2 в качестве библиотеки внедрения зависимостей. Тем не менее, мы не будем вдаваться в подробности его реализации, так как это выйдет за рамки учебника.
Как работает приложение?
Приложение максимально простое. Он имеет только одну активность, где пользователь может узнать погоду, выполнив поиск по названию города или используя текущее местоположение устройства. MainActivity
вызывает MainModel
для получения наблюдаемых LiveData
и реагирует на них. MainModel
извлекает данные о погоде из MainRepository
и объединяет все данные в виде LiveData
. MainRepository
получает свои данные из нескольких источников.
Запуск примера приложения
Загрузите или клонируйте репозиторий из нашего репозитория GitHub и соберите его с помощью Gradle или откройте его в своей IDE. Вы также должны создать учетную запись OpenWeatherMap и получить новый идентификатор приложения. Добавьте идентификатор приложения в строковый ресурс с именем openWeather
.
1
|
<string name=»openWeather»>XXXXXXXXXXXXXXX</string>
|
2. Настройка проекта
Поскольку компоненты архитектуры все еще находятся в альфа-версии, вы должны включить репозиторий Google, содержащий некоторые экспериментальные библиотеки, в build.gradle
проекта.
1
2
3
4
5
6
7
|
allprojects {
repositories {
// add this repository
maven { url ‘https://maven.google.com’ }
}
}
|
В модуле build.gradle добавьте следующее в раздел dependencies
чтобы добавить поддержку для Lifecycles
LiveData
, LiveData
и ViewModel
:
-
compile "android.arch.lifecycle:runtime:1.0.0-alpha5"
-
compile "android.arch.lifecycle:extensions:1.0.0-alpha5"
-
annotationProcessor "android.arch.lifecycle:compiler:1.0.0-alpha5"
Если вы используете Kotlin, вы также должны добавить или заменить annotationProcessor
на kapt
, который обрабатывает аннотации в Kotlin.
1
|
kapt «android.arch.lifecycle:compiler:1.0.0-alpha5»
|
Чтобы включить kapt
, добавьте следующее в корень модуля build.gradle
.
1
2
3
|
kapt {
generateStubs = true
}
|
3. Компонент Lifecycle
Каждый разработчик Android знаком с концепцией жизненного цикла. Система управляет жизненным циклом приложений, действий, фрагментов и т. Д. Без контроля разработчика. Эта концепция является одной из парадигм Android, и до недавнего времени работать с ней было не так просто, поскольку было невозможно напрямую проверить текущее состояние жизненного цикла компонента. Мы могли реагировать на определенные методы, такие как onCreate
и onDestroy
, которые запускаются событиями жизненного цикла.
Все изменилось с момента анонса пакета Компоненты архитектуры, в котором появился компонент под названием Lifecycle
. Теперь к некоторым объектам Android прикреплен Lifecycle
, и это многое меняет для разработчиков. Можно Lifecycle
состояние Lifecycle
в любой момент времени, а также можно реагировать на события Lifecycle
с помощью аннотаций. Фактически, основой новых компонентов архитектуры Android является компонент Lifecycle
.
Все элементы пакета android.arch.lifecycle
важны для концепции жизненного цикла , но два из них заслуживают большего внимания: LifecycleOwner
и LifecycleObserver
. Они создают возможность работы с Lifecycle
, наблюдая, а также реагируя на события, происходящие в действиях, фрагментах, сервисах и так далее.
LifecycleOwner
LifecycleOwner
— это интерфейс с одним методом для классов, содержащих Lifecycle
. Он абстрагируется от владения Lifecycle
, позволяя вам писать компоненты, которые могут с ним работать. По новым стандартам действия и фрагменты являются LifecycleOwner
s. Однако до запуска окончательной версии компонентов архитектуры вы должны использовать некоторые специальные классы: ActivityLifecycle
, FragmentLifecycle
и LifecycleService
.
1
2
3
4
5
|
class MainActivity : LifecycleActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
}
}
|
В реализации этих классов нет существенных изменений по сравнению со стандартными Действиями и Фрагментами. Как только класс расширяет любой из них, к нему присоединяется Lifecycle
, который можно получить в любое время с помощью метода getLifecycle()
. Другая интересная возможность заключается в том, что мы можем проверить текущее состояние жизненного цикла с помощью getCurrentState
, который возвращает Lifecycle.State
.
Существует пять различных состояний Lifecycle
:
-
INITIALIZED
: для объекта, который был вызван, но еще не активен. Это эквивалент состояния перед методомActivity.onCreate
. -
CREATED
: для объектов, которые были только что созданы. Он вызывается после методаonCreate
а такжеonStop
непосредственно перед методомonStop
. -
STARTED
:onStart
послеonStart
и непосредственно перед методомonPause
. -
RESUMED
: активное состояние или возобновленное состояние дляLifecycleOwner
. Вызывается после методаonResume
. -
DESTROYED
: для уничтоженного объектаLifecycleOwner
. ЭтотLifecycle
не будет отправлять больше событий. Это событие достигается непосредственно перед методомonDestroy
.
The LifecycleObserver
Одним из самых интересных свойств Lifecycle
является то, что его легко наблюдать. Классы LifecycleObserver
могут наблюдать компоненты LifecycleOwner
, такие как Действия и Фрагменты. Он получает LifecycleOwner.Event
и может реагировать на них с помощью аннотации @OnLifeCycleEvent( Lifecycle.Event )
.
01
02
03
04
05
06
07
08
09
10
|
class MainObserver : LifecycleObserver, AnkoLogger {
@OnLifecycleEvent( Lifecycle.Event.ON_RESUME )
fun onResult() {
info(«onResult»)
}
@OnLifecycleEvent( Lifecycle.Event.ON_STOP )
fun onStop() {
info(«onStop»)
}
}
|
Методы, аннотированные @OnLifecycleEvent
, не нуждаются в аргументах, но если они используются, первым аргументом должен быть LifecycleOwner
. Когда аннотация использует Lifecycle.Event.ON_ANY
, метод должен ожидать два аргумента: LifecycleOwner
и Lifecycle.Event
.
1
2
3
4
5
6
|
@OnLifecycleEvent( Lifecycle.Event.ON_ANY )
fun onEvent( owner: LifecycleOwner, event: Lifecycle.Event )
{
info(«onEvent: ownerState: ${owner.lifecycle.currentState}»)
info(«onEvent: event: $event»)
}
|
Чтобы активировать аннотацию @OnLifecycleEvent
, LifecycleObserver
должен наблюдать Lifecycle
, иначе он не получит событие. Для этого вызовите Lifecycle.addObserver(LifecycleOwner)
и впоследствии LifecycleOwner
сможет реагировать на Lifecycle.Event
. Также возможно вызвать Lifecycle.removeObsever(LifecycleObserver)
чтобы удалить наблюдателя.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
|
class MainActivity : LifecycleActivity(), AnkoLogger {
@Inject lateinit var mainObserver: MainObserver
override fun onCreate(savedInstanceState: Bundle?) {
// …
// On Kotlin, instead of getLifecycle,
// we can call lifecycle directly
lifecycle.addObserver( mainObserver )
}
override fun onDestroy() {
// …
lifecycle.removeObserver( mainObserver )
}
}
|
Существуют различные интересные варианты использования для LifecycleObserver
. Например, его можно использовать для создания слоя Presenter
из шаблона архитектуры Presenter Model Viewer. Его также можно использовать для создания слушателей, которые могут прекратить прослушивание, когда Lifecycle
отключен.
4. Компонент LiveModel
Разработанный для совместной работы со слоем пользовательского интерфейса, компонент ViewModel
устраняет пробел, существовавший в Android с самого начала: он предоставляет способ элегантного управления и хранения объектов данных, связанных с представлением. Компонент поддерживает целостность данных между изменениями конфигурации, может использоваться совместно Activity и Fragments и является отличным инструментом, позволяющим полностью избежать утечек памяти.
ViewModel
всегда создается в тесной связи с конкретной областью действия, либо действием, либо фрагментом. Область сохраняется до тех пор, пока активность или фрагмент активны. С практической точки зрения ViewModel
повторно соединяется с представлением после изменений конфигурации, сохраняя себя до тех пор, пока основное представление не будет уничтожено. Согласно официальной документации:
Целью
ViewModel
является получение и хранение информации, необходимой для Действия или Фрагмента.
Вдобавок ко всему, ViewModel
облегчает разделение проблем в процессе разработки Android. Перемещая все связанные с данными операции в этот компонент и позволяя ему обрабатывать логику, можно значительно повысить тестируемость и удобство сопровождения приложения. С помощью ViewModel
можно легко принять архитектуру Android, предложенную в Google I / O 2017 года. Вы даже можете использовать его для принятия более сложных архитектурных шаблонов, таких как MVP или MVVM.
Реализация ViewModel
Есть два способа реализации ViewModel
. Стандартным является расширение класса с помощью конструктора без аргументов. Это самый простой способ, но он не работает с Dependency Injection.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
|
class MainViewModel : ViewModel() {
init {
// initialize some behavior
}
fun getData() : LiveData<String> {
// get some data
}
override fun onCleared() {
super.onCleared()
// called before its destruction
}
}
|
Чтобы получить ViewModel
с помощью этого метода, из Activity или Fragment, просто вызовите ViewModelProviders.of(FragmentActivity).get(Class<T>)
. Последний аргумент должен содержать класс ViewModel
. Тот же экземпляр ViewModel
будет выбран представлением и будет содержать все данные для этого представления.
1
2
|
val viewModel: MainViewModel =
ViewModelProviders.of(this).get(MyViewModel::class.java)
|
Обратите внимание, что поскольку ViewModel
взят из метода ViewModelProviders.of
, его конструктор не может получать аргументы. В качестве обходного пути вы можете реализовать ViewModelProvider.Factory
. На самом деле, это та же техника, которую мы используем для внедрения ViewModel
.
Внедрение ViewModel
При использовании DI все становится немного сложнее. Вам нужно будет реализовать ViewModelProvider.Factory
. Следующие шаги могут быть использованы для внедрения ViewModel
с помощью Dagger. ViewModelFactory
— это служебный класс, который предоставляет ViewModel
для области.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
@Suppress(«UNCHECKED_CAST»)
@Singleton
class ViewModelFactory
@Inject
constructor(
private val creators: Map<Class<out ViewModel>,
@JvmSuppressWildcards Provider<ViewModel>>
) : ViewModelProvider.Factory {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
var creator: Provider<out ViewModel>?
if (creator == null) {
for ((key, value) in creators) {
if (modelClass.isAssignableFrom(key)) {
creator = value
break
}
}
}
if (creator == null) {
throw IllegalArgumentException(«unknown model class » + modelClass)
}
try {
return creator.get() as T
} catch (e: Exception) {
throw RuntimeException(e)
}
}
}
|
Dagger также нужен @MapKey
определенный для ViewModel
и связыватель для каждой модели и для фабрики в модуле.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
// @MapKey
@MustBeDocumented
@Target(
AnnotationTarget.FUNCTION,
AnnotationTarget.PROPERTY_GETTER,
AnnotationTarget.PROPERTY_SETTER
)
@kotlin.annotation.Retention()
@MapKey
internal annotation class ViewModelKey(
val value: KClass<out ViewModel>)
// ViewModel Module
@Module
abstract class ViewModelsModule {
// Bind each ViewModel
@Binds
@IntoMap
@ViewModelKey( MainViewModel::class )
abstract fun bindMainViewModel( mainViewModel: MainViewModel ) : ViewModel
// ViewModel factory binding
@Binds
abstract fun bindViewModelFactory( factory: ViewModelFactory ) : ViewModelProvider.Factory
}
|
После этого следуйте стандартным процедурам Dagger, и вы сможете создать ViewModel
способную вводить аргументы в своем конструкторе. Чтобы создать новый экземпляр, получите ViewModelFactory
и возьмите из него желаемую ViewModel
.
1
2
3
4
5
6
7
|
// Get the ViewModel factory
@Inject lateinit var viewModelFactory:
ViewModelProvider.Factory
// Get ViewModel
val viewModel = ViewModelProviders.of(this, viewModelFactory)
.get(MainViewModel::class.java)
|
В нашем примере проекта вы можете внимательно изучить DI с помощью Dagger. Я также предоставил вам папку примеров в учебном репозитории GitHub с фрагментами, показывающими, как настроить ViewModels
в системе Dagger с использованием Kotlin.
1
2
3
4
5
6
7
8
9
|
class MainViewModel
@Inject
constructor(
private val repository: MainRepository
)
: ViewModel(), AnkoLogger {
// … code goes here
}
|
Вывод
Пока что наше путешествие по новым компонентам архитектуры Android было очень продуктивным. Тем не менее, у нас все еще есть основания для покрытия. В следующем уроке мы поговорим об удивительном компоненте LiveData
, исследуем его основные и расширенные функции и применимость этих концепций к нашему образцу приложения.
До скорого! А пока, посмотрите другие наши посты о разработке приложений для Android!