Мы уже рассмотрели много вопросов в нашей серии Компоненты для архитектуры Android.  Мы начали говорить об идее новой архитектуры и рассмотрели ключевые компоненты, представленные в Google I / O.  Во втором посте мы начали наше глубокое исследование основных компонентов пакета , внимательно LiveModel компоненты Lifecycle и LiveModel .  В этом посте мы продолжим исследовать компоненты архитектуры, на этот раз анализируя LiveData компонент LiveData . 
  Я предполагаю, что вы знакомы с концепциями и компонентами, описанными в последних руководствах, такими как Lifecycle , LifecycleOwner и LifecycleObserver .  Если нет, взгляните на первый пост в этой серии , где я обсуждаю общую идею новой архитектуры Android и ее компонентов. 
Мы продолжим работу над примером приложения, которое мы начали в последней части этой серии. Вы можете найти его в репозитории GitHub .
1. Компонент LiveData
  LiveData является держателем данных.  Его можно наблюдать, он может содержать любые данные, и, кроме того, он также поддерживает жизненный цикл .  С практической точки зрения LiveData может быть настроен на отправку обновлений данных только тогда, когда активен наблюдатель.  Благодаря своей осведомленности о LifecycleOwner LiveData компонент LiveData , когда его наблюдает LifecycleOwner будет отправлять обновления только тогда, когда Lifecycle наблюдателя все еще активен, и удаляет наблюдаемую связь после разрушения Lifecycle наблюдателя. 
  Компонент LiveData имеет много интересных характеристик: 
-   предотвращает утечки памяти, когда наблюдатель привязан к 
Lifecycle - предотвращает сбои из-за прекращения деятельности
 - данные всегда актуальны
 - обрабатывает изменения конфигурации плавно
 - позволяет делиться ресурсами
 - автоматически обрабатывает жизненный цикл
 
  2. Наблюдение LiveData 
  Компонент LiveData отправляет обновления данных только тогда, когда его наблюдатель «активен».  При наблюдении LifecycleOwner компонент LiveData считает, что наблюдатель активен, только когда его Lifecycle находится в состоянии STARTED или RESUMED , в противном случае он будет считать наблюдателя неактивным.  Во время неактивного состояния наблюдателя LiveData останавливает поток обновления данных, пока его наблюдатель снова не станет активным.  Если наблюдатель уничтожен, LiveData удалит его ссылку на наблюдателя. 

  Чтобы добиться такого поведения, LiveData создает тесную связь с Lifecycle наблюдателя, когда наблюдается LifecycleOwner .  Эта емкость позволяет избежать утечек памяти при просмотре LiveData .  Однако, если процесс наблюдения вызывается без LifecycleOwner , компонент LiveData не будет реагировать на состояние Lifecycle , и статус наблюдателя должен обрабатываться вручную. 
  Чтобы наблюдать LiveData , вызовите observe(LifecycleOwner, Observer<T>) или observeForever(Observer<T>) . 
-   
observe(LifecycleOwner, Observer<T>): это стандартный способ наблюденияLiveData. Он привязывает наблюдателя кLifecycleLiveData, изменяя активное и неактивное состояниеLiveDataсоответствии с текущим состояниемLifecycleOwner. -   
observeForever(Observer<T>): этот метод не используетLifecycleOwner, поэтомуLiveDataне сможет отвечать на событияLifecycle. При использовании этого метода очень важно вызыватьremoveObserver(Observer<T>), в противном случае наблюдатель может не собирать мусор, что приводит к утечке памяти. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
 // called from a LifecycleOwner 
location.observe( 
        // LifecycleOwner 
        this, 
        // creating an observer 
        Observer { 
            location -> 
            info(«location: ${location!!.latitude}, 
                ${location.longitude}») 
        }) 
} 
// Observing without LifecycleOwner 
val observer = Observer { 
        location -> 
        info(«location: ${location!!.latitude}, 
        ${location.longitude}») 
    }) 
location.observeForever(observer) 
// when observer without a LivecyleOwner 
// it is necessary to remove the observers at some point 
location.removeObserver( observer ) 
 | 
  3. Реализация LiveData 
  LiveData<T> тип в классе LiveData<T> определяет тип данных, которые будут храниться.  Например, LiveData<Location> содержит данные о Location .  Или LiveData<String> содержит String . 
  В реализации компонента необходимо учитывать два основных метода: onActive() и onInactive() .  Оба метода реагируют на состояние наблюдателя. 
Пример реализации
  В нашем примере проекта мы использовали много объектов LiveData , но мы реализовали только один: LocationLiveData .  Класс имеет дело с GPS Location , передавая текущую позицию только для активного наблюдателя.  Обратите внимание, что класс обновляет свое значение в методе onLocationChanged , передавая текущему активному наблюдателю обновленные данные Location . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
 | 
 class LocationLiveData 
    @Inject 
    constructor( 
            context: Context 
    ) : LiveData<Location>(), LocationListener, AnkoLogger { 
    private val locationManager: LocationManager = 
            context.getSystemService(Context.LOCATION_SERVICE) as LocationManager 
    @SuppressLint(«MissingPermission») 
    override fun onInactive() { 
        info(«onInactive») 
        locationManager.removeUpdates(this) 
    } 
    @SuppressLint(«MissingPermission») 
    fun refreshLocation() { 
        info(«refreshLocation») 
        locationManager.requestSingleUpdate(LocationManager.GPS_PROVIDER, this, null ) 
    } 
} 
 | 
  4. Вспомогательный класс MutableLiveData 
  MutableLiveData<T> — это вспомогательный класс, который расширяет LiveData и предоставляет postValue и setValue .  Кроме этого, он ведет себя точно так же, как его родитель.  Чтобы использовать его, определите тип данных, которые он содержит, например MutableLiveData<String> для хранения String , и создайте новый экземпляр. 
| 
 1 
 | 
 val myData: MutableLiveData<String> = MutableLiveData() 
 | 
  Чтобы отправить обновления наблюдателю, позвоните postValue или setValue .  Поведение этих методов очень похоже;  однако setValue будет непосредственно устанавливать новое значение и может вызываться только из основного потока, в то время как postValue создает новую задачу в основном потоке для установки нового значения и может вызываться из фонового потока. 
| 
 1 
2 
3 
4 
5 
6 
7 
8 
 | 
 fun updateData() { 
    // must be called from the main thread 
    myData.value = api.getUpdate 
} 
fun updateDataFromBG(){ 
    // may be called from bg thread 
    myData.postValue(api.getUpdate) 
} 
 | 
  Важно учитывать, что поскольку метод postValue создает новую Task и публикует сообщения в главном потоке, он будет медленнее, чем прямые вызовы setValue . 
  5. Преобразования LiveData 
  В конце концов, вам нужно изменить LiveData и LiveData его новое значение наблюдателю.  Или, может быть, вам нужно создать цепную реакцию между двумя объектами LiveData , чтобы один реагировал на изменения другого.  Чтобы справиться с обеими ситуациями, вы можете использовать класс Transformations . 
Преобразования карты
  Transformations.map применяет функцию к экземпляру LiveData и отправляет результат его наблюдателям, давая вам возможность манипулировать значением данных. 

  Это очень легко реализовать Transformations.map .  Все, что вам нужно сделать, это предоставить наблюдаемые LiveData и Function которая будет вызываться при изменении наблюдаемых LiveData , помня, что Function должна возвращать новое значение преобразованных LiveData . 
  Предположим, что у вас есть LiveData который должен вызывать API при изменении значения String , например поля поиска. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
 | 
 // LiveData that calls api 
// when ‘searchLive’ changes its value 
val apiLive: LiveData<Result> = Transformations.map( 
            searchLive, 
            { 
                query -> return@map api.call(query) 
            } 
    ) 
// Every time that ‘searchLive’ have 
// its value updated, it will call 
// ‘apiLive’ Transformation.map 
fun updateSearch( query: String ) { 
    searchLive.postValue( query ) 
} 
 | 
Преобразования SwitchMap
  Transformations.switchMap очень похож на Transformations.map , но в результате он должен возвращать объект LiveData .  Его немного сложнее в использовании, но он позволяет создавать мощные цепные реакции. 
  В нашем проекте мы использовали Transformations.switchMap чтобы создать реакцию между LocationLiveData и ApiResponse<WeatherResponse> . 
-   Наша 
Transformation.switchMapнаблюдает за изменениямиLocationLiveData. -   Обновленное значение 
LocationLiveDataиспользуется для вызоваMainRepositoryчтобы получить погоду для указанного местоположения. -   Хранилище вызывает 
OpenWeatherServiceкоторый в результате выдаетLiveData<ApiResponse<WeatherResponse>>. -   Затем возвращенные 
LiveDataотслеживаютсяMediatorLiveData, который отвечает за изменение полученного значения и обновление погоды, отображаемой в слое вида. 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
20 
21 
22 
23 
 | 
 class MainViewModel 
@Inject 
constructor( 
        private val repository: MainRepository 
) 
    : ViewModel(), AnkoLogger { 
         // Location 
    private val location: LocationLiveData = 
        repository.locationLiveDa() 
         private var weatherByLocationResponse: 
            LiveData<ApiResponse<WeatherResponse>> = 
                Transformations.switchMap( 
            location, 
            { 
                l -> 
                info(«weatherByLocation: \nlocation: $l») 
                return@switchMap repository.getWeatherByLocation(l) 
            } 
    ) 
             } 
 | 
  Однако LiveData трудоемких операций в ваших преобразованиях LiveData .  В приведенном выше коде оба метода Transformations выполняются в основном потоке. 
  6. MediatorLiveData 
  MediatorLiveData — это более продвинутый тип LiveData .  Он обладает возможностями, очень похожими на возможности класса Transformations : он способен реагировать на другие объекты LiveData , вызывая Function при изменении наблюдаемых данных.  Тем не менее, он имеет много преимуществ по сравнению с Transformations , так как он не должен запускаться в основном потоке и может наблюдать несколько LiveData одновременно. 
  Чтобы наблюдать LiveData , вызовите addSource(LiveData<S>, Observer<S>) , чтобы наблюдатель реагировал на метод onChanged из данной LiveData .  Чтобы остановить наблюдение, вызовите removeSource(LiveData<S>) . 
| 
 01 
02 
03 
04 
05 
06 
07 
08 
09 
10 
11 
12 
13 
14 
15 
16 
17 
18 
19 
 | 
 val mediatorData: MediatorLiveData<String> = MediatorLiveData() 
       mediatorData.addSource( 
               dataA, 
               { 
                   value -> 
                   // react to value 
                   info(«my value $value») 
               } 
       ) 
       mediatorData.addSource( 
               dataB, 
               { 
                   value -> 
                   // react to value 
                   info(«my value $value») 
                   // we can remove the source once used 
                   mediatorData.removeSource(dataB) 
               } 
       ) 
 | 
  В нашем проекте данные, наблюдаемые слоем вида, который содержит выставляемую погоду, представляют собой MediatorLiveData .  Компонент наблюдает за двумя другими объектами LiveData : weatherByLocationResponse , который получает обновления погоды по местоположению, и weatherByCityResponse , который получает обновления погоды по названию города.  Каждый раз, когда эти объекты обновляются, weatherByCityResponse будет обновлять слой представления с текущей запрошенной погодой. 
  В MainViewModel мы наблюдаем LiveData и предоставляем объект weather для просмотра. 
| 
 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 
48 
49 
50 
51 
52 
53 
54 
55 
56 
 | 
 class MainViewModel 
@Inject 
constructor( 
        private val repository: MainRepository 
) 
    : ViewModel(), AnkoLogger { 
         // … 
    // Value observed by View. 
    // It transform a WeatherResponse to a WeatherMain. 
    private val weather: 
            MediatorLiveData<ApiResponse<WeatherMain>> = 
                MediatorLiveData() 
         // retrieve weather LiveData 
    fun getWeather(): LiveData<ApiResponse<WeatherMain>> { 
        info(«getWeather») 
        return weather 
    } 
         private fun addWeatherSources(){ 
        info(«addWeatherSources») 
        weather.addSource( 
                weatherByCityResponse, 
                { 
                    w -> 
                    info(«addWeatherSources: \nweather: ${w!!.data!!}») 
                    updateWeather(w.data!!) 
                } 
        ) 
        weather.addSource( 
                weatherByLocationResponse, 
                { 
                    w -> 
                    info(«addWeatherSources: weatherByLocationResponse: \n${w!!.data!!}») 
                    updateWeather(w.data!!) 
                } 
        ) 
    } 
         private fun updateWeather(w: WeatherResponse){ 
        info(«updateWeather») 
        // getting weather from today 
        val weatherMain = WeatherMain.factory(w) 
        // save on shared preferences 
        repository.saveWeatherMainOnPrefs(weatherMain) 
        // update weather value 
        weather.postValue(ApiResponse(data = weatherMain)) 
    } 
         init { 
        // … 
        addWeatherSources() 
    } 
} 
 | 
  В MainActivity наблюдается погода и ее результат показывается пользователю. 
| 
 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 
 | 
 private fun initModel() { 
       // Get ViewModel 
       viewModel = ViewModelProviders.of(this, viewModelFactory) 
               .get(MainViewModel::class.java) 
       if (viewModel != null) { 
           // observe weather 
           viewModel!!.getWeather().observe( 
                   this@MainActivity, 
                   Observer { 
                       r -> 
                       if ( r != null ) { 
                           info(«Weather received on MainActivity:\n $r») 
                           if (!r.hasError()) { 
                               // Doesn’t have any errors 
                               info(«weather: ${r.data}») 
                               if (r.data != null) 
                                   setUI(r.data) 
                           } else { 
                               // error 
                               error(«error: ${r.error}») 
                               isLoading(false) 
                               if (r.error!!.statusCode != 0) { 
                                   if (r.error!!.message != null) 
                                       toast(r.error.message!!) 
                                   else 
                                       toast(«An error occurred») 
                               } 
                           } 
                       } 
                   } 
           ) 
       } 
   } 
 | 
  MediatorLiveData также использовалась в качестве основы для объекта, который обрабатывает вызовы API OpenWeatherMap.  Посмотрите на эту реализацию;  он более продвинутый, чем приведенный выше, и его действительно стоит изучить.  Если вам интересно, взгляните на OpenWeatherService , обратив особое внимание на класс Mediator<T> . 
7. Заключение
  Мы почти завершили исследование компонентов архитектуры Android.  К настоящему времени вы должны понимать достаточно, чтобы создать несколько мощных приложений.  В следующем посте мы рассмотрим Room , ORM, который оборачивает SQLite и может давать результаты LiveData .  Компонент Room идеально вписывается в эту архитектуру, и это последний кусок головоломки. 
До скорого! А пока, посмотрите другие наши посты о разработке приложений для Android!




