DRAFT [2019-2020][ua] at 2023-01-05 17:17:43 +0200
Logo-do [errata] Profile

Програмування для мобільних пристроїв

Activity. Життєвий цикл додатка

Конспект лекції


Activity. Життєвий цикл додатка

Ключовим компонентом для створення візуального інтерфейсу в додатку Android є activity (активність). Нерідко activity асоціюється з окремим екраном

або вікном додатка, а перемикання між вікнами буде відбуватися як переміщення від однієї activity до іншої. Додаток може мати одну або кілька

activity. Наприклад, в минулій темі робота додатка починалася з класу MainActivity:

public class MainActivity extends AppCompatActivity {

//Вміст класу

}

Всі об'єкти activity є об'єктами класу android.app.Activity, який містить базову функціональність для всіх activity. У додатку з минулого теми ми

безпосередньо з цим класом не працювали, а MainActivity успадковувалась від класу AppCompatActivity. Однак сам клас AppCompatActivity, хоч і не

прямо, успадковується від базового класу Activity.

 

Життєвий цикл додатка

Всі додатки Android мають строго визначену систему життєвого циклу. При запуску користувачем додатку система дає цьому додатку високий

пріоритет. Кожна програма запускається у вигляді окремого процесу, що дозволяє системі давати одним процесам вищий пріоритет, на відміну від

інших. Після припинення роботи з додатком, система звільняє всі пов'язані ресурси і переводить додаток у розряд низькопріоритетного і закриває

його.

Всі об'єкти activity, які є в додатку, управляються системою у вигляді стека activity, який називається back stack. При запуску нової activity вона

поміщається поверх стека і виводиться на екран пристрою, поки не з'явиться нова activity. Коли поточна activity закінчує свою роботу (наприклад,

користувач йде з програми), то вона видаляється з стека, і відновлює роботу та activity, яка раніше була другою в стеці.

Після запуску activity проходить через ряд подій, які обробляються системою і для обробки яких існує ряд зворотних викликів:

protected void onCreate(Bundle saveInstanceState);

protected void onStart();

protected void onRestoreInstanceState(Bundle saveInstanceState);

protected void onRestart();

protected void onResume();

protected void onPause();

protected void onSaveInstanceState(Bundle saveInstanceState);

protected void onStop();

protected void onDestroy();

Схематично взаємозв'язок між усіма цими зворотними викликами можна представити так, як показано на рисунку нижче.

onCreate ()

onCreate - перший метод, з якого починається виконання activity. У цьому методі activity переходить в стан Created. Цей метод обов'язково повинен

бути визначений в класі activity. У ньому проводиться первісне налаштування activity. Зокрема, створюються об'єкти візуального інтерфейсу. Цей

метод отримує об'єкт Bundle, який містить activity в попередньому стані, якщо воно було збережено. Якщо activity заново створюється, то даний об'єкт

має значення null. Якщо ж activity вже раніше була створена, але перебувала в призупиненому стані, то bundle містить пов'язану з activity інформацію.

onStart

У методі onStart () здійснюється підготовка до виведення activity на екран пристрою. Як правило, цей метод не вимагає перевизначення, а всю роботу

робить вбудований код. Після завершення роботи методу activity відображається на екрані, викликається метод onResume, а activity переходить в стан

Resumed.

onRestoreInstanceState

Після завершення методу onStart () викликається метод onRestoreInstanceState, який покликаний відновлювати збережений стан з об'єкта Bundle, який

передається в якості параметра. Але слід враховувати, що цей метод викликається тільки тоді, коли Bundle НЕ дорівнює null і містить раніше

збережений стан. Так, при першому запуску програми цей об'єкт Bundle матиме значення null, тому і метод onRestoreInstanceState не

викликатиметься.

onResume

А при виклику методу onResume activity переходить в стан Resumed і користувач може з нею взаємодіяти. І власне activity залишається в цьому стані,

поки вона не втратить фокус, наприклад, внаслідок перемикання на іншу activity або просто через вимкнення екрану пристрою.

onPause

Якщо користувач вирішить перейти до іншої activity, то система викличе метод onPause. У цьому методі можна звільняти використовувані ресурси,

припиняти процеси, наприклад, відтворення аудіо, анімацій, зупиняти роботу камери (якщо вона використовується) і т.д., щоб вони менше

позначалися на продуктивності системи.

Але треба враховувати, що на роботу даного методу відводиться дуже мало часу, тому не варто тут зберігати якісь дані, особливо якщо при цьому

потрібно звернення до мережі, наприклад, відправка даних по інтернету, або звернення до бази даних.

Після виконання цього методу activity стає невидимою, не відображається на екрані, але вона все ще активна. І якщо користувач вирішить

повернутися до цієї activity, то система викличе знову метод onResume, і activity знову з'явиться на екрані.

Інший варіант роботи може виникнути, якщо раптом система бачить, що для роботи активних додатків необхідно більше пам'яті. І система може сама

завершити повністю роботу activity, яка невидима і знаходиться в тлі. Або користувач може натиснути на кнопку Back (Назад). В цьому випадку у

activity викликається метод onStop.

onSaveInstanceState

Метод onSaveInstanceState викликається після методу onPause (), але до виклику onStop (). У onSaveInstanceState проводиться збереження стану

програми в переданий в якості параметра об'єкт Bundle.

onStop

У цьому методі activity переходить в стан Stopped. У методі onStop слід звільнити використовувані ресурси, які не потрібні користувачеві, коли він не

взаємодіє з activity. Тут також можна зберігати дані, наприклад, в базу даних.

При цьому під час стану Stopped activity залишається в пам'яті пристрою, зберігається стан всіх елементів інтерфейсу. Наприклад, якщо в текстове

поле EditText був введений якийсь текст, то після відновлення роботи activity і переходу її в стан Resumed ми знову побачимо в текстовому полі раніше

введений текст.

Якщо після виклику методу onStop користувач вирішить повернутися до колишньої activity, тоді система викличе метод onRestart. Якщо ж activity

зовсім завершила свою роботу, наприклад, через закриття програми, то викликається метод onDestroy ().

onDestroy

Ну і завершується робота активності викликом методу onDestroy, який виникає або, якщо система вирішить вбити activity, або при виклику методу

finish ().

Також слід зазначити, що при зміні орієнтації екрану система завершує activity і потім створює її заново, викликаючи метод onCreate.

В цілому перехід між станами activity можна виразити наступною схемою:

 

 

Розглянемо кілька ситуацій. Якщо ми працюємо з Activity і потім перемикаємося на іншу програму, або натискаємо на кнопку Home, то у Activity

викликається наступний ланцюжок методів: onPause -> onStop. Activity переходить в стан Stopped. Якщо користувач вирішить повернутися до

Activity, то викликається наступний ланцюжок методів: onRestart -> onStart -> onResume.

Інша ситуація, якщо користувач натискає на кнопку Back (Назад), то викликається наступний ланцюжок onPause -> onStop -> onDestroy. В результаті

Activity знищується. Якщо ми раптом захочемо повернутися до Activity через диспетчер задач або заново відкривши додаток, то activity буде заново

створена через методи onCreate -> onStart -> onResume

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

Ми можемо управляти цими подіями життєвого циклу, перевизначивши відповідні методи.

package com.example.viewsapplication;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.util.Log;

public class MainActivity extends AppCompatActivity {

  private final static String TAG = "MainActivity";

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

    Log.d(TAG, "onCreate");

  }

  @Override

  protected void onDestroy() {

    super.onDestroy();

    Log.d(TAG, "onDestroy");

  }

  @Override

  protected void onStop() {

    super.onStop();

    Log.d(TAG, "onStop");

  }

  @Override

  protected void onStart() {

    super.onStart();

    Log.d(TAG, "onStart");

  }

  @Override

  protected void onPause() {

    super.onPause();

    Log.d(TAG, "onPause");

  }

  @Override

  protected void onResume() {

    super.onResume();

    Log.d(TAG, "onResume");

  }

  @Override

  protected void onRestart() {

    super.onRestart();

    Log.d(TAG, "onRestart");

  }

  @Override

  protected void onSaveInstanceState(Bundle outState) {

    super.onSaveInstanceState(outState);

    Log.d(TAG, "onSaveInstanceState");

  }

  @Override

  protected void onRestoreInstanceState(Bundle savedInstanceState) {

    super.onRestoreInstanceState(savedInstanceState);

    Log.d(TAG, "onRestoreInstanceState");

  }

}

Для запису в лог подій тут використовується клас android.util.Log.

В даному випадку обробляються всі ключові методи життєвого циклу. Вся обробка зведена до виклику методу Log.d (), в який передається TAG – будь-

яке значення рядка і рядок, який виводиться в консолі logcat внизу Android Studio в вікні Android Monitor, виконуючи роль налагоджувальної інформації.

Якщо ця консоль за замовчуванням прихована, то ми можемо перейти до неї через пункт меню View -> Tool Windows -> Android Monitor.

І під час запуску програми ми зможемо побачити у вікні logcat інформацію для відлагодження, яка визначається в методах життєвого циклу activity:

 

Файл маніфесту AndroidManifest.xml

Кожна програма містить файл маніфесту AndroidManifest.xml. Даний файл визначає важливу інформацію про програму - назва, версію, іконки,

дозволи, які додаток використовує, реєструє всі використовувані класи activity, сервіси і т.д.

Файл маніфесту може виглядати так:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.viewsapplication">

<application

android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:supportsRtl="true"

android:theme="@style/AppTheme">

<activity android:name=".MainActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

</application>


</manifest>

Елементом кореневого рівня є вузол manifest. В даному випадку тільки визначається пакет додатка - package = "com.example.eugene.viewsapplication"

Більшість налаштувань рівня додатка визначаються елементом application. Наприклад, через атрибут android: icon = "@ mipmap / ic_launcher"

задається іконка програми, яка знаходиться в каталозі res / mipmap-xxxx

Також тут задається назва додатка, яка буде відображатися на мобільному пристрої в списку додатків і в заголовку: android: label = "@ string /

app_name". В даному випадку вона зберігається в строкових ресурсах.

Вкладені елементи activity визначають всі використовувані в додатку activity. В даному випадку видно, що в додатку є тільки одна activity - MainActivity.

Елемент intent-filter в MainActivity вказує, як дана activity буде використовуватися. Зокрема, за допомогою вузла action android: name =

"android.intent.action.MAIN", що дана activity буде вхідною точкою в додаток і не повинна отримувати будь-які дані ззовні.

Елемент category android: name = "android.intent.category.LAUNCHER" вказує, що MainActivity представлятиме стартовий екран, який відображається

під час запуску програми.

 

Визначення версій

За допомогою атрибутів елемента manifest можна визначити версію програми і його коду:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.viewsapplication"

android:versionName="1.0"

android:versionCode="1" />


<!--решта вмісту-->


</manifest>

При бажанні ми також можемо визначити версію в ресурсах, а тут посилатися на ресурс.

 

Установка версії SDK

Для управління версією android sdk в файлі маніфесту визначається елемент <uses-sdk>. Він може використовувати такі атрибути:

 

Версія визначається номером API, наприклад, Jelly Beans 4.1 має версію 16:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android"

package="com.example.viewsapplication"

android:versionName="1.0"

android:versionCode="1" />

<uses-sdk android:minSdkVersion="16"

android:targetSdkVersion="19" />

<!-- решта вмісту-->


</manifest>

 

Установка дозволів

Іноді додатку потрібні дозволи на доступ до певних ресурсів, наприклад, до списку контактів, камери і т.д. Щоб додаток міг працювати з тим же

списком контактів, в файлі маніфеста необхідно встановити відповідні дозволи. Для установки дозволів застосовується елемент <uses-permission>:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.viewsapplication"/>

<uses-permission android:name="android.permission.READ_CONTACTS" />

<uses-permission android:name="android.permission.CAMERA"

android:maxSdkVersion="21" />

<!-- решта вмісту-->


</manifest>

Атрибут android: name встановлює назву дозволу: в даному випадку на читання списку контактів і використання камери. Опціонально можна

встановити максимальну версію sdk за допомогою атрибута android: maxSdkVersion, який приймає номер API.

 

Підтримка різних роздільних здтаностей

Світ пристроїв Android дуже сильно фрагментований, тут зустрічаються як гаджети з невеликим екраном, так і великі широкоформатні телевізори. І

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

елемент <supports-screens>:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.viewsapplication"/>

<supports-screens android:largeScreens="true"

android:normalScreens="true"

android:smallScreens="false"

android:xlargeScreens="true" />

<!-- решта вмісту-->


</manifest>

Даний елемент приймає чотири атрибута:

Якщо атрибут має значення true, то додаток буде підтримуватися відповідним розміром екрану.

 

Заборона на зміну орієнтації

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

зробити, щоб додаток незалежно від повороту гаджета використовував тільки одну орієнтацію. Для цього в файлі маніфесту у необхідній activity треба

встановити атрибут android: screenOrientation. Наприклад, заборонимо альбомну орієнтацію:

<?xml version="1.0" encoding="utf-8"?>

<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.example.viewsapplication" >


<application


android:allowBackup="true"

android:icon="@mipmap/ic_launcher"

android:label="@string/app_name"

android:theme="@style/AppTheme" >

<activity

android:screenOrientation="portrait"

<!-- решта вмісту-->

Значення android: screenOrientation = "portrait" вказує, що дана activity буде знаходитися тільки в портретній орієнтації. Якщо ж треба встановити тільки

альбомну орієнтацію, тоді треба використовувати значення android: screenOrientation = "landscape"

 

Intent і Intent-фільтри

Для взаємодії між різними об'єктами activity ключовим класом є android.content.Intent. Він являє собою задачу, яку треба виконати з додатком.

Для роботи з Intent додамо новий клас Activity. Для цього натиснемо правою кнопкою миші по папці, в якій знаходиться клас MainActivity, і потім в

контекстному меню виберемо New-> Activity-> Empty Activity:

Новий клас Activity назвемо SecondActivity, а файл його розмітки інтерфейсу - activity_second:

І після цього в проект буде додана нова Activity - SecondActivity:

 

Після цього в файлі маніфесту AndroidManifest.xml ми зможемо знайти такі рядки:

<activity

android:name=".MainActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".SecondActivity"></activity>

Всі використовувані класи activity повинні бути описані у файлі AndroidManifest.xml за допомогою елемента <activity>. Кожен подібний елемент містить

як мінімум один атрибут android: name, який встановлює ім'я класу activity.

Однак по суті activity - це стандартні класи java, які успадковуються від класу Activity або його спадкоємців. Тому замість вбудованих шаблонів в

Android Studio можемо додавати звичайні класи, і потім їх наслідувати від класу Activity. Однак в цьому випадку потрібно буде вручну додавати в файл

маніфесту дані про activity.

Причому для MainActivity в елементі intent-filter визначається Інтент-фільтр. У ньому елемент action має значення "android.intent.action.MAIN" і

представляє головну точку входу в додаток. Тобто MainActivity залишається основною і запускається додатком за замовчуванням.

Для SecondActivity просто вказано, що вона в проекті, і ніяких intent-фільтрів для неї не задано.

Щоб з MainActivity запустити SecondActivity, треба викликати метод startActivity ():

Intent intent = new Intent (this, SecondActivity.class);

startActivity (intent);

Як параметр в метод startActivity передається об'єкт Intent. Для свого створення Intent в конструкторі приймає два параметри: action (яке виконує дію

або завдання) і data (передані в задачу дані). Як параметр action може виступати безліч можливих дій. В даному випадку використовується дія

ACTION_MAIN, яка задається константою "android.intent.action.MAIN".

Тепер розглянемо реалізацію переходу від однієї Activity до іншої. Для цього в файлі activity_main.xml (тобто в інтерфейсі для MainActivity) визначимо

кнопку:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

android:id="@+id/activity_main"

android:layout_width="match_parent" android:layout_height="match_parent">


<Button

android:id="@+id/navButton"

android:textSize="20sp"

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="Перейти до SecondActivity"

android:onClick="onClick" />


</LinearLayout>

І визначимо для кнопки в класі MainActivity обробник натискання, за яким буде здійснюватися перехід до нової Activity:

package com.example.activityapplication;

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

public class MainActivity extends AppCompatActivity {

  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

  }

  public void onClick(View view) {

    Intent intent = new Intent(this, SecondActivity.class);

    startActivity(intent);

  }

}

У обробнику натискання буде запускатися SecondActivity. Далі змінимо код SecondActivity:

package com.example.activityapplication;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.widget.TextView;

public class SecondActivity extends AppCompatActivity {

  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    TextView textView = new TextView(this);

    textView.setTextSize(20);

    textView.setPadding(16, 16, 16, 16);

    textView.setText("Планшет коштує 180 $");

    setContentView(textView);

  }

}

Запустимо програму і перейдемо від однієї Activity до іншої:

 

 

Intent-фільтри і дії

Тепер змінимо визначення SecondActivity в файлі AndroidManifest.xml:

<activity android:name=".MainActivity">

<intent-filter>

<action android:name="android.intent.action.MAIN" />

<category android:name="android.intent.category.LAUNCHER" />

</intent-filter>

</activity>

<activity android:name=".SecondActivity">

<intent-filter>

<action android:name="com.SHOW_SECOND_ACTIVITY" />

<category android:name="android.intent.category.DEFAULT" />

</intent-filter>

</activity>

Тут до SecondActivity доданий intent-фільтр. У ньому елемент action вказує на дію, яку треба виконати для запуску activity. В даному випадку назва дії -

"com.SHOW_SECOND_ACTIVITY" - вона може бути довільною. І також за допомогою елемента category визначена категорія, в даному випадку

це "android.intent.category.DEFAULT".

Тепер для запуску SecondActivity змінимо код MainActivity:

package com.example.activityapplication;

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;

import android.os.Bundle;

import android.view.View;

public class MainActivity extends AppCompatActivity {

  public static final String ACTION = "com.SHOW_SECOND_ACTIVITY";

  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.activity_main);

  }

  public void onClick(View view) {

    Intent intent = new Intent(ACTION);

    startActivity(intent);

  }

}

Знову ж SecondActivity запускається за допомогою методу startActivity, але при створенні об'єкта Intent в його конструктор передається назва дії, яку

визначено в AndroidManifest.xml.

 

Передача даних між Activity. Серіалізація.

Для передачі даних між двома Activity використовується об'єкт Intent. Через його метод putExtra () можна додати ключ і пов'язане з ним значення.

Наприклад, передача з поточної activity в SecondActivity рядку "Hello World" з ключем "hello":

// створення об'єкта Intent для запуску SecondActivity 
Intent intent = new Intent(this, SecondActivity.class);

// передача об'єкта з ключем "hello" та значенням "Hello World" 
intent.putExtra("hello", "Hello World");

// запуск SecondActivity 
startActivity(intent);

Для передачі даних застосовується метод putExtra (), який в якості значення дозволяє передати дані найпростіших типів - String, int, float, double, long,

short, byte, char, масиви цих типів, або об'єкт інтерфейсу Serializable.

Щоб отримати відправлені дані при завантаженні SecondActivity, можна скористатися методом get (), в який передається ключ об'єкта:

Bundle arguments = getIntent ().GetExtras ();

String name = arguments.get ( "hello"). ToString (); // Hello World

Залежно від типу даних, при їх отриманні ми можемо використовувати ряд методів об'єкта Bundle. Всі вони в якості параметра приймають ключ

об'єкта. Основні з них:

get (): універсальний метод, який повертає значення типу Object. Відповідно поле отримання даного значення необхідно перетворити до потрібного

типу

getString (): повертає об'єкт типу String getInt (): повертає значення типу int getByte (): повертає значення типу byte

getChar (): повертає значення типу char getShort (): повертає значення типу short getLong (): повертає значення типу long getFloat (): повертає значення

типу float getDouble (): повертає значення типу double getBoolean (): повертає значення типу boolean getCharArray (): повертає масив об'єктів char

getIntArray (): повертає масив об'єктів int getFloatArray (): повертає масив об'єктів float

getSerializable (): повертає об'єкт інтерфейсу Serializable

Нехай у нас в проекті буде визначено дві activity: MainActivity і SecondActivity. У коді SecondActivity визначимо отримання даних:

package com.example.serializeapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.widget.TextView;

public class SecondActivity extends AppCompatActivity {
  @Override

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    TextView textView = new TextView(this);
    textView.setTextSize(20);
    textView.setPadding(16, 16, 16, 16);

    Bundle arguments = getIntent().getExtras();
    if (arguments != null) {

      String name = arguments.get("name").toString();

      String company = arguments.getString("company");
      int price = arguments.getInt("price");

      textView.setText("Name: " + name + "\nCompany: " + company + "\nPrice: " + price);

    }

    setContentView(textView);

  }

}

В даному випадку в SecondActivity отримуємо всі дані з об'єкта Bundle і виводимо їх в текстове поле TextView. Передбачається, що в дану activity

будуть передаватися три елементи - два рядки з ключами name і company і число з ключем price.

Тепер визначимо передачу в SecondActivity даних. Наприклад, визначимо для MainActivity наступний інтерфейс в файлі activity_main.xml:

<?xml version="1.0" encoding="utf-8"?><LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent"

android:orientation="vertical" 
android:padding="5dp"><TextView

android:layout_width="match_parent" 
android:layout_height="20dp"

android:text="Name:" /><EditText

android:id="@+id/name" 
android:layout_width="match_parent" 
android:layout_height="40dp"/><TextView

android:layout_width="match_parent" 
android:layout_height="20dp" 
android:text="Company:" /><EditText

android:id="@+id/company" 
android:layout_width="match_parent" 
android:layout_height="40dp" /><TextView

android:layout_width="match_parent" 
android:layout_height="20dp" 
android:text="Price:" /><EditText

android:id="@+id/price" 
android:layout_width="match_parent" 
android:layout_height="40dp" /><Button

android:id="@+id/btn" 
android:layout_width="wrap_content" 
android:layout_height="wrap_content" 
android:onClick="onClick" 
android:text="Save"/></LinearLayout>

Тут визначені три текстових поля для введення даних і кнопка. У класі MainActivity визначимо наступний вміст:

package com.example.serializeapp;
import android.content.Intent;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

  }

  public void onClick(View v) {

    final EditText nameText = findViewById(R.id.name);
    final EditText companyText = findViewById(R.id.company);
    final EditText priceText = findViewById(R.id.price);

    String name = nameText.getText().toString();
    String company = companyText.getText().toString();

    int price = Integer.parseInt(priceText.getText().toString());

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtra("name", name);
    intent.putExtra("company", company);
    intent.putExtra("price", price);

    startActivity(intent);

  }

}

У обробнику натиснення кнопки отримуємо введені в текстові поля EditText дані і передаємо їх в об'єкт Intent за допомогою методу putExtra (). Потім

запускаємо SecondActivity.

В результаті при натисканні на кнопку запуститься SecondActivity, яка отримає деякі введені в текстові поля дані.

 

Передача складних об'єктів

В наведеному вище прикладі передавалися прості дані - числа, рядки. Але також ми можемо передавати складніші дані. У цьому випадку

використовується механізм серіалізації.

Наприклад, нехай у нас в проекті буде визначено клас Product:

package com.example.serializeapp;
import java.io.Serializable;

public class Product implements Serializable {

  private String name;
  private String company;
  private int price;

  public Product(String name, String company, int price) {
    this.name = name;

    this.company = company;
    this.price = price;

  }

  public String getName() {
    return name;

  }

  public void setName(String name) {

    this.name = name;

  }

  public String getCompany() {
    return company;

  }

  public void setCompany(String company) {
    this.company = company;

  }

  public int getPrice() {
    return price;

  }

  public void setPrice(int price) {
    this.price = price;

  }

}

Варто зазначити, що даний клас реалізує інтерфейс Serializable. Тепер змінимо код MainActivity:

package com.example.serializeapp;

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.view.View;
import android.widget.EditText;

public class MainActivity extends AppCompatActivity {
  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

  }

  public void onClick(View v) {

    final EditText nameText = findViewById(R.id.name);
    final EditText companyText = findViewById(R.id.company);
    final EditText priceText = findViewById(R.id.price);

    String name = nameText.getText().toString();
    String company = companyText.getText().toString();

    int price = Integer.parseInt(priceText.getText().toString());
    Product product = new Product(name, company, price);

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtra(Product.class.getSimpleName(), product);
    startActivity(intent);

  }

}

Тепер замість трьох розрізнених даних передається один об'єкт Product. Як ключ використовується результат методу Product.class.getSimpleName (),

який по суті повертає назву класу.

І змінимо клас SecondActivity:

package com.example.serializeapp;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.widget.TextView;

public class SecondActivity extends AppCompatActivity {

  @Override

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    TextView textView = new TextView(this);
    textView.setTextSize(20);
    textView.setPadding(16, 16, 16, 16);

    Bundle arguments = getIntent().getExtras();
    final Product product;
    if (arguments != null) {

      product = (Product) arguments.getSerializable(Product.class.getSimpleName());

      textView.setText("Name: " + product.getName() + "\nCompany: " + product.getCompany() + "\nPrice: " + String.valueOf(product.getPrice()));

    }

    setContentView(textView);

  }

}

Для отримання даних застосовується метод getSerializable (), оскільки клас Product реалізує інтерфейс Serializable. Таким чином, ми можемо передати

один єдиний об'єкт замість набору розрізнених даних.

 

Отримання результату з Activity

У минулій темі було розглянуто як викликати нову Activity і передавати їй деякі дані. Але ми можемо не тільки передавати дані тій activity, яку

запускаємо, а й очікувати від неї певного результату роботи.

Наприклад, нехай у нас в проекті будуть дві activity: MainActivity і SecondActivity. А для кожної activity є свій файл інтерфейсу: activity_main.xml і

activity_second.xml.

У минулій темі ми викликали нову activity за допомогою методу startActivity (). Для отримання ж результату роботи activity, що запускається необхідно

використовувати метод startActivityForResult (Intent intent, int requestCode). Цей метод приймає два параметри: Intent передає в дану activity дані, а

другий параметр requestCode вказує на цілочисельний код запиту. Розглянемо його використання на прикладі.

Так, визначимо в файлі activity_main.xml наступний інтерфейс:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/activity_main"

android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp"
android:orientation="vertical">

<TextView

android:id="@+id/textView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="Вкажіть вік"
android:textSize="22dp"/>

<EditText

android:id="@+id/ageBox"
android:inputType="number"
android:layout_width="match_parent"
android:layout_height="wrap_content" />

<Button

android:id="@+id/navButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Надіслати" android:onClick="onClick" />

</LinearLayout>

Для введення даних тут визначено елемент EditText, а для відправки - кнопка. Визначимо в класі MainActivity запуск другої activity:

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.view.View;
import android.widget.EditText;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {
  static final String AGE_KEY = "AGE";

  static final String ACCESS_MESSAGE = "ACCESS_MESSAGE";

  private static final int REQUEST_ACCESS_TYPE = 1;

  TextView textView;

  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    textView = (TextView) findViewById(R.id.textView);

  }

  public void onClick(View view) {

    // Отримуємо введений вік

    EditText ageBox = (EditText) findViewById(R.id.ageBox);
    String age = ageBox.getText().toString();

    Intent intent = new Intent(this, SecondActivity.class);
    intent.putExtra(AGE_KEY, age);
    startActivityForResult(intent, REQUEST_ACCESS_TYPE);

  }

  @Override

  public void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_ACCESS_TYPE) {

      if (resultCode == RESULT_OK) {

        String accessMessage = data.getStringExtra(ACCESS_MESSAGE);
        textView.setText(accessMessage);

      } else {

        textView.setText("Помилка доступу");

      }

    } else {

      super.onActivityResult(requestCode, resultCode, data);

    }

  }

}

У обробнику натиснення кнопки onClick () отримуємо введений в текстове поле вік, додаємо його в об'єкт Intent з ключем AGE_KEY і запускаємо

SecondActivity за допомогою методу startActivityForResult. Причому числовий код запиту представляє константу REQUEST_ACCESS_TYPE. Тут не так

важливо, яке значення передавати в якості результату, але використовуючи це значення, ми потім можемо виконати обробку отриманої відповіді від

SecondActivity, особливо якщо в різних ситуаціях застосовується кілька числових кодів запиту.

Для отримання і обробки результату, отриманого від SecondActivity, необхідно перевизначити метод onActivityResult. Цей метод приймає три

параметри:

В даному випадку в методі onActivityResult () виводяться отримані дані в елемент TextView. Далі перейдемо до SecondActivity і визначимо в файлі

activity_second.xml набір кнопок:

<?xml version="1.0" encoding="utf-8"?>

<LinearLayout

xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@+id/activity_second" 
android:layout_width="match_parent" 
android:layout_height="match_parent"


android:orientation="vertical" 
android:padding="16dp">

<TextView


android:id="@+id/ageView" 
android:textSize="22sp" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" />

<Button


android:id="@+id/button1"


android:text="Відкрити доступ" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:onClick="onButton1Click"/>

<Button


android:id="@+id/button2"
android:text="Відхилити доступ" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:onClick="onButton2Click" />

<Button


android:id="@+id/button3"
android:text="Вік недійсний" 
android:layout_width="match_parent" 
android:layout_height="wrap_content" 
android:onClick="onButton3Click" />

<Button


android:id="@+id/cancel" 
android:text="Скасувати"
android:layout_width="match_parent"
android:layout_height="wrap_content" 
android:onClick="onCancelClick" />

</LinearLayout>

А в класі SecondActivity визначимо обробники для цих кнопок:

import android.content.Intent;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import android.view.View;
import android.widget.TextView;

public class SecondActivity extends AppCompatActivity {
  @Override

  protected void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_second);

    Bundle extras = getIntent().getExtras();
    if (extras! = null) {

      TextView ageView = (TextView) findViewById(R.id.ageView);
      String age = extras.getString(MainActivity.AGE_KEY);

      ageView.setText("Вік:" + age);

    }

  }

  public void onCancelClick(View v) {
    setResult(RESULT_CANCELED);
    finish();

  }

  public void onButton1Click(View v) {
    sendMessage("Доступ дозволений");

  }

  public void onButton2Click(View v) {
    sendMessage("Доступ заборонено");

  }

  public void onButton3Click(View v) {

    sendMessage("Неприпустимий вік");

  }

  private void sendMessage(String message) {

    Intent data = new Intent();
    data.putExtra(MainActivity.ACCESS_MESSAGE, message);
    setResult(RESULT_OK, data);

    finish();

  }

}

Три кнопки викликають метод sendMessage (), в який передають відповідь. Це і буде те

повідомлення, яке отримає MainActivity в методі onActivityResult.

Для повернення результату необхідно викликати метод setResult (), в який передається два параметри:

Після виклику методу setResult () потрібно викликати метод finish, який знищить поточну activity.

Одна кнопка викликає обробник onCancelClick (), в якому передається в setResult тільки код результату - RESULT_CANCELED.

Тобто умовно кажучи, ми отримуємо в SecondActivity введений в MainActivity вік і за допомогою натискання певної кнопки повертаємо деякий

результат у вигляді повідомлення.

Залежно від натиснутої кнопки на SecondActivity ми будемо отримувати різні результати в

MainActivity:

 

Взаємодія між Activity

У минулих темах ми розглянули життєвий цикл activity і запуск нових activity за допомогою об'єкта Intent. Тепер розглянемо деякі особливості взаємодії

між activity в одному додатку. Припустимо, у нас є три activity: MainActivity, SecondActivity і ThirdActivity.

За допомогою Intent, наприклад, після натискання кнопки MainActivity запускається SecondActivity:

Intent intent = new Intent (this, SecondActivity.class);

startActivity (intent);

На SecondActivity теж є кнопка, яка запускає ThirdActivity:

Intent intent = new Intent (this, ThirdActivity.class);

startActivity (intent);

На ThirdActivity також є кнопка, яка повертається до першої activity - MainActivity:

Intent intent = new Intent (this, MainActivity.class);

startActivity (intent);

 

Якщо ми послідовно запустимо всі activity: з головної MainActivity запустимо SecondActivity, з SecondActivity - ThirdActivity, то в результаті у нас

складеться наступний стек activity:

Якщо після цього з ThirdActivity ми захочемо звернутися до MainActivity, то метод startActivity () запустить новий об'єкт MainActivity (а не повернеться

до вже існуючого), і стек вже буде виглядати наступним чином:

Тобто у нас будуть дві незалежні копії MainActivity. Такий стан небажаний, якщо ми просто хочемо перейти до існуючої. І цей момент треба

враховувати. Якщо ми натиснемо на кнопку Back (Назад), то ми зможемо перейти до попередньої activity в стеку.

Щоб вийти з цієї ситуації, ми можемо використовувати прапор Intent.FLAG_ACTIVITY_REORDER_TO_FRONT:

Intent intent = new Intent (this, MainActivity.class);

intent.addFlags (Intent.FLAG_ACTIVITY_REORDER_TO_FRONT);

startActivity (intent);

У цьому випадку після переходу з ThirdActivity до MainActivity стек буде виглядати наступним чином:

Якщо ж нам просто треба перейти з ThirdActivity до MainActivity, так, якби ми перейшли назад за допомогою кнопки Back, то ми можемо

використовувати прапори Intent.FLAG_ACTIVITY_CLEAR_TOP і Intent.FLAG_ACTIVITY_SINGLE_TOP:

Intent intent = new Intent (this, MainActivity.class);

intent.addFlags (Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_SINGLE_TOP);

startActivity (intent);

У цьому випадку після переходу з ThirdActivity до MainActivity стек буде повністю очищений, і там залишиться одна MainActivity.


© 2006—2023 СумДУ