2. Механизм намерений (Intent). Явные и неявные намерения. Контекст.

Так как Activity относится к основным компонентам приложения а также является контекстом (что это, мы разберем позже), для открытия окна мы не можем просто создать объект-наследник AppCompatActivity и вызвать метод для отображения окна на экране устройства, мы должны запрашивать это действие у операционной системы.

Для того, чтобы сделать запрос на открытие окна, мы должны сформировать объект сообщения

Для того, чтобы запустить любой другой экземпляр Activity, необходимо вызвать метод startActivity(Intent intent) любого экземпляра Context (класс Activity наследует Context, поэтому он связан с ним отношением is a - экземпляр Activity является экземпляром Context). Экземпляр Intent, в свою очередь, требует передать экземпляр Context в первом параметре и ссылку на запускаемый класс Activity:

Для открытия окна мы должны воспользоваться механизмом намерений (Intent).

Intent - это механизм для описания одной операции, а также способ обмена сообщениями между компонентами приложения.

Более подробно читайте про механизм намерений здесь и здесь.

Для запуска нового Activity необходим создать объект типа Intent, указать полное имя класса компонента (в данном случае Activity), другие параметры (если требуется) и передать объект методу Context.startActivity(). Далее операционная система запускает компонент, который указан в объекте Intent.

Добавим в макет MainActivity кнопку и напишем слушатель, который будет запускать новое Activity по нажатию на эту кнопку

MainActivity.java
public class MainActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

    public void handleClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent);
    }
}

Проверим работу приложения

Типы объектов Intent

Существуют явные и неявные объекты Intent:

  • явные намерения - указывают компонент, который требуется запустить, по имени (указывается полное имя класса). Явные намерения обычно используются для запуска компонент из собственного приложения, так как известно имя класса Acitvity или службы, которую необходимо запустить;

  • неявные намерения - не содержат имени конкретного компонента. Вместо этого они в целом объявляют действие, которое требуется выполнить, что дает возможность компоненту из другого приложения обработать запрос. Например, если требуется указать пользователю место на карте, то с помощью неявного объекта Intent можно запросить, чтобы это сделало другое приложение, в котором такая возможность предусмотрена.

Схематичная работа механизма неявных намерений представлена на изображении ниже

[1] Activity А создает объект Intent с описанием действия и передает его методу startActivity(); [2] Система Android ищет во всех приложениях фильтры Intent, которые соответствуют данному объекту Intent. Когда приложение с подходящим фильтром найдено; [3] система запускает соответствующую операцию (Activity B), вызвав ее метод onCreate() и передав ему объект Intent.

Более подробную информацию о намерениях и механизме их работы можно найти здесь.

Рассмотрим еще раз код открытия второго окна

MainActivity.java
    public void handleClick(View view) {
        Intent intent = new Intent(this, SecondActivity.class);
        startActivity(intent);
    }

Обратите внимание на первый параметр конструктора класса Intent. Он указывает на наш текущий класс и означает «ссылка на контекст». Разберемся, что такое контекст и зачем он нужен.

Контекст в Android

Контекст (Context) - это базовый абстрактный класс, реализация которого обеспечивается системой Android. Этот класс имеет методы для доступа к специфичным для конкретного приложения ресурсам и классам и служит для выполнения операций на уровне приложения, таких, как запуск активностей, отправка широковещательных сообщений, получение намерений и прочее. От класса Context наследуются такие крупные и важные классы, как Application, Activity и Service, поэтому все его методы доступны из этих классов.

Контекст является базовым классом для классов Application, Activity и Service, а значит его методы входят в их состав. Именно поэтому для передачи контекста в качестве параметра можно использовать как ссылку на сам контекст (getBaseContext), так и ссылки на наследуемые классы (getApplicationContext, getContext, this, MainActivity.this, getActivity).

Иными словами, контекст – это мостик, связующее звено между вашим приложением и операционной системой. Классы, которые реализуют контекст, создаются операционной системой. Так как Activity является подклассом контекста, то мы можем использовать ссылку на объект Activity (например, через ключевое слово this) как контекст. Также, это справедливо для классов Application и Service, которые мы рассмотрим в следующих лабораторных работах.

Получить доступ к ресурсам приложения, записать или считать файл, получить доступ к компонентам устройства (экран, звук, датчики, кнопки), работать с уведомлениями – для всего этого нужен контекст. В данном случае, нам нужен контекст, чтобы отправить ОС намерение открыть второе окно.

Отличная подробная статья на английском языке - здесь. Также, подробнее читайте про контекст здесь, здесь и здесь.

Передача данных между Activity

Важно понимать, что созданием, инициализацией и настройкой экземпляров Activity, которые вы покажете своему пользователю, будет заниматься система - их нельзя создать с помощью ключевого слова new, настроить или иным образом изменить при запуске. Мы передаем системе экземпляр Intent, который определяет, какой класс Activity мы хотим использовать, а система делает все остальное. По этой причине нет никакой возможности изменять свойства и вызывать методы экземпляра Activity непосредственно во время запуска.

Но коль скоро нельзя изменять свойства и вызывать методы экземпляра Activity непосредственно в процессе запуска, как тогда передать ему информацию?

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

Java
// предполагается, что запускающий Ac ti vi ty находится в области видимости
Intent intent = new Intent(this, AnotherAc ti vi ty.class);
intent.putExtra("id", 10);
startAc ti vi ty(intent);
Kotlin
// предполагается, что запускающий Ac ti vi ty находится в области видимости
Intent intent = Intent(this, AnotherAc ti vi ty::class.java);
intent.putExtra("id", 10)
startAc ti vi ty(intent)

Экземпляр Intent, запустивший Activity, доступен через метод getIntent()

Java
public class MyAc ti vi ty extends Ac ti vi ty {
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Intent intent = getIntent();
int id = intent.getIntExtra("id", 0);
Log.d("MyTag", "id: " + id);
}
}
Kotlin
class MyAc ti vi ty : Ac ti vi ty() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val id = intent.getIntExtra("id", 0)
Log.d("MyTag", "id: $id")

Этот способ идеально подходит для передачи простых данных, таких как числовой идентификатор или URL, но не подходит для больших данных (на пример, сериализованных классов Java или даже просто больших строк со сложными экземплярами классов в формате JSON). Эти данные содержатся в определенном хранилище системного уровня, имеющем ограниченный объем 1 Мбайт, и могут использоваться всеми процессами на устройстве. Вот вы- держка из документации с описанием Bundle API:

Буфер транзакций Binder имеет ограниченный размер, в настоящее время 1 Мбайт, и используется всеми транзакциями, выполняемыми процес- сом. Поскольку это ограничение определяется на уровне процесса, а не на уровне активности, этот буфер используют все транзакции привязки в приложении, такие как onSaveInstanceState, startActivity и любые взаимодействия с системой.

Чтобы передать сложную информацию во вновь созданный экземпляр Activity, нужно сохранить ее на диск перед запуском нового Activity, чтобы ее можно было прочитать обратно после создания этого Activity, или передать ссылку на «глобальную» структуру данных. Часто роль такой структуры играет простая переменная уровня класса (т. е. статическая), но в этом случае вам придется учитывать все недостатки, свойственные статическим переменным.

Инженеры Android в свое время рекомендовали использовать статический член Map<WeakReferences> в служебном классе или в экземпляре Application (всегда доступном из любого экземпляра Context через Context.getApplicationContext()). Важно отметить, что пока приложение работает, экземпляр Application будет доступен, и, как утверждают некоторые, при его использовании вы никогда не столкнетесь с утечками памяти.

Last updated