Статьи

Введение в компоненты архитектуры Android

Android был представлен миру в 2005 году, и за эти 12 лет существования платформа достигла удивительных успехов, став самой установленной мобильной ОС. За это время было запущено 14 различных версий операционной системы, причем Android всегда становится все более зрелым. Тем не менее, очень важная область платформы по-прежнему игнорировалась: стандартный шаблон архитектуры, способный обрабатывать особенности платформы и достаточно простой для понимания и принятия средним разработчиком.

Ну, лучше поздно, чем никогда. На последнем вводе / выводе Google команда Android наконец решила решить эту проблему и ответить на отзывы разработчиков по всему миру, объявив официальную рекомендацию по архитектуре приложений Android и предоставив строительные блоки для ее реализации: новую архитектуру Компоненты. И что еще лучше, им удалось сделать это, не ставя под угрозу открытость системы, которую мы все знаем и любим.

Компоненты архитектуры

В этом руководстве мы рассмотрим стандартизированную архитектуру, предложенную командой Android в Google I / O, и рассмотрим основные элементы новых компонентов архитектуры: Lifecycle , ViewModel , LifeData и Room . Мы не будем уделять слишком много внимания коду, а сосредоточимся на концепции и логике этих тем. Мы также рассмотрим некоторые простые фрагменты, все они написаны с использованием Kotlin, удивительного языка, который теперь официально поддерживается Android.

Если вы только начинаете свое путешествие как разработчик, возможно, вы не знаете точно, о чем я говорю. В конце концов, архитектура приложения может быть поначалу неясной темой. Но поверьте мне, вы скоро узнаете его важность! По мере роста и усложнения приложения его архитектура становится все более важной. Это может буквально сделать вашу работу блаженством или адом жизни.

Грубо говоря, архитектура приложения представляет собой согласованный план, который необходимо составить до начала процесса разработки. Этот план предоставляет карту того, как различные компоненты приложения должны быть организованы и связаны друг с другом. В нем представлены руководящие принципы, которые должны соблюдаться в процессе разработки, и вынуждает жертвовать собой (обычно связанные с большим количеством классов и шаблонов), которые в итоге помогут вам создать хорошо написанное приложение, которое будет более тестируемым, расширяемым и обслуживаемым.

Архитектура прикладного программного обеспечения — это процесс определения структурированного решения, отвечающего всем техническим и эксплуатационным требованиям, при оптимизации общих атрибутов качества, таких как производительность, безопасность и управляемость. Он включает ряд решений, основанных на широком спектре факторов, и каждое из этих решений может оказать значительное влияние на качество, производительность, ремонтопригодность и общий успех приложения.

— Руководство по архитектуре и дизайну программного обеспечения Microsoft

Хорошая архитектура учитывает множество факторов, особенно характеристики и ограничения системы. Существует множество архитектурных решений, каждый из которых имеет свои плюсы и минусы. Тем не менее, некоторые ключевые понятия являются общими для всех видений.

До последнего ввода-вывода Google система Android не рекомендовала какой-либо конкретной архитектуры для разработки приложений. Это означает, что вы были полностью свободны в принятии любой модели: MVP, MVC, MVPP или вообще без паттерна. Кроме того, платформа Android даже не предоставила нативных решений для проблем, создаваемых самой системой, в частности, жизненным циклом компонента.

Отличная новость на Google IO 2017

Таким образом, если вы хотите использовать шаблон приложения View View в своем приложении, вам нужно с нуля придумать собственное решение, написать много стандартного кода или использовать библиотеку без официальной поддержки. И это отсутствие стандартов создало множество плохо написанных приложений с кодами, которые было сложно поддерживать и тестировать.

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

Новое Руководство по архитектуре Android определяет некоторые ключевые принципы, которым должно соответствовать хорошее приложение Android, а также предлагает безопасный путь для разработчика для создания хорошего приложения. Однако в руководстве прямо указано, что представленный маршрут не является обязательным, и в конечном итоге решение является личным; Разработчик должен решить, какой тип архитектуры выбрать.

Согласно руководству, хорошее Android-приложение должно обеспечивать четкое разделение проблем и управлять пользовательским интерфейсом от модели. Любой код, который не обрабатывает пользовательский интерфейс или взаимодействие с операционной системой, не должен быть в Деятельности или Фрагменте, потому что поддержание их как можно более чистыми позволит вам избежать многих проблем, связанных с жизненным циклом. В конце концов, система может уничтожить Действия или Фрагменты в любое время. Кроме того, данные должны обрабатываться моделями, которые изолированы от пользовательского интерфейса и, следовательно, от проблем жизненного цикла.

Архитектура, которую рекомендует Android, не может быть легко помечена среди стандартных шаблонов, которые мы знаем. Он выглядит как шаблон Model View Controller, но он настолько тесно связан с архитектурой системы, что трудно маркировать каждый элемент с использованием известных соглашений. Это, однако, не имеет значения, так как важно то, что он полагается на новые компоненты архитектуры, чтобы создать разделение задач с отличной тестируемостью и ремонтопригодностью. А еще лучше, это легко реализовать.

Чтобы понять, что предлагает команда Android, мы должны знать все элементы компонентов архитектуры, поскольку именно они сделают за нас тяжелую работу. Существует четыре компонента, каждый с определенной ролью: Room , ViewModel , LiveData и Lifecycle . Все эти части имеют свои собственные обязанности, и они работают вместе, чтобы создать надежную архитектуру. Давайте посмотрим на упрощенную схему предложенной архитектуры, чтобы лучше ее понять.

Архитектура Android

Как видите, у нас есть три основных элемента, каждый из которых несет ответственность.

  1. Activity и Fragment представляют слой View , который не имеет дело с бизнес-логикой и сложными операциями. Он только настраивает представление, обрабатывает взаимодействие с пользователем и, самое главное, наблюдает и LiveData элементы LiveData взятые из ViewModel .
  2. ViewModel автоматически наблюдает за состоянием Lifecycle представления, поддерживая согласованность во время изменений конфигурации и других событий жизненного цикла Android. Это также требуется представлением для извлечения данных из Repository , который предоставляется как наблюдаемые LiveData . Важно понимать, что ViewModel никогда не ссылается на View напрямую и что обновления данных всегда выполняются сущностью LiveData .
  3. Repository не является специальным компонентом Android. Это простой класс, без какой-либо конкретной реализации, который отвечает за выборку данных из всех доступных источников, из базы данных в веб-службы. Он обрабатывает все эти данные, обычно преобразовывая их в наблюдаемые LiveData и делая их доступными для ViewModel .
  4. База данных Room — это библиотека отображений SQLite, которая облегчает процесс работы с базой данных. Он автоматически записывает кучу шаблонов, проверяет ошибки во время компиляции и, самое главное, может напрямую возвращать запросы с наблюдаемыми LiveData .

Я уверен, что вы заметили, что мы много говорили о наблюдаемых. Шаблон наблюдателя является одной из основ элемента LiveData и компонентов, LiveData Lifecycle . Этот шаблон позволяет объекту уведомлять список наблюдателей о любых изменениях его состояния или данных. Поэтому, когда Activity наблюдает за сущностью LiveData , она будет получать обновления, когда эти данные претерпевают любые изменения.

Еще одна рекомендация Android — консолидировать свою архитектуру с помощью системы Dependency Injection , такой как Google Dagger 2, или с помощью шаблона Service Locator (который намного проще, чем DI, но без многих его преимуществ). Мы не будем описывать DI или Service Locator в этом учебном пособии, но Envato Tuts + имеет несколько отличных учебных пособий по этим темам. Однако имейте в виду, что есть некоторые особенности работы с компонентами Dagger 2 и Android, которые будут описаны во второй части этой серии.

  • Android SDK
    Внедрение зависимостей с помощью Dagger 2 на Android
    Керри Перес Уанка

Мы должны глубоко погрузиться в аспекты новых компонентов, чтобы действительно понять и принять эту модель архитектуры. Однако мы не будем вдаваться во все детали этого урока. Из-за сложности каждого элемента в этом руководстве мы будем говорить только об общей идее каждого из них и рассмотрим некоторые упрощенные фрагменты кода. Мы постараемся охватить достаточно места, чтобы представить компоненты и начать работу. Но не бойтесь, потому что будущие статьи в этой серии будут углубляться и охватывать все особенности компонентов архитектуры.

К большинству компонентов приложения Android прикреплены жизненные циклы, которые управляются непосредственно самой системой. До недавнего времени разработчик мог отслеживать состояние компонентов и действовать соответствующим образом, инициализируя и заканчивая задачи в соответствующее время. Тем не менее, было действительно легко запутаться и сделать ошибки, связанные с этим типом операции. Но пакет android.arch.lifecycle изменил все это.

Теперь к действиям и фрагментам прикреплен объект Lifecycle который можно наблюдать с помощью классов LifecycleObserver , таких как ViewModel или любой объект, реализующий этот интерфейс. Это означает, что наблюдатель будет получать обновления об изменениях состояния объекта, который он наблюдает, например, когда действие приостановлено или когда он запускается. Он также может проверить текущее состояние наблюдаемого объекта. Так что теперь гораздо проще обрабатывать операции, которые должны учитывать жизненные циклы платформы.

LifecycleObserver реагирует на события жизненного цикла

На данный момент, чтобы создать действие или Fragment который соответствует этому новому стандарту, вы должны расширить LifecycleActivity или LifecycleFragment . Тем не менее, возможно, что это не всегда будет необходимо, так как команда Android стремится полностью интегрировать эти новые инструменты в свою среду.

1
2
3
4
5
6
7
class MainActivity : LifecycleActivity() {
 
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
    }
}

LifecycleObserver получает события LifecycleObserver Lifecycle и может реагировать с помощью аннотации. Переопределение метода не требуется.

01
02
03
04
05
06
07
08
09
10
11
class MainActivityObserver : LifecycleObserver, AnkoLogger {
   @OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
    fun onResume() {
       info(«onResume»)
   }
 
    @OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
    fun onPause() {
        info(«onPause»)
    }
}

Компонент LiveData представляет собой держатель данных, который содержит значение, которое можно наблюдать. Учитывая, что наблюдатель предоставил Lifecycle во время LiveData экземпляра LiveData , LiveData будет вести себя в соответствии с состоянием Lifecycle . Если состояние Lifecycle наблюдателя RESUMED или RESUMED , наблюдатель active ; в противном случае он inactive .

LiveData знает, когда данные были изменены, а также active ли наблюдатель и должен получить обновление. Еще одна интересная характеристика LiveData заключается в том, что он способен удалять наблюдателя, если он находится в состоянии Lifecycle.State.DESTROYED , избегая утечек памяти при наблюдении операций и фрагментов.

Процесс обновления значения LiveData

LiveData должен реализовывать onActive и onInactive .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
class LocationLiveData(context: Context)
    : LiveData<Location>(), AnkoLogger, LocationListener {
    private val locationManager: LocationManager =
            context.getSystemService(Context.LOCATION_SERVICE) as LocationManager
 
    override fun onActive() {
        info(«onActive»)
        locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0f, this)
    }
 
    override fun onInactive() {
        info(«onInactive»)
        locationManager.removeUpdates(this)
    }
    // ….
}

Чтобы наблюдать компонент LiveData , вы должны вызвать observer(LifecycleOwner, Observer<T>) .

1
2
3
4
5
6
7
8
9
class MainActivity : LifecycleActivity(), AnkoLogger {
    fun observeLocation() {
        val location = LocationLiveData(this)
        location.observe(this,
                Observer { location ->
                    info(«location: $location»)
                })
    }
}

Одним из наиболее важных классов новых компонентов архитектуры является ViewModel , который предназначен для хранения данных, связанных с пользовательским интерфейсом, сохраняя их целостность во время изменений конфигурации, таких как поворот экрана. ViewModel может LiveData с Repository , получая из него LiveData и делая его доступным, чтобы его можно было наблюдать в представлении. ViewModel также не нужно будет делать новые вызовы в Repository после изменений конфигурации, что значительно оптимизирует код.

ViewModel тесно связан с жизненным циклом пользовательского интерфейса

Чтобы создать модель представления, расширьте класс ViewModel .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
class MainActivityViewModel : ViewModel() {
 
    private var notes: MutableLiveData<List<String>>?
    fun getNotes(): LiveData<List<String>> {
        if (notes == null) {
            notes = MutableLiveData<List<String>>()
            loadNotes()
        }
        return notes!!
    }
    private fun loadNotes() {
        // do async operation to fetch notes
    }
}

Для доступа из представления вы можете вызвать ViewProviders.of(Activity|Fragment).get(ViewModel::class) . Этот фабричный метод возвратит новый экземпляр ViewModel или получит сохраненный, в зависимости от ситуации.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
class MainActivity : LifecycleActivity(), AnkoLogger {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
 
        val viewModel = ViewModelProviders.of(this)
                .get(MainActivityViewModel::class.java)
        viewModel.getNotes().observe(
                this, Observer {
                    notes -> info(«notes: $notes»)
                }
        )
    }
}

Android поддерживал SQLite с самого начала; однако, чтобы это работало, всегда нужно было писать много шаблонов. Кроме того, SQLite не сохранял POJO (простые старые объекты Java) и не проверял запросы во время компиляции. А вот и Room для решения этих проблем! Это библиотека отображения SQLite, способная сохранять Java POJO, напрямую преобразовывать запросы в объекты, проверять ошибки во время компиляции и создавать наблюдаемые LiveData из результатов запроса. Room — это библиотека объектно-реляционного сопоставления с некоторыми интересными дополнениями для Android.

До сих пор вы могли делать большую часть того, на что способен Room , используя другие библиотеки ORM Android. Тем не менее, ни один из них официально не поддерживается, и, самое главное, они не могут давать результаты LifeData . Библиотека Room идеально подходит в качестве постоянного слоя в предложенной архитектуре Android.

Чтобы создать базу данных Room , вам потребуется @Entity для сохранения, которым может быть любой Java POJO, интерфейс @Dao для выполнения запросов и операций ввода / вывода, а также абстрактный класс @Database который должен расширять RoomDatabase .

1
2
3
4
5
6
7
@Entity
class Note {
    @PrimaryKey
    var id: Long?
    var text: String?
    var date: Long?
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
@Dao
interface NoteDAO {
    @Insert( onConflict = OnConflictStrategy.REPLACE )
    fun insertNote(note: Note): Long
 
    @Update( onConflict = OnConflictStrategy.REPLACE )
    fun updateNote(note: Note): Int
 
    @Delete
    fun deleteNote(note: Note): Int
 
    @Query(«SELECT * FROM note»)
    fun findAllNotes(): LiveData<Note>
 
    // on Kotlin the query arguments are renamed
    // to arg[N], being N the argument number.
    // on Java the arguments assume its original name
    @Query(«SELECT * FROM note WHERE id = :arg0»)
    fun findNoteById(id: Long): LiveData<Note>
}
1
2
3
4
@Database( entities = arrayOf(Note::class), version = 1)
abstract class Databse : RoomDatabase() {
    abstract fun noteDAO(): NoteDAO
}

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

1
2
3
4
5
6
7
allprojects {
    repositories {
        jcenter()
        // Add Google repository
        maven { url ‘https://maven.google.com’ }
    }
}

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

Мы говорили о большинстве тем, связанных с предлагаемой архитектурой Android и ее компонентами; однако подробности о реализации Компонентов и некоторых дополнительных функциях, таких как класс Repository и система Dagger 2, не могут быть рассмотрены в этой первой части. Мы рассмотрим эти темы в следующих постах.

До скорого!