- URI (Uniform Resource Identifier)
- Координаты
- Номер телефона
- Контакты
- Урок 31. Вызываем браузер, звонилку, карты с помощью intent с атрибутом data — Uri | Уроки Android Studio
- Полный список
- Как использовать содержимое из Uri
- Что можно делать?
- Чего не стоит делать?
- Просто забрать путь
- Представить, что MediaStore знает, чего вы хотите
- Попробовать извлечь путь
- Как получить имя файла?
- Хорошо, тогда как мне получить расширение файла?
- Что делать, если мне нужно передать файл в библиотеку?
- Как долго я могу использовать Uri?
- Но что делать, если мне понадобится контент на более длительное время?
- Зачем столько боли?
URI (Uniform Resource Identifier)
URI — это специальный идентификатор, по которому можно определить абстрактный или физический ресурс. Самый понятный пример с URI — это обычная веб-страница. Возьмём к примеру страницу http://developer.alexanderklimov.ru/android/catshop/catshop.php. Данный адрес можно разбить на несколько частей:
- Scheme — http
- Scheme-specific part — //developer.alexanderklimov.ru/android/catshop/catshop.php
- Path — /android/catshop/
У протокола http есть и другие параметры, которые рассматривать не будем. Существует также протокол ftp, имеющий свои параметры. Ниже вы увидите другие примеры. Главное здесь — возможность определить нахождение ресурса по представленным данным.
Допустим, мы хотим загрузить видеоматериал в компонент VideoView. Само видео может находиться в ресурсах программы или на SD-карте. С помощью URI мы можем подсказать программе, откуда следует загрузить файл.
Например, если видеофайл playcat.3gp находится в папке /res/raw, то получить адрес для загрузки можно следующим образом:
Если файл хранится на внешней карточке, то код будет следующим (опустим правильное определение имени карточки):
У компонента VideoView есть метод setVideoURI(URI uri), в котором нужно указать объект класса URI:
Посмотрим на другие примеры:
Координаты
Метод uri.getScheme() вернёт geo, а метод uri.getSchemeSpecificPart() — 54.354183,37.34011.
Номер телефона
В данном случае метод uri.getScheme() вернёт tel, а uri.getSchemeSpecificPart() — 1234578.
Контакты
URI также используется при работе с контент-провайдерами, в частности, с контактами.
Источник
Урок 31. Вызываем браузер, звонилку, карты с помощью intent с атрибутом data — Uri | Уроки Android Studio
В этом уроке мы создадим приложение, которое будет вызывать системные приложения: интернет-браузер — чтобы открыть ссылку, диалер — чтобы позвонить по номеру, и приложение Google-карты — чтобы показать на карте заранее определенные координаты.
Скачать файлы для установки Google Apps на эмулятор Genymotion
Код проекта — под видео:
Больше уроков:
Инструменты android разработчика: тут
Дизайн android приложений: тут
Уроки создания игр для android: тут
Основы программирования на JAVA: тут
Здравствуйте Виталий! Большое спасибо за Ваши уроки, которые очень грамотно преподносятся, что даже такой далекий от программирования человек, как я, смог разобраться. У меня к Вам вопрос: при вызове intent c действием ACTION_CALL происходит звонок по заданному в Uri номеру, можно ли программно узнать когда абонент поднимет трубку? Заранее благодарен за помощь.
Для отправки комментария вам необходимо авторизоваться.
Источник
Полный список
— узнаем, что такое Uri и Intent-атрибут data
— вызываем системные приложения (браузер, звонилка, карта)
Мы знаем, что Intent имеет атрибут action. С помощью этого атрибута обычно дается указание действия. Например, просмотр или редактирование. Но действие обычно совершается не просто так, а с чем-либо. Значит кроме указания действия, мы должны указывать на объект, с которым эти действия нужно произвести. Для этого Intent имеет атрибут data.
Один из способов присвоения значения этому атрибуту – метод setData (Uri data) у объекта Intent. На вход этому методу подается объект Uri.
Uri – это объект, который берет строку, разбирает ее на составляющие и хранит в себе эту информацию. Строка, конечно, должна быть не любая, а составлена в соответствии с этим документом RFC 2396. Uri имеет кучу методов, которые позволяют извлекать из распарсенной строки отдельные элементы.
Я создам объект Uri из строки, а в лог буду выводить название метода и (через двоеточие) значение, которое он возвращает. Например возьмем такую строку — http адрес:
Смотрим, чего нам возвращают методы:
uri.getScheme(): http
uri.getSchemeSpecificPart(): //developer.android.com/reference/android/net/Uri.html
uri.getAuthority(): developer.android.com
uri.getHost(): developer.android.com
uri.getPath(): /reference/android/net/Uri.html
uri.getLastPathSegment(): Uri.html
Понятия Scheme, Authority, Host, Path и пр. – взяты из RFC дока, ссылку на который я дал выше. Там можно найти их полное описание, понять что они означают и свериться с тем, что нам вернул Uri.
Рассмотрим еще примеры:
(Код, написанный выше, идет одной строкой на самом деле. Здесь идут пробелы вокруг @ из-за особенностей разметки)
uri.getScheme(): ftp
uri.getSchemeSpecificPart(): // Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра. :80/data/files
uri.getAuthority(): Этот адрес электронной почты защищён от спам-ботов. У вас должен быть включен JavaScript для просмотра. :80
uri.getHost(): google.com
uri.getPort(): 80
uri.getPath(): /data/files
uri.getLastPathSegment(): files
uri.getUserInfo(): bob
uri.getScheme(): geo
uri.getSchemeSpecificPart(): 55.754283,37.62002
Здесь уже получилось выделить только Scheme и SchemeSpecificPart.
Аналогично, получилось выделить только две части из строки.
Контакт из адресной книги
uri.getScheme(): content
uri.getSchemeSpecificPart(): //contacts/people/1
uri.getAuthority(): contacts
uri.getPath(): /people/1
uri.getLastPathSegment(): 1
В этом примере Scheme равен content. Это особый тип данных – Content Provider. Он позволяет любой программе давать доступ к своим данным, а другим программам – читать и менять эти данные. Эту тему мы рассмотрим позднее, и сами будем создавать такой тип данных.
Здесь можно посмотреть какие стандартные Uri поддерживаются.
Примеры показывают, что Uri можно создать из абсолютно разных строк: http-адрес, ftp-адрес, координаты, номер телефона, контакт из адресной книги.
Тип содержимого можно определить по Scheme. И этот же Scheme можно настроить в Intent Filter и отсеивать Intent, только с нужным нам типом данных в Uri, например только http. Этим мы еще займемся позднее, а пока сделаем простой пример, в котором будем формировать Intent с action и data, отправлять его и смотреть, что получится. Попробуем просмотреть следующее: http-адрес, координаты на карте и открыть окно набора номера.
Чтобы посмотреть координаты на карте, необходимо приложение Google Maps. Его нет в стандартных образах Android систем (тех, что вы в SDK Manager скачивали). Нужен образ, название которого начинается с «Google APIs»
Создайте AVD на платформе Google APIs с API Level 10. Назовите его на ваше усмотрение.
Создадим проект. Обратите внимание, используем платформу Google APIs версии 2.3.3
Project name: P0311_SimpleIntents
Build Target: Google APIs 2.3.3
Application name: SimpleIntents
Package name: ru.startandroid.develop.p0311simpleintents
Create Activity: MainActivity
Если у вас не получилось установить Google APIs, то создавайте проект как обычно — с платформой Android 2.3.3. Просто не будет работать вызов Google Maps в этом примере.
Сформируем экран main.xml
На экране три кнопки. Первая будет открывать веб-страницу, вторая — карту, третья – звонилку.
Пишем код в MainActivity.java:
Я использовал три разных способа создания Intent-а и задания его атрибутов.
В случае btnWeb я использовал конструктор Intent (String action, Uri uri). Он создает Intent и на вход сразу принимает action и data. Мы используем стандартный системный action – ACTION_VIEW. Это константа в классе Intent – означает, что мы хотим просмотреть что-либо. В качестве data мы подаем объект Uri, созданный из веб-ссылки: http://developer.android.com. И если попытаться описать словами наш код, то получится так: этот Intent означает, что мы хотим посмотреть содержимое этой ссылки и ищем Activity, которая могла бы нам помочь.
В случае btnMap использовался конструктор Intent(). Он просто создает Intent. А в следующих строках мы уже присваиваем ему атрибуты action и data. action – снова ACTION_VIEW, а в качестве data мы создаем Uri из пары координат — 55.754283,37.62002. Этот Intent означает, что мы хотим посмотреть на карте указанные координаты.
В случае btnCall используем конструктор Intent (String action). На вход ему сразу подается action, а data указывается позже. action в данном случае – ACTION_DIAL – открывает звонилку и набирает номер, указанный в data, но не начинает звонок. В data – помещаем Uri, созданный из номера телефона 12345.
Три этих способа приводят к одному результату — Intent с заполненными атрибутами action и data. Какой из них использовать — решать вам в зависимости от ситуации.
Т.к. нашему приложению понадобится интернет, чтобы открыть ссылку и посмотреть карту, надо чтобы на вашем компе интернет был.
Также в файле манифеста приложения, на вкладке Permission добавьте элемент Uses Permission и справа в поле Name выберите android.permission.INTERNET. Это даст приложению доступ в интернет. Правда у меня почему-то и без этого все работает … Пока не понял почему.
Все сохраняем и запускаем приложение
Жмем кнопку Web,
открывается стандартный браузер и отображает содержимое страницы по ссылке
Возвращаемся, жмем Map. Отображается карта, которая показывает место, соответствующее указанным координатам.
Возвращаемся, жмем Call. Отображается стандартный экран набора номера и видим, что номер, который мы указывали в data, уже набран. Нам остается только нажать кнопку звонка.
Скорее всего, сейчас есть много вопросов типа «Что будет если … ». На некоторые из них сразу могу ответить и предлагаю вам поэкспериментировать в текущем приложении:
1) Что будет, если указать координаты без приставки geo:
Система ругнется, что не нашла подходящего Activity (см. логи). Т.к. в Activity карты настроен Intent Filter, который (как я думаю) настроен на data c Schema = geo.
Аналогично не сработает звонилка, если указать номер без приставки tel.
2) Что будет, если в координатах оставить geo, но координаты указать кривые?
Если мы попробуем посмотреть, например, такие координаты geo:a,b, то карта запустится, но скажет нам Unable to load the URL. Т.е. данные подошли по Schema, но оказались некорректными.
3) Что будет, если координаты указать верно, но action использовать не ACTION_VIEW, а ACTION_EDIT.
Получается, что мы хотим отредактировать место на карте заданное этими координатами. Но система говорит нам, что она не нашла такое Activity. Потому что приложение Google Maps ожидает Intent с action = ACTION_VIEW и оно сможет показать нам это место на карте. А на редактирование оно не подписывалось )
Необходимо понять, что все приложения в системе заточены под конкретные действия с конкретными типами данных. И если вы попробуете позвонить на адрес сайта, или открыть на карте номер телефона – то система просто не найдет приложения, способные на это.
На следующем уроке:
— пишем простой браузер
Присоединяйтесь к нам в Telegram:
— в канале StartAndroid публикуются ссылки на новые статьи с сайта startandroid.ru и интересные материалы с хабра, medium.com и т.п.
— в чатах решаем возникающие вопросы и проблемы по различным темам: Android, Kotlin, RxJava, Dagger, Тестирование
— ну и если просто хочется поговорить с коллегами по разработке, то есть чат Флудильня
— новый чат Performance для обсуждения проблем производительности и для ваших пожеланий по содержанию курса по этой теме
Источник
Как использовать содержимое из Uri
Поскольку становится всё более очевидным, что схема file прекращает своё существование, давайте рассмотрим, как всё это будет работать сейчас.
Посмотрим на такую ситуацию со стороны потребителя: вам передают Uri с каким-нибудь контентом в случае, когда раньше вы ожидали путь к файлу. Как вы будете получать этот контент?
Что можно делать?
Во-первых, убедитесь, что в любом месте, где у вас есть для схемы file, у вас также есть для content, с областью видимости для соответствующего MIME типа. Вы можете получить контент Uri другими способами (например, в onActivityResult()), но если вы поддерживаете файл-подобный контент для таких действий, как ACTION_VIEW, убедитесь, что вы поддерживаете как file, так и content Uri.
Следующим шагом поймите, какую роль играет Uri. Некоторые значения Uri указывают на записи базы данных, например, если вы предлагаете пользователю выбрать контакт. Такие случаи в данный момент нам неинтересны. Другие значения указывают на вещи, которые могут какими-то файлами — вот для этого мы и нужна эта статья.
Затем, если Uri имеет схему content, вы можете сделать следующее с экземпляром ContentResolver:
- Вызовите openInputStream() для чтения контента.
- Вызовите getType(), чтобы получить тип MIME данных.
- Вызовите query(), запрашивающий OpenableColumns, в котором вы сможете получить размер контента и некоторую разновидность отображаемого имени, связанного с контентом.
Первые два пункта напоминают, как вы обрабатываете http или https Uri, и поэтому, если у вас есть написанный для этого код, вы можете переписать его, чтобы избежать дублирования кода.
И это, в общем-то, всё.
Чего не стоит делать?
К сожалению, хотя и не всем нравится эти решение, но многие думают, что это единственный подходящий вариант.
Просто забрать путь
Некоторые разработчики вызывают getPath() у Uri, а затем пытаются открыть его как файл (например, new File(uri.getPath())).
Не делайте так!
Этот способ никогда не был надёжным, так как он предполагает схему file и то, что у вас есть прямой доступ к местоположению файла с заданным путём.
Если у Uri есть какая-либо другая схема, например content, путь в значительной мере становится бесполезным для вас.
Попытка выбрать какие-либо отдельные части из Uri и работать с ними вряд ли окажется успешной для вас.
Представить, что MediaStore знает, чего вы хотите
Некоторые разработчики пытаются запросить MediaStore для столбца DATA в надежде, что они смогут конвертировать Uri в путь к файлу.
Не делайте так!
Не каждый content Uri поступает из MediaStore. На самом деле, в настоящее время относительно немногие Uri так делают. MediaStore ничего не знает о значениях Uri от других поставщиков. Кроме того, даже если вы получите MediaStore Uri, который может индексировать медиа на съёмном носителе, у вас не будет доступа к файловой системе.
Попробовать извлечь путь
Некоторые разработчики используют более сложный вариант для вышеупомянутых подходов, с двумя тоннами кода, чтобы посмотреть на полномочия Uri и попытаться получить путь, основанный на эвристике, разработанной для разных полномочий.
Не делайте так!
Тот факт, что это возможно, является частичной ошибкой Google, поскольку они не сделали значения Uri более скрытыми.
Однако, этот способ тоже ненадёжен:
- Приложения могут менять свои структуры Uri, и поэтому эвристика, которая работает сегодня, может не работать завтра.
- Возможно, у вас не будет доступа к файлу, даже если вы определите путь.
- Скоро случится Кембрийский взрыв приложений, публикующих собственный контент, используя своих собственных поставщиков и значения Uri, благодаря запрету на схему file. Вам необходимо поддерживать этих поставщиков, поэтому вам необходимо использовать метод openInputStream() в любом случае.
Ниже представлен список вопрос, которые могут возникнуть в данной ситуации.
Как получить имя файла?
Вы не можете этого сделать.
Вы можете посмотреть последний сегмент Uri. Это может быть имя файла, а может и не быть. Нет требования, чтобы content Uri использовал реальное имя файла в качестве последнего сегмента пути.
DISPLAY_NAME, который вы можете получить из вышеупомянутого OpenableColumns, может быть именем файла, но также может и не быть. Термин «отображаемое имя» необязательно означает имя файла.
Кроме того, в зависимости от того, откуда контент идёт, фактически никакого файла может не существовать. Предположим. что кто-то в заметку с длинной записью в приложении для заметок, и это приложение делает её доступной через Uri для доступа к другим приложениям. Пользователь не создавал файл, не загружал, не делал ничего, связанного с файлами, с этой заметкой. Даже если для неё есть подлинное имя файла, оно не будет иметь никакого значения для пользователя.
Хорошо, тогда как мне получить расширение файла?
Вы не можете этого сделать.
Ещё раз, вы можете узнать, заканчивается ли Uri тем, что выглядит как имя файла. Нет никакого гарантии, что что-то вроде foo.bar будет являться файлом с расширением .bar.
Вы можете использовать MimeTypeMap или аналогичные виды преобразователей, чтобы попытаться получить MIME тип, который вы получаете из getType(), и затем получить расширение файла. Однако эти конвертеры обрабатывают только популярные MIME типы и не могут обработать кастомные.
Что делать, если мне нужно передать файл в библиотеку?
Во-первых, проверьте библиотеку на наличие в ней варианта InputStream с методом для файла, который вы хотите использовать. Если он есть, используйте InputStream.
Затем, рассмотрите замену библиотеку на ту, которая имеет вариант InputStream.
Наконец, создайте локальную копию контента, получив InputStream из ContentResolver, подучив FileOutputStream в локальном файле (например, внутри getCacheDir() для внутреннего хранилища) и используя операции ввода-вывода для копирования из InputStream в OutputStream. flush(), getFD.sync() и close() FileOutputStream, и теперь у вас есть файл. который вы можете передать в библиотеку.
Конечно, это копия контента, поэтому она может стать устаревшей. Этот способ хорош для одноразовой операции и подходит для случаев, когда вам законным образом нужна локальная копия файла.
Как долго я могу использовать Uri?
Предполагая, что на стороне провайдера ничего не происходит, Uri должен быть действителен в течение всего процесса, но после этого он может стать недействительным.
Представьте, что Uri является глубокой ссылкой на какой-нибудь контент, требующий аутентификации. URL, который вы получаете, является рабочим, поскольку пользователь прошёл аутентификацию. URL может быть полезен даже некоторое время, пока сеанс активен. Но в итоге сеанс завершится и URL станет бесполезным.
Но что делать, если мне понадобится контент на более длительное время?
Вы можете вызвать takePersistableUriPermission() в ContentResolver. Если провайдер предлагает удержать разрешения, и вы их принимаете, то система запомнит, что у вас есть доступ к Uri. Соблюдайте осторожность, вы можете использовать метод для неограниченного доступа к контенту бескончно или до тех пор, пока пользователь не переместит, удалит или сделает недоступным к контенту по старому Uri.
Проблема в том, что takePersistableUriPermission() не говорит вам, были ли получены разрешения. Вы должен будете вызвать getPersistedUriPermissions() и посмотреть, есть ли у вас разрешения. Возможно, здесь нет разрешений, которые можно удержать.
Другой вариант — сделать локальную копию, как было отмечено ранее в FAQ. Таким образом вы сможете контролировать свою копию. Это может потребовать некоторой корректировки вашего UI и терминологии. Вместо использования глаголов типа «перейти» (предполагая, что контент находится в другом месте), используйте «импортировать», «копировать», чтобы подчеркнуть тот факт, что содержимое копируется. Вы можете предложить функции «обновить», «заменить», чтобы пользователь смог получить свежий Uri, который вы можете использовать, чтобы заменить существующую локальную копию новой.
Зачем столько боли?
Видение Google заключается в том, что значения content Uri являются эквивалентом Android http / https в веб-приложениях: универсально понятные и используемые. В конце концов, ContentResolver может сделать этот контент доступным из:
- файлов, в том числе тех, к которым другие люди не имеют доступа.
- столбцов BLOB в базе данных.
- зашифрованных материалов, которые дешифруются на лету.
- материалов, которые хранятся в сети и должны быть загружены и сохранены в кеш.
- материалов, которые генерируются на лету.
Использование значений content Uri намного более гибкое и предлагает больше различных параметров безопасности.
Источник