Android Pro Kotlin. Глава 3. Активности Android

Оглавление

Глава 3. Активности Android

Объявление активностей

Запуск активностей

Активности и задачи

Возврат результата выполнения активности

Фильтры намерений

Action параметры намерений

Категории намерений

Данные в намерениях

     Intent Extra Data
    Флаги намерений

Глава 3. Активности Android

Активности(Activity) – точка входа в пользовательский интерфейс Вашего приложения. Любое приложение, которое предназначено для функционального интерактивного взаимодействия с пользователем, будь то ввод данных, плеер или отображаемая таблица пользовательской активности  – будет содержать как минимум одну активность. Мы говорим функционально, потому что уведомление пользователя о событиях может происходить через уведомления в панели статуса устройства или через всплывающие сообщения – для реализации которых не нужны активности.

Приложение может содержать ноль, одну или несколько активностей. Активность можно запустить двумя способами:

  1. Главная активность, определенная в файле AndroidManifest, запускается при открытии приложения. Это действие схоже по своему смыслу с функцией main() в традиционных Java приложениях.
  2. Все остальные активности можно настроить таким образом, чтобы они запускались через явные и неявные намерения(Интенты), определенные в файле AndroidManifest.xml. Намерения – это объект класса  и выдающаяся особенность ОС Android. Для явных намерений – объект сообщает что ему нужно выполнить какое то действие с выбранным компонентом приложения(активность, сервис и т.д.). Для неявных намерений – намерение просто сообщает что оно должно сделать, не указывая какой именно компонент должен обработать это намерение, это решает пользователь или сама ОС Android.

С точки зрения пользователя – активности, это экраны, которые могут быть запущены в работающем приложении или из другого стороннего приложения. И как только активность запустится, она также помещается в стеке задач, который могут увидеть пользователи, нажав кнопку Назад.


Заметка. Активности – это важная составляющая концепции ОС Android. Больше информации Вы можете и должны подчерпнуть из https://developer.android.com/guide/components/activities/intro-activities и https://developer.android.com/reference/android/app/Activity. Эта глава дает базовые и общие понятия об этой теме


Объявление активностей

Для того, чтобы объявить активность в файле AndroidManifest.xml, необходимо добавить следующий блок кода

<?xml version="1.0" encoding="utf-8"?>
  <manifest ...
      package="com.example.myapp">
    <application ... >
        <activity android:name=".ExampleActivity" />
        ...    
     </application ... >
    ...  
</manifest >

Как видно из этого примера, Вы можете ввести имя активности с символа точки, что означает сокращение имени пакета, в котором располагается активность. В нашем случае полное наименование активности будет com.example.myapp.ExampleActivity. Или в данном случае Вы можете задекларировать активность следующим образом:

<?xml version="1.0" encoding="utf-8"?>
  <manifest ... 
     package="com.example.myapp" ...>
     <application ... >
        <activity android:name="com.example.myapp.ExampleActivity" />
        ...    
     </application ... >
    ... 
</manifest>

Все допустимые атрибуты, которые можно добавить в элемент <activity> файла AndroidManifest.xml, представлены в секции “Activity- Related Manifest Entries” в онлайн документации.

Элементы, которые могут быть добавлены к элементу activity:

  • intent-filter

Интент фильтр. Для большей информации смотрите раздел Фильтры намерений. Вы можете указать ноль, один или несколько фильтров намерений.

  • layout

Начиная с Android 7.0, Вы можете указать указать атрибуты представления для многооконного представления, например:

<layout android:defaultHeight="500dp"        
        android:defaultWidth="600dp"        
        android:gravity="top|end"        
        android:minHeight="450dp"        
        android:minWidth="300dp" />

где Вы конечно можете указывать свои значения. Значения defaultHeight и defaultWidth указывают размеры представления по умолчанию, значение гравитации определяет начальное положение представления относительно окна, а minWidth и minHeight – минимальные размеры окна.

  • meta-data

Произвольная пара значений в виде: <meta-data android:name = "..." android:resource = "..." android:value = "..." />.Их может быть несколько и они входят в элемент android.os.Bundle доступный как PackageItemInfo.metaData


Осторожно! Можно написать приложение без каких либо активностей: приложение может содержать сервисы, широковещательные приемники и хранилище данных – поставщик контента. Но вы должны помнить, что пользователи зачастую не понимают, для чего вообще предназначены такие приложения, которые не содержат пользовательское представление. В большинстве случаев, необходимо реализовать хотя бы простую активность, которая будет давать ясное и короткое представление о том, что делает данное приложение. Хотя в корпоративной среде, наличие таких приложений вполне допустимое явление.


Запуск активностей

Как мы уже упоминали выше, активность можно запустить двумя способами. Первый – если активность помечена как главная активность в приложении, активность может быть запущена при запуске приложения. Для того чтобы определить активность как запускаемую главную активность, необходимо прописать следующее при ее объявлении в файле AndroidManifest.xml:

<activity android:name="com.example.myapp.ExampleActivity">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

Строка android.intent.action.MAIN говорит ОС Android, что это главная активность и она помещается вниз стека задач. А android.intent.category.LAUNCHER говорит, что эта активность должна быть запущена при запуске приложения.

А второй способ запуска активности – она может быть запущена через намерение из этого же или из совершенно другого приложения. Чтобы это было возможно, в манифесте необходимо указать следующее в фильтре намерений:

<activity android:name="com.example.myapp.ExampleActivity">
    <intent-filter>
       <action android:name="com.example.myapp.ExampleActivity.START_ME" />
       <category android:name="android.intent.category.DEFAULT"/>
    </intent-filter>
</activity>

Для запуска активности с данным фильтром намерений, необходимо написать код:

val intent = Intent()  
intent.action ="com.example.myapp.ExampleActivity.START_ME"
startActivity(intent)

Если Вы хотите запретить запуск активности из других приложений, необходимо указать флаг exported="false".Спецификация категории “android.intent.category.DEFAULT” в фильтре намерений указывает, что можно запустить активность даже без указания категории запуска в намерении.

В примере выше мы показали, как запускать активность при явном намерении. Мы точно указали в намерении, какую активность необходимо вызвать. Другой тип намерений называется – неявное намерение. В противоположность явным намерениям, он сообщает системе, какое намерение необходимо обработать, без указания, какой именно компонент будет обрабатывать намерение. Вот как выглядят неявные намерения:

val intent = Intent(Intent.ACTION_SEND)  
intent.type = "text/plain"  
intent.putExtra(Intent.EXTRA_TEXT, "Give me a Quote")  
startActivity(intent)

Что происходит в этот участке кода? Мы вызывает активность, которая может обработать действие "Intent.ACTION_SEND" и принять MIME тип данных "text/plain". В нашем случае это будет строка “Give me a Quote”.  Операционная система Android предложит пользователю список активностей из Вашего или других приложений, которые могут обработать данное намерение. Активности могут также обрабатывать данные с поступившим намерением. Для передачи активности данных, просто используйте перегруженный метод putExtra(...) класса Intent.

Активности и задачи

Поведение запущенной активности в стеке задач, во многом определяется атрибутами:

  • taskAffinity – задача, с которой активность имеет сходство. Активности с тем же родством принадлежат одной и той же задаче, одному и и тому же “приложению” с точки зрения пользователя.
  • launchMode – режим запуска активности. Всего существует 5 режимов запуска активности, которые работаю в сочетании с константами FLAG_ACTIVITY_* объекта Intent.
  • allowTaskReparenting – позволено ли активности перейти от задачи, которая ее запустила, к сходной  задаче,  когда она будет  выдвинута на передний фронт. Значение атрибута true – если позволено и false, если нет
  • clearTaskOnLaunch – удаляются ли из задачи все активности, кроме корневой, при повторном запуске приложения. Возможные значения true, если мы хотим установить атрибут в активное состояние и false в обратном случае. Значение по умолчанию – “false
  • alwaysRetainTaskState – всегда ли состояние задачи с активностью должно поддерживаться системой. “true” – если да, “false” – если система может обнулить задачу до первоначального состояния в определенных ситуациях. Значение по умолчанию – “false“. Атрибут имеет значение только для корневой активности, для всех остальных случаев он игнорируется.
  • finishOnTaskLaunch – закрывается ли текущий экземпляр активности, за исключением корневой активности,  когда пользователь перезапускает задачу, заново запуская ее через главный экран. true – если активируем атрибут, false  в противном случае. По умолчанию параметр установлен в false.

как указано в атрибутах элемента <activity> и флагах намерения

FLAG_ACTIVITY_NEW_TASK
FLAG_ACTIVITY_CLEAR_TOP
FLAG_ACTIVITY_SINGLE_TO
Вы можете указать в Intent.flags = Intent.<FLAG>где <FLAG> – один из списка выше. В случае если атрибуты активности и вызываемые флаги конфликтуют, предпочтение отдается флагам вызывающей стороны. Имя атрибута и флага дает Вам подсказку, что именно они должны делать. Для дополнительной информации, можете почитать  официальную документацию.

Возврат результата выполнения активности

Если Вы запускаете активность:

startActivityForResult(intent:Intent, requestCode:Int)

Это означает, что Вы запускаете активность с намерением получить результат ее выполнения. Конструкция для  возвращения результата выполнения активности выглядит в целом следующим образом:

val intent = Intent()
intent.putExtra(...)  
intent.putExtra(...)  
setResult(Activity.RESULT_OK, intent)  
finish()

где в методе putExtra(...) вы можете вернуть любые данные, которые должны быть возвращены из активности(на самом деле не любые, желательно чтобы это были примитивы или небольшие объекты – есть ограничения по размеру возвращаемых данных). Этот участок кода, Вы можете например добавить в обработчик событий onBackPressed()

В качестве первого аргумента метода setResult(), необходимо использовать следующие константы:

  • Activity.RESULT_OK – если Вы хотите передать вызывающей активности результат успешного выполнения задачи.
  • Activity.RESULT_CANCELED – если Вы хотите передать вызывающей активности, что задача не была выполнена. Вы можете дополнительно передать коды ошибок или описание ошибки через putExtra()
  • Activity.RESULT_FIRST_USER + N – где N любое число от 0 до верхнего предела значений Integer, если Вы хотите определить свои расширенные результаты выполнения работы.

Обратите внимание, что Вам необходимо позаботиться об обработке событий переход “назад”, если Ваш UI содержит Toolbar. Вы можете реализовать обработчик, добавив в метод onCreate() следующие строки:

setSupportActionBar(toolbar)  
supportActionBar!!.setDisplayHomeAsUpEnabled(true)  
// The navigation button from the toolbar does not  
// do the same as the BACK button, more precisely  
// it does not call the onBackPressed() method.  
// We add a listener to do it ourselves  
toolbar.setNavigationOnClickListener { onBackPressed() }

Когда Мы возвращаем результат выполнения активности – нужен какой то способ для обработки полученных результатов. Так как вызов startActivityForResult() происходит асинхронно, мы не можем в главном потоке ожидать завершения работы активности. Для получения результатов выполнения активности, необходимо переопределить метод onActivityResult():

override  fun onActivityResult(requestCode:Int, resultCode:Int, data:Intent) {     
   // do something with 'requestCode' and 'resultCode'     
   // returned data is inside 'data'  
}

где – requestCode, это значение второго параметра из метода startActivityForResult(т.е. таким образом Вы можете понимать, результат какой активности и запроса Вы обрабатывает), а resultCode – значение, передаваемое методом setResult(...)– например Activity.RESULT_OK


Внимание! На некоторых устройства старший бит requestCode установлен в 1, не зависимо от того, что было установлено ранее. Для того, чтобы избежать такой неприятной ситуации, используйте следующую конструкцию

val requestCodeFixed = requestCode and 0xFFFF

внутри функции onActivityResult()


Фильтры намерений

Намерения(Intents) – это объекты, которые сообщают ОС Android что необходимо выполнить какое-нибудь действие. Намерения могут быть явными и неявными, в том случае когда мы не сообщаем какой именно компонент должен обработать намерения, а отдаем выбор пользователю или ОС Android. Если при обработке намерения возникает двусмысленность, т.е. например два компонента могут обработать запрос – ОС Android спросит пользователя, каким приложением обработать запрос. Чтобы можно было обрабатывать неявные намерения, компонент должен объявить, какие именно намерения он может получать.
Например, если активность может отображать текстовый контент, и поступает неявное намерение, которое содержит “Мне нужна активность, которая может отобразить текстовый файл”, возможно именно наша активность будет выбрана для обработки этого намерения. Для этого для компонента указывают в файле AndroidManifest.xml один или несколько фильтров намерений, чтобы наш компонент мог обрабатывать запросы намерений. Шаблон выглядит следующим образом:
<intent-filter android:icon="drawable resource"               
               android:label="string resource"               
               android:priority="integer" >
...
</intent-filter>

где icon – графический ресурс для рисования значка, label – текстовая мелка для отображения заголовка. Если атрибут icon и label не указаны, будут использованы значения из родительского элемента. Атрибут приоритета – это число от -999 до 999 и низкий приоритет предшествует высокому. Для получателя намерения приоритет указывает порядок выполнения для нескольких получателей.


Внимание Атрибут  приоритета следует использовать с осторожностью – компоненты не знают приоритеты друг друга и порядок обработки намерений. Поэтому, указывая приоритет, Вы строите зависимость между приложениями, которые не предусмотрены архитектурой приложений.


Элемент <intent-filter> может быть дочерним элементом:
  • <activity> и<activity-alias>
  • <service>
  • <receiver>

На заметку. Больше информации по теме намерений, Вы можете прочитать на сайте https://developer.android.com/reference/android/content/Intent 

Action – Параметр намерения

Параметр <action> – определяют действие, которое необходимо выполнить. Действий может быть больше одного. Синтаксис выглядит:

<action android:name="string" />

Действия будут обозначать что то вроде: View,” “Pick,”Edit,” “Dial и т.п. Полный список действия определяются константами вида ACTION_*, и находятся в классе android.content.Intent и также доступны для просмотра в онлайн документации в разделе Intent Constituent Parts”. Помимо этих базовых действий, вы также можете определить и свои.


На заметку. При использовании любого стандартного предопределенного действия не стоит ожидать, что в ОС обязательно будет присутствовать приложение, способное обработать его.


Категории намерений

Категории намерений – это дочерний элемент в фильтре намерения. Его синтаксис выглядит следующим образом:

<category android:name="string" />

Категория намерений используется для указания типа компонента, к которому должно обращаться намерение. Вы можете указать сразу несколько категорий. Этот элемент не является обязательным и Вы можете его опустить. Компонент будет соответствовать фильтру намерения, только если намерению соответствуют все категории.

Когда Вы создаете событие – намерение, Вы можете вручную добавить категорию следующим образом:

val intent:Intent = Intent(...)
intent.addCategory("android.intent.category.ALTERNATIVE")

Стандартные категории соответствуют константам, начинающимся с шаблона CATEGORY_* внутри класса android.content.Intent. Полное описание Вы можете найти в онлайн документации в разделе “Intent Constituent Parts“. 


Внимание Для неявных намерений Вы должны использовать категорию Default в фильтре намерений. Это связано с тем, что методы startActivity() и startActivityForResult() используют эту категорию по умолчанию.


Данные в намерениях

Элемент <data> является дочерним элементом фильтра намерений. Имеет следующий синтаксис:

<data android:scheme="string"      
      android:host="string"      
      android:port="string"      
      android:path="string"      
      android:pathPattern="string"      
      android:pathPrefix="string"      
      android:mimeType="string" />

Вы можете указать также:

  • только mime тип данных mimeType, например "text/plain" or "text/html". Например:
    <data android:mimeType="text/html" />
    
  • Тип данных, определяемых схемой, хостом, портом и спецификацией пути: <scheme>://<host>:<port>[<path>|<pathPrefix>|<pathPattern>].Где <path> = путь, <pathPrefix> – начало пути, <pathPattern> – шаблон имени пути, с возможностью использования регулярных выражений: «x*» — это 0 или более совпадений символа «x» и выражение – «.*» – 0 или более вхождений любого символа. Из-за правил экранирования спецсимволов,  необходимо указывать \\*  для литерала «*» и \\\\ для литерала «\».

Вы также можете указать сразу оба параметра, описанных выше.

При создании намерения, вы можете использовать методы setType(), setData(), and setDataAndType() для указания любых комбинаций типов данных.


Осторожно! В неявных намерениях, если вызывающий объект указывает часть данных URI как intent.data = <some URI>, этого может быть недостаточно, если вы собираетесь указать только scheme/host/port/pathв объявлении фильтра. Вам необходимо указать также тип mime данных, например mimeType="*/*";, иначе может быть так, что фильтр не будет соответствовать намерению. Эта ситуация возможна обычно c поставщиками контента, поскольку метод  getType() поставщика контента вызывается для указанного URI, а результат устанавливается для конкретного типа Mime намерения. 

Intent Extra Data

Любое намерение может содержать также дополнительные данные, которые можно использовать помимо тех данных, которые мы указываем в подэлементе <data>.

Вы можете использовать один из перегружаемых методов putExtra(...) для добавления любых Extra данных, будь то примитивы, простые объекты и объекты типа Bundle.

Флаги намерений

Вы можете указать специальные флаги обработки намерений, вызвав

intent.flags = Intent.<FLAG1> or Intent.<FLAG2> or ...

Большинство из этих флагов определяют, как именно намерения будут обрабатываться операционной системой Android. В частности флаги вида: FLAG_ACTIVITY_ предназначены для активностей, вызываемых  Content.startActivity(...), а флаги типа FLAG_RECEIVER_  ориентированы для использования с Context.sendBroadcast(...). Подробности Вы можете прочитать в официальной онлайн документации по Android SDK.

Системные фильтры намерений

Leave a Reply

Please disable your adblocker or whitelist this site!