From c921ee10a3c6e057637f6fc84e174146aa582b32 Mon Sep 17 00:00:00 2001 From: David Friedman Date: Mon, 29 Jun 2015 22:14:16 -0700 Subject: Docs: Localizations of Android dev on-boarding training docs. Bug: 20503574 Change-Id: I348ca743dc106eef7cca5ff8d9855398fc3b3b54 --- .../ru/training/basics/activity-lifecycle/index.jd | 72 ++++ .../training/basics/activity-lifecycle/pausing.jd | 147 ++++++++ .../basics/activity-lifecycle/recreating.jd | 178 ++++++++++ .../training/basics/activity-lifecycle/starting.jd | 285 ++++++++++++++++ .../training/basics/activity-lifecycle/stopping.jd | 187 ++++++++++ .../ru/training/basics/data-storage/databases.jd | 317 +++++++++++++++++ .../intl/ru/training/basics/data-storage/files.jd | 379 +++++++++++++++++++++ .../intl/ru/training/basics/data-storage/index.jd | 57 ++++ .../basics/data-storage/shared-preferences.jd | 120 +++++++ .../intl/ru/training/basics/intents/filters.jd | 236 +++++++++++++ .../intl/ru/training/basics/intents/index.jd | 62 ++++ .../intl/ru/training/basics/intents/result.jd | 178 ++++++++++ .../intl/ru/training/basics/intents/sending.jd | 256 ++++++++++++++ 13 files changed, 2474 insertions(+) create mode 100644 docs/html-intl/intl/ru/training/basics/activity-lifecycle/index.jd create mode 100644 docs/html-intl/intl/ru/training/basics/activity-lifecycle/pausing.jd create mode 100644 docs/html-intl/intl/ru/training/basics/activity-lifecycle/recreating.jd create mode 100644 docs/html-intl/intl/ru/training/basics/activity-lifecycle/starting.jd create mode 100644 docs/html-intl/intl/ru/training/basics/activity-lifecycle/stopping.jd create mode 100644 docs/html-intl/intl/ru/training/basics/data-storage/databases.jd create mode 100644 docs/html-intl/intl/ru/training/basics/data-storage/files.jd create mode 100644 docs/html-intl/intl/ru/training/basics/data-storage/index.jd create mode 100644 docs/html-intl/intl/ru/training/basics/data-storage/shared-preferences.jd create mode 100644 docs/html-intl/intl/ru/training/basics/intents/filters.jd create mode 100644 docs/html-intl/intl/ru/training/basics/intents/index.jd create mode 100644 docs/html-intl/intl/ru/training/basics/intents/result.jd create mode 100644 docs/html-intl/intl/ru/training/basics/intents/sending.jd (limited to 'docs/html-intl/intl/ru') diff --git a/docs/html-intl/intl/ru/training/basics/activity-lifecycle/index.jd b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/index.jd new file mode 100644 index 0000000..b8de11e --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/index.jd @@ -0,0 +1,72 @@ +page.title=Управление жизненным циклом операций +page.tags=жизненный цикл операций +helpoutsWidget=true + +trainingnavtop=true +startpage=true + +@jd:body + +
+
+ + +

Необходимые знания и компоненты

+ + + +

См. также:

+ + + +

Попробуйте сами

+ +
+ Загрузить демонстрацию +

ActivityLifecycle.zip

+
+ +
+
+ +

Во время навигации пользователя по вашему приложению экземпляры +{@link android.app.Activity} внутри приложения переключаются между разными состояниями их +жизненного цикла Например, при первом запуске +операции она получает высокий приоритет в системе и привлекает внимание +пользователя. Во время этого процесса система Android вызывает серию методов жизненного цикла +операции, позволяя настроить пользовательский интерфейс и другие компоненты. Если пользователь выполняет +действие, запускающее другую операцию, или переключается на другое приложение, система вызывает другой набор +методов жизненного цикла для операции, поскольку она переносится на фоновый уровень (операция больше не +отображается, но экземпляр и состояние остаются без изменений).

+ +

В методах обратного вызова жизненного цикла можно декларировать поведение операции, когда +пользователь прекращает и снова запускает операцию. Например, если вы разрабатываете проигрыватель потокового видео, +то можете сделать так, чтобы при переключении пользователя на другое приложение видео ставилось на паузу, +а сетевое соединение разрывалось. После возврата пользователя проигрыватель может снова подключиться к сети, и пользователь сможет возобновить воспроизведение +видео с того же самого места.

+ +

В этом учебном курсе разъясняются важные методы обратного вызова жизненного цикла, которые получает каждый экземпляр {@link +android.app.Activity}, и описывается как их использовать, чтобы операция выполнялась так, как этого ожидает +пользователь, и не потребляла системные ресурсы, когда они ей не нужны.

+ +

Уроки

+ +
+
Запуск операции
+
Из этого урока вы узнаете об основах жизненного цикла операций, способах запуска вашего приложения пользователями и вариантах +создания базовых операций.
+
Приостановка и возобновление операции
+
Вы узнаете, что происходит во время приостановки операции (окно операции частично затемнено) и возобновления операции, +и что следует делать во время подобных изменений состояния.
+
Остановка и перезапуск операции
+
В этом уроке рассказывается о том, что происходит, когда пользователь полностью прекращает операцию, а потом возвращается к ней.
+
Повторное создание операции
+
Вы узнаете, что происходит при полном прекращении операции, и как можно восстановить ее состояние +в случае необходимости.
+
+ diff --git a/docs/html-intl/intl/ru/training/basics/activity-lifecycle/pausing.jd b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/pausing.jd new file mode 100644 index 0000000..c483780 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/pausing.jd @@ -0,0 +1,147 @@ +page.title=Приостановка и возобновление операции +page.tags=жизненный цикл операции +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +
+
+ +

Содержание этого урока

+
    +
  1. Приостановка операции
  2. +
  3. Возобновление операции
  4. +
+ +

См. также:

+ + +

Попробуйте сами

+ +
+ Загрузить демонстрацию +

ActivityLifecycle.zip

+
+ +
+
+ +

При обычном использовании приложения выполняемая на экране операция иногда закрывается другими +визуальными компонентами, в результате чего операция приостанавливается. Например, при открытии полупрозрачной +операции (например диалогового окна) предыдущая операция приостанавливается. Пока +операция остается частично видимой, но не остается в фокусе, она остается на паузе.

+ +

Однако когда операция полностью закрывается и становится невидимой, она останавливается (об этом будет рассказано +на следующем уроке).

+ +

Когда операция приостанавливается, система вызывает метод {@link +android.app.Activity#onPause onPause()} на {@link android.app.Activity}, что позволяет +остановить текущие действия, которые не должны продолжаться во время паузы (например воспроизведение видео), или сохранять +любую информацию, которая должна постоянно сохраняться в случае выхода пользователя из приложения. Если +пользователь возвращается к операции после паузы, система возобновляет ее и вызывает метод +{@link android.app.Activity#onResume onResume()}.

+ +

Примечание. Когда операция получает вызов {@link +android.app.Activity#onPause()}, это может указывать, что операция будет приостановлена совсем ненадолго +и пользователь сможет к ней вернуться. Однако обычно это является первым признаком +выхода пользователя из операции.

+ + +

Рисунок 1. Когда полупрозрачная операция закрывает +вашу операцию, система вызывает {@link android.app.Activity#onPause onPause()} и операция +переходит в состояние паузы (1). Если пользователь возвращается к операции, находящейся в состоянии паузы, +система вызывает {@link android.app.Activity#onResume onResume()} (2).

+ + +

Приостановка операции

+ +

Когда система вызывает {@link android.app.Activity#onPause()} для операции, это +технически означает, что операция остается частично видимой. Однако чаще всего это означает, что +пользователь покидает операцию, и вскоре она войдет в состояние остановки. Обратный вызов + {@link android.app.Activity#onPause()} обычно следует использовать для следующих целей:

+ + + +

Например, если ваше приложение использует {@link android.hardware.Camera}, метод +{@link android.app.Activity#onPause()} подойдет для его освобождения.

+ +
+@Override
+public void onPause() {
+    super.onPause();  // Always call the superclass method first
+
+    // Release the Camera because we don't need it when paused
+    // and other activities might need to use it.
+    if (mCamera != null) {
+        mCamera.release()
+        mCamera = null;
+    }
+}
+
+ +

Обычно не следует использовать {@link android.app.Activity#onPause()} для сохранения +пользовательских изменений (например введенной в форму личной информации) в постоянном хранилище. Изменения +пользователя нужно сохранять в постоянном хранилище {@link android.app.Activity#onPause()} +только в тех случаях, когда пользователи ожидают автоматического сохранения изменений (например при составлении черновика электронного письма). +Однако в состоянии {@link +android.app.Activity#onPause()} следует избегать операций с высокой нагрузкой на процессор, например выполнения записи в базу данных, поскольку это может замедлить видимый +переход к следующей операции (вместо этого следует выполнять операции завершения работы с высокой нагрузкой при +{@link android.app.Activity#onStop onStop()}).

+ +

Количество операций, выполняемых с помощью метода {@link android.app.Activity#onPause +onPause()}, должно быть не очень большим, чтобы пользователь мог быстро переключаться на следующую +задачу при фактической остановке вашей операции.

+ +

Примечание. При приостановке операции экземпляр {@link +android.app.Activity} остается в памяти и заново вызывается при возобновлении операции. +Не нужно заново инициализировать компоненты, созданные с использованием любого из методов обратного вызова для возобновления +операции.

+ + + +

Возобновление операции

+ +

Когда пользователь возобновляет операцию после паузы, система вызывает метод {@link +android.app.Activity#onResume()}.

+ +

Учтите, что система вызывает этот метод каждый раз, когда ваша операция выполняется на экране, +в том числе и при первом ее запуске. В связи с этим, нужно реализовать метод {@link +android.app.Activity#onResume()} для инициализации компонентов, освобождаемых в состоянии {@link +android.app.Activity#onPause()}, и выполнения других операций инициализации, которые должны происходить каждый раз при возобновлении операции +(например при запуске анимированных элементов и компонентов, которые используются +только когда пользователь следит за операцией на экране).

+ +

Следующий пример {@link android.app.Activity#onResume()} дополняет +пример {@link android.app.Activity#onPause()} выше. В нем инициализируется камера, +которая освобождается на время приостановки операции.

+ +
+@Override
+public void onResume() {
+    super.onResume();  // Always call the superclass method first
+
+    // Get the Camera instance as the activity achieves full user focus
+    if (mCamera == null) {
+        initializeCamera(); // Local method to handle camera init
+    }
+}
+
+ + + + + + + diff --git a/docs/html-intl/intl/ru/training/basics/activity-lifecycle/recreating.jd b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/recreating.jd new file mode 100644 index 0000000..acb89fa --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/recreating.jd @@ -0,0 +1,178 @@ +page.title=Воссоздание операции +page.tags=жизненный цикл операции +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +
+ +
+ +

Существуют ситуации, когда операция уничтожается в результате нормального поведения приложения. Например, это происходит, +когда пользователь нажимает кнопку Назад или когда операция подает сигнал о своем уничтожении +посредством вызова {@link android.app.Activity#finish()}. Система также может уничтожить операцию, +если она остановлена и не используется в течение длительного времени, или если для выполнения операции на экране требуется больше +системных ресурсов и системе нужно закрыть фоновые процессы для освобождения памяти.

+ +

Если операция уничтожается при нажатии пользователем кнопки Назад или завершении +операции, система считает, что экземпляр {@link android.app.Activity} исчезает навсегда, +так как такое поведение указывает, что операция больше не нужна. Однако если система уничтожает +операцию в связи с системными ограничениями (а не в процессе обычной работы приложения), хотя фактический +{@link android.app.Activity} экземпляр исчезает, система помнит о его существовании, и если +пользователь вернется к нему, система создаст новый экземпляр действия, используя набор +сохраненных данных, описывающий состояние операции на момент ее уничтожения. Сохраненные данные, используемые +системой для восстановления предыдущего состояния, называются "состоянием экземпляра" и представляют собой набор +пар "ключ-значение", хранящийся в объекте {@link android.os.Bundle}.

+ +

Внимание! Ваша операция будет уничтожаться и восстанавливаться каждый раз, +когда пользователь вращает экран. При изменении ориентации экрана система уничтожает и заново создает +активную операцию, поскольку конфигурация экрана меняется и операции может потребоваться +загрузка альтернативных ресурсов (например нового макета).

+ +

По умолчанию система использует состояние экземпляра {@link android.os.Bundle} для сохранения информации +о каждом объекте {@link android.view.View} в макете операции (например, о текстовом значении, +введенном в объект {@link android.widget.EditText}). Таким образом, если экземпляр вашей операции уничтожается и +воссоздается заново, происходит восстановление предыдущего состояния макета, +и при этом вам не нужно добавлять в приложение дополнительный код. Однако операция +может содержать больше информации о состоянии, чем вы хотите восстановить, например переменные, +отслеживающие ход выполнения операции пользователем.

+ +

Примечание. Чтобы система Android могла восстановить состояние +представлений операции, каждое представление должно иметь уникальный идентификатор, предоставляемый атрибутом +{@code +android:id}.

+ +

Для сохранения дополнительных данных о состоянии операции, необходимо +заменить метод обратного вызова {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. +Система вызывает этот метод, когда пользователь покидает операцию, +и передает ему объект {@link android.os.Bundle}, который будет сохранен в +случае, если операция будет неожиданно уничтожена. Если +системе нужно будет воссоздать экземпляр экземпляра операции, она передаст тот же объект {@link +android.os.Bundle} методам {@link android.app.Activity#onRestoreInstanceState +onRestoreInstanceState()} и {@link android.app.Activity#onCreate onCreate()}. +

+ + +

Рисунок 2. Когда система начинает останавливать операцию, она +вызывает {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} (1), чтобы вы могли указать +дополнительные данные состояния, которые нужно сохранить на случай необходимости воссоздания экземпляра {@link android.app.Activity}. +Если операция будет уничтожена, +и системе нужно будет воссоздать тот же экземпляр, она передаст данные +состояния, определенные в (1), методам {@link android.app.Activity#onCreate onCreate()} +(2) и {@link android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} +(3).

+ + + +

Сохранение состояния операции

+ +

Когда начинается остановка операции, система вызывает метод {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, чтобы операция могла сохранить информацию о состоянии с помощью набора пар +"ключ-значение". По умолчанию при реализации этого метода сохраняется информация о состоянии иерархии +представления операции, например текст в виджете {@link android.widget.EditText} или положение экрана +для {@link android.widget.ListView}.

+ +

Для сохранения дополнительной информации о состоянии операции +необходимо реализовать {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} и добавить +к объекту {@link android.os.Bundle} пары "ключ-значение". Например:

+ +
+static final String STATE_SCORE = "playerScore";
+static final String STATE_LEVEL = "playerLevel";
+...
+
+@Override
+public void onSaveInstanceState(Bundle savedInstanceState) {
+    // Save the user's current game state
+    savedInstanceState.putInt(STATE_SCORE, mCurrentScore);
+    savedInstanceState.putInt(STATE_LEVEL, mCurrentLevel);
+    
+    // Always call the superclass so it can save the view hierarchy state
+    super.onSaveInstanceState(savedInstanceState);
+}
+
+ +

Внимание! Реализацию суперкласса {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} следует вызывать во всех случаях, чтобы реализация +по умолчанию могла сохранить состояние новой иерархии.

+ + + +

Восстановление состояния операции

+ +

В случае воссоздания операции после предыдущего уничтожения сохраненное +состояние можно восстановить из {@link android.os.Bundle}, куда система +передает данные операции. Методы обратного вызова {@link android.app.Activity#onCreate onCreate()} и {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} получают один и +тот же {@link android.os.Bundle}, содержащий информацию о состоянии экземпляра.

+ +

Поскольку метод {@link android.app.Activity#onCreate onCreate()} вызывается, если +система создает новый экземпляр операции или восстанавливает предыдущий экземпляр, перед попыткой чтения необходимо убедиться, +что {@link android.os.Bundle} имеет состояние null. В этом случае +система создает новый экземпляр операции +вместо восстановления ранее уничтоженного экземпляра.

+ +

Приведем пример восстановления некоторых данных о состоянии в {@link android.app.Activity#onCreate +onCreate()}:

+ +
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState); // Always call the superclass first
+   
+    // Check whether we're recreating a previously destroyed instance
+    if (savedInstanceState != null) {
+        // Restore value of members from saved state
+        mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
+        mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
+    } else {
+        // Probably initialize members with default values for a new instance
+    }
+    ...
+}
+
+ +

Вместо восстановления состояния в {@link android.app.Activity#onCreate onCreate()} вы +можете реализовать метод {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}, который система вызывает +после метода {@link android.app.Activity#onStart()}. Система вызывает {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} только при наличии сохраненного состояния +для восстановления, и поэтому вам не нужно проверять, имеет ли {@link android.os.Bundle} значение null:

+ +
+public void onRestoreInstanceState(Bundle savedInstanceState) {
+    // Always call the superclass so it can restore the view hierarchy
+    super.onRestoreInstanceState(savedInstanceState);
+   
+    // Restore state members from saved instance
+    mCurrentScore = savedInstanceState.getInt(STATE_SCORE);
+    mCurrentLevel = savedInstanceState.getInt(STATE_LEVEL);
+}
+
+ +

Внимание! Реализацию суперкласса {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} следует вызывать во всех случаях, чтобы реализация +по умолчанию могла сохранить состояние новой иерархии.

+ +

Более подробную информацию о воссоздании операции в связи +с перезапуском во время исполнения (например при повороте экрана) можно найти в разделе Обработка изменений в режиме выполнения.

+ diff --git a/docs/html-intl/intl/ru/training/basics/activity-lifecycle/starting.jd b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/starting.jd new file mode 100644 index 0000000..3a946e2 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/starting.jd @@ -0,0 +1,285 @@ +page.title=Запуск операции +page.tags=жизненный цикл операции +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + + +
+ +
+ +

В отличие от других парадигм программирования, где приложения запускаются с использованием метода {@code main()}, система +Android запускает код в {@link android.app.Activity}экземпляре посредством активации определенных +методов обратного вызова, соответствующих определенным этапам его +жизненного цикла. Существует последовательность методов обратного вызова, которые запускают операцию и последовательность +методов обратного вызова, уничтожающих операцию.

+ +

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

+ + + +

Изучение обратных вызовов жизненного цикла

+ +

В течение цикла существования операции система вызывает базовый набор методов жизненного цикла в +последовательности, сходной с многоступенчатой пирамидой. Таким образом, каждый этап +жизненного цикла операции представляет собой отдельную ступень пирамиды. Когда система создает новый экземпляр операции, +каждый метод обратного вызова перемещает состояние действия на одну ступень вверх. Вершина пирамиды представляет собой +точку, в которой операция выполняется в экранном режиме, и пользователь может с ней взаимодействовать.

+ +

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

+ + + +

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

+ + +

В зависимости от сложности операции, некоторые +методы жизненного цикла могут не требоваться. Однако очень важно понимать все методы и реализовать их так, чтобы +приложение работало так, как этого ожидают пользователи. Правильная реализация методов жизненного цикла операции +обеспечивает нормальную работу приложения в нескольких аспектах, в том числе:

+ + + + +

Как вы узнаете на следующих уроках, в некоторых ситуациях операция +переключается между разными состояниями, как показано на рисунке 1. Однако только три +из этих состояний могут быть статичными. Это означает, что операция может существовать относительно длительное +время только в одном из этих трех состояний.

+
+
Возобновление
+
В этом состоянии операция выполняется в экранном режиме, и пользователь может с ней взаимодействовать. +Иногда это состояние также называется рабочим состоянием.
+
Приостановка
+
В этом состоянии операция частично закрывается другой операцией — в экранном режиме +эта другая операция или является полупрозрачной, или не закрывает экран целиком. Приостановленная операция +не получает команд пользователя и не может выполнять код. +
Остановка
+
В этом состоянии операция полностью скрыта и невидима для пользователя. Она считается находящейся в +фоновом режиме. В состоянии остановки сохраняется экземпляр операции и вся его информация +о состоянии, например, переменных, однако операция не может выполнять код.
+
+ +

Другие состояния (создание и запуск) являются переходными, и система быстро переходит от них +к следующим состояниям посредством вызова следующего метода обратного вызова в жизненном цикле. Таким образом, после вызова +{@link android.app.Activity#onCreate onCreate()} система быстро вызывает {@link +android.app.Activity#onStart()}, а затем сразу же вызывает {@link +android.app.Activity#onResume()}.

+ +

Так выглядит базовый жизненный цикл операции. Теперь рассмотрим определенные виды +поведения в жизненном цикле.

+ + + +

Указание операции, запускающей приложение

+ +

Когда пользователь выбирает значок приложения на главном экране, система вызывает метод {@link +android.app.Activity#onCreate onCreate()} для {@link android.app.Activity} в вашем приложении +в соответствии с тем, какую операцию вы задекларировали как операцию запуска (или основную операцию). Эта операция выступает +основной точкой входа в пользовательский интерфейс вашего приложения.

+ +

Для определения основной операции вы можете использовать файл манифеста Android {@code AndroidManifest.xml}, +который находится в корневом каталоге вашего проекта.

+ +

Основная операция приложения должна декларироваться в манифесте с помощью фильтра {@code +<intent-filter>}, включающего действие {@link +android.content.Intent#ACTION_MAIN MAIN} и категорию +{@link android.content.Intent#CATEGORY_LAUNCHER LAUNCHER}. Например:

+ +
+<activity android:name=".MainActivity" android:label="@string/app_name">
+    <intent-filter>
+        <action android:name="android.intent.action.MAIN" />
+        <category android:name="android.intent.category.LAUNCHER" />
+    </intent-filter>
+</activity>
+
+ +

Примечание. При создании нового проекта Android с помощью инструментов +Android SDK файлы проекта по умолчанию включают класс {@link android.app.Activity}, который декларируется в +манифесте с помощью этого фильтра.

+ +

Если для одной из операций не декларировано действие {@link android.content.Intent#ACTION_MAIN MAIN} или категория +{@link android.content.Intent#CATEGORY_LAUNCHER LAUNCHER}, значок +приложения не будет отображатья в списке приложений на главном экране.

+ + + +

Создание нового экземпляра

+ +

Большинство приложений содержат различные операции, позволяющие пользователю выполнять различные действия. +Как для основных операций, создаваемых при нажатии на значок приложения, так +и для других операций, которыми приложение реагирует на действия пользователя, система создает +каждый новый экземпляр {@link android.app.Activity} посредством вызова его метода {@link +android.app.Activity#onCreate onCreate()}.

+ +

Вы должны реализовать метод {@link android.app.Activity#onCreate onCreate()} для выполнения базовой +логики запуска приложения, которое должно производиться только один раз для всего срока существования операции. Например, +ваша реализация {@link android.app.Activity#onCreate onCreate()} должна содержать определение пользовательского +интерфейса и возможно создавать экземпляры некоторых переменных уровня класса.

+ +

Например, в следующем примере метода {@link android.app.Activity#onCreate onCreate()} +показан код, выполняющий фундаментальную настройку операции, включая +декларирование пользовательского интерфейса (определен в файле макета XML), определение составных переменных +и частичную настройку пользовательского интерфейса.

+ +
+TextView mTextView; // Member variable for text view in the layout
+
+@Override
+public void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    // Set the user interface layout for this Activity
+    // The layout file is defined in the project res/layout/main_activity.xml file
+    setContentView(R.layout.main_activity);
+    
+    // Initialize member TextView so we can manipulate it later
+    mTextView = (TextView) findViewById(R.id.text_message);
+    
+    // Make sure we're running on Honeycomb or higher to use ActionBar APIs
+    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
+        // For the main activity, make sure the app icon in the action bar
+        // does not behave as a button
+        ActionBar actionBar = getActionBar();
+        actionBar.setHomeButtonEnabled(false);
+    }
+}
+
+ +

Внимание! Использование {@link android.os.Build.VERSION#SDK_INT} для +предотвращения запуска новых API-интерфейсов на старых системах поддерживается только в Android версии 2.0 (API-интерфейсы уровня +5) и более поздних версиях. В старых версиях возникнет ошибка времени исполнения.

+ +

После завершения выполнения {@link android.app.Activity#onCreate onCreate()} система +быстро вызывает методы {@link android.app.Activity#onStart()} и {@link android.app.Activity#onResume()} по +очереди. Операция никогда не остается в состоянии создания или запуска. Технически +операция становится видимой для пользователя при вызове {@link android.app.Activity#onStart()}, однако затем сразу же происходит +{@link android.app.Activity#onResume()} и операция остается в состоянии возобновления, +пока что-то не произойдет, например пока не поступит телефонный звонок, пользователь не переключится +на другую операцию или экран устройства не выключится.

+ +

На последующих уроках вы увидите, как можно с пользой использовать другие методы запуска, {@link +android.app.Activity#onStart()} и {@link android.app.Activity#onResume()}, в жизненном цикле операции +при возобновлении работы после паузы или остановки.

+ +

Примечание. Метод {@link android.app.Activity#onCreate onCreate()} +включает параметр savedInstanceState, о котором будет рассказано на +уроке Воссоздание операции.

+ + + +

Рисунок 2. Еще один пример структуры жизненного +цикла операции, где основное внимание трем главным обратным вызовам, которые система выполняет по очереди при создании +нового экземпляра операции: {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart()} и {@link android.app.Activity#onResume()}. После завершения этой серии +обратных вызовов операция переходит в состояние возобновления, где пользователи могут +взаимодействовать с операцией до тех пор, пока не переключатся на другую операцию.

+ + + + + + + +

Уничтожение операции

+ +

Первым обратным вызовом жизненного цикла операции является {@link android.app.Activity#onCreate +onCreate()}, а последним – {@link android.app.Activity#onDestroy}. Система вызывает +этот метод для операции, подавая окончательный сигнал +о том, что экземпляр операции полностью удаляется из системной памяти.

+ +

Большинству приложений не требуется реализация этого метода, потому что ссылки локальных классов уничтожаются +вместе с операцией, а основные задачи по освобождению ресурсов операция выполняет в состояниях {@link +android.app.Activity#onPause} и {@link android.app.Activity#onStop}. Однако если ваша +операция содержит фоновые потоки, созданные во время выполнения {@link +android.app.Activity#onCreate onCreate()}, или в течение длительного времени использует другие ресурсы, могущие +вызывать утечку памяти при неправильном закрытии, их нужно уничтожить с помощью метода {@link +android.app.Activity#onDestroy}.

+ +
+@Override
+public void onDestroy() {
+    super.onDestroy();  // Always call the superclass
+    
+    // Stop method tracing that the activity started during onCreate()
+    android.os.Debug.stopMethodTracing();
+}
+
+ +

Примечание. Система вызывает {@link android.app.Activity#onDestroy} +после вызова {@link android.app.Activity#onPause} и {@link +android.app.Activity#onStop} во всех случаях, кроме ситуации, когда вы вызываете {@link +android.app.Activity#finish()} из метода {@link android.app.Activity#onCreate onCreate()} +. В некоторых случаях, например когда ваша операция временно отвечает за принятие решения о запуске +другой операции, вы можете вызвать {@link android.app.Activity#finish()} из метода {@link +android.app.Activity#onCreate onCreate()} для уничтожения операции. В этом случае система +сразу же вызывает {@link android.app.Activity#onDestroy}, не вызывая другие методы жизненного +цикла.

diff --git a/docs/html-intl/intl/ru/training/basics/activity-lifecycle/stopping.jd b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/stopping.jd new file mode 100644 index 0000000..27c771f --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/activity-lifecycle/stopping.jd @@ -0,0 +1,187 @@ +page.title=Остановка и перезапуск операции +page.tags=жизненный цикл операции +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +
+
+ +

Содержание этого урока

+
    +
  1. Остановка операции
  2. +
  3. Запуск/перезапуск операции
  4. +
+ +

См. также:

+ + +

Попробуйте сами

+ +
+ Загрузить демонстрацию +

ActivityLifecycle.zip

+
+ +
+
+ +

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

+ + + +

Класс {@link android.app.Activity} предоставляет два метода жизненного цикла, {@link +android.app.Activity#onStop()} и {@link android.app.Activity#onRestart()}, позволяющие явно +обрабатывать остановку и перезапуск операции. В отличие от состояния паузы, +означающем частичное уничтожение пользовательского интерфейса, в состоянии остановки пользовательский интерфейс больше не +отображается и пользователь переключается на отдельную операцию (или отдельное приложение).

+ +

Примечание. Поскольку система хранит ваш экземпляр {@link android.app.Activity} +в системной памяти при остановке, вам, возможно, вообще не потребуется реализация методов +{@link android.app.Activity#onStop()} и {@link android.app.Activity#onRestart()} (или даже {@link +android.app.Activity#onStart()}. Большинство операций относительно простые, и операция +остановится и перезапустится нормально, вам только может потребоваться {@link +android.app.Activity#onPause()} для приостановки текущих операций и отключения от системных ресурсов.

+ + +

Рисунок 1. Когда пользователь выйдет из операции, система +вызовет {@link android.app.Activity#onStop onStop()} для остановки операции (1). Если пользователь возвращается +при остановке операции, система вызывает {@link android.app.Activity#onRestart onRestart()} +(2), затем сразу же {@link android.app.Activity#onStart onStart()} (3) и {@link +android.app.Activity#onResume()} (4). Вне зависимости от причины остановки +операции, система всегда вызывает {@link android.app.Activity#onPause onPause()} перед вызовом {@link +android.app.Activity#onStop onStop()}.

+ + + +

Остановка операции

+ +

Когда операция получает вызов метода {@link android.app.Activity#onStop()}, +она становится невидимой и освобождает практически все ресурсы, которые не нужны ей, когда пользователь ее не +использует. После остановки операции система может уничтожить экземпляр, если ей потребуется +освободить системную память. В чрезвычайных ситуациях система может закрыть процесс приложения без +вызова последнего метода обратного вызова {@link android.app.Activity#onDestroy()} операции, и поэтому важно +использовать {@link android.app.Activity#onStop()} для высвобождения ресурсов, которые могут вызвать утечку памяти.

+ +

Хотя метод {@link android.app.Activity#onPause onPause()} вызывается до +{@link android.app.Activity#onStop()}, вам следует использовать {@link android.app.Activity#onStop onStop()} +для выполнения более масштабных операций выключения с использованием процессорных ресурсов, например при записи информации в базу +данных.

+ +

В качестве примера приведем реализацию {@link android.app.Activity#onStop onStop()}, +сохраняющую содержание черновой заметки в постоянное хранилище:

+ + +
+@Override
+protected void onStop() {
+    super.onStop();  // Always call the superclass method first
+
+    // Save the note's current draft, because the activity is stopping
+    // and we want to be sure the current note progress isn't lost.
+    ContentValues values = new ContentValues();
+    values.put(NotePad.Notes.COLUMN_NAME_NOTE, getCurrentNoteText());
+    values.put(NotePad.Notes.COLUMN_NAME_TITLE, getCurrentNoteTitle());
+
+    getContentResolver().update(
+            mUri,    // The URI for the note to update.
+            values,  // The map of column names and new values to apply to them.
+            null,    // No SELECT criteria are used.
+            null     // No WHERE columns are used.
+            );
+}
+
+ +

При остановке операции объект {@link android.app.Activity} остается в памяти +и вызывается повторно при возобновлении операции. Не нужно заново инициализировать компоненты, +созданные с использованием любого из методов обратного вызова для возобновления операции. Система также +отслеживает текущее состояние каждого {@link android.view.View} в макете, и если +пользователь вводит текст в виджет {@link android.widget.EditText}, этот текст сохраняется, так что его не +нужно специально сохранять и восстанавливать.

+ +

Примечание. Даже если система уничтожит операцию в период остановки, +она сохранит состояние объектов {@link android.view.View} (например, текста в {@link +android.widget.EditText}) в {@link android.os.Bundle} (наборе пар "ключ-значение") и восстановит +их, если пользователь вернется в тот же экземпляр операции (на следующем уроке мы более подробно поговорим об использовании {@link android.os.Bundle} для сохранения +других данных состояния в случае уничтожения и воссоздания вашей операции).

+ + + +

Запуск/перезапуск операции

+ +

Когда ваша операция возвращается в экранный режим из состояния остановки, она получает вызов +{@link android.app.Activity#onRestart()}. Система также вызывает метод {@link +android.app.Activity#onStart()}, что происходит каждый раз, когда операция становится видимой +(при перезапуске или первоначальном создании). Однако метод {@link +android.app.Activity#onRestart()} вызывается только при возобновлении операции из состояния +остановки, так что его можно использовать для выполнения специальных задач восстановления, которые могут требоваться только если операция +была остановлена, но не была уничтожена.

+ +

Приложениям редко требуется использовать {@link android.app.Activity#onRestart()} для восстановления +состояния операции, и поэтому для этого метода нет правил, относящихся +к обычным приложениям. Однако поскольку ваш метод {@link android.app.Activity#onStop()} +должен фактически освободить все ресурсы операции, их нужно снова выделить +при перезапуске операции. Однако их также нужно выделять при первом +создании операции (когда нет существующего экземпляра операции). По этому причине обычно +используют метод обратного вызова {@link android.app.Activity#onStart()} в дополнение +к методу {@link android.app.Activity#onStop()}, поскольку система вызывает {@link +android.app.Activity#onStart()} как при создании операции, так и при ее перезапуске +после остановки.

+ +

Например, если пользователь долго не пользовался приложением, +а затем вернулся в него, метод {@link android.app.Activity#onStart()} хорошо помогает убедиться, что требуемые +системные функции включены:

+ +
+@Override
+protected void onStart() {
+    super.onStart();  // Always call the superclass method first
+    
+    // The activity is either being restarted or started for the first time
+    // so this is where we should make sure that GPS is enabled
+    LocationManager locationManager = 
+            (LocationManager) getSystemService(Context.LOCATION_SERVICE);
+    boolean gpsEnabled = locationManager.isProviderEnabled(LocationManager.GPS_PROVIDER);
+    
+    if (!gpsEnabled) {
+        // Create a dialog here that requests the user to enable GPS, and use an intent
+        // with the android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS action
+        // to take the user to the Settings screen to enable GPS when they click "OK"
+    }
+}
+
+@Override
+protected void onRestart() {
+    super.onRestart();  // Always call the superclass method first
+    
+    // Activity being restarted from stopped state    
+}
+
+ + + + +

Когда система уничтожает вашу операцию, она вызывает метод {@link android.app.Activity#onDestroy()} +для вашего {@link android.app.Activity}. Поскольку вы уже освобождаете большую часть +ресурсов с помощью {@link android.app.Activity#onStop()} к моменту вызова {@link +android.app.Activity#onDestroy()}, большинству приложений почти ничего не нужно делать. Этот метод представляет собой последний +шанс освободить ресурсы, могущие привести к утечке памяти, обеспечивая уверенность +в уничтожении дополнительных потоков и других долгосрочных действий, например, отслеживания +методов.

+ diff --git a/docs/html-intl/intl/ru/training/basics/data-storage/databases.jd b/docs/html-intl/intl/ru/training/basics/data-storage/databases.jd new file mode 100644 index 0000000..418d288 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/data-storage/databases.jd @@ -0,0 +1,317 @@ +page.title=Сохранение данных в базах данных SQL +page.tags=хранение данных +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + + +
+ +
+ + +

Сохранение данных в базе идеально подходит для повторяющихся и структурированных данных, +таких как контактная информация. В этом учебном курсе предполагается, что вы +владеете общими знаниями о базах данных SQL, и он поможет вам начать работать с базами данных +SQLite на платформе Android. API-интерфейсы, необходимые для использования базы данных +на платформе Android, доступны в составе пакета {@link android.database.sqlite}.

+ + +

Определение схемы и контракта

+ +

Одним из основных элементов баз данных SQL является схема, которая представляет собой формальную +декларацию способа организации базы данных. Схема отражается в выражениях SQL, +используемых для создания базы данных. Для вас может оказаться полезным +создать сопутствующий класс (класс-контракт), явно указывающий структуру +схемы систематическим и самодокументирующим способом.

+ +

Класс-контракт представляет собой контейнер, определяющий имена для URI-адресов, +таблиц и столбцов. Класс-контракт позволяет использовать одни и те же постоянные значения +во всех других классах этого же пакета. Таким образом, при изменении имени +столбца в одном месте это изменение применяется во всем коде.

+ +

Для удобства организации класс-контракта стоит поместить +глобальные определения базы данных на корневой уровень класса. Затем нужно создать внутренний +класс для каждой таблицы, производящей нумерацию столбцов.

+ +

Примечание. За счет реализации интерфейса {@link +android.provider.BaseColumns} внутренний класс может наследовать поле первичного +ключа {@code _ID}, наличия которого ожидают от него некоторые +классы Android (например, адаптеры курсора). Это не является обязательным условием, однако может помочь обеспечить гармоничную работу +вашей базы данных в инфраструктуре Android.

+ +

Например, в этом фрагменте кода определяются имя таблицы и имена столбцов для +одной таблицы:

+ + +
+public final class FeedReaderContract {
+    // To prevent someone from accidentally instantiating the contract class,
+    // give it an empty constructor.
+    public FeedReaderContract() {}
+
+    /* Inner class that defines the table contents */
+    public static abstract class FeedEntry implements BaseColumns {
+        public static final String TABLE_NAME = "entry";
+        public static final String COLUMN_NAME_ENTRY_ID = "entryid";
+        public static final String COLUMN_NAME_TITLE = "title";
+        public static final String COLUMN_NAME_SUBTITLE = "subtitle";
+        ...
+    }
+}
+
+ + + +

Создание базы данных с помощью SQL Helper

+ +

После определения внешнего вида базы данных следует реализовать методы +создания и обслуживания базы данных и таблиц. Вот некоторые типичные +выражения для создания и удаления таблиц:

+ +
+private static final String TEXT_TYPE = " TEXT";
+private static final String COMMA_SEP = ",";
+private static final String SQL_CREATE_ENTRIES =
+    "CREATE TABLE " + FeedEntry.TABLE_NAME + " (" +
+    FeedEntry._ID + " INTEGER PRIMARY KEY," +
+    FeedEntry.COLUMN_NAME_ENTRY_ID + TEXT_TYPE + COMMA_SEP +
+    FeedEntry.COLUMN_NAME_TITLE + TEXT_TYPE + COMMA_SEP +
+    ... // Any other options for the CREATE command
+    " )";
+
+private static final String SQL_DELETE_ENTRIES =
+    "DROP TABLE IF EXISTS " + FeedEntry.TABLE_NAME;
+
+ +

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

+ +

Полезный набор API-интерфейсов содержится в классе {@link +android.database.sqlite.SQLiteOpenHelper}. +Если вы используете этот класс для получения ссылок на свою базу данных, система +выполняет потенциально +долговременные операции создания и обновления базы данных только тогда, когда это +необходимо, а не при запуске приложения. Для этого нужно использовать вызов +{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} или +{@link android.database.sqlite.SQLiteOpenHelper#getReadableDatabase}.

+ +

Примечание. Поскольку операции могут выполняться длительное время, +вызывайте {@link +android.database.sqlite.SQLiteOpenHelper#getWritableDatabase} или {@link +android.database.sqlite.SQLiteOpenHelper#getReadableDatabase} в фоновом потоке, +например с {@link android.os.AsyncTask} или {@link android.app.IntentService}.

+ +

Для использования {@link android.database.sqlite.SQLiteOpenHelper} создайте подкласс, заменяющий методы +вызова {@link +android.database.sqlite.SQLiteOpenHelper#onCreate onCreate()}, {@link +android.database.sqlite.SQLiteOpenHelper#onUpgrade onUpgrade()} и {@link +android.database.sqlite.SQLiteOpenHelper#onOpen onOpen()}. Также вы можете +использовать {@link android.database.sqlite.SQLiteOpenHelper#onDowngrade onDowngrade()}, +но это не требуется.

+ +

Например, вот реализация {@link +android.database.sqlite.SQLiteOpenHelper}, при которой используются некоторые из приведенных выше команд:

+ +
+public class FeedReaderDbHelper extends SQLiteOpenHelper {
+    // If you change the database schema, you must increment the database version.
+    public static final int DATABASE_VERSION = 1;
+    public static final String DATABASE_NAME = "FeedReader.db";
+
+    public FeedReaderDbHelper(Context context) {
+        super(context, DATABASE_NAME, null, DATABASE_VERSION);
+    }
+    public void onCreate(SQLiteDatabase db) {
+        db.execSQL(SQL_CREATE_ENTRIES);
+    }
+    public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        // This database is only a cache for online data, so its upgrade policy is
+        // to simply to discard the data and start over
+        db.execSQL(SQL_DELETE_ENTRIES);
+        onCreate(db);
+    }
+    public void onDowngrade(SQLiteDatabase db, int oldVersion, int newVersion) {
+        onUpgrade(db, oldVersion, newVersion);
+    }
+}
+
+ +

Для получения доступа к базе данных создайте экземпляр подкласса {@link +android.database.sqlite.SQLiteOpenHelper}:

+ +
+FeedReaderDbHelper mDbHelper = new FeedReaderDbHelper(getContext());
+
+ + + + +

Размещение информации в базе данных

+ +

Добавьте данные в базу данных, передав объект {@link android.content.ContentValues} +в метод {@link android.database.sqlite.SQLiteDatabase#insert insert()}:

+ +
+// Gets the data repository in write mode
+SQLiteDatabase db = mDbHelper.getWritableDatabase();
+
+// Create a new map of values, where column names are the keys
+ContentValues values = new ContentValues();
+values.put(FeedEntry.COLUMN_NAME_ENTRY_ID, id);
+values.put(FeedEntry.COLUMN_NAME_TITLE, title);
+values.put(FeedEntry.COLUMN_NAME_CONTENT, content);
+
+// Insert the new row, returning the primary key value of the new row
+long newRowId;
+newRowId = db.insert(
+         FeedEntry.TABLE_NAME,
+         FeedEntry.COLUMN_NAME_NULLABLE,
+         values);
+
+ +

Первый аргумент {@link android.database.sqlite.SQLiteDatabase#insert insert()} +представляет собой просто название таблицы. Второй аргумент указывает +имя столбца, в который инфраструктура вставляет значение NULL, если +{@link android.content.ContentValues} является пустым (если вместо этого указать {@code "null"}, +то инфраструктура не будет вставлять строку в случае отсутствия значений).

+ + + + +

Чтение информации из базы данных

+ +

Для чтения из базы данных используйте метод {@link android.database.sqlite.SQLiteDatabase#query query()} +с передачей критериев выделения и желаемых столбцов. +Метод сочетает элементы {@link android.database.sqlite.SQLiteDatabase#insert insert()} +и {@link android.database.sqlite.SQLiteDatabase#update update()}, за исключением того, что список столбцов +определяет данные, которые вы хотите получить, а не данные для вставки. Результаты запроса +возвращаются в объекте {@link android.database.Cursor}.

+ +
+SQLiteDatabase db = mDbHelper.getReadableDatabase();
+
+// Define a projection that specifies which columns from the database
+// you will actually use after this query.
+String[] projection = {
+    FeedEntry._ID,
+    FeedEntry.COLUMN_NAME_TITLE,
+    FeedEntry.COLUMN_NAME_UPDATED,
+    ...
+    };
+
+// How you want the results sorted in the resulting Cursor
+String sortOrder =
+    FeedEntry.COLUMN_NAME_UPDATED + " DESC";
+
+Cursor c = db.query(
+    FeedEntry.TABLE_NAME,  // The table to query
+    projection,                               // The columns to return
+    selection,                                // The columns for the WHERE clause
+    selectionArgs,                            // The values for the WHERE clause
+    null,                                     // don't group the rows
+    null,                                     // don't filter by row groups
+    sortOrder                                 // The sort order
+    );
+
+ +

Чтобы посмотреть на строку в месте курсора, используйте один из методов перемещения +{@link android.database.Cursor}, которые всегда нужно вызывать перед считыванием значений. Обычно следует начинать +с вызова {@link android.database.Cursor#moveToFirst}, который помещает "позицию чтения" +на первую запись в результатах. Для каждой строки значение столбца можно прочитать, вызвав один из методов +{@link android.database.Cursor} get, например {@link android.database.Cursor#getString +getString()} или {@link android.database.Cursor#getLong getLong()}. Для каждого из методов get +вы должны передать указатель желаемого столбца, который может вызвать +{@link android.database.Cursor#getColumnIndex getColumnIndex()} или +{@link android.database.Cursor#getColumnIndexOrThrow getColumnIndexOrThrow()}. +Например:

+ +
+cursor.moveToFirst();
+long itemId = cursor.getLong(
+    cursor.getColumnIndexOrThrow(FeedEntry._ID)
+);
+
+ + + + +

Удаление информации из базы данных

+ +

Для удаления строк из таблицы нужно указать критерии выделения, +идентифицирующие строки. API-интерфейс базы данных обеспечивает механизм для создания критериев +выделения, предоставляющий защиту от внедрения SQL-кода. Механизм делит спецификацию выбора +на предложение выбора и аргументы выбора. Предложение +определяет столбцы для рассмотрения, а также позволяет объединять операции тестирования +столбцов. Аргументы представляют собой значения для тестирования, которые привязаны к пункту. +Поскольку результат обрабатывается не как обычные выражения SQL, +он защищен от внедрения SQL-кода.

+ +
+// Define 'where' part of query.
+String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
+// Specify arguments in placeholder order.
+String[] selectionArgs = { String.valueOf(rowId) };
+// Issue SQL statement.
+db.delete(table_name, selection, selectionArgs);
+
+ + + +

Обновление базы данных

+ +

При необходимости изменить набор значений базы данных используйте метод {@link +android.database.sqlite.SQLiteDatabase#update update()}.

+ +

Обновление таблицы сочетает значения синтаксиса {@link +android.database.sqlite.SQLiteDatabase#insert insert()} и синтаксиса {@code where} +для {@link android.database.sqlite.SQLiteDatabase#delete delete()}.

+ +
+SQLiteDatabase db = mDbHelper.getReadableDatabase();
+
+// New value for one column
+ContentValues values = new ContentValues();
+values.put(FeedEntry.COLUMN_NAME_TITLE, title);
+
+// Which row to update, based on the ID
+String selection = FeedEntry.COLUMN_NAME_ENTRY_ID + " LIKE ?";
+String[] selectionArgs = { String.valueOf(rowId) };
+
+int count = db.update(
+    FeedReaderDbHelper.FeedEntry.TABLE_NAME,
+    values,
+    selection,
+    selectionArgs);
+
+ diff --git a/docs/html-intl/intl/ru/training/basics/data-storage/files.jd b/docs/html-intl/intl/ru/training/basics/data-storage/files.jd new file mode 100644 index 0000000..2afecea --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/data-storage/files.jd @@ -0,0 +1,379 @@ +page.title=Сохранение файлов +page.tags=хранение данных +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + + +
+ +
+ +

Операционная система Android использует файловую систему, +похожую на дисковые файловые системы других платформ. В этом уроке рассказывается, +как работать с файловой системой Android для чтения и записи файлов в {@link java.io.File} +API-интерфейсах.

+ +

Объект {@link java.io.File} подходит для чтения или записи больших объемов данных в порядке +от начала к концу без пропусков. Например, этот способ оптимален для изображений или +любых других файлов, передаваемых по сети.

+ +

В этом уроке вы узнаете, как выполнять в приложении базовые задачи, связанные с файлами. +Для прохождения урока вы должны быть знакомы с основами файловой системы Linux +и стандартными файловыми API-интерфейсами ввода/вывода в {@link java.io}.

+ + +

Выбор внутреннего или внешнего хранилища

+ +

Все устройства Android имеют две области хранения файлов: внутренняя память и внешние хранилища. Эти области +появились в первые годы существования Android, когда на большинстве устройств имелись встроенная память +(внутреннее хранилище) и карты памяти (например micro SD, внешнее хранилище). +Некоторые устройства делят встроенную память на внутренний и внешний разделы, +так что даже без съемных носителей в системе две области хранения файлов, +и API-интерфейс работает одинаково вне зависимости от типа внешнего хранилища. +Ниже подробно описаны обе области хранения файлов.

+ +
+

Внутренняя память

+ +

Внутренняя память лучше всего подходит для ситуаций, когда вы хотите быть уверены, что ни пользователь, ни другие приложения не смогут получить +доступ к вашим файлам.

+
+ +
+

Внешнее хранилище

+ +

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

+
+ + +

+Совет. Хотя по умолчанию приложения устанавливаются во внутреннюю память, +вы можете указать в манифесте атрибут {@code +android:installLocation}, чтобы приложение можно +было установить во внешнее хранилище. Этот вариант будет полезен пользователям при большом размере файла APK, либо если у них есть доступ +к внешнему хранилищу, объем которого превышает внутреннюю память. Дополнительную информацию +см. в разделе Место установки приложения.

+ + +

Получение разрешений доступа к внешнему хранилищу

+ +

Для записи во внешнее хранилище следует указать запрос +разрешения {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE} в файле манифеста:

+ +
+<manifest ...>
+    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
+    ...
+</manifest>
+
+ +

Внимание! +В настоящее время все приложения могут считывать данные из внешних +хранилищ без специального разрешения. Однако в новой версии эта ситуация будет изменена. Если вашему приложению потребуется +считать данные из внешнего хранилища (но не записать туда данные), вам необходимо будет декларировать разрешение {@link +android.Manifest.permission#READ_EXTERNAL_STORAGE}. Чтобы обеспечить дальнейшую работу вашего приложения +ожидаемым образом, вы должны сразу декларировать это разрешение до того, как изменения вступят в силу.

+
+<manifest ...>
+    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
+    ...
+</manifest>
+
+

Однако если ваше приложение использует разрешение {@link android.Manifest.permission#WRITE_EXTERNAL_STORAGE}, +оно косвенно получает разрешение на чтение данных из внешнего хранилища.

+
+ +

Для сохранения файлов во внутреннем хранилище +не требуется никаких разрешений. У вашего приложения всегда будет разрешение на чтение и запись +файлов в его каталог внутренней памяти.

+ + + + + +

Сохранение файла во внутренней памяти

+ +

При сохранении файла во внутреннюю память вы можете получить соответствующую директорию в виде +{@link java.io.File}, вызвав один из двух методов:

+ +
+
{@link android.content.Context#getFilesDir}
+
Возвращает {@link java.io.File}, соответствующий внутренней директории приложения.
+
{@link android.content.Context#getCacheDir}
+
Возвращает {@link java.io.File}, соответствующий внутренней директории файлов временной +кэш-памяти приложения. Обязательно удаляйте каждый файл, когда он перестанет +быть нужным, и устанавливайте разумное ограничение размера памяти, используемой в каждый момент времени, +например, 1 МБ. Если системе будет не хватать места в хранилище, она может удалять файлы +из кэш-памяти без уведомления.
+
+ +

Для создания файла в одной из этих директорий можно использовать конструктор {@link +java.io.File#File(File,String) File()}, который передает элемент {@link java.io.File}, предоставляемый одним из вышеприведенных +методов, с помощью которого указывается директория во внутренней памяти. Например:

+ +
+File file = new File(context.getFilesDir(), filename);
+
+ +

Кроме того, можно вызвать метод {@link +android.content.Context#openFileOutput openFileOutput()} для получения объекта {@link java.io.FileOutputStream} +, производящего запись в файл во внутренней памяти. Вот пример +записи текста в файл:

+ +
+String filename = "myfile";
+String string = "Hello world!";
+FileOutputStream outputStream;
+
+try {
+  outputStream = openFileOutput(filename, Context.MODE_PRIVATE);
+  outputStream.write(string.getBytes());
+  outputStream.close();
+} catch (Exception e) {
+  e.printStackTrace();
+}
+
+ +

Если вам потребуется кэшировать какие-то файлы, используйте {@link +java.io.File#createTempFile createTempFile()}. Например, следующий метод извлекает +имя файла из {@link java.net.URL} и создает файл с этим именем +в каталоге внутренней кэш-памяти вашего приложения:

+ +
+public File getTempFile(Context context, String url) {
+    File file;
+    try {
+        String fileName = Uri.parse(url).getLastPathSegment();
+        file = File.createTempFile(fileName, null, context.getCacheDir());
+    catch (IOException e) {
+        // Error while creating file
+    }
+    return file;
+}
+
+ +

Примечание. +Каталог вашего приложения во внутренней памяти указывается +с использованием имени пакета приложения в определенном месте файловой системы Android. +Технически другое приложение может прочитать ваши файлы во внутренней памяти, если вы установите для файлов +режим Readable (доступно для чтения). Однако для этого другому приложению должны быть известны имя пакета вашего +приложения и имена файлов. Другие приложения не могут просматривать внутренние каталоги вашего приложения и не имеют разрешений на +чтение или запись, если вы специально не установите для своих файлов режим Readable (доступно для чтения) или Writable (доступно для записи). Следовательно, пока +вы будете использовать режим {@link android.content.Context#MODE_PRIVATE} для своих файлов во внутренней памяти, +они будут недоступны другим приложениям.

+ + + + + +

Сохранение файла во внешнем хранилище

+ +

Поскольку внешнее хранилище может быть недоступно— например, если пользователь установил +хранилище в гнездо на компьютере или извлек из устройства SD-карту, — перед доступом к тому всегда следует +проверять его доступность. Состояние внешнего +хранилища можно узнать, если вызвать {@link android.os.Environment#getExternalStorageState}. Если возвращаемое состояние +равнозначно {@link android.os.Environment#MEDIA_MOUNTED}, вы сможете выполнять с файлами операции чтения и +записи. Например, следующие методы будут полезными для определения доступности +хранилища данных:

+ +
+/* Checks if external storage is available for read and write */
+public boolean isExternalStorageWritable() {
+    String state = Environment.getExternalStorageState();
+    if (Environment.MEDIA_MOUNTED.equals(state)) {
+        return true;
+    }
+    return false;
+}
+
+/* Checks if external storage is available to at least read */
+public boolean isExternalStorageReadable() {
+    String state = Environment.getExternalStorageState();
+    if (Environment.MEDIA_MOUNTED.equals(state) ||
+        Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
+        return true;
+    }
+    return false;
+}
+
+ +

Хотя внешнее хранилище может быть изменено пользователем и другими приложениями, существует две +категории файлов, которые в нем можно сохранять:

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

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

+
+
Личные файлы
+
Это файлы, принадлежащие вашему приложению. Они должны удаляться при удалении +вашего приложения пользователем. Хотя технически эти файлы доступны для пользователя и других приложений, поскольку находятся +во внешнем хранилище, они не имеют никакой ценности для пользователей +вне вашего приложения. Когда пользователь удаляет ваше приложение, система удаляет +все файлы из каталога закрытых файлов вашего приложения во внешнем хранилище. +

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

+
+
+ +

Если вы хотите сохранить публичные файлы во внешнем хранилище, используйте методы +{@link android.os.Environment#getExternalStoragePublicDirectory +getExternalStoragePublicDirectory()} для получения {@link java.io.File}, отражающего +соответствующий каталог во внешнем хранилище. Этот метод принимает аргумент, указывающий +тип сохраняемого файла и позволяющий включить его в логическую структуру +с другими публичными файлами, например, {@link android.os.Environment#DIRECTORY_MUSIC} или {@link +android.os.Environment#DIRECTORY_PICTURES}. Например:

+ +
+public File getAlbumStorageDir(String albumName) {
+    // Get the directory for the user's public pictures directory. 
+    File file = new File(Environment.getExternalStoragePublicDirectory(
+            Environment.DIRECTORY_PICTURES), albumName);
+    if (!file.mkdirs()) {
+        Log.e(LOG_TAG, "Directory not created");
+    }
+    return file;
+}
+
+ + +

Если вы хотите сохранить личные файлы вашего приложения, вы можете получить +соответствующий каталог посредством, вызвав метод {@link +android.content.Context#getExternalFilesDir getExternalFilesDir()} и предоставив ему имя с указанием +желаемого типа каталога. Каждый каталог, создаваемый таким способом, добавляется в родительский +каталог, в котором объединены все файлы вашего приложения во внешнем хранилище. При удалении вашего приложения +пользователем система удаляет этот каталог.

+ +

Например, следующий метод позволит вам создать каталог для персонального фотоальбома:

+ +
+public File getAlbumStorageDir(Context context, String albumName) {
+    // Get the directory for the app's private pictures directory. 
+    File file = new File(context.getExternalFilesDir(
+            Environment.DIRECTORY_PICTURES), albumName);
+    if (!file.mkdirs()) {
+        Log.e(LOG_TAG, "Directory not created");
+    }
+    return file;
+}
+
+ +

Если ни одно из готовых имен подкаталогов не подходит для ваших файлов, вы можете вызвать {@link +android.content.Context#getExternalFilesDir getExternalFilesDir()} и передать аргумент {@code null}. При этом +будет возвращен корневой каталог закрытого каталога вашего приложения во внешнем хранилище.

+ +

Следует помнить, что {@link android.content.Context#getExternalFilesDir getExternalFilesDir()} +создает каталог внутри каталога, который удаляется при удалении вашего приложения пользователем. +Если сохраняемые вами файлы должны оставаться доступными после удаления вашего +приложения пользователем — например, если ваше приложение работает с камерой, а пользователь хочет сохранить снимки, — вам +следует использовать {@link android.os.Environment#getExternalStoragePublicDirectory +getExternalStoragePublicDirectory()}.

+ + +

Вне зависимости от того, используете ли вы {@link +android.os.Environment#getExternalStoragePublicDirectory +getExternalStoragePublicDirectory()} для общих файлов или + link android.content.Context#getExternalFilesDir +getExternalFilesDir()} для собственных файлов приложения, вы должны использовать имена каталогов, предоставляемые +постоянными значениями API-интерфейсов, например, +{@link android.os.Environment#DIRECTORY_PICTURES}. Эти имена каталогов обеспечивают +правильную обработку файлов системой. Например, сохраненные в {@link +android.os.Environment#DIRECTORY_RINGTONES} файлы относятся медиа-сканером системы в категорию рингтонов, +а не музыки.

+ + + + +

Запрос доступного пространства

+ +

Если вам заранее известен объем сохраняемых данных, вы можете +определить наличие достаточного пространства без исключения {@link +java.io.IOException}, вызвав метод {@link java.io.File#getFreeSpace} или {@link +java.io.File#getTotalSpace}. Эти методы позволяют узнать текущее доступное пространство и +общее пространство в хранилище. Эта информация также позволять +избежать переполнения объема хранилища сверх определенного уровня.

+ +

Однако система не гарантирует возможность записи такого же количества байт, как указано +в {@link java.io.File#getFreeSpace}. Если выводимое число на +несколько мегабайт превышает размер данных, которые вы хотите сохранить, или если файловая система заполнена +менее, чем на 90%, дальше можно действовать спокойно. +В противном случае запись в хранилище осуществлять нежелательно.

+ +

Примечание. Вам необязательно проверять объем доступного места +перед сохранением файла. Вы можете попробовать сразу записать файл, а затем +определить событие {@link java.io.IOException}, если оно возникнет. Это может потребоваться, +если вы точно не знаете, сколько нужно свободного места. Например, если вы +меняете кодировку файла перед сохранением при конвертации изображения PNG в формат +JPEG, вы не будете знать размер файла заранее.

+ + + + +

Удаление файла

+ +

Ненужные файлы всегда следует удалять. Наиболее простой способ удалить +файл – вызвать в открытом файле ссылку {@link java.io.File#delete} на сам этот файл.

+ +
+myFile.delete();
+
+ +

Если файл сохранен во внутреннем хранилище, вы также можете запросить {@link android.content.Context}, чтобы найти и +удалить файл посредством вызова {@link android.content.Context#deleteFile deleteFile()}:

+ +
+myContext.deleteFile(fileName);
+
+ +
+

Примечание. При удалении пользователем вашего приложения система Android удаляет +следующие элементы:

+ +

Однако вам следует регулярно вручную очищать кэш-память, чтобы удалить файлы, созданные с помощью +{@link android.content.Context#getCacheDir()}, а также удалять любые +другие ненужные файлы.

+
+ diff --git a/docs/html-intl/intl/ru/training/basics/data-storage/index.jd b/docs/html-intl/intl/ru/training/basics/data-storage/index.jd new file mode 100644 index 0000000..064239b --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/data-storage/index.jd @@ -0,0 +1,57 @@ +page.title=Сохранение данных +page.tags=хранение данных,файлы,sql,база данных,настройки +helpoutsWidget=true + +trainingnavtop=true +startpage=true + +@jd:body + +
+
+ +

Необходимые знания и компоненты

+
    +
  • Android 1.6 (уровень API 4) или более поздняя версия
  • +
  • Знакомство с коллекциями значений ключей Map
  • +
  • Знакомство с файловым API-интерфейсом ввода/вывода Java
  • +
  • Знакомство с базами данных SQL
  • +
+ +

См. также:

+ + +
+
+ +

Большинству приложений Android необходимо сохранять данные (даже если это требуется только для сохранения информации о состоянии приложения) +во время использования {@link android.app.Activity#onPause onPause()}, чтобы текущий прогресс пользователя не был утрачен. Большинству +приложений, за исключением самых простых, также требуется сохранять настройки пользователя, а некоторым приложениям также требуется управление большими +объемами информации в файлах и базах данных. В этом учебном курсе вы познакомитесь +с основными вариантами хранения данных в Android, включая следующие:

+ + + + +

Уроки

+ +
+
Сохранение наборов "ключ-значение"
+
В этом уроке вы узнаете, как использовать общий файл настроек для хранения небольших объемов информации +в парах "ключ-значение".
+ +
Сохранение файлов
+
Узнайте, как сохранять простые файлы, например, для хранения длинных последовательностей данных, которые +обычно читаются по порядку.
+ +
Сохранение данных в базах данных SQL
+
Этот урок содержит информацию об использовании баз данных SQLite для чтения и записи структурированных данных.
+ +
diff --git a/docs/html-intl/intl/ru/training/basics/data-storage/shared-preferences.jd b/docs/html-intl/intl/ru/training/basics/data-storage/shared-preferences.jd new file mode 100644 index 0000000..61a0037 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/data-storage/shared-preferences.jd @@ -0,0 +1,120 @@ +page.title=Сохранение наборов "ключ-значение" +page.tags=хранение данных +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + + +
+ +
+ + +

Если вы хотите сохранить относительно небольшой набор пар "ключ-значение", +используйте {@link android.content.SharedPreferences} API-интерфейсы. +Объект {@link android.content.SharedPreferences} указывает на файл, +содержащий пары "ключ-значение", и предоставляет простые методы для чтения и записи. Управление каждым +файлом {@link android.content.SharedPreferences} осуществляется +с помощью инфраструктуры и может быть частным или общим.

+ +

На этом уроке вы узнаете, как использовать API-интерфейсы {@link android.content.SharedPreferences} для сохранения и +получения простых значений.

+ +

Примечание. API-интерфейсы {@link android.content.SharedPreferences} предназначены +только для чтения и записи пар "ключ-значение", и их не следует путать с API-интерфейсами +{@link android.preference.Preference}, которые помогают создать пользовательский интерфейс +для настроек приложения (хотя они используют {@link android.content.SharedPreferences} в качестве своей +реализации для сохранения настроек приложения). Информацию об использовании API-интерфейсов {@link +android.preference.Preference} см. в руководстве Настройки.

+ +

Получение средства обработки SharedPreferences

+ +

Чтобы создать новый файл общих настроек или получить доступ к существующему, +нужно вызвать один из двух методов:

+ + +

Например, следующий код выполняется в {@link android.app.Fragment}. +Он получает доступ к файлу общих настроек, +идентифицируемому по строке ресурсов {@code R.string.preference_file_key} и открывает его, используя +закрытый режим так, что данный файл доступен только вашему приложению.

+ +
+Context context = getActivity();
+SharedPreferences sharedPref = context.getSharedPreferences(
+        getString(R.string.preference_file_key), Context.MODE_PRIVATE);
+
+ +

Файлу общих настроек следует присваивать имя, которое ваше приложение может уникально +идентифицировать, например {@code "com.example.myapp.PREFERENCE_FILE_KEY"}.

+ +

Если для вашей операции нужен только один файл общих настроек, вы можете использовать метод +{@link android.app.Activity#getPreferences(int) getPreferences()}:

+ +
+SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
+
+ +

Внимание! Если вы создадите общий файл настроек +с {@link android.content.Context#MODE_WORLD_READABLE} или {@link +android.content.Context#MODE_WORLD_WRITEABLE}, ваши данные будут доступны всем другим приложениям, которым известен идентификатор +файла.

+ + +

Записать в общие настройки

+ +

Для записи в файл общих настроек создайте объект {@link +android.content.SharedPreferences.Editor} посредством вызова {@link +android.content.SharedPreferences#edit} в {@link android.content.SharedPreferences}.

+ +

Передайте ключи и значения, которые хотите записать, с помощью таких методов, как {@link +android.content.SharedPreferences.Editor#putInt putInt()} и {@link +android.content.SharedPreferences.Editor#putString putString()}. Затем вызовите {@link +android.content.SharedPreferences.Editor#commit} для сохранения изменений. Например:

+ +
+SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
+SharedPreferences.Editor editor = sharedPref.edit();
+editor.putInt(getString(R.string.saved_high_score), newHighScore);
+editor.commit();
+
+ + +

Чтение общих настроек

+ +

Для получения значений из файла общих настроек следует вызвать такие, методы как {@link +android.content.SharedPreferences#getInt getInt()} и {@link +android.content.SharedPreferences#getString getString()}, предоставляя ключ для нужного +вам значения, а также при желании значение по умолчанию, которое будет выводиться при отсутствии +ключа. Например:

+ +
+SharedPreferences sharedPref = getActivity().getPreferences(Context.MODE_PRIVATE);
+int defaultValue = getResources().getInteger(R.string.saved_high_score_default);
+long highScore = sharedPref.getInt(getString(R.string.saved_high_score), defaultValue);
+
+ diff --git a/docs/html-intl/intl/ru/training/basics/intents/filters.jd b/docs/html-intl/intl/ru/training/basics/intents/filters.jd new file mode 100644 index 0000000..0f5bb31 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/intents/filters.jd @@ -0,0 +1,236 @@ +page.title=Разрешение другим приложениям на запуск вашей операции +page.tags=намерения +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +
+ +
+ +

Предыдущие два урока были посвящены одной теме – запуску операции вашего приложения +в другом приложении. Однако если ваше приложение может выполнять операции, полезные другим приложениям, +его нужно подготовить так, чтобы оно реагировало на запросы других приложений. Представьте, что вы разработали +приложение для социальных сетей, позволяющее отправлять сообщения и фотографии друзьям пользователя. В этом случае в ваших интересах +будет поддерживать объекты Intent (намерения) {@link android.content.Intent#ACTION_SEND}, чтобы пользователи могли +поделиться контентом из другого приложения и запускать ваше приложение для выполнения требуемого действия.

+ +

Чтобы разрешить другим приложениям запускать ваши операции, вам нужно добавить в ваш файл манифеста элемент {@code <intent-filter>} + для соответствующего элемента {@code <activity>}.

+ +

Если ваше приложение установлено на устройстве, система идентифицирует ваши +фильтры Intent и добавляет информацию во внутренний каталог намерений, поддерживаемый всеми установленными приложениями. +Когда приложение осуществляет вызов {@link android.app.Activity#startActivity +startActivity()} или {@link android.app.Activity#startActivityForResult startActivityForResult()} +с неявными объектами Intent, система определяет, какая операция (или какие операции) может отреагировать на объект +Intent.

+ + + +

Добавление фильтра Intent

+ +

Чтобы правильно определить, какие объекты Intent может обрабатывать ваша операция, каждый добавляемый вами фильтр Intent +должен быть максимально определенным с точки зрения действий и данных, принимаемых +операцией.

+ +

Система может отправить указанный {@link android.content.Intent} в операцию, если у нее имеется +фильтр Intent, соответствующий следующим критериям объекта {@link android.content.Intent}:

+ +
+
Действие
+
Строка, называющая действие, которое необходимо выполнить. Обычно это одно из определяемых платформой значений, +например, {@link android.content.Intent#ACTION_SEND} или {@link android.content.Intent#ACTION_VIEW}. +

Это следует указать в фильтре Intent с элементом {@code <action>}. +Указанное в этом элементе значение должно представлять собой полное имя строки действия, а не +постоянное значение API-интерфейса (см. примеры ниже).

+ +
Данные
+
Описание данных, связанных с объектом Intent. +

Указывается в фильтре Intent с элементом {@code <data>}. Используя один +или несколько атрибутов в этом элементе, вы можете указать только тип MIME, только префикс URI, +только схему URI или из сочетание, а также другие индикаторы типа +принимаемых данных.

+

Примечание. Если вам не нужно декларировать специфику данных +{@link android.net.Uri}(например, когда операция использует другие виды дополнительных данных вместо +URI), вы должны указать только атрибут {@code android:mimeType} для декларирования типа +данных, с которыми работает ваша операция, например, {@code text/plain} или {@code image/jpeg}.

+
+
Категория
+
Дополнительный способ описания характеристик операции, обрабатывающей объект Intent который обычно связан +с жестом пользователя или местом запуска. Система поддерживает несколько разных категорий, +но большинство из них используется редко. Однако по умолчанию все неявные объекты Intent определяются с +{@link android.content.Intent#CATEGORY_DEFAULT}. +

Это указывается в фильтре Intent с элементом {@code <category>} +.

+
+ +

В своем фильтре Intent вы можете декларировать критерии, принимаемые вашей операцией. +Для этого нужно декларировать каждый из них с соответствующими элементами XML, вложенными в элемент {@code <intent-filter>} +.

+ +

Рассмотрим в качестве примера операцию с фильтром Intent, обрабатывающим объект {@link +android.content.Intent#ACTION_SEND}, для текстовых или графических данных.

+ +
+<activity android:name="ShareActivity">
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="text/plain"/>
+        <data android:mimeType="image/*"/>
+    </intent-filter>
+</activity>
+
+ +

Каждый входящий объект Intent указывает только одно действие и только один тип данных, однако допускается декларирование нескольких +экземпляров элементов {@code +<action>}, {@code +<category>} и {@code +<data>} в каждом +{@code +<intent-filter>}.

+ +

Если поведение любых двух пар действий и данных является взаимоисключающим, +необходимо создать отдельные фильтры Intent с указанием допустимых действий +в сочетании с соответствующими типами данных.

+ +

Допустим, ваша операция обрабатывает текст и изображения для объектов Intent {@link +android.content.Intent#ACTION_SEND} и {@link +android.content.Intent#ACTION_SENDTO}. В этом случае вам необходимо определить два отдельных фильтра Intent +для двух действий, потому что объект Intent {@link +android.content.Intent#ACTION_SENDTO} должен использовать данные {@link android.net.Uri} для указания +адреса получателя с использованием схемы URI {@code send} или {@code sendto}. Например:

+ +
+<activity android:name="ShareActivity">
+    <!-- filter for sending text; accepts SENDTO action with sms URI schemes -->
+    <intent-filter>
+        <action android:name="android.intent.action.SENDTO"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:scheme="sms" />
+        <data android:scheme="smsto" />
+    </intent-filter>
+    <!-- filter for sending text or images; accepts SEND action and text or image data -->
+    <intent-filter>
+        <action android:name="android.intent.action.SEND"/>
+        <category android:name="android.intent.category.DEFAULT"/>
+        <data android:mimeType="image/*"/>
+        <data android:mimeType="text/plain"/>
+    </intent-filter>
+</activity>
+
+ +

Примечание. Для получения неявных объектов Intent необходимо включить категорию +{@link android.content.Intent#CATEGORY_DEFAULT} в фильтр Intent. Методы {@link +android.app.Activity#startActivity startActivity()} и {@link +android.app.Activity#startActivityForResult startActivityForResult()} обрабатывают все объекты Intent, как если бы они +декларировали категорию {@link android.content.Intent#CATEGORY_DEFAULT}. Если вы не декларируете ее +в своем фильтре Intent, никакие неявные объекты Intent не будут разрешаться в вашу операцию.

+ +

Более подробную информацию об отправке и получении объектов {@link android.content.Intent#ACTION_SEND} +Intent для социальных сетей и обмена данными см. в уроке Получение простых данных от других приложений.

+ + +

Обработка объекта Intent в операции

+ +

Чтобы решить, какое действие выполнить в операции, можно прочитать объект {@link +android.content.Intent}, использованный для ее запуска.

+ +

При запуске операции нужно вызвать {@link android.app.Activity#getIntent()} для получения +{@link android.content.Intent}, запустившего операцию. Это можно сделать в любое время в течение +жизненного цикла операции, но обычно это делается при ранних обратных вызовах, например, +{@link android.app.Activity#onCreate onCreate()} или {@link android.app.Activity#onStart()}.

+ +

Например:

+ +
+@Override
+protected void onCreate(Bundle savedInstanceState) {
+    super.onCreate(savedInstanceState);
+
+    setContentView(R.layout.main);
+
+    // Get the intent that started this activity
+    Intent intent = getIntent();
+    Uri data = intent.getData();
+
+    // Figure out what to do based on the intent type
+    if (intent.getType().indexOf("image/") != -1) {
+        // Handle intents with image data ...
+    } else if (intent.getType().equals("text/plain")) {
+        // Handle intents with text ...
+    }
+}
+
+ + +

Возврат результата

+ +

Если вы хотите возвратить результат операции, которая вызвала вашу операцию, просто вызовите {@link +android.app.Activity#setResult(int,Intent) setResult()}, чтобы указать код результата и объект {@link +android.content.Intent} результата. Когда ваша операция завершается, и пользователь должен вернуться к первоначальной +операции, вызовите {@link android.app.Activity#finish()}, чтобы закрыть (и уничтожить) вашу операцию. Например: +

+ +
+// Create intent to deliver some kind of result data
+Intent result = new Intent("com.example.RESULT_ACTION", Uri.parse("content://result_uri");
+setResult(Activity.RESULT_OK, result);
+finish();
+
+ +

Вместе с результатом всегда нужно указывать код результата. Обычно этот код выглядит так: {@link +android.app.Activity#RESULT_OK} или {@link android.app.Activity#RESULT_CANCELED}. После этого при +необходимости можно указать дополнительные данные с помощью {@link android.content.Intent}.

+ +

Примечание. По умолчанию используется результат {@link +android.app.Activity#RESULT_CANCELED}. Таким образом, если пользователь нажимает кнопку Назад +до завершения действия и определения результата, первоначальная операция получает +результат "Отменено".

+ +

Если вам просто нужно возвратить целое число, указывающее один из нескольких вариантов результатов, вы можете установить +для кода результата любое значение больше 0. Если вы используете код результата для передачи целого числа и вам +не нужно включать {@link android.content.Intent}, можете вызвать метод {@link +android.app.Activity#setResult(int) setResult()} и передать только код результата. Например:

+ +
+setResult(RESULT_COLOR_RED);
+finish();
+
+ +

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

+ +

Примечание. Не нужно проверять, запущена ли ваша операция, +используя метод {@link +android.app.Activity#startActivity startActivity()} или {@link +android.app.Activity#startActivityForResult startActivityForResult()}. Просто вызовите метод {@link +android.app.Activity#setResult(int,Intent) setResult()}, если запустивший вашу операцию объект Intent может +ожидать результат. Если исходная операция вызвала метод {@link +android.app.Activity#startActivityForResult startActivityForResult()}, система передаст +ей результат, который вы передаете {@link android.app.Activity#setResult(int,Intent) setResult()}. В противном случае +результат будет проигнорирован.

+ + + + + + diff --git a/docs/html-intl/intl/ru/training/basics/intents/index.jd b/docs/html-intl/intl/ru/training/basics/intents/index.jd new file mode 100644 index 0000000..cfb606d --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/intents/index.jd @@ -0,0 +1,62 @@ +page.title=Взаимодействие с другими приложениями +page.tags=объекты Intent,операция +helpoutsWidget=true + +trainingnavtop=true +startpage=true + +@jd:body + +
+ +
+ +

В приложениях Android обычно имеется несколько операций. Каждая операция отображает +пользовательский интерфейс, позволяющий пользователю выполнить определенную задачу (например, посмотреть карту или сделать снимок). +Чтобы обеспечить переход пользователя от одной операции к другой, приложение должно использовать объект {@link +android.content.Intent} для определения "намерения" приложения что-то сделать. При передаче системе +{@link android.content.Intent} с помощью такого метода как {@link +android.app.Activity#startActivity startActivity()} система использует {@link +android.content.Intent} для идентификации и запуска соответствующего компонента приложения. Использование объектов Intent даже +позволяет приложению запускать операции, содержащиеся в отдельном приложении.

+ +

{@link android.content.Intent} может явно запускать определенный компонент +(определенный экземпляр {@link android.app.Activity}) или косвенно запускать любой +компонент, способный выполнить желаемую операцию (например, "сделать снимок").

+ +

В этом учебном курсе рассказывается о том, как использовать {@link android.content.Intent} для простого +взаимодействия с другими приложениями, например для запуска другого приложения, получения результата от этого приложения и обеспечения +способности приложения реагировать на объекты Intent из других приложений.

+ +

Уроки

+ +
+
Направление пользователя в другое приложение
+
Вы узнаете, как создавать неявные объекты Intent для запуска других приложений, способных выполнить +операцию.
+
Получение результата операции
+
В этом уроке демонстрируется, как запустить другую операцию и получить ее результат.
+
Разрешение другим приложениям на запуск операции
+
В этом уроке демонстрируется, как разрешить другим приложениям использовать операции вашего приложения за счет определения +фильтров объектов Intent, которые декларируют неявные намерения, принимаемые вашим приложением.
+
+ diff --git a/docs/html-intl/intl/ru/training/basics/intents/result.jd b/docs/html-intl/intl/ru/training/basics/intents/result.jd new file mode 100644 index 0000000..8ab03d4 --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/intents/result.jd @@ -0,0 +1,178 @@ +page.title=Получение результата операции +page.tags=объекты Intent +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + +
+ +
+ +

Запуск другой операции не обязательно должен быть односторонним действием. Вы можете запустить другую операцию и +получить от нее результат. Для получения результата нужно вызвать метод {@link android.app.Activity#startActivityForResult +startActivityForResult()} (вместо {@link android.app.Activity#startActivity +startActivity()}).

+ +

Например, ваше приложение может запустить приложение для камеры и получить в качестве результата фотографию. Также вы +можете запустить приложение "Люди", чтобы пользователь выбрал в нем +контакт, и получить контактные данные в качестве результата.

+ +

Разумеется, отвечающая операция должна быть способной возвратить результат. При возврате +результат отправляется как другой объект {@link android.content.Intent}. Ваша операция получает +его в обратном вызове {@link android.app.Activity#onActivityResult onActivityResult()}.

+ +

Примечание. Вы можете использовать явные и неявные результаты при вызове +{@link android.app.Activity#startActivityForResult startActivityForResult()}. При запуске собственной +операции для получения результата вы должны использовать явные результаты, чтобы получить +именно ожидаемый результат.

+ + +

Запуск операции

+ +

В объекте {@link android.content.Intent}, используемом для запуска операции +для получения результата, нет ничего особенного, однако нужно передать дополнительный целочисленный аргумент методы {@link +android.app.Activity#startActivityForResult startActivityForResult()}.

+ +

Этот целочисленный аргумент представляет собой "код запроса", определяющий ваш запрос. Когда вы получите +результат {@link android.content.Intent}, обратный вызов использует тот же самый код результата, +чтобы ваше приложение могло правильно определить результат и понять, как его нужно обработать.

+ +

Например, вот так можно запустить операцию, позволяющую пользователю выбрать контакт:

+ +
+static final int PICK_CONTACT_REQUEST = 1;  // The request code
+...
+private void pickContact() {
+    Intent pickContactIntent = new Intent(Intent.ACTION_PICK, Uri.parse("content://contacts"));
+    pickContactIntent.setType(Phone.CONTENT_TYPE); // Show user only contacts w/ phone numbers
+    startActivityForResult(pickContactIntent, PICK_CONTACT_REQUEST);
+}
+
+ + +

Получение результата

+ +

Когда пользователь завершит последующую операцию и вернется, система вызовет метод вашей операции +{@link android.app.Activity#onActivityResult onActivityResult()}. Этот метод содержит три +аргумента:

+ + + +

Например, результаты для намерения "Выбрать контакт" могут обрабатываться следующим образом:

+ +
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // Check which request we're responding to
+    if (requestCode == PICK_CONTACT_REQUEST) {
+        // Make sure the request was successful
+        if (resultCode == RESULT_OK) {
+            // The user picked a contact.
+            // The Intent's data Uri identifies which contact was selected.
+
+            // Do something with the contact here (bigger example below)
+        }
+    }
+}
+
+ +

В этом примере результаты, {@link android.content.Intent} возвращаемые приложениями +Android Контакты или Люди, предоставляют контент {@link android.net.Uri}, который идентифицирует +выбранный пользователем контакт.

+ +

Для успешной обработки результатов необходимо понимать, каким будет формат этих результатов +{@link android.content.Intent}. Это просто, если результат возвращается одной из ваших +собственных операций. Приложения, входящие в состав платформы Android, имеют собственные прикладные интерфейсы, так что +вы можете рассчитывать на получение определенных результатов. Например, приложение "Люди" (приложение "Контакты" в старых +версиях) всегда возвращает результат с URI контента, идентифицирующий выбранный контакт, а приложение +"Камера" возвращает {@link android.graphics.Bitmap} в дополнительном {@code "data"} (см. урок +Съемка фотографий).

+ + +

Бонус: Чтение контактных данных

+ +

Приведенный выше код, показывающий как получить результаты из приложения "Люди", не описывает +детально чтение данных результатов, потому что для этого нужно более +подробно рассказать о поставщиках +контента. Если вам все-таки интересно, вот еще код, показывающий как запрашивать +данные результатов для получения номера телефона выбранного контакта:

+ +
+@Override
+protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+    // Check which request it is that we're responding to
+    if (requestCode == PICK_CONTACT_REQUEST) {
+        // Make sure the request was successful
+        if (resultCode == RESULT_OK) {
+            // Get the URI that points to the selected contact
+            Uri contactUri = data.getData();
+            // We only need the NUMBER column, because there will be only one row in the result
+            String[] projection = {Phone.NUMBER};
+
+            // Perform the query on the contact to get the NUMBER column
+            // We don't need a selection or sort order (there's only one result for the given URI)
+            // CAUTION: The query() method should be called from a separate thread to avoid blocking
+            // your app's UI thread. (For simplicity of the sample, this code doesn't do that.)
+            // Consider using {@link android.content.CursorLoader} to perform the query.
+            Cursor cursor = getContentResolver()
+                    .query(contactUri, projection, null, null, null);
+            cursor.moveToFirst();
+
+            // Retrieve the phone number from the NUMBER column
+            int column = cursor.getColumnIndex(Phone.NUMBER);
+            String number = cursor.getString(column);
+
+            // Do something with the phone number...
+        }
+    }
+}
+
+ +

Примечание. До выхода версии Android 2.3 (API-интерфейс уровня 9) для выполнения +запроса {@link android.provider.ContactsContract.Contacts Contacts Provider} (как +показанный выше) ваше приложение должно было декларировать разрешение {@link +android.Manifest.permission#READ_CONTACTS} (см. Безопасность и разрешения). Однако, +начиная с версии Android 2.3, приложение "Контакты/Люди" дает вашему приложению временное +разрешение на чтение данных Поставщика контактов при выводе результата. Это временное разрешение +действует только в отношении конкретного запрошенного контакта, так что нельзя запрашивать другой контакт, +кроме указанного объектом Intent {@link android.net.Uri}, если вы не хотите декларировать разрешение {@link +android.Manifest.permission#READ_CONTACTS}.

+ + + + + + + + + + + + + + + diff --git a/docs/html-intl/intl/ru/training/basics/intents/sending.jd b/docs/html-intl/intl/ru/training/basics/intents/sending.jd new file mode 100644 index 0000000..da147ee --- /dev/null +++ b/docs/html-intl/intl/ru/training/basics/intents/sending.jd @@ -0,0 +1,256 @@ +page.title=Направление пользователя в другое приложение +page.tags=объекты Intent +helpoutsWidget=true + +trainingnavtop=true + +@jd:body + + +
+ +
+ +

Одна из наиболее важных возможностей системы Android – способность приложений направлять пользователя в другое приложение +в зависимости от желаемого действия. Например, если +ваше приложение содержит адрес компании, который нужно показать на карте, вам не нужно встраивать в приложение операцию +вывода карты. Вместо этого можно создать запрос на просмотр адреса +с помощью {@link android.content.Intent}. В этом случае система Android запускает приложение, +которое показывает адрес на карте.

+ +

Как объяснялось в первом уроке Создание +первого приложения, объекты Intent нужно использовать для навигации между операциями в собственном приложении. Обычно +для этого используются явные объекты Intent, определяющие точное имя класса +компонента, который вы хотите запустить. Однако если вы хотите, чтобы действие (например +просмотр карты) выполнялось отдельным приложением, следует использовать неявный объект Intent.

+ +

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

+ + + +

Создание неявного объекта Intent

+ +

Неявные объекты Intent не декларируют имя класса запускаемого компонента, а декларируют +выполняемое действие. Действие указывает задачу, которую вы хотите выполнить, например просмотр, +правка, отправка или получение чего-либо. Объекты Intent часто также содержат данные, ассоциируемые +с действием, например адрес для просмотра или электронное сообщение для отправки. +В зависимости от того, какой объект Intent вы хотите создать, данные могут относиться к типу {@link android.net.Uri}, +одному из нескольких других типов данных, либо объекту могут вообще не требоваться данные.

+ +

Если ваши данные относятся к типу {@link android.net.Uri}, вы можете использовать простой конструктор {@link +android.content.Intent#Intent(String,Uri) Intent()} для определения действия и +данных.

+ +

Приведем пример создания объекта Intent для запуска телефонного звонка, в котором данные {@link +android.net.Uri} используются для указания телефонного номера:

+ +
+Uri number = Uri.parse("tel:5551234");
+Intent callIntent = new Intent(Intent.ACTION_DIAL, number);
+
+ +

Когда ваше приложение активирует этот объект Intent посредством вызова {@link android.app.Activity#startActivity +startActivity()}, приложение "Телефон" инициирует звонок на указанный номер телефона.

+ +

Вот еще несколько объектов Intent в сочетании с действиями и {@link android.net.Uri} парами +данных:

+ + + +

Другие виды неявных объектов Intent требуют дополнительные данные, предоставляющие разные типы данных, +например строки. Вы можете добавить один или несколько элементов дополнительных данных с помощью разных методов {@link +android.content.Intent#putExtra(String,String) putExtra()}.

+ +

Система по умолчанию определяет соответствующий тип MIME, который требует объект Intent на базе включенных данных +{@link android.net.Uri}. Если не включать {@link android.net.Uri} в объект +Intent, обычно нужно использовать {@link android.content.Intent#setType setType()} для указания типа +данных, связанного с объектом Intent. Установка типа MIME также определяет, какие виды +действий должен получать объект Intent.

+ +

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

+ + + +

Примечание. Очень важно определять объекты {@link +android.content.Intent} как можно более конкретно. Например, если вы хотите вывести изображение +с помощью объекта Intent {@link android.content.Intent#ACTION_VIEW}, вам следует указать тип MIME +{@code image/*}. Это не даст объекту Intent запускать приложения для просмотра других типов +данных (например картографические приложения).

+ + + +

Проверка наличия приложения для получения объекта Intent

+ +

Хотя платформа Android гарантирует выполнение объектов Intent во +встроенных приложениях (таких как "Телефон", "Электронная почта" или "Календарь"), перед активацией объекта Intent всегда следует добавлять шаг +проверки.

+ +

Внимание! Если вы активируете объект Intent, а на устройстве не +будет приложения, способного его обработать, ваше приложение закроется с ошибкой.

+ +

Чтобы убедиться в наличии операции, реагирующей на объект Intent, вызовите метод {@link +android.content.pm.PackageManager#queryIntentActivities queryIntentActivities()} для получения списка +операций, способных обработать ваш {@link android.content.Intent}. Если полученный в результате список {@link +java.util.List} не пустой, вы можете безопасно использовать данный объект Intent. Например:

+ +
+PackageManager packageManager = {@link android.content.Context#getPackageManager()};
+List activities = packageManager.queryIntentActivities(intent,
+        PackageManager.MATCH_DEFAULT_ONLY);
+boolean isIntentSafe = activities.size() > 0;
+
+ +

ЕслиisIntentSafe имеет значение true, то хотя бы одно приложение отреагирует на объект +Intent. Если же он имеет значение false, то на устройстве нет приложений для обработки данного объекта Intent.

+ +

Примечание. Такую проверку следует выполнять при первом +запуске операции на случай, если понадобится отключить функцию, обрабатывающую объект Intent, до того, как пользователь попытается использовать +ее. Если вам известно определенное приложение, которое может обработать данный объект Intent, вы можете указать ссылку, +по которой пользователь может загрузить приложение (посмотрите, как добавить ссылку на свой продукт в Google +Play).

+ + +

Запуск операции с объектом Intent

+ +
+ +

Рисунок 1. Пример диалогового окна выбора, +которое отображается, если объект Intent могут обработать разные приложения.

+
+ +

После создания {@link android.content.Intent} и установки дополнительной информации вызовите {@link +android.app.Activity#startActivity startActivity()} для отправки в систему. Если система +идентифицирует несколько операций, способных обработать объект Intent, она выводит для пользователя диалоговое окно +выбора приложения, как показано на рисунке 1. Если объект Intent может быть обработан +только одной операцией, система сразу же запускает ее.

+ +
+startActivity(intent);
+
+ +

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

+ +
+// Build the intent
+Uri location = Uri.parse("geo:0,0?q=1600+Amphitheatre+Parkway,+Mountain+View,+California");
+Intent mapIntent = new Intent(Intent.ACTION_VIEW, location);
+
+// Verify it resolves
+PackageManager packageManager = {@link android.content.Context#getPackageManager()};
+List<ResolveInfo> activities = packageManager.queryIntentActivities(mapIntent, 0);
+boolean isIntentSafe = activities.size() > 0;
+
+// Start an activity if it's safe
+if (isIntentSafe) {
+    startActivity(mapIntent);
+}
+
+ + + +

Отображение блока выбора приложения

+ +
+ +

Рисунок 2. Диалог выбора.

+
+ +

Обратите внимание, что при запуске операции посредством передачи {@link android.content.Intent} в {@link +android.app.Activity#startActivity startActivity()} и наличии нескольких приложений, реагирующих на +объект Intent, пользователь может выбрать, какое из этих приложений использовать по умолчанию (для этого нужно установить флажок в нижней +части диалогового окна; см. рисунок 1). Это удобно при выполнении действия, для которого +пользователь обычно хочет всегда использовать одно и то же приложение, например при открытии веб-страницы (пользователи +обычно используют один и тот же браузер) или создании снимка (пользователи обычно предпочитают одно и то же приложение камеры).

+ +

Однако бывает так, что выполняемое действие может быть обработано несколькими приложениями, и пользователь +каждый раз может использовать разные приложения — например, действие "Поделиться", для которого пользователи могут использовать разные +приложения, — и в этом случае ваше приложение должно отображать диалоговое окно выбора приложения, +как показано на рисунке 2. В диалоговом окне +выбора приложения пользователь должен при каждом запуске выбирать, какое приложение использовать для действия (пользователь не +может выбрать приложение по умолчанию).

+ +

Чтобы отобразить блок выбора приложения, создайте {@link android.content.Intent} с помощью {@link +android.content.Intent#createChooser createChooser()} и передайте его {@link +android.app.Activity#startActivity startActivity()}. Например:

+ +
+Intent intent = new Intent(Intent.ACTION_SEND);
+...
+
+// Always use string resources for UI text.
+// This says something like "Share this photo with"
+String title = getResources().getString(R.string.chooser_title);
+// Create intent to show chooser
+Intent chooser = Intent.createChooser(intent, title);
+
+// Verify the intent will resolve to at least one activity
+if (intent.resolveActivity(getPackageManager()) != null) {
+    startActivity(chooser);
+}
+
+ +

В результате отобразится диалоговое окно со списком приложений, которые могут отреагировать на объект Intent, переданный методу {@link +android.content.Intent#createChooser createChooser()} и используют указанный текст в качестве +заголовка диалога.

+ + + -- cgit v1.1