На последнем вводе-выводе Google команда Android выпустила набор мощных компонентов архитектуры Android. Они называют это:
Коллекция библиотек, которые помогут вам разрабатывать надежные, тестируемые и поддерживаемые приложения. Начните с классов для управления жизненным циклом компонента пользовательского интерфейса и сохранением данных.
Если вы еще не узнали о них, настоятельно рекомендуем ознакомиться с нашей замечательной серией статей о Envato Tuts + о компонентах архитектуры Android от Tin Megali. Убедитесь, что вы погрузитесь!
В этом руководстве я покажу, как использовать компоненты LiveData
из архитектурных компонентов Android для создания шины событий. Шина событий может использоваться для эффективной связи между компонентами Android или между слоями вашего приложения, например, для связи с Activity
из IntentService
который файл завершил загрузку.
Мы IntentService
очень простое приложение, которое запускает IntentService
для выполнения некоторой работы — из Activity
. IntentService
этого наша IntentService
свяжется с Activity
после завершения работы. Наш канал связи будет из библиотеки LiveData
.
Предпосылки
Чтобы следовать этому руководству, вам понадобится:
- Android Studio 3.0 или выше
- Плагин Kotlin 1.1.51 или выше
- базовое понимание архитектурных компонентов Android (особенно компонента
LiveData
) - базовое понимание событийного автобуса
Вы также можете изучить все тонкости языка Kotlin в моей серии Kotlin From Scratch .
1. Создайте проект Android Studio
MainActivity
Android Studio 3 и создайте новый проект с пустым действием MainActivity
.
2. Добавьте компоненты жизненного цикла
После создания нового проекта укажите артефакты LifeCycle
и LiveData
в LiveData
модуля вашего приложения. Обратите внимание, что на момент написания статьи новые архитектурные компоненты теперь находятся в стабильной версии Таким образом, это означает, что вы можете начать использовать их в производственных приложениях.
1
2
3
4
5
6
7
8
|
dependencies {
implementation fileTree(dir: ‘libs’, include: [‘*.jar’])
implementation»org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version»
implementation ‘com.android.support:appcompat-v7:26.1.0’
implementation «android.arch.lifecycle:runtime:1.0.3»
implementation «android.arch.lifecycle:extensions:1.0.0»
}
|
Эти артефакты доступны в репозитории Google Maven.
1
2
3
4
5
6
|
allprojects {
repositories {
google()
jcenter()
}
}
|
Добавляя зависимости, мы научили Gradle, как найти библиотеку. Убедитесь, что вы не забыли синхронизировать свой проект после добавления их.
3. Создайте подкласс активности LifecycleOwner
Здесь наша MainActivity
реализует интерфейс LifecycleOwner
.
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
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
|
import android.arch.lifecycle.Lifecycle
import android.arch.lifecycle.LifecycleOwner
import android.arch.lifecycle.LifecycleRegistry
import android.arch.lifecycle.Observer
import android.content.Intent
import android.os.Bundle
import android.support.v7.app.AppCompatActivity
import android.view.View
import android.widget.Button
import android.widget.TextView
class MainActivity : AppCompatActivity(), LifecycleOwner {
private val registry = LifecycleRegistry(this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
registry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}
override fun getLifecycle() : Lifecycle = registry
override fun onStart() {
super.onStart()
registry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}
override fun onResume() {
super.onResume()
registry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}
override fun onPause() {
super.onPause()
registry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}
override fun onStop() {
super.onStop()
registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}
override fun onDestroy() {
super.onDestroy()
registry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
}
}
|
Наша деятельность просто обрабатывает стандартные события жизненного цикла. Внутри каждого из событий жизненного цикла он вызывает registry.handleLifecycleEvent()
, передавая соответствующее событие в качестве параметра.
4. Создайте макет
У нас просто есть Button
которая запускает сервис. TextView
(невидимый по умолчанию) показывает текст "Work completed!"
когда служба связывается с нашей MainActivity
.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
|
<?xml version=»1.0″ encoding=»utf-8″?>
<LinearLayout
xmlns:android=»https://schemas.android.com/apk/res/android»
xmlns:tools=»http://schemas.android.com/tools»
android:layout_width=»match_parent»
android:layout_height=»match_parent»
android:orientation=»vertical»
android:gravity=»center_horizontal|center_vertical»>
<Button
android:id=»@+id/btn_download»
android:layout_width=»wrap_content»
android:layout_height=»wrap_content»
android:text=»Do work»/>
<TextView
android:id=»@+id/tv_result»
android:layout_width=»wrap_content»
android:layout_height=»17dp»
android:text=»Work completed!»
android:visibility=»invisible»/>
</LinearLayout>
|
5. Инициализируйте виджеты
Мы объявили наши свойства doWorkButton
и resultTextView
внутри класса lateinit
модификатором lateinit
. Затем мы инициализируем их внутри onCreate()
. doWorkButton
, когда нажимается doWorkButton
, мы отключаем его (чтобы не нажимать кнопку более одного раза) и запускаем наш MyIntentService
(мы скоро к этому MyIntentService
).
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
|
class MainActivity : AppCompatActivity(), LifecycleOwner {
private lateinit var doWorkButton: Button
private lateinit var resultTextView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
// …
doWorkButton = findViewById(R.id.btn_download)
doWorkButton.setOnClickListener {
doWorkButton.isEnabled = false
resultTextView.visibility = View.INVISIBLE
val serviceIntent = Intent(this, MyIntentService::class.java)
startService(serviceIntent)
}
resultTextView = findViewById(R.id.tv_result)
}
// …
}
|
6. Создайте пользовательский класс событий
Мы просто создаем простой класс сообщений о событиях, который мы хотим передать по шине событий (или LiveData
).
1
|
data class CustomEvent (val eventProp: String)
|
Вы можете добавить больше свойств в этот класс, если хотите.
7. Внедрение услуг
Мы реализовали IntentService под названием MyIntentService
. Помните, что IntentService
вне области действия и имеет фоновый поток, поэтому рекомендуется выполнять трудоемкие задачи, такие как загрузка или извлечение удаленных данных через API внутри него.
Однако обратите внимание, что в Android 8.0, если вы не сделаете IntentService
сервисом переднего плана с помощью startForeground()
, система Android не позволит вашей службе работать более 1 минуты, иначе она будет немедленно остановлена. Этот механизм предназначен для эффективного управления системными ресурсами, такими как время автономной работы. Если ваше приложение ориентировано на Android 8.0, рекомендуется использовать JobIntentService .
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
|
import android.app.IntentService
import android.arch.lifecycle.MutableLiveData
import android.content.Intent
import android.os.SystemClock
class MyIntentService: IntentService(«MyIntentService») {
companion object {
var BUS = MutableLiveData<CustomEvent>()
}
override fun onHandleIntent(intent: Intent?) {
// simulate work
SystemClock.sleep(3000)
// assuming work is done
val event = CustomEvent(«value»)
if (BUS.hasActiveObservers()) {
BUS.postValue(event)
} else {
// show notification
}
}
}
|
Мы создаем безымянный сопутствующий объект, чей сопутствующий класс — MyIntentService
. Этот сопутствующий объект имеет свойство BUS
, которое является экземпляром MutableLiveData
. Помните, что сопутствующие объекты являются синглетонами, поэтому это означает, что существует только один экземпляр BUS
. Мы также передали наше CustomEvent
в качестве аргумента типа в универсальный класс MutableLiveData
.
Помните, что класс MutableLiveData
является подклассом LiveData
и имеет метод postValue()
который можно вызывать из фонового потока.
01
02
03
04
05
06
07
08
09
10
11
|
public class MutableLiveData<T> extends LiveData<T> {
@Override
public void postValue(T value) {
super.postValue(value);
}
@Override
public void setValue(T value) {
super.setValue(value);
}
}
|
Внутри onHandleIntent()
у нас есть бизнес-логика. Помните, что этот метод вызывается в фоновом потоке (одно из основных различий между IntentService
и обычным Service
). IntentService
завершает работу самостоятельно, когда метод onHandleIntent()
завершает свою работу.
В нашем собственном случае мы моделируем выполняемую работу (это может быть загрузка файла или связь с удаленным API) путем ожидания текущего потока в течение 30 секунд. Затем мы проверили, есть ли в нашей BUS
активные наблюдатели, используя метод hasActiveObservers()
. Если они есть, уведомите и передайте им наше сообщение о событии с помощью метода postValue()
, иначе мы можем просто показать уведомление (это не было закодировано в приведенном выше примере для краткости).
Не забудьте включить службу в файл манифеста.
1
|
<service android:name=»MyIntentService»/>
|
8. Реализация наблюдателя
Нам нужен по крайней мере один наблюдатель, чтобы наш механизм был полезным. Поэтому внутри класса MainActivity
мы собираемся подписать анонимного наблюдателя.
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
|
class MainActivity : AppCompatActivity(), LifecycleOwner {
// …
override fun onCreate(savedInstanceState: Bundle?) {
// …
MyIntentService.BUS.observe(
this,
Observer { event ->
resultTextView.visibility = View.VISIBLE
downloadButton.isEnabled = true
Log.d(«MainActivity», event?.eventProp)
})
}
// …
}
|
Внутри onCreate()
MainActivity
мы получили шину событий BUS
от MyIntentService
. Затем мы зарегистрировали наблюдателя для шины событий (т.е. LiveData
) с помощью метода observe()
. Затем мы зарегистрировали и указали анонимного наблюдателя, используя MainActivity
как LifecycleOwner
. Этот анонимный наблюдатель получает уведомление, когда происходит любое из следующего:
- Уже есть данные, доступные в
LiveData
когда он подписывается. - Данные внутри
LiveData
изменены.
Когда происходит что-либо из этого, мы получаем данные event
(из LiveData
) в главном потоке приложения в качестве входных данных для лямбда- LiveData
. Затем мы делаем следующее в теле лямбды:
- Сделайте
resultTextView
видимым. - Включить
doWorkButton
. - Зарегистрируйте наше пользовательское значение свойства
eventProp
в Logcat.
Помните следующее о LiveData
:
- Когда новый наблюдатель присоединяется к нашим
LiveData
после изменения конфигурации,LiveData
отправит наблюдателю последние данные, которые он получил, даже без нашегоLiveData
сделать это. Другими словами, он делает это автоматически. - Когда
LifecycleOwner
будет уничтожен, наблюдатель автоматически отменит подписку. - Наконец,
LiveData
является наблюдаемой, котораяLiveData
жизненный цикл. Согласно документам:
LiveData — это наблюдаемый класс держателя данных. В отличие от обычной наблюдаемой, LiveData учитывает жизненный цикл, то есть он учитывает жизненный цикл других компонентов приложения, таких как действия, фрагменты или службы. Эта осведомленность гарантирует, что LiveData обновляет только те компоненты приложения, которые находятся в состоянии активного жизненного цикла.
9. Тестирование приложения
Наконец, вы можете запустить приложение! Нажмите кнопку Do Work и через 30 секунд вы увидите результат.
Вы можете получить полный исходный код из нашего репозитория GitHub .
Вывод
В этом руководстве вы узнали, как легко использовать компоненты LiveData
из архитектурных компонентов Android для создания шины событий — для эффективной связи с компонентами вашего приложения.
Я предполагаю, что вы знаете о других библиотеках, которые вы можете использовать для той же цели, таких как Android LocalBroadcastManager или популярный зеленый робот EventBus для реализации шины событий в вашем приложении Android. Вы можете видеть, что вместо них предпочтительнее использовать LiveData
потому что вы избегаете написания шаблонного или подробного кода, а LiveData
обеспечивает вам большую гибкость.
Чтобы узнать больше о кодировании для Android, ознакомьтесь с другими нашими курсами и учебными пособиями здесь, на Envato Tuts +!