Освой Android играючи

Сайт Александра Климова

Шкодим

/* Моя кошка замечательно разбирается в программировании. Стоит мне объяснить проблему ей - и все становится ясно. */
John Robbins, Debugging Applications, Microsoft Press, 2000

Многопоточность, синхронный и асинхронный код

Мы все пишем последовательный код и его выполнение идёт строчка за строчкой в соответствии с вашей задумкой. Когда мы вызываем функцию, которая выполняет длительную операцию, то должны дождаться её окончания и затем можем продолжить следующую строчку кода.

Современные системы давно научились выполнять разные задачи в одно время. Мы можем одновременно слушать музыку, листать страницу в браузере и компилировать программу. Ваше приложение тоже может работать в подобном режиме. Функция может выполняться асинхронно, не мешая другому коду.

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

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

В стандартном приложении все действия, работающие с визуальными компонентами, выполняются в отдельном UI-потоке (Main thread, UI thread). Если в этот поток поместить «долгоиграющие» операции, например, загружать картинки с сайтов, пользователю постоянно будет казаться, что приложение зависает. Более того, приложение действительно может зависнуть и получить ошибку ANR (Application not responding), если приложение не отвечает более пяти секунд. Необходимо перенести подобные операции в отдельный поток, а в основном UI-потоке выводить индикатор выполнения задачи или конечный результат.

Ваша задача - не засорять основной поток. Все тяжёлые операции следует перенести в фоновый поток. Тогда кнопки будут нажиматься, картинки обновляться, текст набираться, список прокручиваться и т.д.

Главный поток обычно называют Main Thread или UI Thread. Оставляем его в покое. Разберёмся, что предлагают нам для решения проблемы. В разное время были актуальными разные подходы. Например, вот небольшой список (часть опущена).

  • AsyncTask (устарело)
  • Services/Службы
  • Jobs & JobSchedulers (тоже потихоньку устаревает)
  • WorkManager

Эти инструменты создавались под конкретные задачи и не являются универсальным решением.

NetworkOnMainThreadException

До версии Android 2.3 можно было писать код запроса на сервер в основном потоке. Я как раз начинал программировать под Android и немного застал это время. Вспомним старый пример (только код будет на Kotlin, которого тогда не было). Разместим на экране приложения кнопку для отправки запроса и ImageView, в манифесте добавим разрешение на работу с интернетом.


button.setOnClickListener {
    val catUrl = URL("http://developer.alexanderklimov.ru/android/images/android_cat.jpg")

    val httpConnection = catUrl.openConnection() as HttpURLConnection
    httpConnection.doInput = true
    httpConnection.connect()

    val inputStream = httpConnection.inputStream
    val bitmapImage = BitmapFactory.decodeStream(inputStream)

    imageView.setImageBitmap(bitmapImage)
}

Но с выходом версии 2.3 старый код перестал работать. Приложение падало с ошибкой NetworkOnMainThreadException. Строго говоря, уже тогда существовали рекомендации не использовать сетевые запросы в основном потоке. Профессионалы использовали Thread, Service, AsyncTask и приложения продолжали работать в новых версиях. Но множество примеров у начинающих программистов (у меня тоже) стали неработоспособными.

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


if (Build.VERSION.SDK_INT > 9) {
    val policy = ThreadPolicy.Builder().permitAll().build()
    StrictMode.setThreadPolicy(policy)
}

// Далее код из предыдущего примера

Трюк работает до сих пор, проверил сейчас на Android 6.0.

Thread и runOnUiThread

Пример с ThreadPolicy был грязным лайфхаком, который никогда широко не использовался. Гораздо проще было создать отдельный поток и выполнять тяжёлую работу в нём. Но в созданном потоке нельзя обращаться напрямую к элементам интерфейса экрана, поэтому следовало добавить вызов runOnUiThread().

Перепишем предыдущий пример.


button.setOnClickListener {
    Thread {
        val catUrl =
            URL("http://developer.alexanderklimov.ru/android/images/android_cat.jpg")

        val httpConnection = catUrl.openConnection() as HttpURLConnection
        httpConnection.doInput = true
        httpConnection.connect()

        val inputStream = httpConnection.inputStream
        val bitmapImage = BitmapFactory.decodeStream(inputStream)

        runOnUiThread {
            imageView.setImageBitmap(bitmapImage)
        }
    }.start()
}

Можно избежать вызова runOnUiThread(), немного изменив код.


button.setOnClickListener {

    val mainLooper = Looper.getMainLooper()

    Thread {
        val catUrl =
            URL("http://developer.alexanderklimov.ru/android/images/android_cat.jpg")

        val httpConnection = catUrl.openConnection() as HttpURLConnection
        httpConnection.doInput = true
        httpConnection.connect()

        val inputStream = httpConnection.inputStream
        val bitmapImage = BitmapFactory.decodeStream(inputStream)

        Handler(mainLooper).post {
            imageView.setImageBitmap(bitmapImage)
        }
    }.start()
}

AsyncTask

Долгое время рекомендуемым способом работы с разных потоках был AsyncTask. На сайте до сих пор есть примеры с его использованием, например, Android: HttpURLConnection.

Несколько лет назад Гугл объявила и этот способ устаревшим.

Coroutines

В последнее время набирает популярность технология под названием Корутины/Coroutines. На сегодняшний день это самый рекомендуемый способ для создания асинхронного кода.



Реклама