- Потоки
- Использование фоновых потоков
- Плохое приложение
- Запуск потока
- Усыпить поток
- Приоритет потоков
- Отмена выполнения потока
- Многопоточность и асинхронность
- Создание потоков и визуальный интерфейс
- Многопоточность в Android. Все что вам нужно знать. Часть 1 — Введение
- Многозадачность в Android
- Компоненты многопоточности, которые присоединяются к активности / фрагменту
- AsyncTask
- Загрузчики
- Компоненты многопоточности, которые не присоединяются к активности / фрагменту
- Service
- IntentService
- Русские Блоги
- № 2 Взаимодействие между потоками Android
- № 2 Взаимодействие между потоками Android
- Завершение потока
- Один поток завершает другой поток
- Thread.sleep
- wait & notify
- Thread.join()
- Thread.yield()
- Механизм обработки
- использование обработчика
- ThreadLocal
- Looper
- Утечка памяти
Потоки
Потоки позволяют выполнять несколько задач одновременно, не мешая друг другу, что даёт возможность эффективно использовать системные ресурсы. Потоки используются в тех случаях, когда одно долгоиграющее действие не должно мешать другим действиям. Например, у нас есть музыкальный проигрыватель с кнопками воспроизведения и паузы. Если вы нажимаете кнопку воспроизведения и у вас запускается музыкальный файл в отдельном потоке, то вы не можете нажать на кнопку паузы, пока файл не воспроизведётся полностью. С помощью потоков вы можете обойти данное ограничение.
Использование фоновых потоков
Чтобы быть уверенным, что ваше приложение не теряет отзывчивости, хорошим решением станет перемещение всех медленных, трудоёмких операций из главного потока приложения в дочерний.
Применение фоновых потоков — необходимое условие, если вы хотите избежать появления диалогового окна для принудительного закрытия приложения. Когда активность в 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, система создает поток, который называется основным потоком приложения или UI-поток. Этот поток обрабатывает все изменения и события пользовательского интерфейса. Однако для вспомогательных операций, таких как отправка или загрузка файла, продолжительные вычисления и т.д., мы можем создавать дополнительные потоки.
Для создания новых потоков нам доcтупен стандартный функционал класса Thread из базовой библиотеки Java из пакета java.util.concurrent , которые особой трудности не представляют. Тем не менее трудности могут возникнуть при обновлении визуального интерфейса из потока.
Например, создадим простейшее приложение с использованием потоков. Определим следующую разметку интерфейса в файле activity_main.xml :
Здесь определена кнопка для запуска фонового потока, а также текстовое поле для отображения некоторых данных, которые будут генерироваться в запущенном потоке.
Далее определим в классе MainActivity следующий код:
Итак, здесь к кнопке прикреплен обработчик нажатия, который запускает новый поток. Создавать и запускать поток в Java можно различными способами. В данном случае сами действия, которые выполняются в потоке, определяются в методе run() объекта Runnable :
Для примера получаем текущее время и пытаемся отобразить его в элементе TextView.
Далее определяем объект потока — объект Thread , который принимает объект Runnable. И с помощью метода start() запускаем поток:
Вроде ничего сложного. Но если мы запустим приложение и нажмем на кнопку, то мы столкнемся с ошибкой:
Поскольку изменять состояние визуальных элементов, обращаться к ним мы можем только в основном потоке приложения или UI-потоке.
Для решения этой проблемы — взаимодействия во вторичных потоках с элементами графического интерфейса класс View() определяет метод post() :
В качестве параметра он принимает задачу, которую надо выполнить, и возвращает логическое значение — true , если задача Runnable успешно помещена в очередь сообщение, или false , если не удалось разместить в очереди
Также у класса View есть аналогичный метод:
Он также запускает задачу, только через определенный промежуток времени в миллисекундах, который указывается во втором параметре.
Так, изменим код MainActivity следующим образом
Теперь для обновления TextView применяется метод post:
То есть здесь в методе run() передаемого в метод post() объекта Runnable мы можем обращаться к элементам визуального интерфейса и взаимодействовать с ними.
Подобным образом можно работать и с другими виджетами, которые наследуются от класса View .
Источник
Многопоточность в Android. Все что вам нужно знать. Часть 1 — Введение
13.08.2017 в 11:40
Каждый Android разработчик, в тот или иной момент сталкивается с необходимостью иметь дело с потоками в своем приложении.
Когда приложение запускается, оно создает первый поток выполнения, известный как основной поток или main thread. Основной поток отвечает за отправку событий в соответствующие виджеты пользовательского интерфейса, а также связь с компонентами из набора инструментов Android UI.
Чтобы ваше приложение сохраняло отзывчивость, важно избегать использования основного потока для выполнения любой операции, которая может привести к его блокировке.
Сетевые операции и обращения к базе данных, а также загрузка определенных компонентов, являются типичными примерами операций, которые не следует выполнять в основном потоке. Когда они вызваны в главном потоке, они вызваны синхронно, что означает, что пользовательский интерфейс не будет ни на что реагировать до завершения операции. По этой причине, они обычно выполняются в отдельных потоках, что позволяет избежать блокировки пользовательского интерфейса во время выполнения (т. е. они выполняются асинхронно из UI).
Android предоставляет множество способов создания и управления потоками, и множество сторонних библиотек, которые делают управление потоками гораздо более приятным.
В этой статье вы узнаете о некоторых распространенных сценариях в Android разработке, где многопоточность становится важной, и некоторые простые решения, которые могут быть применены к этим сценариям, и многое другое.
Многозадачность в Android
В Android вы можете классифицировать все компоненты потоков на две основные категории:
Потоки связанные с активностью / фрагментом. Эти потоки привязаны к жизненному циклу активности / фрагмента и завершаются сразу после их уничтожения.
Потоки не связанные с активностью / фрагментом. Эти потоки могут продолжать работать за пределами жизни активности / фрагмента (если есть), из которых они были созданы.
Компоненты многопоточности, которые присоединяются к активности / фрагменту
AsyncTask
AsyncTask это наиболее основной Android компонент для организации потоков. Он прост в использовании и может быть хорошей основой для вашего сценария.
Однако, AsyncTask не подойдет, если вам нужен отложенный запуск задачи, после завершения работы вашей активности / фрагмента. Стоит отметить, что даже такая простая вещь, как вращение экрана может вызвать уничтожение активности.
Загрузчики
Загрузчики могут решить проблемы, упомянутые выше. Загрузчик автоматически останавливается, когда уничтожается активность и перезапускает себя, после пересоздания активности.
В основном есть два типа загрузчиков: AsyncTaskLoader и CursorLoader . О загрузчике CursorLoader вы узнаете далее в этой статье.
AsyncTaskLoader похож на AsyncTask , но немного сложнее.
Компоненты многопоточности, которые не присоединяются к активности / фрагменту
Service
Service это компонент, который полезен для выполнения длинных (или потенциально длительных) операций без какого-либо пользовательского интерфейса.
Service работает в основном потоке своего процесса; не создает свой собственный поток и не запускается в отдельном процессе, если вы это не указали.
Используя Service вы обязаны остановить его, когда его работа будет завершена, вызвав методы stopSelf() или stopService() .
IntentService
IntentService работает в отдельном потоке и автоматически останавливается после завершения работы.
IntentService обычно используется для коротких задач, которые не обязательно должны быть привязаны к какому-либо пользовательскому интерфейсу.
Источник
Русские Блоги
№ 2 Взаимодействие между потоками Android
№ 2 Взаимодействие между потоками Android
Завершение потока
Один поток завершает другой поток
Существует очень эффективный метод завершения потока в потоке. Вы можете использовать stop для эффективного завершения потока, но этот API устарел в версии 4.1, потому что результат непредсказуем. Когда поток завершается, это невозможно понять Какую работу выполняет поток, поэтому он ненадежен для таких неконтролируемых факторов.
Метод прерывания не похож на стоп и смерть, это метка прерывания для этого потока. Цепочка уведомлений будет прервана. Если поток завершается в соответствующем месте, как в приведенном выше коде.
Thread.sleep
Это исключение добавляется во время сна, чтобы поток не спал слишком хорошо, и когда состояние прерывания изменяется, сон завершается и переходит в ненормальную фазу. И InterruptedException также сбросит состояние прерывания, поэтому, если вам нужно закрыть операцию внешнего потребления / цикла, вам нужно вернуться! Этот сон используется, чтобы спать, и его нужно прерывать. .
Это тоже операция сна, но он не бросит отклонения, то есть не приемлет прерываний, он просто спит, а гром не может проснуться.
wait & notify
wait & notify запускается синхронно и влияет на него.
Вышеупомянутый метод вызовет тупиковую ситуацию,
Здесь прерывание также будет вызывать ожидание, потому что wait неясно, кто пробуждается, поэтому перед ожиданием должен быть добавлен цикл while ♻️. wait () и notify () — это не метод потока, а метод объекта. Его функциональный объект или зависимый объект — это монитор. Поведение потоков в ожидании регулируется монитором, поэтому местоположение должно быть методом объекта.
Thread.join()
Метод join () такой же, как и метод python. После thread1.join содержимое thread1 выполняется первым, прежде чем будут выполнены последующие методы thread2.
Thread.yield()
Метод yield также совместим с python. Yield отказывается от своего временного отрезка, чтобы он был выполнен после того, как другие потоки завершили выполнение.
Механизм обработки
использование обработчика
Обработчик воздействует на код указанного запущенного потока для выполнения операций.
Задача задается в обработчике, а цикл предназначен для работы и управления task.run. Сообщение здесь предназначено для отправки содержимого в Queue, которая является очередью связанного списка MessageQueue.
ThreadLocal
ThreadLocal — это параметр переменной для локального кэша потока. Этот параметр не влияет друг на друга. Например, в следующем случае настройка каждого потока не влияет на использование других потоков.
Looper
- Looper.prepare () предназначен для создания цикла для завершения работы по инициализации.
- Looper.myLooper () указывает текущий выполняющийся поток
- Looper.loop () — это задача задачи в повторяющемся цикле while.
исходный код loop ()
Утечка памяти
AsyncTask определен в действии для хранения ссылки на действие, но не обязательно приводит к тому, что он не будет переработан. Механизм сбора мусора определяет, можно ли переработать модуль, не для того, чтобы увидеть, есть ли у него контрольная точка, что ненадежно, а для проверки Независимо от того, содержит ли он ссылку, относящуюся к корню gc, если она содержит связанную ссылку, определяется, что она не может быть собрана сборщиком мусора.
- Во-первых, у запущенного потока есть корень gc, поэтому поток, вызываемый при выполнении asynctask, заставляет его содержать корень gc, что легко вызывает утечку памяти и не может быть освобождено.
- Второй — статический, содержащий корень gc.
- Локальные переменные или параметры методов JNI; глобальные ссылки JNI; объекты мониторинга для синхронизации
Вышесказанное является основной причиной утечки памяти. Если AsyncTask выполнялась в течение определенного периода времени после закрытия страницы, она фактически не будет иметь никакого эффекта.Это не имеет ничего общего с мягкими ссылками и слабыми ссылками. Если это надолго, не включайте это в деятельность.
Источник