[Перевод] Беглый взгляд на Async-Await в 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() {

    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 {

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

    override fun onFailure(call: Call, 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: Эта статья не содержит отмены выполнений и удаления слушателей, которые могут содержать ссылки на активити. Каждый подход может иметь схожий вариант, но без утечек.


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

Комментарии (3)

  • 8 ноября 2016 в 09:36

    –4

    Тут разбираются различные модели однопотоxой многозадачности на примере NodeJS, но те же выводы применимы для любой платформы. async functions, generators, promises, callbacks — теряют стек вызова, из-за чего их сложнее отлаживать, имеют пенальти по производительности и требуют переписывания всего кода приложения «в своём стиле».

    • 8 ноября 2016 в 10:28 (комментарий был изменён)

      0

      Про остальное не скажу, но вот на счет колбеков и async-await, то в котлине стектрейс сохраняется. Да и вообще, без колбеков в андроид разработке сложновато будет жить. Что касается производительности, то за многие вкусности приходится платить. Обычно тут вопрос стоит ли оно того или нет.
  • 8 ноября 2016 в 10:27 (комментарий был изменён)

    0

    -del

© Habrahabr.ru