2015-11-14

Об Интентах

Продолжаю хвастаться своими андроидными приложениями.
В своём сдкартопосылательном приложении я изрядно подружился с андроидными интентами. Intent — это, буквально, намерение. Интенты — это основа Андроида как операционной системы. Это способ межпроцессного взаимодействия. Это сообщения, которые приложения или система посылают другим приложениям, а те как-то реагируют.
Intent workflow
В Андроид ничего не может произойти без Интента. Система может сообщить всем заинтересованным приложениям, что, например, появилось подключение к Сети. Одно приложение, скажем, Ланчер, в котором мы видим значки всех установленных приложений, может послать Интент другому приложению, с указанием отобразить определённый стартовый экран (в терминологиях Андроида — Активити), что приводит к запуску этого приложения и отображению этого самого экрана. Собственно, только для ответа на некий Интент приложения и могут запускаться.
Интент может содержать конкретное имя конкретного компонента (Активности или других) конкретного приложения. Может содержать URI, указывающий на некие данные других приложений, ресурсов в Сети или даже локальные файлы, с указанием MIME типа этих данных. Может содержать произвольный набор (ассоциативный массив) других данных, так называемые Extra, включая сериализованные объекты. Ну и Интент обязательно содержит действие (Action) и его категорию, которое ожидается быть выполненным на стороне получателя Интента.
Intent class
Скажем, запуск программы из Ланчера — это ACTION_MAIN в CATEGORY_LAUNCHER. Открытие файла или URL — это ACTION_VIEW в CATEGORY_DEFAULT или CATEGORY_BROWSABLE, URI и MIME тип того, что нужно открыть, явно передаются в Интенте. Расшарить/поделиться/послать что-то — это ACTION_SEND в CATEGORY_DEFAULT, а что именно посылается добавляется к Интенту либо текстом как EXTRA_TEXT, либо как URI в EXTRA_STREAM.
Это всё было бы не так интересно, если бы приложения не сообщали бы, какие Интенты они умеют принимать. Каждое Андроид приложение объявляет в своём манифесте, из каких компонентов оно состоит, и какие Интенты эти компоненты готовы принимать. Например, если приложение объявляет, что у него есть Активность, которая принимает Интенты с ACTION_MAIN в CATEGORY_LAUNCHER, то значок именно этой Активности появится в Ланчере, и именно эта активность будет запущена при тыкании по значку. А если такой Активности в приложении нет, то оно не появится в Ланчере, и чтобы удалить его, вам придётся залезть в настройки :) Но сам Ланчер — это обычная программа, и он запрашивает у системы, какие именно Активности умеют принимать ACTION_MAIN, и, исходя из полученного списка, рисует свой экран доступных приложений.
Любая другая программа тоже может проделать такой фокус. Скажем, запросить, какие приложения умеют открывать URI, начинающиеся на http://. Это, в каком-то приближении, будет список всех установленных в системе браузеров.
Получается, что в Андроид, после установки приложения, всегда известно, какого рода Интенты этим приложением обрабатываются. И часто используются Интенты, не адресованные конкретному приложению, а, например, отсылающие http:// URI любым приложениям, готовым его принять. При этом отображается диалог выбора конкретного получателя, наверняка часто такой видели. Технически подобный диалог — просто некоторая Активность, принадлежащая некоемому системному приложению.
Intent filters
Итак, мои приложения.
Второе по популярности (двадцать четыре тысячи активных пользователей) — браузерооткрывательное приложение. Дело в том, что почти все браузеры под Андроид, естественно, умеют открывать http:// URL, а также, что не всем известно, открывать и file:// URL. Поэтому, если вы сохранили HTML версию какого-нибудь сайта локально, вы на самом деле можете её открыть в любом удобном браузере и читать. Но беда в том, что если открытие http:// прописано у браузеров в манифесте, то про file:// они типа не знают. Единственным исключением была Опера. Получается, что, чтобы открыть локальный файл в браузере, вам приходится вбивать полный file:// URL в адресную строку вручную. Жутко неудобно. Функционал вроде есть, но воспользоваться им нормально не получается. А ведь нынешние браузеры весьма неплохо отображают картинки и даже видео.
Вот моё приложение и добавляет возможность открывать локальные html и текстовые файлы, а также файлы изображений и видео в любом установленном браузере. Чтобы непосредственно добраться до файла, вам ещё будет необходим какой-нибудь файловый менеджер.
Приложение в своём манифесте заявляет, что умеет открывать файлы. Ещё оно составляет список браузеров в системе, тех, кто умеет открывать http://. Когда к нему приходит Интент, оно просто отображает диалог выбора браузера, либо молча выбирает заранее выбранный браузер, и перенаправляет Интент браузеру. Всё.
И пошла игра с Интентами.
Ненавижу сокращённые URL. По ним совершенно не ясно, куда они ведут. А ведь по доменному имени, по нормальному, согласно ЧПУ, пути, часто можно понять больше, чем даже из заголовка статьи. И вот, чтобы делиться из этих наших твиттеров или RSS лент нормальными читабельными URL я создал урлоудлинятельную программу. Между делом оказалось ещё очень удобным удалять всякие незначащие параметры из URL, которые используются исключительно для трекинга рекламных кампаний, но не несут смысла.
Программа получает Интент с ACTION_SEND. Извлекает из него расшаренный текст. Ищет в тексте URL. Резолвит все редиректы, пока не получит финальный адрес страницы, для этого нужен Интернет. Удаляет из URL параметры, согласно настройкам. Заменяет в тексте URL на результат преобразования. Расшаривает текст далее, где его можно послать другой программе. Просто вклиниваемся еще одним шагом в стандартный процесс расшаривания ссылок.
Похожим образом работает и единственная (ну почти) программа, написанная на заказ — ебаеторговательная программа. Ебай — это, как вы знаете, такой интернет-аукцион. И есть такой, не вполне честный, способ выигрывать торги. Вы натравливаете на лот аукциона, от своего имени, некоего бота, который, за секунды до окончания торгов, внезапно делает ставку чуть-чуть выше текущей максимальной. Это называется sniping.
Так вот, нашлись ребята, которым очень нравился один такой снайперный бот. Только у создателей этого бота не было (и сейчас нет) мобильного приложения. Но зато был мобильный сайт. И вот для этих ребят я написал приложение. Оно получает Интент, которым стандартное приложение Ебая расшаривает ссылку на лот. Ведь это же вполне удобно и безобидно, куда-то ссылку отправить. Приложение находит идентификатор лота в пересылаемом тексте и формирует URL на мобильный сайт снайперного бота с идентификатором лота, чтобы бот сразу начал спайпить данный лот. Ну и формирует новый Интент, на открытие этого сайта. И сайт открывается в браузере. Опять вклиниваемся с процедуру расшаривания ссылки, и получаем новый удобный функционал.
Забавно, чем закончилось текущее существование этого приложения. При очередном обновлении его забанил Гугль в Гуглоплее. Мотивируя это тем, что я использую название и логотип этого самого снайперного бота. Это правда, но у меня было неформальное разрешение на их использование. Формального разрешения авторы бота мне так и не дали, может, не хотят светиться перед Гуглом. А переделать название и лого у меня еще руки не дошли.
Sniping
Я обожаю PlantUML. Так, что даже захотел иметь возможность писать диаграммы на планшете. Делать полноценный текстовый редактор я не рискнул. Ведь уже есть достойные редакторы, нечего с ними бодаться. Поэтому я снова решил поиграть с Интентами.
Сценарий такой. Редактируем текст в редакторе. Когда нужно, посылаем текст в наше приложение посредством Интента с ACTION_SEND. Приложение формирует из текста диаграммы URL на картинку на plantuml.com. Скачивает эту картинку. Открывает картинку в Активности для предварительного просмотра, откуда её можно переслать или открыть в другом приложении. Используем Интенты, чтобы как-то сынтегрироваться с другими приложениями, которые даже не подозревают, что с ними интегрируются. Так получилось плантуэмэльное приложение.
PlantUML
А недавно я задумался о несправедливости. Почему Интенты для расшаривания (ACTION_SEND) и Интенты для открывания (ACTION_VIEW) так неравнозначны? Расшарить текущий документ (или что там сейчас открыто) можно почти из любого приложения. А почему нельзя переоткрыть это же в другом приложении? Почему нельзя открыть текущую страницу в другом браузере? А почему, наоборот, нельзя послать куда-нибудь ссылку из текущего документа? Это худо-бедно возможно в некоторых популярных почтовых клиентах и браузерах. Но обычно ссылка может быть открыта только в этом же приложении.
И я начал делать расшаривательнооткрывательную программу. Она позволяет открывать то, что расшаривается, и расшаривать то, что открывается. На практике, если вы часто кидаете ссылки туда-сюда, оказалось очень удобно. Пока готово на 80%, уже можно пробовать. Пишу на Котлине.