Статьи

Реализация шины событий с LiveData

Конечный продукт
Что вы будете создавать

На последнем вводе-выводе Google команда Android выпустила набор мощных компонентов архитектуры Android. Они называют это:

Коллекция библиотек, которые помогут вам разрабатывать надежные, тестируемые и поддерживаемые приложения. Начните с классов для управления жизненным циклом компонента пользовательского интерфейса и сохранением данных.

Если вы еще не узнали о них, настоятельно рекомендуем ознакомиться с нашей замечательной серией статей о Envato Tuts + о компонентах архитектуры Android от Tin Megali. Убедитесь, что вы погрузитесь!

  • Android SDK
    Введение в компоненты архитектуры Android
    Жестяная мегали

В этом руководстве я покажу, как использовать компоненты LiveData из архитектурных компонентов Android для создания шины событий. Шина событий может использоваться для эффективной связи между компонентами Android или между слоями вашего приложения, например, для связи с Activity из IntentService который файл завершил загрузку.

Мы IntentService очень простое приложение, которое запускает IntentService для выполнения некоторой работы — из Activity . IntentService этого наша IntentService свяжется с Activity после завершения работы. Наш канал связи будет из библиотеки LiveData .

Чтобы следовать этому руководству, вам понадобится:

Вы также можете изучить все тонкости языка Kotlin в моей серии Kotlin From Scratch .

  • Kotlin From Scratch: переменные, базовые типы и массивы

  • Котлин с нуля: классы и объекты

MainActivity Android Studio 3 и создайте новый проект с пустым действием MainActivity .

Android Studio новый экран проекта

После создания нового проекта укажите артефакты 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, как найти библиотеку. Убедитесь, что вы не забыли синхронизировать свой проект после добавления их.

Здесь наша 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() , передавая соответствующее событие в качестве параметра.

У нас просто есть 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>

Мы объявили наши свойства 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)
    }
    // …
}

Мы просто создаем простой класс сообщений о событиях, который мы хотим передать по шине событий (или LiveData ).

1
data class CustomEvent (val eventProp: String)

Вы можете добавить больше свойств в этот класс, если хотите.

Мы реализовали 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»/>

Нам нужен по крайней мере один наблюдатель, чтобы наш механизм был полезным. Поэтому внутри класса 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 обновляет только те компоненты приложения, которые находятся в состоянии активного жизненного цикла.

Наконец, вы можете запустить приложение! Нажмите кнопку Do Work и через 30 секунд вы увидите результат.

Результат учебного проекта

Вы можете получить полный исходный код из нашего репозитория GitHub .

В этом руководстве вы узнали, как легко использовать компоненты LiveData из архитектурных компонентов Android для создания шины событий — для эффективной связи с компонентами вашего приложения.

Я предполагаю, что вы знаете о других библиотеках, которые вы можете использовать для той же цели, таких как Android LocalBroadcastManager или популярный зеленый робот EventBus для реализации шины событий в вашем приложении Android. Вы можете видеть, что вместо них предпочтительнее использовать LiveData потому что вы избегаете написания шаблонного или подробного кода, а LiveData обеспечивает вам большую гибкость.

Чтобы узнать больше о кодировании для Android, ознакомьтесь с другими нашими курсами и учебными пособиями здесь, на Envato Tuts +!

  • Android
    Связь в приложении для Android с EventBus
  • Android
    Как начать работу с Push-уведомлениями на Android
    Ашраф Хатхибелагал
  • Карты Гугл
    Начало работы с Google Maps для Android: основы
    Пол Требилкокс-Руис
  • Android SDK
    Параллелизм в RxJava 2