Просунуті способи підключення: фокус і потоковість – програмування Android

&nbsp

Як було показано в прикладі 77 і в розділі «Слухання подій дотику», події MotіonEvent направляються до того віджету, до робочого прямокутнику якого відноситься точка координат, де відбулося торкання, згенерованого дану подію Не так просто визначити, який саме віджет повинен отримувати подія KeyEvent Щоб це робити, фреймворк для користувача інтерфейсу Android, як і інші подібні фреймворки, підтримує концепцію «виділеної області» (selection), яка також називається терміном «фокус»

Щоб віджет міг потрапити у фокус, його атрибут f ocusabl е повинен мати значення true Цього можна домогтися або за допомогою атрибутів макета (layout attributes) в XML (у видів EditText в прикладі 73 атрибут focusablе має значення false), або за допомогою методу setFocusabl е, як показано в першому рядку коду в прикладі 710 Користувач переміщує фокус з одного обєкта View на інший, працюючи з клавішами хрестовини або натискаючи на екран – якщо він сенсорний

Коли віджет знаходиться у фокусі, він зазвичай відображається з певною підсвічуванням, сигналізуючи, що саме до нього застосовуються дії натискання клавіш, в даний момент Наприклад, коли віджет EditText знаходиться у фокусі, він підсвічується і, крім того, в точці, з якої починається введення тексту, ставиться курсор

Щоб отримувати повідомлення, коли вид потрапляє у фокус або виходить з нього, потрібно встановити OnFocusChangeListener У прикладі 712 показаний слухач, за допомогою якого в демонстраційну програму можна додати функціонал, повязаний з фокусом Цей код автоматично додає в DotView випадково розташовані точки через випадкові інтервали, коли даний вид виявляється у фокусі

Приклад 712 Робота з фокусом

У OnFocusChangeListener немає нічого незвичайного Коли віджет DotView потрапляє у фокус, він створює DotGenerator і породжує потік для його запуску Коли віджет виходить з фокусу, DotGenerator зупиняється і звільняється Нове поле даних dotGenerator (його оголошення в прикладі не показано) є ненульовим лише тоді, коли DotView розташовується у фокусі Є ще один важливий і потужний інструмент, застосовуваний при реалізації DotGenerator, трохи нижче ми його розглянемо

Щоб помістити той чи інший віджет в фокус, потрібно викликати один з методів його класу View – requestFocus Коли requestFocus викликається стосовно до нового віджету, запит відправляється вгору по дереву, від одного батьківського, елемента до іншого, вищестоящому батьківського елементу, до самого кореня дерева Корінь запамятовує, який віджет знаходиться у фокусі, і передає наступні події, повязані з натисканнями клавіш, безпосередньо йому

Саме так фреймворк для користувача інтерфейсу переводить фокус на новий віджет у відповідь на натискання клавіш хрестовини Фреймворк ідентифікує віджет, який повинен виявитися у фокусі наступним, і викликає метод requestFocus цього віджета В результаті віджет, який знаходився у фокусі попереднім, виходить з фокусу, а цільової віджет – потрапляє у фокус

Процес ідентифікації віджету, який повинен потрапити у фокус, досить складний Для визначення цього віджета навігаційний алгоритм виконує складні обчислення, які можуть залежати від того, як розташовані на екрані всі без винятку інші віджети

Розглянемо, наприклад, що станеться, якщо натиснута права клавіша хрестовини і фреймворк намагається перевести фокус на віджет, впритул прилягає праворуч до того віджету, який знаходиться у фокусі в справжній момент При погляді на екран може бути абсолютно очевидно, що це за віджет, але ситуація зовсім не так ясна для дерева видів Цільовий віджет може знаходитися на іншому рівні дерева і відстояти на кілька «Гілок» від віджета, який знаходиться у фокусі зараз Ідентифікація такого віджета залежить від точних параметрів віджетів, що розташовуються зовсім в інших, віддалених частинах дерева На щастя, незважаючи на чималу складність, реалізація цього механізму під фреймворці користувача інтерфейсу Android зазвичай працює так, як очікується

Якщо щось йде не так, то можна скористатися чотирма властивостями – вони задаються або методом програми, або XML-атрибутом, – які примусово викликають бажане навігаційне поведінка при перекладі фокуса Ці властивості – nextFocusDown, nextFocusLeft, nextFocusRight і nextFocusUp Якщо задати одне з цих властивостей з посиланням на конкретний віджет, то можна гарантувати, що при натисканні клавіш хрестовини в певному напрямку фокус буде переходити саме на той віджет, на який вказує посилання

Ще одна складність, повязана з механізмом фокус, полягає в тому, що користувальницький інтерфейс Android розрізняє фокус хрестовини і фокус, що виникає в результаті торкань сенсорного екрану (якщо такий екран є на пристрої) Щоб зрозуміти, навіщо необхідно таке розходження, нагадаємо, що на екрані, що не допускає сенсорного введення, єдиний спосіб натиснути кнопку – навести на неї фокус за допомогою хрестовини, а потім зробити клацання центральною клавішею хрестовини Але на екрані, який приймає сенсорний ввід, взагалі не доводиться наводити фокус на кнопку Щоб натиснути кнопку, достатньо легенько вдарити по ній пальцем, незалежно від того, який саме віджет знаходиться у фокусі в даний момент Проте навіть на сенсорному екрані зберігається необхідність наводити фокус на віджет, який може сприймати натиснення клавіш Такий, наприклад, віджет EditText Його потрібно однозначно визначати як цільової елемент, до якого будуть ставитися наступні події, повязані з клавішами Щоб правильно обробляти обидва різновиди фокуса, потрібно розібратися з тим, як клас View обробляє FOCUSABLE_IN_TOUCH_MODE, а також вивчити методи іsFocusabl elnTouchMode і іslnTouchMode, що відносяться до View

У багатовіконному додатку механізм фокусування набуває ще як мінімум один нюанс Вікно може вийти з фокусу, не повідомляючи про втрату фокусу той зі своїх віджетів, який до останнього моменту як раз перебував у фокусі Якщо задуматися, то стає зрозуміло, чому це доцільно Адже якщо вікно, що вийшло з фокуса, знову опиниться поверх всіх інших вікон, то віджет, який був у фокусі на момент закінчення роботи з цим вікном, знову опиниться у фокусі – і для цього не потрібно виконувати ніяких додаткових дій

Припустимо, вам потрібно додати в довідник номер телефону вашого друга І при цьому ви відразу ж переходите в додаток «Телефон», щоб уточнити останні кілька цифр цього номера Звичайно, вам не сподобається, якщо при перезапуску адресної книги потрібно буде знову вручну наводити фокус на те поле EditText, куди ви тільки що вводили текст Очікується, що програма має бути саме в тому стані, в якому ви його залишили

З іншого боку, у такої поведінки можуть бути незвичайні побічні ефекти Зокрема, при реалізації функції автоматичного додавання точок з прикладу 712 точки продовжують додаватися в вид DotView, навіть якщо зараз він накритий іншим вікном Якщо фонове завдання повинна виконуватися тільки тоді, коли конкретний віджет знаходиться на виду, то це завдання потрібно видаляти при виході віджета з фокусу, тобто коли Window виявляється поза фокусом і робота Activity призупиняється або припиняється

Реалізація механізму фокусування здійснюється в основному в класі ViewGroup, за допомогою методів начебто requestFocus і requestChi 1 dFocus Якщо буде потрібно реалізувати абсолютно новий механізм фокусування, потрібно буде докладно вивчити методи і правильно їх перевизначити

У прикладі 713 ми відвернемося від теми фокусування і повернемося до реалізації нещодавно доданої функції автоматичного додавання точок Тут представлена ​​реалізація DotGenerator

Приклад 713 Обробка потоків

Ось пояснення до виділеним рядкам коду

Створюється обєкт android os Handler

Створюється новий потік, який буде запускати makeDot в елементі 4 © В основному потоці запускається DotGenerator

makeDot запускається обробником Handler, створеним в елементі 1

Спрощена реалізація DotGenerator могла б просто викликати makeDot безпосередньо з методу run Проте така операція небезпечна, оскільки makeDot не є потокобезпечна, так само як і Dots і DotView Таку ситуацію буде складно правильно організувати і ще складніше – підтримувати Фреймворк користувача інтерфейсу Android забороняє доступ до обєкта View від декількох потоків При запуску спрощеної реалізації робота додатка аварійно завершиться, видавши виняток Runti meException такого плану:

З такою проблемою ми вже стикалися, коли створювали обробник Handler Щоб обійти таке обмеження, DotGenerator створює обєкт Handler у своєму конструкторі Обєкт Handler асоційований з потоком, в якому він був створений, і цей потік отримує безпечний конкурентний доступ до базової черги подій

Оскільки DotGenerator створює Handler в ході власного процесу конструкції, Handler асоціюється з основним потоком Тепер DotGenerator може використовувати Handler для постановки в чергу обєкта Runnablе, створеного в іншому потоці, причому такий обєкт викликає makeDot з потоку для користувача інтерфейсу Виявляється, як ви вже й самі здогадалися, що базова чергу подій, на яку вказує Handler, – та сама, з якою працює фреймворк для користувача інтерфейсу Виклик makeDot видаляється з черги і обробляється, як і будь-яке інше подія для користувача інтерфейсу, у звичайному порядку У даному випадку в результаті запускається Runnablе, що відноситься до makeDot makeDot викликається з основного потоку, та інтерфейс користувача залишається однопоточні

Варто повторити, що це – важливий патерн написання основного коду для користувача інтерфейсу Android Якщо обробка дій, запущена з ініціативи користувача, триватиме більше декількох мілісекунд, то при виконанні цієї дії в основному потоці можна сповільнити роботу всього користувальницького інтерфейсу або, що ще гірше, надовго його «підвісити» Якщо основний потік додатки не обслужить свою чергу подій за пару секунд, операційна система Android примусово завершить додаток, так як воно не відповідає Класи Handler і AsyncTask дозволяють програмісту уникати таких небезпечних ситуацій, делегуючи повільні або підлягає виконувані завдання іншим потокам так, щоб основний потік міг продовжувати обслуговувати користувача інтерфейс У цьому прикладі демонструється використання потоку Thread з обробником Handler, який періодично ставить у чергу події поновлення користувача інтерфейсу

Демонстраційний додаток тут небагато спрощено Воно ставить операції створення нової точки і додавання її до моделі в чергу основного потоку Більш складний додаток могло б при створенні моделі передавати їй Handler від основного потоку, а для користувача інтерфейсу надати можливість отримати Handler, створений в потоці моделі Модель, що працює у власному потоці, використовувала б клас Looper для видалення з черги і диспетчеризація повідомлень, що надходять від користувача інтерфейсу Але перш, ніж приступати до побудови чогось настільки складного, спробуйте використовувати Serviсе або ContentProvider

При передачі подій між інтерфейсом користувача і довготривало виконуваним потоком ми, таким чином, значно знижуємо кількість обмежень, які потрібно дотримуватися для забезпечення безпеки потоків Ми згадували, що потік, що ставить події в чергу, не зберігає посилань на обєкт, поставлений в чергу, або, якщо такий обєкт є незмінним, не потрібно ніякої додаткової синхронізації

Джерело: Android Програмування на Java для нового покоління мобільних пристроїв

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*