Потоки
Потоки позволяют выполнять несколько задач одновременно, не мешая друг другу, что даёт возможность эффективно использовать системные ресурсы. Потоки используются в тех случаях, когда одно долгоиграющее действие не должно мешать другим действиям. Например, у нас есть музыкальный проигрыватель с кнопками воспроизведения и паузы. Если вы нажимаете кнопку воспроизведения и у вас запускается музыкальный файл в отдельном потоке, то вы не можете нажать на кнопку паузы, пока файл не воспроизведётся полностью. С помощью потоков вы можете обойти данное ограничение.
Использование фоновых потоков
Чтобы быть уверенным, что ваше приложение не теряет отзывчивости, хорошим решением станет перемещение всех медленных, трудоёмких операций из главного потока приложения в дочерний.
Применение фоновых потоков — необходимое условие, если вы хотите избежать появления диалогового окна для принудительного закрытия приложения. Когда активность в Android на протяжении 5 секунд не отвечает на события пользовательского ввода (например, нажатие кнопки) или приёмник широковещательных намерений не завершает работу обработчика onReceive() в течение 10 секунд, считается, что приложение зависло. Подобные ситуации следует избегать любой ценой. Используйте фоновые потоки для всех трудоёмких операций, включая работу с файлами, сетевые запросы, транзакции в базах данных и сложные вычисления.
Android предоставляет несколько механизмов перемещения функциональности в фоновый режим.
- Activity.runOnUiThread(Runnable)
- View.post(Runnable)
- View.postDelayed(Runnable, long)
- Handlers
- AsyncTask
Класс AsyncTask позволяет определить операции, которые будут выполняться в фоне, вы также будете иметь доступ к обработчику событий, что позволит отслеживать прогресс выполнения задач и выводить результаты в контексте главного графического потока. Подробнее об этом классе в отдельной статье.
Хотя использование AsyncTask — хорошее решение, случается, что для работы в фоновом режиме приходится создавать собственные потоки и управлять ими.
В Java есть стандартный класс Thread, который вы можете использовать следующим образом:
Данный способ подходит только для операций, связанных с временем. Но вы не сможете обновлять графический интерфейс программы.
Если вам нужно обновлять интерфейс программы, то нужно использовать AsyncTask, о котором говорилось выше, или вы можете реализовать ваш собственный класс, наследованный от Thread, используя объект Handler из пакета android.os для синхронизации с потоком GUI перед обновлением пользовательского интерфейса.
Вы можете создавать дочерние потоки и управлять ими с помощью класса Handler, а также классов, доступных в пространстве имён java.lang.Thread. Ниже показан простой каркас для переноса операций в дочерний поток.
Плохое приложение
Напишем «плохое» приложение, неправильно использующее основной поток. Однажды мы писали программу для подсчёта ворон. На этот раз будем считать чёрных котов, которые перебегают нам дорогу. Зачем они это делают — молчит наука. Может быть собранная статистика поможет разгадать тайну. Добавим на экран активности кнопки и текстовую метку. Код для щелчка кнопки.
Для имитации тяжёлой работы программа делает паузу на двадцать секунд, а потом выводит текст с подсчётом котов. Если нажать на кнопку один раз и подождать двадцать секунд, то программа отработает как положено. Но представьте себе, что вы нажали на кнопку один раз. Программа запустила паузу. Вы, не дожидаясь окончания паузы, снова нажали на кнопку. Программа должна выполнить вашу команду, но предыдущая команда ещё не отработала и наступает конфликт. Попробуйте нажать на кнопку несколько раз с небольшими перерывами. В какой-то момент приложение зависнет и выведет системное диалоговое окно:
В реальных приложениях такое окно может разозлить пользователя и он поставит низкую оценку вашему приложению.
В данном случае ошибку вызывает не сам вывод текста в текстовой метке, который, к слову, тоже выполняется в основном потоке, а сам щелчок кнопки. Если вы закомментируете последние две строчки кода, связанные с TextView, то ошибка сохранится.
Вам необходимо перенести трудоёмкую задачу в отдельный поток. Для этого создаётся экземпляр класса Runnable, у которого есть метод run(). Далее создаётся объект Thread, в конструкторе у которого указывается созданный Runnable. После этого можно запускать новый поток с помощью метода start(). Перепишем пример.
Весь код мы перенесли в метод run(). Теперь вы можете безостановочно щёлкать по кнопке. На этот раз приложение сохранит свою работоспособность. Чтобы в этом убедиться, в код добавлено протоколирование логов Log.i(). При каждом нажатии создаётся новый поток, в котором выполняется код. Потоки друг другу не мешают и дожидаются своей очереди, когда система позволит им отработать.
Основной поток также называют UI-потоком. Имено в главном потоке можно обновить текст у текстовой метки. В создаваемых нами потоках это делать нельзя. Если вы уберёте комментарии с последнего примера и запустите проект, то получите сообщение об ошибке.
Нужен некий посредник между создаваемыми потоками и основным UI-потоком. В роли такого посредника служит класс Handler (полное название класса android.os.Handler, не перепутайте). Вам нужно создать экземпляр класса и указать код, который нужно выполнить.
После строчки кода с Log.i() добавьте вызов метода посредника.
Поток вызывает посредника, который в свою очередь обновляет интерфейс. В нашем случае посредник посылает пустое сообщение от потока.
Но бывает так, что от потока требуется получить информацию для обработки. Ниже упрощённый пример.
Запуск потока
Предположим, мы разрабатываем собственный проигрыватель. У нас есть кнопка Play, которая вызывает метод play() для воспроизведения музыки:
Теперь запустим метод в другом потоке. Сначала создаётся новый поток. Далее описывается объект Runnable в конструкторе потока. А внутри созданного потока вызываем наш метод play(). И, наконец, запускаем поток.
Усыпить поток
Иногда требуется временно приостановить поток («усыпить»):
Приоритет потоков
Для установки приоритета процесса используется метод setPriority(), который вызывается до запуска потока. Значение приоритета может варьироваться от Thread.MIN_PRIORITY (1) до Thread.MAX_PRIORITY (10):
Отмена выполнения потока
У потока есть метод stop(), но использовать его не рекомендуется, поскольку он оставляет приложение в неопределённом состоянии. Обычно используют такой подход:
Существует и другой способ, когда все запускаемые потоки объявляются демонами. В этом случае все запущенные потоки будут автоматически завершены при завершении основного потока приложения:
Источник
Процессы и потоки в Android: пишем AsyncTask правильно
Продолжаю свои повествования об Android. И в этот раз хочу поделиться ценной информацией о процессах и потоках, которая должна быть хорошо усвоена и всегда оставаться под рукой во избежании ошибок и недопонимания при написании приложений. В конце статьи приведу пример реализации AsyncTask, который загружает в ImageView картинку по нажатию на кнопку.
Прежде всего отмечу, что подробнее о данной теме можно прочесть в данном руководстве — developer.android.com/guide/topics/fundamentals/processes-and-threads.html
На заметку о процессах и потоках в Android
Когда запускается компонент приложения и приложение не имеет других запущенных компонентов, Android создает новый процесс для приложения с одним потоком исполнения. По умолчанию все компоненты одного приложения запускаются в одном процессе, в потоке называемом «главный». Если компонент приложения запускается и уже существует процесс для данного приложения(какой-то компонент из приложения существует), тогда компонент запущен в этом процессе и использует его поток выполнения. Вы можете изменить данное поведение, задав разные процессы для разных компонентов вашего приложения. Кроме того вы можете добавить потоки в любой процесс.
Задать отдельный процесс для компонента можно с помощью файла манифеста. Каждый тег компонента(activity, service, receiver и provider) поддерживает атрибут android:process. Данный атрибут позволяет задать процесс, в котором будет выполняться компонент. Также вы можете задать процесс в котором будут выполняться компоненты разных приложений. Также данный атрибут поддерживается тегом application, что позволяет задать определенный процесс для всех компонентов приложения.
Android пытается поддерживать процесс приложения как можно дольше, но когда потребуются ресурсы старые процессы будут вытеснены по иерархии важности.
Существует 5 уровней иерархии важности: (процессы первого уровня из списка будут удалены последними)
1.Процесс с которым взаимодействует пользователь(Foreground process)
К таким процессам относится например: активити с которым взаимодействует пользовать; сервис(экземпляр Service), с которым взаимодействует пользователь; сервис запущенный методом startForeground(); сервис, который выполняет один из методов своего жизненного цикла; BroadcastReceiver который выполняет метод onReceive().
2.Видимый процесс
Процесс, в котором не выполнены условия из пункта №1, но который влияет на то, что пользователь видит на экране. К примеру, вызван метод onPause() активити.
3.Сервисный процесс
Служба запущенная методом startService()
4.Фоновый процесс
Процесс выполняемый в фоновом режиме, который невиден пользователю.
Отмечу, что в компонентах приложения существует метод onLowMemory(), но полагаться на то, что данный метод будет вызван нельзя, также как нельзя на 100% полагаться на метод onDestroy(), поэтому логику сохранения данных или каких-либо настроек можно осуществить в методе onStop(), который(как уверяют) точно вызывается.
Когда запускается приложение, система создает «главный» поток выполнения для данного приложения, который также называется UI-потоком. Этот поток очень важен, так как именно в нем происходит отрисовка виджетов(кнопочек, списков), обработка событий вашего приложения. Система не создает отдельный поток для каждого экземпляра компонента. Все компоненты, которые запущенны в одном процессе будут созданы в потоке UI. Библиотека пользовательского интерфейса Android не является потоково-безопасной, поэтому необходимо соблюдать два важных правила:
1) Не блокировать поток UI
2) Не обращаться к компонентам пользовательского интерфейса не из UI-потока
Теперь, предположим, что у нас возникла задача — загрузить картину в ImageView из сети и тут же ее отобразить. Как мы поступим? По логике: мы создадим отдельный поток, который и сделает всю нашу работу, примерно так:
Выглядит правдоподобно, так как мы вынесли операцию загрузки картинки в отдельный поток. Проблема в том, что мы нарушили правило №2. Исправить эту проблему можно с помощью следующих методов:
Activity.runOnUiThread(Runnable)
View.post(Runnable)
View.postDelayed(Runnable, long)
К примеру, воспользуемся первым из них:
Теперь реализация потоково-безопасная: сетевая операция выполняется в отдельном потоке, а к ImageView обращаемся из потока UI.
К счастью, данные операции можно объединить с помощью наследования класса Handler и реализации нужной логики, но лучшее решение — наследовать класс AsyncTask.
AsyncTask позволяет выполнить асинхронную работу и делать обновления пользовательского интерфейса.
Для обновления реализуйте метод onPostExecute(), а всю фоновую работу заключите в метод doInBackground(). После того, как вы реализуете свою собственную задачу, необходимо ее запустить методом execute().
Привожу обещанный пример AsyncTask, в котором реализована задача загрузки и отображения картинки(вариант с аннотациями и отклонением от применения стандартного протокола диалогов):
А теперь рассмотрим самый правильный вариант с точки зрения работы с диалогами:
Кода стало побольше, но лучше использовать стандартный протокол работы с диалогами.
Также я убрал все аннотации, чтобы новичкам было проще попробовать данный код.
Не забудьте добавить в свою разметку кнопочки атрибут с указанным значением: android:onClick=«runButtonHandler»
И добавлю: в оффициальном документе(Тыц ) также, как и в моем случае, не используется preExecute(), но если вам понадобится выполнить какие-то действия с вашим пользовательским интерфейсом до начала выполнения задачи, то смело используйте данный метод.
Параметры передаваемые в AsyncTask:
1. Параметры(в нашем случае адрес URL).
2. Прогресс (единицы задающие ход изменения задачи). В нашем случае не используется.
3. Результат выполнения задачи(в нашем случае объект Bitmap)
Код довольно прост: всю фоновую работу мы выполняем в методе doInBackGround(), вызывая метод publishProgress(), чтобы во время загрузки картинки крутился наш ProgressDialog при вызове метода onProgressUpdate(). После выполнения фоновой работы вызывается метод onPostExecute() в который передается результат работы метода doInBackGround(), в нашем случае это объект Bitmap и тут же мы его загружаем в ImageView.
Отмечу пару важных моментов, которые нужно учитывать:
1) Метод doInBackGround() выполняется в фоновом потоке, потому доступа к потоку UI внутри данного метода нет.
2) Методы onPostExecute() и onProgressUpdate() выполняются в потоке UI, потому мы можем смело обращаться к нашим компонентам UI.
Да, я снова применил библиотеку android-annotations, так что не пугайтесь аннотациям.
Хочу отметить важность понимания модели работы процессов в Android, чтобы не допустить ошибок при разработке приложений.
Данная статья это переработка доступной информации + личный опыт при реализации указанной задачи и работе с потоками.
Как всегда пожелания и замечания по поводу материала статьи в личку. Если вам есть что-то добавить или дополнить — смело пишите в комментарии.
UPD Статья обновленна добавлением более правильной версии в плане работы с диалогами.
Источник
Processes and threads android
Когда стартует компонент приложения и приложение не имеет каких-либо других запущенных компонентов, система Android запускает новый процесс Linux для приложения с одним выполняемым потоком (thread). По умолчанию все компоненты в одном и том же приложении работают в одном процессе и потоке (этот поток еще называют главным, main thread). Если стартует компонент приложения, и там уже существует процесс для этого приложения (потому что существует другой компонент из приложения), то компонент стартует внутри процесса, и использует для выполнения тот же самый поток. Однако Вы можете кое-что сделать для того, чтобы разные компоненты в Вашем приложении работали в отдельных процессах, и можете создать дополнительные потоки для любого процесса.
В этом документе рассматривается, как процессы и потоки работают в приложении Android (перевод документации [1]). Чтобы лучше понять материал, рекомендую ознакомиться со статьей [2], где описывается Activity — важный компонент программы Android. Все непонятные термины и сокращения ищите в Словарике [6].
Для начала стоит коротко рассмотреть, что такое процесс (Process) и что такое поток (Thread), почему их разделили в отдельные понятия. И процесс, и поток относятся к работающему коду, которое выполняется операционной системой Android. Процесс более широкое логическое понятие, олицетворяющее работающее приложение или службу. Процесс может объединять в себе несколько потоков, т. е. в одном процессе может работать несколько потоков (в процессе должен быть как минимум 1 поток), т. е. без потока не может быть работающего процесса. Поток — это элементарная единица, выполняющая какие-то действия.
[Процессы (Process)]
По умолчанию все компоненты в одном и том же приложении работают в одном и том же процессе, и для большинства приложений это менять не нужно. Однако если Вы решили, что нужно управлять, какому процессу какой компонент принадлежит, то Вы можете сделать это в файле манифеста приложения.
Запись в манифесте для каждого типа элемента компонента — , , и — поддерживает атрибут android:process , который указывает процесс, котором этот компонент должен работать. Вы можете установить этот атрибут так, что каждый компонент будет работать в своем собственном процессе, или так, что некоторые компоненты будут разделять процесс с другими, в то время как другие не будут этого делать. Вы также можете установить android:process так, чтобы компоненты различных приложений работали в одном и том же процессе — при условии, что приложения используют один и тот же идентификатор пользователя (Linux user ID), и что они подписаны одними и теми же сертификатами.
Элемент также поддерживает атрибут android:process для установки значения по умолчанию, которое будет применено ко всем компонентам.
В некоторой точке Android может решить прибить процесс, когда памяти мало и она требуется другим процессам, которые более интенсивно эксплуатирует пользователь. Компоненты приложения, работающие в уничтоженном процессе, также будут уничтожены. Процесс запустится снова для тех компонентов, которые по какой-то причине должны работать.
Когда система Android принимает решение, какие процессы прибить, она взвешивает относительную их важность для пользователя. Например, вероятнее всего будет закрыт процесс, у которого активности не видны на экране — по сравнению с видимыми активностями. Поэтому решение о завершении процесса зависит от состояния компонентов, работающих в этом процессе. Ниже будут рассмотрены правила, используемые для принятия решения завершения процессов.
[Жизненный цикл процесса]
Система Android пытается удерживать процесс приложения в работе так долго, как это возможно, но в конечном итоге нужно удалять старые процессы, чтобы высвободить память для новых или более важных процессов. Чтобы определить, какой процесс оставить, а какой прибить, система помещает каждый процесс в «список важности», который составляется на базе наличия запущенных в процессе компонентов и состояния этих компонентов. Процессы, у которого самая низкая важность, будут удалены в первую очередь, затем те, которые следующие по уровню значимости, и так далее, пока не будет освобождено нужное количество ресурсов системы.
В иерархии важности есть 5 уровней. Следующий список представляет разные типы процессов в порядке уменьшения важности (первый в списке тип самый важный и будет прибит в последнюю очередь):
1. Foreground process (процесс верхнего уровня)
Если буквально перевести название, то получится «процесс на переднем плане». Это процесс, который должен работать, потому что сейчас с ним активно взаимодействует пользователь. Считается, что процесс является foreground, если верно любое из следующих условий:
• Если в этом процессе размещена активность (Activity [2]), в которой работает пользователь (в Activity был вызван её метод onResume() ).
• Если в этом процессе размещена служба (Service [4]), связанная с активностью, с которой взаимодействует пользователь.
• Если в этом процессе размещена служба (Service), запущенная «на верхнем уровне» (running «in the foreground») — служба вызвала startForeground() .
• Если в этом процессе размещена служба (Service), которая выполняет один из методов обратного вызова (callback), обслуживающих жизненный цикл активности ( onCreate() , onStart() или onDestroy() ).
• Если в этом процессе размещен BroadcastReceiver, который выполняет свой метод onReceive() .
Обычно в каждый момент времени только небольшое количество процессов находится в состоянии foreground. Такие процессы будут убиты в последнюю очередь — если памяти станет так мало, что они все не смогут выполняться одновременно. Если такое произошло, то устройство находится в состоянии использования файла подкачки, и будут прибиты некоторые foreground-процессы для того, чтобы сохранить работоспособность интерфейса пользователя.
2. Visible process (видимый процесс)
Процесс, который не имеет каких-то foreground-компонентов, но который содержит нечто, что может повлиять на видимое содержимое экрана. Считается, что процесс является visible, если верно любое из следующих условий:
• Если в этом процессе размещена активность (Activity [2]), которая не находится на переднем плане, но все еще видима для пользователя (был вызван метод onPause() активности). Это может произойти, например, если foreground-активность запустила диалог, в результате чего запустившая диалог активность переместилась на экране на задний план, но все еще видна за окном диалога.
• Если в этом процессе размещена служба (Service), связанная с активностью на переднем плане или видимой (foreground или visible).
Visible-процесс считается очень важным, и он не будет прибит, пока это не потребуется для сохранения работоспособности foreground-процессов.
3. Service process (процесс службы)
Процесс, в котором работает служба, запущенная методом startService(), и которая не попадает в одну из предыдущих более важных категорий (Foreground или Visible). Поскольку процессы службы могут быть не привязаны напрямую к тому, что сейчас видит пользователь на экране, они обычно делают вещи, которые все же нужны пользователю (такие как фоновое проигрывание музыки или загрузка данных через сеть). Поэтому система сохранит работу Service-процессов, пока есть ресурсы для работы всех более важных (foreground и visible) процессов.
4. Background process
Если буквально перевести название, то получится «процесс на заднем плане». Это процесс, в котором размещена активность, которая сейчас невидима для пользователя (был вызван метод onStop() активности). Эти процессы не влияют напрямую на работу с пользователем, и система убьет их в любой момент, когда нужно освободить память для процессов видов foreground, visible или service. Обычно есть множество запущенных на заднем плане процессов, и они удерживаются в списке LRU (least recently used, используемые недавно). Список LRU нужен для того, чтобы можно было прибивать Background-процессы, основываясь на порядке, в котором работал с процессами пользователь, т. е. те Background-процессы, с которыми позже всего работал пользователь, будут прибиты в последнюю очередь. Если в активности корректно реализованы методы жизненного цикла (lifecycle methods, подробнее см. [2]), и активность сохраняет свое текущее состояние, то завершение её процесса пользователь не заметит, потому что когда пользователь вернется обратно к этой активности, активность восстановит все свое видимое состояние. См. документ Activities, раздел посвященный сохранению и восстановлению состояния.
5. Empty process (пустой процесс)
Это процесс, в котором не размещены какие-либо активные компоненты приложения. Причина, по которой эти процессы будут сохранены в памяти — только лишь для кэширования, чтобы уменьшить время загрузки в следующий раз, когда понадобится этот процесс запустить. Система часто убивает эти процессы, чтобы поддерживать баланс общих ресурсов системы между кэшированием процессов и кэшированием нижележащих подсистем ядра.
Android оценивает важность процесса на базе самом важном из компонентов, которые в настоящее время активны в процессе. Например, если в процессе размещена служба и видимая активность, то важность процесса будет visible, а не service.
Кроме того, важность процесса может быть увеличена в зависимости от того, какие процессы зависят от него — процесс, который обслуживает другой процесс, не может быть понижен в ранге ниже, чем процесс, который он обслуживает. Например, если провайдер контента (content provider) в процессе A обслуживает клиента в процессе B, или если служба (service) в процессе A привязана к компоненту в процессе B, то процесс A всегда рассматривается как минимум важным на столько же, как и процесс B.
Поскольку процесс со службой (service) оценивается важнее, чем процесс с background активностями, то активность, которая инициирует длительную операцию, могла бы для улучшения обслуживания пользователя запустить службу (service) для этой длительной операции, вместо того, чтобы просто создать отдельный рабочий поток — особенно если эта операция должна пережить активность. К примеру, если активность выгружает картинку на сайт, то она должна запустить службу для этого действия, чтобы выгрузка могла продолжаться и тогда, когда активность ушла в background (т. е. даже если пользователь оставил эту активность и перешел к другим действиям). Использование службы гарантирует, что операция получит приоритет как минимум процесса службы «service process», независимо от того, что случится с самой активностью. По той же самой причине приемники широковещательных сообщений (broadcast receiver) должны использовать службы, а не просто помещать в поток потребляющие время процессора действия.
[Потоки (Thread)]
Когда запускается приложение, система Android создает для него поток, называемый «main». Этот поток очень важен, потому что он отвечает за обработку событий (dispatching events) и направление их к виджетам интерфейса пользователя, включая события отрисовки. Это также поток, в котором Ваше приложение взаимодействует с компонентами тулкита интерфейса пользователя (Android UI toolkit, компоненты из пакетов android.widget и android.view ). Также main thread иногда называют потоком интерфейса пользователя (UI thread).
Система не создает отдельный поток для каждого экземпляра компонента. Таким образом, все компоненты, запущенные в главном потоке UI, работают в том же самом потоке, и системные вызовы каждого компонента диспетчеризированы из этого потока. Следовательно методы, которые отвечают за системные обратные вызовы (system callbacks, такие как onKeyDown() для сообщения о действиях пользователя, или метод жизненного цикла), всегда работают в потоке UI процесса.
Например, когда пользователь касается кнопки на экране, UI thread приложения перенаправляет событие касания виджету, который в свою очередь устанавливает свое нажатое состояние (pressed state), и помещает запрос в очередь событий. Затем UI thread обрабатывает запрос и оповещает виджет от том, что он должен перерисовать сам себя.
Когда Ваше приложение выполняет интенсивную работу в ответ на взаимодействие с пользователем, эта однопоточная модель может привести к низкой производительности, если Вы не построите свое приложение должным образом. В частности, если все происходит в потоке интерфейса (UI thread), то долгие операции наподобие доступа к сети или запросы к базе данных будут блокировать весь пользовательский интерфейс. Когда поток UI заблокирован (например на циклах или ожидании), то события интерфейса не могут быть обработаны, включая события перерисовки. С точки зрения пользователя приложение зависнет. Еще хуже, если UI будет заблокирован больше, чем несколько секунд (в настоящее время этот таймаут составляет около 5 секунд), тогда пользователю будет представлено позорное окно диалога ANR (application not responding, приложение не отвечает). Пользователь может решить выйти из программы, и даже деинсталлировать её, потому что программа работает странно и его это не устраивает [3].
Кроме того, Android UI toolkit не является потокобезопасным (not thread-safe). Так что Вы не должны манипулировать интерфейсом пользователя из дополнительного рабочего потока (worker thread) — все манипуляции с графикой интерфейса должны происходить только из UI thread. Таким образом, отсюда вытекают 2 правила для однопоточной модели приложения Android:
1. Нельзя блокировать выполнение потока обработки интерфейса пользователя (UI thread).
2. Нельзя обращаться к Android UI toolkit из других потоков. Можно работать с Android UI toolkit только из кода UI thread.
[Дополнительные рабочие потоки (Worker thread)]
По причине применения вышеописанной однопоточной модели, для скорости отклика UI приложения жизненно важно, чтобы Вы не блокировали выполнение UI thread. Если Вам нужно выполнить операции, которые не завершатся немедленно, то нужно их перенести в отдельные потоки (так называемые «background», фоновые потоки, или «worker», рабочие потоки).
Например, ниже приведен некоторый код обработчика клика, который загружает картинку из отдельного потока и отображает её в ImageView:
На первый взгляд все будет работать нормально, потому что создается новый поток для обработки сетевой операции. Однако здесь нарушается второе правило однопоточной модели обработки интерфейса пользователя: нельзя обращаться к Android UI toolkit вне кода UI thread — в этом примере ImageView модифицируется из worker thread, вместо того, чтобы это действие выполнилось в UI thread. В результате получим неопределенное, или неожиданное поведение, которое впоследствии будет трудно выявить и исправить.
Чтобы исправить эту проблему, Android предоставляет несколько способов получить доступ к UI thread из других потоков. Вот список этих методов, которые могут помочь:
• Activity.runOnUiThread(Runnable)
• View.post(Runnable)
• View.postDelayed(Runnable, long)
Например, Вы можете исправить вышеприведенный код путем использования метода View.post(Runnable):
Теперь эта реализация является потокозащищенной: сетевая операция производится из отдельного потока, в то время как ImageView манипулируется исключительно из UI thread.
Однако сложность операций возрастает, и такой код может стать сложным и трудным для дальнейшей поддержки. Чтобы обработать более сложные взаимодействия с worker thread, Вы могли бы рассмотреть использовать Handler в Вашем worker thread для обработки сообщений, поступающих из UI thread. Возможно лучшее решение тем не менее состоит в том, чтобы расширить (extend) класс AsyncTask [5], который упрощает выполнение задач worker thread, требующих взаимодействия с UI.
[Использование AsyncTask]
Класс AsyncTask позволяет Вам выполнить асинхронно некую работу в интерфейсе пользователя. Он выполняет блокирующие операции в worker thread, и затем публикует результаты для UI thread, при этом от Вас не требуется самостоятельно обрабатывать потоки и/или обработчики.
Чтобы использовать эту возможность, вы должны произвести подкласс от AsyncTask, и реализовать callback-метод doInBackground(), который работает в пуле фоновых потоков. Для того, чтобы обновить UI, Вы должны реализовать onPostExecute(), который передает результат работы из doInBackground() и работает в UI thread — этим способом можно безопасно обновить UI Вашей программы. Вы можете затем запустить задачу вызовом execute() из UI thread.
Например, Вы можете реализовать предыдущий пример, используя AsyncTask следующим образом:
Теперь UI защищен, и код упростился, потому что здесь работа разделена на две части, одна из которых выполняется в worker thread, а другая должна быть выполнена в UI thread.
Для полного понимания принципов работы AsyncTask прочитайте по нему документацию. Здесь приведен общий обзор того, как это работает:
• Вы можете указать тип параметров, значения прогресса, и конечное значение задачи, используя традиционные способы.
• Метод doInBackground() выполнится автоматически в worker thread.
• onPreExecute() , onPostExecute() и o nProgressUpdate() будут вызваны в UI thread.
• Значение, которые вернет doInBackground(), будет отправлено методу onPostExecute().
• Вы можете вызвать publishProgress() в любое время из кода doInBackground(), чтобы выполнить onProgressUpdate() в пределах UI thread.
• Вы можете остановить задачу в любое время, из любого потока.
Предупреждение: Вы можете столкнуться с другой проблемой, когда используете worker thread, в том случае, когда произойдет неожиданный перезапуск Вашей activity из-за изменения конфигурации во времени выполнения. Такое может произойти, к примеру, если пользователь изменит ориентацию экрана, и это может уничтожить Ваш worker thread. Чтобы посмотреть, как Вы могли бы сохранить свою задачу во время одного из таких перезапусков, и как правильно отменить задачу, когда активность разрушается, см. исходный код примера приложения Shelves.
[Потокобезопасные (Thread-safe) методы]
В некоторых случаях реализованные Вами методы могут быть вызваны более чем из одного потока, и следовательно такие методы должны быть реализованы потокобезопасно (thread-safe).
Это прежде всего верно для методов, которые могут быть вызваны удаленно — такие как методы, привязанные к службе. Когда вызов метода, реализованного в IBinder, происходит в том же самом процессе, в котором работает IBinder, метод выполняется в потоке вызывающей стороны. Однако, когда вызов происходит из другого процесса, метод выполняется в потоке, выбранном из пула потоков, которые система поддерживает в том же процессе, что и IBinder (он не выполняется в UI thread процесса). Например, в то время как метод onBind() службы был бы вызван из UI thread процесса сервиса, методы, реализованные в объекте, который возвращает onBind() (для примера, подкласс, который реализует методы RPC) были бы вызваны из потоков в пуле. Поскольку у службы может быть больше одного клиента, то один и тот же метод IBinder в одно и то же время может задействовать более чем один поток пула. Таким образом, методы IBinder должны быть реализованы потокобезопасно (thread-safe).
Точно так же провайдер контента (content provider) может принять запросы данных, поступающие из других процессов. Поскольку классы ContentResolver и ContentProvider скрывают детали о том, как обрабатываются коммуникации между процессами, то методы ContentProvider, отвечающие на эти запросы — query(), insert(), delete(), update() и getType() — вызываются из пула потоков в процессе провайдера контента, не в UI thread для процесса. Поскольку эти методы могут быть вызваны из любого другого количества потоков в одно и то же время, они также должны быть реализованы thread-safe.
[Взаимодействие между процессами (Interprocess Communication)]
Android предоставляет механизм обмена между процессами (interprocess communication, IPC) с использованием процедур удаленного вызова (remote procedure calls, RPC) в методах, которые вызываются из активности или из другого компонента приложения, но выполняются удаленно (в другом процессе), с любым результатом, который возвращается обратно к вызывающему коду. Это влечет за собой декомпозицию вызова метода и его данных до уровня, который может понять операционная система, с передачей данных из локального процесса и адресного пространства в удаленный процесс и адресное пространство, с последующим повторным сбором и воспроизведением данных в месте вызова. Возвращаемые значения потом передаются в обратном направлении. Android предоставляет весь код для выполнения этих IPC транзакций, так что Вы можете сфокусироваться на определении и реализации программного интерфейса RPC.
Чтобы выполнить IPC, Ваше приложение должно сделать привязку к службе с использованием bindService(). Для дополнительной информации см. документацию разработчика служб [4].
Источник