Что такое handler в python?
Что такое handler в python? Вот обычный код начинается писать с букв а именно handler со символа ‘@’, почему?
Отслеживать
13.7k 12 12 золотых знаков 43 43 серебряных знака 75 75 бронзовых знаков
задан 19 апр 2021 в 16:33
nellvincent nellvincent
148 2 2 серебряных знака 13 13 бронзовых знаков
Это декоратор. Вот статейка: tproger.ru/translations/demystifying-decorators-in-python
19 апр 2021 в 16:35
handler — это просто такое английское слово. Никакой такой особой конструкции в питоне нет. А с собачки в питоне обычно начинается использование вышеупомянутых декораторов, которые можно использовать много для чего, не только для handler’ов
19 апр 2021 в 16:39
Окей, спасибо:)
19 апр 2021 в 17:00
Под handler обычно подразумевается обработчик чего-то (каких-то событий, входящих соединений, сообщений и т.д.) Это не специфическое конкретно для Python понятие. Скорее всего в коде, где вы это увидели, через декоратор делают функцию обработчиком чего-то.
19 апр 2021 в 17:16
1 ответ 1
Сортировка: Сброс на вариант по умолчанию
В Python @ это особый знак который обозначает обернуть функции в другую или же по другому — использовать декоратор.
Теория
Ево можно использовать почти везде как дополнение, а handler это название функции которой обвернуть вашу функцию.
На самом деле лучший пример использования декораторов в создание телеграм ботов, они там на каждом носу.
Вы с помощью декораторов можете добавлять фишки разные например сделать так чтобы ваш декоратор измерял время исполнения функции или изменял аргументы
Практика
Щас покажу на самом простом деле
def clocker(func): def clock(*args): import time start_time = time.time() func(*args) print(f"[]") return clock @clocker def main(): for i in range(0,1000): print(i) main()
Разберём этот код — первое что мы делаем это создаём свою функцию декоратор которая тоже имеет функцию, пайтон воспринимает так дать фукнции clocker саму функции и всё про ее, а функции clock ее аргументы. Наш код идёт так, сначала идёт функция clocker , потом clock она уже запускает нашу функцию и в конце показывает имя функции с временем на выполнения
Урок 80. Handler. Немного теории. Наглядный пример использования
Для полного понимания урока желательно иметь представление о потоках (threads) в Java.
Так просто ведь и не объяснишь, что такое Handler. Можете попробовать почитать официальное описание, но там достаточно нетривиально и мало написано. Я попробую здесь в двух словах рассказать.
В Android к потоку (thread) может быть привязана очередь сообщений. Мы можем помещать туда сообщения, а система будет за очередью следить и отправлять сообщения на обработку. При этом мы можем указать, чтобы сообщение ушло на обработку не сразу, а спустя определенное кол-во времени.
Handler — это механизм, который позволяет работать с очередью сообщений. Он привязан к конкретному потоку (thread) и работает с его очередью. Handler умеет помещать сообщения в очередь. При этом он ставит самого себя в качестве получателя этого сообщения. И когда приходит время, система достает сообщение из очереди и отправляет его адресату (т.е. в Handler) на обработку.
Handler дает нам две интересные и полезные возможности:
1) реализовать отложенное по времени выполнение кода
2) выполнение кода не в своем потоке
Подозреваю, что стало не сильно понятнее, что такое Handler, а главное – зачем он вообще нужен 🙂 . В ближайшие несколько уроков будем с этим разбираться, и все станет понятно.
В этом уроке сделаем небольшое приложение. Оно будет эмулировать какое-либо долгое действие, например закачку файлов и в TextView выводить кол-во закачанных файлов. С помощью этого примера мы увидим, зачем может быть нужен Handler.
Project name: P0801_Handler
Build Target: Android 2.3.3
Application name: Handler
Package name: ru.startandroid.develop.p0801handler
Create Activity: MainActivity
Handler Start Test
ProgressBar у нас будет крутиться всегда. Позже станет понятно, зачем. TextView – для вывода информации о закачке файлов. Кнопка Start будет стартовать закачку. Кнопка Test будет просто выводить в лог слово test.
Кодим MainActivity.java:
package ru.startandroid.develop.p0801handler; import java.util.concurrent.TimeUnit; import android.app.Activity; import android.os.Bundle; import android.os.Handler; import android.util.Log; import android.view.View; import android.widget.Button; import android.widget.TextView; public class MainActivity extends Activity < final String LOG_TAG = "myLogs"; Handler h; TextView tvInfo; Button btnStart; /** Called when the activity is first created. */ public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInfo = (TextView) findViewById(R.id.tvInfo); >public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: for (int i = 1; i break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > > void downloadFile() < // пауза - 1 секунда try < TimeUnit.SECONDS.sleep(1); >catch (InterruptedException e) < e.printStackTrace(); >> >
В обработчике кнопки Start мы организуем цикл для закачки файлов. В каждой итерации цикла выполняем метод downloadFile (который эмулирует закачку файла), обновляем TextView и пишем в лог информацию о том, что кол-во закачанных файлов изменилось. Итого у нас должны закачаться 10 файлов и после закачки каждого из них лог и экран должны показывать, сколько файлов уже закачано.
По нажатию кнопки Test – просто выводим в лог сообщение.
downloadFile – эмулирует закачку файла, это просто пауза в одну секунду.
Все сохраним и запустим приложение.
Мы видим, что ProgressBar крутится. Понажимаем на кнопку Test, в логах появляется test. Все в порядке, приложение отзывается на наши действия.
Теперь расположите AVD на экране монитора так, чтобы он не перекрывал вкладку логов в Eclipse (LogCat). Нам надо будет видеть их одновременно.
Если мы нажмем кнопку Start, то мы должны наблюдать, как обновляется TextView и пишется лог после закачки очередного файла. Но на деле будет немного не так. Наше приложение просто «зависнет» и перестанет реагировать на нажатия. Остановится ProgressBar, не будет обновляться TextView, и не будет нажиматься кнопка Test. Т.е. UI (экран) для нас станет недоступным. И только по логам будет понятно, что приложение на самом деле работает и файлы закачиваются. Нажмите Start и убедитесь.
Экран «висит», а логи идут. Как только все 10 файлов будут закачаны, приложение оживет и снова станет реагировать на ваши нажатия.
А все почему? Потому что работа экрана обеспечивается основным потоком приложения. А мы заняли весь этот основной поток под свои нужды. В нашем случае, как будто под закачку файлов. И как только мы закончили закачивать файлы – поток освободился, и экран стал снова обновляться и реагировать на нажатия.
Для тех, кто имеет опыт кодинга на Java, я ничего нового не открыл. Для остальных же, надеюсь, у меня получилось доступно объяснить. Тут надо понять одну вещь — основной поток приложения отвечает за экран. Этот поток ни в коем случае нельзя грузить чем-то тяжелым – экран просто перестает обновляться и реагировать на нажатия. Если у вас есть долгоиграющие задачи – их надо вынести в отдельный поток. Попробуем это сделать.
Перепишем onclick:
public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: Thread t = new Thread(new Runnable() < public void run() < for (int i = 1; i > >); t.start(); break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > >
Т.е. мы просто помещаем весь цикл в новый поток и запускаем его. Теперь закачка файлов пойдет в этом новом потоке. А основной поток будет не занят и сможет без проблем прорисовывать экран и реагировать на нажатия. А значит, мы будем видеть изменение TextView после каждого закачанного файла и крутящийся ProgressBar. И, вообще, сможем полноценно взаимодействовать с приложением. Казалось бы, вот оно счастье 🙂
Все сохраним и запустим приложение. Жмем Start.
Приложение вылетело с ошибкой. Смотрим лог ошибок в LogCat. Там есть строки:
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at ru.startandroid.develop.p0801handler.MainActivity$1.run(MainActivity.java:37)
Смотрим, что за код у нас в MainActivity.java в 37-й строке:
tvInfo.setText("Закачано файлов: " + i);
При попытке выполнить этот код (не в основном потоке) мы получили ошибку «Only the original thread that created a view hierarchy can touch its views». Если по-русски, то «Только оригинальный поток, создавший view-компоненты, может взаимодействовать с ними». Т.е. работа с view-компонентами доступна только из основного потока. А новые потоки, которые мы создаем, не имеют доступа к элементам экрана.
Т.е. с одной стороны нельзя загружать основной поток тяжелыми задачами, чтобы не «вешался» экран. С другой стороны – новые потоки, созданные для выполнения тяжелых задач, не имеют доступа к экрану, и мы не сможем из них показать пользователю, что наша тяжелая задача как-то движется.
Тут нам поможет Handler. План такой:
— мы создаем в основном потоке Handler
— в потоке закачки файлов обращаемся к Handler и с его помощью помещаем в очередь сообщение для него же самого
— система берет это сообщение, видит, что адресат – Handler, и отправляет сообщение на обработку в Handler
— Handler, получив сообщение, обновит TextView
Чем это отличается от нашей предыдущей попытки обновить TextView из другого потока? Тем, что Handler был создан в основном потоке, и обрабатывать поступающие ему сообщения он будет в основном потоке, а значит, будет иметь доступ к экранным компонентам и сможет поменять текст в TextView. Получить доступ к Handler из какого-либо другого потока мы сможем без проблем, т.к. основной поток монополизирует только доступ к UI. А элементы классов (в нашем случае это Handler в MainActivity.java) доступны в любых потоках. Таким образом Handler выступит в качестве «моста» между потоками.
Перепишем метод onCreate:
public void onCreate(Bundle savedInstanceState) < super.onCreate(savedInstanceState); setContentView(R.layout.main); tvInfo = (TextView) findViewById(R.id.tvInfo); btnStart = (Button) findViewById(R.id.btnStart); h = new Handler() < public void handleMessage(android.os.Message msg) < // обновляем TextView tvInfo.setText("Закачано файлов: " + msg.what); if (msg.what == 10) btnStart.setEnabled(true); >; >; >
Здесь мы создаем Handler и в нем реализуем метод обработки сообщений handleMessage. Мы извлекаем из сообщения атрибут what – это кол-во закачанных файлов. Если оно равно 10, т.е. все файлы закачаны, мы активируем кнопку Start. (кол-во закачанных файлов мы сами кладем в сообщение — сейчас увидите, как)
Метод onclick перепишем так:
public void onclick(View v) < switch (v.getId()) < case R.id.btnStart: btnStart.setEnabled(false); Thread t = new Thread(new Runnable() < public void run() < for (int i = 1; i > >); t.start(); break; case R.id.btnTest: Log.d(LOG_TAG, "test"); break; default: break; > >
Мы деактивируем кнопку Start перед запуском закачки файлов. Это просто защита, чтобы нельзя было запустить несколько закачек одновременно. А в процессе закачки, после каждого закачанного файла, отправляем (sendEmptyMessage) для Handler сообщение с кол-вом уже закачанных файлов. Handler это сообщение примет, извлечет из него кол-во файлов и обновит TextView.
Все сохраняем и запускаем приложение. Жмем кнопку Start.
Кнопка Start стала неактивной, т.к. мы ее сами выключили. А TextView обновляется, ProgressBar крутится и кнопка Test нажимается. Т.е. и закачка файлов идет, и приложение продолжает работать без проблем, отображая статус закачки.
Когда все файлы закачаются, кнопка Start снова станет активной.
Подытожим все вышесказанное.
1) Сначала мы попытались грузить приложение тяжелой задачей в основном потоке. Это привело к тому, что мы потеряли экран – он перестал обновляться и отвечать на нажатия. Случилось это потому, что за экран отвечает основной поток приложения, а он был сильно загружен.
2) Мы создали отдельный поток и выполнили весь тяжелый код там. И это бы сработало, но нам надо было обновлять экран в процессе работы. А из не основного потока доступа к экрану нет. Экран доступен только из основного потока.
3) Мы создали Handler в основном потоке. А из нового потока отправляли для Handler сообщения, чтобы он нам обновлял экран. В итоге Handler помог нам обновлять экран не из основного потока.
Достаточно сложный урок получился. Наверняка, мало, что понятно. Не волнуйтесь, в этом уроке я просто показал, в какой ситуации Handler может быть полезен. А методы работы с ним мы рассмотрим подробно в следующих уроках.
Eclipse может подчеркивать Handler желтым цветом и ругаться примерно такими словами: «This Handler class should be static or leaks might occur«. Тем самым он сообщает нам, что наш код немного плох и может вызвать утечку памяти. Тут он прав абсолютно, но я в своих уроках все-таки буду придерживаться этой схемы, чтобы не усложнять.
А на форуме я отдельно расписал, почему может возникнуть утечка и как это можно пофиксить. Как закончите тему Handler-ов, обязательно загляните туда и почитайте — http://forum.startandroid.ru/viewtopic.php?f=30&t=1870.
На следующем уроке:
— посылаем простейшее сообщение для Handler
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Compose, Kotlin, RxJava, Dagger, Тестирование, Performance
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
Handler vs RX vs Kotlin. Наглядный пример преимущества корутин
Идея написания данной статьи возникла у меня тогда, когда я начал изучать корутины, это был 2019 год, тогда я только вошёл в Андроид-разработку и меня интересовало всё что с этим связанно. В то время корутины только набирали обороты и про них начинали рассказывать на различных конференциях и митапах связанных с Андроид-разработкой, где я собственно говоря и узнал про них. На таких митапах рассказывали чем они хороши, в чем их преимущества от потоков и т.д.. Именно тогда я и заинтересовался ими, стал гуглить про корутины, читать статьи и смотреть видеоролики по корутинам и искать какие-нибудь тренинги в просторах интернета. Однако тогда, да и сейчас я практически не встречал ни одной статьи где показывают корутины на реальном примере и объясняют его простым языком, поэтому мне было трудно разобраться как применить их в реальной задаче, как например мы сможем корутинами заменить Handler, однако таких примеров в глобальной паутине под названием интернет мне найти не удалось. И тут у меня возникла в голове мысль, а вот была бы такая статья на хабаре, сколько бы я времени сэкономил на изучение корутин, посмотрел бы я как их применять в действии, понял бы, как используются они на практике и процесс их изучение был бы гораздо эффективнее и быстрее, да и я бы стал их быстрее применять в реальных проектах. И вот я написал такую статью и надеюсь тебе уважаемый читатель она будет полезна и сэкономит твоё драгоценное время на их изучение.
Каждый, даже начинающий, Android-разработчик знает, что основной поток(MainThread) приложения отвечает только за отрисовку экрана и рендеринг view’шек. Остальные операции такие как выгрузка данных с сервера, из файловой системы, базы данных и т.д. должны выполняться в отдельно потоке дабы не перегружать основной поток, ускорить работу приложения, избежать всякого рода крэшей и т.д.. Для этих целей существует множество способов такие как корутины, handler, AsyncTask, RX и т.д.. В данной статье мы не будем говорить про deprecated методы такие как например AsyncTask, а рассмотрим только 3: корутины, handler и RX.
Сразу хочу предупредить что в данной статье я не буду подробно описывать как работает каждый из этих методов и над «чистотой» кода, который я буду показывать в примерах, я тоже особо не заморачивался. Я просто хочу показать на примере одной задачи, с которой может столкнуться каждый разработчик при создании своего приложения, как будет выглядеть решение с использованием этих трёх методов.
Для демонстрации было разработано приложение, которое выгружает список почтовых адресов из списка контактов и выводит их на экран приложения. Ну что приступим.
Handler
Один из самых распространённых методов асинхронной работы в Android приложениях это использование такого механизма как Handler.
Handler — это механизм, который позволяет работать с очередью сообщений. Он привязан к конкретному потоку (thread) и работает с его очередью. В Android к потоку (thread) может быть привязана очередь сообщений. Мы можем помещать туда сообщения, а система будет за очередью следить и отправлять сообщения на обработку.
Рассмотрим реализацию данного механизма на примере:
Для начала объявим Handler в методе жизненного цикла активити onCreate() и переопределим там функцию handleMessage() и реализуем там обработчик сообщения который будет служить триггером того что список почтовых адресов получены из базы данных и мы можем ими заполнять view-элемент.
override fun onCreate(savedInstanceState: Bundle?) < super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) val handler = Handler(object : Callback() < fun handleMessage(msg: Message?): Boolean < if (MSG_EMAILS_ARE_LOADED === msg.what) < emailView.setAdapter(ArrayAdapter(applicationContext, android.R.layout.simple_dropdown_item_1line, getEmails()))) > return true > >) >
Затем нужно реализовать отдельную функцию, в которой будет создан поток, в котором мы будем получать список почтовых адресов из телефонной книги смартфона и записывать их в коллекцию, как только все почтовые адреса будут получены сигнал MSG_EMAILS_ARE_LOADED будет отправлен Handler’у UI потока, где и будет заполняться view-элемент информацией, полученной из контактов.
private fun loadEmailsFromContacts() < val loadContactsThread = Thread < loadEmailsFromContacts() handler.sendEmptyMessage(MSG_EMAILS_ARE_LOADED) >loadContactsThread.start() >
Согласитесь, что код выглядит не очень читабельным загрузка почтовых адресов реализована в одном месте, а их отображение на экране приложения в другом, а когда такой подход реализован в большом проекте то трудно будет разработчику, который будет читать код (например, во время код ревью), сразу понять, что к чему.
RX
Давайте теперь решим данную задачу с другим не менее известным подходом как RX. Здесь уже не нужно будет что-то целенаправленно объявлять в методах жизненного цикла активити. Достаточно будет только вызвать функцию, в которой и будет реализована вся магия. Давайте реализуем данную задачу.
private fun loadEmailsFromContacts() < Observable.create < list: ObservableEmitter-> loadEmails() list.onNext(getEmails()) list.onComplete() >.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe < v: Any? ->emailsFromContacts = ArrayList(v as List?) emailView.setAdapter(ArrayAdapter(getApplicationContext(), R.layout.simple_dropdown_item_1line, emailsFromContacts)) > >
Здесь код выглядит уже более компактнее. Загрузка и отображение списка почтовых адресов уже находиться в одном месте. Асинхронный код выглядит как последовательный хотя загрузка почтовых адресов и отображения их на экране выполняются в разных потоках.
Coroutines
Чтобы писать код использованием реактивного программирования нужно понимать зачем нужны такие функции как create<>, map, just и т.д., что реализовывать внутри них, что нужно указать в subscribeOn(), а что в observeOn(), зачем нужен Scheduler, какие у него бывают разновидности и что должно быть реализовано в subscribe<>. Ну и в целом хоть асинхронный код выглядит как последовательный, он стал компактнее и проще для понимания, разработчику всё равно понадобится потратить время чтобы разобраться с ним, понять, что на каком потоке выполняется, да и с точки зрения читабельности выглядит он мягко говоря не очень. И тут нам на помощь приходят корутины. Давайте рассмотрим, как данная задача будет реализована.
private fun loadImages() = CoroutineScope(Dispatchers.IO).launch < loadEmails() CoroutineScope(Dispatchers.Main).launch < emailView.setAdapter(ArrayAdapter(applicationContext, android.R.layout.simple_dropdown_item_1line, getEmails())) > >
Здесь количество строчек кода сократилось, асинхронный код выглядит как последовательный, пряча всю сложность внутри библиотек. По примеру видно, что код стал проще для понимания и чище, и сразу видно, что и в каком потоке выполняется.
Также стоит отметить что корутины это “легковесные потоки”, да это не те потоки, которые мы знаем в Java и в отличие от потоков можно создавать сколько угодно, они дешевые в создании т.е. накладные расходы в сравнении с потоками ничтожно малы. Также стоит сказать, что корутины предоставляют возможность запускать асинхронный код без всяких блокировок, что открывает больше возможностей для создания приложений. Простыми словами вычисления становятся прерываемыми, если возникает блокировка, например, из-за ожидания ответа на запрос получения списка почтовых адресов, корутина не простаивает, а берёт на себя другую задачу на выполнение до тех пор, пока ответ не будет получен.
Корутины это своего рода надстройка над потоками, они запускаются в потоках (выполняются в пуле потоков) под управлением библиотеки. Хотя с точки зрения пользователей корутины во многом ведут себя как обычные потоки, но в отличие от нативных потоков у них у них нет таких проблем как deadlocks, starvation и т.д.
Вывод
Из приведённых выше примеров вы наглядно увидели, что использование корутин делает код проще для понимания, асинхронный код визуально выглядит как последовательный. Корутины дешёвые в создании, их можно создавать тысячи, и они будут легко запущены. Корутины позволяют избежать блокировки потоков что позволяет реализовать прерываемые вычисления.
Если же смотреть в сторону RX Java, то там есть методы subscribeOn() и observeOn(), которые указывают в каком потоке мы получаем данные, а в каком их отображаем во view’шках. И читая код написанный на RX Java, разработчику не сразу становиться понятно в каком потоке что выполняется, ему требуется время на понимание всего этого. Другое дело корутины где всё сразу четко и понятно, и это вы можете убедиться на приведённых мною выше примерах. Попробуйте теперь посмотреть примеры RX и корутин в таком ключе.
Посмотрите ещё раз пример написанный на RX, сразу ли вам будет понятно какой блок кода выполняется в основном потоке, а какой в “io”. И взгляните на пример, написанный на корутинах где всё наглядно и очевидно.
Все эти плюсы говорят об одном что использование корутин это то что нужно мобильному разработчику при создании приложения.
Обработчик (функция-обработчик, handler) — что это в программировании
Обработчик (handler) — это функция, которая вызывается какой-либо программной системой в ответ на наступление какого-либо события.
Регистрация обработчика события
Чтобы система поняла, что надо вызывать именно эту функцию в ответ на это событие, обычно необходимо обратиться к какому-то специальной функции регистрации обработчиков событий (например в php)