Беглый взгляд на Async-Await в Android

в 6:07, , рубрики: android, android development, async, await, await/async, kotlin, все читают теги, Разработка под android
От переводчика

Это мой первый перевод, поэтому прошу прощения за неточности. Если вы найдете ошибки в переводе, пожалуйста, сообщите об этом. Я не нашел лучшего перевода слова сoroutine, чем сопрограмма, поэтому решил использовать оригинал. Если у вас появятся идеи по этому поводу, буду рад узнать.

Kotlin версии 1.1 принесет в язык coroutin'ы, которые позволяют приостанавливать вычисления в какой-то точке, а затем продолжить их позднее. Очевидный пример этой возможности — async-await, который был добавлен в C# несколько лет назад.

Каждый android разработчик знает, что когда мы имеем дело с сетевыми запросами или другими I/O задачами, то нам необходимо удостовериться, что не происходит блокировка основного потока, а так же, что мы не трогаем UI из фонового потока. На протяжении многих лет приходят и уходят десятки приемов. В этой статье перечислены наиболее популярные, и показаны примеры удобства, которое несет с собой async-await.

Сценарий

Мы хотим получить данные пользователя Github и положить их в базу данных, а после показать результат на экране. Я не стал объяснять подходы, они скажут всё сами за себя.

Старый добрый Thread

Ручное управление, полный контроль

fun threads() {
  val handler = Handler()

  Thread {
    try {
      val user = githubApi.user()
      userRepository.store(user)
      handler.post {
        threadsTV.text = "threads: [$user]"
      }
    } catch(e: IOException) {
      handler.post {
        threadsTV.text = "threads: [User retrieval failed.]"
      }
    }
  }.start()
}

AsyncTask андроида

Никто же их не использует больше, верно?

fun asyncTask() {
  object : AsyncTask<Unit, Unit, GithubUser?>() {

    private var exception: IOException? = null

    override fun doInBackground(vararg params: Unit): GithubUser? {
      try {
        val user = githubApi.user()
        userRepository.store(user)
        return user
      } catch(e: IOException) {
        exception = e
        return null
      }
    }

    override fun onPostExecute(user: GithubUser?) {
      if (user != null) {
        asyncTaskTV.text = "asyncTask: [$user]"
      } else {
        asyncTaskTV.text = "asyncTask: [User retrieval failed.]"
      }
    }
  }.execute()
}

Callbacks

А Callback-hell кто-ниубдь использует?

fun callbacks() {
  githubApi.userFromCall().enqueue(object : Callback<GithubUser> {

    override fun onResponse(call: Call<GithubUser>, response: Response<GithubUser>) {
      val user = response.body()
      userRepository.storeCallback(user) {
        callbacksTV.text = "callbacks: [$user]"
      }
    }

    override fun onFailure(call: Call<GithubUser>, t: Throwable) {
      if (t is IOException)
        callbacksTV.text = "callbacks: [User retrieval failed.]"
      else
        throw t
    }
  })
}

Rx

Предоставляет крутые вещи...

fun rx() {
  githubApi.userRx()
      .flatMap { user ->
        userRepository.storeRx(user).toSingle { user }
      }
      .observeOn(AndroidSchedulers.mainThread())
      .subscribe(
          { user ->
            rxTV.text = "rx: [$user]"
          },
          { throwable ->
            if (throwable is IOException)
              rxTV.text = "rx: [User retrieval failed.]"
            else
              throw throwable
          }
      )
}

Async-Await

А как вы смотрите на это?

fun asyncAwait() = asyncUI {
  try {
    val user = await(githubApi.userAsync())
    await(userRepository.storeAsync(user))
    asyncAwaitTV.text = "asyncAwait: [$user]"
  } catch(e: IOException) {
    asyncAwaitTV.text = "asyncAwait: [User retrieval failed.]"
  }
}

Тут asyncUI (и аналогичный async<Т>) метод включет функционал coroutin'ы, который предоставляет доступ к методу await. Каждый раз, когда выполнение достигает метода await, вычисления приостанавливаются до тех пор, пока параметр не будет вычислен, но поток, в котором произошел вызов, не блокируется. После этого coroutine продолжит свое выполнение. Метод asyncUI гарантирует, что выполнение продолжится в главном потоке.


Как вы заметили, coroutine повышает читаемость кода. Они доступны уже сейчас в версии kotlin 1.1-M02. Последний пример async-await использует библиотеку, которую я написал для возможности использования coroutines на Android. Если хотите больше узнать о coroutin'ах, то можете ознакомиться с неформальным описанием

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

В ближайшее время появится перевод продолжения.

Автор: andreich

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js