Дія (Activity) являється компонентом програми, яка забезпечує інтерфейс з яким взаємодіє користувач для того, щоб зробити що-небудь, наприклад, набрати номер телефону, зробити фото, надіслати електронну пошту або переглянути карту. Кожній дії надається вікно у якому вона малює свій інтерфейс користувача. Вікно зазвичай заповнює весь екран, але може бути меншим і розміщуватись поверх інших вікон. Програма зазвичай складається з багатьох дій, які слабо зв'язані між собою. Зазвичай, одна дія у програмі вказується як “головна”, яка представляється користувачу під час першого запуску програми. Кожна дія може запустити іншу дію для забезпечення додаткових функцій. Кожного разу, під час запуску нової дії (activity), попередня зупиняється, але система зберігає її у стеку, а нова вставляється зверху даного стеку і отримує фокус користувача. Стек відповідає базовому механізму “перший увійшов - перший вийшов”, отож, коли користувач завершив роботу з поточною дією і натискає кнопку “назад”, вона витягується з стеку і попередня дія продовжує свою роботу. Коли дія зупиняється через створення нової, вона сповіщується цією зміною свого стану через методи життєвого циклу дії. Існує декілька методів, які дія може отримати під час зміни свого стану — коли система створює їх, зупиняє, продовжує роботу або закриває її, і кожний метод дає можливість виконати специфічну роботу у відповідності до потреб програми. Наприклад, під час зупинення, ваша дія повинна вивільняти великі об'єкти, на подобі мережевих підключень або підключень до бази даних. Під час того, як дія виходить з паузи, ви можете поновити необхідні ресурси і продовжити дії, які були перерваними. Ці зміни станів являються частиною життєвого циклу дії.

Створення дії (Activity)

Для створення дії вам необхідно оголосити потомок відносно класу Activity (або відносно існуючого потомка класу). У даному дочірньому класі, вам необхідно реалізувати обробники подій, які система буде викликати коли дія потребуватиме зміни стану життєвого циклу, на подобі створення, паузи, поновлення, або закриття. Два найбільш важливих обробники наступні:
  • onCreate() - вам необхідно реалізувати цей метод. Система викликає даний метод під час створення дії. У вашій реалізації потрібно ініціалізувати компоненти дії. Найбільш важливо щоб в даному методі ви викликали setContentView() для визначення макету дії інтерфейсу користувача.
  • onPause() - система викликає цей метод, коли користувач залишає вашу дію (однак це не означає, що дія буде закритою). В даному обробнику вам зазвичай необхідно виконати будь-які зміни, які повинні бути постійними за межами поточної сесії користувача, через те, що користувач може і не повернутись.
Існує декілька методів життєвих циклів, які ви повинні використати для того щоб забезпечити хороший досвід користувача між діями і обробляти неочікувані переривання, які спричиняють зупинку дії і навіть її закриття.

Реалізація інтерфейсу користувача

Інтерфейс користувача для дії виконується у вигляді ієрархії представлень — об'єктів, які є дочірніми відносно класу View. Кожне представлення контролює певну прямокутну площину у вікні дії і реагує на дії користувача. Наприклад, представлення може бути кнопкою, яка ініціює певну роботу, коли користувач натискає на неї. Android забезпечує декілька готових представлень, які ви можете використати для створення і організації вашого макету. “Віджети” являються представленнями, які забезпечують візуальні (і інтерактивні) елементи для екрану, на подобі кнопок, полів вводу, прапорців або просто зображень. “Layout” являються представленнями які похідні від класу ViewGroup, яка забезпечує унікальну модель макету для її потомків, на подобі лінійних макетів, табличних макетів, або відносних макетів. Ви також можете створити потомки класів View i ViewGroup (або їхніх потомків) для створення ваших власних макетів і застосувати їх до вашої дії (activity). Найбільш поширеним шляхом визначення макету використовуючи представлення являються файли XML, які зберігаються у каталогах ресурсів вашої програми. Таким чином, ви можете підтримувати зовнішній вигляд вашого інтерфейсу відокремлено від коду програми, який визначає поведінку дії. Ви можете встановити необхідний макет використовуючи setContentView(), передаючи ідентифікатор ресурсу. Однак, ви також можете створити нове представлення у коді вашої дії і побудувати ієрархію представлень, вставляючи представлення у об'єкт ViewGroup, після цього використовувати необхідний макет передаючи кореневий об'єкт ViewGroup до setContentView().

Оголошення маніфесту дії

Вам необхідно оголосити вашу дію у маніфесті, щоб вона була доступна для системи. Для оголошення дії (activity), відкрийте ваш маніфест файл і додайте елемент <activity> в якості потомка елементу <application>. Наприклад:
<manifest ... > 
  <application ... > 
      <activity android:name=".ExampleActivity" /> 
      ... 
  </application ... > 
  ... 
</manifest >
Існує декілька інших атрибутів, які ви можете включити у цей елемент, для визначення властивостей на подобі міток, іконок або тем для стилізації інтерфейсу дії. Атрибут android:name являється єдиним обов'язковим атрибутом — він вказує на ім'я класу дії. Як тільки ви опублікуєте свою програму, не потрібно змінювати це ім'я.

Використання фільтрів намірів

Елемент <activity> може оголошувати деякі фільтри намірів (intent) — використовуючи елемент <element-fiter> для оголошення того, як інші компоненти інших програм можуть активізувати її. Коли ви створюєте нову програму, використовуючи інструментарій Android SDK, створена для вас базова дія автоматично включає фільтр намірів, які оголошують спосіб дії відповідати на “головні” події і повинна бути поміщена у категорії “luncher”. Даний фільтр виглядає наступним чином:
<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> 
    <intent-filter> 
        <action android:name="android.intent.action.MAIN" /> 
        <category android:name="android.intent.category.LAUNCHER" /> 
    </intent-filter> 
</activity>
Елементи вказує, що це є головна точка входу до програми. Елемент вказує, що дія повинна бути включена до списку програм системи, для того щоб користувач міг її запустити. Якщо ви бажаєте щоб ваша програма була автономною і інші програми не могли активізувати її дії, тоді вам не потрібно вказувати інші фільтри намірів. Тільки одна дія повинна мати атрибут “головної” і категорію “luncher”, як показано в попередньому прикладі. Дії, які ви бажаєте приховати від інших програм не повинні мати інші фільтри і ви можете запустити їх самостійно використовуючи явні наміри. Однак, якщо ви бажаєте, щоб дія відповідала на неявні наміри, які є похідними від інших програм (і вашої власної), тоді вам потрібно визначити додаткові фільтри намірів для вашої дії. Для кожного типу наміру, на який ви бажаєте відкликатись, вам необхідно включити , які включають елементи і, не обов'язково, елемент і (або) елемент . Дані елементи вказують на тип наміру на який програма повинна відповідати.

Запуск дії

Ви можете запустити іншу дію використовуючи startActivity(), передаючи їй об'єкт Intent, який описує необхідну дію. Об'єкт наміру в точності описує дію, яку ви бажаєте запустити або описує тип дії, яку ви бажаєте виконати (і система обере відповідну дію для вас, яка може знаходитись у іншій програмі). Намір також може містити невелику кількість даних, які будуть використовуватись дією, яка запускається. Під час роботи з власною програмою, вам часто необхідно просто запустити відому дію. Ви можете виконати це, створюючи намір, який неявно вказує на дію, яку вам необхідно запустити, використовуючи ім'я класу. Наприклад, ось як одна дія може запустити іншу, названу SignInActivity:
Intent intent = new Intent(this, SignInActivity.class) ;
startActivity(intent) ;
Однак ви також можете забажати виконати деяку роботу, на подобі надсилання повідомлення, текстового повідомлення, або оновлення статусу, використовуючи дані з вашої дії. В цьому випадку, ваша програма може не мати власних дій для виконання таких операцій, отож ви можете використати дії з інших програм на пристрої, які можуть виконати ці операції для вас. Це показує цінність намірів — ви можете створити намір, який описує операцію, яку ви бажаєте виконати і система запустить відповідну дію з іншої програми. Якщо існує множина дій, які можуть обробити намір, тоді користувач може вибрати який з них використати. Наприклад, якщо ви бажаєте дозволити користувачу надсилати електронний лист, ви можете створити намір як показано нижче:
Intent intent = new Intent (Intent.ACTION_SEND) ;
intent.putExtra (Intent.EXTRA_EMAIL, recipientArray) ; 
startActivity (intent) ;
Особливий параметр наміру EXTRA_EMAIL доданий до створеного, являється масивом рядків, які описують адреси електронних скриньок до яких повинне бути доставлене повідомлення. Коли програма електронних повідомлень відповідає даний на намір, вона читає переданий масив рядків і поміщає їх у поле “надіслати до” форми створення електронного повідомлення. У даній ситуації, дія програми електронних повідомлень запускається і коли користувач завершив створення, дія виходить з паузи.

Запуск дії для отримання результату

Інколи ви можете забажати отримати результат з дії, яку ви запустили. У цьому випадку, запустіть дію за допомогою startActivityForResult() (замість startActivity()). Для того щоб отримати результат з запущеної дії, створіть метод-обробник onActivityResult(). Коли дочірня дія завершить своє виконання, вона поверне результат у об'єкті Intent до вашого методу onActivityResult(). Наприклад, припустимо, що ви бажаєте щоб користувач обрав один з його контактів, отож ваша дія може виконати операції над інформацією про обраний контакт. Ось як ви можете створити даний намір і обробити результат.
private void pickContact() { 
    // Створюємо намір для вибору контакту 
    Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); 
    startActivityForResult(intent, PICK_CONTACT_REQUEST); 
} 

@Override 
protected void onActivityResult(int requestCode, int resultCode, Intent data) { 
    // Якщо запит виконався успішно і дорівнює PICK_CONTACT_REQUEST 
    if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { 
        // Виконати запит до постачальника контенту контактів для ім'я контакту 
        Cursor cursor = getContentResolver().query(data.getData(), 
        new String[] {Contacts.DISPLAY_NAME}, null, null, null); 
        if (cursor.moveToFirst()) { // true якщо курсор не пустий 
            int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); 
            String name = cursor.getString(columnIndex); 
            // виконати що-небудь з контактом 
        } 
    } 
}
Даний приклад показує базову логіку яку потрібно використовувати у вашому методі onActivityResult() для обробка результату дії. Перша умова перевіряє чи запит виконався успішно, якщо так — код результату рівний RESULT_OK, і якщо запит, до якого відноситься даний результат відомий — у даному випадку requestCode співпадає з другим параметром надісланим за допомогою startActivityForResult(). Звідти, код обробляє результат роботи дії, опитуючи дані повернені у об'єкті класу Intent (параметр data). В даному випадку, об'єкт ContentResolver виконує запит над постачальником контенту, який повертає Cursor, що дозволяє зчитувати отримані дані.

Закриття дії

Ви можете закрити дію викликаючи її метод finish(). Ви також можете закрити окрему дію, яку ви відкрили перед тим за допомогою методу finishActivity().

Управління життєвим циклом Дії

Управління життєвим циклом ваших примірників дій за допомогою реалізації методів обробників, являється критичним для розробки сильних і гнучких програм. На життєвий цикл дії прямо впливають інші дії і їхній стек. Дія, по суті, може існувати у трьох станах: Поточний Дія знаходиться на передньому плані і утримує фокус користувача. Пауза Інша дія знаходиться на передньому плані і утримує фокус, але поточна все-одно видима. Тобто, інша дія знаходиться зверху поточної і вона частково прозора або не заповнює весь екран. Зупинена дія являється повністю працюючою, але може бути закритою системою у ситуаціях відсутності необхідної кількості пам'яті. Повністю зупинена Дія повністю закривається іншою дією (поточна дія знаходиться на задньому фоні). Повністю зупинена дія також являється працюючою. Однак вона більше не видима для користувача і може бути закритою операційною системою через брак пам'яті. Якщо дія знаходиться у паузі або повністю зупинена, система може скинути її з основної пам'яті, викликаючи метод finish(), або просто вбиваючи процес. Коли дія відкривається знову (після того як була закритою), вона повинна відтворити свій стан.

Реалізація методів обробників життєвого циклу

Під час того, як дія переходить з одного стану у інший, вона отримує повідомлення через спеціальні методи обробники станів. Усі дані обробники являються методами, які ви можете перевизначити для виконання відповідних операцій під час зміни стану вашої дії. Наступний скелет дії включає кожен з фундаментальних методів-обробників її циклу.
public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // Дія була створена
    }
    @Override
    protected void onStart() {
        super.onStart();
        // Дія буде показана найближчим часом
    }
    @Override
    protected void onResume() {
        super.onResume();
        // Дія вийшла з паузи
    }
    @Override
    protected void onPause() {
        super.onPause();
        // Інша дія отримала фокус користувача
    }
    @Override
    protected void onStop() {
        super.onStop();
        // Дія більше не видима для користувача
    }
    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Дія в даний момент закривається
    }
}
Ваші реалізації даних повідомлень повинні завжди викликати свої відповідники з батьківського класу (super), перед виконанням будь-якої роботи, як показано вище у коді. Разом, дані методи визначають весь життєвий цикл дії. Реалізовуючи дані методи, ви можете вести облік трьох вкладених циклів у життєвому циклі дії:
  • Весь час діяльності відбувається між викликами onCreate() і onDestroy(). Ваша дія повинна виконати встановлення глобального стану (на подобі визначення макету) у onCreate(), і вивільнити усі ресурси всередині методу onDestroy(). Наприклад, якщо ваша дія має потік, який виконується на задньому фоні для завантаження даних з мережі, вона може створити даний потік у onCreate() і після цього зупинити даний потік у методі onDestroy().
  • Час відображення дії відбувається між викликами onStart() і onStop(). В даний проміжок часу, користувач може споглядати дію на екрані і взаємодіяти з нею. Наприклад, метод onStop() викликається коли запускається нова дія і поточна переходить на задній план. Між цими двома методами, ви можете використовувати ресурси, які необхідні для відображення дії для користувача. Наприклад, ви можете зареєструвати об'єкт BroadcastReceiver у методі onStart() для відслідковування змін, які впливають на ваш інтерфейс, і вивільнити його у методі обробнику onStop(), коли користувач більше не зможе споглядати ваші зміни. Система може викликати onStart() i onStop() велику кількість разів у життєвому циклі дії, так як дія переходить між станами видимості і невидимості для користувача.
  • Час активності дії відбувається між викликами onResume() і onPause(). В даний проміжок часу дія являється спереду усіх інших представлень екрану і має фокус введення користувача. Через те що дія може часто змінювати свій стан, код в даних двох методах повинен бути швидкодійним для запобігання повільних переходів, які змусять користувача очікувати.
Наступне зображення проілюструє ці цикли і шляхи переходу між станами дії. Прямокутники представляють методи-обробники, які ви можете реалізувати для виконання операцій коли дія переходить між станами. lifecycle_diagram

Зберігання стану дії

Розділ Управління життєвим циклом дії ясно вказує, що коли дія переходить у стан паузи або зупинки, поточний стан дії зберігається. Це справедливо через те, що об'єкт Activity все-одно утримує пам'ять — уся інформація про його члени і поточний стан все ще доступна. Тобто, будь-які зміни, які користувач виконував з дією відновлюються, отож коли дія повертається на передній план (коли виникає подія onResume()), дані зміни все ще доступні. Однак, коли система закриває дію для забезпечення пам'яттю інші процеси, об'єкт Activity знищується, отож система не може просто відновити її з усіма змінами. Замість того, система повинна створити знову об'єкт Activity, якщо користувач переходить до поточної дії. Проте, користувач не був усвідомлений, що система закрила дію і створила її знову, і тому, він очікує, що дія буде такою ж як перед згортанням. У даній ситуації, ви можете бути впевненими, що важлива інформація про стан дії зберігається, реалізацією додаткового спеціального метода-обробника onSaveInstanceState(), який дозволяє вам зберігати інформацію про стан вашої дії. Система викликає onSaveInstanceState() перед закриттям і передає даному методу об'єкт класу Bundle у якому ви можете зберегти інформацію про дію, у вигляді пар ключ-значення, використовуючи методи на подобі putString() i putInt(). Після цього, якщо система закриває вашу програму і користувач переходить назад до неї, система повторно створює дію і передає об'єкт Bundle до методів onCreate() i onRestoreInstanceState(). Використання цих методів, ви можете отримати ваші збережені значення з об'єкту Bundle і відновити стан дії. Якщо немає інформації про стан, об'єкт Bundle переданий даним методам буде являтись значенням null (також в разі першого створення дії). Activity_restore_cycle Однак, навіть якщо ви не виконуєте будь-які дії для збереження стану за допомогою обробника onSaveInstanceState(), деякі з станів дії відновлюються класом Activity, а саме за допомогою стандартної реалізації onSaveInstanceState(). Точніше, стандартна реалізація викликає відповідний метод onSaveInstanceState() для кожного примірника класу View, що дозволяє кожному представленню надати інформацію про себе, яка буде збереженою. Майже усі віджети у фреймворку Android мають свою відповідну реалізацію даного методу, отож будь-які видимі зміни до інтерфейсу будуть автоматично збереженими і відновленими коли ваша дія повторно створюється. Наприклад, віджет EditText зберігає будь-який текст введений користувачем, а віджет CheckBox зберігає встановлений прапорець. Єдину роботу, яку вам необхідно виконати полягає в наданні унікального ідентифікатора (атрибутом android:id) для кожного віджета, стан якого ви бажаєте зберегти. Якщо віджет немає ідентифікатора, тоді система не може зберегти його стан. Хоча реалізації за умовчанням методу onSaveInstanceState() зберігають корисну інформацію про інтерфейс вашої дії, вам все одно може знадобитись його перевизначення для збереження додаткової інформації. Наприклад, вам може знадобитись зберегти значення полів класу, які змінились під час діяльності дії. Через те, що реалізація за умовчанням методу onSaveInstanceState() допомагає зберегти стан візуального інтерфейсу, якщо ви перевантажите цей метод для зберігання додаткової інформації, вам завжди необхідно викликати реалізацію суперкласу (батьківського) методу onSaveInstanceState() перед тим, як ви виконаєте будь-яку роботу. Також, вам потрібно викликати реалізацію суперкласу методу onRestoreInstanceState() якщо ви перевантажите і її, отож реалізація за умовчанням зможе відновити стан дії. Зверніть увагу, що через те, що не гарантується виклик методу onSaveInstanceState(), вам потрібно використовувати його для збереження тимчасового стану дії. Не потрібно використовувати даний метод для збереження постійних даних. Замість нього, використайте onPause() для збереження постійних даних (на подобі даних, які повинні бути збереженими у базі даних), коли користувач залишає дію. Хороший спосіб для тестування можливостей вашої програми зберігати свій стан, полягає у простій зміні орієнтації екрану. Під час того, як орієнтація екрану змінюється (з альбомної на книжну і навпаки), система видаляє і відновлює дію для застосування альтернативних ресурсів, які можуть бути доступними для нової конфігурації екрану. З цієї причини, дуже важливо, щоб ваші дії (activities) повністю відновлювали свій стан під час повторного створення, через те що користувачі регулярно перевертають екрани під час використання програми.

Обробка зміни конфігурації

Деякі конфігурації пристроїв можуть змінюватись під час виконання програми. Коли така подія настає, Android повторно створює поточну дію (система викликає onDestroy() і тоді onCreate()). Дана поведінка розроблена для того, щоб допомоги програмам адаптуватись до нових конфігурацій автоматично завантажуючи вашу програму разом з новими альтернативними ресурсами, які ви надали під час створення програми. Якщо ви належно розробили свою дію відносно перезапуску від зміни орієнтації екрану і відновили її стан як описано вище. Ваша програма буде більш стійкою до неочікуваних подій у життєвому циклі дії. Найкращим способом для перезапуску являється збереження і відновлення стану вашої дії використовуючи методи onSaveInstaceState() і onRestoreInstanceState() (або onCreate()), як обговорювалось в попередній секції.

Координування діями

Коли одна дія запускає іншу, вони обидві проходять свій життєвий цикл. Перша дія входить в стан паузи і зупиняється (хоча, вона не зупиняється, якщо вона все одно видима на задньому фоні), поки інша дія створюється. У випадку передачі даних між діями за допомогою їх збереження в пам'ять або де-інде, важливо розуміти, що перша дія ще повністю не зупинилась перед тим, як друга створюється. Скоріш, процес створення другої дії накладається з процесом зупинки першої. Черга викликів добре визначена, особливо коли дві дії розміщені у одному процесі і одна запускає іншу. Ось порядок операцій, які виникають коли дія А запускає дію Б.
  1. Виконується метод onPause() дії А.
  2. Викликаються методи onCreate(), onStart() i onResume() дії Б (яка тепер отримала фокус користувача).
  3. Після цього дія А більше не видима на екрані, і виконується її метод onStop().
Даний передбачуваний порядок викликів обробників життєвого циклу дій дозволяє вам управляти передавання інформації від одної дії до іншої. Наприклад, якщо вам потрібно звернутись до бази даних коли перша дія зупиняється, щоб друга дія змогла прочитати дані, в такому разі вам потрібно звернутись до бази даних під час onPause() замість запису даних під час onStop(). Рекомендуємо також для прочитання статтю "Перша програма “Привіт, Світ!” на Android".