Статьи

Android, Rx и Kotlin: часть 2

Я не совсем честен с вами в  моем предыдущем посте : код, который я показал в статье, не совсем приводит к короткому видео приложения в верхней части статьи.

Если вы запустите код как есть, вы заметите что-то очень раздражающее (и недопустимое в любом приложении): всякий раз, когда приложение делает вид, что выполняет сетевой вызов, весь пользовательский интерфейс останавливается на секунду. Вы ничего не можете набрать, и значок загрузки перестает вращаться. Это классический признак блокировки основного потока. Вы помните, что я имитирую сетевые вызовы, просто немного поспав, и, очевидно, если вы сделаете это в главном потоке, вы заморозите свой пользовательский интерфейс.

По умолчанию Rx запускает все в вашем текущем потоке, который является основным потоком в Android: поток, отвечающий за обновление вашего пользовательского интерфейса. Android в точности похож на большинство графических наборов инструментов: вы должны использовать только основной поток для обновления вашего пользовательского интерфейса, но все остальное, что вы делаете (доступ к сети или файловой системе, вычисления, обновления баз и т. Д.), Необходимо выполнять в фоновом потоке. Rx имеет очень хорошее решение этой проблемы.

Резьбонарезной

До недавнего времени  AsyncTask  был рекомендуемым способом выполнения такого рода задач: создавая и выполняя AsyncTask, вы можете запускать свой код в двух местах, один из которых будет выполняться в фоновом потоке (doInBackground ()), и как только эта задача завершится , код, который будет выполняться в главном потоке (onPostExecute ()).

У AsyncTask было смутное прошлое, и оно довольно сильно изменилось во многих версиях API Android: сначала он был однопоточным, затем стал многопоточным, а в последнее время он работает в фоновом режиме в одном потоке, пытаясь обеспечить как параллелизм и последовательность в то же время. Если вам нужна дополнительная информация об AsyncTask,  эта статья объясняет, как она развивалась .

Это не единственная проблема с AsyncTask: также довольно сложно правильно настроить его поведение во время изменений конфигурации или возможности приостановки или уничтожения вашей активности во время выполнения задачи.

Rx предлагает несколько решений некоторых из этих проблем, но не все.

Threading и Rx

Rx предлагает два метода для управления вашей моделью потоков: подписка () и наблюдений ().

В двух словах, наблюдающая () определяет, в каком потоке будет работать ваш наблюдатель (именно здесь вы обычно выполняете работу), а подписка () определяет поток, в котором будут выполняться ваши операторы (map (), filter () и т. Д.).

Параметр, который вы указываете этим методам, — это планировщик, абстракция Rx, которая инкапсулирует поток. Rx определяет несколько стандартных:

  • Schedulers.computation (): когда вы что-то вычисляете.
  • Schedulers.io (): когда вы выполняете ввод / вывод (сеть, файловая система, доступ к базе данных и т. Д.).
  • И несколько других, я не буду входить сюда.

Кроме того, RxAndroid определяет более специфичный для Android AndroidSchedulers.mainThread (), что не требует пояснений.

Типичным фрагментом кода на Android является выполнение нескольких задач в фоновом режиме (доступ к сети, дорогостоящие вычисления, обновление базы данных и т. Д.), И в зависимости от результата этого действия вы обновляете свой пользовательский интерфейс. Способ реализовать это с помощью Rx прост: вы подписываетесь на тот фоновый поток, который больше подходит для ваших действий, и вы наблюдаете в основном потоке:

trait Server {
    fun findUser(name: String) : Observable<JsonObject>
}

data class User(val id: String, val name: String)

fun p(s: String) {
    println("[${Thread.currentThread().getName()}] ${s}")
}

Observable.just("cedric")
    .subscribeOn(Schedulers.io())
    .flatMap {
    	p("Calling server.findUser");
    	server.findUser("cedric")
    }
    .map{jo ->
        p("Mapping to a User object")
    	User(jo.get("id").getAsString(),
             jo.get("name").getAsString())
     }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe{ u -> p("User: ${u}a") }

Мы начинаем со строки (которая может быть из EditText, и мы указываем, что мы будем подписываться из потока ввода-вывода. Затем мы вызываем сервер с этим именем (в потоке ввода-вывода), превращаем ответ JSON в Пользовательский объект, и мы печатаем этот объект:

[IoThreadScheduler-1] Calling server.findUser
[IoThreadScheduler-1] Mapping to a User object
[Main] User: User(id=123, name=cedric)

Обратите внимание, что даже если вы можете указать несколько подписок, все подписки будут происходить в первом планировщике (последующая подписка будет игнорироваться). Я не уверен, является ли это намерением или просто упущением, но на практике это не проблема. Если вы когда-нибудь захотите подписаться на несколько планировщиков, вы всегда можете сделать это в теле вашей подписки (например, в приведенном выше примере, если серверный вызов фактически использовал Retrofit, вы увидите, что он использует свой собственный пул потоков). сделать этот звонок).

И это все, что нужно, чтобы начать управление потоками с Rx на Android. Как вы можете видеть, структурирование вашего кода делает процесс обработки намерений и потоков предельно ясным и легко прослеживаемым, гораздо более простым, чем с AsyncTask.

С ростом числа библиотек Android, добавляющих поддержку Rx, становится все более тривиальным использовать эти библиотеки в этой среде и объединять их прямыми, но мощными способами. В примерах, которые я использовал в этом и предыдущем постах, вы можете видеть, как Rx упрощает объединение сетевых вызовов и обновлений GUI просто благодаря тому, что Retrofit возвращает Observables. Вам также следует взглянуть на SQLBrite , который оборачивает SQLiteOpenHelper в Observables, чтобы предложить вам аналогичную гибкость, но для доступа к базе данных.