diff options
Diffstat (limited to 'docs/html-intl/intl/ru/guide')
31 files changed, 20548 insertions, 0 deletions
diff --git a/docs/html-intl/intl/ru/guide/components/activities.jd b/docs/html-intl/intl/ru/guide/components/activities.jd new file mode 100644 index 000000000000..5f55a3521350 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Операции +page.tags=операция,намерение +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> +<h2>Содержание документа</h2> +<ol> + <li><a href="#Creating">Создание операции</a> + <ol> + <li><a href="#UI">Реализация пользовательского интерфейса</a></li> + <li><a href="#Declaring">Объявление операции в манифесте</a></li> + </ol> + </li> + <li><a href="#StartingAnActivity">Запуск операции</a> + <ol> + <li><a href="#StartingAnActivityForResult">Запуск операции для получения результата</a></li> + </ol> + </li> + <li><a href="#ShuttingDown">Завершение операции</a></li> + <li><a href="#Lifecycle">Управление жизненным циклом операций</a> + <ol> + <li><a href="#ImplementingLifecycleCallbacks">Реализация обратных вызовов жизненного цикла</a></li> + <li><a href="#SavingActivityState">Сохранение состояния операции</a></li> + <li><a href="#ConfigurationChanges">Обработка изменений в конфигурации</a></li> + <li><a href="#CoordinatingActivities">Согласование операций</a></li> + </ol> + </li> +</ol> + +<h2>Ключевые классы</h2> +<ol> + <li>{@link android.app.Activity}</li> +</ol> + +<h2>См. также:</h2> +<ol> + <li><a href="{@docRoot}guide/components/tasks-and-back-stack.html">Задачи и стек +переходов назад</a></li> +</ol> + +</div> +</div> + + + +<p>{@link android.app.Activity} — это компонент приложения, который выдает экран, + и с которым пользователи могут взаимодействовать для выполнения каких-либо действий, например набрать номер телефона, сделать фото, отправить письмо или +просмотреть карту. Каждой операции присваивается окно для прорисовки соответствующего пользовательского интерфейса. Обычно окно +отображается во весь экран, однако его размер может быть меньше, и оно может размещаться поверх других +окон.</p> + +<p> Как правило, приложение состоит из нескольких операций, которые слабо +связаны друг с другом. Обычно одна из операций в приложении обозначается как «основная», +предлагаемая пользователю при первом запуске приложения. В свою очередь, каждая +операция может запустить другую операцию для выполнения различных действий. Каждый раз, когда +запускается новая операция, предыдущая операция останавливается, однако система сохраняет ее +в стеке («стек переходов назад»). При запуске новой операции она помещается в стек переходов назад и +отображается для пользователя. Стек переходов назад работает по принципу «последним вошёл — первым вышел», +поэтому после того как пользователь завершил текущую операцию и нажал кнопку <em>Назад</em>, текущая операция +удаляется из стека (и уничтожается), и возобновляется предыдущая операция. (Подробные сведения о стеке +переходов назад представлены в статье <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Задачи и стек +переходов назад</a>.)</p> + +<p>Когда операция останавливается по причине запуска новой операции, для уведомления об изменении ее состояния +используются методы обратного вызова жизненного цикла операции. +Существует несколько таких методов, которые может принимать операция вследствие изменения своего состояния +— создание операции, ее остановка, возобновление или уничтожение системой; также +каждый обратный вызов представляет возможность выполнить определенное действие, +подходящее для соответствующего изменения состояния. Например, в случае остановки операция должна освободить любые +крупные объекты, например, подключение к сети или базе данных. При возобновлении операции вы можете +повторно получить необходимые ресурсы и возобновить выполнение прерванных действий. Такие изменения состояния +являются частью жизненного цикла операции.</p> + +<p>Далее в этом документе рассматриваются основы создания и использования операций, +включая полное описание жизненного цикла операции, чтобы вы могли лучше понять, как следует управлять +переходами между различными состояниями операции.</p> + + + +<h2 id="Creating">Создание операции</h2> + +<p>Чтобы создать операцию, сначала необходимо создать подкласс класса {@link android.app.Activity} (или +воспользоваться существующим его подклассом). В таком подклассе необходимо реализовать методы обратного вызова, которые +вызывает система при переходе операции из одного состояния своего жизненного цикла в другое, например при +создании, остановке, возобновлении или уничтожении операции. Вот два наиболее важных метода +обратного вызова:</p> + +<dl> + <dt>{@link android.app.Activity#onCreate onCreate()}</dt> + <dd>Этот метод необходимо обязательно реализовать, поскольку система вызывает его при создании вашей +операции. В своей реализации вам необходимо инициализировать ключевые компоненты +операции. + Наиболее важно именно здесь вызвать {@link android.app.Activity#setContentView +setContentView()} для определения макета пользовательского интерфейса операции.</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>Система вызывает этот метод в качестве первого признака выхода пользователя из +операции (однако это не всегда означает, что операция будет уничтожена). Обычно именно здесь +необходимо применять любые изменения, которые должны быть сохранены помимо текущего сеанса работы пользователя (поскольку +пользователь может не вернуться назад).</dd> +</dl> + +<p>Существуют также и некоторые другие методы обратного вызова жизненного цикла, которые необходимо использовать для того, чтобы обеспечить +плавный переход между операциями, а также для обработки непредвиденных нарушений в работе операции, в результате которых она может быть +остановлена или даже уничтожена. Более подробное описание всех методов обратного вызова жизненного цикла представлены в разделе, +посвященном <a href="#Lifecycle">управлению жизненным циклом операций</a>.</p> + + + +<h3 id="UI">Реализация пользовательского интерфейса</h3> + +<p> Для реализации пользовательского интерфейса операции используется иерархия представлений —объектов, полученных из +класса {@link android.view.View}. Каждое представление отвечает за определенную прямоугольную область +окна операции и может реагировать на действия пользователей. Например, представлением может быть +кнопка, нажатие на которую приводит к выполнению определенного действия.</p> + +<p>В Android предусмотрен набор уже готовых представлений, которые можно использовать для создания дизайна макета и его +организации. Виджеты — это представления с визуальными (и интерактивными) элементами, например, +кнопками, текстовыми полями, чекбоксами или просто изображениями. Макеты — это представления, полученные из класса {@link +android.view.ViewGroup}, обеспечивающие уникальную модель компоновки для своих дочерних представлений, таких как линейный +макет, сетка или относительный макет. Также можно создать подкласс для классов {@link android.view.View} и +{@link android.view.ViewGroup} (или воспользоваться существующими подклассами), чтобы создать собственные виджеты и +макеты, и затем применить их к макету своей операции.</p> + +<p>Чаще всего для задания макета с помощью представлений используется XML-файл макета, сохраненный в +ресурсах приложения. Таким образом вы можете хранить дизайн пользовательского интерфейса отдельно от +исходного кода, который служит для задания поведения операции. Чтобы задать макет в качестве пользовательского интерфейса +операции, можно использовать метод {@link android.app.Activity#setContentView(int) setContentView()}, передав в него +идентификатор ресурса для макета. Однако вы также можете создать новые {@link android.view.View} в коде вашей +операции и создать иерархию представлений. Для этого вставьте {@link +android.view.View} в {@link android.view.ViewGroup}, а затем используйте этот макет, передав корневой объект +{@link android.view.ViewGroup} в метод {@link android.app.Activity#setContentView(View) +setContentView()}.</p> + +<p>Подробные сведения о создании пользовательского интерфейса см. в статье <a href="{@docRoot}guide/topics/ui/index.html">Пользовательский интерфейс</a>.</p> + + + +<h3 id="Declaring">Объявление операции в манифесте</h3> + +<p>Чтобы операция стала доступна системе, ее необходимо объявить в файле +манифеста. Для этого откройте файл манифеста и добавьте элемент <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> в +качестве дочернего для элемента +<a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a>. Например:</p> + +<pre> +<manifest ... > + <application ... > + <activity android:name=".ExampleActivity" /> + ... + </application ... > + ... +</manifest > +</pre> + +<p>Существует несколько других атрибутов, которые можно включить в этот элемент, чтобы определить такие +свойства, как метка операции, значок операции или тема оформления для пользовательского интерфейса операции. +Единственным обязательным атрибутом является <a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">{@code android:name}</a> +— он определяет имя класса операции. После +публикации вашего приложения вам не следует переименовывать его, поскольку это может нарушить некоторые +функциональные возможности приложения, например, ярлыки приложения (ознакомьтесь с публикацией <a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Вещи +, которые нельзя менять</a> в блоге разработчиков).</p> + +<p>Дополнительные сведения об объявлении операции +в манифесте см. в справке по элементу <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>.</p> + + +<h4>Использование фильтров намерений</h4> + +<p>Элемент <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a> также может задавать различные фильтры намерений — с помощью элемента <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a> — для объявления того, как другие компоненты приложения +могут активировать операцию.</p> + +<p>При создании нового приложения с помощью инструментов Android SDK в заготовке операции, +создаваемой автоматически, имеется фильтр намерений, который объявляет операцию. +Эта операция реагирует на выполнение «основного» действия, и ее следует поместить в категорию переходсредства запуска. Фильтр намерений +выглядит следующим образом.</p> + +<pre> +<activity android:name=".ExampleActivity" android:icon="@drawable/app_icon"> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> +</activity> +</pre> + +<p>Элемент <a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +<action>}</a> указывает, что это «основная» точка входа в приложение. Элемент <a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a>указывает, что эту операцию следует указать в +средстве запуска приложений системы (чтобы пользователи могли запускать эту операцию).</p> + +<p>Если приложение планируется создать самодостаточным и запретить другим приложениям активировать +его операции, то других фильтров намерений создавать не нужно. В этом случае только в одной операции +должно иметься «основное» действие, и ее следует поместить в категорию средства запуска, как в примере выше. В операциях, +которые не должны быть доступны для других приложений, не следует включать фильтры намерений. +Вы можете самостоятельно запустить такие операции с помощью явных намерений (как описывается в следующем разделе).</p> + +<p>Однако, если вам необходимо, чтобы операция реагировала на неявные намерения, +получаемые от других приложений (а также из вашего приложения), для операции необходимо определить дополнительные фильтры +намерений. Для каждого типа намерения, на который необходимо реагировать, необходимо указать объект <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a>, включающий элемент +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +<action>}</a> и необязательный элемент <a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a> или <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code +<data>}</a> (или оба этих элемента). Эти элементы определяют тип намерения, на который может реагировать +ваша операция.</p> + +<p>Дополнительные сведения о том, как операции могут реагировать на намерения, приведены в статье <a href="{@docRoot}guide/components/intents-filters.html">Намерения и фильтры намерений</a>. +</p> + + + +<h2 id="StartingAnActivity">Запуск операции</h2> + +<p>Для запуска другой операции достаточно вызвать метод {@link android.app.Activity#startActivity +startActivity()}, передав в него объект {@link android.content.Intent}, который описывает запускаемую +операцию. В намерении указывается либо точная операция для запуска, либо описывается +тип операции, которую вы хотите выполнить (после чего система выбирает для вас подходящую +операцию, которая +может даже находиться в другом приложении). Намерение также может содержать небольшой объем данных, +которые будут использоваться запущенной операцией.</p> + +<p>При работе с собственным приложением зачастую требуется лишь запустить нужную операцию. + Для этого необходимо создать намерение, которое явно определяет требуемую операцию +с помощью имени класса. Ниже представлен пример запуска одной операцией другой операции с именем {@code +SignInActivity}.</p> + +<pre> +Intent intent = new Intent(this, SignInActivity.class); +startActivity(intent); +</pre> + +<p>Однако в вашем приложении также может потребоваться выполнить некоторое действие, например, отправить письмо, текстовое +сообщение или обновить статус, используя данные из вашей операции. В этом случае в вашем приложении могут +отсутствовать такие действия, поэтому вы можете воспользоваться операциями +из других приложений, имеющихся на устройстве, которые могут выполнять требуемые действия. Как раз в этом случае +намерения особенно полезны — можно создать намерение, которое описывает необходимое действие, +после чего система +запускает его из другого приложения. При наличии +нескольких операций, которые могут обработать намерение, пользователь может выбирать, какое из них следует использовать. Например, +если пользователю требуется предоставить возможность отправить электронное письмо, можно создать +следующее намерение:</p> + +<pre> +Intent intent = new Intent(Intent.ACTION_SEND); +intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); +startActivity(intent); +</pre> + +<p>Дополнительный компонент {@link android.content.Intent#EXTRA_EMAIL}, добавленный в намерение, представляет собой строковый массив +адресов электронной почты для отправки письма. Когда почтовая программа реагирует на это +намерение, она считывает дополнительно добавленный строковый массив и помещает имеющиеся в нем адреса в поле получателя +в окне создания письма. При этом запускается операция почтовой программы, а после того, как +пользователь завершит требуемые действия, возобновляется ваша операция.</p> + + + + +<h3 id="StartingAnActivityForResult">Запуск операции для получения результата</h3> + +<p>В некоторых случаях после запуска операции может потребоваться получить результат. Для этого +вызовите метод {@link android.app.Activity#startActivityForResult +startActivityForResult()} (вместо {@link android.app.Activity#startActivity +startActivity()}). Чтобы получить результат после выполнения последующей +операции, реализуйте метод обратного +вызова {@link android.app.Activity#onActivityResult onActivityResult()}. По завершении последующей операции она возвращает результат в объекте {@link +android.content.Intent} +в вызванный метод {@link android.app.Activity#onActivityResult onActivityResult()}.</p> + +<p>К примеру, пользователю потребуется выбрать один из контактов, чтобы ваша операция могла +выполнить некоторые действия с информацией об этом контакте. Ниже представлен пример создания такого намерения +и обработки результата.</p> + +<pre> +private void pickContact() { + // Create an intent to "pick" a contact, as defined by the content provider URI + Intent intent = new Intent(Intent.ACTION_PICK, Contacts.CONTENT_URI); + startActivityForResult(intent, PICK_CONTACT_REQUEST); +} + +@Override +protected void onActivityResult(int requestCode, int resultCode, Intent data) { + // If the request went well (OK) and the request was PICK_CONTACT_REQUEST + if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT_REQUEST) { + // Perform a query to the contact's content provider for the contact's name + Cursor cursor = getContentResolver().query(data.getData(), + new String[] {Contacts.DISPLAY_NAME}, null, null, null); + if (cursor.moveToFirst()) { // True if the cursor is not empty + int columnIndex = cursor.getColumnIndex(Contacts.DISPLAY_NAME); + String name = cursor.getString(columnIndex); + // Do something with the selected contact's name... + } + } +} +</pre> + +<p>В этом примере демонстрируется базовая логика, которой следует руководствоваться при использовании метода {@link +android.app.Activity#onActivityResult onActivityResult()} для обработки результата +выполнения операции. Первое условие проверяет, успешен ли запрос, и если он успешен, то результат для +{@code resultCode} будет {@link android.app.Activity#RESULT_OK}; также проверяется, известен ли запрос, +для которого получен этот результат, и в этом случае {@code requestCode} соответствует +второму параметру, отправленному в метод {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Здесь код обрабатывает результат выполнения операции путем запроса данных, +возвращенных в {@link android.content.Intent} (параметр {@code data}).</p> + +<p>При этом {@link +android.content.ContentResolver} выполняет запрос к поставщику контента, который возвращает объект +{@link android.database.Cursor}, обеспечивающий считывание запрошенных данных. Дополнительные сведения представлены в статье +<a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a>.</p> + +<p>Дополнительные сведения об использовании намерений см. в статье <a href="{@docRoot}guide/components/intents-filters.html">Намерения и фильтры +намерений</a>.</p> + + +<h2 id="ShuttingDown">Завершение операции</h2> + +<p>Для завершения операции достаточно вызвать ее метод {@link android.app.Activity#finish +finish()}. Также для завершения отдельной операции, запущенной ранее, можно вызвать метод +{@link android.app.Activity#finishActivity finishActivity()}.</p> + +<p class="note"><strong>Примечание.</strong> В большинстве случаев вам не следует явно завершать операцию +с помощью этих методов. Как указано в следующем разделе, посвященном управлению жизненным циклом операции, система +Android выполняет такое управление за вас, поэтому вам не нужно завершать ваши +собственные операции. Вызов этих методов может отрицательно сказаться на ожидаемом +поведении приложения. Их следует использовать исключительно тогда, когда вы абсолютно не хотите, чтобы пользователь возвращался к этому +экземпляру операции.</p> + + +<h2 id="Lifecycle">Управление жизненным циклом операций</h2> + +<p>Управление жизненным циклом операций путем реализации методов обратного вызова +имеет важное значение при разработке надежных +и гибких приложений. На жизненный цикл операции напрямую влияют его связи +с другими операциями, его задачами и стеком переходов назад.</p> + +<p>Существует всего три состояния операции:</p> + +<dl> + <dt><i>Возобновлена</i></dt> + <dd>Операция выполняется на переднем плане экрана и отображается для пользователя. (Это состояние +также иногда называется «Выполняется».)</dd> + + <dt><i>Приостановлена</i></dt> + <dd>На переднем фоне выполняется другая операция, которая отображается для пользователя, однако эта операция по-прежнему не скрыта. То есть +поверх текущей операции отображается другая операция, частично прозрачная или не занимающая +полностью весь экран. Приостановленная операция полностью активна (объект {@link android.app.Activity} +по-прежнему находится в памяти, в нем сохраняются все сведения о состоянии и информация об элементах, и он также остается связанным с +диспетчером окон), однако в случае острой нехватки памяти система может завершить ее.</dd> + + <dt><i>Остановлена</i></dt> + <dd>Операция полностью перекрывается другой операцией (теперь она выполняется в +фоновом режиме). Остановленная операция по-прежнему активна (объект {@link android.app.Activity} +по-прежнему находится в памяти, в нем сохраняются все сведения о состоянии и информация об элементах, но объект больше <em>не</em> +связан с диспетчером окон). Однако операция больше не видна пользователю, и в случае нехватки памяти система +может завершить ее.</dd> +</dl> + +<p>Если операция приостановлена или полностью остановлена, система может очистить ее из памяти путем +завершения самой операции (с помощью метода {@link android.app.Activity#finish finish()}) или просто завершить ее +процесс. В случае повторного открытия операции (после ее завершения) ее потребуется создать +полностью.</p> + + + +<h3 id="ImplementingLifecycleCallbacks">Реализация обратных вызовов жизненного цикла</h3> + +<p>При переходе операции из одного вышеописанного состояния в другое, уведомления об этом +реализуются через различные методы обратного вызова. Все методы обратного вызова представляют собой привязки, которые +можно переопределить для выполнения подходящего действия при изменении состояния операции. Указанная ниже базовая +операция включает каждый из основных методов жизненного цикла.</p> + + +<pre> +public class ExampleActivity extends Activity { + @Override + public void {@link android.app.Activity#onCreate onCreate}(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // The activity is being created. + } + @Override + protected void {@link android.app.Activity#onStart onStart()} { + super.onStart(); + // The activity is about to become visible. + } + @Override + protected void {@link android.app.Activity#onResume onResume()} { + super.onResume(); + // The activity has become visible (it is now "resumed"). + } + @Override + protected void {@link android.app.Activity#onPause onPause()} { + super.onPause(); + // Another activity is taking focus (this activity is about to be "paused"). + } + @Override + protected void {@link android.app.Activity#onStop onStop()} { + super.onStop(); + // The activity is no longer visible (it is now "stopped") + } + @Override + protected void {@link android.app.Activity#onDestroy onDestroy()} { + super.onDestroy(); + // The activity is about to be destroyed. + } +} +</pre> + +<p class="note"><strong>Примечание.</strong> При реализации этих методов жизненного цикла +всегда вызывайте реализацию суперкласса, прежде чем выполнять какие-либо действия, как показано в примерах выше.</p> + +<p>Вместе все эти методы определяют весь жизненный цикл операции. С помощью реализации этих +методов можно отслеживать три вложенных цикла в жизненном цикле операции: </p> + +<ul> +<li><b>Весь жизненный цикл</b> операции происходит между вызовом метода {@link +android.app.Activity#onCreate onCreate()} и вызовом метода {@link +android.app.Activity#onDestroy}. Ваша операция должна выполнить настройку +«глобального» состояния (например, определение макета) в методе {@link android.app.Activity#onCreate onCreate()}, а затем +освободить все оставшиеся в {@link android.app.Activity#onDestroy} ресурсы. Например, если в вашей операции +имеется поток, выполняющийся в фоновом режиме, для загрузки данных по сети, операция может создать +такой поток в методе {@link android.app.Activity#onCreate onCreate()}, а затем остановить его в методе {@link +android.app.Activity#onDestroy}.</li> + +<li><p><b>Видимый жизненный цикл</b> операции происходит между вызовами методов {@link +android.app.Activity#onStart onStart()} и {@link +android.app.Activity#onStop onStop()}. В течение этого времени операция +отображается на экране, где пользователь может взаимодействовать с ней. Например, метод {@link android.app.Activity#onStop onStop()} вызывается в +случае, когда запускается новая операция, а текущая больше не отображается. В промежутке между вызовами этих двух методов можно +сохранить ресурсы, необходимые для отображения операции для пользователя. Например, можно зарегистрировать объект +{@link android.content.BroadcastReceiver} в методе {@link +android.app.Activity#onStart onStart()} для отслеживания изменений, влияющих на пользовательский интерфейс, а затем отменить его регистрацию +в методе {@link android.app.Activity#onStop onStop()}, когда пользователь больше не видит +отображаемого. В течение всего жизненного цикла операции система может несколько раз вызывать методы {@link android.app.Activity#onStart onStart()} и {@link +android.app.Activity#onStop onStop()}, поскольку +операция то отображается для пользователя, то скрывается от него.</p></li> + +<li><p><b>Жизненный цикл операции, выполняемый на переднем плане</b>, происходит между вызовами методов {@link +android.app.Activity#onResume onResume()} и {@link android.app.Activity#onPause +onPause()}. В течение этого времени операция выполняется на фоне всех прочих операций и +отображается для пользователя. Операция может часто уходить в фоновый режим и выходить из него — например, +метод {@link android.app.Activity#onPause onPause()} вызывается при переходе устройства в спящий режим или +при появлении диалогового окна. Поскольку переход в это состояние может выполняться довольно часто, код в этих двух методах +должен быть легким, чтобы не допустить медленных переходов и не заставлять пользователя ждать.</p></li> +</ul> + +<p>На рисунке 1 иллюстрируются проходы и пути, которые операция может пройти между состояниями. +Прямоугольниками обозначены методы обратного вызова, которые можно реализовать для выполнения действий между переходами операции из одного +состояния в другое. <p> + +<img src="{@docRoot}images/activity_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Жизненный цикл операции.</p> + +<p>Эти же методы жизненного цикла перечислены в таблице 1, в которой подробно описан каждый метод +обратного вызова и указано его место в +жизненном цикле операции в целом, включая сведения о том, может ли система завершить операцию по завершении +метода обратного вызова.</p> + +<p class="table-caption"><strong>Таблица 1.</strong> Сводные сведения о методах обратного вызова +жизненного цикла операции.</p> + +<table border="2" width="85%" frame="hsides" rules="rows"> +<colgroup align="left" span="3"></colgroup> +<colgroup align="left"></colgroup> +<colgroup align="center"></colgroup> +<colgroup align="center"></colgroup> + +<thead> +<tr><th colspan="3">Метод</th> <th>Описание</th> <th>Завершаемый?</th> <th>Следующий</th></tr> +</thead> + +<tbody> +<tr> + <td colspan="3" align="left"><code>{@link android.app.Activity#onCreate onCreate()}</code></td> + <td>Вызывается при первом создании операции. + Здесь необходимо настроить все обычные статические элементы — +создать представления, привязать данные и т. д. Этот метод передает +объект Bundle, содержащий предыдущее состояние операции (если +такое состояние было зафиксировано ранее; см. раздел <a href="#actstate">Сохранение состояния +операции</a>). + <p>За ним всегда следует метод {@code onStart()}.</p></td> + <td align="center">Нет</td> + <td align="center">{@code onStart()}</td> +</tr> + +<tr> + <td rowspan="5" style="border-left: none; border-right: none;"> </td> + <td colspan="2" align="left"><code>{@link android.app.Activity#onRestart +onRestart()}</code></td> + <td>Вызывается после остановки операции непосредственно перед ее +повторным запуском. + <p>За ним всегда следует метод {@code onStart()}.</p></td> + <td align="center">Нет</td> + <td align="center">{@code onStart()}</td> +</tr> + +<tr> + <td colspan="2" align="left"><code>{@link android.app.Activity#onStart onStart()}</code></td> + <td>Вызывается непосредственно перед тем, как операция становится видимой для пользователя. + <p>За ним следует метод {@code onResume()}, если операция +переходит на передний план, или метод {@code onStop()}, если она становится скрытой.</p></td> + <td align="center">Нет</td> + <td align="center">{@code onResume()} <br/>или<br/> {@code onStop()}</td> +</tr> + +<tr> + <td rowspan="2" style="border-left: none;"> </td> + <td align="left"><code>{@link android.app.Activity#onResume onResume()}</code></td> + <td>Вызывается непосредственно перед тем, как операция +начинает взаимодействие с пользователем. На этом этапе операция находится в самом +верху стека операций, и в нее поступают данные, вводимые пользователем. + <p>За ним всегда следует метод {@code onPause()}.</p></td> + <td align="center">Нет</td> + <td align="center">{@code onPause()}</td> +</tr> + +<tr> + <td align="left"><code>{@link android.app.Activity#onPause onPause()}</code></td> + <td>Вызывается, когда система собирается возобновить +другую операцию. Этот метод обычно используется для записи несохраненных изменений +в постоянное место хранения данных, остановки анимаций и других элементов, которые могут +использовать ресурсы ЦП и т. д. Здесь крайне важна оперативность, поскольку +следующая операция не будет возобновлена до тех пор, пока она не будет возвращена на передний план. + <p>За ним следует либо метод {@code onResume()}, если операция +возвращается на передний план, либо метод {@code onStop()}, если операция +становится скрытой для пользователя.</td> + <td align="center"><strong style="color:#800000">Да</strong></td> + <td align="center">{@code onResume()} <br/>или<br/> {@code onStop()}</td> +</tr> + +<tr> + <td colspan="2" align="left"><code>{@link android.app.Activity#onStop onStop()}</code></td> + <td>Вызывается в случае, когда операция больше не отображается для пользователя. Это может +произойти по причине того, что операция уничтожена, или ввиду возобновления поверх нее другой операции +(существующей или новой). + <p>За ним следует либо метод {@code onRestart()}, если +операция возобновляет взаимодействие с пользователем, либо метод +{@code onDestroy()}, если операция переходит в фоновый режим.</p></td> + <td align="center"><strong style="color:#800000">Да</strong></td> + <td align="center">{@code onRestart()} <br/>или<br/> {@code onDestroy()}</td> +</tr> + +<tr> + <td colspan="3" align="left"><code>{@link android.app.Activity#onDestroy +onDestroy()}</code></td> + <td>Вызывается перед тем, как операция будет уничтожена. Это финальный вызов, +который получает операция. Его можно вызвать либо по причине +завершения операции (вызов метода <code>{@link android.app.Activity#finish + finish()}</code>), либо ввиду временного уничтожения системой этого +экземпляра операции с целью освободить место. Чтобы различить эти два +сценария, используется метод <code>{@link + android.app.Activity#isFinishing isFinishing()}</code>.</td> + <td align="center"><strong style="color:#800000">Да</strong></td> + <td align="center"><em>Ничего</em></td> +</tr> +</tbody> +</table> + +<p>В столбце «Завершаемый?» указывается, может ли система +в любое время завершить процесс, содержащий операцию, <em>после возвращения метода</em> без +выполнения другой строки кода операции. Для трех методов в этом столбце указано «Да»: ({@link +android.app.Activity#onPause +onPause()}, {@link android.app.Activity#onStop onStop()} и {@link android.app.Activity#onDestroy +onDestroy()}). Поскольку метод {@link android.app.Activity#onPause onPause()} является первым из +этих трех после создания операции, метод {@link android.app.Activity#onPause onPause()} является +последним, который гарантированно будет вызван перед тем, как процесс <em>можно будет</em> завершить; если +системе потребуется срочно восстановить память в случае аварийной ситуации, то методы {@link +android.app.Activity#onStop onStop()} и {@link android.app.Activity#onDestroy onDestroy()} вызвать +не удастся. Поэтому следует воспользоваться {@link android.app.Activity#onPause onPause()}, чтобы записать +критически важные данные (такие как правки пользователя) в хранилище постоянных данных. Однако следует внимательно подходить к выбору информации, +которую необходимо сохранить во время выполнения метода {@link android.app.Activity#onPause onPause()}, поскольку любая блокировка +процедур в этом методе может вызвать блокирование перехода к следующей операции и тормозить работу +пользователя.</p> + +<p> Методы, для которых в столбце <b>Завершаемый?</b> указано «Нет», защищают процесс, содержащий операцию +, от завершения сразу с момента их вызова. Поэтому завершить операцию +можно в период между возвратом {@link android.app.Activity#onPause onPause()} и вызовом +{@link android.app.Activity#onResume onResume()}. Его снова не удастся завершить, пока снова не будет вызван и возвращен +{@link android.app.Activity#onPause onPause()}. </p> + +<p class="note"><strong>Примечание.</strong> Операцию, которую технически невозможно завершить в соответствии с определением +в таблице 1, по-прежнему может завершить система, однако это может произойти только в +чрезвычайных ситуациях, когда нет другой возможности. Случаи, когда возможно завершение операции, +более подробно рассматриваются в статье<a href="{@docRoot}guide/components/processes-and-threads.html">Процессы и +потоки</a>.</p> + + +<h3 id="SavingActivityState">Сохранение состояния операции</h3> + +<p>В обзорных сведениях об <a href="#Lifecycle">управлении жизненным циклом операции</a> кратко упоминается, +что +в случае приостановки или полной остановки операции ее состояние сохраняется. Это действительно так, поскольку +объект {@link android.app.Activity} при этом по-прежнему находится в памяти +, и вся информация о ее элементах и текущем состоянии по-прежнему активна. Поэтому любые +вносимые пользователем в операции изменения сохраняются, и когда операция возвращается на передний план +(когда она «возобновляется»), эти изменения остаются в этом объекте.</p> + +<p>Однако когда система уничтожает операцию в целях восстановления памяти, объект {@link +android.app.Activity} уничтожается, в результате чего системе не удается просто восстановить состояние операции для взаимодействия +с ней. Вместо этого системе необходимо повторно создать объект {@link android.app.Activity}, если пользователь +возвращается к нему. Но пользователю неизвестно, +что система уже уничтожила операцию и создала ее повторно, поэтому, возможно, +он ожидает, что операция осталась прежней. В этой ситуации можно обеспечить +сохранение важной информации о состоянии операции путем реализации дополнительного +метода обратного вызова, который позволяет сохранить информацию о вашей операции: {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}.</p> + +<p>Прежде чем сделать операцию доступной для уничтожения, система вызывает метод +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Система передает в этот метод объект +{@link android.os.Bundle}, в котором можно сохранить +информацию о состоянии операции в виде пар «имя-значение», используя для этого такие методы, как {@link +android.os.Bundle#putString putString()} и {@link +android.os.Bundle#putInt putInt()}. Затем, если система завершает процесс +вашего приложения и пользователь возвращается к вашей операции, система повторно создает операцию и передает объект +{@link android.os.Bundle} в оба метода: {@link android.app.Activity#onCreate onCreate()} и {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}. С помощью любого из этих +методов можно извлечь из объекта {@link android.os.Bundle} сохраненную информацию о состоянии операции и восстановить +ее. Если такая информация отсутствует, то объект {@link +android.os.Bundle} передается с нулевым значением (это происходит в случае, когда операция +создается в первый раз).</p> + +<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Два способа возврата операции к отображению для пользователя +в неизмененном состоянии: уничтожение операции с последующим ее повторным созданием, когда операция должна восстановить свое +ранее сохраненное состояние, или остановка операции и ее последующее восстановление в неизмененном +состоянии.</p> + +<p class="note"><strong>Примечание.</strong> Нет никаких гарантий, что метод {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} будет вызван до того, как ваша +операция будет уничтожена, поскольку существуют случаи, когда нет необходимости сохранять состояние +(например, когда пользователь покидает вашу операцию нажатием кнопки <em>Назад</em>, +явным образом +закрывая ее). Если система вызывает метод {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()}, она делает это до вызова метода {@link +android.app.Activity#onStop onStop()} и, возможно, перед вызовом метода {@link android.app.Activity#onPause +onPause()}.</p> + +<p>Однако, даже если вы ничего не предпринимаете и не реализуете метод {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}, часть состояния операции восстанавливается +реализацией по умолчанию метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} класса {@link android.app.Activity}. В частности, реализация +по умолчанию вызывает соответствующий метод {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} для каждого объекта {@link +android.view.View} в макете, благодаря чему каждое представление может предоставлять ту информацию о себе, +которую следует сохранить. Почти каждый виджет в платформе Android реализует этот метод необходимым +для себя способом так, что любые видимые изменения в пользовательском интерфейсе автоматически сохраняются и восстанавливаются при повторном +создании операции. Например, виджет {@link android.widget.EditText} сохраняет любой текст, +введенный пользователем, а виджет {@link android.widget.CheckBox} сохраняет информацию о том, был +ли установлен флажок. От вас требуется лишь указать уникальный идентификатор (с атрибутом <a href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a>) +для каждого виджета, состояние которого необходимо сохранить. Если виджету не присвоен идентификатор, то системе +не удастся сохранить его состояние.</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<p>Вы также можете явно отключить сохранение информации о состоянии представления в макете. Для этого задайте для атрибута +{@link android.R.attr#saveEnabled android:saveEnabled} значение {@code "false"} или вызовите метод +{@link android.view.View#setSaveEnabled setSaveEnabled()}. Обычно отключать сохранение такой информации не требуется +, однако это может потребоваться в случаях, когда восстановить состояние пользовательского интерфейса операции необходимо другим образом.</p> +</div> +</div> + +<p>Несмотря на то что реализация метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} по умолчанию позволяет сохранить полезную информацию о +пользовательском интерфейсе вашей операции, вам по-прежнему может потребоваться переопределить ее для сохранения дополнительной информации. +Например, может потребоваться сохранить значения элементов, которые изменялись в течение жизненного цикла операции (которые могут +коррелировать со значениями, восстановленными в пользовательском интерфейсе, однако элементы, содержащие эти значения пользовательского интерфейса, по умолчанию не +были восстановлены).</p> + +<p>Поскольку реализация метода {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} по умолчанию позволяет сохранить состояние пользовательского интерфейса, в случае +, если вы переопределите метод с целью сохранить дополнительную информацию о состоянии, перед выполнением каких-либо действий вы всегда можете вызвать реализацию +суперкласса для метода +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()}. Точно так же реализацию суперкласса {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} следует вызывать в случае ее переопределения, чтобы реализация по умолчанию могла сохранить +состояния представлений.</p> + +<p class="note"><strong>Примечание.</strong> Поскольку вызов метода {@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()} не гарантируется, +вам следует использовать его только для записи переходного состояния операции (состояние +пользовательского интерфейса) — никогда не используйте его для хранения постоянных данных. Вместо этого используйте метод {@link +android.app.Activity#onPause onPause()} для сохранения постоянных данных (например, тех, которые следует сохранить в +базу данных), когда пользователь покидает операцию.</p> + +<p>Отличный способ проверить возможность вашего приложения восстанавливать свое состояние — это просто повернуть +устройство для изменения ориентации экрана. При изменении ориентации экрана система +уничтожает и повторно создает операцию, чтобы применить альтернативные ресурсы, которые могут быть +доступны для новой конфигурации экрана. Только по одной этой причине крайне важно, чтобы ваша операция +могла полностью восстанавливать свое состояние при ее повторном создании, поскольку пользователи постоянно работают с +приложениями в разных ориентациях экрана.</p> + + +<h3 id="ConfigurationChanges">Обработка изменений в конфигурации</h3> + +<p>Некоторые конфигурации устройств могут изменяться в режиме выполнения +(например, ориентация экрана, доступность клавиатуры и язык). В таких случаях Android повторно создает выполняющуюся операцию +(система сначала вызывает метод {@link android.app.Activity#onDestroy}, а затем сразу же вызывает метод {@link +android.app.Activity#onCreate onCreate()}). Такое поведение позволяет +приложению учитывать новые конфигурации путем автоматической перезагрузки в приложение +альтернативных ресурсов, которые вы предоставили (например, различные макеты для +разных ориентаций и экранов разных размеров).</p> + +<p>Если операция разработана должным образом и должным образом поддерживает перезапуск после изменения ориентации экрана и +восстановление своего состояния, как описано выше, ваше приложение можно считать более устойчивым к другим +непредвиденным событиям в жизненном цикле операции.</p> + +<p>Лучший способ обработки такого перезапуска +— сохранить и восстановить состояние операции с помощью методов {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} и {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} (или {@link +android.app.Activity#onCreate onCreate()}), как описано в предыдущем разделе.</p> + +<p>Дополнительные сведения об изменениях конфигурации, происходящих в режиме выполнения, и о способах их обработки +представлены в руководстве<a href="{@docRoot}guide/topics/resources/runtime-changes.html">Обработка изменений в режиме +выполнения</a>.</p> + + + +<h3 id="CoordinatingActivities">Согласование операций</h3> + + <p>Когда одна операция запускает другую, в жизненных циклах обеих из них происходит переход из одного состояния в другое. Первая операция +приостанавливается и заврешается (однако она не будет остановлена, если она по-прежнему видима на фоне), а вторая +операция создается. В случае, если эти операции обмениваются данным, сохраненными на диске или в другом месте, важно понимать, +что первая операция не останавливается полностью до тех пор, пока не будет создана вторая операция. +Наоборот, процесс запуска второй операции накладывается на процесс остановки первой +операции.</p> + +<p>Порядок обратных вызовов жизненного цикла четко определен, в частности, когда в одном и том же процессе находятся две операции +, и одна из них запускает другую. Ниже представлен порядок выполнения действий в случае, когда операция +А запускает операцию Б. </p> + +<ol> +<li>Выполняется метод {@link android.app.Activity#onPause onPause()} операции А.</li> + +<li>Последовательно выполняются методы {@link android.app.Activity#onCreate onCreate()}, {@link +android.app.Activity#onStart onStart()} и {@link android.app.Activity#onResume onResume()} +операции Б. (Теперь для пользователя отображается операция Б.)</li> + +<li>Затем, если операция A больше не отображается на экране, выполняется ее метод {@link +android.app.Activity#onStop onStop()}.</li> +</ol> + + <p>Такая предсказуемая последовательность выполнения обратных вызовов жизненного цикла позволяет управлять переходом +информации из одной операции в другую. Например, если после остановки первой операции требуется выполнить запись в базу данных, +чтобы следующая операция могла считать их, то запись в базу данных следует выполнить +во время выполнения метода {@link android.app.Activity#onPause onPause()}, а не во время выполнения метода {@link +android.app.Activity#onStop onStop()}.</p> + +<!-- +<h2>Beginner's Path</h2> + +<p>For more information about how Android maintains a history of activities and +enables user multitasking, continue with the <b><a +href="{@docRoot}guide/components/tasks-and-back-stack.html">Tasks and Back +Stack</a></b> document.</p> +--> diff --git a/docs/html-intl/intl/ru/guide/components/bound-services.jd b/docs/html-intl/intl/ru/guide/components/bound-services.jd new file mode 100644 index 000000000000..ad690b776ef0 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/bound-services.jd @@ -0,0 +1,658 @@ +page.title=Привязанные службы +parent.title=Службы +parent.link=services.html +@jd:body + + +<div id="qv-wrapper"> +<ol id="qv"> +<h2>Содержание документа</h2> +<ol> + <li><a href="#Basics">Основы</a></li> + <li><a href="#Creating">Создание привязанной службы</a> + <ol> + <li><a href="#Binder">Расширение класса Binder</a></li> + <li><a href="#Messenger">Использование объекта Messenger</a></li> + </ol> + </li> + <li><a href="#Binding">Привязка к службе</a></li> + <li><a href="#Lifecycle">Управление жизненным циклом привязанной службы</a></li> +</ol> + +<h2>Ключевые классы</h2> +<ol> + <li>{@link android.app.Service}</li> + <li>{@link android.content.ServiceConnection}</li> + <li>{@link android.os.IBinder}</li> +</ol> + +<h2>Примеры</h2> +<ol> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code + RemoteService}</a></li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code + LocalService}</a></li> +</ol> + +<h2>См. также:</h2> +<ol> + <li><a href="{@docRoot}guide/components/services.html">Службы</a></li> +</ol> +</div> + + +<p>Привязанная служба предоставляет интерфейс типа клиент-сервер. Привязанная служба позволяет компонентам приложения +(например, операциям) взаимодействовать со службой, отправлять запросы, получать результаты и даже делать то же самое с другими процессами через +IPC. Привязанная служба обычно работает, пока другой компонент приложения +привязан к ней. Она не работает постоянно в фоновом режиме.</p> + +<p>В этом документе рассказывается, как создать привязанную службу, включая привязку +службы к другим компонентам приложения. Также рекомендуем обратиться к статье <a href="{@docRoot}guide/components/services.html">Службы</a>, чтобы узнать подробнее +о службах, например, об организации отправки уведомлений от службы, настройке службы +на работу на переднем плане и т. д.</p> + + +<h2 id="Basics">Основы</h2> + +<p>Привязанная служба представляет собой реализацию класса {@link android.app.Service}, которая позволяет +другим приложениям привязываться к нему и взаимодействовать с ним. Чтобы обеспечить привязку службы +, сначала необходимо реализовать метод обратного вызова {@link android.app.Service#onBind onBind()}. Этот +метод возвращает объект {@link android.os.IBinder}. Он определяет программный интерфейс, +с помощью которого клиенты могут взаимодействовать со службой.</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>Привязка к запущенной службе</h3> + +<p>Как указано в статье <a href="{@docRoot}guide/components/services.html">Службы</a>, +можно создать службу, которая одновременно и запущена, и привязана. Это означает, что службу можно запустить +путем вызова метода {@link android.content.Context#startService startService()}, который позволяет службе +работать неограниченное время, а также позволяет клиентам привязываться к ней с помощью вызова метода {@link +android.content.Context#bindService bindService()}. + <p>Если разрешить запуск и привязку службы, то после ее запуска +система <em>не</em> уничтожает ее после отмены всех привязок клиентов. Вместо этого необходимо +явным образом остановить службу, вызвав метод {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}.</p> + +<p>Несмотря на то, что обычно необходимо реализовывать либо метод {@link android.app.Service#onBind onBind()}, +<em>либо метод</em> {@link android.app.Service#onStartCommand onStartCommand()}, в некоторых случаях требуется +реализовать оба этих метода. Например, в музыкальном проигрывателе может оказаться полезным разрешить выполнение службы в течение +неограниченного времени, а также обеспечить ее привязку. Таким образом, операция может запустить службу для воспроизведения музыки +, которая будет воспроизводиться даже после выхода пользователя из приложения. После того возвращения +пользователя к приложению операция может отменить привязку к службе, чтобы вернуть управление воспроизведением.</p> + +<p>Обязательно ознакомьтесь с разделом, посвященным <a href="#Lifecycle">управлению жизненным циклом привязанной +службы</a>. В нем представлены подробные сведения о жизненном цикле службы, привязанной к уже +запущенной службе.</p> +</div> +</div> + +<p>Для привязки к службе клиент может вызвать метод {@link android.content.Context#bindService +bindService()}. После привязки он должен предоставить реализацию метода {@link +android.content.ServiceConnection}, который служит для отслеживания подключения к службе. Метод {@link +android.content.Context#bindService bindService()} возвращается незамедлительно без значения, однако +, когда система Android устанавливает подключение +клиент-служба, она вызывает метод {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} для {@link +android.content.ServiceConnection}, чтобы выдать объект {@link android.os.IBinder}, который +клиент может использовать для взаимодействия со службой.</p> + +<p>Одновременно к службе могут подключиться сразу несколько клиентов. Однако система вызывает метод +{@link android.app.Service#onBind onBind()} вашей службы для получения объекта {@link android.os.IBinder} +только при первой привязке клиента. После чего система выдает такой же объект {@link android.os.IBinder} для любых +дополнительных клиентов, которые выполняют привязку, без повторного вызова метода {@link android.app.Service#onBind onBind()}.</p> + +<p>Когда отменяется привязка последнего клиента от службы, система уничтожает службу (если только +служба не была так же запущена методом {@link android.content.Context#startService startService()}).</p> + +<p>Самую важную роль в реализации привязанной службы играет определение интерфейса, +который возвращает ваш метод обратного вызова {@link android.app.Service#onBind onBind()}. Существует несколько +различных способов определения интерфейса {@link android.os.IBinder} службы. Каждый из них рассматривается в следующем +разделе.</p> + + + +<h2 id="Creating">Создание привязанной службы</h2> + +<p>При создании службы, обеспечивающей привязку, требуется объект {@link android.os.IBinder}, +который обеспечивает программный интерфейс, с помощью которого клиенты могут взаимодействовать со службой. Существует +три способа определения такого интерфейса:</p> + +<dl> + <dt><a href="#Binder">Расширение класса Binder</a></dt> + <dd>Если служба является частной и предоставляется в рамках вашего собственного приложения, а также выполняется в том же процессе, что и клиент +(общий процесс), создавать интерфейс следует путем расширения класса {@link android.os.Binder} +и возврата его экземпляра из метода +{@link android.app.Service#onBind onBind()}. Клиент получает объект {@link android.os.Binder}, +после чего он может использовать его для получения прямого доступа к общедоступным методам, имеющимся либо в реализации {@link android.os.Binder}, +либо даже в {@link android.app.Service}. + <p>Этот способ является предпочтительным, когда служба просто выполняется в фоновом режиме для +вашего приложения. Этот способ не подходит для создания интерфейса только тогда, +когда ваша служба используется другими приложениями или в отдельных процессах.</dd> + + <dt><a href="#Messenger">Использование объекта Messenger</a></dt> + <dd>Если необходимо, чтобы интерфейс службы был доступен для разных процессов, его можно создать +с помощью объекта {@link android.os.Messenger}. Таким образом, служба +определяет объект {@link android.os.Handler}, соответствующий различным типам объектов {@link +android.os.Message}. Этот объект {@link android.os.Handler} +является основой для объекта {@link android.os.Messenger}, который, в свою очередь, предоставляет клиенту объект {@link android.os.IBinder}, +благодаря чему последний может отправлять команды в службу с помощью объектов {@link +android.os.Message}. Кроме того, клиент может определить объект {@link android.os.Messenger} для самого +себя, что позволяет службе возвращать сообщения клиенту. + <p>Это самый простой способ организовать взаимодействие процессов, поскольку {@link +android.os.Messenger} организует очередь всех запросов в рамках одного потока, поэтому вам не нужно делать +свою службу потокобезопасной.</p> + </dd> + + <dt>Использование языка AIDL</dt> + <dd>AIDL (Android Interface Definition Language) выполняет всю работу по разделению объектов на +примитивы, которые операционная система может распознать и распределить по процессам для организации +взаимодействия между ними (IPC). Предыдущий способ с использованием объекта {@link android.os.Messenger} фактически основан на AIDL, поскольку это его +базовая структура. Как уже упоминалось выше, объект {@link android.os.Messenger} создает очередь из всех +запросов клиентов в рамках одного потока, поэтому служба одновременно получает только один запрос. Однако, +если необходимо, чтобы служба обрабатывала одновременно сразу несколько запросов, можно использовать AIDL +напрямую. В таком случае ваша служба должна поддерживать многопоточность и должна быть потокобезопасной. + <p>Чтобы использовать AIDL напрямую, необходимо +создать файл {@code .aidl}, который определяет программный интерфейс. Этот файл используется инструментами SDK Android для +создания абстрактного класса, который реализует интерфейс и обеспечивает взаимодействие процессов, и который +в дальнейшем можно расширить в службе.</p> + </dd> +</dl> + + <p class="note"><strong>Примечание.</strong> В большинстве приложений <strong>не следует</strong> использовать AIDL для +создания и привязки службы, поскольку для этого может потребоваться поддержка многопоточности, что, в свою очередь, может привести +к более сложной реализации. Поэтому AIDL не подходит для большинства приложений, +и в этой статье мы не будем рассматривать использование этого способа для вашей службы. Если же вы точно уверены, что +вам необходимо использовать AIDL напрямую, обратитесь к статье +<a href="{@docRoot}guide/components/aidl.html">AIDL</a>.</p> + + + + +<h3 id="Binder">Расширение класса Binder</h3> + +<p>Если ваша служба используется только локальным приложением и не взаимодействует с разными процессами, +можно реализовать собственный класс {@link android.os.Binder}, с помощью которого клиент получает прямой +доступ к общедоступным методам в службе.</p> + +<p class="note"><strong>Примечание.</strong> Этот вариант подходит только в том случае, если клиент и служба выполняются внутри +одного приложения и процесса, что является наиболее распространенной ситуацией. Например, расширение класса отлично подойдет для +музыкального приложения, в котором необходимо привязать операцию к собственной службе приложения, которая +воспроизводит музыку в фоновом режиме.</p> + +<p>Вот как это сделать:</p> +<ol> + <li>Создайте в вашей службе экземпляр класса {@link android.os.Binder} со следующими характеристиками: + <ul> + <li>экземпляр содержит общедоступные методы, которые может вызывать клиент; либо</li> + <li>экземпляр возвращает текущий экземпляр класса {@link android.app.Service}, содержащий +общедоступные методы, которые может вызывать клиент; или</li> + <li>экземпляр возвращает экземпляр другого класса, размещенного в службе, содержащий общедоступные методы, +которые может вызывать клиент.</li> + </ul> + <li>Верните этот экземпляр класса {@link android.os.Binder} из метода обратного вызова {@link +android.app.Service#onBind onBind()}.</li> + <li>В клиенте получите класс {@link android.os.Binder} от метода обратного вызова {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} и +выполните вызовы к привязанной службе с помощью предоставленных методов.</li> +</ol> + +<p class="note"><strong>Примечание.</strong> Служба и клиент должны выполняться в одном и том же приложении +, поскольку в этом случае клиент может транслировать возвращенный объект и надлежащим образом вызывать его API-интерфейсы. Кроме того, служба +и клиент должны выполняться в рамках одного и того же процесса, поскольку этот способ не подразумевает какого-либо +распределения по процессам.</p> + +<p>Ниже представлен пример службы, которая предоставляет клиентам доступ к методам посредством реализации класса +{@link android.os.Binder}:</p> + +<pre> +public class LocalService extends Service { + // Binder given to clients + private final IBinder mBinder = new LocalBinder(); + // Random number generator + private final Random mGenerator = new Random(); + + /** + * Class used for the client Binder. Because we know this service always + * runs in the same process as its clients, we don't need to deal with IPC. + */ + public class LocalBinder extends Binder { + LocalService getService() { + // Return this instance of LocalService so clients can call public methods + return LocalService.this; + } + } + + @Override + public IBinder onBind(Intent intent) { + return mBinder; + } + + /** method for clients */ + public int getRandomNumber() { + return mGenerator.nextInt(100); + } +} +</pre> + +<p>Объект {@code LocalBinder} предоставляет для клиентов метод {@code getService()}, чтобы они могли получить +текущий экземпляр класса {@code LocalService}. Благодаря этому клиенты могут вызывать общедоступные методы в +службе. Например, клиенты могут вызвать метод {@code getRandomNumber()} из службы.</p> + +<p>Ниже представлен пример операции, которая выполняет привязку к классу {@code LocalService} и вызывает метод {@code getRandomNumber()} +при нажатии кнопки:</p> + +<pre> +public class BindingActivity extends Activity { + LocalService mService; + boolean mBound = false; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to LocalService + Intent intent = new Intent(this, LocalService.class); + bindService(intent, mConnection, Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } + + /** Called when a button is clicked (the button in the layout file attaches to + * this method with the android:onClick attribute) */ + public void onButtonClick(View v) { + if (mBound) { + // Call a method from the LocalService. + // However, if this call were something that might hang, then this request should + // occur in a separate thread to avoid slowing down the activity performance. + int num = mService.getRandomNumber(); + Toast.makeText(this, "number: " + num, Toast.LENGTH_SHORT).show(); + } + } + + /** Defines callbacks for service binding, passed to bindService() */ + private ServiceConnection mConnection = new ServiceConnection() { + + @Override + public void onServiceConnected(ComponentName className, + IBinder service) { + // We've bound to LocalService, cast the IBinder and get LocalService instance + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + mBound = true; + } + + @Override + public void onServiceDisconnected(ComponentName arg0) { + mBound = false; + } + }; +} +</pre> + +<p>В примере выше показано, как клиент привязывается к службе с помощью реализации +{@link android.content.ServiceConnection} и обратного вызова {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()}. В +следующем разделе представлена более подробная информация об этом процессе привязки к службе.</p> + +<p class="note"><strong>Примечание.</strong> В примере выше не выполняется явная отмена привязки к службе, +однако всем клиентам следует отменять привязку в соответствующие сроки (например, когда операция приостанавливается).</p> + +<p>Примеры кода представлены в статьях, посвященных классам <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code +LocalService.java}</a> и <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalServiceActivities.html">{@code +LocalServiceActivities.java}</a>, в <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a>.</p> + + + + + +<h3 id="Messenger">Использование объекта Messenger</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h4>Сравнение с AIDL</h4> + <p>Когда необходимо организовать взаимодействие между процессами, использование объекта {@link android.os.Messenger} для интерфейса +намного проще реализации с помощью AIDL, поскольку объект {@link android.os.Messenger} помещает +в очередь все запросы к службе, тогда как интерфейс, основанный исключительно на AIDL, отправляет службе несколько запросов одновременно, +для чего требуется поддержка многопоточности.</p> + <p>В большинстве приложений служба не должна поддерживать многопоточность, поэтому при использовании объекта {@link +android.os.Messenger} служба может одновременно обрабатывать один запрос. Если вам важно, +чтобы служба была многопоточной, для определения интерфейса следует использовать <a href="{@docRoot}guide/components/aidl.html">AIDL</a>.</p> +</div> +</div> + +<p>Если необходимо, чтобы служба взаимодействовала с удаленными процессами, для предоставления интерфейса службы можно воспользоваться объектом +{@link android.os.Messenger}. Такой подход +позволяет организовать взаимодействие между процессами (IPC) без необходимости использовать AIDL.</p> + +<p>Вот краткий обзор того, как следует использовать объект {@link android.os.Messenger}:</p> + +<ul> + <li>Служба реализует объект {@link android.os.Handler}, который получает обратный вызов для каждого +вызова от клиента.</li> + <li>Объект {@link android.os.Handler} используется для создания объекта {@link android.os.Messenger} +(который является ссылкой на объект {@link android.os.Handler}).</li> + <li>Объект {@link android.os.Messenger} создает объект {@link android.os.IBinder}, который служба +возвращает клиентам из метода {@link android.app.Service#onBind onBind()}.</li> + <li>Клиенты используют полученный объект {@link android.os.IBinder} для создания экземпляра объекта {@link android.os.Messenger} +(который ссылается на объект {@link android.os.Handler} службы), используемого клиентом для отправки объектов +{@link android.os.Message} в службу.</li> + <li>Служба получает каждый объект {@link android.os.Message} в своем объекте {@link +android.os.Handler} — в частности, в методе {@link android.os.Handler#handleMessage +handleMessage()}.</li> +</ul> + + +<p>Таким образом, для клиента отсутствуют «методы» для отправки вызова службе. Вместо этого +клиент отправляет «сообщения» (объекты {@link android.os.Message}), которые служба получает в +своем объекте {@link android.os.Handler}.</p> + +<p>Ниже представлен пример службы, которая использует интерфейс {@link android.os.Messenger}:</p> + +<pre> +public class MessengerService extends Service { + /** Command to the service to display a message */ + static final int MSG_SAY_HELLO = 1; + + /** + * Handler of incoming messages from clients. + */ + class IncomingHandler extends Handler { + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_SAY_HELLO: + Toast.makeText(getApplicationContext(), "hello!", Toast.LENGTH_SHORT).show(); + break; + default: + super.handleMessage(msg); + } + } + } + + /** + * Target we publish for clients to send messages to IncomingHandler. + */ + final Messenger mMessenger = new Messenger(new IncomingHandler()); + + /** + * When binding to the service, we return an interface to our messenger + * for sending messages to the service. + */ + @Override + public IBinder onBind(Intent intent) { + Toast.makeText(getApplicationContext(), "binding", Toast.LENGTH_SHORT).show(); + return mMessenger.getBinder(); + } +} +</pre> + +<p>Обратите внимание, что метод {@link android.os.Handler#handleMessage handleMessage()} в объекте +{@link android.os.Handler} — это место, где служба получает входящие объекты {@link android.os.Message} +и решает, что делать дальше, руководствуясь элементом {@link android.os.Message#what}.</p> + +<p>Клиенту требуется лишь создать объект {@link android.os.Messenger} на основе объекта {@link +android.os.IBinder}, возвращенного службой, и отправить сообщение с помощью метода {@link +android.os.Messenger#send send()}. Ниже представлен пример того, как простая операция выполняет привязку к +службе и отправляет ей сообщение {@code MSG_SAY_HELLO}:</p> + +<pre> +public class ActivityMessenger extends Activity { + /** Messenger for communicating with the service. */ + Messenger mService = null; + + /** Flag indicating whether we have called bind on the service. */ + boolean mBound; + + /** + * Class for interacting with the main interface of the service. + */ + private ServiceConnection mConnection = new ServiceConnection() { + public void onServiceConnected(ComponentName className, IBinder service) { + // This is called when the connection with the service has been + // established, giving us the object we can use to + // interact with the service. We are communicating with the + // service using a Messenger, so here we get a client-side + // representation of that from the raw IBinder object. + mService = new Messenger(service); + mBound = true; + } + + public void onServiceDisconnected(ComponentName className) { + // This is called when the connection with the service has been + // unexpectedly disconnected -- that is, its process crashed. + mService = null; + mBound = false; + } + }; + + public void sayHello(View v) { + if (!mBound) return; + // Create and send a message to the service, using a supported 'what' value + Message msg = Message.obtain(null, MessengerService.MSG_SAY_HELLO, 0, 0); + try { + mService.send(msg); + } catch (RemoteException e) { + e.printStackTrace(); + } + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + } + + @Override + protected void onStart() { + super.onStart(); + // Bind to the service + bindService(new Intent(this, MessengerService.class), mConnection, + Context.BIND_AUTO_CREATE); + } + + @Override + protected void onStop() { + super.onStop(); + // Unbind from the service + if (mBound) { + unbindService(mConnection); + mBound = false; + } + } +} +</pre> + +<p>Обратите внимание, что в этом примере не показано, как служба отвечает клиенту. Если требуется, +чтобы служба реагировала, необходимо создать объект {@link android.os.Messenger} и в клиенте. Затем, +когда клиент получает обратный вызов {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, она отправляет в службу объект {@link android.os.Message}, который включает объект +{@link android.os.Messenger} клиента в качестве значения параметра {@link android.os.Message#replyTo} +метода {@link android.os.Messenger#send send()}.</p> + +<p>Пример организации двустороннего обмена сообщениями приведен в примерах кода <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerService.html">{@code +MessengerService.java}</a> (служба) и <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/MessengerServiceActivities.html">{@code +MessengerServiceActivities.java}</a> (клиент).</p> + + + + + +<h2 id="Binding">Привязка к службе</h2> + +<p>Для привязки к службе компоненты приложения (клиенты) могут использовать метод +{@link android.content.Context#bindService bindService()}. После этого система Android +вызывает метод {@link android.app.Service#onBind +onBind()} службы, который возвращает объект {@link android.os.IBinder} для взаимодействия со службой.</p> + +<p>Привязка выполняется асинхронно. {@link android.content.Context#bindService +bindService()} возвращается сразу же и <em>не</em> возвращает клиенту объект +{@link android.os.IBinder}. Для получения объекта {@link android.os.IBinder} клиенту необходимо создать экземпляр {@link +android.content.ServiceConnection} и передать его в метод {@link android.content.Context#bindService +bindService()}. Интерфейс {@link android.content.ServiceConnection} включает метод обратного вызова, +который система использует для того, чтобы выдать объект {@link android.os.IBinder}.</p> + +<p class="note"><strong>Примечание.</strong> Выполнить привязку к службе могут только операции, другие службы и поставщики контента +— вы <strong>не можете</strong> самостоятельно выполнить привязку к службе из ресивера.</p> + +<p>Поэтому для привязки к службе из клиента необходимо выполнить указанные ниже действия. </p> +<ol> + <li>Реализуйте интерфейс {@link android.content.ServiceConnection}. + <p>Ваша реализация должна переопределять два метода обратного вызова:</p> + <dl> + <dt>{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}</dt> + <dd>Система вызывает этот метод, чтобы выдать объект {@link android.os.IBinder}, возвращенный методом +{@link android.app.Service#onBind onBind()}службы.</dd> + <dt>{@link android.content.ServiceConnection#onServiceDisconnected +onServiceDisconnected()}</dt> + <dd>Система Android вызывает этот метод в случае непредвиденной потери +подключения к службе, например при сбое в работе службы или в случае ее завершения. Этот метод <em>не</em> вызывается, когда клиент +отменяет привязку.</dd> + </dl> + </li> + <li>Вызовите метод {@link +android.content.Context#bindService bindService()}, передав в него реализацию интерфейса {@link +android.content.ServiceConnection}. </li> + <li>Когда система вызывает ваш метод обратного вызова {@link android.content.ServiceConnection#onServiceConnected +onServiceConnected()}, вы можете приступить к выполнению вызовов к службе с помощью +методов, определенных интерфейсом.</li> + <li>Чтобы отключиться от службы, вызовите метод {@link +android.content.Context#unbindService unbindService()}. + <p>В случае уничтожения клиента выполняется отмена его привязки к службе, однако вам всегда следует отменять +привязку по завершении взаимодействия со службой или в случае приостановки операции, чтобы служба +могла завершить свою работу, когда она не используется. (Более подробно подходящее время для привязки и ее отмены +рассматриваются далее в этом документе).</p> + </li> +</ol> + +<p>Ниже представлен пример фрагмента кода для подключения клиента к созданной выше службе путем +<a href="#Binder">расширения класса Binder</a> — клиенту нужно лишь передать возвращенный объект +{@link android.os.IBinder} в класс {@code LocalService} и запросить экземпляр {@code +LocalService}:</p> + +<pre> +LocalService mService; +private ServiceConnection mConnection = new ServiceConnection() { + // Called when the connection with the service is established + public void onServiceConnected(ComponentName className, IBinder service) { + // Because we have bound to an explicit + // service that is running in our own process, we can + // cast its IBinder to a concrete class and directly access it. + LocalBinder binder = (LocalBinder) service; + mService = binder.getService(); + mBound = true; + } + + // Called when the connection with the service disconnects unexpectedly + public void onServiceDisconnected(ComponentName className) { + Log.e(TAG, "onServiceDisconnected"); + mBound = false; + } +}; +</pre> + +<p>С помощью этого интерфейса {@link android.content.ServiceConnection} клиент может выполнить привязку к службе, передав ее в +метод {@link android.content.Context#bindService bindService()}. Например:</p> + +<pre> +Intent intent = new Intent(this, LocalService.class); +bindService(intent, mConnection, Context.BIND_AUTO_CREATE); +</pre> + +<ul> + <li>Первый параметр в методе {@link android.content.Context#bindService bindService()} представляет собой объект +{@link android.content.Intent}, который явным образом именует службу для привязки (хотя переход может быть +и неявным).</li> +<li>Второй параметр — это объект {@link android.content.ServiceConnection}.</li> +<li>Третий параметр представляет собой флаг, указывающий параметры привязки. Обычно им является {@link +android.content.Context#BIND_AUTO_CREATE}, создающий службу, если она уже не выполняется. +Другие возможные значения: {@link android.content.Context#BIND_DEBUG_UNBIND} +и {@link android.content.Context#BIND_NOT_FOREGROUND} или {@code 0}, если значение отсутствует.</li> +</ul> + + +<h3>Дополнительные примечания</h3> + +<p>Ниже представлены некоторые важные замечания о привязке к службе.</p> +<ul> + <li>Всегда отлавливайте исключения {@link android.os.DeadObjectException}, которые выдаются +при возникновении сбоя подключения. Это единственное исключение, которое выдается удаленными методами.</li> + <li>Для объектов всегда учитываются ссылки на них в процессах. </li> + <li>Обычно необходимо связать привязку и ее отмену во время +сопоставления моментов подключения и отключения в жизненном цикле клиента. Например: + <ul> + <li>Если взаимодействие со службой требуется лишь в то время, когда операция отображается, привязку +необходимо выполнить во время метода {@link android.app.Activity#onStart onStart()}, а отмену привязки — во время выполнения метода {@link +android.app.Activity#onStop onStop()}.</li> + <li>Если необходимо, чтобы операция получала ответы даже в случае ее остановки во время работы в фоновом режиме, +то привязку можно выполнить во время {@link android.app.Activity#onCreate onCreate()}, а отмену привязки + — во время выполнения {@link android.app.Activity#onDestroy onDestroy()}. Однако следует помнить, что такой способ подразумевает, +что вашей операции необходимо использовать службу все время, пока она выполняется (даже если она выполняется в фоновом режиме). Поэтому, +если служба находится в другом процессе, вы тем самым утяжеляете процесс, а это +повышает вероятность того, что система завершит его.</li> + </ul> + <p class="note"><strong>Примечание.</strong> Обычно <strong>не</strong> следует выполнять привязку или отменять ее +во время выполнения методов {@link android.app.Activity#onResume onResume()} и {@link +android.app.Activity#onPause onPause()} вашей операции, поскольку такие обратные вызовы происходят при каждом переходе из одного состояния в другое, + а обработка данных, выполняемая при таких переходах, должна быть минимальной. Кроме того, если к одной и той же +службе привязано несколько операций в вашем приложении, и имеется переход между +двумя этими операциями, служба может быть уничтожена и создана повторно, поскольку текущая операция выполняет отмену привязки +(во время приостановки) до того, как следующая служба выполнит привязку (во время возобновления). (Подробные сведения о согласовании жизненных циклов операций при таких переходах +представлены в статье +<a href="{@docRoot}guide/components/activities.html#CoordinatingActivities">Операции</a>.)</p> +</ul> + +<p>Пример кода, в котором показан порядок привязки к службе, см. в статье, посвященной классу <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code +RemoteService.java}</a>, в <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a>.</p> + + + + + +<h2 id="Lifecycle">Управление жизненным циклом привязанной службы</h2> + +<p>Когда выполняется отмена привязки службы ко всем клиентам, система Android уничтожает такую службу (если она не была запущена +вместе с {@link android.app.Service#onStartCommand onStartCommand()}). В таком случае вам не нужно +управлять жизненным циклом своей службы, если она исключительно привязанная служба +— система Android управляет ей за вас на основании привязки службы к любым другим клиентам.</p> + +<p>Однако, если вы решите реализовать метод обратного вызова {@link android.app.Service#onStartCommand +onStartCommand()}, вам необходимо явным образом остановить службу, поскольку в +этом случае она считается <em>запущенной</em>. В таком случае служба выполняется до тех пор, пока +сама не остановит свою работу с помощью метода {@link android.app.Service#stopSelf()} или до тех пор, пока другой компонент не вызовет метод {@link +android.content.Context#stopService stopService()}, независимо от привязки службы к каким-либо +клиентам.</p> + +<p>Кроме того, если ваша служба запущена и принимает привязку, то при вызове системой +вашего метода {@link android.app.Service#onUnbind onUnbind()} вы также можете вернуть +{@code true}, если желаете получить вызов к {@link android.app.Service#onRebind +onRebind()} при следующей привязке к службе (вместо получения вызова к методу {@link +android.app.Service#onBind onBind()}). Метод {@link android.app.Service#onRebind +onRebind()} возвращает значение void, однако клиент по-прежнему получает объект {@link android.os.IBinder} в своем методе обратного вызова +{@link android.content.ServiceConnection#onServiceConnected onServiceConnected()}. +На рисунке 1 ниже иллюстрируется логика жизненного цикла такого рода.</p> + + +<img src="{@docRoot}images/fundamentals/service_binding_tree_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Жизненный цикл запущенной службы, +для которой выполняется привязка.</p> + + +<p>Дополнительные сведения о жизненном цикле уже запущенной службы представлены в статье <a href="{@docRoot}guide/components/services.html#Lifecycle">Службы</a>.</p> + + + + diff --git a/docs/html-intl/intl/ru/guide/components/fragments.jd b/docs/html-intl/intl/ru/guide/components/fragments.jd new file mode 100644 index 000000000000..b13fcc3da7ef --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=Фрагменты +parent.title=Операции +parent.link=activities.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> + <ol> + <li><a href="#Design">Философия проектирования</a></li> + <li><a href="#Creating">Создание фрагмента</a> + <ol> + <li><a href="#UI">Добавление пользовательского интерфейса</a></li> + <li><a href="#Adding">Добавление фрагмента в операцию</a></li> + </ol> + </li> + <li><a href="#Managing">Управление фрагментами</a></li> + <li><a href="#Transactions">Выполнение транзакций с фрагментами</a></li> + <li><a href="#CommunicatingWithActivity">Взаимодействие с операцией</a> + <ol> + <li><a href="#EventCallbacks">Создание обратного вызова события для операции</a></li> + <li><a href="#ActionBar">Добавление элементов в строку действий</a></li> + </ol> + </li> + <li><a href="#Lifecycle">Управление жизненным циклом фрагмента</a> + <ol> + <li><a href="#CoordinatingWithActivity">Согласование с жизненным циклом операции</a></li> + </ol> + </li> + <li><a href="#Example">Пример:</a></li> + </ol> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.app.Fragment}</li> + <li>{@link android.app.FragmentManager}</li> + <li>{@link android.app.FragmentTransaction}</li> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="{@docRoot}training/basics/fragments/index.html">Создание динамического интерфейса пользователя с использованием фрагментов</a></li> + <li><a href="{@docRoot}guide/practices/tablets-and-handsets.html">Поддержка планшетов +и смартфонов</a></li> + </ol> +</div> +</div> + +<p>Фрагмент (класс {@link android.app.Fragment}) представляет поведение или часть пользовательского интерфейса в операции +(класс {@link android.app.Activity}). Разработчик может объединить несколько фрагментов в одну операцию для построения +многопанельного пользовательского интерфейса и повторного использования фрагмента в нескольких операциях. Фрагмент можно рассматривать как + модульную часть операции. Такая часть имеет свой жизненный цикл и самостоятельно обрабатывает события ввода. Кроме того, +ее можно добавить или удалить непосредственно во время выполнения операции. Это нечто вроде вложенной операции, которую +можно многократно использовать в различных операциях.</p> + +<p>Фрагмент всегда должен быть встроен в операцию, и на его жизненный цикл напрямую +влияет жизненный цикл операции. Например, когда операция приостановлена, в том же состоянии находятся и все +фрагменты внутри нее, а когда операция уничтожается, уничтожаются и все фрагменты. Однако пока +операция выполняется (это соответствует состоянию <em>возобновлена</em> <a href="{@docRoot}guide/components/activities.html#Lifecycle">жизненного цикла</a>), можно +манипулировать каждым фрагментом независимо, например добавлять или удалять их. Когда разработчик выполняет такие +транзакции с фрагментами, он может также добавить их в стек переходов назад, которым управляет +операция. Каждый элемент стека переходов назад в операции является записью + выполненной транзакции с фрагментом. Стек переходов назад позволяет пользователю обратить транзакцию с фрагментом (выполнить навигацию в обратном направлении), +нажимая кнопку <em>Назад</em>.</p> + +<p>Когда фрагмент добавлен как часть макета операции, он находится в объекте {@link +android.view.ViewGroup} внутри иерархии представлений операции и определяет собственный +макет представлений. +Разработчик может вставить фрагмент в макет операции двумя способами. Для этого следует объявить фрагмент в +файле макета операции как элемент {@code <fragment>} или добавить его в +существующий объект{@link android.view.ViewGroup} в коде приложения. Впрочем, фрагмент не обязан быть частью +макета операции. Можно использовать фрагмент без интерфейса в качестве невидимого рабочего потока +операции.</p> + +<p>В этом документе показано, как построить приложение, использующее фрагменты. В частности, обсуждается, +как фрагменты могут поддерживать свое состояние, когда они добавляются в стек переходов назад операции, использовать +события совместно с операцией и другими фрагментами внутри нее, выводить данные в +строку действий операции и т. д.</p> + + +<h2 id="Design">Философия проектирования</h2> + +<p>Фрагменты впервые появились в Android версии 3.0 (API уровня 11), главным образом, для обеспечения +большей динамичности и гибкости пользовательских интерфейсов на больших экранах, например, у планшетов. Поскольку экраны +планшетов гораздо больше, чем у смартфонов, они предоставляют больше возможностей для объединения и +перестановки компонентов пользовательского интерфейса. Фрагменты позволяют делать это, избавляя разработчика от необходимости управлять +сложными изменениями в иерархии представлений. Разбивая макет операции на фрагменты, разработчик получает возможность +модифицировать внешний вид операции в ходе выполнения и сохранять эти изменения в стеке переходов назад, +которым управляет операция.</p> + +<p>Например, новостное приложение может использовать один фрагмент для показа списка статей слева, +а другой—для отображения статьи справа. Оба фрагмента отображаются за +одну операцию рядом друг с другом, и каждый имеет собственный набор методов обратного вызова жизненного цикла и управляет +собственными событиями пользовательского ввода. Таким образом, вместо применения одной операции для выбора статьи, а другой + — для чтения статей, пользователь может выбрать статью и читать ее в рамках одной +операции, как на планшете, изображенном на рисунке 1.</p> + +<p>Следует разрабатывать каждый фрагмент как модульный и повторно используемый компонент операции. Поскольку + каждый фрагмент определяет собственный макет и собственное поведение со своими обратными вызовами жизненного цикла, разработчик может +включить один фрагмент в несколько операций. Поэтому он должен предусмотреть повторное использование фрагмента и не допускать, +чтобы один фрагмент непосредственно манипулировал другим. Это особенно важно, потому что модульность фрагментов +позволяет изменять их сочетания в соответствии с различными размерами экранов. Если +приложение должно работать и на планшетах, и на смартфонах, можно повторно использовать фрагменты в различных +конфигурациях макета, чтобы оптимизировать взаимодействие с пользователем в зависимости от доступного размера экрана. Например, +на смартфоне может возникнуть необходимость в разделении фрагментов для предоставления однопанельного пользовательского интерфейса, если + разработчику не удается поместить более одного фрагмента в одну операцию.</p> + +<img src="{@docRoot}images/fundamentals/fragments.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Пример того, как два модуля пользовательского интерфейса, определенные +фрагментами, могут быть объединены внутри одной операции для работы на планшетах, но разделены на +смартфонах.</p> + +<p>Вернемся к примеру с новостным приложением. Оно может иметь +два фрагмента, встроенных в <em>Операцию А</em>, когда выполняется на устройстве планшетного формата. В то же время на +экране смартфона недостаточно места для обоих фрагментов, и поэтому <em>Операция А</em> включает в себя +только фрагмент со списком статей. Когда пользователь выбирает статью, запускается +<em>Операция В</em>, содержащая второй фрагмент для чтения статьи. Таким образом, приложение +поддерживает как планшеты, так и смартфоны благодаря повторному использованию фрагментов в различных сочетаниях, как показано на +рисунке 1.</p> + +<p>Подробные сведения относительно разработки приложения с различными сочетаниями фрагментов для +различных конфигураций экрана приводятся в руководстве <a href="{@docRoot}guide/practices/tablets-and-handsets.html">Поддержка планшетов и смартфонов</a></p> + + + +<h2 id="Creating">Создание фрагмента</h2> + +<div class="figure" style="width:327px"> +<img src="{@docRoot}images/fragment_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Жизненный цикл фрагмента (во время +выполнения операции)</p> +</div> + +<p>Для создания фрагмента необходимо создать подкласс класса {@link android.app.Fragment} (или его существующего +подкласса). Класс {@link android.app.Fragment} имеет код, во многом схожий с +кодом {@link android.app.Activity}. Он содержит методы обратного вызова, аналогичные методам операции, такие +как {@link android.app.Fragment#onCreate onCreate()}, {@link android.app.Fragment#onStart onStart()}, +{@link android.app.Fragment#onPause onPause()} и {@link android.app.Fragment#onStop onStop()}. На практике, +если требуется преобразовать существующее приложение Android так, чтобы в нем использовались фрагменты, достаточно просто переместить +код из методов обратного вызова операции в соответствующие методы обратного вызова +фрагмента.</p> + +<p>Как правило, необходимо реализовать следующие методы жизненного цикла:</p> + +<dl> + <dt>{@link android.app.Fragment#onCreate onCreate()}</dt> + <dd>Система вызывает этот метод, когда создает фрагмент. В своей реализации разработчик должен +инициализировать ключевые компоненты фрагмента, которые требуется сохранить, когда фрагмент находится в состоянии +паузы или возобновлен после остановки.</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>Система вызывает этот метод при первом отображении пользовательского интерфейса фрагмента +на дисплее. Для прорисовки пользовательского интерфейса фрагмента следует возвратить из этого метода объект {@link android.view.View}, +который является корневым в макете фрагмента. Если фрагмент не имеет пользовательского интерфейса, можно +возвратить null.</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>Система вызывает этот метод как первое указание того, что пользователь покидает +фрагмент (это не всегда означает уничтожение фрагмента). Обычно именно в этот момент +необходимо фиксировать все изменения, которые должны быть сохранены за рамками текущего сеанса работы пользователя (поскольку +пользователь может не вернуться назад).</dd> +</dl> + +<p>В большинстве приложений для каждого фрагмента должны быть реализованы, как минимум, эти три метода. Однако существуют и +другие методы обратного вызова, которые следует использовать для управления различными этапами жизненного цикла +фрагмента. Все методы обратного вызова жизненного цикла подробно обсуждаются в разделе + <a href="#Lifecycle">Управление жизненным циклом фрагмента</a>.</p> + + +<p>Существует также ряд подклассов, которые, возможно, потребуется расширить вместо использования базового класса {@link +android.app.Fragment}:</p> + +<dl> + <dt>{@link android.app.DialogFragment}</dt> + <dd>Отображение перемещаемого диалогового окна. Использование этого класса для создания диалогового окна является хорошей альтернативой + вспомогательным методам диалогового окна в классе{@link android.app.Activity}. Дело в том, что он дает возможность +вставить диалоговое окно фрагмента в управляемый операцией стек переходов назад для фрагментов, +что позволяет пользователю вернуться к закрытому фрагменту.</dd> + + <dt>{@link android.app.ListFragment}</dt> + <dd>Отображение списка элементов, управляемых адаптером (например, {@link +android.widget.SimpleCursorAdapter}), аналогично классу {@link android.app.ListActivity}. Этот класс предоставляет +несколько методов для управления списком представлений, например, метод обратного вызова {@link +android.app.ListFragment#onListItemClick(ListView,View,int,long) onListItemClick()} для +обработки нажатий.</dd> + + <dt>{@link android.preference.PreferenceFragment}</dt> + <dd>Отображение иерархии объектов {@link android.preference.Preference} в виде списка, аналогично классу +{@link android.preference.PreferenceActivity}. Этот класс полезен, когда в приложении создается +операция «Настройки».</dd> +</dl> + + +<h3 id="UI">Добавление пользовательского интерфейса</h3> + +<p>Фрагмент обычно используется как часть пользовательского интерфейса операции, при этом он добавляет в операцию +свой макет.</p> + +<p>Чтобы создать макет для фрагмента, разработчик должен реализовать метод обратного вызова {@link +android.app.Fragment#onCreateView onCreateView()}, который система Android вызывает, +когда для фрагмента наступает время отобразить свой макет. Реализация этого метода должна возвращать объект +{@link android.view.View}, который является корневым в макете фрагмента. </p> + +<p class="note"><strong>Примечание.</strong> Если фрагмент является подклассом класса {@link +android.app.ListFragment}, реализация по умолчанию возвращает класс {@link android.widget.ListView} из метода + {@link android.app.Fragment#onCreateView onCreateView()}, так что реализовывать его нет необходиомости.</p> + +<p>Чтобы возвратить макет из метода {@link +android.app.Fragment#onCreateView onCreateView()}, можно выполнить его раздувание из <a href="{@docRoot}guide/topics/resources/layout-resource.html">ресурса макета</a>, определенного в XML-файле. Для +этой цели метод {@link android.app.Fragment#onCreateView onCreateView()} предоставляет объект +{@link android.view.LayoutInflater}.</p> + +<p>Например, код подкласса класса {@link android.app.Fragment}, загружающий макет из файла +{@code example_fragment.xml}, может выглядеть так:</p> + +<pre> +public static class ExampleFragment extends Fragment { + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout for this fragment + return inflater.inflate(R.layout.example_fragment, container, false); + } +} +</pre> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>Создание макета</h3> + <p>В приведенном коде конструкция {@code R.layout.example_fragment} является ссылкой на ресурс макета +по имени {@code example_fragment.xml}, хранящийся в ресурсах приложения. Подробные сведения о +создании макета в XML см. в статье <a href="{@docRoot}guide/topics/ui/index.html">Пользовательский интерфейс</a>. +</p> +</div> +</div> + +<p>Параметр {@code container}, передаваемый методу {@link android.app.Fragment#onCreateView +onCreateView()}, является родительским классом {@link android.view.ViewGroup} (из макета операции), в который +будет вставлен макет +фрагмента. Параметр {@code savedInstanceState} является классом {@link android.os.Bundle}, который +предоставляет данные о предыдущем экземпляре фрагмента во время возобновления фрагмента +(восстановление состояния подробно обсуждается в разделе <a href="#Lifecycle">Управление +жизненным циклом фрагмента</a>).</p> + +<p>Метод {@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} принимает +три аргумента:</p> +<ul> + <li>Идентификатор ресурса макета, раздувание которого следует выполнить.</li> + <li>Объект класса {@link android.view.ViewGroup}, который должен стать родительским для макета после раздувания. Передача параметра {@code +container} необходима для того, чтобы система смогла применить параметры макета к корневому представлению +раздутого макета, определяемому родительским представлением, в которое направляется макет.</li> + <li>Логическое значение, показывающее, следует ли прикрепить макет к объекту {@link +android.view.ViewGroup} (второй параметр) во время раздувания. (В данном случае это +false, потому что система уже вставляет раздутый макет в объект {@code +container}, ипередача значения true создала бы лишнюю группу представления в окончательном макете).</li> +</ul> + +<p>Мы увидели, как создавать фрагмент, предоставляющий макет. Теперь необходимо добавить +фрагмент в операцию.</p> + + + +<h3 id="Adding">Добавление фрагмента в операцию</h3> + +<p>Как правило, фрагмент добавляет часть пользовательского интерфейса в операцию, и этот интерфейс встраивается +в общую иерархию представлений операции. Разработчик может добавить фрагмент в макет операции двумя +способами:</p> + +<ul> + <li><b>объявив фрагмент в файле макета операции.</b> +<p>В этом случае можно +указать свойства макета для фрагмента, как будто он является представлением. Например, файл макета операции +с двумя фрагментами может выглядеть следующим образом:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="horizontal" + android:layout_width="match_parent" + android:layout_height="match_parent"> + <fragment android:name="com.example.news.ArticleListFragment" + android:id="@+id/list" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="match_parent" /> + <fragment android:name="com.example.news.ArticleReaderFragment" + android:id="@+id/viewer" + android:layout_weight="2" + android:layout_width="0dp" + android:layout_height="match_parent" /> +</LinearLayout> +</pre> + <p>Атрибут {@code android:name} в элементе {@code <fragment>} определяет класс {@link +android.app.Fragment}, экземпляр которого создается в макете.</p> + +<p>Когда система создает этот макет операции, она создает экземпляр каждого фрагмента, определенного в макете, +и для каждого вызывает метод {@link android.app.Fragment#onCreateView onCreateView()}, +чтобы получить макет каждого фрагмента. Система вставляет объект {@link android.view.View}, возвращенный + фрагментом, непосредственно вместо элемента {@code <fragment>}.</p> + +<div class="note"> + <p><strong>Примечание.</strong> Каждый фрагмент должен иметь уникальный идентификатор, который +система сможет использовать для восстановления фрагмента в случае перезапуска операции. (Что касается разработчика, он может использовать этот идентификатор для +захвата фрагмента с целью выполнения транзакций с ним, например, чтобы удалить его). Предоставить +идентификатор фрагменту можно тремя способами:</p> + <ul> + <li>указать атрибут {@code android:id} с уникальным идентификатором;</li> + <li>указать атрибут {@code android:tag} с уникальной строкой;</li> + <li>ничего не предпринимать, чтобы система использовала идентификатор контейнерного +представления.</li> + </ul> +</div> + </li> + + <li><b>или программным образом, добавив фрагмент в существующий объект {@link android.view.ViewGroup}.</b> +<p>В любой момент выполнения операции разработчик может добавить фрагменты в ее макет. Для +этого достаточно указать объект {@link +android.view.ViewGroup}, в котором следует разместить фрагмент.</p> + <p>Для выполнения транзакций с фрагментами внутри операции (таких как добавление, удаление или замена +фрагмента) необходимо использовать API-интерфейсы из {@link android.app.FragmentTransaction}. Экземпляр + класса {@link android.app.FragmentTransaction} можно получить от объекта {@link android.app.Activity} следующим образом:</p> + +<pre> +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()} +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()}; +</pre> + +<p>После этого можно добавить фрагмент методом {@link +android.app.FragmentTransaction#add(int,Fragment) add()}, указав добавляемый фрагмент и +представление, в которое он должен быть добавлен. Например:</p> + +<pre> +ExampleFragment fragment = new ExampleFragment(); +fragmentTransaction.add(R.id.fragment_container, fragment); +fragmentTransaction.commit(); +</pre> + + <p>Первый аргумент, передаваемый методу {@link android.app.FragmentTransaction#add(int,Fragment) add()}, +представляет собой контейнерный объект {@link android.view.ViewGroup} для фрагмента, указанный при помощи +идентификатора ресурса. Второй параметр — это фрагмент, который нужно добавить.</p> + <p>Выполнив изменения с помощью +{@link android.app.FragmentTransaction}, необходимо +вызвать метод {@link android.app.FragmentTransaction#commit}, чтобы они вступили в силу.</p> + </li> +</ul> + + +<h4 id="AddingWithoutUI">Добавление фрагмента, не имеющего пользовательского интерфейса</h4> + +<p>Пример, приведенный выше, демонстрирует, как добавлять в операцию фрагмент с предоставлением пользовательского интерфейса. Однако +можно использовать фрагмент и для реализации фонового поведения операции без какого-либо дополнительного +пользовательского интерфейса.</p> + +<p>Чтобы добавить фрагмент без пользовательского интерфейса, добавьте фрагмент из операции, используя метод {@link +android.app.FragmentTransaction#add(Fragment,String)} (передав ему уникальный строковый «тег» для +фрагмента вместо идентификатора представления). Фрагмент будет добавлен, но, поскольку он не связан +с представлением в макете операции, он не будет принимать вызов метода {@link +android.app.Fragment#onCreateView onCreateView()}. Поэтому в реализации этого метода нет необходимости.</p> + +<p>Передача строкового тега свойственна не только фрагментам без пользовательского интерфейса, поэтому можно +передавать строковые теги и фрагментам, имеющим пользовательский интерфейс. Однако, если у фрагмента нет + пользовательского интерфейса, то строковый тег является единственным способом его идентификации. Если впоследствии потребуется получить фрагмент от +операции, нужно будет вызвать метод {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.</p> + +<p>Пример операции, использующей фрагмент в качестве фонового потока, без пользовательского интерфейса, приведен в образце кода {@code +FragmentRetainInstance.java}, входящем в число образцов в SDK (и доступном при помощи +Android SDK Manager). Путь к нему в системе — +<code><sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java</code>.</p> + + + +<h2 id="Managing">Управление фрагментами</h2> + +<p>Для управления фрагментами в операции нужен класс {@link android.app.FragmentManager}. Чтобы получить его, + следует вызвать метод {@link android.app.Activity#getFragmentManager()} из кода операции.</p> + +<p>Ниже указаны действия, которые позволяет выполнить{@link android.app.FragmentManager}:</p> + +<ul> + <li>получать фрагменты, имеющиеся в операции, с помощью метода {@link +android.app.FragmentManager#findFragmentById findFragmentById()} (для фрагментов, предоставляющих пользовательский интерфейс в + макете операции) или {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()} (как для фрагментов, имеющих пользовательский интерфейс, так и для фрагментов без него);</li> + <li>снимать фрагменты со стека переходов назад методом {@link +android.app.FragmentManager#popBackStack()} (имитируя нажатие кнопки <em>Назад</em> пользователем);</li> + <li>регистрировать процесс-слушатель изменений в стеке переходов назад при помощи метода {@link +android.app.FragmentManager#addOnBackStackChangedListener addOnBackStackChangedListener()}.</li> +</ul> + +<p>Дополнительные сведения об этих и других методах приводятся в документации по классу {@link +android.app.FragmentManager}.</p> + +<p>Как было показано в предыдущем разделе, можно использовать класс {@link android.app.FragmentManager} +для открытия {@link android.app.FragmentTransaction}, что позволяет выполнять транзакции с фрагментами, например, +добавление и удаление.</p> + + +<h2 id="Transactions">Выполнение транзакций с фрагментами</h2> + +<p>Большим достоинством использования фрагментов в операции является возможность добавлять, удалять, заменять их и +выполнять другие действия с ними в ответ на действия пользователя. Любой набор изменений, +вносимых в операцию, называется транзакцией. Ее можно выполнить при помощи API-интерфейсов в {@link +android.app.FragmentTransaction}. Каждую транзакцию можно сохранить в стеке переходов назад, +которым управляет операция. Это позволит пользователю перемещаться назад по изменениям во фрагментах (аналогично перемещению +назад по операциям).</p> + +<p>Экземпляр класса {@link android.app.FragmentTransaction} можно получить от {@link +android.app.FragmentManager}, например, так:</p> + +<pre> +FragmentManager fragmentManager = {@link android.app.Activity#getFragmentManager()}; +FragmentTransaction fragmentTransaction = fragmentManager.{@link android.app.FragmentManager#beginTransaction()}; +</pre> + +<p>Каждая транзакция является набором изменений, выполняемых одновременно. Разработчик может указать +все изменения, которые ему нужно выполнить в данной транзакции, вызывая методы {@link +android.app.FragmentTransaction#add add()}, {@link android.app.FragmentTransaction#remove remove()} +и {@link android.app.FragmentTransaction#replace replace()}. Затем, чтобы применить транзакцию +к операции, следует вызвать метод {@link android.app.FragmentTransaction#commit()}.</p> +</dl> + +<p>Впрочем, до вызова метода{@link +android.app.FragmentTransaction#commit()} у разработчика может возникнуть необходимость вызвать метод {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, чтобы добавить транзакцию +в стек переходов назад по транзакциям фрагмента. Этим стеком переходов назад управляет операция, что позволяет +пользователю вернуться к предыдущему состоянию фрагмента, нажав кнопку <em>Назад</em>.</p> + +<p>Например, следующий код демонстрирует, как можно заменить один фрагмент другим, сохранив при этом предыдущее +состояние в стеке переходов назад:</p> + +<pre> +// Create new fragment and transaction +Fragment newFragment = new ExampleFragment(); +FragmentTransaction transaction = getFragmentManager().beginTransaction(); + +// Replace whatever is in the fragment_container view with this fragment, +// and add the transaction to the back stack +transaction.replace(R.id.fragment_container, newFragment); +transaction.addToBackStack(null); + +// Commit the transaction +transaction.commit(); +</pre> + +<p>В этом коде объект {@code newFragment} замещает фрагмент (если таковой имеется), находящийся в +контейнере макета, на который указывает идентификатор {@code R.id.fragment_container}. В результате вызова метода {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} транзакция замены +сохраняется в стеке переходов назад, чтобы пользователь мог обратить транзакцию и вернуть +предыдущий фрагмент, нажав кнопку <em>Назад</em>.</p> + +<p>Если в транзакцию добавить несколько изменений (например, еще раз вызвать {@link +android.app.FragmentTransaction#add add()} или {@link android.app.FragmentTransaction#remove +remove()}), а затем вызвать {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()}, все изменения, примененные до вызова +метода {@link android.app.FragmentTransaction#commit commit()}, будут добавлены в стек + переходов назад как одна транзакция, и кнопка<em>Назад</em> обратит их все вместе.</p> + +<p>Порядок добавления изменений к объекту {@link android.app.FragmentTransaction} не играет роли + за следующими исключениями:</p> +<ul> + <li>метод {@link android.app.FragmentTransaction#commit()} должен быть вызван в последнюю очередь;</li> + <li>если в один контейнер добавляется несколько фрагментов, то порядок их +добавления определяет порядок, в котором они появляются в иерархии видов.</li> +</ul> + +<p>Если при выполнении транзакции, удаляющей фрагмент, не вызвать метод {@link android.app.FragmentTransaction#addToBackStack(String) +addToBackStack()}, при фиксации транзакции фрагмент +уничтожается, и пользователь теряет возможность вернуться к нему. В то же время, если +вызвать {@link android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} при +удалении фрагмента, фрагмент перейдет в состояние <em>остановки</em> и будет возобновлен, если пользователь вернется +к нему.</p> + +<p class="note"><strong>Совет.</strong> К каждой транзакции с фрагментом можно применить анимацию +перехода, вызвав {@link android.app.FragmentTransaction#setTransition setTransition()} до +фиксации.</p> + +<p>Вызов метода {@link android.app.FragmentTransaction#commit()} не приводит к немедленному выполнению + транзакции. Метод запланирует ее выполнение в потоке пользовательского интерфейса операции (в «главном» потоке), как только +у потока появится возможность для этого. Впрочем, при необходимости можно вызвать {@link +android.app.FragmentManager#executePendingTransactions()} из потока пользовательского интерфейса, чтобы +транзакции, запланированные методом {@link android.app.FragmentTransaction#commit()} были выполнены немедленно. Как правило, +в этом нет необходимости, за исключением случаев, когда транзакция является зависимостью для заданий в других потоках.</p> + +<p class="caution"><strong>Внимание!</strong> Фиксировать транзакцию методом {@link +android.app.FragmentTransaction#commit commit()} можно только до того, как операция<a href="{@docRoot}guide/components/activities.html#SavingActivityState">сохранит свое +состояние</a> (после того, как пользователь покинет ее). Попытка зафиксировать транзакцию после этого момента + вызовет исключение. Дело в том, что состояние после фиксации может быть потеряно, если понадобится +восстановить операцию. В ситуациях, в которых потеря фиксации не критична, следует вызывать {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}.</p> + + + + +<h2 id="CommunicatingWithActivity">Взаимодействие с операцией</h2> + +<p>Хотя {@link android.app.Fragment} реализован как объект, независимый от +класса {@link android.app.Activity}, и может быть использован внутри нескольких операций, конкретный экземпляр +фрагмента напрямую связан с содержащей его операцией.</p> + +<p>В частности, фрагмент может обратиться к экземпляру {@link android.app.Activity} с помощью метода {@link +android.app.Fragment#getActivity()} и без труда выполнить такие задачи, как поиск представления в макете +операции:</p> + +<pre> +View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list); +</pre> + +<p>Аналогичным образом операция может вызывать методы фрагмента, получив ссылку на объект +{@link android.app.Fragment} от {@link android.app.FragmentManager} с помощью метода {@link +android.app.FragmentManager#findFragmentById findFragmentById()} или {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()}. Например:</p> + +<pre> +ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); +</pre> + + +<h3 id="EventCallbacks">Создание обратного вызова события для операции</h3> + +<p>В некоторых случаях необходимо, чтобы фрагмент использовал события совместно с операцией. Хороший способ реализации этого +состоит в том, чтобы определить интерфейс обратного вызова внутри фрагмента и потребовать от контейнерной операции +его реализации. Когда операция примет обратный вызов через этот интерфейс, она сможет обмениваться информацией с +другими фрагментами в макете по мере необходимости.</p> + +<p>Пусть, например, у новостного приложения имеются два фрагмента в одной операции: один для отображения списка +статей (фрагмент A), а другой—для отображения статьи (фрагмент B). Тогда фрагмент A должен сообщать +операции о том, что выбран пункт списка, чтобы она могла сообщить фрагменту B о необходимости отобразить статью. В +этом случае интерфейс {@code OnArticleSelectedListener} объявляется во фрагменте A:</p> + +<pre> +public static class FragmentA extends ListFragment { + ... + // Container Activity must implement this interface + public interface OnArticleSelectedListener { + public void onArticleSelected(Uri articleUri); + } + ... +} +</pre> + +<p>Тогда операция, содержащая этот фрагмент, реализует интерфейс {@code OnArticleSelectedListener} +и переопределит +метод {@code onArticleSelected()}, чтобы извещать фрагмент B о событии, исходящем от фрагмента A. Чтобы +контейнерная операция наверняка реализовала этот интерфейс, метод обратного вызова {@link +android.app.Fragment#onAttach onAttach()} во фрагменте A (который система вызывает при добавлении +фрагмента в операцию) создает экземпляр класса {@code OnArticleSelectedListener}, выполнив +приведение типа объекта {@link android.app.Activity}, который передается методу {@link android.app.Fragment#onAttach +onAttach()}:</p> + +<pre> +public static class FragmentA extends ListFragment { + OnArticleSelectedListener mListener; + ... + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + try { + mListener = (OnArticleSelectedListener) activity; + } catch (ClassCastException e) { + throw new ClassCastException(activity.toString() + " must implement OnArticleSelectedListener"); + } + } + ... +} +</pre> + +<p>Если операция не реализовала интерфейс, фрагмент генерирует исключение +{@link java.lang.ClassCastException}. +В случае успеха элемент {@code mListener} будет содержать ссылку на реализацию интерфейса +{@code OnArticleSelectedListener} в операции, чтобы фрагмент A мог использовать +события совместно с операцией, вызывая методы, определенные интерфейсом {@code OnArticleSelectedListener}. Например, если фрагмент A является расширением +класса {@link android.app.ListFragment}, то всякий раз, +когда пользователь нажимает элемент списка, система вызывает {@link android.app.ListFragment#onListItemClick +onListItemClick()} во фрагменте. Этот метод, в свою очередь, вызывает метод {@code onArticleSelected()}, чтобы использовать +событие совместно с операцией:</p> + +<pre> +public static class FragmentA extends ListFragment { + OnArticleSelectedListener mListener; + ... + @Override + public void onListItemClick(ListView l, View v, int position, long id) { + // Append the clicked item's row ID with the content provider Uri + Uri noteUri = ContentUris.{@link android.content.ContentUris#withAppendedId withAppendedId}(ArticleColumns.CONTENT_URI, id); + // Send the event and Uri to the host activity + mListener.onArticleSelected(noteUri); + } + ... +} +</pre> + +<p>Параметр {@code id}, передаваемый методу {@link +android.app.ListFragment#onListItemClick onListItemClick()}, — это идентификатор строки с выбранным элементом списка, +который операция (или другой фрагмент) использует для получения статьи от объекта {@link +android.content.ContentProvider} приложения.</p> + +<p><!--To see a complete implementation of this kind of callback interface, see the <a +href="{@docRoot}resources/samples/NotePad/index.html">NotePad sample</a>. -->Дополнительные сведения о +работе с поставщиком контента приводятся в документе <a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a>.</p> + + + +<h3 id="ActionBar">Добавление элементов в строку действий</h3> + +<p>Фрагменты могут добавлять пункты меню в <a href="{@docRoot}guide/topics/ui/menus.html#options-menu">Меню вариантов</a> операции (и, следовательно, в <a href="{@docRoot}guide/topics/ui/actionbar.html">Строку действий</a>), реализовав +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()}. Однако, чтобы этот метод мог +принимать вызовы, необходимо вызывать {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()} во время выполнения метода {@link +android.app.Fragment#onCreate(Bundle) onCreate()}, чтобы сообщить, что фрагмент +намеревается добавить пункты в Меню вариантов (в противном случае фрагмент не примет вызов метода +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}).</p> + +<p>Любые пункты, добавляемые фрагментом в Меню вариантов, присоединяются к +уже существующим. Кроме того, фрагмент принимает обратные вызовы метода {@link +android.app.Fragment#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}, когда пользователь выбирает пункт +меню.</p> + +<p>Разработчик может также зарегистрировать представление в макете своего фрагмента, чтобы предоставить контекстное меню. Для этого следует вызвать метод {@link +android.app.Fragment#registerForContextMenu(View) registerForContextMenu()}. Когда пользователь открывает +контекстное меню, фрагмент принимает вызов метода {@link +android.app.Fragment#onCreateContextMenu(ContextMenu,View,ContextMenu.ContextMenuInfo) +onCreateContextMenu()}. Когда пользователь выбирает пункт меню, фрагмент принимает вызов метода {@link +android.app.Fragment#onContextItemSelected(MenuItem) onContextItemSelected()}.</p> + +<p class="note"><strong>Примечание.</strong> Хотя фрагмент принимает обратный вызов по событию «выбран пункт меню» +для каждого добавленного им пункта, операция первой принимает соответствующий обратный вызов, когда пользователь +выбирает пункт меню. Если имеющаяся в операции реализация обратного вызова по событию «выбран пункт меню» не +обрабатывает выбранный пункт, событие передается методу обратного вызова во фрагменте. Это справедливо для +Меню вариантов и контекстных меню.</p> + +<p>Подробные сведения относительно меню см. в руководствах для разработчиков <a href="{@docRoot}guide/topics/ui/menus.html">Меню</a> и <a href="{@docRoot}guide/topics/ui/actionbar.html">Строка действий</a> </p> + + + + +<h2 id="Lifecycle">Управление жизненным циклом фрагмента</h2> + +<div class="figure" style="width:350px"> +<img src="{@docRoot}images/activity_fragment_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Рисунок 3</strong>. Влияние жизненного цикла операции на жизненный цикл +фрагмента</p> +</div> + +<p>Управление жизненным циклом фрагмента во многом аналогично управлению жизненным циклом операции. Как и +операция, фрагмент может существовать в одном из трех состояний:</p> + +<dl> + <dt><i>Возобновлен</i></dt> + <dd>Фрагмент виден во время выполнения операции.</dd> + + <dt><i>Приостановлен</i></dt> + <dd>На переднем плане выполняется и находится в фокусе другая операция, но операция, содержащая данный +фрагмент, по-прежнему видна (операция переднего плана частично прозрачна или не +занимает весь экран).</dd> + + <dt><i>Остановлен</i></dt> + <dd>Фрагмент не виден. Либо контейнерная операция остановлена, либо +фрагмент удален из нее, но добавлен в стек переходов назад. Остановленный фрагмент +по-прежнему активен (вся информация о состоянии и элементах сохранена в системе). Однако он больше +не виден пользователю и будет уничтожен в случае уничтожения операции.</dd> +</dl> + +<p>Здесь снова просматривается аналогия с операцией: разработчик может сохранить состояние фрагмента с помощью {@link +android.os.Bundle} на случай, если процесс операции будет уничтожен, а разработчику понадобится восстановить +состояние фрагмента при повторном создании операции. Состояние можно сохранить во время выполнения метода обратного вызова {@link +android.app.Fragment#onSaveInstanceState onSaveInstanceState()} во фрагменте и восстановить его во время выполнения +{@link android.app.Fragment#onCreate onCreate()}, {@link +android.app.Fragment#onCreateView onCreateView()} или {@link +android.app.Fragment#onActivityCreated onActivityCreated()}. Дополнительные сведения о сохранении +состояния приводятся в документе <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Операции</a>. +</p> + +<p>Самое значительное различие в ходе жизненного цикла между операцией и фрагментом состоит в принципах +их сохранения в соответствующих стеках переходов назад. По умолчанию операция помещается в управляемый системой стек переходов назад для операций, +когда она останавливается (чтобы пользователь мог вернуться +к ней с помощью кнопки <em>Назад</em>, как описано в статье <a href="{@docRoot}guide/components/tasks-and-back-stack.html">Задачи и стек переходов назад</a>). +В то же время, фрагмент помещается в стек переходов назад, управляемый операцией, только когда разработчик +явно запросит сохранение конкретного экземпляра, вызвав метод {@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} во время транзакции, +удаляющей фрагмент.</p> + +<p>В остальном управление жизненным циклом фрагмента очень похоже на управление жизненным циклом +операции. Поэтому практические рекомендации по <a href="{@docRoot}guide/components/activities.html#Lifecycle">управлению жизненным циклом +операций</a> применимы и к фрагментам. При этом разработчику необходимо понимать, как жизненный цикл +операции влияет на жизненный цикл фрагмента.</p> + +<p class="caution"><strong>Внимание!</strong> Если возникнет необходимость в объекте {@link android.content.Context} +внутри объекта класса {@link android.app.Fragment}, можно вызвать метод{@link android.app.Fragment#getActivity()}. +Однако разработчик должен быть внимательным и вызывать метод {@link android.app.Fragment#getActivity()} только когда фрагмент +прикреплен к операции. Если фрагмент еще не прикреплен или был откреплен в конце +его жизненного цикла, метод {@link android.app.Fragment#getActivity()} возвратит null.</p> + + +<h3 id="CoordinatingWithActivity">Согласование с жизненным циклом операции</h3> + +<p>Жизненый цикл операции, содержащей фрагмент, непосредственным образом влияет на жизненый цикл +фрагмента, так что каждый обратный вызов жизненного цикла операции приводит к аналогичному обратного вызову для каждого +фрагмента. Например, когда операция принимает вызов {@link android.app.Activity#onPause}, каждый +ее фрагмент принимает {@link android.app.Fragment#onPause}.</p> + +<p>Однако у фрагментов есть несколько дополнительных методов обратного вызова жизненого цикла, которые обеспечивают уникальное взаимодействие с операцией +для выполнения таких действий, как создание и уничтожение пользовательского интерфейса фрагмента. Вот эти +методы:</p> + +<dl> + <dt>{@link android.app.Fragment#onAttach onAttach()}</dt> + <dd>Вызывается, когда фрагмент связывается с операцией (ему передается объект {@link +android.app.Activity}).</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>Вызывается для создания иерархии представлений, связанной с фрагментом.</dd> + <dt>{@link android.app.Fragment#onActivityCreated onActivityCreated()}</dt> + <dd>Вызывается, когда метод {@link android.app.Activity#onCreate +onCreate()}, принадлежащий операции, возвращает управление.</dd> + <dt>{@link android.app.Fragment#onDestroyView onDestroyView()}</dt> + <dd>Вызывается при удалении иерархии представлений, связанной с фрагментом.</dd> + <dt>{@link android.app.Fragment#onDetach onDetach()}</dt> + <dd>Вызывается при разрыве связи фрагмента с операцией.</dd> +</dl> + +<p>Зависимость жизненого цикла фрагмента от содержащей его операции иллюстрируется +рисунком 3. На этом рисунке можно видеть, что очередное состояние операции определяет, какие +методы обратного вызова может принимать фрагмент. Например, когда операция принимает свой метод обратного вызова {@link +android.app.Activity#onCreate onCreate()}, фрагмент внутри этой операции принимает всего лишь метод обратного вызова +{@link android.app.Fragment#onActivityCreated onActivityCreated()}.</p> + +<p>Когда операция переходит в состояние «возобновлена», можно свободно добавлять в нее фрагменты и удалять +их. Таким образом, жизненный цикл фрагмента может быть независимо изменен, только пока операция остается +в состоянии «возобновлена».</p> + +<p>Однако, когда операция выходит из этого состояния, продвижение фрагмента по его +жизненному циклу снова осуществляется операцией.</p> + + + + +<h2 id="Example">Пример:</h2> + +<p>Чтобы суммировать все сказанное в этом документе, рассмотрим пример операции, +использующей два фрагмента для создания макета с двумя панелями. Операция, код которой приведен ниже, включает в себя один фрагмент для +отображения списка пьес Шекспира, а другой — для отображения краткого содержания пьесы, выбранной +из списка. В примере показано, как следует организовывать различные конфигурации фрагментов +в зависимости от конфигурации экрана. </p> + +<p class="note"><strong>Примечание.</strong> Полный исходный код этой операции находится в разделе +<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.html">{@code +FragmentLayout.java}</a>.</p> + +<p>Главная операция применяет макет обычным способом, в методе {@link +android.app.Activity#onCreate onCreate()}:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java main} + +<p>Здесь применяется макет {@code fragment_layout.xml}:</p> + +{@sample development/samples/ApiDemos/res/layout-land/fragment_layout.xml layout} + +<p>Пользуясь этим макетом, система создает экземпляр класса {@code TitlesFragment} (список +пьес), как только операция загрузит макет. При этом объект {@link android.widget.FrameLayout} +(в котором будет находиться фрагмент с кратким содержанием) занимает место в правой части +экрана, но поначалу остается пустым. Как будет показано ниже, фрагмент не помещается в {@link android.widget.FrameLayout}, пока пользователь не выберет элемент +в списке.</p> + +<p>Однако не все экраны достаточно широки, чтобы отображать +краткое содержание рядом со списком пьес. Поэтому описанный выше макет используется только при альбомной ориентации +экрана и хранится в файле {@code res/layout-land/fragment_layout.xml}.</p> + +<p>Когда же устройство находится в книжной ориентации, система применяет макет, приведенный ниже, который +хранится в файле{@code res/layout/fragment_layout.xml}:</p> + +{@sample development/samples/ApiDemos/res/layout/fragment_layout.xml layout} + +<p>В этом макете присутствует только объект {@code TitlesFragment}. Это означает, что при +книжной ориентации устройства виден только список пьес. Когда пользователь нажимает на элемент +списка в этой конфигурации, приложение запускает новую операцию для отображения краткого содержания, +а не загружает второй фрагмент.</p> + +<p>Далее можно видеть, как это реализовано в классах фрагмента. Вначале идет код класса {@code +TitlesFragment}, отображающий список пьес Шекспира. Этот фрагмент является расширением класса {@link +android.app.ListFragment} и использует его функции для выполнения основной работы со списком.</p> + +<p>Изучая код, обратите внимание на то, что в качестве реакции на нажатие пользователем +элемента списка возможны две модели поведения. В зависимости от того, какой из двух макетов активен, либо в рамках одной операции создается и отображается новый фрагмент +с кратким содержанием (за счет добавления фрагмента в объект {@link +android.widget.FrameLayout}), либо запускается новая операция (отображающая фрагмент).</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java titles} + +<p>Второй фрагмент, {@code DetailsFragment}, отображает краткое содержание пьесы, выбранной в +списке {@code TitlesFragment}:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java details} + +<p>Вспомним код класса {@code TitlesFragment}: если пользователь нажимает на пункт списка, а +текущий макет <em>не</em> включает в себя представление {@code R.id.details} (которому принадлежит фрагмент +{@code DetailsFragment}), то приложение запускает операцию {@code DetailsActivity} +для отображения содержимого элемента.</p> + +<p>Далее идет код класса {@code DetailsActivity}, который всего лишь содержит объект {@code DetailsFragment} для отображения +краткого содержания выбранной пьесы на экране в книжной ориентации:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +<p>Обратите внимание, что в альбомной конфигурации эта операция самостоятельно завершается, чтобы главная +операция могла принять управление и отобразить фрагмент {@code DetailsFragment} рядом с фрагментом{@code TitlesFragment}. +Это может произойти, если пользователь запустит операцию {@code DetailsActivity} в книжной ориентации экрана, а +затем перевернет устройство в альбомную ориентацию (в результате чего текущая операция будет перезапущена).</p> + + +<p>Дополнительные образцы кода, использующего фрагменты (и файлы с полным исходным кодом этого примера), +доступны в приложении-примере API Demos в разделе <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/index.html#Fragment"> +ApiDemos</a> (которое можно загрузить из <a href="{@docRoot}resources/samples/get.html">компонента Samples SDK</a>).</p> + + diff --git a/docs/html-intl/intl/ru/guide/components/fundamentals.jd b/docs/html-intl/intl/ru/guide/components/fundamentals.jd new file mode 100644 index 000000000000..181cbbdfb107 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/fundamentals.jd @@ -0,0 +1,480 @@ +page.title=Основы создания приложений +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> +<li><a href="#Components">Компоненты приложения</a> + <ol> + <li><a href="#ActivatingComponents">Активация компонентов</a></li> + </ol> +</li> +<li><a href="#Manifest">Файл манифеста</a> + <ol> + <li><a href="#DeclaringComponents">Объявление компонентов</a></li> + <li><a href="#DeclaringRequirements">Объявление требований приложения</a></li> + </ol> +</li> +<li><a href="#Resources">Ресурсы приложения</a></li> +</ol> +</div> +</div> + +<p>Приложения для Android пишутся на языке программирования Java. Инструменты Android SDK (Software Development Kit – комплект разработки программного обеспечения) компилируют +написанный вами код — и все требуемые файлы данных и ресурсов — в файл APK – <i>программный пакет Android</i>, +который представляет собой файл архива с расширением {@code .apk}. В файле APK находится все, что требуется для работы +Android-приложения, и он позволяет установить приложение на любом устройстве под управлением системы Android.</p> + +<p>Каждое приложение Android, установленное на устройстве, работает в собственной "песочнице" (изолированной программной среде): </p> + +<ul> + <li>операционная система Android представляет собой многопользовательскую систему Linux, в которой каждое приложение является +отдельным пользователем;</li> + +<li>по умолчанию система назначает каждому приложению уникальный идентификатор пользователя Linux (этот идентификатор используется только +системой и неизвестен приложению); система устанавливает полномочия для всех файлов + в приложении, с тем чтобы доступ к ним был разрешен только пользователю с идентификатором, назначенным этому приложению; </li> + +<li>у каждого процесса имеется собственная виртуальная машина (ВМ), так что код приложения выполняется изолированно от +других приложений;</li> + +<li>по умолчанию каждое приложение выполняется в собственном процессе Linux. Android запускает процесс, когда требуется +выполнить какой-либо компонент приложения, а затем завершает процесс, когда он больше не +нужен либо когда системе требуется освободить память для других приложений.</li> +</ul> + +<p>Таким образом система Android реализует <em>принцип предоставления минимальных прав</em>. То есть +каждое приложение по умолчанию имеет доступ только к тем компонентам, которые ему необходимы для работы, и +ни к каким другим. Благодаря этому формируется исключительно безопасная среда, в которой приложение не имеет доступа к недозволенным областям +системы.</p> + +<p>Однако у приложения есть варианты предоставления своих данных другим приложениям и +доступа к системным службам:</p> + +<ul> + <li>двум приложениям можно назначить один идентификатор пользователя Linux. В этом случае +каждый из них сможет обращаться к файлам другого приложения. Для экономии ресурсов системы также можно +сделать так, чтобы приложения с одинаковым идентификатором пользователя выполнялись в одном процессе Linux и использовали одну ВМ ( +приложения также должны быть подписаны одним сертификатом);</li> + <li>приложение может запросить разрешение на доступ к данным устройства, например к контактам +пользователя, SMS-сообщениям, подключаемой карте памяти (SD-карте), камере, Bluetooth и др. Все +разрешения должны предоставляться приложению при его установке.</li> +</ul> + +<p>Это основные сведения о том, каким образом приложение Android существует в системе. В остальной части +этого документа раскрываются следующие темы:</p> +<ul> + <li>базовые компоненты, которые определяют приложение;</li> + <li>файл манифеста, в котором объявляются компоненты и функции устройства, необходимые для +приложения;</li> + <li>ресурсы, которые существуют отдельно от кода приложения и позволяют приложению +адаптировать свою работу к устройствам с различными конфигурациями.</li> +</ul> + + + +<h2 id="Components">Компоненты приложения</h2> + +<p>Компоненты приложения являются кирпичиками, из которых состоит приложение для Android. Каждый +компонент представляет собой отдельную точку, через которую система может войти в приложение. Не все +компоненты являются точками входа для пользователя, а некоторые из них зависят друг от друга. При этом каждый компонент является +самостоятельной структурной единицей и играет определенную роль — каждый из них представляет собой уникальный элемент структуры, который +определяет работу приложения в целом.</p> + +<p>Компоненты приложения можно отнести к одному из четырех типов. Компоненты каждого типа предназначены для определенной цели, +они имеют собственный жизненный цикл, который определяет способ создания и прекращения существования компонента.</p> + +<p>Четыре типа компонентов:</p> + +<dl> + +<dt><b>Операции</b></dt> + +<dd>Операция (Activity) <i>представляет</i> собой один экран с пользовательским интерфейсом. Например, +в приложении для работы с электронной почтой одна операция может служить для отображения списка новых +сообщений, другая – для составления сообщения и третья операция – для чтения сообщений. Несмотря на то что +операции совместно формируют связное взаимодействие пользователя с приложением по работе с электронной почтой, каждая из них +не зависит от других операций. Любые из этих операций могут быть запущены +другим приложением (если это позволяет приложение по работе с электронной почтой). Например, приложение для камеры может запустить +операцию в приложении по работе с электронной почтой, которая составляет новое сообщение, чтобы пользователь мог отослать фотографию. + +<p>Операция относится к подклассу класса {@link android.app.Activity}. Подробные сведения об этом можно +найти в руководстве для разработчиков в статье <a href="{@docRoot}guide/components/activities.html">Операции</a> +.</p> +</dd> + + +<dt><b>Службы</b></dt> + +<dd>Служба (Service) <i>представляет</i> собой компонент, который работает в фоновом режиме и выполняет длительные +операции, связанные с работой удаленных процессов. Служба +не имеет пользовательского интерфейса. Например, она может воспроизводить музыку в фоновом режиме, пока +пользователь работает в другом приложении, или же она может получать данные по сети, не +блокируя взаимодействие пользователя с операцией. Служба может быть запущена другим компонентом, который затем будут взаимодействовать с ней, – например +операцией. + +<p>Служба относится к подклассу класса {@link android.app.Service}. Подробные сведения об этом можно +найти в руководстве для разработчиков в статье <a href="{@docRoot}guide/components/services.html">Службы</a> +.</p> +</dd> + + +<dt><b>Поставщики контента</b></dt> + +<dd>Поставщик <i>контента (Content provider)</i> управляет общим набором данных приложения. Данные можно хранить в +файловой системе, базе данных SQLite, в Интернете или любом другом постоянном месте хранения, к которому у вашего +приложения имеется доступ. Посредством поставщика контента другие приложения могут запрашивать или даже изменять +данные (если поставщик контента позволяет делать это). Например, в системе Android есть поставщик +контента, который управляет информацией контактов пользователя. Любое приложение, получившее соответствующие +разрешения, может запросить часть этого поставщика контента (например {@link +android.provider.ContactsContract.Data}), для чтения и записи сведений об определенном человеке. + +<p>Поставщики контента также используются для чтения и записи данных, доступ к которым внешним компонентам +приложение не предоставляет. Например, в образце приложения <a href="{@docRoot}resources/samples/NotePad/index.html">Note Pad</a> с помощью +поставщика контента выполняется сохранение заметок.</p> + +<p>Поставщик контента относится к подклассу класса {@link android.content.ContentProvider}. +Он должен реализовывать стандартный набор API-интерфейсов, с помощью которых другие приложения будут выполнять +транзакции. Подробные сведения можно найти в руководстве для разработчиков в статье <a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a> +.</p> +</dd> + + +<dt><b>Приемники широковещательных сообщений</b></dt> + +<dd>Приемник широковещательных сообщений (Broadcast receiver) <i>представляет</i> собой компонент, который реагирует на объявления +распространяемые по всей системе. Многие из этих объявлений рассылает система — например объявление о том, +что экран выключился, аккумулятор разряжен или был сделан фотоснимок. +Объявления также могут рассылаться приложениями, — например, чтобы сообщить другим приложениям о том, что +какие-то данные были загружены на устройство и теперь готовы для использования. Несмотря на то что приемники широковещательных сообщений +не имеют пользовательского интерфейса, они могут<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">создавать уведомления в строке состояния,</a> +чтобы предупредить пользователя о событии "рассылка объявления". Однако чаще всего они являются +просто "шлюзом" для других компонентов и предназначены для выполнения минимального объема работы. Например +, они могут инициировать выполнение службой определенных действий при возникновении события. + +<p>Приемник широковещательных сообщений относится к подклассу класса {@link android.content.BroadcastReceiver} +, а каждое такое сообщение предоставляется как объект {@link android.content.Intent}. Подробные сведения изложены +в руководстве, посвященном классу {@link android.content.BroadcastReceiver}.</p> +</dd> + +</dl> + + + +<p>Уникальной особенностью системы Android является то, что любое приложение может запустить компонент +другого приложения. Например, если вы хотите дать пользователю возможность фотографировать, используя +камеру устройства, то, поскольку наверняка имеется другое приложение, которое может выполнить это действие, вместо того чтобы разработать операцию фотографирования в своем приложении, вы можете вызвать +такое приложение. Вам не +нужно внедрять код из приложения для камеры или даже устанавливать на него ссылку. +Вместо этого вы можете просто запустить операцию фотографирования + из приложения для камеры. По завершении этой операции фотография будет возвращена в ваше приложение, и ее можно будет использовать. Для пользователя + это будет выглядеть как одно приложение.</p> + +<p>Когда система запускает компонент, она запускает процесс для этого приложения (если +он еще не был запущен) и создает экземпляры классов, которые требуются этому компоненту. Например, если ваше приложение +запустит операцию фотографирования в приложении для камеры, эта операция +будет выполняться в процессе, который относится к этому стороннему приложению, а не в процессе вашего приложения. +Поэтому, в отличие от приложений для большинства других систем, в приложениях для Android отсутствует единая +точка входа (например, в них нет функции {@code main()}).</p> + +<p>Поскольку система выполняет каждое приложение в отдельном процессе с такими правами доступа к файлам, которые +ограничивают доступ в другие приложения, ваше приложение не может напрямую вызвать компонент из +другого приложения. Это может сделать сама система Android. Поэтому, чтобы вызвать компонент в +другом приложении, необходимо сообщить системе о своем намерении (<em>Intent</em>) +запустить определенный компонент. После этого система активирует для вас этот компонент.</p> + + +<h3 id="ActivatingComponents">Активация компонентов</h3> + +<p>Компоненты трех из четырех возможных типов — операции, службы и +приемники широковещательных сообщений — активируются асинхронным сообщением, которое называется <em>Intent</em> (намерение). +Объекты Intent связывают друг с другом отдельные компоненты во время выполнения, будь то это компоненты + вашего или стороннего приложения (эти объекты Intent можно представить себе +в виде мессенджеров, которые посылают другим компонентам запрос на выполнение действий).</p> + +<p>Объект Intent создается с помощью объекта {@link android.content.Intent}, который описывает запрос на +активацию либо конкретного компонента, либо компонента конкретного <em>типа</em> — соответственно, намерение Intent +может быть явным или неявным.</p> + +<p>Для операций и служб Объект Intent определяет действие, которое требуется выполнить (например, просмотреть (view) или +отправить (send) что-то), а также может указывать URI (Uniform Resource Identifier – унифицированный идентификатор ресурса) данных, с которыми это действие нужно выполнить (помимо прочих сведений, которые +нужно знать запускаемому компоненту). Например, объект Intent может передавать запрос +на выполнение операции "показать изображение" или "открыть веб-страницу". В некоторых ситуациях операцию можно +запустить, чтобы получить результат. В этом случае операция возвращает +результат также в виде объекта {@link android.content.Intent} (например, можно отправить сообщение Intent, чтобы дать +пользователю возможность выбрать контакт и вернуть его вам — в ответном сообщении Intent будет содержаться +URI, указывающий на выбранный контакт).</p> + +<p>Для приемников широковещательных сообщений Intent просто определяет +передаваемое объявление (например, широковещательное сообщение о низком уровне заряда аккумулятора +содержит только строку "аккумулятор разряжен").</p> + +<p>Компоненты четвертого типа – поставщики контента – сообщениями Intent не активируются. Они +активируются по запросу от {@link android.content.ContentResolver}. Процедура определения + контента (content resolver) обрабатывает все прямые транзакции с поставщиком контента, с тем чтобы этого не пришлось делать компоненту, который +выполняет транзакции с поставщиком. Вместо этого он вызывает методы для объекта {@link +android.content.ContentResolver}. Это формирует слой, абстрагирующий (в целях безопасности) поставщика +контента от компонента, запрашивающего информацию.</p> + +<p>Для активации компонентов каждого типа имеются отдельные методы:</p> +<ul> + <li>Можно запустить операцию (или определить для нее какое-то новое действие), +передав объект {@link android.content.Intent} методу {@link android.content.Context#startActivity +startActivity()} или {@link android.app.Activity#startActivityForResult startActivityForResult()} +(если требуется, чтобы операция вернула результат).</li> + <li>Можно запустить службу (либо выдать работающей службе новые инструкции), +передав объект {@link android.content.Intent} методу {@link android.content.Context#startService +startService()}. Либо можно установить привязку к службе, передав объект{@link android.content.Intent} методу +{@link android.content.Context#bindService bindService()}.</li> + <li>Можно инициировать рассылку сообщений, передав объект {@link android.content.Intent} таким методам, как +{@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, {@link +android.content.Context#sendOrderedBroadcast(Intent, String) sendOrderedBroadcast()} и {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.</li> + <li>Можно выполнить запрос к поставщику контента, вызвав метод {@link +android.content.ContentProvider#query query()} для объекта {@link android.content.ContentResolver}.</li> +</ul> + +<p>Подробные сведения об использовании объектов Intent приведены в документе <a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent и +фильтры объектов Intent</a>. Более подробная информация об активации определенных компонентов также приведена +в следующих документах: <a href="{@docRoot}guide/components/activities.html">Операции</a>, <a href="{@docRoot}guide/components/services.html">Службы</a>, {@link +android.content.BroadcastReceiver} и <a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a>.</p> + + +<h2 id="Manifest">Файл манифеста</h2> + +<p>Для запуска компонента приложения системе Android необходимо знать, что +компонент существует. Для этого она читает файл {@code AndroidManifest.xml} приложения (файл +манифеста). В этом файле, который должен находиться в корневой папке +приложения, должны быть объявлены все компоненты приложения.</p> + +<p>Помимо объявления компонентов приложения, манифест служит и для других целей, +среди которых:</p> +<ul> + <li>указание всех полномочий пользователя, которые требуются приложению, например разрешения на доступ в Интернет или +на чтение контактов пользователя;</li> + <li>объявление минимального<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">уровня API</a>, +требуемого приложению, с учетом того, какие API-интерфейсы оно использует;</li> + <li>объявление аппаратных и программных функций, которые нужны приложению или используются им, например камеры, +службы Bluetooth или сенсорного экрана;</li> + <li>указание библиотек API, с которыми необходимо связать приложение (отличные от API-интерфейсов платформы +Android), например библиотеки <a href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">Google Maps +</a>;</li> + <li>и многое другое.</li> +</ul> + + +<h3 id="DeclaringComponents">Объявление компонентов</h3> + +<p>Основная задача манифеста – это информировать систему о компонентах приложения. Например, + файл манифеста может объявлять операцию следующим образом: </p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<manifest ... > + <application android:icon="@drawable/app_icon.png" ... > + <activity android:name="com.example.project.ExampleActivity" + android:label="@string/example_label" ... > + </activity> + ... + </application> +</manifest></pre> + +<p>Атрибут {@code android:icon} в элементе <code><a +href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +указывает на ресурсы для значка, который обозначает +приложение.</p> + +<p>Атрибут {@code android:name} в элементе <code><a +href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +указывает полное имя класса подкласса {@link +android.app.Activity}, а атрибут {@code android:label} указывает строку, +которую необходимо использовать в качестве метки операции, отображаемой для пользователя.</p> + +<p>Все компоненты приложения необходимо объявлять следующим образом:</p> +<ul> + <li>элементы <code><a +href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +для операций;</li> + <li>элементы <code><a +href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> +для служб;</li> + <li>элементы <code><a +href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code> +для приемников широковещательных сообщений;</li> + <li>элементы <code><a +href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +для поставщиков контента</li> +</ul> + +<p>Системе не видны операции, службы и поставщики контента, которые имеются в исходном коде, но не объявлены +в манифесте, поэтому они не могут быть запущены. А вот +приемники широковещательных сообщений +можно либо объявить в манифесте, либо создать динамически в коде (как объекты +{@link android.content.BroadcastReceiver}) и зарегистрировать в системе путем вызова +{@link android.content.Context#registerReceiver registerReceiver()}.</p> + +<p>Подробные сведения о структуризации файла манифеста для приложения см. в документе <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">Файл AndroidManifest.xml</a> +. </p> + + + +<h3 id="DeclaringComponentCapabilities">Объявление возможностей компонентов</h3> + +<p>Как уже говорилось в разделе <a href="#ActivatingComponents">Активация компонентов</a>, с помощью объекта +{@link android.content.Intent} можно запускать операции, службы и приемники широковещательных сообщений. Для этого в объекте Intent следует +явно указать имя целевого компонента (с помощью имени класса компонента). Однако +в полной мере возможности объектов Intent раскрываются при использовании концепции <em>неявных Intent</em>. В неявном сообщении Intent +просто описывается тип действия, которое требуется выполнить (а также, хотя это и не обязательно, дата, в которую вы бы хотели +выполнить это действие). Системе же предоставляется возможности найти на устройстве компонент, который может выполнить это +действие, и запустить его. При наличии нескольких компонентов, которые могут выполнить действие, описанное в сообщении +Intent, пользователь выбирает, какой из них будет использоваться.</p> + +<p>Система определяет компоненты, которые могут ответить на сообщение Intent, путем сравнения +полученного сообщения Intent с <i>фильтрами объектов Intent,</i> указанными в файле манифеста других приложений, имеющихся + на устройстве.</p> + +<p>При объявлении операции в манифесте своего приложения по желанию можно указать +фильтры объектов Intent, которые указывают возможности операции, с тем чтобы она могла реагировать на сообщения Intent +от других приложений. Чтобы объявить фильтр Intent для своего компонента, +необходимо добавить элемент <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a> в качестве дочернего для элемента объявления компонента.</p> + +<p>Например, если вы создали приложение для работы с электронной почтой с операцией составления нового сообщения, вы можете +объявить фильтр для ответа на сообщения Intent типа "send" (для отправки нового сообщения электронной почты) следующим образом:</p> +<pre> +<manifest ... > + ... + <application ... > + <activity android:name="com.example.project.ComposeEmailActivity"> + <intent-filter> + <action android:name="android.intent.action.SEND" /> + <data android:type="*/*" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> + </activity> + </application> +</manifest> +</pre> + +<p>Затем, если другое приложение создаст объект Intent с действием {@link +android.content.Intent#ACTION_SEND} и передаст его в {@link android.app.Activity#startActivity +startActivity()}, система сможет запустить вашу операцию, дав пользователю возможность написать и отправить +сообщение электронной почты.</p> + +<p>Подробные сведения о создании фильтров объектов Intent приведены в документе <a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent и фильтры объектов Intent</a>. +</p> + + + +<h3 id="DeclaringRequirements">Объявление требований приложения</h3> + +<p>Существует огромное количество устройств, работающих под управлением Android, и не все они имеют +одинаковые функциональные возможности. Чтобы ваше приложение не могло быть установлено на устройствах, +в которых отсутствуют функции, необходимые приложению, важно четко определить профиль для +типов устройств, поддерживаемых вашим приложением, указав требования к аппаратному и программному обеспечению в +файле манифеста. Эти объявления по большей части носят информационный характер, система их не +читает. Однако их читают внешние службы, например Google Play, с целью обеспечения +фильтрации для пользователей, которые ищут приложения для своих устройств.</p> + +<p>Например, если вашему приложению требуется камера и оно использует API-интерфейсы из Android 2.1 (<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">уровень API</a> 7), +эти параметры следует объявить в файле манифеста в качестве требований следующим образом:</p> + +<pre> +<manifest ... > + <uses-feature android:name="android.hardware.camera.any" + android:required="true" /> + <uses-sdk android:minSdkVersion="7" android:targetSdkVersion="19" /> + ... +</manifest> +</pre> + +<p>Теперь ваше приложение нельзя будет установить из Google Play на устройствах, в которых <em>нет</em> камеры, а также на устройствах, работающих под управлением +Android версии <em>ниже</em> 2.1.</p> + +<p>Однако можно также объявить, что приложение использует камеру, но для его работы она не является +<em>непременно необходимой</em>. В этом случае в приложении атрибуту <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html#required">{@code required}</a> +необходимо задать значение {@code "false"}, а во время работы оно должно проверять, имеется ли +на устройстве камера, и при необходимости отключать свои функции, которые используют камеру.</p> + +<p>Более подробные сведения об управлении совместимостью своего приложения с различными устройствами +приведены в документе <a href="{@docRoot}guide/practices/compatibility.html">Совместимость устройств</a> +.</p> + + + +<h2 id="Resources">Ресурсы приложения</h2> + +<p>Приложение Android состоит не только из кода — ему необходимы такие существующие отдельно от исходного кода +ресурсы, как изображения, аудиофайлы и все, что связано с визуальным +представлением приложения. Например, необходимо определять анимацию, меню, стили, цвета +и макет пользовательских интерфейсов операций в файлах XML. Используя ресурсы приложения, можно без труда +изменять его различные характеристики, не меняя код, а, кроме того, —путем предоставления +наборов альтернативных ресурсов — можно оптимизировать свое приложение для работы с различными +конфигурациями устройств (например, для различных языков или размеров экрана).</p> + +<p>Для каждого ресурса, включаемого в проект Android, инструменты SDK задают уникальный +целочисленный идентификатор, который может использоваться, чтобы сослаться на ресурс из кода приложения или из +других ресурсов, определенных в XML. Например, если в вашем приложении имеется файл изображения с именем {@code +logo.png} (сохраненный в папке {@code res/drawable/}), инструменты SDK сформируют идентификатор ресурса +под именем {@code R.drawable.logo}, с помощью которого на изображение можно будет ссылаться и вставлять его в +пользовательский интерфейс.</p> + +<p>Один из наиболее важных аспектов предоставления ресурсов отдельно от исходного кода +заключается в возможности использовать альтернативные ресурсы для различных конфигураций +устройств. Например, определив строки пользовательского интерфейса в XML, вы сможете перевести их на другие +языки и сохранить эти переводы в отдельных файлах. Затем по <em>квалификатору</em> языка +, добавленному к имени каталога ресурса (скажем {@code res/values-fr/} для строк на французском +языке), и выбранному пользователем языку система Android применит к вашему пользовательскому интерфейсу строки на +соответствующем языке.</p> + +<p>Android поддерживает разные <em>квалификаторы</em> для соответствующих ресурсов. Квалификатор + представляет собой короткую строку, которая включается в имена каталогов ресурсов с целью +определения конфигурации устройства, для которой эти ресурсы следует использовать. В качестве другого +примера можно сказать, что для своих операций следует создавать разные макеты, которые будут соответствовать +размеру и ориентации экрана устройства. Например, когда экран устройства имеет книжную +ориентацию (расположен вертикально), кнопки в макете можно также размещатьь по вертикали, а когда экран +развернут горизонтально (альбомная ориентация), кнопки следует размещать по горизонтали. Чтобы при изменении ориентации экрана изменялся макет, +можно определить два разных макета и применить соответствующий +квалификатор к имени каталога каждого макета. После этого система будет автоматически применять соответствующий +макет в зависимости от ориентации устройства.</p> + +<p>Подробные сведения о различных видах ресурсов, которые можно включить в приложение, а также о том, как +создавать альтернативные ресурсы для разных конфигурацией устройств, см. в разделе <a href="{@docRoot}guide/topics/resources/providing-resources.html">Предоставление ресурсов</a>.</p> + + + +<div class="next-docs"> +<div class="col-6"> + <h2 class="norule">Также читайте:</h2> + <dl> + <dt><a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent и фильтры объектов Intent</a> + </dt> + <dd>Сведения об использовании API-интерфейсов {@link android.content.Intent} для + активации таких компонентов приложений, как операции и службы, а также о предоставлении возможности другим приложениям + использовать компоненты своего приложения.</dd> + <dt><a href="{@docRoot}guide/components/activities.html">Операции</a></dt> + <dd>Сведения о создании экземпляра класса {@link android.app.Activity}, + который выдает определенный экран в вашем приложении с пользовательским интерфейсом.</dd> + <dt><a href="{@docRoot}guide/topics/resources/providing-resources.html">Предоставление ресурсов</a></dt> + <dd>Описание структуры приложений Android, в которой ресурсы приложения существуют отдельно от + его кода, а также сведения о том, как предоставлять альтернативные ресурсы для определенных конфигураций + устройств. + </dd> + </dl> +</div> +<div class="col-6"> + <h2 class="norule">Возможно, вас также заинтересует:</h2> + <dl> + <dt><a href="{@docRoot}guide/practices/compatibility.html">Совместимость устройств</a></dt> + <dd>Сведения о том, каким образом система Android работает на устройствах разных типов, и общие сведения о том, + как оптимизировать свое приложение для каждого устройства или ограничить круг устройств, на которых может быть установлено + приложение.</dd> + <dt><a href="{@docRoot}guide/topics/security/permissions.html">Системные разрешения</a></dt> + <dd>Сведения о том, как система Android ограничивает доступ приложений к определенным API-интерфейсам с помощью системы + разрешений, которая требует согласия пользователя на использование этих API-интерфейсов вашим приложением.</dd> + </dl> +</div> +</div> + diff --git a/docs/html-intl/intl/ru/guide/components/index.jd b/docs/html-intl/intl/ru/guide/components/index.jd new file mode 100644 index 000000000000..41d5a3491bf2 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/index.jd @@ -0,0 +1,57 @@ +page.title=Компоненты приложения +page.landing=true +page.landing.intro=Платформа приложений системы Android позволяет создавать функциональные и инновационные приложения с помощью набора компонентов, которые можно использовать многократно. В этом разделе рассказывается о том, как создавать компоненты, определяющие элементы структуры вашего приложения, и как связывать их воедино с помощью объектов Intent. +page.metaDescription=Платформа приложений системы Android позволяет создавать функциональные и инновационные приложения с помощью набора компонентов, которые можно использовать многократно. В этом разделе рассказывается о том, как создавать компоненты, определяющие элементы структуры вашего приложения, и как связывать их воедино с помощью объектов Intent. +page.landing.image=images/develop/app_components.png +page.image=images/develop/app_components.png + +@jd:body + +<div class="landing-docs"> + + <div class="col-6"> + <h3>Статьи блога</h3> + + <a href="http://android-developers.blogspot.com/2012/05/using-dialogfragments.html"> + <h4>Использование класса DialogFragment</h4> + <p>В этой статье я расскажу, как с помощью DialogFragment с использованием вспомогательной библиотеки v4 (в целях обеспечения совместимости с устройствами, работающими под управлением системы с версией ниже, чем Honeycomb) можно отобразить простое диалоговое окно редактирования и вернуть результат в вызывающую операцию с помощью интерфейса.</p> + </a> + + <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> + <h4>Фрагменты для всех</h4> + <p>Сегодня мы выпустили библиотеку статических элементов, которая предоставляет доступ к тому же Fragments API (а также новому классу LoaderManager и нескольким другим классам), с тем чтобы приложения, совместимые с Android 1.6 и более поздними версиями, могли использовать фрагменты для создания пользовательских интерфейсов для планшетов. </p> + </a> + + <a href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html"> + <h4>Многопоточность для повышения производительности</h4> + <p>Для создания быстро реагирующих приложений рекомендуется, чтобы в основном потоке пользовательского интерфейса +выполнялся минимальный объем работы. Любая задача, которая в принципе может выполнять долго и привести к зависанию приложения, должна +обрабатываться в другом потоке.</p> + </a> + </div> + + <div class="col-6"> + <h3>Обучение</h3> + + <a href="http://developer.android.com/training/basics/activity-lifecycle/index.html"> + <h4>Управление жизненным циклом операций</h4> + <p>В этом учебном курсе разъясняются важные методы обратного вызова жизненного цикла, которые получает каждый экземпляр + операции, и описывается, как их использовать, чтобы операция выполнялась так, как этого ожидает пользователь, и не потребляла системные + ресурсы, когда они ей не нужны.</p> + </a> + + <a href="http://developer.android.com/training/basics/fragments/index.html"> + <h4>Создание динамического интерфейса пользователя с использованием фрагментов</h4> + <p>Данный курс обучения посвящен созданию динамического интерфейса пользователя с использованием +фрагментов и его оптимизации для устройств с экранами разных размеров, включая поддержку +устройств с версией Android 1.6.</p> + </a> + + <a href="http://developer.android.com/training/sharing/index.html"> + <h4>Общий доступ к контенту</h4> + <p>В этом курсе обучения рассказывается о некоторых стандартных способах отправки и получения контента + приложениями с помощью API-интерфейсов Intent и объекта ActionProvider.</p> + </a> + </div> + +</div> diff --git a/docs/html-intl/intl/ru/guide/components/intents-filters.jd b/docs/html-intl/intl/ru/guide/components/intents-filters.jd new file mode 100644 index 000000000000..d710081e4181 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/intents-filters.jd @@ -0,0 +1,899 @@ +page.title=Объекты Intent и фильтры объектов Intent +page.tags="IntentFilter" +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> + <li><a href="#Types">Типы объектов Intent</a></li> + <li><a href="#Building">Создание объекта Intent</a> + <ol> + <li><a href="#ExampleExplicit">Пример явного объекта Intent</a></li> + <li><a href="#ExampleSend">Пример неявного объекта Intent</a></li> + <li><a href="#ForceChooser">Принудительное использование блока выбора приложения</a></li> + </ol> + </li> + <li><a href="#Receiving">Получение неявного объекта Intent</a> + <ol> + <li><a href="#ExampleFilters">Примеры фильтров</a></li> + </ol> + </li> + <li><a href="#PendingIntent">Использование ожидающего объекта Intent</a></li> + <li><a href="#Resolution">Разрешение объектов Intent</a> + <ol> + <li><a href="#ActionTest">Тестирование действия</a></li> + <li><a href="#CategoryTest">Тестирование категории</a></li> + <li><a href="#DataTest">Тестирование данных</a></li> + <li><a href="#imatch">Сопоставление объектов Intent</a></li> + </ol> + </li> +</ol> + +<h2>См. также</h2> +<ol> +<li><a href="{@docRoot}training/basics/intents/index.html">Взаимодействие с другими приложениями</a></li> +<li><a href="{@docRoot}training/sharing/index.html">Общий доступ к контенту</a></li> +</ol> + +</div> +</div> + + + + +<p>{@link android.content.Intent} представляет собой объект обмена сообщениями, с помощью которого можно запросить выполнение действия +у <a href="{@docRoot}guide/components/fundamentals.html#Components">компонента другого приложения</a>. +Несмотря на то, что объекты Intent упрощают обмен данными между компонентами по нескольким аспектам, в основном они используются +в трех ситуациях:</p> + +<ul> +<li><b>Для запуска операции:</b> +<p>Компонент {@link android.app.Activity} представляет собой один экран в приложении. Для запуска нового +экземпляра компонента {@link android.app.Activity} необходимо передать объект {@link android.content.Intent} +методу{@link android.content.Context#startActivity startActivity()}. Объект {@link android.content.Intent} +описывает операцию, которую требуется запустить, а также содержит все остальные необходимые данные.</p> + +<p>Если после завершения операции от нее требуется получить результат, +вызовите метод {@link android.app.Activity#startActivityForResult +startActivityForResult()}. Ваша операция получит результат +в виде отдельного объекта {@link android.content.Intent} в обратном вызове метода {@link +android.app.Activity#onActivityResult onActivityResult()} операции. +Подробные сведения см. в руководстве <a href="{@docRoot}guide/components/activities.html">Операции</a>.</p></li> + +<li><b>Для запуска службы:</b> +<p>{@link android.app.Service} является компонентом, который выполняет действия в фоновом режиме +без пользовательского интерфейса. Службу можно запустить для выполнения однократного действия +(например, чтобы загрузить файл), передав объект{@link android.content.Intent} +методу {@link android.content.Context#startService startService()}. Объект {@link android.content.Intent} +описывает службу, которую требуется запустить, а также содержит все остальные необходимые данные.</p> + +<p>Если служба сконструирована с интерфейсом клиент-сервер, к ней +можно установить привязку из другого компонента, передав объект{@link android.content.Intent} методу {@link +android.content.Context#bindService bindService()}</code>. Подробные сведения см. в руководстве <a href="{@docRoot}guide/components/services.html">Службы</a>.</p></li> + +<li><b>Для рассылки широковещательных сообщений:</b> +<p>Широковещательное сообщение ― это сообщение, которое может принять любое приложение. Система выдает различные +широковещательные сообщения о системных событиях, например, когда система загружается или устройство начинает заряжаться. +Для выдачи широковещательных сообщений другим приложениям необходимо передать объект {@link android.content.Intent} +методу {@link android.content.Context#sendBroadcast(Intent) sendBroadcast()}, +{@link android.content.Context#sendOrderedBroadcast(Intent, String) +sendOrderedBroadcast()} или {@link +android.content.Context#sendStickyBroadcast sendStickyBroadcast()}.</p> +</li> +</ul> + + + + +<h2 id="Types">Типы объектов Intent</h2> + +<p>Есть два типа объектов Intent:</p> + +<ul> +<li><b>Явные объекты Intent</b> указывают компонент, который требуется запустить, по имени ( +полное имя класса). Явные объекты Intent обычно используются для запуска компонента из +вашего собственного приложения, поскольку вам известно имя класса операции или службы, которую необходимо запустить. Например +, можно запустить новую операцию в ответ на действие пользователя или запустить службу, чтобы загрузить +файл в фоновом режиме.</li> + +<li><b>Неявные объекты Intent</b> не содержат имени конкретного компонента. Вместо этого они в целом объявляют действие, +которое требуется выполнить, что дает возможность компоненту из другого приложения обработать этот запрос. Например, если требуется +показать пользователю место на карте, то с помощью неявного объекта Intent можно запросить, чтобы это сделало другое приложение, в котором +такая возможность предусмотрена.</li> +</ul> + +<p>Когда создан явный объект Intent для запуска операции или службы, система немедленно +запускает компонент приложения, указанный в объекте {@link android.content.Intent}.</p> + +<div class="figure" style="width:446px"> +<img src="{@docRoot}images/components/intent-filters@2x.png" width="446" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Схематическое изображение процесса передачи неявного объекта Intent +по системе для запуска другой операции: <b>[1]</b> <em>Операция А</em> создает объект +{@link android.content.Intent} с описанием действия и передает его методу {@link +android.content.Context#startActivity startActivity()}. <b>[2]</b> Система Android ищет во всех +приложениях фильтры Intent, которые соответствуют данному объекту Intent. Когда приложение с подходящим фильтром найдено, <b>[3]</b> система +запускает соответствующую операцию (<em>Операция B</em>), вызвав ее метод {@link +android.app.Activity#onCreate onCreate()} и передав ему объект {@link android.content.Intent}. +</p> +</div> + +<p>Когда создан неявный объект Intent, система Android находит подходящий компонент путем +сравнения содержимого объекта Intent с <em>фильтрами Intent</em>, объявленными в <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">файлах манифеста</a> других приложений, имеющихся на +устройстве. Если объект Intent совпадает с фильтром Intent, система запускает этот компонент и передает ему +объект {@link android.content.Intent}. Если подходящими оказываются несколько фильтров Intent, система +выводит диалоговое окно, где пользователь может выбрать приложение для выполнения данного действия.</p> + +<p>Фильтр Intent представляет собой выражение в файле манифеста приложения, +указывающее типы объектов Intent, которые мог бы +принимать компонент. Например, объявив фильтр Intent для операции, +вы даете другим приложениям возможность напрямую запускать вашу операцию с помощью некоторого объекта Intent. +Точно так же, если вы <em>не</em> объявите какие-либо фильтры Intent для операции, то ее можно будет запустить +только с помощью явного объекта Intent.</p> + +<p class="caution"><strong>Внимание!</strong> В целях обеспечения безопасности приложения всегда используйте явный объект +Intent при запуске {@link android.app.Service} и не +объявляйте фильтры Intent для своих служб. Запуск служб с помощью неявных объектов Intent является +рискованным с точки зрения безопасности, поскольку нельзя быть на абсолютно уверенным, какая служба отреагирует на такой объект Intent, +а пользователь не может видеть, какая служба запускается. Начиная с Android 5.0 (уровень API 21) система +вызывает исключение при вызове метода {@link android.content.Context#bindService bindService()} +с помощью неявного объекта Intent.</p> + + + + + +<h2 id="Building">Создание объекта Intent</h2> + +<p>Объект {@link android.content.Intent} содержит информацию, на основании которой система Android +определяет, какой компонент требуется запустить (например, точное имя компонента или категорию +компонентов, которые должны получить этот объект Intent), а также сведения, которые необходимы компоненту-получателю, +чтобы надлежащим образом выполнить действие (а именно — выполняемое действие и данные, с которыми его требуется выполнить).</p> + + +<p>Основные сведения, содержащиеся в объекте {@link android.content.Intent}:</p> + +<dl> + +<dt><b>Имя компонента</b></dt> +<dd>Имя компонента, который требуется запустить. + +<p>Эта информация является необязательной, но именно она и делает объект Intent +<b>явным</b>. Ее наличие означает, что объект Intent следует доставить только компоненту приложения, +определенному по имени. При отсутствии имени компонента объект Intent является <b>неявным</b>, а +система определяет, какой компонент получит этот объект Intent по другим сведениям, которые в нем содержатся +(например, по действию, данным и категории — см. описание далее). Поэтому, если вам требуется запустить определенный +компонент из своего приложения, следует указать его имя.</p> + +<p class="note"><strong>Примечание.</strong> При запуске {@link android.app.Service} следует +<strong>всегда указывать имя компонента</strong>. В противном случае вы не сможете быть на абсолютно уверенным в том, какая служба +отреагирует на объект Intent, а пользователь не может видеть, какая служба запускается.</p> + +<p>Это поле объекта {@link android.content.Intent} представляет собой объект +{@link android.content.ComponentName}, который можно указать с помощью полного +имени класса целевого компонента, включая имя пакета приложения. Например, +{@code com.example.ExampleActivity}. Задать имя компонента можно с помощью метода {@link +android.content.Intent#setComponent setComponent()}, {@link android.content.Intent#setClass +setClass()}, {@link android.content.Intent#setClassName(String, String) setClassName()} или конструктора +{@link android.content.Intent}.</p> + +</dd> + +<p><dt><b>Действие</b></dt> +<dd>Строка, определяющая стандартное действие, которое требуется выполнить (например, <em>view</em> (просмотр) или <em>pick</em> (выбор)). + +<p>При выдаче объектов Intent с широковещательными сообщениями это действие, которое произошло и о котором сообщается. +Действие в значительной степени определяет, каким образом структурирована остальная часть объекта Intent,—в частности, +что именно содержится в разделе данных и дополнительных данных. + +<p>Для использования объектами Intent в пределах своего приложения (либо для использования другими +приложениями, чтобы вызывать компоненты из вашего приложения) можно указать собственные действия. Обычно же следует использовать константы действий, +определенные классом {@link android.content.Intent} или другими классами платформы. Вот несколько +стандартных действий для запуска операции:</p> + +<dl> +<dt>{@link android.content.Intent#ACTION_VIEW}</dt> + <dd>Используйте это действие в объекте Intent с методом {@link + android.content.Context#startActivity startActivity()}, когда имеется определенная информация, которую + операция может показать пользователю, например, фотография в приложении галереи или адрес для + просмотра в картографическом приложении.</dd> + +<dt>{@link android.content.Intent#ACTION_SEND}</dt> + <dd>Его еще называют объектом Intent "share" (намерение предоставить общий доступ). Это действие следует использовать в объекте Intent с методом {@link + android.content.Context#startActivity startActivity()}, при наличии определенных данных, доступ к которым пользователь может + предоставить через другое приложение, например приложение для работы с электронной почтой или социальными сетями.</dd> +</dl> + +<p>Другие константы, определяющие стандартные действия, см. в справочнике по классу {@link android.content.Intent} +. Другие действия определяются +в других частях платформы Android. Например, в {@link android.provider.Settings} определяются действия, +открывающие ряд экранов приложения настройки системы.</p> + +<p>Действие можно указать для объекта Intent с методом {@link android.content.Intent#setAction +setAction()} или конструктором {@link android.content.Intent}.</p> + +<p>Если вы определяете собственные действия, обязательно используйте в качестве их префикса имя пакета +вашего приложения. Например:</p> +<pre>static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";</pre> +</dd> + +<dt><b>Данные</b></dt> +<dd>URI (объект {@link android.net.Uri}), ссылающийся на данные, с которыми будет выполняться действие и/или +тип MIME этих данных. Тип передаваемых данных обычно определяется действием объекта Intent. Например, +если действием является {@link android.content.Intent#ACTION_EDIT}, в данных должен содержаться +URI документа, который требуется отредактировать. + +<p>При создании объекта Intent, +помимо URI, зачастую бывает важно указать тип данных (их тип MIME). +Например, операция, которая может выводить на экран изображения, скорее всего, не сможет +воспроизвести аудиофайл, даже если и у тех, и у других данных будут одинаковые форматы URI. +Поэтому указание типа MIME данных помогает системе Android +найти наиболее подходящий компонент для получения вашего объекта Intent. +Однако тип MIME иногда можно унаследовать от URI — в частности, когда данные представляют собой +{@code content:} URI, который указывает, что данные находятся на устройстве и ими управляет +{@link android.content.ContentProvider}, а это дает возможность системе видеть тип MIME данных.</p> + +<p>Чтобы задать только URI данных, вызовите {@link android.content.Intent#setData setData()}. +Чтобы задать только тип MIME, вызовите {@link android.content.Intent#setType setType()}. При необходимости +оба этих параметра можно в явном виде задать с помощью {@link +android.content.Intent#setDataAndType setDataAndType()}.</p> + +<p class="caution"><strong>Внимание!</strong> Если требуется задать и URI, и тип MIME, +<strong>не</strong> вызывайте {@link android.content.Intent#setData setData()} и +{@link android.content.Intent#setType setType()}, поскольку каждый из этих методов аннулирует результат выполнения другого. +Чтобы задать URI и тип MIME всегда используйте + метод {@link android.content.Intent#setDataAndType setDataAndType()}.</p> +</dd> + +<p><dt><b>Категория</b></dt> +<dd>Строка, содержащая прочие сведения о том, каким компонентом +должна выполняться обработка этого объекта Intent. В объект Intent можно поместить любое количество +описаний категорий, однако большинству объектов Intent категория не требуется. +Вот некоторые стандартные категории: + +<dl> +<dt>{@link android.content.Intent#CATEGORY_BROWSABLE}</dt> + <dd>Целевая операция позволяет запускать себя веб-браузером для отображения данных, + указанных по ссылке — например, изображения или сообщения электронной почты. + </dd> +<dt>{@link android.content.Intent#CATEGORY_LAUNCHER}</dt> + <dd>Эта операция является начальной операцией задачи, она указана в + средстве запуска приложений системы. + </dd> +</dl> + +<p>Полный список категорий см. в описании класса {@link android.content.Intent} +.</p> + +<p>Указать категорию можно с помощью{@link android.content.Intent#addCategory addCategory()}.</p> +</dd> +</dl> + + +<p>Приведенные выше свойства (имя компонента, действие, данные и категория) представляют собой +характеристики, определяющие объект Intent. На основании этих свойств система Android +может решить, какой компонент следует запустить.</p> + +<p>Однако в объекте Intent может быть приведена и другая информация, которая не влияет на то, +каким образом определяется требуемый компонент приложения. Объект Intent также может содержать:</p> + +<dl> +<dt><b>Дополнительные данные</b></dt> +<dd>Пары "ключ-значение", содержащие прочую информацию, которая необходима для выполнения запрошенного действия. +Точно так же, как некоторые действия используют определенные виды URI данных, некоторые действия используют определенные дополнительные данные. + +<p>Добавлять дополнительные данные можно с помощью различных методов {@link android.content.Intent#putExtra putExtra()}, +каждый из которых принимает два параметра: имя и значение ключа. +Также можно создать объект {@link android.os.Bundle} со всеми дополнительными данными, затем вставить +объект {@link android.os.Bundle} в объект {@link android.content.Intent} с помощью метода {@link +android.content.Intent#putExtras putExtras()}.</p> + +<p>Например, при создании объекта Intent для отправки сообщения электронной почты с методом +{@link android.content.Intent#ACTION_SEND} можно указать получателя с помощью ключа +{@link android.content.Intent#EXTRA_EMAIL}, а тему сообщения ― с помощью ключа +{@link android.content.Intent#EXTRA_SUBJECT}.</p> + +<p>Класс {@link android.content.Intent} указывает много констант {@code EXTRA_*} +для стандартных типов данных. Если вам требуется объявить собственные дополнительные ключи (для объектов Intent, которые +принимает ваше приложение), обязательно указывайте в качестве префикса +имя пакета своего приложения. Например:</p> +<pre>static final String EXTRA_GIGAWATTS = "com.example.EXTRA_GIGAWATTS";</pre> +</dd> + +<dt><b>Флаги</b></dt> +<dd>Флаги, определенные в классе {@link android.content.Intent}, которые действуют как метаданные для объекта +Intent. Флаги должны указывать системе Android, каким образом следует запускать операцию (например, к какой +<a href="{@docRoot}guide/components/tasks-and-back-stack.html">задаче</a> должна принадлежать операция +) и как с ней обращаться после запуска (например, будет ли она указана в списке последних +операций). + +<p>Подробные сведения см. в документе, посвященном методу {@link android.content.Intent#setFlags setFlags()}.</p> +</dd> + +</dl> + + + + +<h3 id="ExampleExplicit">Пример явного объекта Intent</h3> + +<p>Явные объекты Intent используются для запуска конкретных компонентов приложения, например +определенной операции или службы. Чтобы создать явный объект Intent, задайте +имя компонента для объекта {@link android.content.Intent} — все +остальные свойства объекта Intent можно не задавать.</p> + +<p>Например, если в своем приложении вы создали службу с именем {@code DownloadService}, +предназначенную для загрузки файлов из Интернета, то для ее запуска можно использовать следующий код:</p> + +<pre> +// Executed in an Activity, so 'this' is the {@link android.content.Context} +// The fileUrl is a string URL, such as "http://www.example.com/image.png" +Intent downloadIntent = new Intent(this, DownloadService.class); +downloadIntent.setData({@link android.net.Uri#parse Uri.parse}(fileUrl)); +startService(downloadIntent); +</pre> + +<p>Конструктор {@link android.content.Intent#Intent(Context,Class)} +предоставляет {@link android.content.Context} приложению, а +компоненту ― объект {@link java.lang.Class}. Фактически +этот объект Intent явно запускает класс{@code DownloadService} в приложении.</p> + +<p>Подробные сведения о создании и запуске службы см. в руководстве +<a href="{@docRoot}guide/components/services.html">Службы</a>.</p> + + + + +<h3 id="ExampleSend">Пример неявного объекта Intent</h3> + +<p>Неявный объект Intent указывает действие, которым может быть вызвано любое имеющееся на устройстве приложение, способное +выполнить это действие. Неявные объекты Intent используются, когда ваше приложение не может выполнить +то или иное действие, а другие приложения, скорее всего, могут и вы хотите, чтобы пользователь имел возможность выбрать, какое приложение для этого использовать.</p> + +<p>Например, если у вас есть контент и вы хотите, чтобы пользователь поделился им с другими людьми, создайте объект Intent +с действием {@link android.content.Intent#ACTION_SEND} +и добавьте дополнительные данные, указывающие на контент, общий доступ к которому следует предоставить. Когда с помощью этого объекта Intent вы вызываете +{@link android.content.Context#startActivity startActivity()}, пользователь сможет +выбрать приложение, посредством которого к контенту будет предоставлен общий доступ.</p> + +<p class="caution"><strong>Внимание!</strong> Возможна ситуация, когда на устройстве пользователя не будет<em>никакого</em> +приложения, которое может откликнуться на неявный объект Intent, отправленный вами методу {@link android.content.Context#startActivity +startActivity()}. В этом случае вызов закончится неудачей, а работа приложения аварийно завершится. Чтобы проверить, +будет получен ли операцией объект Intent, вызовите метод {@link android.content.Intent#resolveActivity +resolveActivity()} для своего объекта {@link android.content.Intent}. Если результатом будет значение, отличное от null, +значит, имеется хотя бы одно приложение, которое способно откликнуться на объект Intent и можно вызывать +{@link android.content.Context#startActivity startActivity()}. Если же результатом будет значение null, +объект Intent не следует использовать и по возможности следует отключить функцию, которая выдает +этот объект Intent.</p> + + +<pre> +// Create the text message with a string +Intent sendIntent = new Intent(); +sendIntent.setAction(Intent.ACTION_SEND); +sendIntent.putExtra(Intent.EXTRA_TEXT, textMessage); +sendIntent.setType("text/plain"); + +// Verify that the intent will resolve to an activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(sendIntent); +} +</pre> + +<p class="note"><strong>Примечание.</strong> В этом случае URI не используется, а вместо этого следует объявить +тип данных объекта Intent, чтобы указать контент, содержащийся в дополнительных данных.</p> + + +<p>При вызове метода {@link android.content.Context#startActivity startActivity()} система +анализирует все установленные приложения, чтобы определить, какие из них могут откликнуться на объект Intent этого вида (объект +Intent с действием {@link android.content.Intent#ACTION_SEND} и данными "text/plain" +). Если имеется только одно подходящее приложение, оно будет сразу же открыто и получит данный объект +Intent. Если объект Intent принимают несколько операций, система +отображает диалоговое окно, в котором пользователь может выбрать приложение для выполнения данного действия.</p> + + +<div class="figure" style="width:200px"> + <img src="{@docRoot}images/training/basics/intent-chooser.png" alt=""> + <p class="img-caption"><strong>Рисунок 2.</strong> Диалоговое окно выбора.</p> +</div> + +<h3 id="ForceChooser">Принудительное использование блока выбора приложения</h3> + +<p>При наличии нескольких приложений, откликающихся на ваш неявный объект Intent, +пользователь может выбрать требуемое приложение и указать, что оно будет по умолчанию выполнять это +действие. Это удобно в случае действия, для выполнения которого +пользователь обычно хочет всегда использовать одно и то же приложение, например, при открытии веб-страницы (пользователи +обычно используют один и тот же браузер).</p> + +<p>Однако если на объект Intent могут откликнуться несколько приложений, возможно, пользователь предпочтет каждый раз использовать другое +приложение, поэтому следует явно выводить диалоговое окно выбора. В диалоговом окне +выбора приложения пользователю предлагается при каждом запуске выбирать, какое приложение использовать для действия (пользователь не может выбрать приложение, +используемое по умолчанию). Например, когда ваше приложение выполняет операцию "share" (поделиться) с помощью действия {@link +android.content.Intent#ACTION_SEND}, пользователи могут, в зависимости от ситуации, предпочесть каждый раз делать это с помощью разных приложений +, поэтому следует всегда использовать диалоговое окно выбора, как показано на рисунке 2.</p> + + + + +<p>Чтобы вывести на экран блок выбора приложения, создайте {@link android.content.Intent} с помощью {@link +android.content.Intent#createChooser createChooser()} и передайте его {@link +android.app.Activity#startActivity startActivity()}. Например:</p> + +<pre> +Intent sendIntent = 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 the chooser dialog +Intent chooser = Intent.createChooser(sendIntent, title); + +// Verify the original intent will resolve to at least one activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(chooser); +} +</pre> + +<p>В результате на экран будет выведено диалоговое окно со списком приложений, которые могут отреагировать на объект Intent, переданный методу {@link +android.content.Intent#createChooser createChooser()} и используют указанный текст в качестве +заголовка диалога.</p> + + + + + + + + + +<h2 id="Receiving">Получение неявного объекта Intent</h2> + +<p>Чтобы указать, какие неявные объекты Intent может принимать ваше приложение, объявите один или несколько фильтров Intent для +каждого компонента приложения с помощью элемента <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> +в <a href="{@docRoot}guide/topics/manifest/manifest-intro.html">файле манифеста</a>. +Каждый фильтр Intent указывает тип объектов Intent, которые принимает компонент на основании действия, +данных и категории, заданных в объекте Intent. Система передаст неявный объект Intent вашему приложению, только если +он может пройти через один из ваших фильтров Intent.</p> + +<p class="note"><strong>Примечание.</strong> Явный объект Intent всегда доставляется его целевому компоненту, +без учета любых фильтров Intent, объявленных компонентом.</p> + +<p>Компонент приложения должен объявлять отдельные фильтры для каждой уникальной работы, которую он может выполнить. +Например, у операции из приложения для работы с галереей изображений может быть два фильтра: один фильтр +для просмотра изображения, и второй для его редактирования. Когда операция запускается, +она анализирует объект {@link android.content.Intent} и выбирает режим своей работы на основании информации, +приведенной в {@link android.content.Intent} (например, показывать элементы управления редактора или нет).</p> + +<p>Каждый фильтр Intent определяется элементом <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> +в файле манифеста приложения, указанном в объявлении соответствующего компонента приложения (например, +в элементе <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>). + Внутри элемента <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a>, +можно указать тип объектов Intent, которые будут приниматься, с помощью одного или нескольких +из следующих трех элементов:</p> + +<dl> +<dt><a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a></dt> + <dd>Объявляет принимаемое действие, заданное в объекте Intent, в атрибуте {@code name}. Значение + должно быть текстовой строкой действия, а не константой класса.</dd> +<dt><a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a></dt> + <dd>Объявляет тип принимаемых данных, для чего используется один или несколько атрибутов, указывающих различные + составные части URI данных (<code>scheme</code>, <code>host</code>, <code>port</code>, + <code>path</code> и т. д.) и тип MIME.</dd> +<dt><a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a></dt> + <dd>Объявляет принимаемую категорию, заданную в объекте Intent, в атрибуте {@code name}. Значение + должно быть текстовой строкой действия, а не константой класса. + + <p class="note"><strong>Примечание.</strong> Для получения неявных объектов Intent + <strong>необходимо включить</strong> категорию +{@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 не будут переданы в +вашу операцию.</p> + </dd> +</dl> + +<p>В следующем примере объявлена операция с фильтром Intent, определяющим получение объекта Intent +{@link android.content.Intent#ACTION_SEND}, когда данные относятся к типу text:</p> + +<pre> +<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"/> + </intent-filter> +</activity> +</pre> + +<p>Можно создавать фильтры, в которых будет несколько экземпляров +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code <action>}</a>, +<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a> или +<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code <category>}</a>. +В этом случае просто нужно убедиться в том, что компонент может справиться с любыми сочетаниями +этих элементов фильтра.</p> + +<p>Когда требуется обрабатывать объекты Intent нескольких видов, но только в определенных сочетаниях +действия, типа данных и категории, необходимо создать несколько фильтров Intent.</p> + + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Ограничение доступа к компонентам</h2> +<p>Использование фильтра Intent не является безопасным способом предотвращения запуска ваших компонентов +другими приложениями. Несмотря на то, что после применения фильтров Intent компонент будет реагировать только +на неявные объекты Intent определенного вида, другое приложение теоретически может запустить компонент вашего приложения +с помощью явного объекта Intent, если разработчик определит имена ваших компонентов. +Если важно, чтобы <em>только ваше собственное приложение</em> могло запускать один из ваших компонентов, +задайте для атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#exported">{@code +exported}</a> этого компонента значение {@code "false"}. +</p> +</div> +</div> + +<p>Неявный объект Intent проверяется фильтром путем сравнения объекта Intent с каждым из +этих трех элементов. Чтобы объект Intent был доставлен компоненту, он должен пройти все три теста. +Если он не будет соответствовать хотя бы одному из них, система Android не доставит этот объект Intent +компоненту. Однако, поскольку у компонента может быть несколько фильтров Intent, объект Intent, который +не проходит через один из фильтров компонента, может пройти через другой фильтр. +Подробные сведения о том, каким образом система принимает решения по поводу объектов Intent, см. в приведенном далее разделе + <a href="#Resolution">Разрешение объектов Intent</a>.</p> + +<p class="caution"><strong>Внимание!</strong> Чтобы случайно не запустить +{@link android.app.Service} другого приложения, всегда используйте явные объекты Intent для запуска собственных служб и не +объявляйте для них фильтры Intent.</p> + +<p class="note"><strong>Примечание.</strong> +Фильтры Intent необходимо объявлять в файле манифеста для всех операций. +Фильтры для приемников широковещательных сообщений можно регистрировать динамически путем вызова +{@link android.content.Context#registerReceiver(BroadcastReceiver, IntentFilter, String, +Handler) registerReceiver()}. После этого регистрацию приемника широковещательных сообщений можно отменить с помощью {@link +android.content.Context#unregisterReceiver unregisterReceiver()}. В результате ваше приложение +сможет воспринимать определенные объявления только в течение указанного периода времени в процессе работы +приложения.</p> + + + + + + + +<h3 id="ExampleFilters">Примеры фильтров</h3> + +<p>Чтобы лучше понять различные режимы работы фильтров Intent, рассмотрите следующий фрагмент +из файла манифеста приложения для работы с социальными сетями.</p> + +<pre> +<activity android:name="MainActivity"> + <!-- This activity is the main entry, should appear in app launcher --> + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> +</activity> + +<activity android:name="ShareActivity"> + <!-- This activity handles "SEND" actions with text data --> + <intent-filter> + <action android:name="android.intent.action.SEND"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="text/plain"/> + </intent-filter> + <!-- This activity also handles "SEND" and "SEND_MULTIPLE" with media data --> + <intent-filter> + <action android:name="android.intent.action.SEND"/> + <action android:name="android.intent.action.SEND_MULTIPLE"/> + <category android:name="android.intent.category.DEFAULT"/> + <data android:mimeType="application/vnd.google.panorama360+jpg"/> + <data android:mimeType="image/*"/> + <data android:mimeType="video/*"/> + </intent-filter> +</activity> +</pre> + +<p>Первая операция ({@code MainActivity}) является основной точкой входа приложения — это операция, которая +открывается, когда пользователь запускает приложение нажатием на его значок:</p> +<ul> + <li>Действие {@link android.content.Intent#ACTION_MAIN} + указывает на то, что это основная точка входа, и не ожидает никаких данных объекта Intent.</li> + <li>Категория {@link android.content.Intent#CATEGORY_LAUNCHER} указывает, что значок этой операции + следует поместить в средство запуска приложений системы. Если элемент <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> + не содержит указаний на конкретный значок с помощью {@code icon}, то система воспользуется значком из элемента <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> +.</li> +</ul> +<p>Чтобы операция отображалась в средстве запуска приложений системы, два этих элемента необходимо связать вместе.</p> + +<p>Вторая операция ({@code ShareActivity}) предназначена для упрощения обмена текстовым и мультимедийным +контентом. Несмотря на то, что пользователи могут входить в эту операцию, выбрав ее из {@code MainActivity}, +они также могут входить в {@code ShareActivity} напрямую из другого приложения, которое выдает неявный объект +Intent, соответствующий одному из двух фильтров Intent.</p> + +<p class="note"><strong>Примечание.</strong> Тип MIME +(<a href="https://developers.google.com/panorama/android/">{@code +application/vnd.google.panorama360+jpg}</a>) является особым типом данных, указывающим на +панорамные фотографии, с которыми можно работать с помощью API-интерфейсов <a href="{@docRoot}reference/com/google/android/gms/panorama/package-summary.html">Google +panorama</a>.</p> + + + + + + + + + + + + + +<h2 id="PendingIntent">Использование ожидающего объекта Intent</h2> + +<p>Объект {@link android.app.PendingIntent} является оболочкой, в которую заключается объект {@link +android.content.Intent}. Объект {@link android.app.PendingIntent} +в основном предназначен для того, чтобы предоставлять разрешение внешним приложениям +на использование содержащегося в нем объекта {@link android.content.Intent}, как если бы он исполнялся из +процесса вашего собственного приложения.</p> + +<p>Основные варианты использования ожидающего объекта Intent:</p> +<ul> + <li>Объявление объекта Intent, который должен будет исполняться, когда пользователь выполняет действие с вашим <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">уведомлением</a> + ({@link android.app.NotificationManager} системы Android + исполняет {@link android.content.Intent}). + <li>Объявление объекта Intent, который должен будет исполняться, когда пользователь выполняет действие с вашим +<a href="{@docRoot}guide/topics/appwidgets/index.html">виджетом приложения</a> + (приложение главного экрана исполняет {@link android.content.Intent}). + <li>Объявление объекта Intent, который должен будет исполняться в указанное время в будущем ({@link android.app.AlarmManager} системы +Android исполняет {@link android.content.Intent}). +</ul> + +<p>Поскольку каждый объект {@link android.content.Intent} предназначен для обработки компонентом приложения, +который относится к определенному типу ({@link android.app.Activity}, {@link android.app.Service} или + {@link android.content.BroadcastReceiver}), объект {@link android.app.PendingIntent} также следует создавать +с учетом этого обстоятельства. При использовании ожидающего объекта Intent ваше приложение не будет +исполнять объект Intent вызовом, например, {@link android.content.Context#startActivity +startActivity()}. Вместо этого вам необходимо будет объявить требуемый тип компонента при создании +{@link android.app.PendingIntent} путем вызова соответствующего метода-создателя:</p> + +<ul> + <li>Метод {@link android.app.PendingIntent#getActivity PendingIntent.getActivity()} для + {@link android.content.Intent}, который запускает {@link android.app.Activity}.</li> + <li>Метод {@link android.app.PendingIntent#getService PendingIntent.getService()} для + {@link android.content.Intent}, который запускает {@link android.app.Service}.</li> + <li>Метод {@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()} для + {@link android.content.Intent}, который запускает {@link android.content.BroadcastReceiver}.</li> +</ul> + +<p>Если только ваше приложение не <em>принимает</em> ожидающие объекты Intent от других приложений, +указанные выше методы создания {@link android.app.PendingIntent} являются единственными методами +{@link android.app.PendingIntent}, которые вам когда-либо понадобятся.</p> + +<p>Каждый метод принимает текущий {@link android.content.Context} приложения, объект +{@link android.content.Intent}, который требуется поместить в оболочку, и один или несколько флагов, указывающих, +каким образом следует использовать объект Intent (например, можно ли использовать объект Intent неоднократно).</p> + +<p>Подробные сведения об использовании ожидающих объектов Intent приведены в документации по каждому +из соответствующих вариантов использования, например, в руководствах, посвященным API-интерфейсам: <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Уведомления</a> +и <a href="{@docRoot}guide/topics/appwidgets/index.html">Виджеты приложений</a>.</p> + + + + + + + +<h2 id="Resolution">Разрешение объектов Intent</h2> + + +<p>Когда система получает неявный объект Intent для запуска операции, она выполняет поиск +наиболее подходящей операции путем сравнения объекта Intent с фильтрами Intent по трем критериям:</p> + +<ul> + <li>действие объекта Intent; + <li>данные объекта Intent (тип URI и данных); + <li>категория объекта Intent. +</ul> + +<p>В следующих разделах описывается, каким образом объекты Intent сопоставляются с соответствующими компонентами, +а именно, как должен быть фильтр Intent объявлен в файле манифеста приложения.</p> + + +<h3 id="ActionTest">Тестирование действия</h3> + +<p>Для указания принимаемых действий объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +<a href="{@docRoot}guide/topics/manifest/action-element.html">{@code +action>}</a>. Например:</p> + +<pre> +<intent-filter> + <action android:name="android.intent.action.EDIT" /> + <action android:name="android.intent.action.VIEW" /> + ... +</intent-filter> +</pre> + +<p>Чтобы пройти через этот фильтр, действие, указанное в объекте {@link android.content.Intent}, + должно соответствовать одному или нескольким действиям, перечисленным в фильтре.</p> + +<p>Если в фильтре не перечислены какие-либо действия, объекту +Intent будет нечему соответствовать, поэтому все объекты Intent не пройдут этот тест. Однако, если в объекте {@link android.content.Intent} +не указано действие, он пройдет тест (если в фильтре +содержится хотя бы одно действие).</p> + + + +<h3 id="CategoryTest">Тестирование категории</h3> + +<p>Для указания принимаемых категорий объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +<a href="{@docRoot}guide/topics/manifest/category-element.html">{@code +<category>}</a>. Например:</p> + +<pre> +<intent-filter> + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + ... +</intent-filter> +</pre> + +<p>Чтобы объект Intent прошел тестирование категории, все категории, приведенные в объекте {@link android.content.Intent}, +должны соответствовать категории из фильтра. Обратное не требуется — фильтр Intent может +объявлять и другие категории, которых нет в объекте {@link android.content.Intent}, объект +{@link android.content.Intent} при этом все равно пройдет тест. Поэтому объект Intent без категорий +всегда пройдет этот тест, независимо от того, какие категории объявлены в фильтре.</p> + +<p class="note"><strong>Примечание.</strong> +Система Android автоматически применяет категорию {@link android.content.Intent#CATEGORY_DEFAULT} +ко всем неявным объектам Intent, которые передаются в {@link +android.content.Context#startActivity startActivity()} и {@link +android.app.Activity#startActivityForResult startActivityForResult()}. +Поэтому, если вы хотите, чтобы ваша операция принимала неявные объекты Intent, в ее фильтрах Intent +должна быть указана категория для {@code "android.intent.category.DEFAULT"} (как +показано в предыдущем примере {@code <intent-filter>}).</p> + + + +<h3 id="DataTest">Тестирование данных</h3> + +<p>Для указания принимаемых данных объекта Intent фильтр Intent может объявлять любое (в том числе нулевое) число элементов +<a href="{@docRoot}guide/topics/manifest/data-element.html">{@code +<data>}</a>. Например:</p> + +<pre> +<intent-filter> + <data android:mimeType="video/mpeg" android:scheme="http" ... /> + <data android:mimeType="audio/mpeg" android:scheme="http" ... /> + ... +</intent-filter> +</pre> + +<p>Каждый элемент <code><a href="{@docRoot}guide/topics/manifest/data-element.html"><data></a></code> +может конкретизировать структуру URI и тип данных (тип мультимедиа MIME). Имеются отдельные +атрибуты — {@code scheme}, {@code host}, {@code port} +и {@code path} — для каждой составной части URI: +</p> + +<p style="margin-left: 2em">{@code <scheme>://<host>:<port>/<path>}</p> + +<p> +Например: +</p> + +<p style="margin-left: 2em">{@code content://com.example.project:200/folder/subfolder/etc}</p> + +<p>В этом URI схема имеет вид {@code content}, узел ― {@code com.example.project}, +порт ― {@code 200}, а путь ― {@code folder/subfolder/etc}. +</p> + +<p>Каждый из этих атрибутов является необязательным в элементе <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>, +однако имеются линейные зависимости:</p> +<ul> + <li>Если схема не указана, узел игнорируется.</li> + <li>Если узел не указан, порт игнорируется.</li> + <li>Если не указана ни схема, ни узел, путь игнорируется.</li> +</ul> + +<p>Когда URI, указанный в объекте Intent, сравнивается с URI из фильтра, +сравнение выполняется только с теми составными частями URI, которые приведены в фильтре. Например:</p> +<ul> + <li>Если в фильтре указана только схема, то все URI с этой схемой будет соответствовать +фильтру.</li> + <li>Если в фильтре указаны схема и полномочия, но отсутствует путь, все URI +с такими же схемой и полномочиями пройдут фильтр, а их пути учитываться не будут.</li> + <li>Если в фильтре указаны схема, полномочия и путь, то только URI с такими же схемой, +полномочиями и путем пройдут фильтр.</li> +</ul> + +<p class="note"><strong>Примечание.</strong> Путь может быть +указан с подстановочным символом (*), чтобы потребовалось только частичное соответствие имени пути.</p> + +<p>При выполнении тестирования данных сравнивается и URI, и тип MIME, указанные в объекте Intent, с URI +и типом MIME из фильтра. Действуют следующие правила: +</p> + +<ol type="a"> +<li>Объект Intent, который не содержит ни URI, ни тип MIME, пройдет этот +тест, только если в фильтре не указано никаких URI или типов MIME.</li> + +<li>Объект Intent, в котором имеется URI, но отсутствует тип MIME (ни явный, ни тот, который можно вывести из +URI), пройдет этот тест, только если URI соответствует формату URI из фильтра +, а в фильтре также не указан тип MIME.</li> + +<li>Объект Intent, в котором имеется тип MIME, но отсутствует URI, пройдет этот тест, +только если в фильтре указан тот же тип MIME и не указан формат URI.</li> + +<li>Объект Intent, в котором имеется и URI, и тип MIME (явный или тот, который можно вывести из +URI), пройдет только часть этого теста, проверяющую тип MIME, +в том случае, если этот тип совпадает с типом, приведенным в фильтре. Он пройдет часть этого теста, которая проверяет URI, +либо если его URI совпадает с URI из фильтра, либо если этот объект содержит URI {@code content:} +или {@code file:}, а в фильтре URI не указан. Другими словами, +предполагается, что компонент поддерживает данные {@code content:} и {@code file:}, если в его +фильтре указан <em>только</em> тип MIME.</p></li> +</ol> + +<p> +Это последнее правило (правило (d)) отражает ожидание того, +что компоненты будут в состоянии получать локальные данные из файла или от поставщика контента. +Поэтому их фильтры могут содержать только тип данных, а явно +указывать схемы {@code content:} и {@code file:} не требуется. +Это типичный случай. Например, такой элемент <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>, как +приведенный далее, сообщает системе Android, что компонент может получать данные изображений от поставщика +контента и выводить их на экран: +</p> + +<pre> +<intent-filter> + <data android:mimeType="image/*" /> + ... +</intent-filter></pre> + +<p> +Поскольку имеющиеся данные преимущественно распространяются поставщиками контента, фильтры, в которых +указан тип данных и нет URI, вероятно, являются самыми распространенными. +</p> + +<p> +Другой стандартной конфигурацией являются фильтры со схемой и типом данных. Например, +такой элемент <a href="{@docRoot}guide/topics/manifest/data-element.html">{@code <data>}</a>, +как приведенный далее, сообщает системе Android, что +компонент может получать видеоданные из сети для выполнения действия: +</p> + +<pre> +<intent-filter> + <data android:scheme="http" android:type="video/*" /> + ... +</intent-filter></pre> + + + +<h3 id="imatch">Сопоставление объектов Intent</h3> + +<p>Объекты Intent сопоставляются с фильтрами Intent не только для определения целевого +компонента, который требуется активировать, но также для выявления определенных сведений о наборе +компонентов, имеющихся на устройстве. Например, приложение главного экрана заполняет средство запуска приложений +путем поиска всех операций с фильтрами Intent, в которых указано действие +{@link android.content.Intent#ACTION_MAIN} и категория +{@link android.content.Intent#CATEGORY_LAUNCHER}.</p> + +<p>Ваше приложение может использовать сопоставление объектов Intent таким же образом. +В {@link android.content.pm.PackageManager} имеется набор методов {@code query...()}, +которые возвращают все компоненты, способные принять определенный объект, а также +схожий набор методов {@code resolve...()}, которые определяют наиболее подходящий +компонент, способный реагировать на объект Intent. Например, метод +{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} возвращает передаваемый как аргумент список всех операций, которые могут выполнить +объект Intent, а метод {@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} возвращает такой же список служб. +Ни тот, ни другой метод не активирует компоненты; они просто перечисляют те из них, которые +могут откликнуться. Имеется схожий метод для приемников широковещательных сообщений ( +{@link android.content.pm.PackageManager#queryBroadcastReceivers +). +</p> + + + + diff --git a/docs/html-intl/intl/ru/guide/components/loaders.jd b/docs/html-intl/intl/ru/guide/components/loaders.jd new file mode 100644 index 000000000000..eea72a2296ee --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=Загрузчики +parent.title=Операции +parent.link=activities.html +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> + <ol> + <li><a href="#summary">Сводная информация об API-интерфейсе загрузчика</a></li> + <li><a href="#app">Использование загрузчиков в приложении</a> + <ol> + <li><a href="#requirements"></a></li> + <li><a href="#starting">Запуск загрузчика</a></li> + <li><a href="#restarting">Перезапуск загрузчика</a></li> + <li><a href="#callback">Использование обратных вызовов класса LoaderManager</a></li> + </ol> + </li> + <li><a href="#example">Пример</a> + <ol> + <li><a href="#more_examples">Другие примеры</a></li> + </ol> + </li> + </ol> + + <h2>Основные классы</h2> + <ol> + <li>{@link android.app.LoaderManager}</li> + <li>{@link android.content.Loader}</li> + + </ol> + + <h2>Образцы кода по теме</h2> + <ol> + <li> <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html"> +LoaderCursor</a></li> + <li> <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> +LoaderThrottle</a></li> + </ol> + </div> +</div> + +<p>Загрузчики, которые появились в Android 3.0, упрощают асинхронную загрузку данных +в операцию или фрагмент. Загрузчики имеют следующие свойства:</p> + <ul> + <li>Они имеются для любых операций {@link android.app.Activity} и фрагментов {@link +android.app.Fragment}.</li> + <li>Они обеспечивают асинхронную загрузку данных.</li> + <li>Они отслеживают источник своих данных и выдают новые результаты при +изменении контента.</li> + <li>Они автоматически переподключаются к последнему курсору загрузчика при +воссоздании после изменения конфигурации. Таким образом, им не требуется повторно запрашивать свои +данные.</li> + </ul> + +<h2 id="summary">Сводная информация об API-интерфейсе загрузчика</h2> + +<p>Имеется несколько классов и интерфейсов, которые могут использовать +загрузчики в приложении. Они приведены в этой таблице:</p> + +<table> + <tr> + <th>Класс/интерфейс</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.app.LoaderManager}</td> + <td>Абстрактный класс, связываемый с {@link android.app.Activity} или +{@link android.app.Fragment} для управления одним или несколькими интерфейсами {@link +android.content.Loader}. Это позволяет приложению управлять +длительно выполняющимися операциями вместе с жизненным циклом {@link android.app.Activity} +или {@link android.app.Fragment}; чаще всего этот класс используется с +{@link android.content.CursorLoader}, однако приложения могут писать +свои собственные загрузчики для работы с другими типами данных. + <br /> + <br /> + Имеется только один класс {@link android.app.LoaderManager} на операцию или фрагмент. Однако у класса {@link android.app.LoaderManager} может быть +несколько загрузчиков.</td> + </tr> + <tr> + <td>{@link android.app.LoaderManager.LoaderCallbacks}</td> + <td>Интерфейс обратного вызова, обеспечивающий взаимодействие клиента с {@link +android.app.LoaderManager}. Например, с помощью метода обратного вызова {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +создается новый загрузчик.</td> + </tr> + <tr> + <td>{@link android.content.Loader}</td> + <td>Абстрактный класс, который выполняет асинхронную загрузку данных. Это +базовый класс для загрузчика. Обычно используется {@link +android.content.CursorLoader}, но можно реализовать и собственный подкласс. Когда +загрузчики активны, они должны отслеживать источник своих данных и выдавать новые +результаты при изменении контента. </td> + </tr> + <tr> + <td>{@link android.content.AsyncTaskLoader}</td> + <td>Абстрактный загрузчик, который предоставляет {@link android.os.AsyncTask} для выполнения работы.</td> + </tr> + <tr> + <td>{@link android.content.CursorLoader}</td> + <td>Подкласс класса {@link android.content.AsyncTaskLoader}, который запрашивает +{@link android.content.ContentResolver} и возвращает {@link +android.database.Cursor}. Этот класс реализует протокол {@link +android.content.Loader} стандартным способом для выполнения запросов к курсорам. +Он строится на {@link android.content.AsyncTaskLoader} для выполнения запроса к курсору +в фоновом потоке, чтобы не блокировать пользовательский интерфейс приложения. Использование +этого загрузчика — это лучший способ асинхронной загрузки данных из {@link +android.content.ContentProvider} вместо выполнения управляемого запроса через +платформу или API-интерфейсы операции.</td> + </tr> +</table> + +<p>Приведенные в этой таблице классы и интерфейсы являются наиболее важными компонентами, +с помощью которых в приложении реализуется загрузчик. При создании каждого загрузчика +не нужно использовать все эти компоненты, однако всегда следует указывать ссылку на {@link +android.app.LoaderManager} для инициализации загрузчика и использовать реализацию +класса {@link android.content.Loader}, например {@link +android.content.CursorLoader}. В следующих разделах рассказывается, как использовать эти +классы и интерфейсы в приложении.</p> + +<h2 id ="app">Использование загрузчиков в приложении</h2> +<p>В этом разделе описывается использование загрузчиков в приложении для Android. В +приложениях, использующих загрузчики, обычно имеются следующие элементы:</p> +<ul> + <li>{@link android.app.Activity} или {@link android.app.Fragment};</li> + <li>экземпляр {@link android.app.LoaderManager};</li> + <li>{@link android.content.CursorLoader} для загрузки данных, выдаваемых {@link +android.content.ContentProvider}. Вы также можете реализовать собственный подкласс +класса {@link android.content.Loader} или {@link android.content.AsyncTaskLoader} для +загрузки данных из другого источника;</li> + <li>реализация для {@link android.app.LoaderManager.LoaderCallbacks}. +Именно здесь создаются новые загрузчики и ведется управление ссылками на существующие +загрузчики;</li> +<li>способ отображения данных загрузчика, например {@link +android.widget.SimpleCursorAdapter};</li> + <li>источник данных, например {@link android.content.ContentProvider}, когда используется +{@link android.content.CursorLoader}.</li> +</ul> +<h3 id="starting">Запуск загрузчика</h3> + +<p>{@link android.app.LoaderManager} управляет одним или несколькими экземплярами {@link +android.content.Loader} в {@link android.app.Activity} или +{@link android.app.Fragment}. Имеется только один {@link +android.app.LoaderManager} на каждую операцию или каждый фрагмент.</p> + +<p>{@link android.content.Loader} обычно +инициализируется в методе {@link +android.app.Activity#onCreate onCreate()} операции или в методе фрагмента +{@link android.app.Fragment#onActivityCreated onActivityCreated()}. Делается +это следующим образом:</p> + +<pre>// Prepare the loader. Either re-connect with an existing one, +// or start a new one. +getLoaderManager().initLoader(0, null, this);</pre> + +<p>Метод {@link android.app.LoaderManager#initLoader initLoader()} принимает +следующие параметры:</p> +<ul> + <li>уникальный идентификатор, обозначающий загрузчик. В данном примере идентификатором является 0;</li> +<li>необязательные аргументы, которые передаются загрузчику при +построении (в данном примере это <code>null</code>);</li> + +<li>реализация {@link android.app.LoaderManager.LoaderCallbacks}, которая +вызывает класс {@link android.app.LoaderManager} для выдачи событий загрузчика. В данном +примере локальный класс реализует интерфейс {@link +android.app.LoaderManager.LoaderCallbacks}, поэтому он передает ссылку +самому себе: {@code this}.</li> +</ul> +<p>Вызов {@link android.app.LoaderManager#initLoader initLoader()} обеспечивает инициализацию +загрузчика. Возможен один из двух результатов:</p> +<ul> + <li>Если загрузчик, указанный с помощью идентификатора, уже существует, будет повторно использован загрузчик, созданный +последним.</li> + <li>Если загрузчик, указанный с помощью идентификатора, <em>не</em> существует, +{@link android.app.LoaderManager#initLoader initLoader()} вызывает метод +{@link android.app.LoaderManager.LoaderCallbacks} из {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. +Именно здесь реализуется код для создания экземпляра и возврата нового загрузчика. +Более подробные сведения см. в разделе <a href="#onCreateLoader">onCreateLoader</a>.</li> +</ul> +<p>В любом случае данная реализация {@link android.app.LoaderManager.LoaderCallbacks} +связывается с загрузчиком и будет вызываться +при изменении состояния загрузчика. Если в момент этого вызова вызывающий компонент находится в +запущенном состоянии, это означает, что запрошенный загрузчик уже существует и сформировал свои +данные. В этом случае система сразу же вызовет {@link +android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} + (во время{@link android.app.LoaderManager#initLoader initLoader()}), +будьте готовы к этому. Более подробные сведения об этом обратном вызове см. в разделе <a href="#onLoadFinished"> +onLoadFinished</a>.</p> + +<p>Обратите внимание, что метод {@link android.app.LoaderManager#initLoader initLoader()} +возвращает создаваемый класс {@link android.content.Loader}, но записывать +ссылку на него не требуется. Класс {@link android.app.LoaderManager} управляет +жизненным циклом загрузчика автоматически. Класс {@link android.app.LoaderManager} +начинает загрузку и заканчивает ее при необходимости, а также поддерживает состояние загрузчика +и связанного с ним контента. А это подразумевает, что вы будете редко взаимодействовать с загрузчиками +напрямую (однако пример использования методов загрузчика для тонкой настройки его +поведения см. в образце кода <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a>). +Для вмешательства в процесс загрузки при возникновении определенных событий обычно используются методы {@link +android.app.LoaderManager.LoaderCallbacks} +. Более подробные сведения об этом см. в разделе <a href="#callback">Использование обратных вызовов LoaderManager</a>.</p> + +<h3 id="restarting">Перезапуск загрузчика</h3> + +<p>При использовании метода {@link android.app.LoaderManager#initLoader initLoader()}, как +показано выше, он задействует существующий загрузчик с указанным идентификатором — в случае его наличия. +Если такого загрузчика нет, метод его создаст. Однако иногда старые данные нужно отбросить +и начать все заново.</p> + +<p>Для удаления старых данных используется метод {@link +android.app.LoaderManager#restartLoader restartLoader()}. Например, эта +реализация метода{@link android.widget.SearchView.OnQueryTextListener} перезапускает +загрузчик, когда изменяется запрос пользователя. Загрузчик необходимо перезагрузить, + с тем чтобы он мог использовать измененный фильтр поиска для выполнения нового запроса:</p> + +<pre> +public boolean onQueryTextChanged(String newText) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + getLoaderManager().restartLoader(0, null, this); + return true; +}</pre> + +<h3 id="callback">Использование обратных вызовов класса LoaderManager</h3> + +<p>{@link android.app.LoaderManager.LoaderCallbacks} представляет собой интерфейс обратного вызова, +который позволяет клиенту взаимодействовать с классом {@link android.app.LoaderManager}. </p> +<p>Ожидается, что загрузчики, в частности {@link android.content.CursorLoader}, будут сохранять +свои данные после их остановки. Это позволяет приложениям сохранять свои +данные в методах {@link android.app.Activity#onStop +onStop()} и {@link android.app.Activity#onStart onStart()} операции или фрагмента, с тем чтобы, +когда пользователь вернется в приложение, ему не пришлось ждать, пока данные +загрузятся заново. Методы {@link android.app.LoaderManager.LoaderCallbacks} используются, +чтобы узнать, когда требуется создать новый загрузчик, а также для того, чтобы указать приложению, когда +пришло время перестать использовать данные загрузчика.</p> + +<p>Интерфейс {@link android.app.LoaderManager.LoaderCallbacks} использует следующие +методы:</p> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} — +создает экземпляр и возвращает новый класс {@link android.content.Loader} для данного идентификатора. +</li></ul> +<ul> + <li> {@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished onLoadFinished()} +— вызывается, когда созданный ранее загрузчик завершил загрузку. +</li></ul> +<ul> + <li>{@link android.app.LoaderManager.LoaderCallbacks#onLoaderReset onLoaderReset()} +— вызывается, когда состояние созданного ранее загрузчика сбрасывается, в результате чего его данные +теряются. +</li> +</ul> +<p>Более подробно эти методы описаны в разделах ниже.</p> + +<h4 id ="onCreateLoader">onCreateLoader</h4> + +<p>При попытке доступа к загрузчику (например, посредством метода {@link +android.app.LoaderManager#initLoader initLoader()}), он проверяет, существует ли +загрузчик, указанный с помощью идентификатора. Если он не существует, он вызывает метод {@link +android.app.LoaderManager.LoaderCallbacks} {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}. Именно +здесь и создается новый загрузчик. Обычно это будет класс {@link +android.content.CursorLoader}, однако можно реализовать и собственный подкласс класса {@link +android.content.Loader}. </p> + +<p>В этом примере метод обратного вызова {@link +android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()} +создает класс {@link android.content.CursorLoader}. Вы должны построить +класс {@link android.content.CursorLoader} с помощью его метода конструктора, для чего +требуется полный набор информации, которая нужна для выполнения запроса к {@link +android.content.ContentProvider}. В частности, требуется:</p> +<ul> + <li><em>uri</em> — URI контента, который необходимо получить; </li> + <li><em>projection</em> — список столбцов, которые будут возвращены. При передаче +<code>null</code> будут возвращены все столбцы, а это неэффективно; </li> + <li><em>selection</em> — фильтр, объявляющий, какие строки возвращать, +отформатированный в виде предложения SQL WHERE (за исключением самого WHERE). При передаче +<code>null</code> будут возвращены все строки для данного URI; </li> + <li><em>selectionArgs</em> — в выборку можно включить символы "?", которые будут +заменены значениями из <em>selectionArgs</em> в порядке следования в +выборке. Значения будут привязаны как строки; </li> + <li><em>sortOrder</em> — порядок расположения строк, отформатированный в виде предложения SQL +ORDER BY (за исключением самого ORDER BY). При передаче <code>null</code> будет +использоваться стандартный порядок сортировки, поэтому, список, возможно, будет неотсортирован.</li> +</ul> +<p>Например:</p> +<pre> + // If non-null, this is the current filter the user has provided. +String mCurFilter; +... +public Loader<Cursor> onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. + Uri baseUri; + if (mCurFilter != null) { + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(mCurFilter)); + } else { + baseUri = Contacts.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + + Contacts.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); +}</pre> +<h4 id="onLoadFinished">onLoadFinished</h4> + +<p>Этот метод вызывается, когда созданный ранее загрузчик завершил загрузку. +Этот метод гарантировано вызывается до высвобождения последних данных, +которые были предоставлены этому загрузчику. К этому моменту необходимо полностью перестать использовать +старые данные (поскольку они скоро будут заменены). Однако этого не следует делать +самостоятельно, поскольку данными владеет загрузчик и он позаботится об этом.</p> + + +<p>Загрузчик высвободит данные, как только узнает, что приложение их больше не +использует. Например, если данными является курсор из {@link +android.content.CursorLoader}, не следует вызывать {@link +android.database.Cursor#close close()} самостоятельно. Если курсор +размещается в {@link android.widget.CursorAdapter}, следует использовать метод {@link +android.widget.SimpleCursorAdapter#swapCursor swapCursor()} с тем, чтобы +старый {@link android.database.Cursor} не закрылся. Например:</p> + +<pre> +// This is the Adapter being used to display the list's data.<br +/>SimpleCursorAdapter mAdapter; +... + +public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); +}</pre> + +<h4 id="onLoaderReset">onLoaderReset</h4> + +<p>Этот метод вызывается, когда состояние созданного ранее загрузчика сбрасывается, в результате чего +его данные теряются. Этот обратный вызов позволяет узнать, когда данные +вот-вот будут высвобождены, с тем чтобы можно было удалить свою ссылку на них. </p> +<p>Данная реализация вызывает +{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()} +со значением <code>null</code>:</p> + +<pre> +// This is the Adapter being used to display the list's data. +SimpleCursorAdapter mAdapter; +... + +public void onLoaderReset(Loader<Cursor> loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); +}</pre> + + +<h2 id="example">Пример</h2> + +<p>В качестве примера далее приведена полная реализация фрагмента {@link +android.app.Fragment}, который отображает {@link android.widget.ListView} с +результатами запроса к поставщику такого контента, как контакты. Для управления запросом к поставщику используется класс {@link +android.content.CursorLoader}.</p> + +<p>Чтобы приложение могло обращаться к контактам пользователя, как показано в этом примере, в его +манифесте должно присутствовать разрешение +{@link android.Manifest.permission#READ_CONTACTS READ_CONTACTS}.</p> + +<pre> +public static class CursorLoaderListFragment extends ListFragment + implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { + + // This is the Adapter being used to display the list's data. + SimpleCursorAdapter mAdapter; + + // If non-null, this is the current filter the user has provided. + String mCurFilter; + + @Override public void onActivityCreated(Bundle savedInstanceState) { + super.onActivityCreated(savedInstanceState); + + // Give some text to display if there is no data. In a real + // application this would come from a resource. + setEmptyText("No phone numbers"); + + // We have a menu item to show in action bar. + setHasOptionsMenu(true); + + // Create an empty adapter we will use to display the loaded data. + mAdapter = new SimpleCursorAdapter(getActivity(), + android.R.layout.simple_list_item_2, null, + new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, + new int[] { android.R.id.text1, android.R.id.text2 }, 0); + setListAdapter(mAdapter); + + // Prepare the loader. Either re-connect with an existing one, + // or start a new one. + getLoaderManager().initLoader(0, null, this); + } + + @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { + // Place an action bar item for searching. + MenuItem item = menu.add("Search"); + item.setIcon(android.R.drawable.ic_menu_search); + item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); + SearchView sv = new SearchView(getActivity()); + sv.setOnQueryTextListener(this); + item.setActionView(sv); + } + + public boolean onQueryTextChange(String newText) { + // Called when the action bar search text has changed. Update + // the search filter, and restart the loader to do a new query + // with this filter. + mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; + getLoaderManager().restartLoader(0, null, this); + return true; + } + + @Override public boolean onQueryTextSubmit(String query) { + // Don't care about this. + return true; + } + + @Override public void onListItemClick(ListView l, View v, int position, long id) { + // Insert desired behavior here. + Log.i("FragmentComplexList", "Item clicked: " + id); + } + + // These are the Contacts rows that we will retrieve. + static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { + Contacts._ID, + Contacts.DISPLAY_NAME, + Contacts.CONTACT_STATUS, + Contacts.CONTACT_PRESENCE, + Contacts.PHOTO_ID, + Contacts.LOOKUP_KEY, + }; + public Loader<Cursor> onCreateLoader(int id, Bundle args) { + // This is called when a new Loader needs to be created. This + // sample only has one Loader, so we don't care about the ID. + // First, pick the base URI to use depending on whether we are + // currently filtering. + Uri baseUri; + if (mCurFilter != null) { + baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, + Uri.encode(mCurFilter)); + } else { + baseUri = Contacts.CONTENT_URI; + } + + // Now create and return a CursorLoader that will take care of + // creating a Cursor for the data being displayed. + String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + + Contacts.DISPLAY_NAME + " != '' ))"; + return new CursorLoader(getActivity(), baseUri, + CONTACTS_SUMMARY_PROJECTION, select, null, + Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); + } + + public void onLoadFinished(Loader<Cursor> loader, Cursor data) { + // Swap the new cursor in. (The framework will take care of closing the + // old cursor once we return.) + mAdapter.swapCursor(data); + } + + public void onLoaderReset(Loader<Cursor> loader) { + // This is called when the last Cursor provided to onLoadFinished() + // above is about to be closed. We need to make sure we are no + // longer using it. + mAdapter.swapCursor(null); + } +}</pre> +<h3 id="more_examples">Другие примеры</h3> + +<p>В <strong>ApiDemos</strong> есть несколько различных примеров, которые +иллюстрируют использование загрузчиков:</p> +<ul> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html"> +LoaderCursor</a> — полная версия +показанного выше фрагмента.</li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html"> LoaderThrottle</a> — пример того, как использовать регулирование +для сокращения числа запросов, выполняемых поставщиком контента при изменении его данных.</li> +</ul> + +<p>Сведения о загрузке и установке образцов кода SDK см. в статье <a href="http://developer.android.com/resources/samples/get.html"> Получение +образцов кода</a>. </p> + diff --git a/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd b/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd new file mode 100644 index 000000000000..fd298e0d3c49 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/processes-and-threads.jd @@ -0,0 +1,411 @@ +page.title=Процессы и потоки +page.tags=жизненный цикл,фон + +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> +<li><a href="#Processes">Процессы</a> + <ol> + <li><a href="#Lifecycle">Жизненный цикл процесса</a></li> + </ol> +</li> +<li><a href="#Threads">Потоки</a> + <ol> + <li><a href="#WorkerThreads">Рабочие потоки</a></li> + <li><a href="#ThreadSafe">Потокобезопасные методы</a></li> + </ol> +</li> +<li><a href="#IPC">Взаимодействие процессов</a></li> +</ol> + +</div> +</div> + +<p>Когда компонент приложения запускается при отсутствии других работающих компонентов +, система Android запускает новый процесс Linux для приложения с одним потоком +выполнения. По умолчанию все компоненты одного приложения работают в одном процессе и потоке +(называется «главным потоком»). Если компонент приложения запускается при наличии процесса +для этого приложения (так как существует другой компонент из приложения), тогда компонент +запускается в этом процессе и использует тот же поток выполнения. Однако можно организовать выполнение +других компонентов приложения в отдельных процессах и создавать дополнительный +поток для любого процесса.</p> + +<p>В этом документе обсуждается работа процессов и потоков в приложении Android.</p> + + +<h2 id="Processes">Процессы</h2> + +<p>По умолчанию все компоненты одного приложения работают в одном процессе, и большинство приложений +не должно менять это поведение. Однако, если необходимо контролировать, к какому процессу принадлежат определенный +компонент, можно сделать это в файле манифеста.</p> + +<p>Запись манифеста для каждого типа элементов компонента —<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a>, <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code +<service>}</a>, <a href="{@docRoot}guide/topics/manifest/receiver-element.html">{@code +<receiver>}</a> и <a href="{@docRoot}guide/topics/manifest/provider-element.html">{@code +<provider>}</a> —поддерживает атрибут {@code android:process}, позволяющий задавать +процесс, в котором следует выполнять этот компонент. Можно установить этот атрибут так, чтобы каждый компонент выполнялся +в собственном процессе, или так, чтобы только некоторые компоненты совместно использовали один процесс. Можно также настроить процесс +{@code android:process} так, чтобы компоненты разных приложений выполнялись в одном +процессе, при условии что приложения совместно используют один идентификатор пользователя Linux и выполняют вход с +одним сертификатом.</p> + +<p>Элемент <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code +<application>}</a> также поддерживает атрибут {@code android:process}, позволяющий задать +значение по умолчанию, которое применяется ко всем компонентам.</p> + +<p>Android может остановить процесс в некоторой точке, когда не хватает памяти и она необходима другим +процессам, которые обслуживают пользователя в данный момент. Работа компонентов +приложения, работающих в этом процессе, последовательно останавливается. Процесс для этих компонентов запускается +повторно, когда для них появляется работа.</p> + +<p>Принимая решение о прерывании процессов, система Android взвешивает их относительную важность +для пользователя. Например, более вероятно выключение процессов, содержащих действия, которые +не отображаются на экране, по сравнению с процессом, содержащим видимые действия. Следовательно, решение +о прерывании процесса зависит от состояния компонентов, работающих в этом процессе. Ниже обсуждаются правила, +на основании которых принимается решение о выборе прерываемых процессов. </p> + + +<h3 id="Lifecycle">Жизненный цикл процесса</h3> + +<p>Система Android пытается сохранять процесс приложения как можно дольше, но + в конечном счете вынуждена удалять старые процессы, чтобы восстановить память для новых или более важных процессов. Чтобы +определить, какие процессы сохранить, +а какие удалить, система помещает каждый процесс в «иерархию важности» на основе +компонентов, выполняющихся в процессе, и состояния этих компонентов. Процессы с самым низким уровнем +важности исключаются в первую очередь, затем исключаются процессы следующего уровня важности и т. д., насколько это необходимо +для восстановления ресурсов системы.</p> + +<p>В иерархии важности предусмотрено пять уровней. В следующем списке представлены различные +типы процессов в порядке важности (первый процесс является <em>наиболее важным</em> и +<em>удаляется в последнюю очередь</em>):</p> + +<ol> + <li><b>Процесс переднего плана</b> + <p>Процесс, необходимый для текущей деятельности пользователя. Процесс + считается процессом переднего плана, если выполняется любое из следующих условий:</p> + + <ul> + <li>Он содержит действие {@link android.app.Activity}, с которым взаимодействует пользователь (вызван метод {@link +android.app.Activity} +{@link android.app.Activity#onResume onResume()}).</li> + + <li>Он содержит службу {@link android.app.Service}, связанную с действием, с которым +взаимодействует пользователь.</li> + + <li>Он содержит службу {@link android.app.Service}, которая выполняется "на переднем плане", — службу, +которая называется {@link android.app.Service#startForeground startForeground()}. + + <li>Он содержит службу{@link android.app.Service}, которая выполняет один из обратных вызовов + жизненного цикла ({@link android.app.Service#onCreate onCreate()}, {@link android.app.Service#onStart +onStart()} или {@link android.app.Service#onDestroy onDestroy()}).</li> + + <li>Он содержит ресивер {@link android.content.BroadcastReceiver}, который выполняет метод {@link +android.content.BroadcastReceiver#onReceive onReceive()}.</li> + </ul> + + <p>Обычно одновременно работает лишь несколько процессов переднего плана. Они уничтожаются только +в крайнем случае, если памяти остается так мало, что они не могут продолжать совместную работу. Обычно в этот момент +устройство достигло состояния разбиения памяти на страницы, поэтому для того, чтобы пользовательский интерфейс откликался на действия пользователя, необходимо +удаление некоторых процессов на переднем плане.</p></li> + + <li><b>Видимые процессы</b> + <p>Процессы, которые не содержат компонентов переднего плана, но могут +влиять на отображение на экране. Процесс считается видимым, +если выполняется любое из следующих условий:</p> + + <ul> + <li>Он содержит действие {@link android.app.Activity}, которое не находится на переднем плане, но +видно пользователю (вызван метод {@link android.app.Activity#onPause onPause()}). +Например, это может происходить, если действие на переднем плане запустило диалоговое окно, которое позволяет видеть +предыдущее действие позади него.</li> + + <li>Он содержит службу {@link android.app.Service}, связанную с видимым +действием или действием переднего плана.</li> + </ul> + + <p>Видимый процесс считается исключительно важным, его следует удалять +только в случае, если требуется сохранить работу всех процессов переднего плана. </p> + </li> + + <li><b>Служебный процесс</b> + <p>Процесс, который выполняет службу, запущенную с помощью метода {@link +android.content.Context#startService startService()}, и не попадает ни в одну из двух +категорий более высокого уровня. Хотя служебные процессы не связаны непосредственно с тем, что видит пользователь, +они обычно выполняют важные для пользователя действия (например, воспроизводят музыку в фоновом режиме или +загружают данные в сеть), поэтому система сохраняет их выполнение, если памяти достаточно для +их работы наряду со всеми видимыми процессами и процессами переднего плана. </p> + </li> + + <li><b>Фоновый процесс</b> + <p>Процесс, содержащий действия, которые не видны пользователю в настоящее время (вызван метод +{@link android.app.Activity#onStop onStop()} действия). Эти процессы не оказывают непосредственного +воздействия на работу пользователя, и система может удалить их в любой момент, чтобы освободить память для +процессов переднего плана, +видимых или служебных процессов. Обычно выполняется множество фоновых процессов, поэтому они хранятся в списке +LRU (недавно использованные), чтобы процессы, содержащие самые +недавние действия, которые видел пользователь, удалялись в последнюю очередь. Если для действия правильно реализованы методы жизненного цикла, +и действие сохраняет текущее состояние, удаление процесса этого действия не оказывает видимого воздействия на +работу пользователя, так как когда пользователь возвращается к этому действию, оно восстанавливает +все элементы своего видимого состояния. Информацию о сохранении и восстановлении состояния см. в документе <a href="{@docRoot}guide/components/activities.html#SavingActivityState">Действия</a> +.</p> + </li> + + <li><b>Пустой процесс</b> + <p>Процесс, не содержащий никаких компонентов активного приложения. Единственная причина сохранять процесс +такого типа — это кэширование, которое улучшает время следующего запуска +компонента в этом процессе. Система часто удаляет эти процессы для равномерного распределения всех системных +ресурсов между кэшем процесса и кэшем базового ядра.</p> + </li> +</ol> + + + <p>Система Android относит процесс к максимально высокому уровню на основе важности +компонентов, активных в процессе в текущее время. Например, если процесс содержит служебное и видимое действие, +процесс считается видимым, а не служебным процессом.</p> + + <p>Кроме того, уровень процесса может быть повышен, поскольку имеются другие процессы, зависимые от него. +Например, процесс, обслуживающий другой процесс, не может иметь уровень ниже уровня обслуживаемого +процесса. Например, если поставщик контента в процессе A обслуживает клиента в процессе B или +служебный процесс A связан с компонентом в процессе B, процесс A всегда считается не менее +важным, чем процесс B.</p> + + <p>Так как процесс, выполняющий службу, оценивается выше процесса с фоновыми действиям, +действие, запускающее долговременную операцию, может запустить <a href="{@docRoot}guide/components/services.html">службу</a> для этой операции, а не просто +создать рабочий поток, особенно в случае, если операция продлится дольше действия. +Например, действие, которое загружает изображение на веб-сайт, должно запустить службу для выполнения +загрузки, так что загрузка может продолжаться в фоновом режиме даже после выхода пользователя из действия. +Использование службы гарантирует, что операция будет иметь приоритет не ниже «служебного процесса», +независимо от того, что происходит с действием. По этой же причине ресиверы должны +использовать службы, а не просто ставить в поток операции, требующие много времени для выполнения.</p> + + + + +<h2 id="Threads">Потоки</h2> + +<p>При запуске приложения система создает поток выполнения для приложения, +который называется «главным». Этот поток очень важен, так как он отвечает за диспетчеризацию событий +на виджеты соответствующего интерфейса пользователя, включая события графического представления. Он также является потоком, в котором +приложение взаимодействует с компонентами из набора инструментов пользовательского интерфейса Android (компонентами из пакетов {@link +android.widget} и {@link android.view}). По существу, главный поток — это то, что иногда называют +потоком пользовательского интерфейса.</p> + +<p>Система <em>не</em> создает отдельного потока для каждого экземпляра компонента. Все +компоненты, которые выполняются в одном процессе, создают экземпляры в потоке пользовательского интерфейса, и системные вызовы +каждого компонента отправляются из этого потока. Поэтому методы, которые отвечают на системные +обратные вызовы (такие как метод {@link android.view.View#onKeyDown onKeyDown()} для сообщения о действиях пользователя +или метод обратного вызова жизненного цикла), всегда выполняются в потоке пользовательского интерфейса процесса.</p> + +<p>Например, когда пользователь нажимает кнопку на экране, поток пользовательского интерфейса вашего приложения отправляет +событие нажатия в виджет, который, в свою очередь, устанавливает кнопку в нажатое состояние и отправляет запрос на аннулирование +в очередь событий. Поток пользовательского интерфейса исключает запрос из очереди и уведомляет виджет, что он должен +отобразиться повторно.</p> + +<p>Когда приложение выполняет интенсивную работу в ответ на действия пользователя, эта одиночная модель потока +может показывать плохую производительность, если приложение реализовано неправильно. То есть, если +все происходит в потоке пользовательского интерфейса, выполнение долговременных операций, таких как сетевой доступ или +запросы к базе данных, будет блокировать весь пользовательский интерфейс. Когда поток заблокирован, не могут обрабатываться никакие события, +включая события изменения отображения. С точки зрения пользователя +приложение выглядит зависшим. Хуже того, если поток пользовательского интерфейса заблокирован более нескольких секунд +(в настоящее время около 5 секунд), отображается печально известное диалоговое окно <a href="http://developer.android.com/guide/practices/responsiveness.html">«приложение не +отвечает</a>». После этого недовольный пользователь может выйти из вашего приложения +и удалить его.</p> + +<p>Кроме того, набор инструментов пользовательского интерфейса Andoid <em>не</em> является потокобезопасным. Поэтому, вы не должны работать +с пользовательским интерфейсом из рабочего потока. Манипуляции с пользовательским интерфейсом необходимо выполнять из +потока пользовательского интерфейса. Таким образом, существует только два правила однопоточной модели Android:</p> + +<ol> +<li>Не блокируйте поток пользовательского интерфейса +<li>Не обращайтесь к набору инструментов пользовательского интерфейса Android снаружи потока пользовательского интерфейса +</ol> + +<h3 id="WorkerThreads">Рабочие потоки</h3> + +<p>Вследствие описанной выше однопоточной модели для динамичности пользовательского интерфейса ваших приложений +очень важно не блокировать поток пользовательского интерфейса. Если требуется выполнять операции, +занимающие некоторое время, обязательно выполняйте их в отдельных потоках (»фоновых» или +«рабочих» потоках).</p> + +<p>Например, ниже приведен код контроля нажатий, который загружает изображение из отдельного +потока и отображает их в виджете {@link android.widget.ImageView}:</p> + +<pre> +public void onClick(View v) { + new Thread(new Runnable() { + public void run() { + Bitmap b = loadImageFromNetwork("http://example.com/image.png"); + mImageView.setImageBitmap(b); + } + }).start(); +} +</pre> + +<p>На первый взгляд, он должен работать хорошо, так как он создает новый поток для обработки сетевой +операции. Однако, он нарушает второе правило однопоточной модели: <em>не обращайтесь к набору инструментов пользовательского интерфейса +Android снаружи потока пользовательского интерфейса</em> — этот пример изменяет {@link +android.widget.ImageView} из рабочего потока, а не из потока пользовательского интерфейса. Это может привести +к неопределенному и непредвиденному поведению, отследить которое будет трудно.</p> + +<p>Для устранения этой проблемы Android предлагает несколько путей доступа к потоку пользовательского интерфейса из других +потоков. Ниже приведен список полезных методов:</p> + +<ul> +<li>{@link android.app.Activity#runOnUiThread(java.lang.Runnable) +Activity.runOnUiThread(Runnable)}</li> +<li>{@link android.view.View#post(java.lang.Runnable) View.post(Runnable)}</li> +<li>{@link android.view.View#postDelayed(java.lang.Runnable, long) View.postDelayed(Runnable, +long)}</li> +</ul> + +<p>Например, можно исправить приведенный выше код с помощью метода {@link +android.view.View#post(java.lang.Runnable) View.post(Runnable)}:</p> + +<pre> +public void onClick(View v) { + new Thread(new Runnable() { + public void run() { + final Bitmap bitmap = loadImageFromNetwork("http://example.com/image.png"); + mImageView.post(new Runnable() { + public void run() { + mImageView.setImageBitmap(bitmap); + } + }); + } + }).start(); +} +</pre> + +<p>Теперь реализация является потокобезопасной: сетевая операция выполняется из отдельного потока, +тогда как {@link android.widget.ImageView} работает из потока пользовательского интерфейса.</p> + +<p>Однако по мере роста сложности, код такого типа может становиться запутанным и сложным +для поддержания. Чтобы обрабатывать более сложные взаимодействия с рабочим потоком, можно +использовать метод {@link android.os.Handler} в рабочем потоке для обработки сообщений, поступающих из потока +пользовательского интерфейса. Вероятно, самым лучшим решением является расширение класса {@link android.os.AsyncTask}, +которое упрощает выполнение заданий рабочего потока, которые должны взаимодействовать с пользовательским интерфейсом.</p> + + +<h4 id="AsyncTask">Использование AsyncTask</h4> + +<p>Метод {@link android.os.AsyncTask} позволяет выполнять асинхронную работу в пользовательском +интерфейсе. Он выполняет операции блокирования в рабочем потоке и затем публикует результаты в потоке +пользовательского интерфейса без необходимости самостоятельно обрабатывать потоки и/или обработчики.</p> + +<p>Для использования этого метода необходимо создать подкласс {@link android.os.AsyncTask} и реализовать метод обратного вызова {@link +android.os.AsyncTask#doInBackground doInBackground()}, который работает в пуле +фоновых потоков. Чтобы обновить пользовательский интерфейс, следует реализовать метод {@link +android.os.AsyncTask#onPostExecute onPostExecute()}, который доставляет результат из {@link +android.os.AsyncTask#doInBackground doInBackground()} и работает в потоке пользовательского интерфейса, так что вы можете безопасно +обновлять пользовательский интерфейс. Задача выполняется через вызов метода {@link android.os.AsyncTask#execute execute()} +из потока пользовательского интерфейса.</p> + +<p>Например, можно реализовать предыдущий пример с помощью метода {@link android.os.AsyncTask} следующим +образом:</p> + +<pre> +public void onClick(View v) { + new DownloadImageTask().execute("http://example.com/image.png"); +} + +private class DownloadImageTask extends AsyncTask<String, Void, Bitmap> { + /** The system calls this to perform work in a worker thread and + * delivers it the parameters given to AsyncTask.execute() */ + protected Bitmap doInBackground(String... urls) { + return loadImageFromNetwork(urls[0]); + } + + /** The system calls this to perform work in the UI thread and delivers + * the result from doInBackground() */ + protected void onPostExecute(Bitmap result) { + mImageView.setImageBitmap(result); + } +} +</pre> + +<p>Теперь пользовательский интерфейс защищен и код стал проще, так как работа разделена на +часть, которая должна выполняться в рабочем потоке, и часть, которая должна выполняться в потоке пользовательского интерфейса.</p> + +<p>Прочитайте статью {@link android.os.AsyncTask}, чтобы полностью понять +использование этого класса. Здесь приведен краткий обзор его работы:</p> + +<ul> +<li>Можно указывать тип параметров, значения хода выполнения и конечное +значение задания с помощью универсальных компонентов</li> +<li>Метод {@link android.os.AsyncTask#doInBackground doInBackground()} выполняется автоматически +в рабочем потоке</li> +<li>Методы {@link android.os.AsyncTask#onPreExecute onPreExecute()}, {@link +android.os.AsyncTask#onPostExecute onPostExecute()} и {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} запускаются в потоке пользовательского интерфейса</li> +<li>Значение, возвращенное методом {@link android.os.AsyncTask#doInBackground doInBackground()}, отправляется в метод +{@link android.os.AsyncTask#onPostExecute onPostExecute()}</li> +<li>Можно вызвать {@link android.os.AsyncTask#publishProgress publishProgress()} в любой момент в {@link +android.os.AsyncTask#doInBackground doInBackground()} для выполнения {@link +android.os.AsyncTask#onProgressUpdate onProgressUpdate()} в потоке пользовательского интерфейса</li> +<li>Задание можно отменить в любой момент из любого потока</li> +</ul> + +<p class="caution"><strong>Предупреждение!</strong> Другая проблема, с которой вы можете столкнуться при использовании рабочего +потока, состоит в непредсказуемом перезапуске действия вследствие <a href="{@docRoot}guide/topics/resources/runtime-changes.html">изменения конфигурации в режиме выполнения</a>, +(например, когда пользователь изменяет ориентацию экрана), что может разрушить рабочий поток. Чтобы +увидеть, как можно сохранить задание во время одного из подобных перезапусков и как правильно отменить задание +при разрушении действия, изучите исходный код примера приложения <a href="http://code.google.com/p/shelves/">Shelves</a>.</p> + + +<h3 id="ThreadSafe">Потокобезопасные методы</h3> + +<p> В некоторых ситуациях реализованные методы могут вызываться из нескольких потоков и, следовательно, +должны быть написаны с сохранением потокобезопасности. </p> + +<p>В первую очередь это относится к методам, которые можно вызывать удаленно, например, к методам в <a href="{@docRoot}guide/components/bound-services.html">связанной службе</a>. Когда вызов +метода реализуется в классе {@link android.os.IBinder}, происходящем из того же процесса, в котором выполняется +{@link android.os.IBinder IBinder}, метод выполняется в потоке вызывающего метода. +Однако, когда вызов происходит из другого процесса, метод выполняется в потоке, выбранном из пула +потоков, которые система поддерживает в том же процессе, что и {@link android.os.IBinder +IBinder} (он не выполняется в потоке пользовательского интерфейса процесса). Например, поскольку метод +{@link android.app.Service#onBind onBind()} службы будет вызываться из потока пользовательского интерфейса +процесса службы, методы, реализованные в объекте, который возвращает {@link android.app.Service#onBind +onBind()} (например, подкласс, который реализует методы RPC), будут вызываться из потоков +в пуле. Так как служба может иметь несколько клиентов, несколько потоков из пула могут одновременно использовать +один и тот же метод {@link android.os.IBinder IBinder}. Поэтому методы {@link android.os.IBinder +IBinder} должны быть реализованы с сохранением потокобезопасности.</p> + +<p> Аналогичным образом поставщик контента может получать запросы данных, которые происходят из другого процесса. +Хотя классы {@link android.content.ContentResolver} и {@link android.content.ContentProvider} +скрывают подробности управления взаимодействием процессов, методы {@link +android.content.ContentProvider}, которые отвечают на эти запросы, —методы {@link +android.content.ContentProvider#query query()}, {@link android.content.ContentProvider#insert +insert()}, {@link android.content.ContentProvider#delete delete()}, {@link +android.content.ContentProvider#update update()} и {@link android.content.ContentProvider#getType +getType()} —вызываются из пула потоков в процессе поставщика контента, а не в процессе +потока пользовательского интерфейса. Поскольку эти методы могут вызываться из любого числа потоков одновременно, +они также должны быть реализованы с сохранением потокобезопасности. </p> + + +<h2 id="IPC">Взаимодействие процессов</h2> + +<p>Система Android предлагает механизм взаимодействия процессов (IPC) с помощью удаленного вызова процедуры +(RPC), при котором метод вызывается действием или другим компонентом приложения, но выполняется +удаленно (в другом процессе) с возвратом всех результатов +вызывающему компоненту. Это влечет разложение вызова метода и его данных до уровня, понятного +операционной системе, передачу его из локального процесса и адресного пространства удаленному процессу и +адресному пространству, а затем повторную сборку и восстановление вызова. После этого возвращенные значения +передаются в обратном направлении. Система Android содержит все коды для выполнения этих механизмов IPC, +так что вы можете сосредоточиться на определении и реализации программного интерфейса RPC. </p> + +<p>Для выполнения IPC приложение должно быть привязано к службе с помощью метода {@link +android.content.Context#bindService bindService()}. Дополнительные сведения представлены в разделе <a href="{@docRoot}guide/components/services.html">Службы</a> руководства для разработчиков.</p> + + +<!-- +<h2>Beginner's Path</h2> + +<p>For information about how to perform work in the background for an indefinite period of time +(without a user interface), continue with the <b><a +href="{@docRoot}guide/components/services.html">Services</a></b> document.</p> +--> diff --git a/docs/html-intl/intl/ru/guide/components/recents.jd b/docs/html-intl/intl/ru/guide/components/recents.jd new file mode 100644 index 000000000000..37206318e2f3 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/recents.jd @@ -0,0 +1,256 @@ +page.title=Экран обзора +page.tags="recents","overview" + +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Содержание документа</h2> + <ol> + <li><a href="#adding">Добавление задач на экран обзора</a> + <ol> + <li><a href="#flag-new-doc">Использование флага Intent для добавления задачи</a></li> + <li><a href="#attr-doclaunch">Использование атрибута Операция для добавления задачи</a></li> + </ol> + </li> + <li><a href="#removing">Удаление задач</a> + <ol> + <li><a href="#apptask-remove">Использование класса AppTask для удаления задач</a></li> + <li><a href="#retain-finished">Сохранение завершенных задач</a></li> + </ol> + </li> + </ol> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.app.ActivityManager.AppTask}</li> + <li>{@link android.content.Intent}</li> + </ol> + + <h2>Пример кода</h2> + <ol> + <li><a href="{@docRoot}samples/DocumentCentricApps/index.html">Приложения для работы с документами</a></li> + </ol> + +</div> +</div> + +<p>Экран обзора (также используются названия: экран последних задач, список последних задач или последние приложения) +является элементом пользовательского интерфейса системного уровня, в котором содержится список последних <a href="{@docRoot}guide/components/activities.html"> +операций</a> и <a href="{@docRoot}guide/components/tasks-and-back-stack.html">задач</a>. Пользователь +может перемещаться по списку и выбирать задачи для возобновления, или жестом удалять задачи +из списка. В версии Android 5.0 (уровень API 21) несколько экземпляров +одной операции, содержащие различные документы, могут отображаться в виде задач на экране обзора. Например, +Google Диск может иметь задачу для каждого из нескольких документов Google. На экране обзора каждый документ +отображается в виде задачи.</p> + +<img src="{@docRoot}images/components/recents.png" alt="" width="284" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Экран обзора, на котором показаны три документа Google Диск, + представленные в виде отдельных задач.</p> + +<p>Обычно следует разрешить системе определить способ представления ваших задач и +операций на экране обзора. Вам не нужно менять это поведение. +Однако приложение может определять способ и время появления операции на экране обзора. С помощью класса +{@link android.app.ActivityManager.AppTask} можно управлять задачами, а с помощью флагов операции класса +{@link android.content.Intent} указывается, когда операция добавляется на экран обзора +или удаляется с него. Кроме того, атрибуты <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"> +<activity></a></code> позволяют устанавливать поведение в манифесте.</p> + +<h2 id="adding">Добавление задач на экран обзора</h2> + +<p>Использование флагов класса {@link android.content.Intent} для добавления задачи обеспечивает лучшее управление +временем и способом открытия или повторного открытия документа на экране обзора. С помощью атрибутов +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +можно выбрать открытие документа в новой задаче или повторное использование +существующей задачи для документа.</p> + +<h3 id="flag-new-doc">Использование флага Intent для добавления задачи</h3> + +<p>При создании нового документа для операции вы вызываете метод +{@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +класса {@link android.app.ActivityManager.AppTask}, передавая ему intent, +который запускает операцию. Для вставки логического разрыва, чтобы система обрабатывала вашу операцию как новую +задачу на экране обзора, передайте флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +в метод {@link android.content.Intent#addFlags(int) addFlags()} {@link android.content.Intent}, +который запускает операцию.</p> + +<p class="note"><strong>Примечание.</strong> Флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +замещает флаг {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET}, +который является устаревшим для систем Android 5.0 и выше (уровень API 21).</p> + +<p>Если вы установили флаг {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} при создании +нового документа, система всегда создает новую задачу с целевой операцией в качестве корня. +Этот параметр позволяет открывать один документ в нескольких задачах. Следующий код показывает, +как это делает основная операция:</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html"> +DocumentCentricActivity.java</a></p> +<pre> +public void createNewDocument(View view) { + final Intent newDocumentIntent = newDocumentIntent(); + if (useMultipleTasks) { + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); + } + startActivity(newDocumentIntent); + } + + private Intent newDocumentIntent() { + boolean useMultipleTasks = mCheckbox.isChecked(); + final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class); + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); + newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet()); + return newDocumentIntent; + } + + private static int incrementAndGet() { + Log.d(TAG, "incrementAndGet(): " + mDocumentCounter); + return mDocumentCounter++; + } +} +</pre> + +<p class="note"><strong>Примечание.</strong> Операции, запущенные с флагом {@code FLAG_ACTIVITY_NEW_DOCUMENT}, +должны иметь значение атрибута {@code android:launchMode="standard"} (по умолчанию), установленное +в манифесте.</p> + +<p>Когда основная операция запускает новую операцию, система ищет в существующих задачах одну, +значение intent которой соответствует имени компонента и данным Intent для операции. Если задача +не найдена или intent содержит флаг {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, +создается новая задача с операцией в качестве корня. Если задача найдена, система выводит + эту задачу на передний план и передает новое значение intent в {@link android.app.Activity#onNewIntent onNewIntent()}. +Новая операция получает intent и создает новый документ на экране обзора, как +в следующем примере:</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html"> +NewDocumentActivity.java</a></p> +<pre> +@Override +protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_new_document); + mDocumentCount = getIntent() + .getIntExtra(DocumentCentricActivity.KEY_EXTRA_NEW_DOCUMENT_COUNTER, 0); + mDocumentCounterTextView = (TextView) findViewById( + R.id.hello_new_document_text_view); + setDocumentCounterText(R.string.hello_new_document_counter); +} + +@Override +protected void onNewIntent(Intent intent) { + super.onNewIntent(intent); + /* If FLAG_ACTIVITY_MULTIPLE_TASK has not been used, this activity + is reused to create a new document. + */ + setDocumentCounterText(R.string.reusing_document_counter); +} +</pre> + + +<h3 id="#attr-doclaunch">Использование атрибута Операция для добавления задачи</h3> + +<p>В манифесте операции можно также указать, что операция всегда запускается в новой задаче. Для этого используется атрибут +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +, <a href="{@docRoot}guide/topics/manifest/activity-element.html#dlmode"> +{@code android:documentLaunchMode}</a>. Этот атрибут имеет четыре значения, которые работают следующим образом, +когда пользователь открывает документ в приложении:</p> + +<dl> + <dt>"{@code intoExisting}"</dt> + <dd>Операция повторно использует существующую задачу для документа. Это равносильно установке +флага {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} <em>без</em> установки +флага {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, как описано в разделе +<a href="#flag-new-doc">Использование флага Intent для добавления задачи</a> выше.</dd> + + <dt>"{@code always}"</dt> + <dd>Операция создает новую задачу для документа, даже если документ уже открыт. Использование +этого значения равносильно установке обоих флагов {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +и {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}.</dd> + + <dt>"{@code none”}"</dt> + <dd>Операция не создает новой задачи для документа. Экран обзора обрабатывает +операцию как операцию по умолчанию: на экране обзора отображается одна задача для приложения, которая +возобновляется с любой последней операции, вызванной пользователем.</dd> + + <dt>"{@code never}"</dt> + <dd>Операция не создает новой задачи для документа. Установка этого значения +переопределяет поведение флагов {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} + и {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK}, если оба они установлены в +intent, и на экране обзора отображается одна задача для приложения, которая +возобновляется с любой последней операции, вызванной пользователем.</dd> +</dl> + +<p class="note"><strong>Примечание.</strong> Для значений кроме {@code none} и {@code never} +операция должна быть определена с атрибутом {@code launchMode="standard"}. Если этот атрибут не указан, +используется {@code documentLaunchMode="none"}.</p> + +<h2 id="removing">Удаление задач</h2> + +<p>По умолчанию задача документа автоматически удаляется с экрана обзора после завершения +соответствующей операции. Можно переопределить это поведение с помощью класса {@link android.app.ActivityManager.AppTask}, +с флагом {@link android.content.Intent} или атрибутом <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"> +<activity></a></code>.</p> + +<p>Можно в любой момент полностью убрать задачу с экрана обзора, установив для атрибута +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> + <a href="{@docRoot}guide/topics/manifest/activity-element.html#exclude"> +{@code android:excludeFromRecents}</a> значение {@code true}.</p> + +<p>Можно установить максимальное число задач, которое ваше приложение может включить в экран обзора, установив для атрибута +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> + <a href="{@docRoot}guide/topics/manifest/activity-element.html#maxrecents">{@code android:maxRecents} +</a> целое значение. Значение по умолчанию: 16. При достижении максимального количества задач самая +долго не используемая задача удаляется с экрана обзора. Максимальное значение {@code android:maxRecents} составляет +50 (25 для устройств с малым объемом памяти); Значения менее 1 не допускаются.</p> + +<h3 id="#apptask-remove">Использование класса AppTask для удаления задач</h3> + +<p>В операции, которая создает новую задачу на экране обзора, +можно указать время удаления задачи и завершения всех связанных с ней операций, вызвав +метод {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}.</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html"> +NewDocumentActivity.java</a></p> +<pre> +public void onRemoveFromRecents(View view) { + // The document is no longer needed; remove its task. + finishAndRemoveTask(); +} +</pre> + +<p class="note"><strong>Примечание.</strong> Использование +метода {@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()}, +который переопределяет использование тега {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, +рассмотрен ниже.</p> + +<h3 id="#retain-finished">Сохранение завершенных задач</h3> + +<p>Чтобы сохранить задачу на экране обзора, даже если ее операция завершена, передайте +флаг {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} в метод +{@link android.content.Intent#addFlags(int) addFlags()} объекта Intent, который запускает операцию.</p> + +<p class="code-caption"><a href="{@docRoot}samples/DocumentCentricApps/index.html"> +DocumentCentricActivity.java</a></p> +<pre> +private Intent newDocumentIntent() { + final Intent newDocumentIntent = new Intent(this, NewDocumentActivity.class); + newDocumentIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT | + android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS); + newDocumentIntent.putExtra(KEY_EXTRA_NEW_DOCUMENT_COUNTER, incrementAndGet()); + return newDocumentIntent; +} +</pre> + +<p>Для достижения того же результата установите для атрибута +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> + <a href="{@docRoot}guide/topics/manifest/activity-element.html#autoremrecents"> +{@code android:autoRemoveFromRecents}</a> значение {@code false}. Значение по умолчанию {@code true} +для операций документа и {@code false} для обычных операций. Использование этого атрибута переопределяет +флаг {@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS}, описанный выше.</p> + + + + + + + diff --git a/docs/html-intl/intl/ru/guide/components/services.jd b/docs/html-intl/intl/ru/guide/components/services.jd new file mode 100644 index 000000000000..62a6a7ef9d15 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/services.jd @@ -0,0 +1,813 @@ +page.title=Службы +@jd:body + +<div id="qv-wrapper"> +<ol id="qv"> +<h2>Содержание документа</h2> +<ol> +<li><a href="#Basics">Основы</a></li> +<ol> + <li><a href="#Declaring">Объявление службы в манифесте</a></li> +</ol> +<li><a href="#CreatingAService">Создание запущенной службы</a> + <ol> + <li><a href="#ExtendingIntentService">Наследование класса IntentService</a></li> + <li><a href="#ExtendingService">Наследование класса Service</a></li> + <li><a href="#StartingAService">Запуск службы</a></li> + <li><a href="#Stopping">Остановка службы</a></li> + </ol> +</li> +<li><a href="#CreatingBoundService">Создание привязанной службы</a></li> +<li><a href="#Notifications">Отправка уведомлений пользователю</a></li> +<li><a href="#Foreground">Запуск службы на переднем плане</a></li> +<li><a href="#Lifecycle">Управление жизненным циклом службы</a> +<ol> + <li><a href="#LifecycleCallbacks">Реализация обратных вызовов жизненного цикла</a></li> +</ol> +</li> +</ol> + +<h2>Ключевые классы</h2> +<ol> + <li>{@link android.app.Service}</li> + <li>{@link android.app.IntentService}</li> +</ol> + +<h2>Примеры</h2> +<ol> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/ServiceStartArguments.html">{@code + ServiceStartArguments}</a></li> + <li><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/LocalService.html">{@code + LocalService}</a></li> +</ol> + +<h2>См. также:</h2> +<ol> +<li><a href="{@docRoot}guide/components/bound-services.html">Привязанные службы</a></li> +</ol> + +</div> + + +<p>{@link android.app.Service} является компонентом приложения, который может выполнять +длительные операции в фоновом режиме и не содержит пользовательского интерфейса. Другой компонент +приложения может запустить службу, которая продолжит работу в фоновом режиме даже в том случае, когда пользователь +перейдет в другое приложение. Кроме того, компонент может привязаться к службе для +взаимодействия с ней и даже выполнять межпроцессное взаимодействие (IPC). Например, служба может +обрабатывать сетевые транзакции, воспроизводить музыку, выполнять ввод-вывод файла или взаимодействовать с поставщиком контента, и все +это в фоновом режиме.</p> + +<p>Фактически служба может принимать две формы:</p> + +<dl> + <dt>Запущенная</dt> + <dd>Служба является «запущенной», когда компонент приложения (например, операция) запускает ее +вызовом {@link android.content.Context#startService startService()}. После запуска служба +может работать в фоновом режиме в течение неограниченного времени, даже если уничтожен компонент, который ее запустил. Обычно +запущенная служба выполняет одну операцию и не возвращает результатов вызывающему компоненту. +Например, она может загружать или выгружать файл по сети. Когда операция выполнена, +служба должна остановиться самостоятельно.</dd> + <dt>Привязанная</dt> + <dd>Служба является «привязанной», когда компонент приложения привязывается к ней вызовом {@link +android.content.Context#bindService bindService()}. Привязанная служба предлагает интерфейс клиент-сервер, +который позволяет компонентам взаимодействовать со службой, отправлять запросы, получать результаты и даже +делать это между разными процессами посредством межпроцессного взаимодействия (IPC). Привязанная служба работает только пока +к ней привязан другой компонент приложения. К службе могут быть привязаны несколько компонентов одновременно, +но когда все они отменяют привязку, служба уничтожается.</dd> +</dl> + +<p>Хотя в этой документации эти два типа служб обсуждаются отдельно, служба может +работать обеими способами — она может быть запущенной (и работать в течение неограниченного времени) и допускать привязку. +Это зависит от реализации пары методов обратного вызова: {@link +android.app.Service#onStartCommand onStartCommand()} позволяет компонентам запускать службу, а {@link +android.app.Service#onBind onBind()} позволяет выполнять привязку.</p> + +<p>Независимо от состояния приложения (запущенное, привязанное или и оба сразу) любой компонент приложения +может использовать службу (даже из отдельного приложения) подобно тому, как любой компонент может использовать +операцию — запустив ее с помощью {@link android.content.Intent}. Однако вы можете объявить закрытую +службу в файле манифеста и заблокировать доступ к ней из других приложений. Более подробно +это обсуждается в разделе <a href="#Declaring">Объявление службы +в манифесте</a>.</p> + +<p class="caution"><strong>Внимание!</strong> Служба работает +в основном потоке ведущего процесса — служба <strong>не</strong> создает своего потока +и <strong>не</strong> выполняется в отдельном процессе (если вы не указали иное). Это означает, +что если ваша служба собирается выполнять любую работу с высокой нагрузкой ЦП или блокирующие операции (например, воспроизведение MP3 +или сетевые операции), вы должны создать в службе новый поток для выполнения этой работы. Используя +отдельный поток, вы снижаете риск возникновения ошибок «Приложение не отвечает», и +основной поток приложения может отрабатывать взаимодействие пользователя с вашими операциями.</p> + + +<h2 id="Basics">Основы</h2> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>Что лучше использовать — службу или поток?</h3> + <p>Служба — это просто компонент, который может выполняться в фоновом режиме, даже когда пользователь +не взаимодействует с приложением. Следовательно, вы должны создавать службу только в том случае, если вам нужно +именно это.</p> + <p>Если вам требуется выполнить работу за пределами основного потока, но только в то время, когда пользователь взаимодействует +с приложением, то вам, вероятно, следует создать новый поток, а не службу. Например, +если вы хотите воспроизводить определенную музыку, но только во время работы операции, вы можете создать +поток в {@link android.app.Activity#onCreate onCreate()}, запустить его выполнение в методе {@link +android.app.Activity#onStart onStart()}, а затем остановить его в методе {@link android.app.Activity#onStop +onStop()}. Также рассмотрите возможность использования класса {@link android.os.AsyncTask} или {@link android.os.HandlerThread} +вместо обычного класса {@link java.lang.Thread}. В документе <a href="{@docRoot}guide/components/processes-and-threads.html#Threads">Процессы +и потоки</a> содержится дополнительная информация об этих потоках.</p> + <p>Помните, что если вы действительно используете службу, она выполняется в основном потоке вашего приложения по умолчанию, +поэтому вы должны создать новый поток в службе, если она выполняет интенсивные или +блокирующие операции.</p> +</div> +</div> + +<p>Чтобы создать службу, необходимо создать подкласс класса {@link android.app.Service} (или одного +из существующих его подклассов). В вашей реализации необходимо переопределить некоторые методы обратного вызова, +которые обрабатывают ключевые моменты жизненного цикла службы и при необходимости +предоставляют механизм привязывания компонентов. Наиболее важные методы обратного вызова, которые необходимо переопределить:</p> + +<dl> + <dt>{@link android.app.Service#onStartCommand onStartCommand()}</dt> + <dd>Система вызывает этот метод, когда другой компонент, например, операция, +запрашивает запуск этой службы, вызывая {@link android.content.Context#startService +startService()}. После выполнения этого метода служба запускается и может в течение неограниченного времени +работать в фоновом режиме. Если вы реализуете такой метод, вы обязаны остановить службу +посредством вызова {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}. (Если требуется только обеспечить привязку, +реализовывать этот метод не обязательно).</dd> + <dt>{@link android.app.Service#onBind onBind()}</dt> + <dd>Система вызывает этот метод, когда другой компонент хочет выполнить привязку +к службе (например, для выполнения удаленного вызова процедуры) путем вызова {@link android.content.Context#bindService +bindService()}. В вашей реализации этого метода вы должны обеспечить интерфейс, который клиенты +используют для взаимодействия со службой, возвращая {@link android.os.IBinder}. Всегда необходимо реализовывать +этот метод, но если вы не хотите разрешать привязку, необходимо возвращать значение null.</dd> + <dt>{@link android.app.Service#onCreate()}</dt> + <dd>Система вызывает этот метод при первом создании службы для выполнения однократных +процедур настройки (перед вызовом {@link android.app.Service#onStartCommand onStartCommand()} или +{@link android.app.Service#onBind onBind()}). Если служба уже запущена, этот метод не +вызывается.</dd> + <dt>{@link android.app.Service#onDestroy()}</dt> + <dd>Система вызывает этот метод, когда служба более не используется и выполняется ее уничтожение. +Ваша служба должна реализовать это для очистки ресурсов, таких как потоки, зарегистрированные +приемники, ресиверы и т. д. Это последний вызов, который получает служба.</dd> +</dl> + +<p>Если компонент запускает службу посредством вызова {@link +android.content.Context#startService startService()} (что приводит к вызову {@link +android.app.Service#onStartCommand onStartCommand()}), то служба +продолжает работу, пока она не остановится самостоятельно с помощью {@link android.app.Service#stopSelf()} или другой +компонент не остановит ее посредством вызова {@link android.content.Context#stopService stopService()}.</p> + +<p>Если компонент вызывает +{@link android.content.Context#bindService bindService()} для создания службы (и {@link +android.app.Service#onStartCommand onStartCommand()} <em>не</em> вызывается), то служба работает, пока +к ней привязан компонент. Как только выполняется отмена привязки службы ко всем клиентам, +система уничтожает службу.</p> + +<p>Система Android будет принудительно останавливать службу только в том случае, когда не хватает памяти, и необходимо восстановить системные +для операции, которая отображается на переднем плане. Если служба привязана к операции, которая отображается на переднем плане, +менее вероятно, что она будет уничтожена, и если служба объявлена для <a href="#Foreground">выполнения в фоновом режиме</a> (как обсуждалось выше), она почти никогда не будет уничтожаться. +В противном случае, если служба была запущена и является длительной, система со временем будет опускать ее положение в списке +фоновых задач, и служба станет очень чувствительной к +уничтожению — если ваша служба запущена, вы должны предусмотреть изящную обработку ее перезапуска +системой. Если система уничтожает вашу службу, она перезапускает ее, как только снова появляется +доступ к ресурсам (хотя это также зависит от значения, возвращаемого методом {@link +android.app.Service#onStartCommand onStartCommand()}, как обсуждается ниже). Дополнительная информация +о ситуациях, в которых система может уничтожить службу приведена в документе <a href="{@docRoot}guide/components/processes-and-threads.html">Процессы и потоки</a> +.</p> + +<p>В следующих разделах описаны способы создания служб каждого типа и использования +их из других компонентов приложения.</p> + + + +<h3 id="Declaring">Объявление службы в манифесте</h3> + +<p>Все службы, как и операции (и другие компоненты), должны быть объявлены в файле +манифеста вашего приложения.</p> + +<p>Чтобы объявить службу, добавьте элемент <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> +, в качестве дочернегоэлемента <a href="{@docRoot}guide/topics/manifest/application-element.html">{@code <application>}</a> +. Например:</p> + +<pre> +<manifest ... > + ... + <application ... > + <service android:name=".ExampleService" /> + ... + </application> +</manifest> +</pre> + +<p>Дополнительные сведения об объявлении службы +в манифесте см. в справке по элементу <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a>.</p> + +<p>Имеются другие атрибуты, которые можно включить в элемент <a href="{@docRoot}guide/topics/manifest/service-element.html">{@code <service>}</a> для +задания свойств, например, необходимых для запуска разрешений, и процесса, +в котором должна выполняться служба. Атрибут <a href="{@docRoot}guide/topics/manifest/service-element.html#nm">{@code android:name}</a> +является единственным обязательным атрибутом — он указывает имя класса для службы. После +публикации вашего приложения вам не следует менять это имя, поскольку это может разрушить +код из-за зависимости от явных намерений, используемых, чтобы запустить или привязать службу (ознакомьтесь с публикацией <a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Вещи, которые +нельзя менять</a> в блоге разработчиков). + +<p>Для обеспечения безопасности приложения <strong>всегда используйте явное намерение при запуске +или привязке {@link android.app.Service}</strong> и не объявляйте фильтров намерений для службы. Если вам +важно допустить некоторую неопределенность в отношении того, какая служба запускается, вы можете +предоставить фильтры намерений для ваших служб и исключить имя компонента из {@link +android.content.Intent}, но затем вы должны установить пакет для намерения с помощью {@link +android.content.Intent#setPackage setPackage()}, который обеспечивает достаточное устранение неоднозначности +для целевой службы.</p> + +<p>Дополнительно можно обеспечить доступность вашей службы только для вашего приложения, +включив атрибут <a href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code android:exported}</a> +и установив для него значение {@code "false"}. Это не позволяет другим приложениям запускать +вашу службу даже при использовании явного намерения.</p> + + + + +<h2 id="CreatingStartedService">Создание запущенной службы</h2> + +<p>Запущенная служба — это служба, которую запускает другой компонент вызовом {@link +android.content.Context#startService startService()}, что приводит к вызову +метода {@link android.app.Service#onStartCommand onStartCommand()} службы.</p> + +<p>При запуске служба обладает сроком жизни, не зависящим от запустившего ее компонента, +и может работать в фоновом режиме в течение неограниченного времени, +даже если уничтожен компонент, который ее запустил. Поэтому после выполнения своей работы служба должна остановиться самостоятельно +посредством вызова метода {@link android.app.Service#stopSelf stopSelf()}, либо ее может остановить другой компонент +посредством вызова метода{@link android.content.Context#stopService stopService()}.</p> + +<p>Компонент приложения, например, операция, может запустить службу, вызвав метод {@link +android.content.Context#startService startService()} и передав объект {@link android.content.Intent}, +который указывает службу и любые данные, которые служба должна использовать. Служба получает +этот объект {@link android.content.Intent} в методе {@link android.app.Service#onStartCommand +onStartCommand()}.</p> + +<p>Предположим, что операции требуется сохранить некоторые данные в сетевой базе данных. Операция может +запустить службу и предоставить ей данные для сохранения, передав намерение в метод {@link +android.content.Context#startService startService()}. Служба получает намерение в методе {@link +android.app.Service#onStartCommand onStartCommand()}, подключается к Интернету и выполняет транзакцию +с базой данных. Когда транзакция выполнена, служба останавливается +самостоятельно и уничтожается.</p> + +<p class="caution"><strong>Внимание!</strong> По умолчанию службы работают в том же процессе, что и приложение, +в котором они объявлены, а также в основном потоке этого приложения. Поэтому, если ваша служба +выполняет интенсивные или блокирующие операции, в то время как пользователь взаимодействует с операцией из того же +приложения, служба будет замедлять выполнение операции. Чтобы избежать негативного воздействия на скорость работы +приложения, вы должны запустить новый поток внутри службы.</p> + +<p>Традиционно имеется два класса, которые вы можете наследовать для создания запущенной службы:</p> +<dl> + <dt>{@link android.app.Service}</dt> + <dd>Это базовый класс для всех служб. Когда вы наследуете этот класс, важно +создать новый поток, в котором будет выполняться вся работа службы, поскольку по умолчанию служба использует основной поток вашего +приложения, что может замедлить любую операцию, которую +выполняет ваше приложение.</dd> + <dt>{@link android.app.IntentService}</dt> + <dd>Это подкласс класса {@link android.app.Service}, который использует рабочий поток для обработки всех +запросов запуска поочередно. Это оптимальный вариант, если вам не требуется, чтобы ваша служба +обрабатывала несколько запросов одновременно. Достаточно реализовать метод {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, который получает намерение для каждого +запроса запуска, позволяя выполнять фоновую работу.</dd> +</dl> + +<p>В следующих разделах описано, как реализовать службу с помощью любого их этих +классов.</p> + + +<h3 id="ExtendingIntentService">Наследование класса IntentService</h3> + +<p>Так как большинству запущенных приложений не требуется обрабатывать несколько запросов одновременно, +(что может быть действительно опасным сценарием), вероятно будет лучше, если вы +реализуете свою службу с помощью класса {@link android.app.IntentService}.</p> + +<p>Класс {@link android.app.IntentService} делает следующее:</p> + +<ul> + <li>Создает рабочий поток по умолчанию, который выполняет все намерения, доставленные в метод {@link +android.app.Service#onStartCommand onStartCommand()}, отдельно от основного потока +вашего приложения.</li> + <li>Создает рабочую очередь, которая передает намерения по одному в вашу реализацию метода {@link +android.app.IntentService#onHandleIntent onHandleIntent()}, поэтому вы не должны беспокоиться +относительно многопоточности.</li> + <li>Останавливает службу после обработки всех запросов запуска, поэтому вам никогда не требуется вызывать +{@link android.app.Service#stopSelf}.</li> + <li>Предоставляет реализацию метода {@link android.app.IntentService#onBind onBind()} по умолчанию, которая +возвращает null.</li> + <li>Предоставляет реализацию метода {@link android.app.IntentService#onStartCommand +onStartCommand()} по умолчанию, которая отправляет намерение в рабочую очередь и затем в вашу реализацию {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.</li> +</ul> + +<p>Все это означает, что вам достаточно реализовать метод {@link +android.app.IntentService#onHandleIntent onHandleIntent()} для выполнения работы, предоставленной +клиентом. (Хотя, кроме того, вы должны предоставить маленький конструктор для службы).</p> + +<p>Здесь приведен пример реализации класса {@link android.app.IntentService}:</p> + +<pre> +public class HelloIntentService extends IntentService { + + /** + * A constructor is required, and must call the super {@link android.app.IntentService#IntentService} + * constructor with a name for the worker thread. + */ + public HelloIntentService() { + super("HelloIntentService"); + } + + /** + * The IntentService calls this method from the default worker thread with + * the intent that started the service. When this method returns, IntentService + * stops the service, as appropriate. + */ + @Override + protected void onHandleIntent(Intent intent) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + long endTime = System.currentTimeMillis() + 5*1000; + while (System.currentTimeMillis() < endTime) { + synchronized (this) { + try { + wait(endTime - System.currentTimeMillis()); + } catch (Exception e) { + } + } + } + } +} +</pre> + +<p>Это все, что нужно: конструктор и реализация класса {@link +android.app.IntentService#onHandleIntent onHandleIntent()}.</p> + +<p>Если вы решили переопределить также и другие методы обратного вызова, такие как {@link +android.app.IntentService#onCreate onCreate()}, {@link +android.app.IntentService#onStartCommand onStartCommand()} или {@link +android.app.IntentService#onDestroy onDestroy()}, обязательно вызовите реализацию суперкласса, +чтобы класс {@link android.app.IntentService} мог правильно обрабатывать жизненный цикл рабочего потока.</p> + +<p>Например, метод {@link android.app.IntentService#onStartCommand onStartCommand()} должен возвращать +реализацию по умолчанию (которая доставляет намерение в {@link +android.app.IntentService#onHandleIntent onHandleIntent()}):</p> + +<pre> +@Override +public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + return super.onStartCommand(intent,flags,startId); +} +</pre> + +<p>Помимо {@link android.app.IntentService#onHandleIntent onHandleIntent()}, единственный метод, +из которого вам не требуется вызывать суперкласс, это метод {@link android.app.IntentService#onBind +onBind()} (но его нужно реализовывать только в случае, если ваша служба допускает привязку).</p> + +<p>В следующем разделе вы увидите, как реализовывается служба такого же типа при наследовании +базового класса {@link android.app.Service}, которая содержит намного больше кода, но которая может +подойти, если вам требуется обрабатывать одновременные запросы запуска.</p> + + +<h3 id="ExtendingService">Наследование класса Service</h3> + +<p>Как вы видели в предыдущем разделе, использование класса {@link android.app.IntentService} значительно упрощает +реализацию запущенной службы. Однако, если необходимо, чтобы ваша служба +поддерживала многопоточность (вместо обработки запросов запуска через рабочую очередь), можно +наследовать класс {@link android.app.Service} для обработки каждого намерения.</p> + +<p>В качестве примера приведена следующая реализация класса {@link +android.app.Service}, которая выполняет ту же работу, как и пример выше, использующий класс {@link +android.app.IntentService}. То есть для каждого запроса запуска он использует рабочий поток для выполнения +задания и обрабатывает запросы по одному.</p> + +<pre> +public class HelloService extends Service { + private Looper mServiceLooper; + private ServiceHandler mServiceHandler; + + // Handler that receives messages from the thread + private final class ServiceHandler extends Handler { + public ServiceHandler(Looper looper) { + super(looper); + } + @Override + public void handleMessage(Message msg) { + // Normally we would do some work here, like download a file. + // For our sample, we just sleep for 5 seconds. + long endTime = System.currentTimeMillis() + 5*1000; + while (System.currentTimeMillis() < endTime) { + synchronized (this) { + try { + wait(endTime - System.currentTimeMillis()); + } catch (Exception e) { + } + } + } + // Stop the service using the startId, so that we don't stop + // the service in the middle of handling another job + stopSelf(msg.arg1); + } + } + + @Override + public void onCreate() { + // Start up the thread running the service. Note that we create a + // separate thread because the service normally runs in the process's + // main thread, which we don't want to block. We also make it + // background priority so CPU-intensive work will not disrupt our UI. + HandlerThread thread = new HandlerThread("ServiceStartArguments", + Process.THREAD_PRIORITY_BACKGROUND); + thread.start(); + + // Get the HandlerThread's Looper and use it for our Handler + mServiceLooper = thread.getLooper(); + mServiceHandler = new ServiceHandler(mServiceLooper); + } + + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + Toast.makeText(this, "service starting", Toast.LENGTH_SHORT).show(); + + // For each start request, send a message to start a job and deliver the + // start ID so we know which request we're stopping when we finish the job + Message msg = mServiceHandler.obtainMessage(); + msg.arg1 = startId; + mServiceHandler.sendMessage(msg); + + // If we get killed, after returning from here, restart + return START_STICKY; + } + + @Override + public IBinder onBind(Intent intent) { + // We don't provide binding, so return null + return null; + } + + @Override + public void onDestroy() { + Toast.makeText(this, "service done", Toast.LENGTH_SHORT).show(); + } +} +</pre> + +<p>Как можно видеть, этот код значительно длиннее, чем код с использованием класса {@link android.app.IntentService}.</p> + +<p>Однако, так как вы обрабатываете каждый вызов {@link android.app.Service#onStartCommand +onStartCommand()} самостоятельно, вы можете выполнять несколько запросов одновременно. Данный код +выполняет не совсем эту работу, но при необходимости вы можете создавать новые потоки для каждого +запроса и сразу запускать их (а не ожидать завершения предыдущего запроса).</p> + +<p>Обратите внимание, что метод {@link android.app.Service#onStartCommand onStartCommand()} должен +возвращать целое число. Это целое число описывает, как система должна продолжать выполнение службы в случае, +когда система уничтожила ее (как описано выше, реализация по умолчанию для класса {@link +android.app.IntentService} обрабатывает эту ситуацию, хотя вы изменить ход реализации). Значение, +возвращаемое методом {@link android.app.Service#onStartCommand onStartCommand()}, должно быть одной из следующих +констант:</p> + +<dl> + <dt>{@link android.app.Service#START_NOT_STICKY}</dt> + <dd>Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, <em>не нужно</em> повторно создавать службу, если нет ожидающих +доставки намерений. Это самый безопасный вариант, позволяющий избежать запуска вашей службы, когда это не нужно +и когда ваше приложение может просто перезапустить любые незавершенные задания.</dd> + <dt>{@link android.app.Service#START_STICKY}</dt> + <dd>Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, повторно создайте службу и вызовите {@link +android.app.Service#onStartCommand onStartCommand()}, но <em>не</em> передавайте последнее намерение повторно. +Вместо этого система вызывает метод {@link android.app.Service#onStartCommand onStartCommand()} с намерением, +которое имеет значение null, если нет ожидающих намерений для запуска службы. Если ожидающие намерения есть, +они доставляются. Это подходит для мультимедийных проигрывателей (или подобных служб), которые не +выполняют команды, а работают независимо и ожидают задание.</dd> + <dt>{@link android.app.Service#START_REDELIVER_INTENT}</dt> + <dd>Если система уничтожает службу после возвращения из {@link android.app.Service#onStartCommand +onStartCommand()}, повторно создайте службу и вызовите {@link +android.app.Service#onStartCommand onStartCommand()} с последним намерением, которое было доставлено +в службу. Все ожидающие намерения доставляются по очереди. Это подходит для служб, +активно выполняющих задание, которое должно быть возобновлено немедленно, например, для загрузок файла.</dd> +</dl> +<p>Для получения дополнительных сведений об этих возвращаемых значениях см. справочную документацию по ссылке для каждой +константы.</p> + + + +<h3 id="StartingAService">Запуск службы</h3> + +<p>Можно запустить службу из операции или другого компонента приложения, передав объект +{@link android.content.Intent} (указывающий службу, которую требуется запустить) в {@link +android.content.Context#startService startService()}. Система Android вызывает метод {@link +android.app.Service#onStartCommand onStartCommand()} службы и передает ей {@link +android.content.Intent}. (Ни в коем случае не следует вызывать метод {@link android.app.Service#onStartCommand +onStartCommand()} напрямую).</p> + +<p>Например, операция может запустить службу из примера в предыдущем разделе ({@code +HelloSevice}), используя явное намерение с помощью {@link android.content.Context#startService +startService()}:</p> + +<pre> +Intent intent = new Intent(this, HelloService.class); +startService(intent); +</pre> + +<p>Метод {@link android.content.Context#startService startService()} возвращается немедленно, и система +Android вызывает метод службы {@link android.app.Service#onStartCommand +onStartCommand()}. Если служба еще не выполняется, система сначала вызывает {@link +android.app.Service#onCreate onCreate()}, а затем {@link android.app.Service#onStartCommand +onStartCommand()}.</p> + +<p>Если служба также не представляет привязку, намерение, доставляемое с помощью {@link +android.content.Context#startService startService()}, является единственным режимом связи между +компонентом приложения и службой. Однако, если вы хотите, чтобы служба оправляла результат обратно, +клиент, который запускает службу, может создать объект {@link android.app.PendingIntent} для сообщения +(с помощью {@link android.app.PendingIntent#getBroadcast getBroadcast()}) и доставить его в службу +в объекте {@link android.content.Intent}, который запускает службу. Затем служба может использовать +сообщение для доставки результата.</p> + +<p>Несколько запросов запуска службы приводят к нескольким соответствующим вызовам метода +{@link android.app.Service#onStartCommand onStartCommand()} службы. Однако для ее остановки достаточно только одного запроса на остановку +службы (с помощью {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}).</p> + + +<h3 id="Stopping">Остановка службы</h3> + +<p>Запущенная служба должна управлять своим жизненным циклом. То есть, система не останавливает и не +уничтожает службу, если не требуется восстановить память системы, и служба +продолжает работу после возвращения из метода {@link android.app.Service#onStartCommand onStartCommand()}. Поэтому +служба должна останавливаться самостоятельно посредством вызова метода {@link android.app.Service#stopSelf stopSelf()}, либо другой +компонент может остановить ее посредством вызова метода {@link android.content.Context#stopService stopService()}.</p> + +<p>Получив запрос на остановку посредством {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}, система как можно скорее уничтожает службу +.</p> + +<p>Однако, если служба обрабатывает несколько запросов {@link +android.app.Service#onStartCommand onStartCommand()} одновременно, вы не должны останавливать службу +после завершения обработки запроса запуска, поскольку вы, вероятно, уже получили новый +запрос запуска (остановка в конце первого запроса привела бы к прерыванию второго). Чтобы избежать +этой проблемы, вы можете использовать метод {@link android.app.Service#stopSelf(int)}, гарантирующий, что ваш запрос на +остановку службы всегда основан на самом последнем запросе запуска. То есть, когда вы вызываете {@link +android.app.Service#stopSelf(int)}, вы передаете идентификатор запроса запуска (идентификатор <code>startId</code>, +доставленный в {@link android.app.Service#onStartCommand onStartCommand()}), которому соответствует ваш +запрос остановки. Тогда, если служба получит новый запрос запуска до того, как вы сможете вызвать {@link +android.app.Service#stopSelf(int)}, идентификатор не будет совпадать и служба не будет остановлена.</p> + +<p class="caution"><strong>Внимание!</strong> Ваше приложение обязательно должно останавливать свои службы по окончании работы, +чтобы избежать расходования ресурсов системы и потребления энергии аккумулятора. При необходимости +другие компоненты могут остановить службу посредством вызова метода {@link +android.content.Context#stopService stopService()}. Даже если вы можете выполнять привязку службы, +следует всегда останавливать службу самостоятельно, если она когда-либо получила вызов {@link +android.app.Service#onStartCommand onStartCommand()}.</p> + +<p>Дополнительные сведения о жизненном цикле службы представлены в разделе <a href="#Lifecycle">Управление жизненным циклом службы</a> ниже.</p> + + + +<h2 id="CreatingBoundService">Создание привязанной службы</h2> + +<p>Привязанная служба — это служба, которая допускает привязку к ней компонентов приложения посредством вызова {@link +android.content.Context#bindService bindService()} для создания долговременного соединения +(и обычно не позволяет компонентам <em>запускать</em> ее посредством вызова {@link +android.content.Context#startService startService()}).</p> + +<p>Вы должны создать привязанную службу, когда вы хотите взаимодействовать со службой из операций +и других компонентов вашего приложения или показывать некоторые функции вашего приложения +другим приложениям посредством межпроцессного взаимодействия (IPC).</p> + +<p>Чтобы создать привязанную службу, необходимо реализовать метод обратного вызова {@link +android.app.Service#onBind onBind()} для возвращения объекта {@link android.os.IBinder}, +который определяет интерфейс взаимодействия со службой. После этого другие компоненты приложения могут вызвать +метод {@link android.content.Context#bindService bindService()} для извлечения интерфейса и +начать вызывать методы службы. Служба существует только для обслуживания привязанного к ней компонента приложения, +поэтому, когда нет компонентов, привязанных к службе, система уничтожает ее +(вам <em>не</em> требуется останавливать привязанную службу, как это требуется для службы, запущенной +посредством {@link android.app.Service#onStartCommand onStartCommand()}).</p> + +<p>Чтобы создать привязанную службу, необходимо в первую очередь определить интерфейс, взаимодействия +клиента со службой. Этот интерфейс между службой +и клиентом должен быть реализацией объекта {@link android.os.IBinder}, которую ваша служба должна +возвращать из метода обратного вызова {@link android.app.Service#onBind +onBind()}. После того, как клиент получает объект {@link android.os.IBinder}, он может начать +взаимодействие со службой посредством этого интерфейса.</p> + +<p>Одновременно к службе могут быть привязаны несколько клиентов. Когда клиент заканчивает взаимодействие +со службой, он вызывает {@link android.content.Context#unbindService unbindService()} для отмены привязки. Как только +не остается ни одного клиента, привязанного к службе, система уничтожает службу.</p> + +<p>Существует несколько способов реализации привязанной службы, и эти реализации сложнее, +чем реализации запущенной службы, поэтому обсуждение привязанной службы приведено в отдельном +документе <a href="{@docRoot}guide/components/bound-services.html">Привязанные службы</a>.</p> + + + +<h2 id="Notifications">Отправка уведомлений пользователю</h2> + +<p>После запуска служба может уведомлять пользователя о событиях, используя <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Всплывающие уведомления</a> или <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Уведомления в строке состояния</a>.</p> + +<p>Всплывающее уведомление — это сообщение, кратковременно появляющееся на поверхности текущего окна, +тогда как уведомление в строке состояния — это значок в строке состояния с сообщением, +который пользователь может выбрать, чтобы выполнить действие (такое как запуск операции).</p> + +<p>Обычно уведомление в строке состояния является самым удобным решением, когда завершается какая-то фоновая работа +(например, завершена +загрузка файла), и пользователь может действовать. Когда пользователь выбирает уведомление в +расширенном виде, уведомление может запустить операцию (например, для просмотра загруженного файла).</p> + +<p>Дополнительную информацию см. в руководствах для разработчиков <a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Всплывающие уведомления</a> и<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html"> +Уведомления в строке состояния</a>.</p> + + + +<h2 id="Foreground">Запуск службы на переднем плане</h2> + +<p>Служба переднего плана — это служба, о которой пользователь активно +осведомлен, и поэтому она не является кандидатом для удаления системой в случае нехватки памяти. Служба +переднего плана должна выводить уведомление в строку состояния, которая находится под заголовком +«Постоянные». Это означает, что уведомление не может быть удалено, пока служба +не будет остановлена или удалена с переднего плана.</p> + +<p>Например, музыкальный проигрыватель, который воспроизводит музыку из службы, должен быть настроен на работу +на переднем плане, так как пользователь точно знает о +его работе. Уведомление в строке состояния может показывать текущее произведение и позволять пользователю +запускать операцию для взаимодействия с музыкальным проигрывателем.</p> + +<p>Для запроса на выполнение вашей службы на переднем плане вызовите метод {@link +android.app.Service#startForeground startForeground()}. Этот метод имеет два параметра: целое число, +которое однозначно идентифицирует уведомление и объект {@link +android.app.Notification} для строки состояния. Например:</p> + +<pre> +Notification notification = new Notification(R.drawable.icon, getText(R.string.ticker_text), + System.currentTimeMillis()); +Intent notificationIntent = new Intent(this, ExampleActivity.class); +PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); +notification.setLatestEventInfo(this, getText(R.string.notification_title), + getText(R.string.notification_message), pendingIntent); +startForeground(ONGOING_NOTIFICATION_ID, notification); +</pre> + +<p class="caution"><strong>Внимание!</strong> Целочисленный идентификатор ID, который вы передаете в метод {@link +android.app.Service#startForeground startForeground()}, не должен быть равен 0.</p> + + +<p>Чтобы удалить службу с переднего плана, вызовите {@link +android.app.Service#stopForeground stopForeground()}. Этот метод содержит логическое значение, указывающее, +следует ли также удалять уведомление в строке состояния. Этот метод <em>не</em> останавливает +службу. Однако, если вы останавливаете службу, работающую на переднем плане, +уведомление также удаляется.</p> + +<p>Дополнительную информацию об уведомлениях см. в разделе <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Создание уведомлений +в строке состояния</a>.</p> + + + +<h2 id="Lifecycle">Управление жизненным циклом службы</h2> + +<p>Жизненный цикл службы намного проще, чем жизненный цикл операции. Однако, намного важнее +уделить пристальное внимание тому, как ваша служба создается и уничтожается, так как служба +может работать в фоновом режиме без ведома пользователя.</p> + +<p>Жизненный цикл службы от создания до уничтожения может следовать двум +разным путям:</p> + +<ul> +<li>Запущенная служба + <p>Служба создается, когда другой компонент вызывает метод {@link +android.content.Context#startService startService()}. Затем служба работает в течение неограниченного времени и должна +остановиться самостоятельно посредством вызова метода {@link +android.app.Service#stopSelf() stopSelf()}. Другой компонент также может остановить службу +посредством вызова метода {@link android.content.Context#stopService +stopService()}. Когда служба останавливается, система уничтожает ее.</p></li> + +<li>Привязанная служба + <p>Служба создается, когда другой компонент (клиент) вызывает метод {@link +android.content.Context#bindService bindService()}. Затем клиент взаимодействует со службой +через интерфейс {@link android.os.IBinder}. Клиент может закрыть соединение посредством вызова +метода {@link android.content.Context#unbindService unbindService()}. К одной службе могут быть привязано +несколько клиентов, и когда все они отменяют привязку, система уничтожает службу. (Служба +<em>не</em> должна останавливаться самостоятельно.)</p></li> +</ul> + +<p>Эти два способа необязательно работают независимо друг от друга. То есть вы можете привязать службу, которая уже была +запущена посредством метода {@link android.content.Context#startService startService()}. Например, фоновая +музыкальная служба может быть запущена посредством вызова метода {@link android.content.Context#startService +startService()} с объектом {@link android.content.Intent}, который идентифицирует музыку для воспроизведения. Позже, +например, когда пользователь хочет получить доступ к управлению проигрывателем или информацию о текущем произведении, +операция может установить привязку к службе посредством вызова метода {@link +android.content.Context#bindService bindService()}. В подобных случаях методы {@link +android.content.Context#stopService stopService()} и {@link android.app.Service#stopSelf +stopSelf()} фактически не останавливают службу, пока не будет отменена привязка всех клиентов. </p> + + +<h3 id="LifecycleCallbacks">Реализация обратных вызовов жизненного цикла</h3> + +<p>Подобно операции, служба содержит методы обратного вызова жизненного цикла, которые можно реализовать для контроля +изменений состояния службы и выполнения работы в соответствующие моменты времени. Указанная ниже базовая +служба показывает каждый из методов жизненного цикла.</p> + +<pre> +public class ExampleService extends Service { + int mStartMode; // indicates how to behave if the service is killed + IBinder mBinder; // interface for clients that bind + boolean mAllowRebind; // indicates whether onRebind should be used + + @Override + public void {@link android.app.Service#onCreate onCreate}() { + // The service is being created + } + @Override + public int {@link android.app.Service#onStartCommand onStartCommand}(Intent intent, int flags, int startId) { + // The service is starting, due to a call to {@link android.content.Context#startService startService()} + return <em>mStartMode</em>; + } + @Override + public IBinder {@link android.app.Service#onBind onBind}(Intent intent) { + // A client is binding to the service with {@link android.content.Context#bindService bindService()} + return <em>mBinder</em>; + } + @Override + public boolean {@link android.app.Service#onUnbind onUnbind}(Intent intent) { + // All clients have unbound with {@link android.content.Context#unbindService unbindService()} + return <em>mAllowRebind</em>; + } + @Override + public void {@link android.app.Service#onRebind onRebind}(Intent intent) { + // A client is binding to the service with {@link android.content.Context#bindService bindService()}, + // after onUnbind() has already been called + } + @Override + public void {@link android.app.Service#onDestroy onDestroy}() { + // The service is no longer used and is being destroyed + } +} +</pre> + +<p class="note"><strong>Примечание.</strong> В отличие от методов обратного вызова жизненного цикла операции, вам +<em>не</em> требуется вызывать реализацию суперкласса этих методов обратного вызова.</p> + +<img src="{@docRoot}images/service_lifecycle.png" alt="" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Жизненный цикл службы. На схеме слева +показан жизненный цикл, когда служба создана посредством метода {@link android.content.Context#startService +startService()}, а на схеме справа показан жизненный цикл, когда служба создана +посредством метода {@link android.content.Context#bindService bindService()}.</p> + +<p>С помощью реализации этих методов можно отслеживать два вложенных цикла в жизненном цикле службы: </p> + +<ul> +<li><strong>Весь жизненный цикл</strong> службы происходит между вызовом метода {@link +android.app.Service#onCreate onCreate()} и возвратом из метода {@link +android.app.Service#onDestroy}. Подобно операции, служба выполняет начальную настройку в методе +{@link android.app.Service#onCreate onCreate()} и освобождает все оставшиеся ресурсы в методе {@link +android.app.Service#onDestroy onDestroy()}. Например, +служба воспроизведения музыки может создать поток для воспроизведения музыки в методе {@link +android.app.Service#onCreate onCreate()}, затем остановить поток в методе {@link +android.app.Service#onDestroy onDestroy()}. + +<p>Методы {@link android.app.Service#onCreate onCreate()} и {@link android.app.Service#onDestroy +onDestroy()} вызываются для всех служб, независимо от метода создания: +{@link android.content.Context#startService startService()} или {@link +android.content.Context#bindService bindService()}.</p></li> + +<li><strong>Активный жизненный цикл</strong> службы начинается с вызова метода {@link +android.app.Service#onStartCommand onStartCommand()} или {@link android.app.Service#onBind onBind()}. +Каждый метод направляется намерением {@link +android.content.Intent}, которое было передано методу {@link android.content.Context#startService +startService()} или {@link android.content.Context#bindService bindService()}, соответственно. +<p>Если служба запущена, активный жизненный цикл заканчивается одновременно с окончанием +всего жизненного цикла (служба активна даже после возврата из метода {@link android.app.Service#onStartCommand +onStartCommand()}). Если служба является привязанной, активный жизненный цикл заканчивается, когда возвращается метод {@link +android.app.Service#onUnbind onUnbind()}.</p> +</li> +</ul> + +<p class="note"><strong>Примечание.</strong> Хотя запущенная служба останавливается посредством вызова +метода {@link android.app.Service#stopSelf stopSelf()} или {@link +android.content.Context#stopService stopService()}, для службы не существует соответствующего обратного вызова +(нет обратного вызова {@code onStop()}). Поэтому, если служба не привязана к клиенту, +система уничтожает ее при остановке службы — метод {@link +android.app.Service#onDestroy onDestroy()} является единственным получаемым методом обратного вызова.</p> + +<p>Рисунок 2 иллюстрирует типичные методы обратного вызова для службы. Хотя на рисунке отделены +службы, созданные посредством метода {@link android.content.Context#startService startService()}, от служб, +созданных посредством метода {@link android.content.Context#bindService bindService()}, помните, +что любая служба, независимо от способа запуска, позволяет клиентам выполнять привязку к ней. +Поэтому служба, изначально созданная посредством метода {@link android.app.Service#onStartCommand +onStartCommand()} (клиентом, который вызвал {@link android.content.Context#startService startService()}), +может получать вызов метода {@link android.app.Service#onBind onBind()} (когда клиент вызывает +метод {@link android.content.Context#bindService bindService()}).</p> + +<p>Дополнительные сведения о создании службы, которая обеспечивает привязку, см. в документе <a href="{@docRoot}guide/components/bound-services.html">Привязанные службы</a>, +который содержит дополнительную информацию о методе обратного вызова {@link android.app.Service#onRebind onRebind()} +в разделе <a href="{@docRoot}guide/components/bound-services.html#Lifecycle">Управление жизненным циклом +привязанной службы</a>.</p> + + +<!-- +<h2>Beginner's Path</h2> + +<p>To learn how to query data from the system or other applications (such as contacts or media +stored on the device), continue with the <b><a +href="{@docRoot}guide/topics/providers/content-providers.html">Content Providers</a></b> +document.</p> +--> diff --git a/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd new file mode 100644 index 000000000000..c9fdc0e069bb --- /dev/null +++ b/docs/html-intl/intl/ru/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=Задачи и стек переходов назад +parent.title=Операции +parent.link=activities.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> +<li><a href="#ActivityState">Сохранение состояния операции</a></li></li> +<li><a href="#ManagingTasks">Управление задачами</a> + <ol> + <li><a href="#TaskLaunchModes">Определение режимов запуска</a></li> + <li><a href="#Affinities">Обработка привязок</a></li> + <li><a href="#Clearing">Очистка стека переходов назад</a></li> + <li><a href="#Starting">Запуск задачи</a></li> + </ol> +</li> +</ol> + +<h2>Статьи</h2> +<ol> + <li><a href="http://android-developers.blogspot.com/2010/04/multitasking-android-way.html"> +Многозадачность в системе Android</a></li> +</ol> + +<h2>См. также:</h2> +<ol> + <li><a href="{@docRoot}design/patterns/navigation.html">Дизайн Android: +навигация</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html">Элемент манифеста +{@code <activity>}</a></li> + <li><a href="{@docRoot}guide/components/recents.html">Экран обзора</a></li> +</ol> +</div> +</div> + + +<p>Обычно приложение содержит несколько <a href="{@docRoot}guide/components/activities.html">операций</a>. Каждая операция +должна разрабатываться в связи с действием определенного типа, которое пользователь может выполнять, и может запускать другие +операции. Например, приложение электронной почты может содержать одну операцию для отображения списка новых сообщений. +Когда пользователь выбирает сообщение, открывается новая операция для просмотра этого сообщения.</p> + +<p>Операция может даже запускать операции, существующие в других приложениях на устройстве. Например, +если ваше приложение хочет отправить сообщение электронной почты, вы можете определить намерение для выполнения +действия «отправить» и включить в него некоторые данные, например, адрес электронной почты и текст сообщения. После этого открывается операция из другого +приложения, которая объявила, что она обрабатывает намерения такого типа. В этом случае намерение состоит в том, чтобы +отправить сообщение электронной почты, поэтому в приложении электронной почты запускается операция «составить сообщение» (если одно намерение +может обрабатываться несколькими операциями, система предлагает пользователю выбрать, какую из операций использовать). После отправки сообщения электронной почты +ваша операция возобновляет работу, и все выглядит так, будто операция отправки электронной почты является частью вашего приложения. Хотя +операции могут быть частями разных приложений, система Android поддерживает удобство работы +пользователя, сохраняя обе операции в одной <em>задаче</em>.</p> + +<p>Задача — это коллекция операций, с которыми взаимодействует пользователь +при выполнении определенного задания. Операции упорядочены в виде стека (<em>стека переходов назад</em>), в том +порядке, в котором открывались операции.</p> + +<!-- SAVE FOR WHEN THE FRAGMENT DOC IS ADDED +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h3>Adding fragments to a task's back stack</h3> + +<p>Your activity can also include {@link android.app.Fragment}s to the back stack. For example, +suppose you have a two-pane layout using fragments, one of which is a list view (fragment A) and the +other being a layout to display an item from the list (fragment B). When the user selects an item +from the list, fragment B is replaced by a new fragment (fragment C). In this case, it might be +desireable for the user to navigate back to reveal fragment B, using the <em>Back</em> button.</p> +<p>In order to add fragment B to the back stack so that this is possible, you must call {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()} before you {@link +android.app.FragmentTransaction#commit()} the transaction that replaces fragment B with fragment +C.</p> +<p>For more information about using fragments and adding them to the back stack, see the {@link +android.app.Fragment} class documentation.</p> + +</div> +</div> +--> + +<p>Начальным местом для большинства задач является главный экран устройства. Когда пользователь касается значка в средстве +запуска +приложений (или ярлыка на главном экране), эта задача приложения переходит на передний план. Если для +приложения нет задач (приложение не использовалось в последнее время), тогда создается новая задача +и открывается «основная» операция для этого приложения в качестве корневой операции в стеке.</p> + +<p>Когда текущая операция запускает другую, новая операция помещается в вершину стека +и получает фокус. Предыдущая операция остается в стеке, но ее выполнение останавливается. Когда операция останавливается +, система сохраняет текущее состояние ее пользовательского интерфейса. Когда пользователь нажимает кнопку +<em>Назад</em>, +текущая операция удаляется из вершины стека (операция уничтожается) и возобновляется +работа предыдущей операции (восстанавливается предыдущее состояние ее пользовательского интерфейса). Операции в стеке +никогда не переупорядочиваются, только добавляются в стек и удаляются из него — добавляются в стек при запуске текущей операцией +и удаляются, когда пользователь выходит из нее с помощью кнопки <em>Назад</em>. По существу, +стек +переходов назад работает по принципу «последним вошел — первым вышел». На рисунке 1 это поведение +показано на временной шкале: состояние операций и текущее состояние стека переходов назад + показано в каждый момент времени.</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Иллюстрация того, как каждая новая операция в задаче +добавляет элемент в стек переходов назад. Когда пользователь нажимает кнопку<em>Назад</em>, текущая +операция +уничтожается, и возобновляется работа предыдущей операции.</p> + + +<p>Если пользователь продолжает нажимать кнопку <em>Назад</em>, операции поочередно удаляются из стека, +открывая +предыдущую операцию, пока пользователь не вернется на главный экран (или в операцию, которая была запущена +в начале выполнения задачи). Когда все операции удалены из стека, задача прекращает существование.</p> + +<div class="figure" style="width:287px"> +<img src="{@docRoot}images/fundamentals/diagram_multitasking.png" alt="" /> <p +class="img-caption"><strong>Рисунок 2.</strong> Две задачи: Задача B взаимодействует с пользователем +на переднем плане, тогда как Задача A находится в фоновом режиме, ожидая возобновления.</p> +</div> +<div class="figure" style="width:215px"> + <img src="{@docRoot}images/fundamentals/diagram_multiple_instances.png" alt="" /> <p +class="img-caption"><strong>Рисунок 3.</strong> Создается несколько экземпляров одной операции.</p> +</div> + +<p>Задача — это связанный блок, который может переходить в фоновый режим, когда пользователи начинают новую задачу или переходят +на главный экран с помощью кнопки <em>Домой</em>. В фоновом режиме все операции +задачи +остановлены, но стек обратного вызова для задачи остается неизменным. Задача просто потеряла фокус во время +выполнения другой задачи, как показано на рисунке 2. Затем задача может вернуться на передний план, так что пользователи +могут продолжить ее с прерванного места. Предположим, например, что текущая задача (Задача A) содержит три операции +в своем стеке — две операции под текущей операцией. Пользователь нажимает кнопку <em>Домой</em>, + затем запускает +новое приложение из средства запуска приложений. Когда появляется главный экран, Задача A переходит +в фоновый режим. Когда запускается новое приложение, система запускает задачу для этого приложения +(Задачу B) со своим собственным стеком операций. После взаимодействия с этим +приложением пользователь снова возвращается на главный экран и выбирает изначально запущенную +Задачу A. Теперь Задача A переходит на передний +план — все три операции ее стека остались неизменными, и возобновляется операция, находящаяся на +вершине стека. В этот +момент пользователь может также переключиться обратно на Задачу B, перейдя на главный экран и выбрав значок приложения, +которое запустило эту задачу (или выбрав задачу приложения на +<a href="{@docRoot}guide/components/recents.html">экране обзора</a>). +Это пример многозадачности в системе Android.</p> + +<p class="note"><strong>Примечание.</strong> В фоновом режиме может находиться несколько задач одновременно. +Однако, если пользователь запускает много фоновых задач одновременно, система может начать +уничтожение фоновых операций для освобождения памяти, что приведет к потере состояния задач. +См. следующий раздел <a href="#ActivityState">Состояние операции</a>.</p> + +<p>Поскольку операции в стеке никогда не переупорядочиваются, если ваше приложение позволяет +пользователям запускать определенную операцию из нескольких операций, новый экземпляр +такой операции создается и помещается в стек (вместо помещения любого из предыдущих экземпляров +операции на вершину стека). По существу, для одной операции вашего приложения может быть создано несколько +экземпляров (даже из разных задач), как показано на рисунке 3. Поэтому, если пользователь переходит назад с помощью +кнопки <em>Назад</em>, каждый экземпляр операции появляется в том порядке, в котором они +были открыты (каждый +со своим состоянием пользовательского интерфейса). Однако вы можете изменить это поведение, если вы не хотите, чтобы создавалось несколько +экземпляров операции. Это описано в разделе <a href="#ManagingTasks">Управление задачами</a> ниже.</p> + + +<p>Подведем итоги поведения операций и задач:</p> + +<ul> + <li>Когда Операция A запускает Операцию B, Операция A останавливается, но система сохраняет ее состояние +(например, положение прокрутки и текст, введенный в формы). +Если пользователь нажимает кнопку <em>Назад</em> в Операции B, Операция A возобновляет работу +из сохраненного состояния.</li> + <li>Когда пользователь выходит из задачи нажатием кнопки <em>Домой</em>, текущая операция +останавливается и +ее задача переводится в фоновый режим. Система сохраняет состояние каждой операции в задаче. Если +пользователь впоследствии возобновляет задачу, выбирая значок запуска задачи, она переводится +на передний план и возобновляет операцию на вершине стека.</li> + <li>Если пользователь нажимает кнопку <em>Назад</em>, текущая операция удаляется из стека +и +уничтожается. Возобновляется предыдущая операция в стеке. Когда операция уничтожается, система +<em>не</em> сохраняет состояние операции.</li> + <li>Можно создавать несколько экземпляров операции, даже из других задач.</li> +</ul> + + +<div class="note design"> +<p><strong>Дизайн навигации</strong></p> + <p>Для получения дополнительной информации о работе навигации в приложении Android, прочитайте раздел <a href="{@docRoot}design/patterns/navigation.html">Навигация</a> руководства «Дизайн для Android».</p> +</div> + + +<h2 id="ActivityState">Сохранение состояния операции</h2> + +<p>Как говорилось выше, система по умолчанию сохраняет состояние операции, когда она +останавливается. Таким образом, когда пользователи возвращаются обратно в предыдущую операцию, восстанавливается ее пользовательский интерфейс +в момент остановки. Однако вы можете — и <strong>должны</strong> — с упреждением сохранять +состояние ваших операций посредством методов обратного вызова на случай уничтожения операции и необходимости ее +повторного создания.</p> + +<p>Когда система останавливает одну из ваших операций (например, когда запускается новая операция или задача +перемещается в фоновый режим), система может полностью уничтожить эту операцию, если необходимо восстановить +память системы. Когда это происходит, информация о состоянии операции теряется. Если это происходит, +система +знает, что операция находится в стеке переходов назад, но когда операция переходит +на вершину стека, система должна создать ее повторно (а не возобновить ее). Чтобы избежать +потери работы пользователя, вы должны с упреждением сохранять ее путем реализации методов +обратного вызова {@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +в вашей операции.</p> + +<p>Дополнительную информацию о сохранении состояния вашей операции см. в документе +<a href="{@docRoot}guide/components/activities.html#SavingActivityState">Операции</a>.</p> + + + +<h2 id="ManagingTasks">Управление задачами</h2> + +<p>Для большинства приложений способ, которым Android управляет задачами и стеком переходов назад, описанный выше, — помещение всех +операций последовательно в одну задачу в стек «последним вошёл — первым вышел», — +работает хорошо, и вы не должны беспокоиться о связи ваших операций с задачами +или об их существовании в стеке переходов назад. Однако вы можете решить, что вы хотите прервать +обычное поведение. Возможно, вы хотите, чтобы операция в вашем приложении начинала новую задачу +при запуске (вместо помещения в текущую задачу), или при запуске операции вы хотите +перенести на передний план ее существующий экземпляр (вместо создания нового +экземпляра на вершине стека переходов назад), или вы хотите чтобы при выходе пользователя из задачи из вашего стек переходов удалялись все +операции, кроме корневой операции.</p> + +<p>Вы можете совершать эти и многие другие действия с помощью атрибутов в элементе манифеста +<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +и с помощью флагов в намерении, которое вы передаете в +{@link android.app.Activity#startActivity startActivity()}.</p> + +<p>В этом смысле главными атрибутами <a href="{@docRoot}guide/topics/manifest/activity-element.html"> +{@code <activity>}</a>, которые вы можете использовать, являются следующие:</p> + +<ul class="nolist"> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff"> + {@code taskAffinity}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode"> + {@code launchMode}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent"> + {@code allowTaskReparenting}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#clear"> + {@code clearTaskOnLaunch}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#always"> + {@code alwaysRetainTaskState}</a></li> + <li><a href="{@docRoot}guide/topics/manifest/activity-element.html#finish"> + {@code finishOnTaskLaunch}</a></li> +</ul> + +<p>А главными флагами намерений, которые вы можете использовать, являются следующие:</p> + +<ul class="nolist"> + <li>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</li> + <li>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</li> + <li>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</li> +</ul> + +<p>В следующих разделах показано, как можно использовать эти атрибуты манифеста и флаги +намерений для определения связи операций с задачами и их поведения в стеке переходов назад.</p> + +<p>Кроме того, отдельно обсуждаются рекомендации о представлении задач и операций и управлении ими +на экране обзора. Дополнительную информацию см. в разделе +<a href="{@docRoot}guide/components/recents.html">Экран обзора</a>. Обычно следует разрешить системе определить способ представления вашей задачи и +операций на экране обзора. Вам не нужно менять это поведение.</p> + +<p class="caution"><strong>Внимание!</strong> В большинстве приложений не следует прерывать поведение +операций и задач по умолчанию. Если вы обнаружили, что вашей операции необходимо изменить +поведение по умолчанию, будьте внимательны и протестируйте удобство работы с операцией во время +запуска и при обратной навигации к ней из других операций и задач с помощью кнопки <em>Назад</em>. +Обязательно протестируйте поведение навигации, которое может противоречить поведению, ожидаемому пользователем.</p> + + +<h3 id="TaskLaunchModes">Определение режимов запуска</h3> + +<p>Режимы запуска позволяют вам определять связь нового экземпляра операции +с текущей задачей. Вы можете задавать различные режимы запуска двумя способами:</p> +<ul class="nolist"> + <li><a href="#ManifestForTasks">Использование файла манифеста</a> + <p>Когда вы объявляете операцию в вашем файле манифеста, вы можете указать, как операция +должна связываться с задачами при ее запуске.</li> + <li><a href="#IntentFlagsForTasks">Использование флагов намерений</a> + <p>Когда вы вызываете {@link android.app.Activity#startActivity startActivity()}, +вы можете включить флаг в {@link android.content.Intent}, который объявляет, как должна быть связана +новая операция с текущей задачей (и должна ли).</p></li> +</ul> + +<p>По существу, если Операция A запускает Операцию B, Операция B может определить в своем манифесте, как она +должна быть связана с текущей задачей (если вообще должна), а Операция A может также запросить, как Операция B +должна быть связана с текущей задачей. Если обе операции определяют, как Операция B +должна быть связана с задачей, тогда запрос Операции A (как определено в намерении) обрабатывается через +запрос Операции B (как определено в ее манифесте).</p> + +<p class="note"><strong>Примечание.</strong> Некоторые режимы запуска, доступные для файла манифеста, +недоступны в виде флагов для намерения и, аналогичным образом, некоторые режимы запуска, доступные в виде флагов +для намерения, не могут быть определены в манифесте.</p> + + +<h4 id="ManifestForTasks">Использование файла манифеста</h4> + +<p>При объявлении операции в вашем файле манифеста вы можете указать, как операция должна +быть связана с задачей посредством атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> +элемента <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>.</p> + +<p>Атрибут <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> указывает инструкцию по запуску операции в +задаче. Существует четыре различных режима запуска, +которые вы можете назначить атрибуту +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code>:</p> + +<dl> +<dt>{@code "standard"} (режим по умолчанию)</dt> + <dd>Режим по умолчанию. Система создает новый экземпляр операции в задаче, из +которой она была запущена, и направляет ему намерение. Может быть создано несколько экземпляров операции, +каждый экземпляр может принадлежать различным задачам, и одна задача может содержать несколько экземпляров.</dd> +<dt>{@code "singleTop"}</dt> + <dd>Если экземпляр операции уже существует на вершине текущей задачи, система +направляет намерение в этот экземпляр путем вызова его метода {@link +android.app.Activity#onNewIntent onNewIntent()}, а не путем создания нового экземпляра +операции. Может быть создано несколько экземпляров операции, каждый экземпляр может +принадлежать различным задачам, и одна задача может содержать несколько экземпляров (но только если +операция на вершине стека переходов назад <em>не</em> является существующим экземпляром операции). + <p>Предположим, что стек переходов назад задачи состоит из корневой операции A с операциями B, C +и D на вершине (стек имеет вид A-B-C-D и D находится на вершине). Поступает намерение для операции типа D. +Если D имеет режим запуска {@code "standard"} по умолчанию, запускается новый экземпляр класса и +стек принимает вид A-B-C-D-D. Однако, если D имеет режим запуска {@code "singleTop"}, существующий экземпляр +D получает намерение через {@link +android.app.Activity#onNewIntent onNewIntent()}, так как этот экземпляр находится на вершине стека — +стек сохраняет вид A-B-C-D. Однако, если поступает намерение для операции типа B, тогда в стек +добавляется новый экземпляр B, даже если он имеет режим запуска {@code "singleTop"}.</p> + <p class="note"><strong>Примечание.</strong> Когда создается новый экземпляр операции, +пользователь может нажать кнопку <em>Назад</em> для возврата к предыдущей операции. Но когда существующий +экземпляр +операции обрабатывает новое намерение, пользователь не может нажать кнопку <em>Назад</em> для возврата к +состоянию +операции до поступления нового намерения в {@link android.app.Activity#onNewIntent +onNewIntent()}.</p> +</dd> + +<dt>{@code "singleTask"}</dt> + <dd>Система создает новую задачу и создает экземпляр операции в корне новой задачи. +Однако, если экземпляр операции уже существует в отдельной задаче, система направляет +намерение в существующий экземпляр путем вызова его метода {@link +android.app.Activity#onNewIntent onNewIntent()}, а не путем создания нового экземпляра. Одновременно +может существовать только один экземпляр операции. + <p class="note"><strong>Примечание.</strong> Хотя операция запускает новую задачу, кнопка +<em>Назад</em> возвращает пользователя к предыдущей операции.</p></dd> +<dt>{@code "singleInstance"}.</dt> + <dd>То же, что и {@code "singleTask"}, но при этом система не запускает никаких других операций +в задаче, содержащей этот экземпляр. Операция всегда является единственным членом своей задачи; +любые операции, запущенные этой операцией, открываются в отдельной задаче.</dd> +</dl> + + +<p>В качестве другого примера: приложение Android Browser объявляет, что операция веб-браузера должна +всегда открываться в своей собственной задаче — путем указания режима запуска {@code singleTask} в элементе <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>. +Это означает, что если ваше приложение выдает +намерение открыть Android Browser, его операция <em>не</em> помещается в ту же +задачу, что и ваше приложение. Вместо этого, либо для браузера запускается новая задача, либо, если браузер уже +имеет задачу, работающую в фоновом режиме, эта задача переводится на передний план для обработки нового +намерения.</p> + +<p>И при запуске операции в новой задаче, и при запуске операции в существующей задаче, + кнопка <em>Назад</em> всегда возвращает пользователя к предыдущей операции. Однако, если вы +запускаете операцию, которая указывает режим запуска {@code singleTask}, вся задача переводится на передний план, если экземпляр +этой операции существует в фоновой задаче. В этот момент +стек переходов назад помещает все операции из задачи, переведенной на передний план, на вершину +стека. Рисунок 4 иллюстрирует сценарий этого типа.</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" /> +<p class="img-caption"><strong>Рисунок 4.</strong> Представление того, как операция с режимом +запуска singleTask добавляется в стек переходов назад. Если операция уже является частью +фоновой задачи со своим собственным стеком переходов назад, то весь стек переходов назад также переносится вверх, +на вершину текущей задачи.</p> + +<p>Дополнительную информацию об использовании режимов запуска в файле манифеста см. в документации элемента +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>, + где более подробно обсуждаются атрибут {@code launchMode} +и принимаемые значения.</p> + +<p class="note"><strong>Примечание.</strong> Поведение, которое вы указываете для вашей операции с помощью атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>, +может быть переопределено флагами, включенными в намерение, которое запускает вашу операцию, как описано +в следующем разделе.</p> + + + +<h4 id="#IntentFlagsForTasks">Использование флагов намерений</h4> + +<p>При запуске операции вы можете изменить связывание операции с ее задачей по умолчанию +путем включения флагов в намерение, которое доставляется в {@link +android.app.Activity#startActivity startActivity()}. Для изменения поведения по умолчанию +вы можете использовать следующие флаги:</p> + +<p> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt> + <dd>Запуск операции в новой задаче. Если задача уже работает для операции, которую вы запускаете +сейчас, эта задача переводится на передний план, ее последнее состояние восстанавливается, и операция получает +новое намерение в {@link android.app.Activity#onNewIntent onNewIntent()}. + <p>Это приводит к тому же поведению, что и значение <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> в режиме {@code "singleTask"}, +как описано в предыдущем разделе.</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt> + <dd>Если запускаемая операция является текущей операцией (находится на вершине стека переходов назад), тогда +вызов в {@link android.app.Activity#onNewIntent onNewIntent()} получает существующий экземпляр, + без создания нового экземпляра операции. + <p>Это приводит к тому же поведению, что и значение <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> в режиме {@code "singleTop"}, +как описано в предыдущем разделе.</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt> + <dd>Если запускаемая операция уже работает в текущей задаче, тогда вместо +запуска нового экземпляра этой операции уничтожаются все другие операции, расположенные в стеке выше нее +, и это намерение доставляется в возобновленный экземпляр этой операции (которая теперь находится на вершине стека) +посредством {@link android.app.Activity#onNewIntent onNewIntent()}). + <p>Для формирования такого поведения не существует значения для атрибута +<a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a>.</p> + <p>Флаг {@code FLAG_ACTIVITY_CLEAR_TOP} чаще всего используется совместно с +флагом {@code FLAG_ACTIVITY_NEW_TASK}. +При использовании вместе эти флаги позволяют найти существующую операцию +в другой задаче и поместить ее в положение, где она сможет реагировать на намерение. </p> + <p class="note"><strong>Примечание.</strong> Если для назначенной операции установлен режим запуска +{@code "standard"}, +она также удаляется из стека и на ее месте запускается новый экземпляр, чтобы обработать +входящее намерение. Именно поэтому в режиме запуска {@code "standard"} всегда создается новый +экземпляр для нового намерения. </p> +</dd> +</dl> + + + + + +<h3 id="Affinities">Обработка привязок</h3> + +<p><em>Привязка</em> указывает предпочтительную принадлежность операции к задаче. По умолчанию все +операции из одного приложения имеют привязку друг к другу. Поэтому по умолчанию все +операции одного приложения предпочитают находиться в одной задаче. Однако вы можете изменить +привязку по умолчанию для операции. Операции, определенные +в разных приложениях, могут совместно использовать одну привязку; таким же образом операции, определенные в одном приложении, могут получить +привязки к разным задачам.</p> + +<p>Вы можете изменить привязку любой данный операции с помощью +атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> +элемента <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a>.</p> + +<p>Атрибут <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> +принимает строковое значение, которое должно отличаться от имени пакета по умолчанию, +объявленного в элементе <a href="{@docRoot}guide/topics/manifest/manifest-element.html"> +{@code <manifest>} +</a>, поскольку система использует это имя для идентификации привязки задачи по умолчанию +для приложения.</p> + +<p>Привязка вступает в игру в двух случаях:</p> +<ul> + <li>Когда намерение, запускающее +операцию, содержит флаг +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}. + +<p>Новая операция по умолчанию запускается в задаче той операции, +которая вызвала {@link android.app.Activity#startActivity startActivity()}. Она помещается в тот же +стек переходов назад, что и вызывающая операция. Однако, если намерение, переданное +в {@link android.app.Activity#startActivity startActivity()}, +содержит флаг {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}, + система ищет другую задачу для помещения новой операции. Часто это новая задача. +Но необязательно. Если уже существует задача с той же привязкой, что +и у новой операции, операция запускается в этой задаче. Если нет, операция начинает новую задачу.</p> + +<p>Если этот флаг приводит к тому, что операция начинает новую задачу, и пользователь нажимает кнопку <em>Домой</em> +для выхода из нее, +должен существовать способ, позволяющий пользователю вернуться к задаче. Некоторые объекты (такие как +диспетчер уведомлений) всегда запускают операции во внешней задаче, а не в составе собственной, поэтому +они всегда помещают флаг {@code FLAG_ACTIVITY_NEW_TASK} в намерения, которые они передают +в {@link android.app.Activity#startActivity startActivity()}. +Если у вас есть операция, которую можно вызвать +внешним объектом, использующим этот флаг, позаботьтесь о том, чтобы у пользователя был независимый способ +вернуться в запущенную задачу, например, с помощью значка запуска (корневая операция задачи +содержит фильтр намерений {@link android.content.Intent#CATEGORY_LAUNCHER}; см. раздел <a href="#Starting">Запуск задачи</a> ниже).</p> +</li> + + <li>Если для атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent"> +{@code allowTaskReparenting}</a> операции установлено значение {@code "true"}. + <p>В этом случае операция может переместиться из запустившей ее задачи в задачу, к которой у операции есть привязка, +когда эта задача переходит на передний план.</p> + <p>Предположим, что операция, которая сообщает о погодных условиях в выбранных городах, +определена в составе приложения для путешественников. Она имеет ту же привязку, что и другие операции в том же +приложении (привязка приложения по умолчанию), и допускает переподчинение с этим атрибутом. +Когда одна из ваших операций запускает операцию прогноза погоды, она изначально принадлежит той же +задаче, что и ваша операция. Однако, когда задача из приложения для путешественников переходит на передний план, +операция прогноза погоды переназначается этой задаче и отображается внутри нее.</p> +</li> +</ul> + +<p class="note"><strong>Совет.</strong> Если файл {@code .apk} содержит более одного «приложения» +с точки зрения пользователя, вы, вероятно, захотите использовать атрибут <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> +для назначения разных привязок операциям, связанным с каждым «приложением».</p> + + + +<h3 id="Clearing">Очистка стека переходов назад</h3> + +<p>Если пользователь выходит из задачи на длительное время, система удаляет из задачи все операции, кроме +корневой операции. Когда пользователь возвращается в задачу, восстанавливается только корневая операция. +Система ведет себя таким образом, так как после продолжительного времени пользователи обычно уже забросили то, +чем они занимались ранее, и возвращаются в задачу, чтобы начать что-то новое. </p> + +<p>Для изменения такого поведения предусмотрено несколько атрибутов операции, которыми вы можете воспользоваться: </p> + +<dl> +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code> +</dt> +<dd>Если для этого атрибута установлено значение {@code "true"} в корневой операции задачи, +описанное выше поведение по умолчанию не происходит. +Задача восстанавливает все операции в своем стеке даже по истечении длительного периода времени.</dd> + +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt> +<dd>Если для этого атрибута установлено значение {@code "true"} в корневой операции задачи, +стек очищается до корневой операции каждый раз, когда пользователь выходит из задачи +и возвращается в нее. Другими словами, этот атрибут противоположен атрибуту +<a href="{@docRoot}guide/topics/manifest/activity-element.html#always"> +{@code alwaysRetainTaskState}</a>. Пользователь всегда возвращается в задачу в ее +исходном состоянии, даже после кратковременного выхода из нее.</dd> + +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#finish">finishOnTaskLaunch</a></code> +</dt> +<dd>Этот атрибут похож на <a href="{@docRoot}guide/topics/manifest/activity-element.html#clear">{@code clearTaskOnLaunch}</a>, +но он действует на +одну операцию, а не на всю задачу. Он также может приводить к удалению любой операции, +включая корневую операцию. Когда для него установлено значение {@code "true"}, +операция остается частью задачи только для текущего сеанса. Если пользователь +выходит из задачи, а затем возвращается в нее, операция уже отсутствует.</dd> +</dl> + + + + +<h3 id="Starting">Запуск задачи</h3> + +<p>Вы можете сделать операцию точкой входа, назначая ей фильтр намерений со значением +{@code "android.intent.action.MAIN"} в качестве указанного действия и +{@code "android.intent.category.LAUNCHER"} +в качестве указанной категории. Например:</p> + +<pre> +<activity ... > + <intent-filter ... > + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + ... +</activity> +</pre> + +<p>Фильтр намерений такого типа приводит к тому, что в средстве запуска приложения отображаются значок и метка для +операции, что позволяет пользователю запускать операцию +и возвращаться в создавшую ее задачу в любой момент после ее запуска. +</p> + +<p>Эта вторая возможность очень важна: пользователи должны иметь возможность выходить из задачи и затем возвращаться в нее +с помощью этого средства запуска операции. Поэтому два <a href="#LaunchModes">режима +запуска</a>, которые отмечают, что операции всегда инициируют задачу, {@code "singleTask"} и +{@code "singleInstance"}, должны использоваться только в тех случаях, когда операция содержит +{@link android.content.Intent#ACTION_MAIN} +и фильтр {@link android.content.Intent#CATEGORY_LAUNCHER}. Представьте, например, что может произойти, +если фильтр отсутствует: намерение запускает операцию {@code "singleTask"}, которая инициирует +новую задачу, и пользователь некоторое время работает в этой задаче. Затем пользователь нажимает кнопку +<em>Домой</em>. Задача переводится в фоновый режим и не отображается. Теперь у пользователя нет возможности вернуться +к задаче, так как она отсутствует в средстве запуска приложения.</p> + +<p>Для таких случаев, когда вы не хотите, чтобы пользователь мог вернуться к операции, установите для атрибута +<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code finishOnTaskLaunch}</a> +элемента +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +значение {@code "true"} (см. раздел <a href="#Clearing">Очистка стека</a>).</p> + +<p>Дополнительную информацию о представлении задач и операций и управлении ими +на экране обзора см. в разделе <a href="{@docRoot}guide/components/recents.html"> +Экран обзора</a>.</p> + +<!-- +<h2>Beginner's Path</h2> + +<p>For more information about how to use intents to +activate other application components and publish the intents to which your components +respond, continue with the <b><a +href="{@docRoot}guide/components/intents-filters.html">Intents and Intent +Filters</a></b> document.</p> +--> diff --git a/docs/html-intl/intl/ru/guide/index.jd b/docs/html-intl/intl/ru/guide/index.jd new file mode 100644 index 000000000000..b0732722e1b8 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Общие сведения о платформе Android + +@jd:body + + +<div class="sidebox" style="width:220px"><!-- width to match col-4 below --> +<p>Чтобы узнать, как работают приложения, начните с раздела +<a href="{@docRoot}guide/components/fundamentals.html">Основы создания приложений</a>.</p> +<p>Чтобы сразу приступить к программированию, читайте раздел <a href="{@docRoot}training/basics/firstapp/index.html">Создание первого приложения</a>.</p> +</div> + +<p>Система Android предоставляет разностороннюю платформу приложений, на основе которой можно создавать инновационные приложения и игры +для мобильных устройств в среде языка Java. В документах, ссылки на которые приведены на панели навигации слева, +рассказывается о том, как создавать приложения с помощью различных API-интерфейсов Android.</p> + +<p>Если создание программ для Android является для вас новым делом, вам важно усвоить +следующие основные концепции, касающиеся платформы приложений Android:</p> + + +<div class="landing-banner"> + +<div class="col-6"> + +<h4>Приложения имеют несколько точек входа</h4> + +<p>Приложения для Android строятся из отдельных компонентов, которые можно вызывать +независимо друг от друга. Например, отдельная <em>операция</em> предоставляет один +экран для пользовательского интерфейса, а <em>служба</em> независимо выполняет +работу в фоновом режиме.</p> + +<p>С помощью объекта <em>Intent</em> из одного компонента можно запустить другой компонент. Можно даже запустить +компонент из другого приложения, скажем, операцию из картографического приложения, чтобы показать адрес. Эта модель +формирует несколько точек входа для одного приложения, и при этом пользователь может выбрать любое приложение для выполнения по умолчанию +того или иного действия, которое могут вызывать другие приложения.</p> + + +<p><b>Подробнее:</b></p> +<ul class="nolist"> +<li><a href="{@docRoot}guide/components/fundamentals.html">Основы создания приложений</a> +<li><a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent и фильтры объектов Intent</a> +<li><a href="{@docRoot}guide/components/activities.html">Операции</a> +</ul> + +</div> + + +<div class="col-6"> + +<h4>Приложения адаптируются к различным устройствам</h4> + +<p>Android предоставляет адаптивную платформу приложений, которая позволяет обеспечивать уникальные ресурсы +для различных конфигураций устройств. Например, можно создать разные файлы XML +макета для экранов разных размеров, а система будет +определять, какой макет использовать, с учетом размера экрана данного устройства.</p> + +<p>Если каким-либо функциям приложения требуется определенное оборудование, например камера, можно +запрашивать его наличие в устройстве во время выполнения. При необходимости также можно объявлять функции, которые требуются приложению, +с тем чтобы такие магазины приложений, как Google Play, не позволяли устанавливать приложения на устройствах, в которых +этой функции нет.</p> + + +<p><b>Подробнее:</b></p> +<ul class="nolist"> +<li><a href="{@docRoot}guide/practices/compatibility.html">Совместимость устройств</a> +<li><a href="{@docRoot}guide/topics/resources/overview.html">Обзор ресурсов</a> +<li><a href="{@docRoot}guide/topics/ui/overview.html">Обзор пользовательского интерфейса</a> +</ul> + +</div> + +</div><!-- end landing-banner --> + + + diff --git a/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 000000000000..f2c5a9e03590 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=Манифест приложения +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> +<li><a href="#filestruct">Структура файла манифеста</a></li> +<li><a href="#filec">Соглашения о компонентах файла</a> +<li><a href="#filef">Отображение функций в файле</a> + <ol> + <li><a href="#ifs">Фильтры объектов Intent</a></li> + <li><a href="#iconlabel">Значки и метки</a></li> + <li><a href="#perms">Разрешения</a></li> + <li><a href="#libs">Библиотеки</a></li> + </ol></li> +</ol> +</div> +</div> + +<p> + В корневой папке каждого приложения должен находиться файл AndroidManifest.xml (который именно так и называется +). <span itemprop="description">Файл манифеста + содержит важную информацию о приложении, которая требуется системе Android. + Только получив эту информацию, система может выполнить какой-либо код + приложения.</span> Среди прочего файл манифеста выполняет следующие действия: +</p> + +<ul> +<li>Он задает имя пакета Java для приложения. +Это имя пакета служит уникальным идентификатором приложения.</li> + +<li>Он описывает компоненты приложения — операции, +службы, приемники широковещательных сообщений и поставщиков контента, из которых состоит +приложение. Он содержит имена классов, которые реализуют каждый компонент, и +публикует их возможности (указывает, например, какие сообщения {@link android.content.Intent +Intent} они могут принимать). На основании этих деклараций система Android +может определить, из каких компонентов состоит приложение и при каких условиях их можно запускать.</li> + +<li>Он определяет, в каких процессах будут размещаться компоненты приложения.</li> + +<li>Он объявляет, какие разрешения должны быть выданы приложению, чтобы оно могло получить +доступ к защищенным частям API-интерфейса и взаимодействовать с другими приложениями.</li> + +<li>Он также объявляет разрешения, требуемые для +взаимодействия с компонентами данного приложения.</li> + +<li>Он содержит список классов {@link android.app.Instrumentation}, которые при выполнении приложения предоставляют +сведения о профиле и прочую информацию. Эти объявления +присутствуют в файле манифеста только во время разработки и отладки +приложения и удаляются перед его публикацией.</li> + +<li>Он объявляет минимальный уровень API-интерфейса Android, который требуется +приложению.</li> + +<li>Он содержит список библиотек, с которыми должно быть связано приложение.</li> +</ul> + + +<h2 id="filestruct">Структура файла манифеста</h2> + +<p> +Приведенная далее схема позволяет ознакомиться с общей структурой файла манифеста и +всеми элементами, которые могут в нем содержаться. Каждый элемент вместе со всеми своими +атрибутами, полностью описывается в отдельном файле. Для просмотра подробных +сведений о любом элементе, щелкните имя элемента на схеме, +в алфавитном списке элементов, приведенном после схемы, или +в любом другом месте, где этот элемент упоминается. +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> + +<a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a> + + <a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission /></a> + <a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission /></a> + <a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree /></a> + <a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group /></a> + <a href="{@docRoot}guide/topics/manifest/instrumentation-element.html"><instrumentation /></a> + <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><uses-sdk /></a> + <a href="{@docRoot}guide/topics/manifest/uses-configuration-element.html"><uses-configuration /></a> <!-- ##api level 3## --> + <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><uses-feature /></a> <!-- ##api level 4## --> + <a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><supports-screens /></a> <!-- ##api level 4## --> + <a href="{@docRoot}guide/topics/manifest/compatible-screens-element.html"><compatible-screens /></a> <!-- ##api level 9## --> + <a href="{@docRoot}guide/topics/manifest/supports-gl-texture-element.html"><supports-gl-texture /></a> <!-- ##api level 11## --> + + <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a> + + <a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/action-element.html"><action /></a> + <a href="{@docRoot}guide/topics/manifest/category-element.html"><category /></a> + <a href="{@docRoot}guide/topics/manifest/data-element.html"><data /></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/activity-element.html"></activity></a> + + <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"></activity-alias></a> + + <a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data/></a> + <a href="{@docRoot}guide/topics/manifest/service-element.html"></service></a> + + <a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/receiver-element.html"></receiver></a> + + <a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a> + <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission /></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/path-permission-element.html"><path-permission /></a> + <a href="{@docRoot}guide/topics/manifest/provider-element.html"></provider></a> + + <a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library /></a> + + <a href="{@docRoot}guide/topics/manifest/application-element.html"></application></a> + +<a href="{@docRoot}guide/topics/manifest/manifest-element.html"></manifest></a> +</pre> + +<p> +Далее приведен список всех элементов, расположенных в алфавитном порядке, которые могут +присутствовать в файле манифеста. Там могут находиться только эти элементы, а никакие другие +элементы или атрибуты добавлять нельзя. +</p> + +<p style="margin-left: 2em"> +<code><a href="{@docRoot}guide/topics/manifest/action-element.html"><action></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/category-element.html"><category></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/data-element.html"><data></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/instrumentation-element.html"><instrumentation></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><supports-screens></a></code> <!-- ##api level 4## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-configuration-element.html"><uses-configuration></a></code> <!-- ##api level 3## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><uses-feature></a></code> <!-- ##api level 4## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><uses-sdk></a></code> +</p> + + + + +<h2 id="filec">Соглашения о компонентах файла</h2> + +<p> +Ко всем элементам и атрибутам +из файла манифеста применяется рад соглашений и правил: +</p> + +<dl> +<dt><b>Элементы</b></dt> +<dd>Обязательными +являются только элементы<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> и +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +. Оба они должны присутствовать в файле манифеста, при этом указать их можно только один раз. +Большинство других элементов можно указывать по нескольку раз или не указывать вовсе — хотя по +крайней мере некоторые из них нужны, чтобы файл манифеста был сколько-нибудь +информативным. + +<p> +Если в элементе и есть какое-то содержимое, то это другие элементы. +Все значения задаются с помощью атрибутов, а не как символьные данные в элементе. +</p> + +<p> +Элементы, находящиеся на одном уровне, обычно не упорядочиваются. Например, + элементы <code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>, +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> и +<code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> +можно указать в любой последовательности. (Элемент +<code><a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a></code> +является исключением из этого правила. Он должен следовать за элементом +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>, +псевдонимом которого он является.) +</p></dd> + +<dt><b>Атрибуты</b></dt> +<dd>Формально все атрибуты являются необязательными. Однако некоторые их них +указывать необходимо, чтобы файл мог выполнять свое предназначение. В качестве руководства используйте эту +документацию. В отношении атрибутов, которые являются и вправду необязательными, в ней указывается значение, +используемое по умолчанию, или говорится, что произойдет, если такой атрибут не будет указан. + +<p>За исключением некоторых атрибутов корневого элемента +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> +, имена всех атрибутов должны начинаться с префикса {@code android:} — +например, {@code android:alwaysRetainTaskState}. Поскольку этот префикс является +универсальным, в документации при указании атрибутов по имени +он обычно опускается.</p></dd> + +<dt><b>Объявление имен классов</b></dt> +<dd>Многие элементы соответствуют объектам Java, в том числе элементы для самого +приложения (элемент +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +) и основных его компонентов — операций +(<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>), +служб +(<code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code>), +приемников широковещательных сообщений +(<code><a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code>) +и поставщиков контента +(<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>). + +<p> +Если вы определяете подкласс, а это практически всегда делается для классов компонентов +({@link android.app.Activity}, {@link android.app.Service}, +{@link android.content.BroadcastReceiver} и {@link android.content.ContentProvider}), +выполняется это с помощью атрибута {@code name}. В состав имени должно входить +полное обозначение пакета. +Например, подкласс {@link android.app.Service} можно объявить следующим образом: +</p> + +<pre><manifest . . . > + <application . . . > + <service android:name="com.example.project.SecretService" . . . > + . . . + </service> + . . . + </application> +</manifest></pre> + +<p> +Однако его можно укоротить. Если первым символом в строке указать точку, эта +строка будет добавляться к имени пакета приложения (указанного атрибутом +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html#package">package</a></code> +элемента +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html#package">package</a></code> +). Следующее назначение является таким же, как приведенное выше: +</p> + +<pre><manifest package="com.example.project" . . . > + <application . . . > + <service android:name=".SecretService" . . . > + . . . + </service> + . . . + </application> +</manifest></pre> + +<p> +При запуске компонента Android создает экземпляр подкласса, указанного по имени. +Если подкласс не указан, система создает экземпляр базового класса. +</p></dd> + +<dt><b>Несколько значений</b></dt> +<dd>Если можно указать несколько значений, элемент почти всегда +приводится повторно. Делается это вместо перечисления нескольких значений в одном элементе. +Например, в фильтре Intent может быть перечислено несколько действий: + +<pre><intent-filter . . . > + <action android:name="android.intent.action.EDIT" /> + <action android:name="android.intent.action.INSERT" /> + <action android:name="android.intent.action.DELETE" /> + . . . +</intent-filter></pre></dd> + +<dt><b>Значения ресурсов</b></dt> +<dd>Значения некоторых атрибутов могут отображаться на экране — например, +метка и значок операции. Значения этих атрибутов +следует локализовать, поэтому они должны задаваться в ресурсе или теме. Значения +ресурсов выражаются в следующем формате:</p> + +<p style="margin-left: 2em">{@code @[<i>пакет</i>:]<i>тип</i>:<i>имя</i>}</p> + +<p> +где <i>имя пакета</i> можно опустить, если ресурс находится в одном пакете +с приложением, <i>тип —</i> это тип ресурса, — например "string" или +"drawable", — а <i>имя —</i> это имя, определяющее ресурс. +Например: +</p> + +<pre><activity android:icon="@drawable/smallPic" . . . ></pre> + +<p> +Значения из темы выражаются схожим образом, только в начале у них идет "{@code ?}", +а не "{@code @}": +</p> + +<p style="margin-left: 2em">{@code ?[<i>пакет</i>:]<i>тип</i>:<i>имя</i>} +</p></dd> + +<dt><b>Строковые значения</b></dt> +<dd>Когда значением атрибута является строка, следует использовать двойную обратную косую черту ("{@code \\}") +для выделения управляющей последовательности символов, — например "{@code \\n}" для +новой строки или "{@code \\uxxxx}" для символа Юникода.</dd> +</dl> + + +<h2 id="filef">Отображение функций в файле</h2> + +<p> +В следующих разделах описано, как некоторые функции Android отображаются +в файле манифеста. +</p> + + +<h3 id="ifs">Фильтры объектов Intent</h3> + +<p> +Базовые компоненты приложения (его операции, службы и +приемники широковещательных сообщений) активируются <i>объектами Intent</i>. Intent — +это совокупность информации (объект {@link android.content.Intent}), описывающей +требуемое действие, — в том числе в ней указаны данные, с которыми следует выполнить это действие, категория +компонентов, которые должны выполнять это действие, и другие уместные инструкции. +Система Android находит компонент, который отреагирует на объект Intent, запускает +новый экземпляр компонента, если он требуется, и передает ему +объект Intent. +</p> + +<p> +Компоненты объявляют свои возможности — виды объектов Intent, на которые они могут +реагировать, — с помощью <i>фильтров Intent</i>. Поскольку система Android +должна узнать, какие объекты Intent может обрабатывать тот или иной компонент, до того как она его запустит, +фильтры Intent указываются в файле манифеста как +элементы <code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> +. Компонент может иметь любое количество фильтров, каждый из которых описывает +отдельную возможность компонента. +</p> + +<p> +Объект Intent, в котором целевой компонент явно указан по имени, активирует этот компонент, +и фильтр при этом не учитывается. Но объект Intent, в котором имя целевого +компонента не указано, может активировать компонент, только если он может пройти через один из фильтров +компонента. +</p> + +<p> +Сведения о том, каким образом объекты Intent проверяются по фильтрам Intent, +см. в отдельном документе +<a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent +и фильтры объектов Intent</a>. +</p> + + +<h3 id="iconlabel">Значки и метки</h3> + +<p> +У ряда элементов есть атрибуты {@code icon} и {@code label} для +небольшого значка и текстовой метки, которые могут отображаться на экране. У некоторых из них также есть атрибут +{@code description} для более длинного описательного текста, который также может +отображаться на экране. Например, элемент +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +имеет все три таких атрибута, поэтому, когда пользователю задается вопрос, предоставить ли +разрешение запросившему его приложению, на экране может отображаться значок, +представляющий разрешение, имя разрешения и описание того, что оно +за собой влечет. +</p> + +<p> +В любом случае значок и метка, заданные в элементе-контейнере, становятся параметрами +{@code icon} и {@code label}, используемыми по умолчанию для всех вложенных в этот контейнер дочерних элементов. +Так, значок и метка, заданные в элементе +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code>, +являются значком и меткой, используемыми по умолчанию для каждого компонента приложения. +Точно так же, значок и метка, заданные для компонента, — например элемента +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>, — +являются параметрами, используемыми по умолчанию для каждого элемента +<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> + компонента. Если в элементе +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +задана метка, а в операции и ее фильтре Intent — нет, +метка приложения будет считаться меткой и для операции, и для +фильтра Intent. +</p> + +<p> +Значок и метка, заданные для фильтра Intent, используются для обозначения компонента, +когда он представляется пользователю, для указания функции, +которую анонсирует фильтр. Например, фильтр с параметрами +"{@code android.intent.action.MAIN}" и +"{@code android.intent.category.LAUNCHER}" сообщает, что эта операция +инициирует приложение, — то есть он обозначает ее как + операцию, которая должна быть отображена в средстве запуска приложений. Отсюда следует, что значок и метка, +заданные в фильтре, отображаются в средстве запуска. +</p> + + +<h3 id="perms">Разрешения</h3> + +<p> +Разрешение <i>представляет</i> собой ограничение на доступ к части кода +или к данным, имеющимся на устройстве. Это ограничение накладывается для защиты важных +данных и кода, ненадлежащее использование которых может пагубно сказаться на работе приложения. +</p> + +<p> +Каждое разрешение обозначается уникальной меткой. Зачастую метка обозначает +действие, выполнение которого ограничивается. Например, вот некоторые разрешения, определенные +системой Android: +</p> + +<p style="margin-left: 2em">{@code android.permission.CALL_EMERGENCY_NUMBERS} +<br/>{@code android.permission.READ_OWNER_DATA} +<br/>{@code android.permission.SET_WALLPAPER} +<br/>{@code android.permission.DEVICE_POWER}</p> + +<p> +Функцию можно защитить не более чем одним разрешением. +</p> + +<p> +Если приложению требуется доступ к функции, защищенной разрешением, +оно должно объявить, что ему необходимо это разрешение, с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +в файле манифеста. Затем, когда приложение устанавливается на +устройство, установщик определяет, выдать ли запрошенное +разрешение, проверяя полномочия органов, подписавших сертификаты +приложения, а также, в некоторых случаях, спрашивая об этом пользователя. +Если разрешение предоставляется, приложение сможет использовать защищенные +функции. В противном случае его попытки доступа к этим функциям будут безуспешными, +причем пользователь не получит никакого уведомления об этом. +</p> + +<p> +Приложение также может защищать с помощью разрешений собственные компоненты (операции, службы, +приемники широковещательных сообщений и поставщиков контента). Оно может использовать +любые разрешения, определенные системой Android (они приведены в объекте +{@link android.Manifest.permission android.Manifest.permission}) или объявленные +другими приложениями. Либо оно может определить разрешения самостоятельно. Новое разрешение объявляется +с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +. Например, операцию можно защитить следующим образом: +</p> + +<pre> +<manifest . . . > + <permission android:name="com.example.project.DEBIT_ACCT" . . . /> + <uses-permission android:name="com.example.project.DEBIT_ACCT" /> + . . . + <application . . .> + <activity android:name="com.example.project.FreneticActivity" + android:permission="com.example.project.DEBIT_ACCT" + . . . > + . . . + </activity> + </application> +</manifest> +</pre> + +<p> +Обратите внимание, что в этом примере разрешение {@code DEBIT_ACCT} не только +объявляется с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +, его использование также запрашивается с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +. Чтобы другие компоненты приложения запускали защищенную +операцию, ее использование должно быть запрошено, даже несмотря на то, что защита +наложена самим приложением. +</p> + +<p> +В этом же примере: если атрибут {@code permission} был бы задан как +разрешение, объявленное где-то еще +(например, {@code android.permission.CALL_EMERGENCY_NUMBERS}), его бы не +нужно было объявлять еще раз с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +. Однако все равно нужно было бы запрашивать его использование с помощью +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code>. +</p> + +<p> +Элемент +<code><a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree></a></code> +объявляет пространство имен для группы разрешений, которые будут определены в +коде. А элемент +<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +определяет метку для набора разрешений (как для разрешений, объявленных в файле манифеста с помощью элементов +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +, так и для объявленных где-то еще). Это влияет только на то, каким образом разрешения +группируются, когда отображаются пользователю. Элемент +<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +не указывает, какие разрешения относятся к группе. +Он просто дает группе имя. Чтобы включить разрешение в группу, +атрибуту +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html#pgroup">permissionGroup</a></code> + его элемента +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +необходимо присвоить имя группы. +</p> + + +<h3 id="libs">Библиотеки</h3> + +<p> +Каждое приложение связывается с используемой по умолчанию библиотекой Android, в которой +имеются базовые пакеты для построения приложений (со стандартными классами, +например Activity, Service, Intent, View, Button, Application, ContentProvider +и так далее). +</p> + +<p> +Однако некоторые пакеты находятся в собственных библиотеках. Если ваше приложение +использует код из одного из таких пакетов, оно должно в явном виде потребовать, чтобы его связали +с этим пакетом. Файл манифеста должен содержать отдельный элемент +<code><a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library></a></code> +для указания имени каждой библиотеки. (Имя библиотеки можно найти в +документации по пакету.) +</p> diff --git a/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd new file mode 100644 index 000000000000..2d12e1261d93 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=Поставщик календаря +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> + <ol> + <li><a href="#overview">Основы</a></li> + <li><a href="#manifest">Разрешения пользователей</a></li> + <li><a href="#calendar">Таблица календарей</a> +<ol> + <li><a href="#query">Запрос календаря</a></li> + <li><a href="#modify-calendar">Изменение календаря</a></li> + <li><a href="#insert-calendar">Вставка календаря</a></li> + </ol> + </li> + <li><a href="#events">Таблица событий</a> +<ol> + <li><a href="#add-event">Добавление событий</a></li> + <li><a href="#update-event">Обновление событий</a></li> + <li><a href="#delete-event">Удаление событий</a></li> + </ol> + </li> + <li><a href="#attendees">Таблица участников</a> +<ol> + <li><a href="#add-attendees">Добавление участников</a></li> + </ol> + </li> + <li><a href="#reminders">Таблица напоминаний</a> +<ol> + <li><a href="#add-reminders">Добавление напоминаний</a></li> + </ol> + </li> + <li><a href="#instances">Таблица экземпляров</a> + <ol> + <li><a href="#query-instances">Запрос таблицы экземпляров</a></li> + </ol></li> + <li><a href="#intents">Намерения календаря</a> + <ol> + <li><a href="#intent-insert">Использование намерения для вставки события</a></li> + <li><a href="#intent-edit">Использование намерения для редактирования события</a></li> + <li><a href="#intent-view">Использование намерения для просмотра данных календаря</a></li> + </ol> + </li> + + <li><a href="#sync-adapter">Адаптеры синхронизации</a></li> +</ol> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.provider.CalendarContract.Calendars}</li> + <li>{@link android.provider.CalendarContract.Events}</li> + <li>{@link android.provider.CalendarContract.Attendees}</li> + <li>{@link android.provider.CalendarContract.Reminders}</li> + </ol> +</div> +</div> + +<p>Поставщик календаря представляет собой репозиторий для событий календаря пользователя. API +поставщика календаря позволяет запрашивать, вставлять, обновлять и удалять календари, +события, участников, напоминания и т. д.</p> + + +<p>API поставщика календаря может использоваться как приложениями, так и адаптерами синхронизации. Правила +зависят от типа программы, которая выполняет вызовы. В этой статье +главным образом рассматривается использование API поставщика календаря в качестве приложения. Сведения о различиях +между адаптерами синхронизации представлены в разделе +<a href="#sync-adapter">Адаптеры синхронизации</a>.</p> + + +<p>Обычно, чтобы считать или записать данные календаря, в манифесте приложения +должны быть включены надлежащие разрешения, которые описываются в разделе <a href="#manifest">Разрешения +пользователей</a>. Чтобы упростить выполнение часто используемых операций, в поставщике +календаря предусмотрен набор намерений, как описано в разделе <a href="#intents">Намерения +календаря</a>. Эти намерения позволяют пользователям переходить в приложение календаря для вставки, просмотра +и редактирования событий. После взаимодействия пользователя с календарем он возвращается +в исходное приложение. Поэтому вашему приложению не нужно запрашивать разрешения, +а также предоставлять пользовательский интерфейс для просмотра или создания событий.</p> + +<h2 id="overview">Основы</h2> + +<p><a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a> хранят в себе данные и предоставляют к ним доступ +для приложений. Поставщики контента, предлагаемые платформой Android (включая поставщик календаря) обычно представляют данные в виде набора таблиц, в основе +которых лежит модель реляционной базы данных. Каждая строка в такой таблице представляет собой запись, а каждый столбец — данные +определенного типа и значения. Благодаря API поставщика календаря приложения +и адаптеры синхронизации получают доступ на чтение/запись к таблицам в базе данных, в которых +представлены данные календаря пользователя.</p> + +<p>Каждый поставщик календаря предоставляет общедоступный URI (упакованный в объект +{@link android.net.Uri}), +который служит уникальным идентификатором своего набора данных. Поставщик контента, который управляет +несколькими наборами данных (несколькими таблицами), предоставляет отдельный URI для каждого набора. Все +URI поставщиков начинаются со строки content://. Она +определяет данные, которые находятся под управлением поставщика контента. Поставщик календаря +задает константы для URI каждого из своих классов (таблиц). Такие +URI имеют формат <code><em><class></em>.CONTENT_URI</code>. Например, +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.</p> + +<p>На рисунке 1 изображено графическое представление модели данных поставщика календаря. На нем представлены +основные таблицы и поля, которые связывают их друг с другом.</p> + +<img src="{@docRoot}images/providers/datamodel.png" alt="Calendar Provider Data Model" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Модель данных поставщика календаря.</p> + +<p>У пользователя может быть несколько календарей, причем они могут быть связаны с аккаунтами разных типов (Google Календарь, Exchange и т. д.).</p> + +<p>Класс {@link android.provider.CalendarContract} определяет модель данных календаря и информацию, относящуюся к событиям. Эти данные хранятся в различных таблицах, указанных ниже.</p> + +<table> + <tr> + <th>Таблица (класс)</th> + <th>Описание</th> + </tr> + <tr> + <td><p>{@link android.provider.CalendarContract.Calendars}</p></td> + + <td>В этой таблице находится +информация о календарях. В каждой строке этой таблицы представлены сведения +об отдельном календаре, например, его название, цвет, информация о синхронизации и т. д.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Events}</td> + + <td>В этой таблице находится +информация о событиях. В каждой строке этой таблицы содержится информация об отдельном +событии —например, заголовок события, место проведения, время начала, время +завершения и т. д. Событие может быть однократным или повторяющимся. Сведения об участниках, +напоминаниях и расширенные свойства хранятся в отдельных таблицах. +В каждой из них имеется целочисленная переменная {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}, +которая ссылается на объект {@link android.provider.BaseColumns#_ID} в таблице событий.</td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances}</td> + + <td>В этой таблице содержатся данные о времени +начала и окончания каждого повторения события. В каждой строке этой таблицы +представлено одно повторение события. Однократные события сопоставляются с повторениями +один к одному. Для повторяющихся событий автоматически создаются несколько строк, +которые соответствуют нескольким повторениям события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Attendees}</td> + + <td>В этой таблице находится +информация об участниках (гостях). В каждой строке этой таблицы указан один +гость. В ней указываются тип гостя и информация о том, +посетит ли он событие.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Reminders}</td> + + <td>В этой таблице находятся +данные уведомлений или оповещений. В каждой строке этой таблицы указано одно уведомление или оповещение. Для одного +события можно создать несколько напоминаний. Максимальное количество таких напоминаний для события +задается с помощью +целочисленной переменной {@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS}, +значение которой задает адаптер синхронизации, владеющий +указанным календарем. Напоминания задаются в минутах до начала события и +имеют метод, который определяет порядок уведомления пользователя.</td> + </tr> + +</table> + +<p>API поставщика календаря обеспечивает достаточную гибкость и эффективность. В то же время +важно предоставить интерфейс, который будет удобным для пользователя, +и обеспечить защиту целостности календаря и его данных. Поэтому существует +ряд моментов, которые следует учитывать при использовании этого API.</p> + +<ul> + +<li><strong>Вставка, обновление и просмотр событий календаря.</strong> Чтобы вставить, изменить и считать события напрямую из поставщика календаря, требуются соответствующие <a href="#manifest">разрешения</a>. Однако, если вы не планируете создавать полнофункциональное приложение календаря или адаптер синхронизации, запрашивать такие разрешения не обязательно. Вместо этого можно использовать намерения, поддерживаемые приложением «Календарь» Android, для обработки операций чтения и записи в этом приложении. При использовании намерений ваше приложение отправляет пользователям приложение «Календарь» для выполнения требуемой операции +в предварительно заполненной форме. По завершении они возвращаются в приложение. +Реализовав в вашем приложении возможность выполнения часто используемых операций через приложение «Календарь», +вы обеспечиваете для пользователей единообразный и функциональный пользовательский интерфейс. Мы рекомендуем использовать +именно такой подход. Дополнительные сведения представлены в разделе <a href="#intents">Намерения +календаря</a>.</p> + + +<li><strong>Адаптеры синхронизации.</strong> Адаптер синхронизации синхронизирует данные календаря +на устройстве пользователя с данными на сервере или в другом источнике данных. В таблицах +{@link android.provider.CalendarContract.Calendars} и +{@link android.provider.CalendarContract.Events} имеются +столбцы, зарезервированные для адаптеров синхронизации. +Ни поставщик, ни приложения не должны изменять их. Фактически они скрыты +до тех пор, пока адаптер синхронизации не начнет использовать их. Дополнительные сведения об +адаптерах синхронизации представлены в разделе <a href="#sync-adapter">Адаптеры синхронизации</a>.</li> + +</ul> + + +<h2 id="manifest">Разрешения пользователей</h2> + +<p>Чтобы считать данные календаря, в файл манифеста приложения необходимо включить разрешение {@link +android.Manifest.permission#READ_CALENDAR}. Также в него +следует включить разрешение {@link android.Manifest.permission#WRITE_CALENDAR} +для удаления, вставки или обновления данных календаря:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"...> + <uses-sdk android:minSdkVersion="14" /> + <uses-permission android:name="android.permission.READ_CALENDAR" /> + <uses-permission android:name="android.permission.WRITE_CALENDAR" /> + ... +</manifest> +</pre> + + +<h2 id="calendar">Таблица календарей</h2> + +<p>В таблице {@link android.provider.CalendarContract.Calendars} содержатся подробные сведения +о каждом отдельном календаре. Выполнять запись в указанные ниже столбцы +этой таблицы могут и приложение, и <a href="#sync-adapter">адаптер синхронизации</a>. +Полный список поддерживаемых полей представлен в справке по классу +{@link android.provider.CalendarContract.Calendars}.</p> +<table> + <tr> + <th>Константа</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#NAME}</td> + <td>Название календаря.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}</td> + <td>Название этого календаря, которое отображается для пользователя.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#VISIBLE}</td> + + <td>Логическое значение, обозначающее, выбран ли календарь для отображения. Значение +«0» указывает на то, что события, связанные с +этим календарем, не отображаются. Значение «1» указывает на то, что события, связанные с +этим календарем, отображаются. Это значение влияет на создание строк в таблице {@link +android.provider.CalendarContract.Instances}.</td> + + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}</td> + + <td>Логическое значение, обозначающее, следует ли синхронизировать календарь и хранить имеющиеся в нем события +на устройстве. Значение «0» указывает, что не следует синхронизировать этот календарь или +хранить имеющиеся в нем события на устройстве. Значение «1» указывает, что этот календарь следует синхронизировать и +хранить имеющиеся в нем события на устройстве.</td> + </tr> +</table> + +<h3 id="query">Запрос календаря</h3> + +<p>Ниже представлен пример того, как получить календари, которыми +владеет определенный пользователь. Для простоты демонстрации операция запроса в этом примере находится в +потоке пользовательского интерфейса («основной поток»). На практике это следует делать в асинхронном +потоке, а не в основном. Дополнительные сведения представлены в статье +<a href="{@docRoot}guide/components/loaders.html">Загрузчики</a>. Если же вы не только +считываете данные, но и вносите в них изменения, обратитесь к справке по классу {@link android.content.AsyncQueryHandler}. +</p> + + +<pre> +// Projection array. Creating indices for this array instead of doing +// dynamic lookups improves performance. +public static final String[] EVENT_PROJECTION = new String[] { + Calendars._ID, // 0 + Calendars.ACCOUNT_NAME, // 1 + Calendars.CALENDAR_DISPLAY_NAME, // 2 + Calendars.OWNER_ACCOUNT // 3 +}; + +// The indices for the projection array above. +private static final int PROJECTION_ID_INDEX = 0; +private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1; +private static final int PROJECTION_DISPLAY_NAME_INDEX = 2; +private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;</pre> + + +<div class="sidebox-wrapper"> <div class="sidebox"> <h3>Зачем необходимо указывать параметр +ACCOUNT_TYPE?</h3> <p>При создании запроса {@link +android.provider.CalendarContract.Calendars#ACCOUNT_NAME +Calendars.ACCOUNT_NAME} необходимо также +указать +{@link android.provider.CalendarContract.Calendars#ACCOUNT_TYPE Calendars.ACCOUNT_TYPE}. Это необходимо сделать ввиду того, что указанный аккаунт +считается уникальным только тогда, когда для него указаны и параметр <code>ACCOUNT_NAME</code>, и параметр +<code>ACCOUNT_TYPE</code>. Параметр <code>ACCOUNT_TYPE</code> в строке обозначает +структуру проверки подлинности аккаунта, которая использовалась при регистрации аккаунта с помощью +{@link android.accounts.AccountManager}. Существует также особый тип аккаунтов, называемый {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. Он используется для календарей, +которые не связаны с аккаунтом устройства. Аккаунты {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} не +синхронизируются.</p> </div> </div> + + +<p> В следующей части примера создается запрос. С помощью выбора определяются +критерии для запроса. В этом примере выполняется поиск +календарей со следующими значениями параметров: <code>ACCOUNT_NAME</code> +— sampleuser@google.com, <code>ACCOUNT_TYPE</code> +— com.google и <code>OWNER_ACCOUNT</code> + — sampleuser@google.com. Для просмотра всех просмотренных +пользователем календарей, а не только имеющихся у него, не указывайте параметр <code>OWNER_ACCOUNT</code>. +Запрос возвращает объект {@link android.database.Cursor}, +который можно использовать для перебора результатов, возвращенных запросом к базе +данных. Дополнительные сведения об использовании запросов в поставщиках контента +представлены в статье <a href="{@docRoot}guide/topics/providers/content-providers.html">Поставщики контента</a>.</p> + + +<pre>// Run query +Cursor cur = null; +ContentResolver cr = getContentResolver(); +Uri uri = Calendars.CONTENT_URI; +String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" + + Calendars.ACCOUNT_TYPE + " = ?) AND (" + + Calendars.OWNER_ACCOUNT + " = ?))"; +String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google", + "sampleuser@gmail.com"}; +// Submit the query and get a Cursor object back. +cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);</pre> + +<p>В следующем разделе кода выполняется пошаговый обзор набора результатов с помощью курсора. В нем +используются константы, которые были заданы в начале примера, для получения значений +для каждого из полей.</p> + +<pre>// Use the cursor to step through the returned records +while (cur.moveToNext()) { + long calID = 0; + String displayName = null; + String accountName = null; + String ownerName = null; + + // Get the field values + calID = cur.getLong(PROJECTION_ID_INDEX); + displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX); + accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX); + ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX); + + // Do something with the values... + + ... +} +</pre> + +<h3 id="modify-calendar">Изменение календаря</h3> + +<p>Чтобы обновить календарь, можно указать {@link +android.provider.BaseColumns#_ID} календаря: либо в виде идентификатора, +добавленного к URI + +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}), +либо в качестве первого элемента выделения. Выделение +должно начинаться с <code>"_id=?"</code>, а первым аргументом +<code>selectionArg</code> должен быть {@link +android.provider.BaseColumns#_ID} календаря. +Также для выполнения обновлений можно закодировать идентификатор в URI. В этом примере для +изменения отображаемого имени календаря используется +подход +({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}):</p> + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long calID = 2; +ContentValues values = new ContentValues(); +// The new display name for the calendar +values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar"); +Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID); +int rows = getContentResolver().update(updateUri, values, null, null); +Log.i(DEBUG_TAG, "Rows updated: " + rows);</pre> + +<h3 id="insert-calendar">Вставка календаря</h2> + +<p>Для управления календарями в основном используются адаптеры синхронизации, поэтому +новые календари следует вставлять исключительно как адаптер синхронизации. По большей части +приложения могут вносить в календари только поверхностные изменения, такие как изменение отображаемого имени. Если +приложению требуется создать локальный календарь, это можно сделать путем +вставки календаря в виде адаптера синхронизации с помощью параметра {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} типа {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL}. +{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +представляет собой особый тип аккаунтов для календарей, которые не связаны +с аккаунтом устройства. Календари этого типа не синхронизируются с сервером. Дополнительные сведения +об адаптерах синхронизации представлены в статье <a href="#sync-adapter">Адаптеры синхронизации</a>.</p> + +<h2 id="events">Таблица событий</h2> + +<p>В таблице {@link android.provider.CalendarContract.Events} содержатся подробные сведения +о каждом отдельном событии. Чтобы получить возможность добавлять, обновлять или удалять события, +в <a href="#manifest">файл манифеста</a> +приложения необходимо включить разрешение {@link android.Manifest.permission#WRITE_CALENDAR}.</p> + +<p>Выполнять запись в указанные ниже столбцы этой таблицы могут и приложение, и +адаптер синхронизации. Полный список поддерживаемых полей представлен в справке по классу {@link +android.provider.CalendarContract.Events}.</p> + +<table> + <tr> + <th>Константа</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}</td> + <td>{@link android.provider.BaseColumns#_ID} календаря, к которому принадлежит событие.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}</td> + <td>Адрес эл. почты организатора (владельца) события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#TITLE}</td> + <td>Название события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}</td> + <td>Место проведения. </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}</td> + <td>Описание события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DTSTART}</td> + <td>Время начала события по UTC (в миллисекундах) от точки отсчета. </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DTEND}</td> + <td>Время окончания события по UTC (в миллисекундах) от точки отсчета. </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}</td> + <td>Часовой пояс события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}</td> + <td>Часовой пояс для времени окончания события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DURATION}</td> + + <td>Продолжительность события в формате <a href="http://tools.ietf.org/html/rfc5545#section-3.8.2.5">RFC5545</a>. +Например, значение <code>"PT1H"</code> обозначает, что событие +должно длиться один час, а значение <code>"P2W"</code> указывает на продолжительность +в 2 недели. </td> + + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}</td> + + <td>Значение «1» обозначает, что это событие занимает весь день по +местному часовому поясу. Значение «0» указывает на то, что это регулярное событие, которое может начаться +и завершиться в любое время в течение дня.</td> + + + </tr> + + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#RRULE}</td> + + <td>Правило повторения для формата события. Например, +<code>"FREQ=WEEKLY;COUNT=10;WKST=SU"</code>. С другими +примерами можно ознакомиться <a href="http://tools.ietf.org/html/rfc5545#section-3.8.5.3">здесь</a>.</td> + + </tr> + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#RDATE}</td> + <td>Даты повторения события. +Обычно {@link android.provider.CalendarContract.EventsColumns#RDATE} +используется вместе с {@link android.provider.CalendarContract.EventsColumns#RRULE} +для определения агрегированного набора +повторяющихся событий. Дополнительные сведения представлены в <a href="http://tools.ietf.org/html/rfc5545#section-3.8.5.2">спецификации RFC5545</a>.</td> +</tr> + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}</td> + + <td>Если событие считается как занятое или как свободное время, + доступное для планирования. </td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}</td> + <td>Указывает, могут ли гости вносить изменения в событие. </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}</td> + <td>Указывает, могут ли гости приглашать других гостей. </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}</td> + <td>Указывает, могут ли гости просматривать список участников.</td> + </tr> +</table> + +<h3 id="add-event">Добавление событий</h3> + +<p>Когда ваше приложение вставляет новое событие, мы рекомендуем использовать намерение +{@link android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе <a href="#intent-insert">Использование намерения для вставки события</a>. Однако при +необходимости вы можете вставлять события напрямую. В этом разделе как раз описывается то, как это +сделать.</p> + + +<p>Ниже указаны правила, которыми следует руководствоваться для вставки нового события. </p> +<ul> + + <li>Необходимо указать {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} и {@link +android.provider.CalendarContract.EventsColumns#DTSTART}.</li> + +<li>Необходимо указать {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}. Чтобы получить список +установленных в системе идентификаторов часовых поясов, воспользуйтесь методом {@link +java.util.TimeZone#getAvailableIDs()}. Обратите внимание, что это правило не применяется при +вставке события с помощью намерения {@link +android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе <a href="#intent-insert">Использование намерения для вставки события</a>, — в этом +случае используется часовой пояс по умолчанию.</li> + + <li>Для однократных событий необходимо указать {@link +android.provider.CalendarContract.EventsColumns#DTEND}. </li> + + + <li>Для повторяющихся событий необходимо указать {@link +android.provider.CalendarContract.EventsColumns#DURATION} в дополнение к {@link +android.provider.CalendarContract.EventsColumns#RRULE} или {@link +android.provider.CalendarContract.EventsColumns#RDATE}. Обратите внимание, что это правило не применяется при +вставке события с помощью намерения {@link +android.content.Intent#ACTION_INSERT INSERT}, как описано в разделе <a href="#intent-insert">Использование намерения для вставки события</a>, — в этом +случае можно использовать {@link +android.provider.CalendarContract.EventsColumns#RRULE} в сочетании с {@link android.provider.CalendarContract.EventsColumns#DTSTART} и {@link android.provider.CalendarContract.EventsColumns#DTEND}; кроме того, приложение «Календарь» + автоматически преобразует указанный период в продолжительность.</li> + +</ul> + +<p>Ниже представлен пример вставки события. Для простоты все это выполняется в потоке +пользовательского интерфейса. На практике же все вставки и обновления следует выполнять в +асинхронном потоке, чтобы переместить операцию в фоновый поток. Дополнительные сведения представлены в справке по +{@link android.content.AsyncQueryHandler}.</p> + + +<pre> +long calID = 3; +long startMillis = 0; +long endMillis = 0; +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2012, 9, 14, 7, 30); +startMillis = beginTime.getTimeInMillis(); +Calendar endTime = Calendar.getInstance(); +endTime.set(2012, 9, 14, 8, 45); +endMillis = endTime.getTimeInMillis(); +... + +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Events.DTSTART, startMillis); +values.put(Events.DTEND, endMillis); +values.put(Events.TITLE, "Jazzercise"); +values.put(Events.DESCRIPTION, "Group workout"); +values.put(Events.CALENDAR_ID, calID); +values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles"); +Uri uri = cr.insert(Events.CONTENT_URI, values); + +// get the event ID that is the last element in the Uri +long eventID = Long.parseLong(uri.getLastPathSegment()); +// +// ... do something with event ID +// +//</pre> + +<p class="note"><strong>Примечание.</strong> Ниже демонстрируется, как в примере кода выполняется захват +идентификатора события после создания этого события. Это самый простой способ получить идентификатор события. Зачастую +идентификатор события необходим для выполнения других действий с календарем — например, для добавления участников или +напоминаний о событии.</p> + + +<h3 id="update-event">Обновление событий</h3> + +<p>Когда ваше приложение хочет предоставить пользователю возможность изменить событие, мы рекомендуем использовать намерение +{@link android.content.Intent#ACTION_EDIT EDIT}, как описано в разделе +<a href="#intent-edit">Использование намерения для вставки события</a>. +Однако при необходимости вы можете редактировать события напрямую. Чтобы обновить +событие, можно указать +<code>_ID</code> события: либо в виде идентификатора, добавленного к URI({@link +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}), +либо в качестве первого элемента выделения. Выделение +должно начинаться с <code>"_id=?"</code>, а первым аргументом +<code>selectionArg</code> должен быть <code>_ID</code> события. Также можно обновлять +выделения без идентификаторов. Ниже представлен пример обновления +события. Это пример изменения названия события с помощью +метода +{@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}:</p> + + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long eventID = 188; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +Uri updateUri = null; +// The new title for the event +values.put(Events.TITLE, "Kickboxing"); +updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +int rows = getContentResolver().update(updateUri, values, null, null); +Log.i(DEBUG_TAG, "Rows updated: " + rows); </pre> + +<h3 id="delete-event">Удаление событий</h3> + +<p>Удалить событие можно по его {@link +android.provider.BaseColumns#_ID}, который добавлен в качестве идентификатора к URI, или с помощью +стандартного выделения. В случае использования добавленного идентификатора невозможно также выполнить и выделение. +Существует две версии операции удаления: для приложения и для адаптера синхронизации. При удалении +для приложения в столбце <em>deleted</em> устанавливается значение «1». Этот флаг +сообщает адаптеру синхронизации о том, что строка была удалена и информацию об удалении следует +передать серверу. При удалении для адаптера синхронизации событие удаляется из +базы данных вместе со всеми связанными с ним данными. Ниже представлен пример удаления +события для приложения по его {@link android.provider.BaseColumns#_ID}.</p> + + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long eventID = 201; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +Uri deleteUri = null; +deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +int rows = getContentResolver().delete(deleteUri, null, null); +Log.i(DEBUG_TAG, "Rows deleted: " + rows); +</pre> + +<h2 id="attendees">Таблица участников</h2> + +<p>В каждой строке таблицы {@link android.provider.CalendarContract.Attendees} +указан один участник или гость события. При вызове метода +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +возвращается список участников для события +с заданным {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}. +Этот {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +должен соответствовать {@link +android.provider.BaseColumns#_ID} определенного события.</p> + +<p>В таблице ниже указаны +поля, доступные для записи. При вставке нового участника необходимо указать все эти поля, кроме +<code>ATTENDEE_NAME</code>. +</p> + + +<table> + <tr> + <th>Константа</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}</td> + <td>Идентификатор события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}</td> + <td>Имя участника.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}</td> + <td>Адрес эл. почты участника.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}</td> + <td><p>Связь участника с событием. Одно из следующего:</p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}</li> + </ul> + </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}</td> + <td><p>Тип участника. Одно из следующего: </p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}</li> + </ul></td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}</td> + <td><p>Статус посещения события участником. Одно из следующего:</p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}</li> + </ul></td> + </tr> +</table> + +<h3 id="add-attendees">Добавление участников</h3> + +<p>Ниже представлен пример добавления одного участника события. Обратите внимание, что нужно в обязательном порядке +указать +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}.</p> + +<pre> +long eventID = 202; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Attendees.ATTENDEE_NAME, "Trevor"); +values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com"); +values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); +values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL); +values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED); +values.put(Attendees.EVENT_ID, eventID); +Uri uri = cr.insert(Attendees.CONTENT_URI, values); +</pre> + +<h2 id="reminders">Таблица напоминаний</h2> + +<p>В каждой строке таблицы {@link android.provider.CalendarContract.Reminders} +указано одно напоминание о событии. При вызове метода +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()}возвращается список напоминаний для события +с заданным +{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}.</p> + + +<p>В таблице ниже указаны поля, доступные для записи. При вставке нового +напоминания необходимо указать все эти поля. Обратите внимание, что адаптеры синхронизации задают +типы напоминаний, которые они поддерживают, в таблице {@link +android.provider.CalendarContract.Calendars}. Подробные сведения см. +в справке по +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS}.</p> + + +<table> + <tr> + <th>Константа</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}</td> + <td>Идентификатор события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#MINUTES}</td> + <td>Время срабатывания уведомления (в минутах) до начала события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#METHOD}</td> + <td><p>Метод уведомления, заданный на сервере. Одно из следующего:</p> + <ul> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}</li> + </ul></td> + </tr> +</table> + +<h3 id="add-reminders">Добавление напоминаний</h3> + +<p>Ниже представлен пример добавления напоминания в событие. Напоминание срабатывает за 15 +минут до начала события.</p> +<pre> +long eventID = 221; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Reminders.MINUTES, 15); +values.put(Reminders.EVENT_ID, eventID); +values.put(Reminders.METHOD, Reminders.METHOD_ALERT); +Uri uri = cr.insert(Reminders.CONTENT_URI, values);</pre> + +<h2 id="instances">Таблица экземпляров</h2> + +<p>В таблице +{@link android.provider.CalendarContract.Instances} содержатся данные о времени +начала и окончания повторений события. В каждой строке этой таблицы +представлено одно повторение события. Таблица экземпляров недоступна для записи; она предоставляет только +возможность запрашивать повторения событий. </p> + +<p>В таблице ниже перечислены некоторые из полей, которые можно запросить для экземпляра. Обратите внимание, +что часовой пояс задается параметрами +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +и +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES}.</p> + + +<table> + <tr> + <th>Константа</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#BEGIN}</td> + <td>Время начала экземпляра в формате UTC (в миллисекундах).</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END}</td> + <td>Время окончания экземпляра в формате UTC (в миллисекундах).</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END_DAY}</td> + + <td>День окончания экземпляра по юлианскому календарю относительно часового пояса +приложения «Календарь». + +</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END_MINUTE}</td> + + <td>Минута окончания экземпляра, вычисленная от полуночи по часовому поясу +приложения «Календарь».</td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#EVENT_ID}</td> + <td><code>_ID</code> события для этого экземпляра.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#START_DAY}</td> + <td>День начала экземпляра по юлианскому календарю относительно часового пояса приложения «Календарь». + </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#START_MINUTE}</td> + + <td>Минута начала экземпляра, вычисленная от полуночи по часовому поясу +приложения «Календарь». +</td> + + </tr> + +</table> + +<h3 id="query-instances">Запрос таблицы экземпляров</h3> + +<p>Чтобы запросить таблицу экземпляров, необходимо указать промежуток времени для запроса в +URI. В этом примере {@link android.provider.CalendarContract.Instances} +получает доступ к полю {@link +android.provider.CalendarContract.EventsColumns#TITLE} посредством своей реализации +интерфейса {@link android.provider.CalendarContract.EventsColumns}. +Другими словами, {@link +android.provider.CalendarContract.EventsColumns#TITLE} +возвращается посредством обращения к базе данных, а не путем запроса таблицы {@link +android.provider.CalendarContract.Instances} с необработанными данными.</p> + +<pre> +private static final String DEBUG_TAG = "MyActivity"; +public static final String[] INSTANCE_PROJECTION = new String[] { + Instances.EVENT_ID, // 0 + Instances.BEGIN, // 1 + Instances.TITLE // 2 + }; + +// The indices for the projection array above. +private static final int PROJECTION_ID_INDEX = 0; +private static final int PROJECTION_BEGIN_INDEX = 1; +private static final int PROJECTION_TITLE_INDEX = 2; +... + +// Specify the date range you want to search for recurring +// event instances +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2011, 9, 23, 8, 0); +long startMillis = beginTime.getTimeInMillis(); +Calendar endTime = Calendar.getInstance(); +endTime.set(2011, 10, 24, 8, 0); +long endMillis = endTime.getTimeInMillis(); + +Cursor cur = null; +ContentResolver cr = getContentResolver(); + +// The ID of the recurring event whose instances you are searching +// for in the Instances table +String selection = Instances.EVENT_ID + " = ?"; +String[] selectionArgs = new String[] {"207"}; + +// Construct the query with the desired date range. +Uri.Builder builder = Instances.CONTENT_URI.buildUpon(); +ContentUris.appendId(builder, startMillis); +ContentUris.appendId(builder, endMillis); + +// Submit the query +cur = cr.query(builder.build(), + INSTANCE_PROJECTION, + selection, + selectionArgs, + null); + +while (cur.moveToNext()) { + String title = null; + long eventID = 0; + long beginVal = 0; + + // Get the field values + eventID = cur.getLong(PROJECTION_ID_INDEX); + beginVal = cur.getLong(PROJECTION_BEGIN_INDEX); + title = cur.getString(PROJECTION_TITLE_INDEX); + + // Do something with the values. + Log.i(DEBUG_TAG, "Event: " + title); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(beginVal); + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime())); + } + }</pre> + +<h2 id="intents">Намерения календаря</h2> +<p>Вашему приложению не нужно запрашивать <a href="#manifest">разрешения</a> на чтение и запись данных календаря. Вместо этого можно использовать намерения, поддерживаемые приложением «Календарь» Android, для обработки операций чтения и записи в этом приложении. В таблице ниже указаны намерения, поддерживаемые поставщиком календаря.</p> +<table> + <tr> + <th>Действие</th> + <th>URI</th> + + <th>Описание</th> + <th>Дополнительные данные</th> + </tr> + <tr> + <td><br> + {@link android.content.Intent#ACTION_VIEW VIEW} <br></td> + <td><p><code>content://com.android.calendar/time/<ms_since_epoch></code></p> + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI}. +Пример использования этого намерения представлен в разделе <a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-view">Использование намерений для просмотра данных календаря</a>. + + </td> + <td>Открытие календаря во время, заданное параметром <code><ms_since_epoch></code>.</td> + <td>Отсутствуют.</td> + </tr> + <tr> + <td><p>{@link android.content.Intent#ACTION_VIEW VIEW} </p> + + </td> + <td><p><code>content://com.android.calendar/events/<event_id></code></p> + + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе <a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-view">Использование намерений для просмотра данных календаря</a>. + + </td> + <td>Просмотр события, указанного с помощью <code><event_id></code>.</td> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}<br> + <br> + <br> + {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}</td> + </tr> + + <tr> + <td>{@link android.content.Intent#ACTION_EDIT EDIT} </td> + <td><p><code>content://com.android.calendar/events/<event_id></code></p> + + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе <a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-edit">Использование намерения для редактирования события</a>. + + + </td> + <td>Редактирование события, указанного с помощью <code><event_id></code>.</td> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}<br> + <br> + <br> + {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}</td> + </tr> + + <tr> + <td>{@link android.content.Intent#ACTION_EDIT EDIT} <br> + <br> + {@link android.content.Intent#ACTION_INSERT INSERT} </td> + <td><p><code>content://com.android.calendar/events</code></p> + + Сослаться на URI также можно с помощью +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}. +Пример использования этого намерения представлен в разделе <a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-insert">Использование намерения для редактирования события</a>. + + </td> + + <td>Создание события.</td> + <td>Любые из дополнительных данных, указанных в таблице ниже.</td> + </tr> +</table> + +<p>В таблице ниже указаны дополнительные данные намерения, которые поддерживаются поставщиком календаря. +</p> +<table> + <tr> + <th>Дополнительные данные намерения</th> + <th>Описание</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}</td> + <td>Название события.</td> + </tr> + <tr> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}</td> + <td>Время начала события (в миллисекундах) от эпохи.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}</td> + + <td>Время окончания события (в миллисекундах) от эпохи.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}</td> + + <td>Логическое значение, обозначающее, что это событие на весь день. Значение может быть +<code>true</code> или <code>false</code>.</td> </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}</td> + + <td>Место проведения события.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}</td> + + <td>Описание события.</td> + </tr> + <tr> + <td> + {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}</td> + <td>Адреса эл. почты приглашенных (через запятую).</td> + </tr> + <tr> + <td> + {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}</td> + <td>Правило повторения для события.</td> + </tr> + <tr> + <td> + {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}</td> + + <td>Указывает на то, является ли событие общедоступным или закрытым.</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}</td> + + <td>Если событие считается как занятое или как свободное время, доступное для планирования.</td> + +</table> +<p>В разделах ниже указан порядок использования этих намерений.</p> + + +<h3 id="intent-insert">Использование намерения для вставки события</h3> + +<p>С помощью намерения {@link android.content.Intent#ACTION_INSERT INSERT} +ваше приложение может отправлять задачи вставки события прямо в приложение «Календарь». +Благодаря этому в <a href="#manifest">файл манифеста</a> вашего приложения не нужно включать разрешение {@link +android.Manifest.permission#WRITE_CALENDAR}.</p> + + +<p>Когда пользователи работают с приложением, в котором используется такой подход, приложение отправляет +их в «Календарь» для завершения добавления события. Намерение {@link +android.content.Intent#ACTION_INSERT INSERT} использует дополнительные поля +для предварительного указания в форме сведений о событии в приложении «Календарь». После этого пользователи +могут отменить событие, отредактировать форму или сохранить событие в своем +календаре.</p> + + + +<p>Ниже представлен фрагмент кода, в котором на 19 января 2012 г. планируется событие, которое будет проходить с +7:30 до 8:30. Однако следует учесть некоторые моменты, касающиеся этого примера кода.</p> + +<ul> + <li>В качестве URI в нем задается +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}.</li> + + <li>В нем используются дополнительные поля {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} и {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} для предварительного указания в форме +сведений о времени события. Значения времени должны быть указаны в формате UTC и в миллисекундах от +эпохи.</li> + + <li>В нем используется дополнительное поле {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +для предоставления списка участников, разделенных запятыми (их адреса эл. почты).</li> + +</ul> +<pre> +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2012, 0, 19, 7, 30); +Calendar endTime = Calendar.getInstance(); +endTime.set(2012, 0, 19, 8, 30); +Intent intent = new Intent(Intent.ACTION_INSERT) + .setData(Events.CONTENT_URI) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()) + .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()) + .putExtra(Events.TITLE, "Yoga") + .putExtra(Events.DESCRIPTION, "Group class") + .putExtra(Events.EVENT_LOCATION, "The gym") + .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY) + .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com"); +startActivity(intent); +</pre> + +<h3 id="intent-edit">Использование намерения для редактирования события</h3> + +<p>Событие можно отредактировать напрямую, как описано в разделе <a href="#update-event">Обновление событий</a>. Благодаря намерению {@link +android.content.Intent#ACTION_EDIT EDIT} приложение, +у которого нет разрешения, может делегировать редактирование события приложению «Календарь». +По завершении редактирования события в приложении «Календарь» пользователи возвращаются +в исходное приложение.</p> <p>Ниже представлен пример намерения, который задает +новый заголовок для указанного события и позволяет пользователям редактировать событие в приложении «Календарь».</p> + + +<pre>long eventID = 208; +Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +Intent intent = new Intent(Intent.ACTION_EDIT) + .setData(uri) + .putExtra(Events.TITLE, "My New Title"); +startActivity(intent);</pre> + +<h3 id="intent-view">Использование намерений для просмотра данных календаря</h3> +<p>Поставщик календаря позволяет использовать намерение {@link android.content.Intent#ACTION_VIEW VIEW} двумя различными способами:</p> +<ul> + <li>Открытие приложения «Календарь» на определенной дате.</li> + <li>Просмотр события.</li> + +</ul> +<p>Ниже представлен пример открытия приложения «Календарь» на определенной дате.</p> +<pre>// A date-time specified in milliseconds since the epoch. +long startMillis; +... +Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); +builder.appendPath("time"); +ContentUris.appendId(builder, startMillis); +Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(builder.build()); +startActivity(intent);</pre> + +<p>Ниже представлен пример открытия события для его просмотра.</p> +<pre>long eventID = 208; +... +Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(uri); +startActivity(intent); +</pre> + + +<h2 id="sync-adapter">Адаптеры синхронизации</h2> + + +<p>Существуют лишь незначительные различия в том, как приложение и адаптер синхронизации +получают доступ к поставщику календаря.</p> + +<ul> + <li>Адаптеру синхронизации необходимо указать, что он является таковым, задав для параметра {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} значение <code>true</code>.</li> + + + <li>Адаптеру синхронизации необходимо предоставить {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} и {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} в качестве параметров запроса в URI. </li> + + <li>Адаптер синхронизации имеет доступ на запись к большему числу столбцов, чем приложение или виджет. + Например, приложение может изменять только некоторые характеристики календаря, +такие как название, отображаемое имя, настройки видимости и +синхронизации. Тогда как адаптер синхронизации имеет доступ не только к этим столбцам, но и ко многим другим его характеристикам, +таким как цвет календаря, часовой пояс, уровень доступа, местоположение и т. д. +Однако адаптер синхронизации ограничен задаваемыми им параметрами <code>ACCOUNT_NAME</code> и +<code>ACCOUNT_TYPE</code>.</li> </ul> + +<p>Ниже представлен метод, который можно использовать, чтобы получить URI для использования вместе с адаптером синхронизации.</p> +<pre> static Uri asSyncAdapter(Uri uri, String account, String accountType) { + return uri.buildUpon() + .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true") + .appendQueryParameter(Calendars.ACCOUNT_NAME, account) + .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); + } +</pre> +<p>Пример реализации адаптера синхронизации (который не связан с приложением «Календарь») представлен в статье, посвященной +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">SampleSyncAdapter</a>. diff --git a/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd new file mode 100644 index 000000000000..4d07856f250e --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=Поставщик контактов +@jd:body +<div id="qv-wrapper"> +<div id="qv"> +<h2>Краткое описание</h2> +<ul> + <li>Репозиторий Android с пользовательскими данными.</li> + <li> + Синхронизация со службами в Интернете. + </li> + <li> + Интеграция с потоками данных из социальных сетей. + </li> +</ul> +<h2>Содержание документа</h2> +<ol> + <li> + <a href="#InformationTypes">Структура поставщика контактов</a> + </li> + <li> + <a href="#RawContactBasics">Необработанные контакты</a> + </li> + <li> + <a href="#DataBasics">Данные</a> + </li> + <li> + <a href="#ContactBasics">Контакты</a> + </li> + <li> + <a href="#Sources">Данные, полученные от адаптеров синхронизации</a> + </li> + <li> + <a href="#Permissions">Требуемые разрешения</a> + </li> + <li> + <a href="#UserProfile">Профиль пользователя</a> + </li> + <li> + <a href="#ContactsProviderMetadata">Метаданные поставщика контактов</a> + </li> + <li> + <a href="#Access">Доступ к поставщику контактов</a> + <li> + </li> + <li> + <a href="#SyncAdapters">Адаптеры синхронизации поставщика контактов</a> + </li> + <li> + <a href="#SocialStream">Потоки данных из социальных сетей</a> + </li> + <li> + <a href="#AdditionalFeatures">Дополнительные возможности поставщика контактов</a> + </li> +</ol> +<h2>Ключевые классы</h2> +<ol> + <li>{@link android.provider.ContactsContract.Contacts}</li> + <li>{@link android.provider.ContactsContract.RawContacts}</li> + <li>{@link android.provider.ContactsContract.Data}</li> + <li>{@code android.provider.ContactsContract.StreamItems}</li> +</ol> +<h2>Образцы кода по теме</h2> +<ol> + <li> + <a href="{@docRoot}resources/samples/ContactManager/index.html"> +Диспетчер контактов +</a> + </li> + <li> + <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html"> +Пример адаптера синхронизации</a> + </li> +</ol> +<h2>См. также:</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +Основные сведения о поставщике контента +</a> + </li> +</ol> +</div> +</div> +<p> + Поставщик контактов представляет собой эффективный и гибкий компонент Android, который управляет +центральным репозиторием устройства, в котором хранятся пользовательские данные. Поставщик контактов — это источник данных, +которые отображаются в приложении «Контакты» на вашем устройстве. Вы также можете получить доступ к этим данным в своем собственном +приложении и организовать обмен такими данными между устройством и службами в Интернете. Поставщик взаимодействует +с широким набором источников данных и пытается организовать управление как можно большим набором данных о каждом человеке, поэтому +организация поставщика довольно сложная. По этой причине API поставщика +содержит широкий набор классов-контрактов и интерфейсов, отвечающих как за получение данных, так и за их +изменение. +</p> +<p> + В этом руководстве рассматриваются следующие вопросы: +</p> + <ul> + <li> + основная структура поставщика; + </li> + <li> + порядок получения данных от поставщика; + </li> + <li> + порядок изменения данных в поставщике; + </li> + <li> + порядок записи адаптера синхронизации для синхронизации данных, полученных с вашего сервера, с данными в +поставщике контактов. + </li> + </ul> +<p> + При изучении данного материала подразумевается, что вы уже знакомы с основами поставщиков контента Android. Дополнительные сведения +о поставщиках контента Android представлены в руководстве +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">Основные +сведения о поставщике контента</a>. Пример +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">адаптера синхронизации</a> +служит примером использования такого приложения для обмена данными между поставщиком +контактов и приложением, размещенным в веб-службах Google. +</p> +<h2 id="InformationTypes">Структура поставщика контактов</h2> +<p> + Поставщик контактов представляет собой поставщик контента Android. Он содержит в себе три типа +данных о пользователе, каждый из которых указан в отдельной таблице, предоставляемой поставщиком, +как показано на рисунке 1. +</p> +<img src="{@docRoot}images/providers/contacts_structure.png" alt="" height="364" id="figure1" /> +<p class="img-caption"> + <strong>Рисунок 1.</strong> Структура таблицы поставщика контактов. +</p> +<p> + В качестве имен этих трех таблиц обычно используются названия соответствующих классов-контрактов. Эти классы +определяют константы для URI контента, названий столбцов и значений в столбцах этих таблиц. +</p> +<dl> + <dt> + Таблица {@link android.provider.ContactsContract.Contacts} + </dt> + <dd> + Строки в этой таблице содержат данные о разных пользователях, полученные путем агрегации строк необработанных контактов. + </dd> + <dt> + Таблица {@link android.provider.ContactsContract.RawContacts} + </dt> + <dd> + Строки в этой таблице содержат сводные данные о пользователе, относящиеся к пользовательскому аккаунту и его типу. + </dd> + <dt> + Таблица {@link android.provider.ContactsContract.Data} + </dt> + <dd> + Строки в этой таблице содержат сведения о необработанных контактах, такие как адреса эл. почты или номера телефонов. + </dd> +</dl> +<p> + Другие таблицы, представленные классами-контрактами в {@link android.provider.ContactsContract}, +представляют собой вспомогательные таблицы, которые поставщик контактов использует для управления своими операциями или поддержки +определенных функций, имеющихся в приложении устройства «Контакты» или приложениях для телефонной связи. +</p> +<h2 id="RawContactBasics">Необработанные контакты</h2> +<p> + Необработанный контакт представляет собой данные о пользователе, поступающие из одного аккаунта определенного +типа. Поскольку в качестве источника данных о пользователе в поставщике контактов может выступать сразу несколько онлайн-служб, +для одного и того же пользователя в поставщике контактов может существовать несколько необработанных контактов. + Это также позволяет пользователю объединять пользовательские данные из нескольких аккаунтов +одного и того же типа. +</p> +<p> + Большая часть данных необработанного контакта не хранится в таблице +{@link android.provider.ContactsContract.RawContacts}. Вместо этого они хранятся в одной или нескольких +строках в таблице {@link android.provider.ContactsContract.Data}. В каждой строке данных имеется +столбец {@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID}, +в котором содержится значение {@code android.provider.BaseColumns#_ID RawContacts._ID} его +родительской строки{@link android.provider.ContactsContract.RawContacts}. +</p> +<h3 id="RawContactsColumns">Важные столбцы необработанных контактов</h3> +<p> + В таблице 1 указаны столбцы таблицы {@link android.provider.ContactsContract.RawContacts}, +которые имеют большое значение. Обязательно ознакомьтесь с примечаниями, приведенными после этой таблицы. +</p> +<p class="table-caption" id="table1"> + <strong>Таблица 1.</strong> Важные столбцы необработанных контактов. +</p> +<table> + <tr> + <th scope="col">Название столбца</th> + <th scope="col">Использование</th> + <th scope="col">Примечания</th> + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + </td> + <td> + Имя аккаунта для типа аккаунта, который выступает в роли источника данных для необработанного контакта. + Например, имя аккаунта Google является одним из эл. адресов почты Gmail +владельца устройства. Дополнительные сведения +см. в следующей записи +{@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE}. + </td> + <td> + Формат этого имени зависит от типа аккаунта. Это не обязательно +адрес эл. почты. + </td> + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + </td> + <td> + Тип аккаунта, который выступает в роли источника данных для необработанного контакта. Например, тип аккаунта +Google — <code>com.google</code>. Всегда указывайте тип аккаунта +и идентификатор домена, которым вы владеете или управляете. Это позволит гарантировать уникальность +типа вашего аккаунта. + </td> + <td> + У типа аккаунта, выступающего в роли источника данных контактов, обычно имеется связанный с ним адаптер синхронизации, +который синхронизирует данные с поставщиком контактов. + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + </td> + <td> + Флаг deleted для необработанного контакта. + </td> + <td> + Этот флаг позволяет поставщику контактов хранить строку внутри себя до тех пор, +пока адаптеры синхронизации не смогут удалить эту строку с серверов, а затем +удалить ее из репозитория. + </td> + </tr> +</table> +<h4>Примечания</h4> +<p> + Ниже представлены важные примечания к таблице +{@link android.provider.ContactsContract.RawContacts}. +</p> +<ul> + <li> + Имя необработанного контакта не хранится в его строке в таблице +{@link android.provider.ContactsContract.RawContacts}. Вместо этого оно хранится в +таблице {@link android.provider.ContactsContract.Data} +в строке {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}. У необработанного контакта +имеется в таблице {@link android.provider.ContactsContract.Data} только одна строка такого типа. + </li> + <li> + <strong>Внимание!</strong> Чтобы использовать в строке необработанного контакта данные собственного аккаунта, строку +, сначала необходимо зарегистрировать его в классе {@link android.accounts.AccountManager}. Для этого предложите +пользователям добавить тип аккаунта и его имя в список аккаунтов. Если +не сделать этого, поставщик контактов автоматически удалит вашу строку необработанного контакта. + <p> + Например, если необходимо, чтобы в вашем приложении хранились данные контактов для веб-службы +в домене {@code com.example.dataservice}, и аккаунт пользователя службы + выглядит следующим образом:{@code becky.sharp@dataservice.example.com}, то пользователю сначала необходимо добавить +«тип» аккаунта ({@code com.example.dataservice}) и его «имя» +({@code becky.smart@dataservice.example.com}) прежде чем ваше приложение сможет добавлять строки необработанных контактов. + Это требование можно указать в документации для пользователей, а также можно предложить +пользователю добавить тип и имя своего аккаунта, либо реализовать оба этих варианта. Более подробно типы и имена аккаунтов +рассматриваются в следующем разделе. + </li> +</ul> +<h3 id="RawContactsExample">Источники данных необработанных контактов</h3> +<p> + Чтобы понять, что такое необработанный контакт, рассмотрим пример с пользователем Emily Dickinson, на устройстве которой имеется три +следующих аккаунта: +</p> +<ul> + <li><code>emily.dickinson@gmail.com</code>;</li> + <li><code>emilyd@gmail.com</code>;</li> + <li>Аккаунт belle_of_amherst в Twitter</li> +</ul> +<p> + Она включила функцию <em>Синхронизировать контакты</em> для всех трех этих аккаунтов +в настройках <em>Аккаунты</em>. +</p> +<p> + Предположим, что Emily Dickinson открывает браузер, входит в Gmail под именем +<code>emily.dickinson@gmail.com</code>, затем открывает +Контакты и добавляет новый контакт Thomas Higginson. Позже она снова входит в Gmail под именем +<code>emilyd@gmail.com</code> и отправляет письмо пользователю Thomas Higginson, который автоматически +добавляется в ее контакты. Также она подписана на новости от colonel_tom (аккаунт пользователя Thomas Higginson в Twitter) в +Twitter. +</p> +<p> + В результате этих действий поставщик контактов создает три необработанных контакта: +</p> +<ol> + <li> + Необработанный контакт Thomas Higginson связан с аккаунтом <code>emily.dickinson@gmail.com</code>. + Тип этого аккаунта — Google. + </li> + <li> + Второй необработанный контакт Thomas Higginson связан с аккаунтом <code>emilyd@gmail.com</code>. + Тип этого аккаунта — также Google. Второй необработанный контакт создается даже в том случае, +если его имя полностью совпадает с именем предыдущего контакта, поскольку первый +был добавлен для другого аккаунта. + </li> + <li> + Третий необработанный контакт Thomas Higginson связан с аккаунтом belle_of_amherst. Тип этого аккаунта +— Twitter. + </li> +</ol> +<h2 id="DataBasics">Данные</h2> +<p> + Как уже указывалось ранее, данные необработанного контакта +хранятся в строке {@link android.provider.ContactsContract.Data}, которая связана со значением +<code>_ID</code> необработанного контакта. Благодаря этому для одного необработанного контакта может существовать несколько экземпляров +одного и того же типа данных (например, адресов эл. почты или номеров телефонов). Например, если у контакта +Thomas Higginson для аккаунта {@code emilyd@gmail.com} (строка необработанного контакта Thomas Higginson, +связанная с учетной записью Google <code>emilyd@gmail.com</code>) имеется адрес эл. почты +<code>thigg@gmail.com</code> и служебный адрес эл. почты +<code>thomas.higginson@gmail.com</code>, то поставщик контактов сохраняет две строки +адреса эл. почты и связывает их с этим необработанным контактом. +</p> +<p> + Обратите внимание, что в этой таблице хранятся данные разных типов. Строки со сведениями об отображаемом имени, +номере телефона, адресе эл. почты, почтовом адресе, фото и веб-сайте хранятся в таблице +{@link android.provider.ContactsContract.Data}. Для упрощения управления этими данными в таблице +{@link android.provider.ContactsContract.Data} предусмотрены столбцы с описательными именами, +а также другие столбцы с универсальными именами. Содержимое в столбце с описательным именем имеет то же значение, +независимо от типа данных в строке, тогда как содержимое столбца с универсальным именем +может иметь разное значение в зависимости от типа данных. +</p> +<h3 id="DescriptiveColumns">Описательные имена столбцов</h3> +<p> + Вот некоторые примеры столбцов с описательными именами: +</p> +<dl> + <dt> + {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} + </dt> + <dd> + Значение в столбце <code>_ID</code> необработанного контакта для этих данных. + </dd> + <dt> + {@link android.provider.ContactsContract.Data#MIMETYPE} + </dt> + <dd> + Тип данных, хранящихся в этой строке, в виде настраиваемого типа MIME. Поставщик контактов +использует типы MIME, заданные в подклассах класса +{@link android.provider.ContactsContract.CommonDataKinds}. Эти типы MIME являются открытыми, и +их может использовать любое приложение или адаптер синхронизации, поддерживающие работу с поставщиком контактов. + </dd> + <dt> + {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} + </dt> + <dd> + Если этот тип данных для необработанного контакта встречается несколько раз, то +столбец {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} помечает флагом +строки данных, в которых содержатся основные данные для этого типа. Например, если +пользователь нажал и удерживает номер телефона контакта и выбрал параметр <strong>Использовать по умолчанию</strong>, +то в столбце {@link android.provider.ContactsContract.Data} с этим номером телефона +в соответствующем столбце {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} задается +значение, отличное от нуля. + </dd> +</dl> +<h3 id="GenericColumns">Универсальные имена столбцов</h3> +<p> + Существует 15 общедоступных столбцов с универсальными именами (<code>DATA1</code>–<code>DATA15</code>) +и четыре дополнительных столбца +(<code>SYNC1</code>–<code>SYNC4</code>), которые используются только адаптерами +синхронизации. Константы столбцов с универсальными именами применяются всегда, независимо от типа данных, +содержащихся в столбце. +</p> +<p> + Столбец <code>DATA1</code> является индексируемым. Поставщик контактов всегда использует этот столбец для +данных, которые, как он ожидает, будут наиболее часто являться целевыми в запросах. Например, +в строке с адресами эл. почты в этом столбце указывается фактический адрес эл. почты. +</p> +<p> + Обычно столбец <code>DATA15</code> зарезервирован для данных больших двоичных объектов +(BLOB), таких как миниатюры фотографий. +</p> +<h3 id="TypeSpecificNames">Имена столбцов по типам строк</h3> +<p> + Для упрощения работы со столбцами определенного типа строк в поставщике контактов +также предусмотрены константы для имен столбцов по типам строк, которые определены в подклассах класса +{@link android.provider.ContactsContract.CommonDataKinds}. Эти константы просто +присваивают одному и тому же имени столбца различные константы, что позволяет вам получать доступ к данным в строке +определенного типа. +</p> +<p> + Например, класс {@link android.provider.ContactsContract.CommonDataKinds.Email} определяет +константы имени столбца для строки {@link android.provider.ContactsContract.Data}, +в которой имеется тип MIME +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}. В этом классе содержится константа +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} для столбца адреса +эл. почты. Фактическое значение +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} — data1, которое совпадает +с универсальным именем столбца. +</p> +<p class="caution"> + <strong>Внимание!</strong> Не добавляйте свои настраиваемые данные в таблицу +{@link android.provider.ContactsContract.Data} с помощью строки, в которой имеется один из +предварительно заданных поставщиком типов MIME. В противном случае вы можете потерять данные или вызвать неполадки +в работе поставщика. Например, не следует добавлять строку с типом MIME +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE +Email.CONTENT_ITEM_TYPE}, в которой в столбце +<code>DATA1</code> вместо адреса эл. почты содержится имя пользователя. Если вы укажете в строке собственный настраиваемый тип MIME, вы можете свободно +указывать собственные имена столбцов по типам строк и использовать их так, как пожелаете. +</p> +<p> + На рисунке 2 показано, как столбцы с описательными именами и столбцы данных отображаются в строке +{@link android.provider.ContactsContract.Data}, а также как имена столбцов по типу строк «накладываются» +на универсальные имена столбцов. +</p> +<img src="{@docRoot}images/providers/data_columns.png" alt="How type-specific column names map to generic column names" height="311" id="figure2" /> +<p class="img-caption"> + <strong>Рисунок 2.</strong> Имена столбцов по типам строк и универсальные имена столбцов. +</p> +<h3 id="ColumnMaps">Классы имен столбцов по типам строк</h3> +<p> + В таблице 2 перечислены наиболее часто используемые классы имен столбцов по типам строк. +</p> +<p class="table-caption" id="table2"> + <strong>Таблица 2.</strong> Классы имен столбцов по типам строк</p> +<table> + <tr> + <th scope="col">Класс сопоставления</th> + <th scope="col">Тип данных</th> + <th scope="col">Примечания</th> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}</td> + <td>Данные об имени необработанного контакта, связанного с этой строкой данных.</td> + <td>У необработанного контакта имеется только одна строка такого типа.</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.Photo}</td> + <td>Основная фотография необработанного контакта, связанного с этой строкой данных.</td> + <td>У необработанного контакта имеется только одна строка такого типа.</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.Email}</td> + <td>Адрес эл. почты необработанного контакта, связанного с этой строкой данных.</td> + <td>У необработанного контакта может быть несколько адресов эл. почты.</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}</td> + <td>Почтовый адрес необработанного контакта, связанного с этой строкой данных.</td> + <td>У необработанного контакта может быть несколько почтовых адресов.</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}</td> + <td>Идентификатор, с помощью которого необработанный контакт связан с одной из групп в поставщике контактов.</td> + <td> + Группы представляют собой необязательный компонент для типа аккаунта и имени аккаунта. Дополнительные сведения о +группах представлены в разделе <a href="#Groups">Группы контактов</a>. + </td> + </tr> +</table> +<h3 id="ContactBasics">Контакты</h3> +<p> + Поставщик контактов объединяет строки с необработанными контактами для всех типов аккаунтов и имен аккаунтов +для создания <strong>контакта</strong>. Это позволяет упростить отображение и изменение всех данных, +собранных в отношении пользователя. Поставщик контактов управляет процессом создания строк +контактов и агрегированием необработанных контактов, у которых имеется строка контакта. Ни приложениям, ни +адаптерам синхронизации не разрешается добавлять контакты, а некоторые столбцы в строке контакта доступны только для чтения. +</p> +<p class="note"> + <strong>Примечание.</strong> Если попытаться добавить контакт в поставщик контактов с помощью метода +{@link android.content.ContentResolver#insert(Uri,ContentValues) insert()}, будет выдано исключение +{@link java.lang.UnsupportedOperationException}. Если вы попытаетесь обновить столбец, +доступный только для чтения, это действие будет проигнорировано. +</p> +<p> + При добавлении нового необработанного контакта, +который не соответствует ни одному из существующих контактов, поставщик контактов создает новый контакт. Поставщик поступает аналогично в случае, если +данные в строке существующего необработанного контакта изменяются таким образом, что они больше не соответствуют контакту, +с которым они ранее были связаны. При создании приложением или адаптером синхронизации нового контакта, +который <em></em> соответствует существующему контакту, то новый контакт объединяется с +существующим контактом. +</p> +<p> + Поставщик контактов связывает строку контакта с его строками необработанного контакта посредством столбца +<code>_ID</code> строки контакта в таблице +{@link android.provider.ContactsContract.Contacts Contacts}. Столбец <code>CONTACT_ID</code> в таблице необработанных контактов +{@link android.provider.ContactsContract.RawContacts} содержит значения <code>_ID</code> для +строки контактов, связанной с каждой строкой необработанных контактов. +</p> +<p> + В таблице {@link android.provider.ContactsContract.Contacts} также имеется столбец +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY}, который выступает в роли +«постоянной ссылки» на строку контакта. Поскольку поставщик контактов автоматически сохраняет контакты, +в ответ на агрегирование или синхронизацию он может изменить значение {@code android.provider.BaseColumns#_ID} +строки контакта. Даже если это произойдет, URI контента +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}, объединенный с +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} контакта, будет +по-прежнему указывать на строку контакта, поэтому вы можете смело использовать +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} +для сохранения ссылок на «избранные» контакты и др. Столбец имеет собственный формат, +который не связан с форматом столбца{@code android.provider.BaseColumns#_ID}. +</p> +<p> + На рисунке 3 показаны взаимосвязи этих трех основных таблиц друг с другом. +</p> +<img src="{@docRoot}images/providers/contacts_tables.png" alt="Contacts provider main tables" height="514" id="figure4" /> +<p class="img-caption"> + <strong>Рисунок 3</strong>. Взаимосвязи между таблицами контактов, необработанных контактов и сведений. +</p> +<h2 id="Sources">Данные, полученные от адаптеров синхронизации</h2> +<p> + Пользователь вводит данные контактов прямо на устройстве, однако данные также поступают в поставщик контактов +из веб-служб посредством <strong>адаптеров синхронизации</strong>, что позволяет автоматизировать +обмен данными между устройством и службами в Интернете. Адаптеры синхронизации выполняются в фоновом режиме под +управлением системы, и для управления данными они вызывают методы +{@link android.content.ContentResolver}. +</p> +<p> + В Android веб-служба, с которой работает адаптер синхронизации, определяется по типу аккаунта. + Каждый адаптер синхронизации работает с одним типом аккаунта, однако он может поддерживать несколько имен аккаунтов +такого типа. Вкратце типы и имена аккаунтов рассматриваются +в разделе <a href="#RawContactsExample">Источники данных необработанных контактов</a>. Указанные ниже определения позволяют +более точно охарактеризовать связь между типами и именами аккаунтов и адаптерами синхронизации и службами. +</p> +<dl> + <dt> + Тип аккаунта + </dt> + <dd> + Определяет службу, в которой пользователь хранит данные. В большинстве случаев для работы со +службой пользователю необходимо пройти проверку подлинности. Например, Google Контакты представляет собой тип аккаунтов, обозначаемый кодом +<code>google.com</code>. Это значение соответствует типу аккаунта, используемого +{@link android.accounts.AccountManager}. + </dd> + <dt> + Имя аккаунта + </dt> + <dd> + Определяет конкретный аккаунт или имя для входа для типа аккаунта. Аккаунты «Контакты Google» +— это то же, что и аккаунты Google, в качестве имени которых используется адрес эл. почты. + В других службах может использоваться имя пользователя, состоящее из одного слова, или числовой идентификатор. + </dd> +</dl> +<p> + Типы аккаунтов не обязательно должны быть уникальными. Пользователь может создать несколько аккаунтов Google Контакты +и загрузить данные из них в поставщик контактов; это может произойти в случае, если у пользователя имеется один набор +персональных контактов для личного аккаунта, и другой набор — для служебного аккаунта. Имена +аккаунтов обычно уникальные. Вместе они формируют определенный поток данных между поставщиком контактов +и внешними службами. +</p> +<p> + Если необходимо передать данные из службы в поставщик контактов, необходимо создать +собственный адаптер синхронизации. Дополнительные сведения об этом представлены в разделе +<a href="#SyncAdapters">Адаптеры синхронизации поставщика контактов</a>. +</p> +<p> + На рисунке 4 показано, какую роль выполняет поставщик контактов в потоке передачи данных +о пользователях. В области, отмеченной на рисунке как sync adapters, каждый адаптер промаркирован в соответствии с поддерживаемым им типом аккаунтов. +</p> +<img src="{@docRoot}images/providers/ContactsDataFlow.png" alt="Flow of data about people" height="252" id="figure5" /> +<p class="img-caption"> + <strong>Рисунок 4</strong>. Поток передачи данных через поставщика контактов. +</p> +<h2 id="Permissions">Требуемые разрешения</h2> +<p> + Приложения, которым требуется доступ к поставщику контактов, +должны запросить указанные ниже разрешения. +</p> +<dl> + <dt>Доступ на чтение одной или нескольких таблиц</dt> + <dd> + {@link android.Manifest.permission#READ_CONTACTS}, указанный в файле +<code>AndroidManifest.xml</code> с элементом +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> в виде +<code><uses-permission android:name="android.permission.READ_CONTACTS"></code>. + </dd> + <dt>Доступ на запись в одну или несколько таблиц</dt> + <dd> + {@link android.Manifest.permission#WRITE_CONTACTS}, указанный в файле +<code>AndroidManifest.xml</code> с элементом +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> в виде +<code><uses-permission android:name="android.permission.WRITE_CONTACTS"></code>. + </dd> +</dl> +<p> + Эти разрешения не распространяются на работу с данными профиля пользователя. Профиль пользователя +и требуемые разрешения для работы с ним рассматриваются в разделе +<a href="#UserProfile">Профиль пользователя</a> ниже. +</p> +<p> + Помните, что данные о контактах пользователя являются личными и конфиденциальными. Пользователи заботятся о своей конфиденциальности, +поэтому не хотят, чтобы приложения собирали данные о них самих или их контактах. + Если пользователю не до конца ясно, для чего требуется разрешение на доступ к данным контактов, они могут присвоить +вашему приложению низкий рейтинг или вообще откажутся его устанавливать. +</p> +<h2 id="UserProfile">Профиль пользователя</h2> +<p> + В таблице {@link android.provider.ContactsContract.Contacts} имеется всего одна строка, содержащая +данные профиля для устройства пользователя. Эти данные описывают <code>user</code> устройства, +а не один из контактов пользователя. Строка контактов профиля связана со строкой +необработанных контактов в каждой из систем, в которой используется профиль. + В каждой строке необработанного контакта профиля может содержаться несколько строк данных. Константы для доступа к профилю пользователя +представлены в классе {@link android.provider.ContactsContract.Profile}. +</p> +<p> + Для доступа к профилю пользователя требуются особые разрешения. Кроме разрешений +{@link android.Manifest.permission#READ_CONTACTS} и +{@link android.Manifest.permission#WRITE_CONTACTS}, которые требуются для чтения и записи, для доступа к профилю пользователя необходимы разрешения +{@code android.Manifest.permission#READ_PROFILE} и +{@code android.Manifest.permission#WRITE_PROFILE} +на чтение и запись соответственно. +</p> +<p> + Всегда следует помнить, что профиль пользователя представляет собой конфиденциальную информацию. Разрешение +{@code android.Manifest.permission#READ_PROFILE} предоставляет вам доступ к личной информации на устройстве +пользователя. В описании своего приложения обязательно укажите, для +чего вам требуется доступ к профилю пользователя. +</p> +<p> + Чтобы получить строку контакта с профилем пользователя, вызовите метод +{@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}. Задайте для URI контента значение +{@link android.provider.ContactsContract.Profile#CONTENT_URI} и не указывайте никаких критериев +выборки. Этот URI контента также можно использовать в качестве основного URI для получения +необработанных контактов или данных профиля. Ниже представлен фрагмент кода для получения данных профиля. +</p> +<pre> +// Sets the columns to retrieve for the user profile +mProjection = new String[] + { + Profile._ID, + Profile.DISPLAY_NAME_PRIMARY, + Profile.LOOKUP_KEY, + Profile.PHOTO_THUMBNAIL_URI + }; + +// Retrieves the profile from the Contacts Provider +mProfileCursor = + getContentResolver().query( + Profile.CONTENT_URI, + mProjection , + null, + null, + null); +</pre> +<p class="note"> + <strong>Примечание.</strong> Если при извлечении несколько строк контактов необходимо определить, +какой из них является профилем пользователя, обратитесь к столбцу +{@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} этой строки. Если в этом столбце +указано значение «1», то в это строке находится профиль пользователя. +</p> +<h2 id="ContactsProviderMetadata">Метаданные поставщика контактов</h2> +<p> + Поставщик контактов управляет данными, которые используются для отслеживания состояния данных контакта в +репозитории. Эти метаданные о репозитории хранятся в различных местах, включая +строки необработанных контактов, данные и строки таблицы контактов, таблицу +{@link android.provider.ContactsContract.Settings} и таблицу +{@link android.provider.ContactsContract.SyncState}. В таблице ниже +указаны значения каждого из этих фрагментов метаданных. +</p> +<p class="table-caption" id="table3"> + <strong>Таблица 3.</strong> Метаданные в поставщике контактов</p> +<table> + <tr> + <th scope="col">Таблица</th> + <th scope="col">Столбец</th> + <th scope="col">Значения</th> + <th scope="col">Описание</th> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.RawContacts}</td> + <td rowspan="2">{@link android.provider.ContactsContract.SyncColumns#DIRTY}</td> + <td>«0» — с момента последней синхронизации изменения не вносились.</td> + <td rowspan="2"> + Служит для отметки необработанных контактов, которые были изменены на устройстве и которые необходимо снова синхронизировать с +сервером. Значение устанавливается автоматически поставщиком контактов при обновлении строк приложениями +Android. + <p> + Адаптеры синхронизации, которые вносят изменения в необработанные контакты или таблицы данных, должны всегда добавлять к используемому ими URI +контента строку +{@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}. Это позволяет предотвратить пометку таких строк поставщиком как «грязных». + В противном случае изменения, внесенные адаптером синхронизации, будут рассматриваться как локальные изменения, +которые подлежат отправке на сервер, даже если источником таких изменений был сам сервер. + </p> + </td> + </tr> + <tr> + <td>«1» — с момента последней синхронизации были внесены изменения, которые необходимо отразить и на сервере.</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.RawContacts}</td> + <td>{@link android.provider.ContactsContract.SyncColumns#VERSION}</td> + <td>Номер версии этой строки.</td> + <td> + Поставщик контактов автоматически увеличивает это значение при каждом +изменении в строке или в связанных с ним данных. + </td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.Data}</td> + <td>{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}</td> + <td>Номер версии этой строки.</td> + <td> + Поставщик контактов автоматически увеличивает это значение при каждом +изменении в строке данных. + </td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.RawContacts}</td> + <td>{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}</td> + <td> + Строковое значение, которое служит уникальным идентификатором данного необработанного контакта в аккаунте, +в котором он был создан. + </td> + <td> + Когда адаптер синхронизации создает новый необработанный контакт, в этом столбце следует указать +уникальный идентификатор этого необработанного контакта на сервере. Когда же приложение Android создает +новый необработанный контакт, то приложению следует оставить этот столбец пустым. Это служит сигналом для +адаптера синхронизации, чтобы создать новый необработанный контакт на сервере и +получить значение {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}. + <p> + В частности, идентификатор источника должен быть <strong>уникальным</strong> для каждого типа аккаунта +и неизменным при синхронизации. + </p> + <ul> + <li> + Уникальный: у каждого необработанного контакта для аккаунта должен быть свой собственный идентификатор источника. Если +не применить это требование, у вас обязательно возникнут проблемы с приложением «Контакты». + Обратите внимание, что два необработанных контакта одного и того же <em>типа</em> аккаунта могут +иметь одинаковый идентификатор источника. Например, необработанный контакт Thomas Higginson для +аккаунта {@code emily.dickinson@gmail.com} может иметь такой же +идентификатор источника, что и контакт Thomas Higginson для аккаунта +{@code emilyd@gmail.com}. + </li> + <li> + Стабильный: идентификаторы источников представляют собой неизменную часть данных онлайн-службы для +необработанного контакта. Например, если пользователь выполняет повторную синхронизацию после очистки хранилища контактов в +настройках приложения, идентификаторы источников восстановленных необработанных контактов должны быть такими же, +как и прежде. Если не применить это требование, перестанут +работать ярлыки. + </li> + </ul> + </td> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.Groups}</td> + <td rowspan="2">{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}</td> + <td>«0» — контакты, представленные в этой группе, не должны отображаться в интерфейсах пользователя приложений Android.</td> + <td> + Этот столбец предназначен для сведений о совместимости с серверами, позволяющими пользователям скрывать контакты +в определенных группах. + </td> + </tr> + <tr> + <td>«1» — контакты, представленные в этой группе, могут отображаться в интерфейсах пользователя приложений.</td> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.Settings}</td> + <td rowspan="2"> + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE}</td> + <td> + «0» — для этого аккаунта и аккаунтов этого типа контакты, не принадлежащие к группе, +не отображаются в интерфейсах пользователя приложений Android. + </td> + <td rowspan="2"> + По умолчанию контакты скрыты, если ни один из их необработанных контактов не принадлежит группе +(принадлежность необработанного контакта к группе указывается в одном или нескольких строках +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} +в таблице {@link android.provider.ContactsContract.Data}). + Установив этот флаг в строке таблицы {@link android.provider.ContactsContract.Settings} +для типа аккаунта и имени аккаунта, вы можете принудительно сделать видимыми контакты, не принадлежащие какой-либо группе. + Один из вариантов использования этого флага — отображение контактов с серверов, на которых не используются группы. + </td> + </tr> + <tr> + <td> + «1» — для этого аккаунта и аккаунтов этого типа контакты, не принадлежащие к группе, +отображаются в интерфейсах пользователя приложений Android. + </td> + + </tr> + <tr> + <td>{@link android.provider.ContactsContract.SyncState}</td> + <td>(все)</td> + <td> + Эта таблица используется для хранения метаданных для вашего адаптера синхронизации. + </td> + <td> + С помощью этой таблицы вы можете на постоянной основе хранить на устройстве сведения о состоянии синхронизации и другие связанные с +синхронизацией данные. + </td> + </tr> +</table> +<h2 id="Access">Доступ к поставщику контактов</h2> +<p> + В этом разделе рассматриваются инструкции по получению доступа к данным из поставщика контактов, в частности, +следующие: +</p> +<ul> + <li> + запросы объектов; + </li> + <li> + пакетное изменение; + </li> + <li> + получение и изменение данных с помощью намерений; + </li> + <li> + целостность данных. + </li> +</ul> +<p> + Дополнительные сведения о внесении изменений с помощью адаптеров синхронизации представлены в разделе +<a href="#SyncAdapters">Адаптеры синхронизации поставщика контактов</a>. +</p> +<h3 id="Entities">Запрос объектов</h3> +<p> + Таблицы поставщика контактов имеют иерархическую структуру, поэтому зачастую полезно +извлекать строку и все связанные с ней ее дочерние строки. Например, для отображения +всей информации о пользователе вы, возможно, захотите извлечь все строки +{@link android.provider.ContactsContract.RawContacts} для одной строки +{@link android.provider.ContactsContract.Contacts} или все строки +{@link android.provider.ContactsContract.CommonDataKinds.Email} для одной строки +{@link android.provider.ContactsContract.RawContacts}. Чтобы упростить этот процесс, в поставщике контактов имеются +<strong>объекты</strong>, которые выступают в роли соединителей базы данных между +таблицами. +</p> +<p> + Объект представляет собой подобие таблицы, состоящей из выбранных столбцов родительской таблицы и ее дочерней таблицы. + При запросе объекта вы предоставляете проекцию и критерии поиска на основе доступных в +объекте столбцов. В результате вы получаете объект {@link android.database.Cursor}, в котором +содержится одна строка для каждой извлеченной строки дочерней таблицы. Например, если запросить объект +{@link android.provider.ContactsContract.Contacts.Entity} для имени контакта и все строки +{@link android.provider.ContactsContract.CommonDataKinds.Email} для всех необработанных контактов, +соответствующих этому имени, вы получите объект {@link android.database.Cursor}, содержащий по одной строке +для каждой строки {@link android.provider.ContactsContract.CommonDataKinds.Email}. +</p> +<p> + Использование объектов упрощает запросы. С помощью объекта можно извлечь сразу все данные для +контакта или необработанного контакта, вместо того, чтобы сначала делать запрос к родительской таблице для получения +идентификатора и последующего запроса к дочерней таблице с использованием полученного идентификатора. Кроме того, поставщик контактов обрабатывает запрос +объекта за одну транзакцию, что гарантирует внутреннюю согласованность полученных +данных. +</p> +<p class="note"> + <strong>Примечание.</strong> Объект обычно содержит не все столбцы +родительской и дочерней таблиц. Если вы попытаетесь изменить имя столбца, который отсутствует в списке +констант имени столбца для объекта, вы получите {@link java.lang.Exception}. +</p> +<p> + Ниже представлен пример кода для получения всех строк необработанного контакта для контакта. Это фрагмент +более крупного приложения, в котором имеются две операции: «основная» и «для получения сведений». Основная операция +служит для получения списка строк контактов; при выборе пользователем контакта операция отправляет его идентификатор в операцию +для получения сведений. Операция для получения сведений использует объект {@link android.provider.ContactsContract.Contacts.Entity} +для отображения всех строк данных, полученных ото всех необработанных контактов, которые связаны с выбранным +контактом. +</p> +<p> + Вот фрагмент кода операции «для получения сведений»: +</p> +<pre> +... + /* + * Appends the entity path to the URI. In the case of the Contacts Provider, the + * expected URI is content://com.google.contacts/#/entity (# is the ID value). + */ + mContactUri = Uri.withAppendedPath( + mContactUri, + ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); + + // Initializes the loader identified by LOADER_ID. + getLoaderManager().initLoader( + LOADER_ID, // The identifier of the loader to initialize + null, // Arguments for the loader (in this case, none) + this); // The context of the activity + + // Creates a new cursor adapter to attach to the list view + mCursorAdapter = new SimpleCursorAdapter( + this, // the context of the activity + R.layout.detail_list_item, // the view item containing the detail widgets + mCursor, // the backing cursor + mFromColumns, // the columns in the cursor that provide the data + mToViews, // the views in the view item that display the data + 0); // flags + + // Sets the ListView's backing adapter. + mRawContactList.setAdapter(mCursorAdapter); +... +@Override +public Loader<Cursor> onCreateLoader(int id, Bundle args) { + + /* + * Sets the columns to retrieve. + * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. + * DATA1 contains the first column in the data row (usually the most important one). + * MIMETYPE indicates the type of data in the data row. + */ + String[] projection = + { + ContactsContract.Contacts.Entity.RAW_CONTACT_ID, + ContactsContract.Contacts.Entity.DATA1, + ContactsContract.Contacts.Entity.MIMETYPE + }; + + /* + * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw + * contact collated together. + */ + String sortOrder = + ContactsContract.Contacts.Entity.RAW_CONTACT_ID + + " ASC"; + + /* + * Returns a new CursorLoader. The arguments are similar to + * ContentResolver.query(), except for the Context argument, which supplies the location of + * the ContentResolver to use. + */ + return new CursorLoader( + getApplicationContext(), // The activity's context + mContactUri, // The entity content URI for a single contact + projection, // The columns to retrieve + null, // Retrieve all the raw contacts and their data rows. + null, // + sortOrder); // Sort by the raw contact ID. +} +</pre> +<p> + По завершении загрузки {@link android.app.LoaderManager} выполняет обратный вызов метода +{@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) +onLoadFinished()}. Одним их входящих аргументов для этого метода является +{@link android.database.Cursor} с результатом запроса. В своем приложении вы можете +получить данные из этого объекта {@link android.database.Cursor} для его отображения или дальнейшей работы с ним. +</p> +<h3 id="Transactions">Пакетное изменение</h3> +<p> + При каждой возможности данные в поставщике контактов следует вставлять, обновлять и удалять в +«пакетном режиме» путем создания {@link java.util.ArrayList} из объектов +{@link android.content.ContentProviderOperation} и вызова метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. Поскольку +поставщик контактов выполняет все операции в методе +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} за одну +транзакцию, ваши изменения всегда будут находиться в рамках репозитория контактов и +всегда будут согласованными. Пакетное изменение также упрощает вставку необработанного контакта одновременно с его подробными +данными. +</p> +<p class="note"> + <strong>Примечание.</strong> Чтобы изменить <em>отдельный</em> необработанный контакт, рекомендуется отправить намерение в +приложение для работы с контактами на устройстве вместо обработки изменения в вашем приложении. +Более подробно эта операция описана в разделе +<a href="#Intents">Получение и изменение данных с помощью намерений</a>. +</p> +<h4>Пределы</h4> +<p> + Пакетное изменение большого количества операций может заблокировать выполнение других процессов, +что может негативно сказаться на работе пользователя с приложением. Чтобы упорядочить все необходимые изменения +в рамках как можно меньшего количества отдельных списков и при этом предотвратить +блокирование работы системы, следует задать <strong>пределы</strong> для одной или нескольких операций. + Предел представляет собой объект {@link android.content.ContentProviderOperation}, в качестве значения параметра +{@link android.content.ContentProviderOperation#isYieldAllowed()} которого задано +<code>true</code>. При достижении поставщиком контактов предела он приостанавливает свою работу, +чтобы могли выполняться другие процессы, и закрывает текущую транзакцию. Когда поставщик запускается снова, он +выполняет следующую операцию в {@link java.util.ArrayList} и запускает +новую транзакцию. +</p> +<p> + Использование пределов позволяет за один вызов метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} выполнять более одной транзакции. По этой причине +вам следует задать предел для последней операции с набором связанных строк. + Например, необходимо задать предел для последней операции в наборе, которая +добавляет строки необработанного контакта и связанные с ним строки данных, или для последней операции с набором строк, связанных +с одним контактом. +</p> +<p> + Пределы также являются единицами атомарных операций. Все операции доступа в промежутке между двумя пределами завершаются либо +успехом, либо сбоем в рамках одной единицы. Если любой из пределов не задан, наименьшей +атомарной операцией считается весь пакет операций. Использование пределов позволяет предотвратить +снижение производительности системы и одновременно обеспечить выполнение набора +операций на атомарном уровне. +</p> +<h4>Изменение обратных ссылок</h4> +<p> + При вставке новой строки необработанного контакта и связанных с ним рядов данных в виде набора объектов +{@link android.content.ContentProviderOperation} вам приходится связывать строки данных +со строкой необработанного контакта путем вставки значения +{@code android.provider.BaseColumns#_ID} необработанного контакта в виде значения +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Однако это значение +недоступно, когда вы создаете{@link android.content.ContentProviderOperation} +для строки данных, поскольку вы еще не применили +{@link android.content.ContentProviderOperation} для строки необработанного контакта. Чтобы обойти это ограничение, +в классе {@link android.content.ContentProviderOperation.Builder} предусмотрен метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()}. + С помощью этого метода можно вставлять или изменять столбец +с результатом предыдущей операции. +</p> +<p> + В методе {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +предусмотрено два аргумента: +</p> + <dl> + <dt> + <code>key</code> + </dt> + <dd> + Ключ для пары «ключ-значение». Значением этого аргумента должно быть имя столбца +в таблице, которую вы изменяете. + </dd> + <dt> + <code>previousResult</code> + </dt> + <dd> + Индекс значения, начинающийся с «0», в массиве объектов +{@link android.content.ContentProviderResult} из метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. По мере +выполнения пакетных операций результат каждой операции сохраняется в +промежуточном массиве результатов. Значением <code>previousResult</code> является индекс +одного из этих результатов, который извлекается и хранится со значением +<code>key</code>. Благодаря этому можно вставить новую запись необработанного контакта и получить обратно его значение +{@code android.provider.BaseColumns#_ID}, а затем создать «обратную ссылку» на +значение при добавлении строки {@link android.provider.ContactsContract.Data}. + <p> + Целиком весь результат создается при первом вызове метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. +Размер результата равен размеру {@link java.util.ArrayList} предоставленных вами объектов +{@link android.content.ContentProviderOperation}. Однако для всех +элементов в массиве результатов присваивается значение <code>null</code>, и при попытке +воспользоваться обратной ссылкой на результат еще не выполненной операции метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +выдает {@link java.lang.Exception}. + + </p> + </dd> + </dl> +<p> + Ниже представлены фрагменты кода для вставки нового необработанного контакта и его данных в пакетном режиме. Они включают +код, который задает предел и использует обратную ссылку. Эти фрагменты +представляют собой расширенную версию метода <code>createContacEntry()</code>, который входит в класс +<code>ContactAdder</code> в примере приложения +<code><a href="{@docRoot}resources/samples/ContactManager/index.html"> + Contact Manager</a></code>. +</p> +<p> + Первый фрагмент кода служит для извлечения данных контакта из пользовательского интерфейса. На этом этапе пользователь уже выбрал +аккаунт, для которого необходимо добавить новый необработанный контакт. +</p> +<pre> +// Creates a contact entry from the current UI values, using the currently-selected account. +protected void createContactEntry() { + /* + * Gets values from the UI + */ + String name = mContactNameEditText.getText().toString(); + String phone = mContactPhoneEditText.getText().toString(); + String email = mContactEmailEditText.getText().toString(); + + int phoneType = mContactPhoneTypes.get( + mContactPhoneTypeSpinner.getSelectedItemPosition()); + + int emailType = mContactEmailTypes.get( + mContactEmailTypeSpinner.getSelectedItemPosition()); +</pre> +<p> + В следующем фрагменте кода создается операция для вставки строки необработанного контакта в таблицу +{@link android.provider.ContactsContract.RawContacts}: +</p> +<pre> + /* + * Prepares the batch operation for inserting a new raw contact and its data. Even if + * the Contacts Provider does not have any data for this person, you can't add a Contact, + * only a raw contact. The Contacts Provider will then add a Contact automatically. + */ + + // Creates a new array of ContentProviderOperation objects. + ArrayList<ContentProviderOperation> ops = + new ArrayList<ContentProviderOperation>(); + + /* + * Creates a new raw contact with its account type (server type) and account name + * (user's account). Remember that the display name is not stored in this row, but in a + * StructuredName data row. No other data is required. + */ + ContentProviderOperation.Builder op = + ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); +</pre> +<p> + Затем код создает строки данных для строк отображаемого имени, телефона и адреса эл. почты. +</p> +<p> + Каждый объект компонента операции использует метод +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +для получения +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}. Ссылка возвращается +обратно к объекту {@link android.content.ContentProviderResult} из первой операции, +в результате чего добавляется строка необработанного контакта и возвращается его новое значение +{@code android.provider.BaseColumns#_ID}. После этого каждая строка данных автоматически связывается по своему +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} +с новой строкой {@link android.provider.ContactsContract.RawContacts}, которой она принадлежит. +</p> +<p> + Объект {@link android.content.ContentProviderOperation.Builder}, который добавляет строку адреса эл. почты, +помечается флагом с помощью метода {@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) +withYieldAllowed()}, который задает предел: +</p> +<pre> + // Creates the display name for the new raw contact, as a StructuredName data row. + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * withValueBackReference sets the value of the first argument to the value of + * the ContentProviderResult indexed by the second argument. In this particular + * call, the raw contact ID column of the StructuredName data row is set to the + * value of the result returned by the first operation, which is the one that + * actually adds the raw contact row. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to StructuredName + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + + // Sets the data row's display name to the name in the UI. + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); + + // Inserts the specified phone number and type as a Phone data row + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * Sets the value of the raw contact id column to the new raw contact ID returned + * by the first operation in the batch. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to Phone + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + + // Sets the phone number and type + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); + + // Inserts the specified email and type as a Phone data row + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * Sets the value of the raw contact id column to the new raw contact ID returned + * by the first operation in the batch. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to Email + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + + // Sets the email address and type + .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); + + /* + * Demonstrates a yield point. At the end of this insert, the batch operation's thread + * will yield priority to other threads. Use after every set of operations that affect a + * single contact, to avoid degrading performance. + */ + op.withYieldAllowed(true); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); +</pre> +<p> + В последнем фрагменте кода представлен вызов метода +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} +для вставки нового необработанного контакта и его строк данных. +</p> +<pre> + // Ask the Contacts Provider to create a new contact + Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" + + mSelectedAccount.getType() + ")"); + Log.d(TAG,"Creating contact: " + name); + + /* + * Applies the array of ContentProviderOperation objects in batch. The results are + * discarded. + */ + try { + + getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + } catch (Exception e) { + + // Display a warning + Context ctx = getApplicationContext(); + + CharSequence txt = getString(R.string.contactCreationFailure); + int duration = Toast.LENGTH_SHORT; + Toast toast = Toast.makeText(ctx, txt, duration); + toast.show(); + + // Log exception + Log.e(TAG, "Exception encountered while inserting contact: " + e); + } +} +</pre> +<p> + С помощью пакетных операций можно также реализовать<strong>оптимистическое управление параллелизмом</strong>. +Это метод применения транзакций изменения без необходимости блокировать базовый репозиторий. + Чтобы воспользоваться этим методом, необходимо применить транзакцию и проверить наличие других изменений, +которые могли быть внесены в это же время. Если обнаружится, что имеется несогласованное изменение, +транзакцию можно откатить и выполнить повторно. +</p> +<p> + Оптимистическое управление параллелизмом подходит для мобильных устройств, где с системой одновременно взаимодействует только один пользователь, + а одновременный доступ к репозиторию данных нескольких пользователей — довольно редкое явление. Поскольку не применяется блокировка, +экономится ценное время, которое требуется на установку блокировок или ожидания того, когда другие транзакции отменят свои блокировки. +</p> +<p> + Ниже представлен порядок использования оптимистического управления параллелизмом при обновлении одной строки +{@link android.provider.ContactsContract.RawContacts}. +</p> +<ol> + <li> + Извлеките столбец {@link android.provider.ContactsContract.SyncColumns#VERSION} +необработанного контакта, а также другие данные, которые необходимо извлечь. + </li> + <li> + Создайте объект {@link android.content.ContentProviderOperation.Builder}, подходящий для +применения ограничения, с помощью метода +{@link android.content.ContentProviderOperation#newAssertQuery(Uri)}. Для URI контента +используйте {@link android.provider.ContactsContract.RawContacts#CONTENT_URI +RawContacts.CONTENT_URI}, +добавив к нему {@code android.provider.BaseColumns#_ID} необработанного контакта. + </li> + <li> + Для объекта {@link android.content.ContentProviderOperation.Builder} вызовите метод +{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) +withValue()}, чтобы сравнить столбец {@link android.provider.ContactsContract.SyncColumns#VERSION} +с номером версии, которую вы только что получили. + </li> + <li> + Для того же объекта {@link android.content.ContentProviderOperation.Builder} вызовите метод +{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()}, чтобы убедиться в том, что проверочное утверждение проверяет только одну строка. + </li> + <li> + Вызовите метод {@link android.content.ContentProviderOperation.Builder#build()}, чтобы создать объект +{@link android.content.ContentProviderOperation}, затем добавьте этот объект в качестве первого объекта в +{@link java.util.ArrayList}, который вы передаете в метод +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()}. + </li> + <li> + Примените пакетную транзакцию. + </li> +</ol> +<p> + Если в промежутке между считыванием строки и попыткой ее изменения строка необработанного контакта была обновлена другой +операцией, assert {@link android.content.ContentProviderOperation} +завершится сбоем и в выполнении всего пакета операций будет отказано. Можно выбрать повторное выполнение +пакета или выполнить другое действие. +</p> +<p> + В примере кода ниже демонстрируется, как создать assert +{@link android.content.ContentProviderOperation} после запроса одного необработанного контакта с помощью +{@link android.content.CursorLoader}. +</p> +<pre> +/* + * The application uses CursorLoader to query the raw contacts table. The system calls this method + * when the load is finished. + */ +public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + + // Gets the raw contact's _ID and VERSION values + mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); + mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); +} + +... + +// Sets up a Uri for the assert operation +Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID); + +// Creates a builder for the assert operation +ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri); + +// Adds the assertions to the assert operation: checks the version and count of rows tested +assertOp.withValue(SyncColumns.VERSION, mVersion); +assertOp.withExpectedCount(1); + +// Creates an ArrayList to hold the ContentProviderOperation objects +ArrayList ops = new ArrayList<ContentProviderOperationg>; + +ops.add(assertOp.build()); + +// You would add the rest of your batch operations to "ops" here + +... + +// Applies the batch. If the assert fails, an Exception is thrown +try + { + ContentProviderResult[] results = + getContentResolver().applyBatch(AUTHORITY, ops); + + } catch (OperationApplicationException e) { + + // Actions you want to take if the assert operation fails go here + } +</pre> +<h3 id="Intents">Получение и изменение данных с помощью намерений</h3> +<p> + С помощью отправки намерения в приложение для работы с контактами, которое имеется на устройстве, можно в обход получить доступ +к поставщику контактов. Намерение запускает пользовательский интерфейс приложения на устройстве, посредством которого пользователь +может работать с контактами. Такой тип доступа позволяет пользователю выполнять следующие действия: + <ul> + <li>выбор контактов из списка и их возврат в приложение для дальнейшей работы;</li> + <li>изменение данных существующего контакта;</li> + <li>вставка нового необработанного контакта для любых других аккаунтов;</li> + <li>удаление контакта или его данных.</li> + </ul> +<p> + Если пользователь вставляет или обновляет данные , вы можете сначала собрать эти данные, а затем отправить их вместе +с намерением. +</p> +<p> + При использовании намерений для доступа к поставщику контактов посредством приложения для работы с контактами, имеющегося на устройстве, вам +не нужно создавать собственный пользовательский интерфейс или код для доступа к поставщику. Также вам +не нужно запрашивать разрешение на чтение или запись в поставщик. Приложение для работы с контактами, имеющееся на устройстве, может +делегировать вам разрешение на чтение контакта, и поскольку вы вносите изменения +в поставщик через другое приложение, вам не нужно разрешение на запись. +</p> +<p> + Более подробно общий процесс отправки намерения для получения доступа к поставщику описан в руководстве +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">Основные сведения о поставщике контента</a> +в разделе «Доступ к данным посредством намерений». В таблице 4 ниже представлены сводные сведения об операциях, +типе MIME и значениях данных, которые используются для доступных задач. Значения +дополнительных данных, которые можно использовать для +{@link android.content.Intent#putExtra(String, String) putExtra()}, указаны в справочной +документации по {@link android.provider.ContactsContract.Intents.Insert}. +</p> +<p class="table-caption" id="table4"> + <strong>Таблица 4.</strong> Намерения в поставщике контактов. +</p> +<table style="width:75%"> + <tr> + <th scope="col" style="width:10%">Задача</th> + <th scope="col" style="width:5%">Действие</th> + <th scope="col" style="width:10%">Данные</th> + <th scope="col" style="width:10%">Тип MIME</th> + <th scope="col" style="width:25%">Примечания</th> + </tr> + <tr> + <td><strong>Выбор контакта из списка</strong></td> + <td>{@link android.content.Intent#ACTION_PICK}</td> + <td> + Одно из следующего: + <ul> + <li> +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI} +(отображение списка контактов); + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI} +(отображение номеров телефонов для необработанного контакта); + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI} +(отображение списка почтовых адресов для необработанного контакта); + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI} +(отображение адресов эл. почты для необработанного контакта). + </li> + </ul> + </td> + <td> + Не используется + </td> + <td> + Отображение списка необработанных контактов или списка данных необработанного контакта (в зависимости от +предоставленного URI контента). + <p> + Вызовите метод +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()}, +который возвращает URI контента для выбранной строки. URI представлен в форме +URI контента таблицы, к которому добавлен <code>LOOKUP_ID</code> строки. + Приложение для работы с контактами, установленное на устройстве, делегирует этому URI контента +разрешения на чтение и запись, которые действуют в течение всего жизненного цикла вашей операции. Дополнительные сведения представлены в статье +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">Основные +сведения о поставщике контента</a>. + </p> + </td> + </tr> + <tr> + <td><strong>Вставка нового необработанного контакта</strong></td> + <td>{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}</td> + <td>Н/Д</td> + <td> + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE}, тип MIME для набора необработанных контактов. + </td> + <td> + Отображение экрана <strong>Добавить контакт</strong> в приложении для работы с контактами, которое установлено на устройстве. Отображаются +значения дополнительных данных, добавленных вами в намерение. При отправке с помощью метода +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} +URI контента нового добавленного необработанного контакта передается обратно в метод обратного вызова +{@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} +вашей операции в аргументе {@link android.content.Intent} в поле +data. Чтобы получить значение, вызовите метод {@link android.content.Intent#getData()}. + </td> + </tr> + <tr> + <td><strong>Изменение контакта</strong></td> + <td>{@link android.content.Intent#ACTION_EDIT}</td> + <td> + {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} +контакта. Операция редактора разрешит пользователю изменить любые данные, связанные с +этим контактом. + </td> + <td> + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE}, один контакт.</td> + <td> + Отображение экрана редактирования контакта в приложении для работы с контактами. Отображаются +значения дополнительных данных, добавленных вами в намерение. Когда пользователь нажимает на кнопку <strong>Готово</strong> для сохранения +внесенных изменений, ваша операция возвращается на передний план. + </td> + </tr> + <tr> + <td><strong>Отображение средства выбора, в котором также можно добавлять данные</strong></td> + <td>{@link android.content.Intent#ACTION_INSERT_OR_EDIT}</td> + <td> + Н/Д + </td> + <td> + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + </td> + <td> + Это намерение также отображает экран средства выбора приложения для работы с контактами. Пользователь +может выбрать контакт для изменения или добавить новый контакт. В зависимости от выбора отображается экран редактирования или добавления контакта, +и отображаются дополнительные данные, +переданные вами в намерение. Если ваше приложение отображает такие данные контакта, как адрес эл. почты или номер телефона, +воспользуйтесь этим намерением, чтобы разрешить пользователю добавлять данные к существующему +контакту. + <p class="note"> + <strong>Примечание.</strong> Вам не нужно отправлять значение имени в дополнительные данные этого намерения, +поскольку пользователь всегда выбирает существующее имя или добавляет новое. Кроме того, +если отправить имя, а пользователь решит внести изменения, приложение для работы с контактами +отобразит отправленное вами имя, перезаписав предыдущее значение. Если пользователь +не заметит этого и сохранит изменения, старое значение будет утеряно. + </p> + </td> + </tr> +</table> +<p> + Приложение для работы с контактами, имеющееся на устройстве, не разрешает вам удалять необработанные контакты или любые его данные с +помощью намерений. Вместо этого для обработки необработанного контакта используйте метод +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} +или {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}. +</p> +<p> + Ниже представлен фрагмент кода для создания и отправки намерения, который вставляет новый +необработанный контакт и его данные. +</p> +<pre> +// Gets values from the UI +String name = mContactNameEditText.getText().toString(); +String phone = mContactPhoneEditText.getText().toString(); +String email = mContactEmailEditText.getText().toString(); + +String company = mCompanyName.getText().toString(); +String jobtitle = mJobTitle.getText().toString(); + +// Creates a new intent for sending to the device's contacts application +Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); + +// Sets the MIME type to the one expected by the insertion activity +insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); + +// Sets the new contact name +insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); + +// Sets the new company and job title +insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); +insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); + +/* + * Demonstrates adding data rows as an array list associated with the DATA key + */ + +// Defines an array list to contain the ContentValues objects for each row +ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); + + +/* + * Defines the raw contact row + */ + +// Sets up the row as a ContentValues object +ContentValues rawContactRow = new ContentValues(); + +// Adds the account type and name to the row +rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()); +rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); + +// Adds the row to the array +contactData.add(rawContactRow); + +/* + * Sets up the phone number data row + */ + +// Sets up the row as a ContentValues object +ContentValues phoneRow = new ContentValues(); + +// Specifies the MIME type for this data row (all data rows must be marked by their type) +phoneRow.put( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE +); + +// Adds the phone number and its type to the row +phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); + +// Adds the row to the array +contactData.add(phoneRow); + +/* + * Sets up the email data row + */ + +// Sets up the row as a ContentValues object +ContentValues emailRow = new ContentValues(); + +// Specifies the MIME type for this data row (all data rows must be marked by their type) +emailRow.put( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE +); + +// Adds the email address and its type to the row +emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); + +// Adds the row to the array +contactData.add(emailRow); + +/* + * Adds the array to the intent's extras. It must be a parcelable object in order to + * travel between processes. The device's contacts app expects its key to be + * Intents.Insert.DATA + */ +insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); + +// Send out the intent to start the device's contacts app in its add contact activity. +startActivity(insertIntent); +</pre> +<h3 id="DataIntegrity">Целостность данных</h3> +<p> + Поскольку в репозитории контактов содержится важная и конфиденциальная информация, которая, как ожидает пользователь, верна и +актуальна, в поставщике контактов предусмотрены четко определенные правила для обеспечения целостности данных. При изменении данных контактов вы обязаны +соблюдать эти правила. Вот наиболее +важные из этих правил: +</p> +<dl> + <dt> + Всегда добавляйте строку {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} +к каждой добавляемой вами строке {@link android.provider.ContactsContract.RawContacts}. + </dt> + <dd> + Если в таблице {@link android.provider.ContactsContract.Data} имеется строка {@link android.provider.ContactsContract.RawContacts} +без строки {@link android.provider.ContactsContract.CommonDataKinds.StructuredName}, +то могут возникнуть +проблемы во время агрегирования. + </dd> + <dt> + Всегда связывайте новые строки {@link android.provider.ContactsContract.Data} с их +родительскими строками {@link android.provider.ContactsContract.RawContacts}. + </dt> + <dd> + Строка {@link android.provider.ContactsContract.Data}, которая не связана +с {@link android.provider.ContactsContract.RawContacts}, не будет отображаться в +приложении для работы с контактами, имеющемся на устройстве, а также может привести к проблемам с адаптерами синхронизации. + </dd> + <dt> + Следует изменять данные только тех необработанных контактов, которыми вы владеете. + </dt> + <dd> + Помните о том, что поставщик контактов обычно управляет данными из +нескольких аккаунтов различных типов или служб в Интернете. Необходимо убедиться в том, что ваше приложение изменяет +или удаляет данные только для тех строк, которые принадлежат вам, а также в том, что оно вставляет данные с использованием только тех типов и имени аккаунта, +которыми вы управляете. + </dd> + <dt> + Всегда используйте +для центров, URI контента, путей URI, имен столбцов, типов MIME и значений +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} только те константы, которые определены в классе {@link android.provider.ContactsContract} и его подклассах. + </dt> + <dd> + Это позволит избежать ошибок. Если какая либо константа устарела, компилятор сообщит об этом +с помощью соответствующего предупреждения. + </dd> +</dl> +<h3 id="CustomData">Настраиваемые строки данных</h3> +<p> + Создав и используя собственные типы MIME, вы можете вставлять, изменять, удалять и извлекать +в таблице{@link android.provider.ContactsContract.Data} собственные строки данных. Ваши строки +ограничены использованием столбца, который определен в +{@link android.provider.ContactsContract.DataColumns}, однако вы можете сопоставить ваши собственные имена столбцов по типам строк +с именами столбцов по умолчанию. Данные для ваших строк отображаются в приложении для работы с контактами, которое имеется на устройстве, +однако их не удастся изменить или удалить, а также пользователи не смогут +добавить дополнительные данные. Чтобы разрешить пользователям изменять ваши настраиваемые строки данных, необходимо реализовать в вашем приложении операцию +редактора. +</p> +<p> + Для отображения настраиваемых данных укажите файл <code>contacts.xml</code>, содержащий элемент +<code><ContactsAccountType></code> и один или несколько его +<code><ContactsDataKind></code> дочерних элементов. Дополнительные сведения об этом представлены в разделе +<a href="#SocialStreamDataKind"><code><ContactsDataKind> element</code></a>. +</p> +<p> + Дополнительные сведения о настраиваемых типах MIME представлены в руководстве +<a href="{@docRoot}guide/topics/providers/content-provider-creating.html">Создание +поставщика контента</a>. +</p> +<h2 id="SyncAdapters">Адаптеры синхронизации поставщика контактов</h2> +<p> + Поставщик контактов разработан специально для обработки операций <strong>синхронизации</strong> +данных контактов между устройством и службой в Интернете. Благодаря этому пользователи +могут загружать существующие данные на новое устройство и отправлять их в новый аккаунт. + Синхронизация также обеспечивает предоставление пользователю всегда актуальных сведений, независимо от +источника их добавления и внесенных в них изменений. Еще одно преимущество синхронизации заключается в том, +что данные контактов доступны даже в том случае, если устройство не подключено к сети. +</p> +<p> + Несмотря на множество различных способов реализации синхронизации данных, в системе Android имеется +подключаемая платформа синхронизации, которая позволяет автоматизировать выполнение следующих задач: + <ul> + + <li> + проверка доступности сети; + </li> + <li> + планирование и выполнение синхронизации на основе предпочтений пользователя; + </li> + <li> + перезапуск остановленных процессов синхронизации. + </li> + </ul> +<p> + Для использования этой платформы вы предоставляете подключаемый модуль адаптера синхронизации. Каждый адаптер синхронизации является уникальным +для службы и поставщика контента, однако способен работать с несколькими аккаунтами в одной службе. В платформе +также предусмотрена возможность использовать несколько адаптеров синхронизации для одной и той же службы или поставщика. +</p> +<h3 id="SyncClassesFiles">Классы и файлы адаптера синхронизации</h3> +<p> + Адаптер синхронизации реализуется как подкласс класса +{@link android.content.AbstractThreadedSyncAdapter} и устанавливается в составе приложения +Android. Система узнает о наличии адаптера синхронизации из элементов в манифесте +вашего приложения, а также из особого файла XML, на который имеется указание в манифесте. В файле XML определяются +тип аккаунта в онлайн-службе и центр поставщика контента, которые вместе служат уникальными +идентификаторами адаптера. Адаптер синхронизации находится в неактивном состоянии до тех пор, пока пользователь не добавит +тип аккаунта и не включит синхронизацию +с поставщиком контента. На этом этапе система вступает в управление адаптером +, при необходимости вызывая его для синхронизации данных между поставщиком контента и сервером. +</p> +<p class="note"> + <strong>Примечание.</strong> Использование типа аккаунта для идентификации адаптера синхронизации +позволяет системе обнаруживать и группировать адаптеры синхронизации, которые обращаются к разным службам +из одной и той же организации. Например, у всех адаптеров синхронизации для онлайн-служб Google +один и тот же тип аккаунта — <code>com.google</code>. Когда пользователи добавляют на свои устройства аккаунт Google, все +установленные адаптеры синхронизации группируются вместе; каждый из адаптеров +синхронизируется только с отдельным поставщиком контента на устройстве. +</p> +<p> + Поскольку в большинстве служб пользователям сначала необходимо подтвердить свою подлинность, прежде чем они смогут получить доступ к данным, +система Android предлагает платформу аутентификации, которая аналогична +платформе адаптера синхронизации и зачастую используется совместно с ней. Платформа аутентификации +использует подключаемые структуры проверки подлинности, которые представляют собой подклассы класса +{@link android.accounts.AbstractAccountAuthenticator}. Такая структура проверяет +подлинность пользователя следующим образом: + <ol> + <li> + Сначала собираются сведения об имени пользователя и его пароле или аналогичная информация (<strong>учетные данные</strong> +пользователя). + </li> + <li> + Затем учетные данные оправляются в службу. + </li> + <li> + Наконец, изучается ответ, полученный от службы. + </li> + </ol> +<p> + Если служба приняла учетные данные, структура проверки подлинности может +сохранить их для использования в дальнейшем. Благодаря использованию структур проверки подлинности +{@link android.accounts.AccountManager} может предоставить доступ к любым маркерам аутентификации, которые +поддерживает структура проверки подлинности и которые она решает предоставить, например, к маркерам аутентификации OAuth2. +</p> +<p> + Несмотря на то что аутентификация не требуется, она используется большинством служб для работы с контактами. + Тем не менее, вам не обязательно использовать платформу аутентификации Android для проверки подлинности. +</p> +<h3 id="SyncAdapterImplementing">Реализация адаптера синхронизации</h3> +<p> + Чтобы реализовать адаптер синхронизации для поставщика контактов, начните с создания +приложения Android, которое содержит следующие компоненты. +</p> + <dl> + <dt> + Компонент {@link android.app.Service}, который отвечает на запросы системы +для привязки к адаптеру синхронизации. + </dt> + <dd> + Когда системе требуется запустить синхронизацию, она вызывает метод +{@link android.app.Service#onBind(Intent) onBind()} службы, чтобы получить объект +{@link android.os.IBinder} для адаптера синхронизации. Благодаря этому система +может вызывать методы адаптера между процессами. + <p> + В +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">примере адаптера синхронизации</a> именем класса этой службы является +<code>com.example.android.samplesync.syncadapter.SyncService</code>. + </p> + </dd> + <dt> + Адаптер синхронизации, фактически реализованный как конкретный подкласс +класса {@link android.content.AbstractThreadedSyncAdapter}. + </dt> + <dd> + Этот класс не подходит для загрузки данных с сервера, отправки данных +с устройства и разрешения конфликтов. Основную свою работу адаптер +выполняет в методе {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()}. Этот класс допускает создание только одного экземпляра. + <p> + В +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">примере адаптера синхронизации</a> адаптер определяется в классе +<code>com.example.android.samplesync.syncadapter.SyncAdapter</code>. + </p> + </dd> + <dt> + Подкласс класса {@link android.app.Application}. + </dt> + <dd> + Этот класс выступает в роли фабрики для единственного экземпляра адаптера синхронизации. Воспользуйтесь методом +{@link android.app.Application#onCreate()}, чтобы создать экземпляр адаптера синхронизации, а затем +предоставьте статический метод get, чтобы возвратить единственный экземпляр в метод +{@link android.app.Service#onBind(Intent) onBind()} службы +адаптера синхронизации. + </dd> + <dt> + <strong>Необязательно:</strong> компонент {@link android.app.Service}, который отвечает на запросы +системы для аутентификации пользователей. + </dt> + <dd> + {@link android.accounts.AccountManager} запускает службу, чтобы начать процесс +аутентификации. Метод {@link android.app.Service#onCreate()} службы создает экземпляр +объекта структуры проверки подлинности. Когда системе требуется запустить аутентификацию аккаунта пользователя для +адаптера синхронизации приложения, она вызывает метод +{@link android.app.Service#onBind(Intent) onBind()} службы, чтобы получить объект +{@link android.os.IBinder} для структуры проверки подлинности. Благодаря этому система +может вызывать методы структуры проверки подлинности между процессами. + <p> + В +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">примере адаптера синхронизации</a> именем класса этой службы является +<code>com.example.android.samplesync.authenticator.AuthenticationService</code>. + </p> + </dd> + <dt> + <strong>Необязательно:</strong> конкретный подкласс класса +{@link android.accounts.AbstractAccountAuthenticator}, который обрабатывает запросы на +аутентификацию. + </dt> + <dd> + В этом классе имеются методы, которые {@link android.accounts.AccountManager} вызывает +для проверки подлинности учетных данных пользователя на сервере. Подробности процесса +аутентификации значительно различаются в зависимости от технологии, используемой на сервере. Чтобы узнать подробнее об аутентификации, +обратитесь к соответствующей документации к программному обеспечению используемого сервера. + <p> + В +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">примере адаптера синхронизации</a> структура проверки подлинности определяется в классе +<code>com.example.android.samplesync.authenticator.Authenticator</code>. + </p> + </dd> + <dt> + Файлы XML, в которых определяются адаптер синхронизация и структура проверки подлинности для системы. + </dt> + <dd> + Описанные ранее компоненты службы адаптера синхронизации и структуры проверки подлинности определяются +в элементах +<code><<a href="{@docRoot}guide/topics/manifest/service-element.html">service</a>></code> +в манифесте приложения. Эти элементы +включают дочерние элементы +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +, в которых имеются определенные данные для +системы. + <ul> + <li> + Элемент +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +для службы адаптера синхронизации указывает на файл +XML <code>res/xml/syncadapter.xml</code>. В свою очередь, в этом файле задается +URI веб-службы для синхронизации с поставщиком контактов, +а также тип аккаунта для этой веб-службы. + </li> + <li> + <strong>Необязательно:</strong> элемент +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +для структуры проверки подлинности указывает на файл XML +<code>res/xml/authenticator.xml</code>. В свою очередь, в этом файле задается +тип аккаунта, который поддерживает структура проверки подлинности, а также ресурсы пользовательского интерфейса, +которые отображаются в процессе аутентификации. Тип аккаунта, указанный в этом +элементе, должен совпадать с типом аккаунта, который задан для +адаптера синхронизации. + </li> + </ul> + </dd> + </dl> +<h2 id="SocialStream">Потоки данных из социальных сетей</h2> +<p> + Для управления входящими данными из социальных сетей используются таблицы {@code android.provider.ContactsContract.StreamItems} +и +{@code android.provider.ContactsContract.StreamItemPhotos}. Можно создать адаптер синхронизации, который добавляет поток данных +из вашей собственной сети в эти таблицы, либо вы можете считывать поток данных из этих таблиц и отображать +их в собственном приложении. Можно также реализовать оба этих способа. С помощью этих функций вы можете интегрировать службы социальных сетей +в компоненты Android для работы с социальными сетями. +</p> +<h3 id="StreamText">Текст из потока данных из социальных сетей</h3> +<p> + Элементы потока всегда ассоциируются с необработанным контактом. Идентификатор +{@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} связывается со значением +<code>_ID</code> необработанного контакта. Тип аккаунта и его имя для необработанного +контакта также хранятся в строке элемента потока. +</p> +<p> + Данные из потока следует хранить в следующих столбцах: +</p> +<dl> + <dt> + {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} + </dt> + <dd> + <strong>Обязательный.</strong> Тип аккаунта пользователя для необработанного контакта, связанного с +этим элементом потока. Не забудьте задать это значение при вставке элемента потока. + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} + </dt> + <dd> + <strong>Обязательный.</strong> Имя аккаунта пользователя для необработанного контакта, связанного с +этим элементом потока. Не забудьте задать это значение при вставке элемента потока. + </dd> + <dt> + Столбцы идентификатора + </dt> + <dd> + <strong>Обязательный.</strong> При вставке элемента потока необходимо вставить +указанные ниже столбцы идентификатора. + <ul> + <li> + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}: значение +{@code android.provider.BaseColumns#_ID} для контакта, с которым ассоциирован +этот элемент потока. + </li> + <li> + {@code android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}: значение +{@code android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} для контакта, с которым ассоциирован +этот элемент потока. + </li> + <li> + {@code android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}: значение +{@code android.provider.BaseColumns#_ID} для необработанного контакта, с которым ассоциирован +этот элемент потока. + </li> + </ul> + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemsColumns#COMMENTS} + </dt> + <dd> + Необязательный. В нем хранится сводная информация, которую можно отобразить в начале элемента потока. + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemsColumns#TEXT} + </dt> + <dd> + Текст элемента потока: либо контент, опубликованный источником элемента, +либо описание некоторого действия, сгенерировавшего элемент потока. В этом столбце могут содержаться +любое форматирование и встроенные изображения ресурсов, рендеринг которых можно выполнить с помощью метода +{@link android.text.Html#fromHtml(String) fromHtml()}. Поставщик может обрезать слишком длинный контент +или заменить его часть многоточием, однако он попытается избежать нарушения тегов. + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} + </dt> + <dd> + Текстовая строка с информацией о времени вставки или обновления элемента в +<em>миллисекундах</em> от начала отсчета времени. Обслуживанием этого столбца занимаются приложения, которые вставляют или +обновляют элементы потока; поставщик контактов не выполняет +это автоматически. + </dd> +</dl> +<p> + Для отображения идентификационной информации для элементов потока воспользуйтесь +{@code android.provider.ContactsContract.StreamItemsColumns#RES_ICON}, +{@code android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} и +{@code android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} для связывания с ресурсами +в вашем приложении. +</p> +<p> + В таблице {@code android.provider.ContactsContract.StreamItems} также имеются столбцы +{@code android.provider.ContactsContract.StreamItemsColumns#SYNC1}—{@code android.provider.ContactsContract.StreamItemsColumns#SYNC4}, +которые предназначены исключительно для +адаптеров синхронизации. +</p> +<h3 id="StreamPhotos">Фотографии из потока данных из социальных сетей</h3> +<p> + Фотографии, связанные с элементом потока, хранятся в таблице +{@code android.provider.ContactsContract.StreamItemPhotos}. Столбец +{@code android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID} +в этой таблице связан со столбцом {@code android.provider.BaseColumns#_ID} +в таблице {@code android.provider.ContactsContract.StreamItems}. Ссылки на фотографии хранятся в следующих столбцах +таблицы: +</p> +<dl> + <dt> + Столбец {@code android.provider.ContactsContract.StreamItemPhotos#PHOTO} (объект BLOB). + </dt> + <dd> + Представление фотографии в двоичном формате и с измененным поставщиком размером для ее хранения и отображения. + Этот столбец доступен для обеспечения обратной совместимости с предыдущими версиями поставщика +контактов, которые использовались для хранения фотографий. Однако в текущей версии +поставщика мы не рекомендуем использовать этот столбец для хранения фотографий. Вместо этого воспользуйтесь столбцом +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} или +{@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} (или обоими +столбцами, как описано далее) для хранения фотографий в файле. В этом +столбце теперь хранятся миниатюры фотографий, доступных для чтения. + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} + </dt> + <dd> + Числовой идентификатор фотографии для необработанного контакта. Добавьте это значение к константе +{@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI}, +чтобы получить URI контента для одного файла фотографии, а затем вызовите метод +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}, чтобы получить средство обработки файла фотографии. + </dd> + <dt> + {@code android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} + </dt> + <dd> + URI контента, указывающий на файл фотографии, для фотографии, которая представлена этой строкой. + Вызовите метод {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()}, передав в него этот URI, чтобы получить средство обработки файла фотографии. + </dd> +</dl> +<h3 id="SocialStreamTables">Использование таблиц из потока данных из социальных сетей</h3> +<p> + Эти таблицы работают аналогично другим основным таблицам в поставщике контактов, за исключением указанных ниже моментов. +</p> + <ul> + <li> + Для работы с этими таблицами требуются дополнительные разрешения на доступ. Для чтения данных из них вашему приложению +должно быть предоставлено разрешение {@code android.Manifest.permission#READ_SOCIAL_STREAM}. Для +изменения таблиц ваше приложение должно иметь разрешение +{@code android.Manifest.permission#WRITE_SOCIAL_STREAM}. + </li> + <li> + Для таблицы {@code android.provider.ContactsContract.StreamItems} существует ограничение на количество строк, +которое можно хранить для каждого необработанного контакта. При достижении этого ограничения +поставщик контактов освобождает место для новых строк элементов потока путем автоматического удаления +строк со самой старой меткой +{@code android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP}. Чтобы получить это ограничение, +запросите URI контента +{@code android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI}. Для всех аргументов, +отличных от URI контента, можно оставить значение <code>null</code>. Запрос возвращает +объект Cursor, в котором содержится одна строка с одним столбцом +{@code android.provider.ContactsContract.StreamItems#MAX_ITEMS}. + </li> + </ul> + +<p> + Класс {@code android.provider.ContactsContract.StreamItems.StreamItemPhotos} определяет +дочернюю таблицу объектов {@code android.provider.ContactsContract.StreamItemPhotos}, в которой содержатся +строки для одного элемента потока. +</p> +<h3 id="SocialStreamInteraction">Взаимодействие с потоками данных из социальных сетей</h3> +<p> + Управление потоком данных из социальных сетей осуществляется поставщиком контактов совместно с приложением для управления контактами, имеющимся на устройстве. +Такой подход позволяет организовать эффективное использование данных из социальных сетей +с данными о существующих контактах. Доступны указанные ниже функции. +</p> + <ul> + <li> + Организовав синхронизацию данных из социальной службы с поставщиком контактов посредством +адаптера синхронизации, вы можете получать данные о недавней активности контактов пользователя и хранить такие данные в таблицах +,{@code android.provider.ContactsContract.StreamItems} +и {@code android.provider.ContactsContract.StreamItemPhotos} для использования в дальнейшем. + </li> + <li> + Помимо регулярной синхронизации, адаптер синхронизации можно настроить на получение +дополнительных данных при выборе пользователем контакта для просмотра. Благодаря этому ваш адаптер синхронизации +может получать фотографии высокого разрешения и самые актуальные элементы потока для контакта. + </li> + <li> + Регистрируя уведомление в приложении для работы с контактами и в поставщике +контактов, вы можете<em>получать</em> намерения при просмотре контакта и обновлять на этом этапе +данные о состоянии контакта из вашей службы. Такой подход может обеспечить большее быстродействие и меньший объем +использования полосы пропускания, чем выполнение полной синхронизации с помощью адаптера синхронизации. + </li> + <li> + Пользователи могут добавить контакт в вашу службу социальной сети, обратившись к контакту +в приложении для работы с контактами, которое имеется на устройстве. Это реализуется с помощью функции «пригласить контакт», +для включения которой используется сочетание операции, +которая добавляет существующий контакт в вашу сеть, и файла XML, в котором представлены сведения о вашем приложении для поставщика контактов и приложения для работы с +контактами. + </li> + </ul> +<p> + Регулярная синхронизация элементов потока с помощью поставщика контактов выполняется так же, +как и любая другая синхронизация. Дополнительные сведения о синхронизации представлены в разделе +<a href="#SyncAdapters">Адаптеры синхронизации поставщика контактов</a>. Регистрация уведомлений +и приглашение контактов рассматриваются в следующих двух разделах. +</p> +<h4>Регистрация для обработки просмотров контактов в социальных сетях</h4> +<p> + Чтобы зарегистрировать адаптер синхронизации для получения уведомлений о просмотрах пользователями контакта, +управление которым осуществляется вашим адаптером синхронизации, выполните указанные ниже действия. +</p> +<ol> + <li> + В каталоге <code>res/xml/</code> своего проекта создайте файл +<code>contacts.xml</code>. Если у вас уже есть этот файл, переходите к следующему действию. + </li> + <li> + В этом файле добавьте элемент +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>. + Если этот элемент уже существует, можете переходить к следующему действию. + </li> + <li> + Чтобы зарегистрировать службу, которой отправляется уведомление при открытии пользователем страницы со сведениями о контакте +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +<code>viewContactNotifyService="<em>serviceclass</em>"</code> к элементу, где +<code><em>serviceclass</em></code> — это полное имя класса службы, +которая должна получить намерение из приложения для работы с контактами. Для службы-уведомителя +используйте класс, который является расширением класса {@link android.app.IntentService}, чтобы разрешить службе +получать намерения. Данные во входящем намерении содержат URI контента необработанного +контакта, выбранного пользователем. В службе-уведомителе можно привязать адаптер синхронизации, а затем вызвать его +для обновления данных для необработанного контакта. + </li> +</ol> +<p> + Чтобы зарегистрировать операцию, которую следует вызвать при выборе пользователем элемента потока или фотографии (или обоих элементов), выполните указанные ниже действия. +</p> +<ol> + <li> + В каталоге <code>res/xml/</code> своего проекта создайте файл +<code>contacts.xml</code>. Если у вас уже есть этот файл, переходите к следующему действию. + </li> + <li> + В этом файле добавьте элемент +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>. + Если этот элемент уже существует, можете переходить к следующему действию. + </li> + <li> + Чтобы зарегистрировать одну из ваших операций для обработки выбора пользователем элемента потока +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +<code>viewStreamItemActivity="<em>activityclass</em>"</code> к элементу, где +<code><em>activityclass</em></code> — это полное имя класса операции, +которая должна получить намерение из приложения для работы с контактами. + </li> + <li> + Чтобы зарегистрировать одну из ваших операций для обработки выбора пользователем фотографии в потоке +в приложении для работы с контактами, которое имеется на устройстве, добавьте атрибут +<code>viewStreamItemPhotoActivity="<em>activityclass</em>"</code> к элементу, где +<code><em>activityclass</em></code> — это полное имя класса операции, +которая должна получить намерение из приложения для работы с контактами. + </li> +</ol> +<p> + Дополнительные сведения об элементе <code><ContactsAccountType></code> представлены в разделе +<a href="#SocialStreamAcctType">элемент <ContactsAccountType></a>. +</p> +<p> + Данные во входящем намерении содержат URI контента элемента или фотографии, выбранных пользователем. + Чтобы использовать разные операции для текстовых элементов и фотографий, используйте оба атрибута в одном файле. +</p> +<h4>Взаимодействие со службой социальной сети</h4> +<p> + Пользователям не обязательно выходить из приложения для работы с контактами, которое имеется на устройстве, чтобы пригласить контакт на сайт +социальной сети. Вместо этого приложение для работы с контактами может отправить намерение для приглашения +контакта в одну из ваших операций. Для этого выполните указанные ниже действия. +</p> +<ol> + <li> + В каталоге <code>res/xml/</code> своего проекта создайте файл +<code>contacts.xml</code>. Если у вас уже есть этот файл, переходите к следующему действию. + </li> + <li> + В этом файле добавьте элемент +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>. + Если этот элемент уже существует, можете переходить к следующему действию. + </li> + <li> + Добавьте следующие атрибуты: + <ul> + <li><code>inviteContactActivity="<em>activityclass</em>"</code>;</li> + <li> + <code>inviteContactActionLabel="@string/<em>invite_action_label</em>"</code>. + </li> + </ul> + Значение <code><em>activityclass</em></code> представляет собой полное имя класса операции, +которая должна получить намерение. Значение<code><em>invite_action_label</em></code> +— это текстовая строка, которая отображается в меню <strong>Добавить подключение</strong> +в приложении для работы с контактами. + </li> +</ol> +<p class="note"> + <strong>Примечание.</strong> <code>ContactsSource</code> — это устаревшее имя тега для +<code>ContactsAccountType</code>, которое больше не используется. +</p> +<h3 id="ContactsFile">Ссылка contacts.xml</h3> +<p> + В файле <code>contacts.xml</code> содержатся элементы XML, которые управляют взаимодействием вашего +адаптера синхронизации и вашего приложения с поставщиком контактов и приложением для работы с контактами. Эти +элементы описаны в следующих разделах. +</p> +<h4 id="SocialStreamAcctType">Элемент <ContactsAccountType></h4> +<p> + Элемент <code><ContactsAccountType></code> управляет взаимодействием вашего +приложения с приложением для работы с контактами. Ниже представлен его синтаксис. +</p> +<pre> +<ContactsAccountType + xmlns:android="http://schemas.android.com/apk/res/android" + inviteContactActivity="<em>activity_name</em>" + inviteContactActionLabel="<em>invite_command_text</em>" + viewContactNotifyService="<em>view_notify_service</em>" + viewGroupActivity="<em>group_view_activity</em>" + viewGroupActionLabel="<em>group_action_text</em>" + viewStreamItemActivity="<em>viewstream_activity_name</em>" + viewStreamItemPhotoActivity="<em>viewphotostream_activity_name</em>"> +</pre> +<p> + <strong>находится в:</strong> +</p> +<p> + <code>res/xml/contacts.xml</code> +</p> +<p> + <strong>может содержать:</strong> +</p> +<p> + <strong><code><ContactsDataKind></code></strong> +</p> +<p> + <strong>Описание</strong> +</p> +<p> + Этот элемент объявляет компоненты и элементы пользовательского интерфейса, с помощью которых пользователи могут приглашать свои контакты +в социальную сеть, уведомлять пользователей при обновлении одного из их потоков данных из социальных сетей и +др. +</p> +<p> + Обратите внимание, что префикс атрибута <code>android:</code> необязательно использовать для атрибутов +<code><ContactsAccountType></code>. +</p> +<p> + <strong>Атрибуты</strong> +</p> +<dl> + <dt>{@code inviteContactActivity}</dt> + <dd> + Полное имя класса операции в вашем приложении, которую необходимо активировать +при выборе пользователем элемента <strong>Добавить подключение</strong> в приложении +для работы с контактами, которое имеется на устройстве. + </dd> + <dt>{@code inviteContactActionLabel}</dt> + <dd> + Текстовая строка, которая отображается для операции, заданной в +{@code inviteContactActivity}, в меню <strong>Добавить подключение</strong>. + Например, можно указать фразу «Следите за новостями в моей сети». Для этого элемента можно использовать +идентификатор строкового ресурса. + </dd> + <dt>{@code viewContactNotifyService}</dt> + <dd> + Полное имя класса службы в вашем приложении, которая должна получать +уведомления при просмотре контакта пользователем. Такое уведомление отправляется приложением для работы с контактами, +которое имеется на устройстве; благодаря этому ваше приложение может отложить выполнение операций, требующих обработки большого объема данных, до тех пор +, пока это не потребуется. Например, ваше приложение может реагировать на такое уведомление +путем считывания и отображения фотографии контакта в высоком разрешении и самых актуальных +элементов потока данных из социальной сети. Дополнительные сведения об этой функции представлены в разделе +<a href="#SocialStreamInteraction">Взаимодействие с потоками данных из социальных сетей</a>. Пример +службы уведомлений представлен в файле +<code>NotifierService.java</code> в +образце приложения<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">SampleSyncAdapter</a>. + </dd> + <dt>{@code viewGroupActivity}</dt> + <dd> + Полное имя класса операции в вашем приложении, которая может отобразить +информацию о группе. При нажатии пользователем на метку группы в приложении для работы с данными, +которое имеется на устройстве, отображается пользовательский интерфейс для этой операции. + </dd> + <dt>{@code viewGroupActionLabel}</dt> + <dd> + Метка, отображаемая приложением для работы с контактами для элемента пользовательского интерфейса, с помощью которой +пользователь может просмотреть группы в вашем приложении. + <p> + Например, если вы установили приложение Google+ на ваше устройство и выполняете синхронизацию +данных в Google+ с приложением для работы с контактами, то круги Google+ будут обозначены в приложении для работы с контактами как +группы на вкладке <strong>Группы</strong>. При нажатии на на круг +Google+ участники крга отобразятся как группа контактов. В верхней части экрана +находится значок Google+; если нажать на него, управление перейдет в приложение +Google+. В приложении для управления контактами это реализовано с помощью +{@code viewGroupActivity}, в которой значок Google+ используется в качестве значения +{@code viewGroupActionLabel}. + </p> + <p> + Для этого атрибута можно использовать идентификатор строкового ресурса. + </p> + </dd> + <dt>{@code viewStreamItemActivity}</dt> + <dd> + Полное имя класса операции в вашем приложении, которую запускает +приложение для работы с контактами, когда пользователь выбирает элемент потока для необработанного контакта. + </dd> + <dt>{@code viewStreamItemPhotoActivity}</dt> + <dd> + Полное имя класса операции в вашем приложении, которую запускает +приложение для работы с контактами, когда пользователь выбирает фотографию в элементе +потока для необработанного контакта. + </dd> +</dl> +<h4 id="SocialStreamDataKind">Элемент <ContactsDataKind></h4> +<p> + Элемент <code><ContactsDataKind></code> управляет отображением настраиваемых строк данных вашего +приложения в интерфейсе приложения для работы с контактами, которое имеется на устройстве. Ниже представлен его синтаксис. +</p> +<pre> +<ContactsDataKind + android:mimeType="<em>MIMEtype</em>" + android:icon="<em>icon_resources</em>" + android:summaryColumn="<em>column_name</em>" + android:detailColumn="<em>column_name</em>"> +</pre> +<p> + <strong>находится в:</strong> +</p> +<code><ContactsAccountType></code> +<p> + <strong>Описание</strong> +</p> +<p> + Используйте этот элемент для отображения содержимого настраиваемой строки данных в приложении для работы с контактами как части +сведений о необработанном контакте. Каждый дочерний элемент <code><ContactsDataKind></code> +элемента <code><ContactsAccountType></code> представляет собой тип настраиваемой строки данных, который +адаптер синхронизации добавляет в таблицу {@link android.provider.ContactsContract.Data}. Для каждого используемого вами настраиваемого типа MIME необходимо добавить один элемент +<code><ContactsDataKind></code>. Вам не нужно добавлять элемент +, если у вас имеется настраиваемая строка данных, для которой не требуется отображать данные. +</p> +<p> + <strong>Атрибуты</strong> +</p> +<dl> + <dt>{@code android:mimeType}</dt> + <dd> + Определенные вами настраиваемые типы MIME для одного из ваших типов настраиваемых строк данных в таблице +{@link android.provider.ContactsContract.Data}. Например, значение +<code>vnd.android.cursor.item/vnd.example.locationstatus</code> может быть настраиваемым +типом MIME для строки данных, в которой находятся записи о последнем известном местоположении контакта. + </dd> + <dt>{@code android:icon}</dt> + <dd> + <a href="{@docRoot}guide/topics/resources/drawable-resource.html">Графический ресурс </a> +Android, +который приложение для работы с контактами отображает рядом с вашими данными. Используйте его для обозначения того, +что эти данные получены из вашей службы. + </dd> + <dt>{@code android:summaryColumn}</dt> + <dd> + Имя столбца для первого из двух значений, полученных из строки данных. Значение +отображается в виде первой строки записи в этой строке данных. Первая строка +предназначена для использования в качестве сводных данных, однако она необязательна. См. также +<a href="#detailColumn">android:detailColumn</a>. + </dd> + <dt>{@code android:detailColumn}</dt> + <dd> + Имя столбца для второго из двух значений, полученных из строки данных. Значение +отображается в виде второй строки записи в этой строке данных. См. также +{@code android:summaryColumn}. + </dd> +</dl> +<h2 id="AdditionalFeatures">Дополнительные возможности поставщика контактов</h2> +<p> + Помимо основных функций, описанных разделах выше, в поставщике +контактов предусмотрены указанные ниже полезные функции для работы с данными контактов. +</p> + <ul> + <li>Группы контактов</li> + <li>Функции работы с фотографиями</li> + </ul> +<h3 id="Groups">Группы контактов</h3> +<p> + Поставщик контактов может дополнительно отметить коллекции связанных контактов с данными о +<strong>группе</strong>. Если серверу, который связан с учетной записью пользователя, +требуется сохранить группы, адаптеру синхронизации для типа этого аккаунта следует передать +данные о группах из поставщика контактов на сервер. При добавлении пользователем нового контакта на сервер +и последующем помещении этого контакта в новую группу адаптер синхронизации должен добавить эту новую группу в таблицу +{@link android.provider.ContactsContract.Groups}. Группа или группы, в которые входит необработанный контакт, +хранятся в таблице {@link android.provider.ContactsContract.Data} с использованием типа MIME +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}. +</p> +<p> + Если необходимо создать адаптер синхронизации, который будет добавлять данные необработанного контакта с сервера +в поставщик контактов, а вы не используете группы, то вам необходимо указать для поставщика, +чтобы он сделал ваши данные видимыми. В коде, который выполняется при добавлении пользователем +аккаунта на устройство, обновите строку {@link android.provider.ContactsContract.Settings}, +которую поставщик контактов добавляет для этого аккаунта. В этой строке укажите в столбце +{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} значение «1». После этого поставщик контактов всегда будет +делать ваши данные видимыми, даже если вы не используете группы. +</p> +<h3 id="Photos">Фотографии контактов</h3> +<p> + В таблице {@link android.provider.ContactsContract.Data} хранятся фотографии в виде строк +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} типа MIME. Столбец +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} в строке связан со столбцом +{@code android.provider.BaseColumns#_ID} необработанного контакта, которому он принадлежит. + Класс {@link android.provider.ContactsContract.Contacts.Photo} определяет вложенную таблицу +{@link android.provider.ContactsContract.Contacts}, в которой содержится информация об основной фотографии +контакта (которая является основной фотографией основного необработанного контакта этого контакта). Аналогичным образом класс +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} определяет вложенную таблицу +{@link android.provider.ContactsContract.RawContacts}, в которой содержится информация об основной фотографии +необработанного контакта. +</p> +<p> + В справочной документации по {@link android.provider.ContactsContract.Contacts.Photo} и +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} содержатся примеры +получения информации о фотографии. К сожалению, отсутствует класс для удобного извлечения миниатюры +основной фотографии необработанного контакта, однако вы можете отправить запрос в таблицу +{@link android.provider.ContactsContract.Data}, выбрать +{@code android.provider.BaseColumns#_ID} необработанного контакта, +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} и столбец {@link android.provider.ContactsContract.Data#IS_PRIMARY}, +чтобы найти строку основной фотографии необработанного контакта. +</p> +<p> + Потоки данных из социальных сетей также могут включать фотографии. Они находятся в таблице +{@code android.provider.ContactsContract.StreamItemPhotos}, дополнительные сведения о которой представлены в разделе +<a href="#StreamPhotos">Фотографии из потока данных из социальных сетей</a>. +</p> diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 000000000000..c912dbc03752 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=Основные сведения о поставщике контента +@jd:body +<div id="qv-wrapper"> +<div id="qv"> +<!-- In this document --> +<h2>Содержание документа</h2> +<ol> + <li> + <a href="#Basics">Обзор</a> + <ol> + <li> + <a href="#ClientProvider">Доступ к поставщику</a> + </li> + <li> + <a href="#ContentURIs">URI контента</a> + </li> + </ol> + </li> + <li> + <a href="#SimpleQuery">Получение данных от поставщика</a> + <ol> + <li> + <a href="#RequestPermissions">Запрос разрешения на чтение</a> + </li> + <li> + <a href="#Query">Создание запроса</a> + </li> + <li> + <a href="#DisplayResults">Отображение результатов запроса</a> + </li> + <li> + <a href="#GettingResults">Получение данных из результатов запроса</a> + </li> + </ol> + </li> + <li> + <a href="#Permissions">Разрешения поставщика контента</a> + </li> + <li> + <a href="#Modifications">Вставка, обновление и удаление данных</a> + <ol> + <li> + <a href="#Inserting">Вставка данных</a> + </li> + <li> + <a href="#Updating">Обновление данных</a> + </li> + <li> + <a href="#Deleting">Удаление данных</a> + </li> + </ol> + </li> + <li> + <a href="#DataTypes">Типы поставщиков данных</a> + </li> + <li> + <a href="#AltForms">Альтернативные формы доступа к поставщику</a> + <ol> + <li> + <a href="#Batch">Пакетный доступ</a> + </li> + <li> + <a href="#Intents">Доступ к данным с помощью намерений</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClasses">Классы-контракты</a> + </li> + <li> + <a href="#MIMETypeReference">Справка по типам MIME</a> + </li> +</ol> + + <!-- Key Classes --> +<h2>Ключевые классы</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.content.ContentResolver} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> + + <!-- Related Samples --> +<h2>Связанные примеры</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> + Cursor (People)</a> + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> + Cursor (Phones)</a> + </li> + </ol> + + <!-- See also --> +<h2>См. также</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> +Создание поставщика контента</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> +Поставщик календаря</a> + </li> + </ol> +</div> +</div> + + <!-- Intro paragraphs --> +<p> + Поставщик контента управляет доступом к центральному репозиторию данных. Поставщик +является компонентом приложения Android, который зачастую имеет собственный пользовательский интерфейс для +работы с данными. Однако поставщики контента предназначены в первую очередь для использования другими приложениями, +которые получают доступ к поставщику посредством клиентского объекта поставщика. Вместе поставщики +и клиенты поставщиков обеспечивают согласованный, стандартный интерфейс к данным, который также обрабатывает +взаимодействие между процессами и обеспечивает защищенный доступ к данным. +</p> +<p> + В этой статье рассматриваются основные сведения, касающиеся следующих тем: +</p> + <ul> + <li>принцип работы поставщика контента;</li> + <li>API, используемый для получения данных от поставщика контента;</li> + <li>API, используемый для вставки данных в поставщик контента и их обновления или удаления в нем;</li> + <li>другие функции API, которые упрощают работу с поставщиками.</li> + </ul> + + <!-- Basics --> +<h2 id="Basics">Обзор</h2> +<p> + Поставщик контента предоставляет данные внешним приложениям в виде одной или нескольких таблиц, +аналогичных таблицам в реляционной базе данных. Строка представляет собой экземпляр некоторого типа +собираемых поставщиком данных, а каждый столбец в этой строке — это отдельный элемент данных, +собранных для экземпляра. +</p> +<p> + Примером встроенного поставщика в платформе Android может служить пользовательский словарь, +в котором хранятся данные о написании нестандартных слов, добавленных пользователем. В таблице 1 показано, +как данные могут выглядеть в этой таблице поставщика. +</p> +<p class="table-caption"> + <strong>Таблица 1.</strong> Пример таблицы пользовательского словаря. +</p> +<table id="table1" style="width: 50%;"> + <tr> + <th style="width:20%" align="center" scope="col">word</th> + <th style="width:20%" align="center" scope="col">app id</th> + <th style="width:20%" align="center" scope="col">frequency</th> + <th style="width:20%" align="center" scope="col">locale</th> + <th style="width:20%" align="center" scope="col">_ID</th> + </tr> + <tr> + <td align="center" scope="row">mapreduce</td> + <td align="center">user1</td> + <td align="center">100</td> + <td align="center">en_US</td> + <td align="center">1</td> + </tr> + <tr> + <td align="center" scope="row">precompiler</td> + <td align="center">user14</td> + <td align="center">200</td> + <td align="center">fr_FR</td> + <td align="center">2</td> + </tr> + <tr> + <td align="center" scope="row">applet</td> + <td align="center">user2</td> + <td align="center">225</td> + <td align="center">fr_CA</td> + <td align="center">3</td> + </tr> + <tr> + <td align="center" scope="row">const</td> + <td align="center">user1</td> + <td align="center">255</td> + <td align="center">pt_BR</td> + <td align="center">4</td> + </tr> + <tr> + <td align="center" scope="row">int</td> + <td align="center">user5</td> + <td align="center">100</td> + <td align="center">en_UK</td> + <td align="center">5</td> + </tr> +</table> +<p> + В каждой строке таблицы 1 представлен экземпляр слова, которое отсутствует +в стандартном словаре. В каждом ее столбце содержатся некоторые данные для слова, +например, данные о языке, на котором это слово было впервые использовано. Заголовки столбцов представляют собой имена столбцов, которые хранятся +в поставщике. Чтобы узнать язык строки, необходимо обратиться к столбцу <code>locale</code>. В этом +поставщике столбец <code>_ID</code> выступает в роли «основного ключа», +который поставщик автоматически сохраняет. +</p> +<p class="note"> + <strong>Примечание.</strong> В поставщике необязательно должен быть основной ключ, а также ему необязательно +использовать<code>_ID</code> в качестве имени столбца основного ключа, если таковой имеется. Однако, если +необходимо привязать данные из поставщика к классу {@link android.widget.ListView}, +один из столбцов должен именоваться <code>_ID</code>. Дополнительные сведения об этом требовании представлены в разделе +<a href="#DisplayResults">Отображение результатов запроса</a>. +</p> +<h3 id="ClientProvider">Доступ к поставщику</h3> +<p> + Для доступа приложения к данным из поставщика контента +используется клиентский объект {@link android.content.ContentResolver}. В этом объекте имеются методы, которые вызывают +идентичные методы в объекте поставщика, который представляет собой экземпляр одного из конкретных +подклассов класса {@link android.content.ContentProvider}. В этих методах +{@link android.content.ContentResolver} представлены основные функции + CRUD (аббревиатура create, retrieve, update, delete [создание, получение, обновление и удаление]) постоянного хранилища. +</p> +<p> + Объект {@link android.content.ContentResolver} в процессе клиентского приложения +и объект {@link android.content.ContentProvider} в приложении, +которое владеет поставщиком, автоматически обрабатывают взаимодействие между процессами. +Объект {@link android.content.ContentProvider} также выступает в роли уровня абстракции между +репозиторием данных и внешним представлением данных в виде таблиц. +</p> +<p class="note"> + <strong>Примечание.</strong> Для доступа к поставщику ваше приложение обычно должно запросить определенные разрешения +в своем файле манифеста. Дополнительные сведения об этом представлены в разделе +<a href="#Permissions">Разрешения поставщика контента</a>. +</p> +<p> + Например, чтобы получить из поставщика пользовательского словаря список слов и языков, на которых они представлены, +вызовите метод {@link android.content.ContentResolver#query ContentResolver.query()}. + В свою очередь, метод {@link android.content.ContentResolver#query query()} вызывает метод +{@link android.content.ContentProvider#query ContentProvider.query()}, определенный поставщиком +пользовательского словаря. В примере кода ниже показан вызов метода +{@link android.content.ContentResolver#query ContentResolver.query()}. +<p> +<pre> +// Queries the user dictionary and returns results +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Selection criteria + mSelectionArgs, // Selection criteria + mSortOrder); // The sort order for the returned rows +</pre> +<p> + В таблице 2 указано соответствие аргументов для метода +{@link android.content.ContentResolver#query +query(Uri,projection,selection,selectionArgs,sortOrder)} SQL-инструкции SELECT. +</p> +<p class="table-caption"> + <strong>Таблица 2.</strong> Сравнение метода query() и SQL-запроса. +</p> +<table id="table2" style="width: 75%;"> + <tr> + <th style="width:25%" align="center" scope="col">Аргумент метода query()</th> + <th style="width:25%" align="center" scope="col">Параметр/ключевое слово SELECT</th> + <th style="width:50%" align="center" scope="col">Примечания</th> + </tr> + <tr> + <td align="center"><code>Uri</code></td> + <td align="center"><code>FROM <em>table_name</em></code></td> + <td><code>Uri</code> соответствует таблице <em>table_name</em> в поставщике.</td> + </tr> + <tr> + <td align="center"><code>projection</code></td> + <td align="center"><code><em>col,col,col,...</em></code></td> + <td> + <code>projection</code> представляет собой массив столбцов, которые следует включить +в каждую полученную строку. + </td> + </tr> + <tr> + <td align="center"><code>selection</code></td> + <td align="center"><code>WHERE <em>col</em> = <em>value</em></code></td> + <td><code>selection</code> задает критерии для выбора строк.</td> + </tr> + <tr> + <td align="center"><code>selectionArgs</code></td> + <td align="center"> + (Точный эквивалент отсутствует. В предложении выбора заполнители <code>?</code> +заменяются аргументами выбора). + </td> + </tr> + <tr> + <td align="center"><code>sortOrder</code></td> + <td align="center"><code>ORDER BY <em>col,col,...</em></code></td> + <td> + <code>sortOrder</code> задает порядок отображения строк в возвращаемом объекте +{@link android.database.Cursor}. + </td> + </tr> +</table> +<h3 id="ContentURIs">URI контента</h3> +<p> + <strong>URI контента</strong> представляет собой URI, который определяет данные в поставщике. URI контента +могут включать символическое имя всего поставщика (его <strong>центр</strong>) и +имя, которое указывает на таблицу (<strong>путь</strong>). При вызове +клиентского метода для доступа к таблице в поставщике URI контента этой таблицы выступает в роли одного +из аргументов этого метода. +</p> +<p> + Константа +{@link android.provider.UserDictionary.Words#CONTENT_URI} в предыдущих строках кода содержит URI контента +таблицы words в пользовательском словаре. Объект{@link android.content.ContentResolver} +анализирует центр URI и использует его для «разрешения» поставщика +путем сравнения центра с системной таблицей известных поставщиков. {@link android.content.ContentResolver} +может отправить аргументы запроса в соответствующий +поставщик. +</p> +<p> + {@link android.content.ContentProvider} использует часть URI контента, в которой указан путь, для выбора таблицы +для доступа. В поставщике обычно имеется <strong>путь</strong> для каждой предоставляемой им таблицы. +</p> +<p> + В предыдущих строках кода полный URI для таблицы words выглядит следующим образом: +</p> +<pre> +content://user_dictionary/words +</pre> +<p> + Строка <code>user_dictionary</code> ֪– это центр поставщика, а строка +<code>words</code> — это путь к таблице. Строка +<code>content://</code> (<strong>схема</strong>) присутствует всегда; +она определяет, что это URI контента. +</p> +<p> + Многие поставщики предоставляют доступ к одной строке в таблице путем добавления идентификатора +в конец URI. Например, чтобы извлечь из пользовательского словаря строку, в столбце <code>_ID</code> которой +указано <code>4</code>, можно воспользоваться следующим URI контента: +</p> +<pre> +Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4); +</pre> +<p> + Идентификаторы часто используются в случае, когда вы извлекли набор строк и хотите обновить или удалить +одну из них. +</p> +<p class="note"> + <strong>Примечание.</strong> В классах {@link android.net.Uri} и {@link android.net.Uri.Builder} +имеются методы для удобного создания правильно оформленных объектов URI из строк. {@link android.content.ContentUris} +содержит методы для удобного добавления идентификаторов +к URI. В примере кода выше для добавления идентификатора к URI контента UserDictionary используется метод {@link android.content.ContentUris#withAppendedId +withAppendedId()}. +</p> + + + <!-- Retrieving Data from the Provider --> +<h2 id="SimpleQuery">Получение данных от поставщика</h2> +<p> + В это разделе рассматривается порядок получения данных от поставщика на примере +поставщика пользовательского словаря. +</p> +<p class="note"> + Для полной ясности в примерах кода, приведенных в этом разделе, методы +{@link android.content.ContentResolver#query ContentResolver.query()} вызываются в потоке пользовательского интерфейса. В реальном +коде запросы следует выполнять асинхронно в отдельном потоке. Одним из способов реализовать +это является использование класса {@link android.content.CursorLoader}, который более подробно описан в +статье +<a href="{@docRoot}guide/components/loaders.html">Загрузчики</a>. Кроме того, в этой статье представлены лишь фрагменты кода; они не представляют собой готовое +приложение. +</p> +<p> + Чтобы получить данные из поставщика, выполните указанные ниже основные действия. +</p> +<ol> + <li> + Запросите у поставщика разрешение на чтение. + </li> + <li> + Определите код, который отвечает за отправку запроса поставщику. + </li> +</ol> +<h3 id="RequestPermissions">Запрос разрешения на чтение</h3> +<p> + Чтобы ваше приложение могло получать данные от поставщика, приложению требуется получить от поставщика разрешение +на чтение. Это разрешение невозможно получить во время выполнения; вместо этого вам необходимо указать, что вам требуется +такое разрешение, в манифесте приложения. Для этого воспользуйтесь элементом +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +и укажите точное название разрешения, +определенное поставщиком. Указав этот элемент в манифесте, вы тем самым запрашиваете +необходимое разрешение для вашего приложения. Когда пользователи устанавливают ваше приложение, они косвенно +получают разрешение по этому запросу. +</p> +<p> + Чтобы узнать точное название разрешения на чтение в используемом поставщике, +а также названия других используемых в нем разрешений на чтение, обратитесь +к документации поставщика. +</p> +<p> + Дополнительные сведения о роли разрешений в получении доступа к поставщику представлены в разделе +<a href="#Permissions">Разрешения поставщика контента</a>. +</p> +<p> + Поставщик пользовательского словаря задает разрешение +<code>android.permission.READ_USER_DICTIONARY</code> в своем файле манифеста, +поэтому приложению, которому требуется выполнить чтение данных из поставщика, необходимо запросить именно это разрешение. +</p> +<!-- Constructing the query --> +<h3 id="Query">Создание запроса</h3> +<p> + Следующим этапом получения данных от поставщика является создание запроса. В следующем фрагменте кода +задаются некоторые переменные для доступа к поставщику пользовательского словаря: +</p> +<pre class="prettyprint"> + +// A "projection" defines the columns that will be returned for each row +String[] mProjection = +{ + UserDictionary.Words._ID, // Contract class constant for the _ID column name + UserDictionary.Words.WORD, // Contract class constant for the word column name + UserDictionary.Words.LOCALE // Contract class constant for the locale column name +}; + +// Defines a string to contain the selection clause +String mSelectionClause = null; + +// Initializes an array to contain selection arguments +String[] mSelectionArgs = {""}; + +</pre> +<p> + В следующем фрагменте кода демонстрируется порядок использования метода +{@link android.content.ContentResolver#query ContentResolver.query()} (в качестве примера выступает +поставщик пользовательского словаря): Клиентский запрос поставщика аналогичен SQL-запросу. В нем содержится +набор столбцов, которые возвращаются, набор критериев выборки и порядок сортировки. +</p> +<p> + Набор столбцов, которые должен возвратить запрос, называется <strong>проекцией</strong> +(переменная <code>mProjection</code>). +</p> +<p> + Выражение, которое задает строки для получения, состоит из предложения выбора +и аргументов выбора. Предложение выбора представляет собой сочетание логических выражений, +имен столбцов и значений (переменная <code>mSelectionClause</code>). Если вместо значения указать подставляемый параметр +<code>?</code>, метод запроса извлекает значение из массива аргументов выбора (переменная +<code>mSelectionArgs</code>). +</p> +<p> + В следующем фрагменте кода, если пользователь не указал слово, то для предложения выбора задается значение +<code>null</code>, а запрос возвращает все слова, имеющиеся в поставщике. Если пользователь указал слово, то для предложения выбора задается значение +<code>UserDictionary.Words.WORD + " = ?"</code>, +а для первого элемента в массиве аргументов выбора задается введенное пользователем слово. +</p> +<pre class="prettyprint"> +/* + * This defines a one-element String array to contain the selection argument. + */ +String[] mSelectionArgs = {""}; + +// Gets a word from the UI +mSearchString = mSearchWord.getText().toString(); + +// Remember to insert code here to check for invalid or malicious input. + +// If the word is the empty string, gets everything +if (TextUtils.isEmpty(mSearchString)) { + // Setting the selection clause to null will return all words + mSelectionClause = null; + mSelectionArgs[0] = ""; + +} else { + // Constructs a selection clause that matches the word that the user entered. + mSelectionClause = UserDictionary.Words.WORD + " = ?"; + + // Moves the user's input string to the selection arguments. + mSelectionArgs[0] = mSearchString; + +} + +// Does a query against the table and returns a Cursor object +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Either null, or the word the user entered + mSelectionArgs, // Either empty, or the string the user entered + mSortOrder); // The sort order for the returned rows + +// Some providers return null if an error occurs, others throw an exception +if (null == mCursor) { + /* + * Insert code here to handle the error. Be sure not to use the cursor! You may want to + * call android.util.Log.e() to log this error. + * + */ +// If the Cursor is empty, the provider found no matches +} else if (mCursor.getCount() < 1) { + + /* + * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily + * an error. You may want to offer the user the option to insert a new row, or re-type the + * search term. + */ + +} else { + // Insert code here to do something with the results + +} +</pre> +<p> + Этот запрос аналогичен следующей инструкции SQL: +</p> +<pre> +SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC; +</pre> +<p> + В этой инструкции SQL вместо констант класса-контракта используются фактические имена столбцов. +</p> +<h4 id="Injection">Защита от ввода вредоносного кода</h4> +<p> + Если данные, которыми управляет поставщик контента, находятся в базе данных SQL, то включение в необработанные инструкции +SQL внешних ненадежных данных может привести к атаке путем внедрения кода SQL. +</p> +<p> + Рассмотрим следующее предложение выбора: +</p> +<pre> +// Constructs a selection clause by concatenating the user's input to the column name +String mSelectionClause = "var = " + mUserInput; +</pre> +<p> + Если вы используете это предложение, вы разрешаете пользователю связать вашу инструкцию SQL с вредоносным кодом SQL. + Например, пользователь может ввести nothing; DROP TABLE *; для <code>mUserInput</code>, что +приведет к созданию следующего предложения выбора: <code>var = nothing; DROP TABLE *;</code>. Поскольку +предложение выбора выполняется в потоке как инструкция SQL, это может привести к тому, что поставщик удалит все +таблицы в соответствующей базе данных SQLite (если только в поставщик не настроен на отслеживание попыток +<a href="http://en.wikipedia.org/wiki/SQL_injection">внедрить вредоносный код SQL</a>). +</p> +<p> + Чтобы избежать этого, воспользуйтесь предложением выбора, в котором <code>?</code> выступает в качестве подставляемого +параметра, а также отдельным массивом аргументов выбора. После этого ввод пользователя +будет связан напрямую с запросом и не будет интерпретироваться как часть инструкции SQL. + Поскольку в этом случае введенный пользователем запрос не рассматривается как код SQL, то в него не удастся внедрить вредоносный код SQL. Вместо +объединения, которое следует включить в пользовательский ввод, используйте следующее предложение выбора: +</p> +<pre> +// Constructs a selection clause with a replaceable parameter +String mSelectionClause = "var = ?"; +</pre> +<p> + Настройте массив аргументов выбора следующим образом: +</p> +<pre> +// Defines an array to contain the selection arguments +String[] selectionArgs = {""}; +</pre> +<p> + Укажите значение для массива аргументов выбора: +</p> +<pre> +// Sets the selection argument to the user's input +selectionArgs[0] = mUserInput; +</pre> +<p> + Предложение выбора, в котором <code>?</code> используется в качестве подстановочного параметра, и массив +аргументов выбора представляют собой предпочтительный способ указания выбора, даже если поставщик +не использует базу данных SQL. +</p> +<!-- Displaying the results --> +<h3 id="DisplayResults">Отображение результатов запроса</h3> +<p> + Клиентский метод {@link android.content.ContentResolver#query ContentResolver.query()} всегда возвращает объект +{@link android.database.Cursor}, содержащий столбцы, указанные в проекции +запроса для строк, которые соответствуют критериям выборки в запросе. Объект +{@link android.database.Cursor} предоставляет прямой доступ на чтение содержащихся в нем строк и +столбцов. С помощью методов {@link android.database.Cursor} можно выполнить итерацию по строкам +в результатах, определить тип данных для каждого столбца, получить данные из столбца, а также проверить другие свойства +результатов. Некоторые реализации объекта {@link android.database.Cursor} автоматически обновляют +объект при изменении данных в поставщике или запускают выполнение методов в объекте-наблюдателе +при изменении объекта{@link android.database.Cursor}, либо выполняют и то, и другое. +</p> +<p class="note"> + <strong>Примечание.</strong> Поставщик может ограничить доступ к столбцам на основе характера +объекта, выполняющего запрос. Например, поставщик контактов ограничивает доступ адаптеров синхронизации к некоторым столбцам, +поэтому он не возвращает их в операцию или службу. +</p> +<p> + Если строки, соответствующие критериям выборки, отсутствуют, поставщик +возвращает объект{@link android.database.Cursor}, в котором для метода +{@link android.database.Cursor#getCount Cursor.getCount()} указано значение «0» (пустой объект cursor). +</p> +<p> + При возникновении внутренней ошибки результаты запроса зависят от определенного поставщика. Поставщик может +возвратить<code>null</code> или выдать {@link java.lang.Exception}. +</p> +<p> + Поскольку {@link android.database.Cursor} представляет собой «список» строк, то наилучшим способом отобразить содержимое объекта +{@link android.database.Cursor} будет связать его с {@link android.widget.ListView} +посредством {@link android.widget.SimpleCursorAdapter}. +</p> +<p> + Следующий фрагмент кода является продолжением предыдущего фрагмента. Он создает объект +{@link android.widget.SimpleCursorAdapter}, содержащий объект{@link android.database.Cursor}, +который был получен в запросе, а затем определяет этот объект в качестве адаптера для +{@link android.widget.ListView}: +</p> +<pre class="prettyprint"> +// Defines a list of columns to retrieve from the Cursor and load into an output row +String[] mWordListColumns = +{ + UserDictionary.Words.WORD, // Contract class constant containing the word column name + UserDictionary.Words.LOCALE // Contract class constant containing the locale column name +}; + +// Defines a list of View IDs that will receive the Cursor columns for each row +int[] mWordListItems = { R.id.dictWord, R.id.locale}; + +// Creates a new SimpleCursorAdapter +mCursorAdapter = new SimpleCursorAdapter( + getApplicationContext(), // The application's Context object + R.layout.wordlistrow, // A layout in XML for one row in the ListView + mCursor, // The result from the query + mWordListColumns, // A string array of column names in the cursor + mWordListItems, // An integer array of view IDs in the row layout + 0); // Flags (usually none are needed) + +// Sets the adapter for the ListView +mWordList.setAdapter(mCursorAdapter); +</pre> +<p class="note"> + <strong>Примечание.</strong> Чтобы вернуть {@link android.widget.ListView} с объектом +{@link android.database.Cursor}, объект cursor должен содержать столбец с именем <code>_ID</code>. + Поэтому показанный ранее запрос извлекает столбец<code>_ID</code> для таблицы +words, даже если {@link android.widget.ListView} не отображает ее. + Данное ограничение также объясняет, почему в каждой таблице поставщика имеется столбец +<code>_ID</code>. +</p> + + <!-- Getting data from query results --> +<h3 id="GettingResults">Получение данных из результатов запроса</h3> +<p> + Вместо того, чтобы просто отобразить результаты запроса, вы можете использовать их для выполнения других задач. Например, +можно получить написание слов из пользовательского словаря, а затем выполнить их поиск в +других поставщиках. Для этого выполните итерацию по строкам в объекте {@link android.database.Cursor}: +</p> +<pre class="prettyprint"> + +// Determine the column index of the column named "word" +int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); + +/* + * Only executes if the cursor is valid. The User Dictionary Provider returns null if + * an internal error occurs. Other providers may throw an Exception instead of returning null. + */ + +if (mCursor != null) { + /* + * Moves to the next row in the cursor. Before the first movement in the cursor, the + * "row pointer" is -1, and if you try to retrieve data at that position you will get an + * exception. + */ + while (mCursor.moveToNext()) { + + // Gets the value from the column. + newWord = mCursor.getString(index); + + // Insert code here to process the retrieved word. + + ... + + // end of while loop + } +} else { + + // Insert code here to report an error if the cursor is null or the provider threw an exception. +} +</pre> +<p> + Реализации объекта {@link android.database.Cursor} содержат несколько методов get для +получения из объекта различных типов данных. Например, в следующем фрагменте кода используется метод +{@link android.database.Cursor#getString getString()}. В них также имеется метод +{@link android.database.Cursor#getType getType()}, который возвращает значение, указывающее на тип +данных в столбце. +</p> + + + <!-- Requesting permissions --> +<h2 id="Permissions">Разрешения поставщика контента</h2> +<p> + Приложение поставщика может задавать разрешения, которые требуются другим приложениям для доступа к +данным в поставщике. Такие разрешения гарантируют, что пользователь знает, к каким +данным приложение будет пытаться получить доступ. На основе требований поставщика другие +приложения запрашивают разрешения, которые требуются им для доступа к поставщику. Конечные пользователи видят +запрошенные разрешения при установке приложения. +</p> +<p> + Если приложение поставщика не задает никаких разрешений, другие приложения не получают доступ к +данным поставщика. Однако компонентам приложения поставщика +всегда предоставлен полный доступ на чтение и запись, независимо от заданных разрешений. +</p> +<p> + Как уже было отмечено ранее, для получения данных из поставщика пользовательского словаря требуется разрешение +<code>android.permission.READ_USER_DICTIONARY</code>. + В поставщике предусмотрено отдельное разрешение<code>android.permission.WRITE_USER_DICTIONARY</code> +для вставки, обновления или удаления данных. +</p> +<p> + Чтобы получить разрешения, необходимые для доступа к поставщику, приложение запрашивает их с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +в файле манифеста. При установке менеджером пакетов Android приложения пользователю необходимо +утвердить все разрешения, запрашиваемые приложением. В случае утверждения всех разрешений +менеджер пакетов продолжает установку; если же пользователь отклоняет их, менеджер +пакетов отменяет установку. +</p> +<p> + Для запроса доступа на чтение данных в поставщике пользовательского словаря используется +следующий элемент +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code>: +</p> +<pre> + <uses-permission android:name="android.permission.READ_USER_DICTIONARY"> +</pre> +<p> + Дополнительные сведения о влиянии разрешений на доступ к поставщику представлены в статье +<a href="{@docRoot}guide/topics/security/security.html">Безопасность и разрешения</a>. +</p> + + +<!-- Inserting, Updating, and Deleting Data --> +<h2 id="Modifications">Вставка, обновление и удаление данных</h2> +<p> + Подобно тому, как вы получаете данные от поставщика, вы также можете можете использовать возможности взаимодействия между клиентом поставщика и объектом +{@link android.content.ContentProvider} поставщика для изменения данных. + Можно вызвать метод объекта {@link android.content.ContentResolver}, указав аргументы, +которые были переданы в соответствующий метод объекта {@link android.content.ContentProvider}. Поставщик и клиент поставщика +автоматически обрабатывают взаимодействие между процессами и обеспечивают безопасность. +</p> +<h3 id="Inserting">Вставка данных</h3> +<p> + Для вставки данных в поставщик вызовите +метод +{@link android.content.ContentResolver#insert ContentResolver.insert()}. Этот метод вставляет новую строку в поставщик и возвращает URI контента для этой строки. + В следующем фрагменте кода демонстрируется порядок вставки нового слова в поставщик пользовательского словаря: +</p> +<pre class="prettyprint"> +// Defines a new Uri object that receives the result of the insertion +Uri mNewUri; + +... + +// Defines an object to contain the new values to insert +ContentValues mNewValues = new ContentValues(); + +/* + * Sets the values of each column and inserts the word. The arguments to the "put" + * method are "column name" and "value" + */ +mNewValues.put(UserDictionary.Words.APP_ID, "example.user"); +mNewValues.put(UserDictionary.Words.LOCALE, "en_US"); +mNewValues.put(UserDictionary.Words.WORD, "insert"); +mNewValues.put(UserDictionary.Words.FREQUENCY, "100"); + +mNewUri = getContentResolver().insert( + UserDictionary.Word.CONTENT_URI, // the user dictionary content URI + mNewValues // the values to insert +); +</pre> +<p> + Данные для новой строки поступают в один объект {@link android.content.ContentValues}, +который аналогичен объекту cursor с одной строкой. Столбцы в этом объекте необязательно +должны содержать данные такого же типа, и если вы вообще не собираетесь указывать значение, вы можете задать для столбца значение +<code>null</code> с помощью метода {@link android.content.ContentValues#putNull ContentValues.putNull()}. +</p> +<p> + Код в представленном фрагменте не добавляет столбец <code>_ID</code>, поскольку этот столбец сохраняется +автоматически. Поставщик присваивает уникальное значение <code>_ID</code> каждой +добавляемой строке. Обычно поставщики используют это значение в качестве основного ключа таблицы. +</p> +<p> + URI контента, возвращенный в элементе <code>newUri</code>, служит для идентификации новой добавленной строки +в следующем формате: +</p> +<pre> +content://user_dictionary/words/<id_value> +</pre> +<p> + <code><id_value></code> — это содержимое столбца <code>_ID</code> для новой строки. + Большинство поставщиков автоматически определяют эту форму URI контента, а затем +выполняют запрошенную операцию с требуемой строкой. +</p> +<p> + Чтобы получить значение <code>_ID</code> из возвращенного объекта {@link android.net.Uri}, вызовите метод +{@link android.content.ContentUris#parseId ContentUris.parseId()}. +</p> +<h3 id="Updating">Обновление данных</h3> +<p> + Чтобы обновить строку, используйте объект {@link android.content.ContentValues} с обновленными +значениями (точно так же, как вы это делаете при вставке) и критериями выборки (так же, как и с запросом). + Используемый вами клиентский метод называется +{@link android.content.ContentResolver#update ContentResolver.update()}. Вам не нужно добавлять значения в объект +{@link android.content.ContentValues} для обновляемых столбцов. Чтобы очистить содержимое столбца, задайте значение +<code>null</code>. +</p> +<p> + Следующий фрагмент кода служит для изменения языка во всех строках, где в качестве языка указано en, на +<code>null</code>. Возвращаемое значение представляет собой количество строк, которые были обновлены: +</p> +<pre> +// Defines an object to contain the updated values +ContentValues mUpdateValues = new ContentValues(); + +// Defines selection criteria for the rows you want to update +String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; +String[] mSelectionArgs = {"en_%"}; + +// Defines a variable to contain the number of updated rows +int mRowsUpdated = 0; + +... + +/* + * Sets the updated value and updates the selected words. + */ +mUpdateValues.putNull(UserDictionary.Words.LOCALE); + +mRowsUpdated = getContentResolver().update( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mUpdateValues // the columns to update + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + Также следует проверить пользовательский ввод при вызове метода +{@link android.content.ContentResolver#update ContentResolver.update()}. Дополнительные сведения об этом +представлены в разделе <a href="#Injection">Защита от ввода вредоносного кода</a>. +</p> +<h3 id="Deleting">Удаление данных</h3> +<p> + Удаление данных аналогично получению данных строки: необходимо указать критерии выборки для строк, +которые требуется удалить, после чего клиентский метод возвратит количество удаленных строк. + Ниже представлен фрагмент кода для удаления строк с идентификатором appid user. Метод возвращает +количество удаленных строк. +</p> +<pre> + +// Defines selection criteria for the rows you want to delete +String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; +String[] mSelectionArgs = {"user"}; + +// Defines a variable to contain the number of rows deleted +int mRowsDeleted = 0; + +... + +// Deletes the words that match the selection criteria +mRowsDeleted = getContentResolver().delete( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + Также следует проверить пользовательский ввод при вызове метода +{@link android.content.ContentResolver#delete ContentResolver.delete()}. Дополнительные сведения об этом +представлены в разделе <a href="#Injection">Защита от ввода вредоносного кода</a>. +</p> +<!-- Provider Data Types --> +<h2 id="DataTypes">Типы поставщиков данных</h2> +<p> + Поставщики контента могут предоставлять различные тип данных. Поставщик пользовательского словаря предоставляет только +текст, но также может предоставлять следующие форматы: +</p> + <ul> + <li> + целое число; + </li> + <li> + длинное целое число (long); + </li> + <li> + число с плавающей запятой; + </li> + <li> + длинное число с плавающей запятой (double). + </li> + </ul> +<p> + Другим типом данных, предлагаемых поставщиком, является большой двоичный объект (BLOB), реализованный как +64-разрядный массив. Чтобы просмотреть доступные типы данных, обратитесь к методам get класса +{@link android.database.Cursor}. +</p> +<p> + Тип данных для каждого столбца в поставщике обычно указывается в документации к поставщику. + Типы данных для поставщика пользовательского словаря указаны в справочной документации +для класса-контракта {@link android.provider.UserDictionary.Words} (дополнительные сведения о классах-контрактах представлены в разделе +<a href="#ContractClasses">Классы-контракты</a>). + Также определить тип данных можно путем вызова метода {@link android.database.Cursor#getType +Cursor.getType()}. +</p> +<p> + Поставщики также хранят информацию о типе данных MIME для каждого определяемого ими URI контента. Эту информацию +можно использовать для определения того, может ли ваше приложение обрабатывать предлагаемые +поставщиком данные, а также для выбора типа обработки на основе типа MIME. Информация о типе +MIME обычно требуется при работе с поставщиком, который содержит +сложные структуры данных или файлы. Например, в таблице{@link android.provider.ContactsContract.Data} +в поставщике контактов используются типы MIME для отметки типа данных контакта, которые хранятся в каждой +строке. Чтобы получить тип MIME, соответствующий URI контента, вызовите метод +{@link android.content.ContentResolver#getType ContentResolver.getType()}. +</p> +<p> + Синтаксис стандартных и настраиваемых типов MIME описан в +<a href="#MIMETypeReference">справке по типам MIME</a>. +</p> + + +<!-- Alternative Forms of Provider Access --> +<h2 id="AltForms">Альтернативные формы доступа к поставщику</h2> +<p> + При разработке приложения следует учитывать три альтернативных формы доступа к поставщику: +</p> +<ul> + <li> + <a href="#Batch">Пакетный доступ:</a> можно создать пакет вызовов доступа с использованием методов в классе +{@link android.content.ContentProviderOperation}, а затем применить их с помощью метода +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. + </li> + <li> + Асинхронные запросы: Запросы следует выполнять в отдельном потоке. Одним из способов реализовать это является использование объекта +{@link android.content.CursorLoader}. Примеры этого представлены в +статье +<a href="{@docRoot}guide/components/loaders.html">Загрузчики</a>. + </li> + <li> + <a href="#Intents">Доступ к данным с помощью намерений:</a> Несмотря на то, что намерение +невозможно отправить напрямую в поставщик, вы можете отправить запрос в приложение поставщика, в котором обычно +имеется больше возможностей для изменения данных поставщика. + </li> +</ul> +<p> + Пакетный доступ и изменение с помощью намерений описаны в следующих разделах. +</p> +<h3 id="Batch">Пакетный доступ</h3> +<p> + Пакетный доступ к поставщику полезно использовать в случаях, когда необходимо вставить большое количество строк, или для вставки +строк в несколько таблиц в рамках одного вызова метода, а также в общих случаях для выполнения ряда +операций на границах процессов в виде транзакции (атомарной операции). +</p> +<p> + Для доступа к поставщику в «пакетном режиме» необходимо создать массив объектов +{@link android.content.ContentProviderOperation}, а затем +отправить их в поставщик контента с помощью метода +{@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()}. В этот метод необходимо передать +<em>центр</em> поставщика контента, а не определенный URI контента. +Это позволит каждому объекту {@link android.content.ContentProviderOperation} в массиве взаимодействовать +с разными таблицами. Метод {@link android.content.ContentResolver#applyBatch +ContentResolver.applyBatch()} возвращает массив результатов. +</p> +<p> + В описании класса-контракта {@link android.provider.ContactsContract.RawContacts} +также представлен фрагмент кода, в котором демонстрируется вставка в пакетном режиме. В исходном файле <code>ContactAdder.java</code> примера приложения +<a href="{@docRoot}resources/samples/ContactManager/index.html">Диспетчер контактов</a> +имеется пример пакетного +доступа. +</p> +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Отображение данных с помощью вспомогательного приложения</h2> +<p> + Если вашему приложению <em>не предоставлены</em> разрешения, вы по-прежнему можете воспользоваться +намерением для отображения данных в другом приложении. Например, приложение «Календарь» принимает намерение +{@link android.content.Intent#ACTION_VIEW}, которое позволяет отобразить определенную дату или событие. + Благодаря этому информацию календаря можно отображать без необходимости создавать собственный пользовательский интерфейс. +Дополнительные сведения об этой функции представлены в статье +<a href="{@docRoot}guide/topics/providers/calendar-provider.html">Поставщик календаря</a>. +</p> +<p> + Приложение, в которое вы отправляете намерение, не обязательно +должно быть связано с поставщиком. Например, +в поставщике контактов можно создать форму контакта, а затем отправить намерение {@link android.content.Intent#ACTION_VIEW}, +содержащее URI контента для изображения контакта, в средство просмотра изображений. +</p> +</div> +</div> +<h3 id="Intents">Доступ к данным с помощью намерений</h3> +<p> + Намерения позволяют в обход получать доступ к поставщику контента. Вы можете разрешить пользователям доступ к +данным в поставщике даже в том случае, если у приложения отсутствуют разрешения на доступ, либо путем +получения результирующего намерения от приложения, у которого имеются необходимые разрешения, либо путем активации +приложения, у которого имеются разрешения и которое разрешает пользователю работать с ним. +</p> +<h4>Получение доступа с временными разрешениями</h4> +<p> + Вы можете получить доступ к данным в поставщике контента даже тогда, когда у вас нет необходимых разрешений на доступ +, путем отправки намерения в приложение, у которого есть такие разрешения, и получения +результирующего намерения, которое содержит разрешения URI. + Эти разрешения для определенного URI контента действуют до тех пор, пока не будет завершена операция, получившая +их. Приложение, у которой имеются бессрочные разрешения, предоставляет временные +разрешения путем задания соответствующего флага в результирующем намерении: +</p> +<ul> + <li> + <strong>Разрешение на чтение:</strong> +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} + </li> + <li> + <strong>Разрешение на запись:</strong> +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} + </li> +</ul> +<p class="note"> + <strong>Примечание.</strong> Эти флаги не предоставляют общий доступ на чтение или запись поставщику, +центр которого указан в URI контента. Доступ предоставляется только самому URI. +</p> +<p> + Поставщик определяет разрешения URI для URI контента в своем манифесте с помощью атрибута +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">android:grantUriPermission</a></code> +элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>, +а также +с помощью дочернего элемента +<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission></a></code> +элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>. Дополнительные сведения о механизме разрешений URI представлены в статье +<a href="{@docRoot}guide/topics/security/security.html">Безопасность и разрешения</a> в разделе +Разрешения URI. +</p> +<p> + Например, можно получить данные о контакте из поставщика контактов, даже если у вас нет разрешения +{@link android.Manifest.permission#READ_CONTACTS}. Возможно, это потребуется реализовать +в приложении, которое отправляет электронные поздравления контакту в день его рождения. Вместо запроса +{@link android.Manifest.permission#READ_CONTACTS}, когда вы получаете доступ ко всем контактам пользователя +и всей информации о них, можно предоставить пользователю возможность указать, +какие контакты используются вашим приложением. Для этого воспользуйтесь указанным ниже процессом. +</p> +<ol> + <li> + Ваше приложение отправляет намерение, содержащее действие +{@link android.content.Intent#ACTION_PICK} и тип MIME +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} контактов, используя для этого метод +{@link android.app.Activity#startActivityForResult +startActivityForResult()}. + </li> + <li> + Поскольку это намерение соответствует условиям отбора намерений для операции выбора +приложения «Контакты», эта операция переходит на передний план. + </li> + <li> + В операции выбора пользователь выбирает +контакт для обновления. Когда это происходит, операция выбора вызывает метод +{@link android.app.Activity#setResult setResult(resultcode, intent)} +для создания намерения, которое будет передано обратно в ваше приложение. Намерение содержит URI контента +выбранного пользователем контакта, а также флаги +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} дополнительных данных. Эти флаги предоставляют вашему приложению разрешение URI +на чтение данных контакта, на который указывает +URI контента. Затем операция выбора вызывает метод{@link android.app.Activity#finish()}, +чтобы вернуть управление вашему приложению. + </li> + <li> + Ваша операция возвращается на передний план, а система вызывает метод +{@link android.app.Activity#onActivityResult onActivityResult()} +вашей операции. Этот метод получает результирующее намерение, созданное операцией выбора в +приложении «Контакты». + </li> + <li> + С помощью URI контента из результирующего намерения можно выполнить чтение данных контакта +из поставщика контактов, даже если вы не запрашивали у поставщика постоянный доступ на чтение +в своем манифесте. Можно получить информацию о дне рождения контакта +или сведения о его адресе эл. почты, а затем отправить контакту электронное поздравление. + </li> +</ol> +<h4>Использование другого приложения</h4> +<p> + Простой способ разрешить пользователю изменять данные, на доступ к которым у вас нет доступа +— это активировать приложение, у которого есть такие разрешения, а затем предоставить пользователю возможность выполнять необходимые действия в этом приложении. +</p> +<p> + Например, приложение «Календарь» принимает намерения +{@link android.content.Intent#ACTION_INSERT}, с помощью которого можно активировать +пользовательский интерфейс приложения для вставки. Вы можете передать в это намерение дополнительные данные, которые приложение +использует для заполнения полей в пользовательском интерфейсе. Поскольку синтаксис повторяющихся событий довольно сложный, то события предпочтительно +вставлять в поставщик календаря путем активации приложения «Календарь» с помощью действия +{@link android.content.Intent#ACTION_INSERT} и последующего предоставления пользователю возможности самому вставить событие в этом приложении. +</p> +<!-- Contract Classes --> +<h2 id="ContractClasses">Классы-контракты</h2> +<p> + Класс-контракт определяет константы, которые обеспечивают для приложений возможность работать с URI контента, именами +столбцов, операциями намерения и другими функциями поставщика контента. Классы-контракты не +включены в поставщик; разработчику поставщика следует определить их и сделать +их доступными для других разработчиков. Многие из поставщиков, включенные в платформу Android, +содержат соответствующие классы-контракты в пакете {@link android.provider}. +</p> +<p> + Например, в поставщике пользовательского календаря имеется класс-контракт +{@link android.provider.UserDictionary}, содержащий константы URI контента и имен столбцов. URI +контента для таблицы words определен в константе +{@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI}. + В классе {@link android.provider.UserDictionary.Words} также имеются константы имен столбцов, +которые используются в фрагментах кода примера приложения, представленных в этой статье. Например, проекцию запроса +можно определить следующим образом: +</p> +<pre> +String[] mProjection = +{ + UserDictionary.Words._ID, + UserDictionary.Words.WORD, + UserDictionary.Words.LOCALE +}; +</pre> +<p> + Другим классом-контрактом является класс {@link android.provider.ContactsContract} для поставщика контактов. + В справочной документации к этому классу представлены фрагменты кода примера приложения. Один из его подклассов, +{@link android.provider.ContactsContract.Intents.Insert}, представляет собой класс-контракт, +который содержит константы для намерений и их данных. +</p> + + +<!-- MIME Type Reference --> +<h2 id="MIMETypeReference">Справка по типам MIME</h2> +<p> + Поставщики контента могут возвращать как стандартные типы мультимедиа MIME, так и строки с настраиваемым типом MIME, либо оба этих типа. +</p> +<p> + Типы MIME имеют следующий формат: +</p> +<pre> +<em>type</em>/<em>subtype</em> +</pre> +<p> + Например, хорошо известный тип MIME <code>text/html</code> имеет тип <code>text</code> и подтип +<code>html</code>. Если поставщик возвращает этот тип URI, это означает, что +строка запроса, в которой используется этот URI, возвратит текста с тегами HTML. +</p> +<p> + Строки с настраиваемым типом MIME, которые также называются типами MIME поставщика, имеют более сложные значения +<em>типов</em> и <em>подтипов</em>. Значение <em>типа</em> всегда следующее: +</p> +<pre> +vnd.android.cursor.<strong>dir</strong> +</pre> +<p> + для нескольких строк, или +</p> +<pre> +vnd.android.cursor.<strong>item</strong> +</pre> +<p> + для одной строки. +</p> +<p> + <em>Подтип</em> зависит от поставщика. Встроенные поставщики Android обычно содержат простой +подтип. Например, когда приложение «Контакты» создает строку для номера телефона, +оно задает следующий тип MIME в этой строке: +</p> +<pre> +vnd.android.cursor.item/phone_v2 +</pre> +<p> + Обратите внимание, что значение подтипа просто <code>phone_v2</code>. +</p> +<p> + Разработчики поставщиков могут создавать свои собственные шаблоны подтипов на основе +центра и названий таблиц поставщика. Например, рассмотрим поставщик, который содержит расписание движения поездов. + Центром поставщика является <code>com.example.trains</code>, в котором содержатся таблицы +Line1, Line2 и Line3. В ответ на следующий URI контента +</p> +<p> +<pre> +content://com.example.trains/Line1 +</pre> +<p> + для таблицы Line1 поставщик возвращает следующий тип MIME +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.example.line1 +</pre> +<p> + В ответ на следующий URI контента +</p> +<pre> +content://com.example.trains/Line2/5 +</pre> +<p> + для строки 5 в таблице Line2 поставщик возвращает следующий тип MIME +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.example.line2 +</pre> +<p> + В большинстве поставщиков контента определены константы класса-контракта для используемых в них типов MIME. Например, класс-контракт +{@link android.provider.ContactsContract.RawContacts} +поставщика контактов определяет константу +{@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} для типа MIME одной +строки необработанного контакта. +</p> +<p> + URI контента для единичных строк описываются в разделе +<a href="#ContentURIs">URI контента</a>. +</p> diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 000000000000..d8f787393eaa --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=Создание поставщика контента +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + +<h2>Содержание документа</h2> +<ol> + <li> + <a href="#DataStorage">Проектирование хранилища данных</a> + </li> + <li> + <a href="#ContentURI">Проектирование URI контента</a> + </li> + <li> + <a href="#ContentProvider">Реализация класса ContentProvider</a> + <ol> + <li> + <a href="#RequiredAccess">Необходимые методы</a> + </li> + <li> + <a href="#Query">Реализация метода query()</a> + </li> + <li> + <a href="#Insert">Реализация метода insert()</a> + </li> + <li> + <a href="#Delete">Реализация метода delete()</a> + </li> + <li> + <a href="#Update">Реализация метода update()</a> + </li> + <li> + <a href="#OnCreate">Реализация метода onCreate()</a> + </li> + </ol> + </li> + <li> + <a href="#MIMETypes">Реализация типов MIME поставщика контента</a> + <ol> + <li> + <a href="#TableMIMETypes">Типы MIME для таблиц</a> + </li> + <li> + <a href="#FileMIMETypes">Типы MIME для файлов</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClass">Реализация класса-контракта</a> + </li> + <li> + <a href="#Permissions">Реализация разрешений поставщика контента</a> + </li> + <li> + <a href="#ProviderElement">Элемент <provider></a> + </li> + <li> + <a href="#Intents">Намерения и доступ к данным</a> + </li> +</ol> +<h2>Ключевые классы</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> +<h2>Образцы кода по теме</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/NotePad/index.html"> +Пример приложения Note Pad +</a> + </li> + </ol> +<h2>См. также:</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +Основные сведения о поставщике контента</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> +Поставщик календаря</a> + </li> + </ol> +</div> +</div> + + +<p> + Поставщик контента управляет доступом к центральному репозиторию данных. Реализация +поставщика включает один или несколько классов в приложении Android, а также элементы +в файле манифеста. Один из классов реализует подкласс +{@link android.content.ContentProvider}, который выступает в роли интерфейса между вашим поставщиком и +другими приложениями. Несмотря на то, что поставщики контента изначально предназначены для предоставления доступа к данным +другим приложениям, в вашем приложении, несомненно, могут содержаться операции, которые разрешают пользователю +запрашивать и изменять данные, управляемые вашим поставщиком. +</p> +<p> + В данной статье представлены базовые инструкции по созданию поставщика контента и список необходимых для этого +API-интерфейсов. +</p> + + +<!-- Before You Start Building --> +<h2 id="BeforeYouStart">Подготовка к созданию поставщика</h2> +<p> + Прежде чем приступить к созданию поставщика, выполните указанные ниже действия. +</p> +<ol> + <li> + <strong>Решите, нужен ли вообще вам поставщик контента</strong>. Поставщик +контента требуется в случаях, если вы хотите реализовать в своем приложении одну или несколько следующих функций: + <ul> + <li>предоставление сложных данных или файлов другим приложениям;</li> + <li>предоставление пользователям возможности копировать сложные данные из вашего приложения в другие приложения;</li> + <li>предоставление настраиваемых поисковых подсказок с помощью платформы поиска.</li> + </ul> + <p> + Вам <em>не нужен</em> поставщик для работы с базой данных SQLite, если ее планируется использовать +исключительно в вашем приложении. + </p> + </li> + <li> + Если вы еще не приняли окончательное решение, ознакомьтесь со +статьей +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">Основные сведения о поставщике контента</a>, чтобы узнать подробнее о поставщиках контента. + </li> +</ol> +<p> + После этого можно приступать к созданию поставщика. Для этого выполните указанные ниже действия. +</p> +<ol> + <li> + Спроектируйте базовое хранилище для своих данных. Поставщик контента предоставляет данные двумя способами: + <dl> + <dt> + Данные для файлов + </dt> + <dd> + Данные, которые обычно поступают в файлы, такие как +фотографии, аудио- или видеоданные. Файлы следует хранить в закрытом +пространстве вашего приложения. В ответ на запрос файла из другого приложения +ваш поставщик может предложить дескриптор для файла. + </dd> + <dt> + Структурированные данные + </dt> + <dd> + Данные, которые обычно поступают в базу данных, массив или аналогичную структуру. + Данные следует хранить в той форме, которая совместима с таблицами из строк и столбцов. Строка +представляет собой объект, например, пользователя, позицию или учетную единицу. Столбец +представляет собой некоторые данные об объекте, например, имя пользователя или стоимость единицы. Обычно +данные такого типа хранятся в базе данных SQLite, однако вы можете использовать постоянное хранилище +любого типа. Дополнительные сведения о типах хранилищ, доступных в системе +Android, представлены в разделе +<a href="#DataStorage">Проектирование хранилища данных</a>. + </dd> + </dl> + </li> + <li> + Определите конкретную реализацию класса {@link android.content.ContentProvider} +и его необходимые методы. Этот класс выступает в роли интерфейса между вашими данными и остальной частью системы +Android. Дополнительные сведения об этом классе представлены в разделе +<a href="#ContentProvider">Реализация класса ContentProvider</a>. + </li> + <li> + Определите строку центра поставщика, его URI контента и имена столбцов. Если необходимо, +чтобы приложение поставщика обрабатывало намерения, также необходимо определить действия намерений, дополнительные данные +и флаги. Кроме того, необходимо определить разрешения, которые будут необходимы приложениям для доступа к вашим +данным. Все эти значения следует определить как константы в отдельном +классе-контракте; в дальнейшем этот класс можно предоставить другим разработчикам. Дополнительные сведения о + URI контента представлены в разделе +<a href="#ContentURI">Проектирование URI контента</a>. + Дополнительные сведения о намерениях представлены в разделе +<a href="#Intents">Намерения и доступ к данным</a>. + </li> + <li> + Добавьте другие дополнительные компоненты, например, демонстрационные данные или реализация адаптера +{@link android.content.AbstractThreadedSyncAdapter}, который служит для синхронизации данных между +поставщиком и облаком. + </li> +</ol> + + +<!-- Designing Data Storage --> +<h2 id="DataStorage">Проектирование хранилища данных</h2> +<p> + Поставщик контента представляет собой интерфейс для передачи данных, сохраненных в структурированном формате. Прежде чем создавать +интерфейс, определите способ хранения данных. Данные можно хранить +в любой форме, а затем спроектировать интерфейс для чтения и записи данных при необходимости. +</p> +<p> + В Android имеются некоторые технологии хранения данных: +</p> +<ul> + <li> + В системе Android имеется API базы данных SQLite, который используется собственными поставщиками Android для +хранения табличных данных. С помощью класса +{@link android.database.sqlite.SQLiteOpenHelper} можно создавать базы данных, а класс +{@link android.database.sqlite.SQLiteDatabase} представляет собой базовый класс для доступа +к базам данных. + <p> + Обратите внимание, что вам не обязательно использовать базу данных для реализации своего репозитория. Поставщик представляет собой +внешний набор таблиц, как в случае с реляционной базой данных, однако это +не является требованием к внутренней реализации поставщика. + </p> + </li> + <li> + Для хранения данных файлов в Android предусмотрены различные API-интерфейсы для работы с файлами. + Дополнительные сведения о хранилище файлов представлены в статье +<a href="{@docRoot}guide/topics/data/data-storage.html">Хранилище данных</a>. Если вы +проектируете поставщик, который предлагает мультимедийные данные, такие как музыка или видео, можно создать поставщик, +объединяющий табличные данные и файлы. + </li> + <li> + Для работы с сетевыми данными используйте классы в {@link java.net} и +{@link android.net}. Вы также можете синхронизировать сетевые данные с локальным +хранилищем данных (например, с базой данных), а затем предоставить такие данные в виде таблиц или файлов. + Такой тип синхронизации демонстрируется в +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">примере приложения адаптера синхронизации</a>. + </li> +</ul> +<h3 id="DataDesign"> + Рекомендации по проектированию данных +</h3> +<p> + Вот несколько советов и рекомендаций, касающихся проектирования структуры данных поставщика: +</p> +<ul> + <li> + В табличных данных всегда должен быть столбец для «основного ключа», который поставщик хранит +в виде уникального числового значения для каждой строки. Вы можете использовать это значение для связывания строки +со строками в других таблицах (используя его в качестве «внешнего ключа»). Несмотря на то, что вы можете использовать любое имя +для этого столбца, рекомендуется указать имя {@link android.provider.BaseColumns#_ID BaseColumns._ID}, +поскольку для связывания результатов запроса поставщика с +{@link android.widget.ListView} необходимо, чтобы один из получаемых столбцов назывался +<code>_ID</code>. + </li> + <li> + Если вы планируете предоставлять растровые изображения или очень большие фрагменты данных для файлов, то данные +следует хранить в файлах, а затем предоставлять их косвенно вместо хранения таких данных прямо в +таблице. В таком случае вам необходимо сообщить пользователям вашего поставщика о том, что для доступа к данным им потребуется воспользоваться методом +{@link android.content.ContentResolver}. + </li> + <li> + Для хранения данных разного размера или с разной структурой используйте тип +BLOB. Например, столбец BLOB можно использовать для хранения +<a href="http://code.google.com/p/protobuf">буфера протокола</a> или +<a href="http://www.json.org">структуры JSON</a>. + <p> + BLOB также можно использовать для реализации таблицы, <em>не зависящей от схемы</em>. В таблице +такого типа определяются столбец основного ключа, столбец типа MIME и один +или несколько общих столбцов BLOB. На смысл данных в столбцах BLOB +указывает значение в столбце типа MIME. Благодаря этому в одной и той же таблице можно хранить строки +разных типов. Примером таблицы, не зависящей от схемы, может служить таблица с данными поставщика +контента +{@link android.provider.ContactsContract.Data}. + </p> + </li> +</ul> +<!-- Designing Content URIs --> +<h2 id="ContentURI">Проектирование URI контента</h2> +<p> + <strong>URI контента</strong> представляет собой URI, который определяет данные в поставщике. URI контента +могут включать символическое имя всего поставщика (его <strong>центр</strong>) и +имя, которое указывает на таблицу или файл (<strong>путь</strong>). Дополнительная часть URI с идентификатором +указывает на отдельную строку в таблице. У каждого метода доступа к данным в классе +{@link android.content.ContentProvider} имеется URI контента (в виде аргумента); благодаря этому вы можете +определить таблицу, строку или файл для доступа. +</p> +<p> + Базовые сведения о URI контента представлены в +статье +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">Основные сведения о поставщике контента</a>. +</p> +<h3>Проектирование центра поставщика</h3> +<p> + У поставщика обычно имеется только один центр, который выступает в качестве его внутреннего имени в системе Android. Во +избежание конфликтов с другими поставщиками в качестве основы центра поставщика должны выступать сведения о владении доменом в Интернете +(в обратном порядке). Поскольку эта рекомендация также применяется и к названиям пакетов Android, +вы можете определить центр своего поставщика в виде расширения названия +пакета, в котором содержится поставщик. Например, если пакет Android называется +<code>com.example.<appname></code>, то центром +вашего поставщика должен быть <code>com.example.<appname>.provider</code>. +</p> +<h3>Проектирование структуры пути</h3> +<p> + Обычно разработчики создают URI контента на основе центра поставщика, добавляя к нему путь, который указывает +на отдельные таблицы. Например, если имеется две таблицы, <em>table1</em> и +<em>table2</em>, центр поставщика из предыдущего примера следует объединить для формирования +следующих URI контента: +<code>com.example.<appname>.provider/table1</code> и +<code>com.example.<appname>.provider/table2</code>. Пути не ограничены +одним сегментом, и не на каждом уровне пути имеется таблица. +</p> +<h3>Обработка идентификаторов URI контента</h3> +<p> + Обычно поставщики предоставляют доступ к одной строке в таблице путем принятия URI контента, +в конце которого указано значение идентификатора строки. Также поставщики обычно проверяют совпадение +значения идентификатора по столбцу <code>_ID</code> в таблице и предоставляют запрашиваемый доступ к +соответствующей строке. +</p> +<p> + Это упрощает создание общего метода проектирования для приложений, получающих доступ к поставщику. Приложение +отправляет запрос поставщику и отображает полученный в результате такого запроса объект {@link android.database.Cursor} +в объекте {@link android.widget.ListView} с помощью {@link android.widget.CursorAdapter}. + Для определения {@link android.widget.CursorAdapter} необходимо, чтобы один из столбцов в объекте +{@link android.database.Cursor} назывался <code>_ID</code> +</p> +<p> + Затем пользователь выбирает в пользовательском интерфейсе одну из отображаемых строк, чтобы просмотреть данные +или изменить их. Приложение получает соответствующую строку из объекта{@link android.database.Cursor} в базовом объекте +{@link android.widget.ListView}, получает значение <code>_ID</code> для этой строки, добавляет его к +URI контента, а затем отправляет поставщику запрос на доступ. Затем поставщик может +запросить или изменить строку, выбранную пользователем. +</p> +<h3>Шаблоны URI контента</h3> +<p> + Чтобы помочь вам в выборе действия для выполнения со входящим URI контента, в API поставщика +имеется класс {@link android.content.UriMatcher}, который сопоставляет шаблоны URI контента с +целочисленными значениями. Такие целочисленные значения можно использовать в операторе <code>switch</code>, +который выбирает подходящее действие для URI контента, которые соответствуют определенному шаблону. +</p> +<p> + Для определения совпадения URI контента с шаблоном используются подстановочные символы: +</p> + <ul> + <li> + <strong><code>*</code>:</strong> соответствие строке любой длины с любыми допустимыми символами; + </li> + <li> + <strong><code>#</code>:</strong> соответствие строке любой длины с цифрами. + </li> + </ul> +<p> + В качестве примера для проектирования и написания кода для обработки URI контента +рекомендуется использовать центр поставщика<code>com.example.app.provider</code>, который распознает +следующие URI контента, указывающие на таблицы: +</p> +<ul> + <li> + <code>content://com.example.app.provider/table1</code>: таблица <code>table1</code>; + </li> + <li> + <code>content://com.example.app.provider/table2/dataset1</code>: таблица +<code>dataset1</code>; + </li> + <li> + <code>content://com.example.app.provider/table2/dataset2</code>: таблица +<code>dataset2</code>; + </li> + <li> + <code>content://com.example.app.provider/table3</code>: таблица <code>table3</code>. + </li> +</ul> +<p> + Поставщик также распознает следующие URI контента, если к ним добавлен идентификатор строки (например, +<code>content://com.example.app.provider/table3/1</code> для строки с +идентификатором<code>1</code> в таблице <code>table3</code>. +</p> +<p> + Возможно использование следующих шаблонов URI контента: +</p> +<dl> + <dt> + <code>content://com.example.app.provider/*</code> + </dt> + <dd> + Совпадает с любым URI контента в поставщике. + </dd> + <dt> + <code>content://com.example.app.provider/table2/*</code>: + </dt> + <dd> + Совпадает с URI контента в таблицах <code>dataset1</code> +и <code>dataset2</code>, однако не совпадает с URI контента в таблице <code>table1</code> или +<code>table3</code>. + </dd> + <dt> + <code>content://com.example.app.provider/table3/#</code>: Совпадает с URI контента +для отдельных строк в таблице <code>table3</code>, такими как +<code>content://com.example.app.provider/table3/6</code> для строки с идентификатором +<code>6</code>. + </dt> +</dl> +<p> + Во фрагменте кода ниже показано, как работают методы в классе {@link android.content.UriMatcher}. + Этот код обрабатывает URI для всей таблицы иначе, чем для URI +для отдельной строки, используя шаблон URI контента +<code>content://<authority>/<path></code> для таблиц и шаблон +<code>content://<authority>/<path>/<id></code> — для отдельных строк. +</p> +<p> + Метод {@link android.content.UriMatcher#addURI(String, String, int) addURI()} сопоставляет +центр поставщика и его путь с целочисленным значением. Метод {@link android.content.UriMatcher#match(Uri) +match()} возвращает целочисленное значение для URI. Оператор <code>switch</code> +выбирает, следует ли ему выполнить запрос всей таблицы или только отдельной записи: +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider { +... + // Creates a UriMatcher object. + private static final UriMatcher sUriMatcher; +... + /* + * The calls to addURI() go here, for all of the content URI patterns that the provider + * should recognize. For this snippet, only the calls for table 3 are shown. + */ +... + /* + * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used + * in the path + */ + sUriMatcher.addURI("com.example.app.provider", "table3", 1); + + /* + * Sets the code for a single row to 2. In this case, the "#" wildcard is + * used. "content://com.example.app.provider/table3/3" matches, but + * "content://com.example.app.provider/table3 doesn't. + */ + sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); +... + // Implements ContentProvider.query() + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { +... + /* + * Choose the table to query and a sort order based on the code returned for the incoming + * URI. Here, too, only the statements for table 3 are shown. + */ + switch (sUriMatcher.match(uri)) { + + + // If the incoming URI was for all of table3 + case 1: + + if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; + break; + + // If the incoming URI was for a single row + case 2: + + /* + * Because this URI was for a single row, the _ID value part is + * present. Get the last path segment from the URI; this is the _ID value. + * Then, append the value to the WHERE clause for the query + */ + selection = selection + "_ID = " uri.getLastPathSegment(); + break; + + default: + ... + // If the URI is not recognized, you should do some error handling here. + } + // call the code to actually do the query + } +</pre> +<p> + Другой класс, {@link android.content.ContentUris}, предоставляет удобные методы для работы с частью +<code>id</code> URI контента. Классы {@link android.net.Uri} и +{@link android.net.Uri.Builder} содержат удобные методы для синтаксического анализа существующих объектов +{@link android.net.Uri} и создания новых. +</p> + +<!-- Implementing the ContentProvider class --> +<h2 id="ContentProvider">Реализация класса ContentProvider</h2> +<p> + Экземпляр класса {@link android.content.ContentProvider} управляет доступом к структурированному набору данных + путем обработки запросов от других приложений. В конечном счете, при всех формах доступа +вызывается метод {@link android.content.ContentResolver}, который затем вызывает конкретный метод +{@link android.content.ContentProvider} для получения доступа. +</p> +<h3 id="RequiredAccess">Необходимые методы</h3> +<p> + В абстрактном классе {@link android.content.ContentProvider} определены шесть абстрактных методов, +которые необходимо реализовать в рамках вашего собственного конкретного подкласса. Все указанные ниже методы, кроме +{@link android.content.ContentProvider#onCreate() onCreate()}, вызываются клиентским приложением, +которое пытается получить доступ к вашему поставщику контента. +</p> +<dl> + <dt> + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +query()} + </dt> + <dd> + Получение данных от поставщика. Использует аргументы для выбора таблицы для запроса, +строк и столбцов, которые необходимо возвратить, и указания порядка сортировки результатов. + Возвращает данные в виде объекта {@link android.database.Cursor}. + </dd> + <dt> + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} + </dt> + <dd> + Вставка строки в ваш поставщик. Использует аргументы для выбора +конечной таблицы и получения значений столбца, которые следует использовать. Возвращает URI контента +для новой вставленной строки. + </dd> + <dt> + {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} + </dt> + <dd> + Обновление существующих строк в поставщике. Использует аргументы для выбора +таблицы и строк для обновления, а также для получения обновленных значений столбца. Возвращает количество обновленных строк. + </dd> + <dt> + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + </dt> + <dd> + Удаление строк из поставщика. Использует аргументы для выбора +таблицы и строк для удаления. Возвращает количество удаленных строк. + </dd> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + Возвращение типа MIME, соответствующего URI контента. Дополнительные сведения об этом методе представлены в разделе +<a href="#MIMETypes">Реализация типов MIME поставщика контента</a>. + </dd> + <dt> + {@link android.content.ContentProvider#onCreate() onCreate()} + </dt> + <dd> + Инициализация поставщика. Система Android вызывает этот метод сразу после +создания вашего поставщика. Обратите внимание, что поставщик не будет создан до тех пор, пока объект +{@link android.content.ContentResolver} не прекратит попытки получить доступ к нему. + </dd> +</dl> +<p> + Подпись этих методов аналогична подписи для идентичных методов в объекте +{@link android.content.ContentResolver}. +</p> +<p> + При реализации этих методов следует учитывать указанные ниже моменты. +</p> +<ul> + <li> + Все эти методы, кроме {@link android.content.ContentProvider#onCreate() onCreate()}, +можно вызвать сразу из нескольких потоков, поэтому они должны быть реализованы с сохранением потокобезопасности. Дополнительные сведения об +использовании нескольких потоков представлены в +статье +<a href="{@docRoot}guide/components/processes-and-threads.html">Процессы и потоки</a>. + </li> + <li> + Избегайте слишком длинных операций в методе {@link android.content.ContentProvider#onCreate() +onCreate()}. Отложите выполнение задач инициализации до тех пор, пока они не потребуются. + Дополнительные сведения об этом представлены в разделе +<a href="#OnCreate">Реализация метода onCreate</a>. + </li> + <li> + Несмотря на то, что вы должны реализовать эти методы, ваш код необязательно должен выполнять какие-либо другие действия, кроме +возврата ожидаемого типа данных. Например, может потребоваться, чтобы другие приложения не имели возможности +вставлять данные в некоторые таблицы. Для этого можно игнорировать вызов метода +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} и возвратить +0. + </li> +</ul> +<h3 id="Query">Реализация метода query()</h3> +<p> + Метод +{@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()} должен возвращать объект {@link android.database.Cursor}, а при сбое выдавать +{@link java.lang.Exception}. Если в качестве хранилища данных используется база данных SQLite, +можно просто возвратить объект {@link android.database.Cursor}, который был возвращен одним из методов +<code>query()</code> класса {@link android.database.sqlite.SQLiteDatabase}. + Если запрос не соответствует ни одной строке, следует возвратить экземпляр объекта{@link android.database.Cursor}, метод +{@link android.database.Cursor#getCount()} которого возвращает 0. + <code>null</code> следует возвращать только в том случае, если во время обработки запроса произошла внутренняя ошибка. +</p> +<p> + Если вы не используете базу данных SQLite в качестве хранилища данных, обратитесь к одному из конкретных подклассов объекта +{@link android.database.Cursor}. Например, класс {@link android.database.MatrixCursor} +реализует объект cursor, в котором каждая строка представляет собой массив класса {@link java.lang.Object}. С помощью этого класса воспользуйтесь методом +{@link android.database.MatrixCursor#addRow(Object[]) addRow()}, чтобы добавить новую строку. +</p> +<p> + Следует помнить, что система Android должна иметь возможность взаимодействовать с {@link java.lang.Exception} +в пределах процесса. Система Android позволяет это делать для указанных ниже исключений, которые могут быть полезны при обработке +ошибок запросов. +</p> +<ul> + <li> + {@link java.lang.IllegalArgumentException} (это исключение можно выдать в случае, +если поставщик получает недопустимый URI контента); + </li> + <li> + {@link java.lang.NullPointerException}. + </li> +</ul> +<h3 id="Insert">Реализация метода insert()</h3> +<p> + Метод {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} добавляет новую +строку в соответствующую строку, используя значения в аргументе +{@link android.content.ContentValues}. Если в аргументе {@link android.content.ContentValues} отсутствует имя столбца, +возможно, потребуется указать для него значение по умолчанию (либо в коде поставщика, либо в +схеме базы данных). +</p> +<p> + Этот метод должен возвращать URI контента для новой строки. Для этого добавьте значение +<code>_ID</code> новой строки (или иной основной ключ) к URI контента таблицы, используя метод +{@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()}. +</p> +<h3 id="Delete">Реализация метода delete()</h3> +<p> + Методу {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} +необязательно фактически удалять строки из вашего хранилища данных. Если для работы с поставщиком используется адаптер синхронизации, +рассмотрите возможность отметки удаленной строки флагом +delete вместо окончательного удаления строки. Адаптер синхронизации может +проверить наличие удаленных строк с флагом delete и удалить их с сервера перед удалением их из поставщика. +</p> +<h3 id="Update">Реализация метода update()</h3> +<p> + Метод {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) +update()} принимает тот же аргумент {@link android.content.ContentValues}, который используется методом +{@link android.content.ContentProvider#insert(Uri, ContentValues) insert()}, и те же аргументы +<code>selection</code> и <code>selectionArgs</code>, которые используются методами +{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} и +{@link android.content.ContentProvider#query(Uri, String[], String, String[], String) +ContentProvider.query()}. Благодаря этому код можно повторно использовать между данными методами. +</p> +<h3 id="OnCreate">Реализация метода onCreate()</h3> +<p> + Система Android вызывает метод {@link android.content.ContentProvider#onCreate() +onCreate()} при запуске поставщика. В этом методе следует выполнять только быстро выполняющиеся задачи +инициализации, а создание базы данных и загрузку данных отложить до момента фактического получения +поставщиком запроса на данные. Слишком длинные операции в методе +{@link android.content.ContentProvider#onCreate() onCreate()} приводят к увеличению времени +запуска поставщика. В свою очередь, это увеличивает время отклика поставщика на запросы от других +приложений. +</p> +<p> + Например, если вы используете базу данных SQLite, +в методе +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} можно создать новый объект +{@link android.database.sqlite.SQLiteOpenHelper}, а затем создать таблицы SQL при первом открытии базы данных. Чтобы упростить это, при первом вызове метода +{@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase +getWritableDatabase()} он автоматически вызывает метод +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}. +</p> +<p> + В двух указанных ниже фрагментах кода иллюстрируется взаимодействие между методом +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} и методом +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}. В первом фрагменте кода представлена реализация метода +{@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()}: +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider + + /* + * Defines a handle to the database helper object. The MainDatabaseHelper class is defined + * in a following snippet. + */ + private MainDatabaseHelper mOpenHelper; + + // Defines the database name + private static final String DBNAME = "mydb"; + + // Holds the database object + private SQLiteDatabase db; + + public boolean onCreate() { + + /* + * Creates a new helper object. This method always returns quickly. + * Notice that the database itself isn't created or opened + * until SQLiteOpenHelper.getWritableDatabase is called + */ + mOpenHelper = new MainDatabaseHelper( + getContext(), // the application context + DBNAME, // the name of the database) + null, // uses the default SQLite cursor + 1 // the version number + ); + + return true; + } + + ... + + // Implements the provider's insert method + public Cursor insert(Uri uri, ContentValues values) { + // Insert code here to determine which table to open, handle error-checking, and so forth + + ... + + /* + * Gets a writeable database. This will trigger its creation if it doesn't already exist. + * + */ + db = mOpenHelper.getWritableDatabase(); + } +} +</pre> +<p> + В следующем фрагменте кода представлена реализация метода +{@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) +SQLiteOpenHelper.onCreate()}, включая вспомогательный класс: +</p> +<pre class="prettyprint"> +... +// A string that defines the SQL statement for creating a table +private static final String SQL_CREATE_MAIN = "CREATE TABLE " + + "main " + // Table's name + "(" + // The columns in the table + " _ID INTEGER PRIMARY KEY, " + + " WORD TEXT" + " FREQUENCY INTEGER " + + " LOCALE TEXT )"; +... +/** + * Helper class that actually creates and manages the provider's underlying data repository. + */ +protected static final class MainDatabaseHelper extends SQLiteOpenHelper { + + /* + * Instantiates an open helper for the provider's SQLite data repository + * Do not do database creation and upgrade here. + */ + MainDatabaseHelper(Context context) { + super(context, DBNAME, null, 1); + } + + /* + * Creates the data repository. This is called when the provider attempts to open the + * repository and SQLite reports that it doesn't exist. + */ + public void onCreate(SQLiteDatabase db) { + + // Creates the main table + db.execSQL(SQL_CREATE_MAIN); + } +} +</pre> + + +<!-- Implementing ContentProvider MIME Types --> +<h2 id="MIMETypes">Реализация типов MIME для класса ContentProvider</h2> +<p> + В классе {@link android.content.ContentProvider} предусмотрены два метода для возврата типов MIME: +</p> +<dl> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + Один из необходимых методов, который требуется реализовать для каждого поставщика. + </dd> + <dt> + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + </dt> + <dd> + Метод, который нужно реализовать в случае, если поставщик предоставляет файлы. + </dd> +</dl> +<h3 id="TableMIMETypes">Типы MIME для таблиц</h3> +<p> + Метод {@link android.content.ContentProvider#getType(Uri) getType()} возвращает объект +{@link java.lang.String} в формате MIME, который описывает тип данных, возвращаемых аргументом +URI контента. Аргумент {@link android.net.Uri} может выступать в качестве шаблона, а не в виде определенного URI; +в этом случае необходимо возвратить тип данных, связанный с URI контента, который соответствует +шаблону. +</p> +<p> + Для общих типов данных, таких как текст, HTML или JPEG, метод +{@link android.content.ContentProvider#getType(Uri) getType()} должен возвращать стандартный тип +MIME. Полный список стандартных типов представлен на +веб-сайте +<a href="http://www.iana.org/assignments/media-types/index.htm">IANA MIME Media Types</a>. +</p> +<p> + Для URI контента, которые указывают на одну или несколько строк табличных данных, метод +{@link android.content.ContentProvider#getType(Uri) getType()} должен возвращать +тип MIME в формате MIME поставщика, который имеется в системе Android: +</p> +<ul> + <li> + Часть типа: <code>vnd</code> + </li> + <li> + Часть подтипа: + <ul> + <li> + Если шаблон URI предназначен для одной строки: <code>android.cursor.<strong>item</strong>/</code> + </li> + <li> + Если шаблон URI предназначен для нескольких строк: <code>android.cursor.<strong>dir</strong>/</code> + </li> + </ul> + </li> + <li> + Часть поставщика: <code>vnd.<name></code>.<code><type></code> + <p> + Вы указываете <code><name></code> и <code><type></code>. + Значение <code><name></code> должно быть уникальным глобально, +а значение <code><type></code> должно быть уникальным для соответствующего шаблона +URI. В качестве <code><name></code> рекомендуется использовать название вашей компании +или часть названия пакета Android вашего приложения. В качестве +<code><type></code> рекомендуется использовать строку, которая определяет связанную с +URI таблицу. + </p> + + </li> +</ul> +<p> + Например, если центром поставщика является +<code>com.example.app.provider</code>, который предоставляет таблицу +<code>table1</code>, то тип MIME для нескольких строк в таблице <code>table1</code> будет следующим: +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.com.example.provider.table1 +</pre> +<p> + Для одной строки в таблице <code>table1</code> тип MIME будет следующим: +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.com.example.provider.table1 +</pre> +<h3 id="FileMIMETypes">Типы MIME для файлов</h3> +<p> + Если поставщик предоставляет файлы, необходимо реализовать метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}. + Этот метод возвращает массив {@link java.lang.String} с типами MIME для файлов, +возвращаемых поставщиком для заданного URI контента. Предлагаемые поставщиком типы следует сортировать с помощью аргумента фильтра типов MIME, +чтобы возвращались только те типы MIME, которые необходимо обработать клиенту. +</p> +<p> + Например, рассмотрим поставщик, который предоставляет фотографии в виде файлов в форматах <code>.jpg</code>, +<code>.png</code> и <code>.gif</code>. + Если приложение вызывает метод {@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()} со строкой фильтра <code>image/*</code> (нечто вроде «изображения»), +то метод {@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} +должен возвращать следующий массив: +</p> +<pre> +{ "image/jpeg", "image/png", "image/gif"} +</pre> +<p> + Если же приложению требуются только файлы <code>.jpg</code>, то оно вызывает метод +{@link android.content.ContentResolver#getStreamTypes(Uri, String) +ContentResolver.getStreamTypes()} со строкой фильтра <code>*\/jpeg</code>; метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) +ContentProvider.getStreamTypes()} при этом должен возвращать следующее: +<pre> +{"image/jpeg"} +</pre> +<p> + Если поставщик не предоставляет ни один из типов MIME, запрошенных в строке фильтра, то метод +{@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} +должен возвращать <code>null</code>. +</p> + + +<!-- Implementing a Contract Class --> +<h2 id="ContractClass">Реализация класса-контракта</h2> +<p> + Класс-контракт представляет собой класс <code>public final</code>, в котором содержатся определения констант для +URI, имен столбцов, типов MIME и других метаданных поставщика. Класс +устанавливает контрактные отношения между поставщиком и другими приложениями путем обеспечения +прямого доступа к поставщику даже в случае изменения фактических значений URI, имен столбцов и +т. д. +</p> +<p> + Класс-контракт также полезен для разработчиков тем, что в нем содержатся мнемонические имена для его констант, +благодаря чему снижается риск того, что разработчики воспользуются неправильными значениями для имен столбцов или URI. Поскольку это класс, +он может содержать документацию Javadoc. Интегрированные среды разработки, такие как +Eclipse, могут автоматически заполнять имена констант из класса-контракта и отображать Javadoc для +констант. +</p> +<p> + У разработчиков нет доступа к файлу класса-контракта из вашего приложения, однако они могут +статически скомпилировать класс-контракт в свое приложение из предоставленного вами файла <code>.jar</code>. +</p> +<p> + Примом класса-контракта может служить класс +{@link android.provider.ContactsContract} и его вложенные классы. +</p> +<h2 id="Permissions">Реализация разрешений поставщика контента</h2> +<p> + Разрешения и доступ ко всем компонентам системы Android подробно описаны в статье +<a href="{@docRoot}guide/topics/security/security.html">Безопасность и разрешения</a>. + Кроме того, в статье <a href="{@docRoot}guide/topics/data/data-storage.html">Хранилище данных</a> +представлены сведения о безопасности и разрешениях, применяемых к различным типам хранилища. + Ниже приведен краткий обзор основных моментов. +</p> +<ul> + <li> + По умолчанию файлы с данными хранятся во внутреннем хранилище устройства и доступны только +вашему приложению и поставщику. + </li> + <li> + Создаваемые вами базы данных {@link android.database.sqlite.SQLiteDatabase} также доступны только вашему +приложению и поставщику. + </li> + <li> + Файлы с данными, которые вы сохраняете во внешнем хранилище, по умолчанию являются <em>общедоступными</em>, которые +<em>может считать любой пользователь</em>. Вам не удастся использовать поставщик контента для ограничения доступа к файлам, +которые хранятся во внешнем хранилище, поскольку приложения могут использовать другие вызовы API для их чтения или записи. + </li> + <li> + Вызовы метода для открытия или создания файлов или баз данных SQLite, находящихся во внутреннем хранилище на вашем устройстве, +потенциально могут предоставить всем другим приложениям доступ как на запись, так и на чтение данных. Если вы используете +внутренний файл или базу данных в качестве репозитория поставщика, выполнить чтение или запись данных в котором может +любой пользователь, то разрешений, заданных в манифесте поставщика, будет явно недостаточно для +защиты ваших данных. По умолчанию доступ к файлам и базам данных +во внутреннем хранилище является закрытым, и вам не следует изменять параметры доступа к репозиторию вашего поставщика. + </li> +</ul> +<p> + При необходимости использовать разрешения поставщика контента для управления доступом к данным, данные +следует хранить во внутренних файлах, в базах данных SQLite или в облаке (например, +на удаленном сервере), а доступ к файлам и базам данных должен быть предоставлен только вашему приложению. +</p> +<h3>Реализация разрешений</h3> +<p> + Любое приложение может выполнять чтение данных в поставщике или записывать их, даже если соответствующие данные +являются закрытыми, поскольку по умолчанию для поставщика не заданы разрешения. Чтобы изменить эти настройки, +задайте разрешения для поставщика в файле манифеста с помощью атрибутов элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> или его дочерних элементов. Можно задать разрешения, которые применяются ко всему поставщику +или только к определенным таблицам, либо даже только к определенным записям или всему дереву. +</p> +<p> + Для задания разрешений используется один или несколько +элементов <code><a href="{@docRoot}guide/topics/manifest/permission-element.html"> + <permission></a></code> в файле манифеста. Чтобы разрешения +были уникальными для поставщика, используйте области, аналогичные Java, для атрибута +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html#nm"> + android:name</a></code>. Например, присвойте разрешению на чтение имя +<code>com.example.app.provider.permission.READ_PROVIDER</code>. + +</p> +<p> + Ниже перечислены области разрешений для поставщика, начиная с разрешений, +которые применяются ко всему поставщику, и заканчивая более подробными разрешениями. + Более подробные разрешения имеют преимущество над разрешениями с более широкими областями. +</p> +<dl> + <dt> + Единичное разрешение на чтение/запись на уровне поставщика + </dt> + <dd> + Единичное разрешение, которое управляет доступом как на чтение, так и на запись для всего поставщика, которое задается с помощью атрибута +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code> элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. + </dd> + <dt> + Отдельное разрешение на чтение/запись на уровне поставщика + </dt> + <dd> + Разрешение на чтение и запись для всего поставщика. Такие разрешения задаются с помощью атрибутов +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code> и +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code> элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. Они имеют преимущественную силу над разрешением, заданным с помощью атрибута +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code>. + </dd> + <dt> + Разрешение на уровне пути + </dt> + <dd> + Разрешение на чтение, запись или чтение/запись для URI контента в поставщике. Каждый +URI, которым необходимо управлять, задается с помощью дочернего элемента +<code><a href="{@docRoot}guide/topics/manifest/path-permission-element.html"> + <path-permission></a></code> элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. Для каждого указываемого URI контента можно задать разрешение +на чтение/запись, только чтение или только запись, либо все три разрешения. Разрешения на чтение и +запись имеют преимущественную силу над разрешением на чтение/запись. Кроме того, разрешения на уровне пути +имеют преимущественную силу над разрешениями на уровне поставщика. + </dd> + <dt> + Временное разрешение + </dt> + <dd> + Разрешения этого уровня предоставляют приложению временный доступ, даже если у приложения +нет разрешений, которые обычно требуются. Функция временного доступа +ограничивает набор разрешений, которые приложению необходимо запросить в своем +манифесте. Если включены временные разрешения, единственными приложениями, +которым требуются «постоянные» разрешения на работу с поставщиком, являются те, которые непрерывно получают доступ ко всем вашим +данным. + <p> + Рассмотрим пример с разрешениями, которые необходимо реализовать для поставщика электронной почты и приложения, когда вам +необходимо разрешить внешнему приложению для просмотра изображений отображать вложенные в письма фотографии +из поставщика. Чтобы предоставить средству просмотра изображений требуемый доступ без запроса разрешений, +задайте временные разрешения для URI контента фотографий. Спроектируйте ваше приложение для работы с электронной почтой таким образом, +чтобы в случаях, когда пользователь желает отобразить фотографию, приложение отправляло намерение, в котором содержится +URI контента фотографии и флаги разрешения для средства просмотра изображений. Затем средство просмотра изображений +может отправить поставщику эл. почты запрос на получение фотографии, даже если у средства просмотра отсутствует обычное +разрешение на чтение данных из поставщика. + </p> + <p> + Чтобы включить временные разрешения, задайте атрибут +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> для элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>, либо добавьте один или несколько дочерних элементов +<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code>в ваш элемент +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. Если вы используете временные разрешения, вам необходимо вызывать метод +{@link android.content.Context#revokeUriPermission(Uri, int) +Context.revokeUriPermission()} каждый раз, когда вы осуществляете удаленную поддержку URI контента +из поставщика, а URI контента связан с временным разрешением. + </p> + <p> + Значение атрибута определяет, какая часть поставщика доступна. + Если для атрибута задано значение <code>true</code>, система предоставит временные +разрешения для всего поставщика, отменяя тем самым любые другие разрешения, которые требуются +на уровне поставщика или на уровне пути. + </p> + <p> + Если для флага задано значение <code>false</code>, вам необходимо добавить дочерние элементы +<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code> в свой элемент +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. Каждый дочерний элемент задает URI контента, +для которых предоставляется временное разрешение. + </p> + <p> + Чтобы делегировать приложению временный доступ, в намерении должны быть указаны флаги +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} или +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} , либо оба флага. Эти флаги задаются с помощью метода +{@link android.content.Intent#setFlags(int) setFlags()}. + </p> + <p> + Если атрибут <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> отсутствует, предполагается, что его значение +<code>false</code>. + </p> + </dd> +</dl> + + + +<!-- The Provider Element --> +<h2 id="ProviderElement">Элемент <provider></h2> +<p> + Как и компоненты {@link android.app.Activity} и {@link android.app.Service}, +подкласс класса {@link android.content.ContentProvider} +должен быть определен в файле манифеста приложения с помощью элемента +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. Ниже указана информация, которую система Android получает из +этого элемента. +<dl> + <dt> + Центр поставщика +(<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code +android:authorities}</a>) + </dt> + <dd> + Символические имена, которые идентифицируют весь поставщик в системе. Дополнительные сведения об этом атрибуте представлены в +разделе +<a href="#ContentURI">Проектирование URI контента</a>. + </dd> + <dt> + Название класса поставщика +(<code> +<a href="{@docRoot}guide/topics/manifest/provider-element.html#nm">android:name</a> + </code>) + </dt> + <dd> + Класс, который реализует класс {@link android.content.ContentProvider}. Дополнительные сведения об этом классе представлены в +разделе +<a href="#ContentProvider">Реализация класса ContentProvider</a>. + </dd> + <dt> + Разрешения + </dt> + <dd> + Атрибуты, которые определяют разрешения, необходимые другим приложениям для +доступа к данным в поставщике: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermssions</a></code>: флаг временного разрешения; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code>: единичное разрешение на чтение/запись на уровне поставщика; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code>: разрешение на чтение на уровне поставщика; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code>: разрешение на запись на уровне поставщика. + </li> + </ul> + <p> + Дополнительные сведения о разрешениях и соответствующих атрибутах представлены в +разделе +<a href="#Permissions">Реализация разрешений поставщика контента</a>. + </p> + </dd> + <dt> + Атрибуты запуска и управления + </dt> + <dd> + Следующие атрибуты определяют порядок и время запуска поставщика системой Android, характеристики +процесса поставщика, а также другие параметры выполнения: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#enabled"> + android:enabled</a></code>: флаг, позволяющий системе запускать поставщик; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#exported"> + android:exported</a></code>: флаг, позволяющий другим приложениям использовать этот поставщик; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#init"> + android:initOrder</a></code>: порядок запуска поставщика +(относительно других поставщиков в одном и том же процессе); + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#multi"> + android:multiProcess</a></code>: флаг, позволяющий системе запускать поставщик +в том же процессе, что и вызывающий клиент; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#proc"> + android:process</a></code>: название процесса, в котором +запускается поставщик; + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#sync"> + android:syncable</a></code>: флаг, указывающий на то, что данные в поставщике +следует синхронизировать с данными на сервере. + </li> + </ul> + <p> + Полная документация по атрибутам представлена в статье, посвященной +элементу +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. + </p> + </dd> + <dt> + Информационные атрибуты + </dt> + <dd> + Дополнительный значок и метка для поставщика: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#icon"> + android:icon</a></code>: графический ресурс, содержащий значок для поставщика. + Значок отображается рядом с меткой поставщика в списке приложений в разделе +<em>Настройки</em> > <em>Приложения</em> > <em>Все</em>. + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#label"> + android:label</a></code>: информационная метка с описанием поставщика или его +данных, либо обоих описаний. Метка отображается в списке приложений в разделе +<em>Настройки</em> > <em>Приложения</em> > <em>Все</em>. + </li> + </ul> + <p> + Полная документация по атрибутам представлена в статье, посвященной +элементу<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code>. + </p> + </dd> +</dl> + +<!-- Intent Access --> +<h2 id="Intents">Намерения и доступ к данным</h2> +<p> + Приложения могут получать доступ к поставщику контента в обход с помощью объектов {@link android.content.Intent}. + Приложение при этом не вызывает какие-либо методы классов {@link android.content.ContentResolver} или +{@link android.content.ContentProvider}. Вместо этого оно отправляет намерение, запускающе операцию, +которая обычно является частью собственного приложения поставщика. Получение и отображение данных в своем пользовательском интерфейсе +выполняет конечная операция. В зависимости от действия, указанного в намерении, +конечная операция также может предложить пользователю внести изменения в данные поставщика. + В намерении также могут содержаться дополнительные данные, которые конечная операция отображает в +пользовательском интерфейсе; затем пользователю предлагается возможность изменить эти данные, прежде чем использовать их для изменения +данных в поставщике. +</p> +<p> + +</p> +<p> + Возможно, доступ с помощью намерения потребуется использовать для обеспечения целостности данных. Для вставки, +обновления и удаления данных в поставщике может существовать строго определенный программный код, реализующий его функциональные возможности. В +этом случае предоставление другим приложениям прямого доступа для изменения данных +может привести к тому, что данные станут недействительными. Если вы хотите предоставить разработчикам возможность доступа посредством намерений, вам следует тщательно задокументировать такую функцию. + Объясните им, почему доступ посредством намерений через пользовательский интерфейс вашего приложения намного лучше изменения +данных посредством их кода. +</p> +<p> + Обработка входящего намерения для изменения данных поставщика ничем не отличается +от обработки других намерений. Дополнительные сведения об использовании намерений представлены в статье +<a href="{@docRoot}guide/components/intents-filters.html">Объекты Intent и фильтры объектов Intent</a>. +</p> diff --git a/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd new file mode 100644 index 000000000000..3d7b6515c8a5 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=Поставщики контента +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + +<!-- In this document --> +<h2>Темы</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +Основные сведения о поставщике контента</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> +Создание поставщика контента</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html">Поставщик календаря</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html">Поставщик контактов</a> + </li> +</ol> + + <!-- Related Samples --> +<h2>Образцы кода по теме</h2> + <ol> + <li> + Приложение +<a href="{@docRoot}resources/samples/ContactManager/index.html">Диспетчер контактов</a> + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> +Cursor (People) +</a> + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> +Cursor (Phones)</a> + </li> + <li> + <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html"> +Пример адаптера синхронизации</a> + </li> + </ol> +</div> +</div> +<p> + Поставщики контента управляют доступом к структурированному набору данных. Они инкапсулируют данные +и предоставляют механизмы обеспечения их безопасности. Поставщики контента представляют собой стандартный +интерфейс для объединения данных в одном процессе с кодом, который выполняется в другом процессе. +</p> +<p> + Когда вам требуется доступ к данным в поставщике контента, используйте объект +{@link android.content.ContentResolver} в интерфейсе +{@link android.content.Context} вашего приложения, чтобы подключиться к поставщику как клиент. + Объект {@link android.content.ContentResolver} взаимодействует с объектом поставщика, который представляет собой экземпляр класса, реализующий объект +{@link android.content.ContentProvider}. Объект +поставщика получает от клиентов запросы данных, выполняет запрашиваемые действия и +возвращает результаты. +</p> +<p> + Вам не нужно разрабатывать собственный поставщик, если вы не планируете предоставлять доступ к своим данным +другим приложениям. Однако вам потребуется собственный поставщик для предоставления настраиваемых +поисковых подсказок в вашем собственном приложении. Вам также потребуется собственный поставщик, если вы хотите копировать и вставлять сложные данные или файлы из своего приложения +в другие приложения. +</p> +<p> + В состав системы Android входят поставщики контента, которые управляют такими данными, как аудио, видео, изображения и +личная контактная информация. Некоторые из поставщиков указаны в справочной документации для +пакета +<code><a href="{@docRoot}reference/android/provider/package-summary.html">android.provider</a> + .</code> Работать с этими поставщиками может любое приложение Android +(однако с некоторыми ограничениями). +</p><p> + Ниже перечислены статьи, в которых представлено более подробное описание поставщиков контента. +</p> +<dl> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +Основные сведения о поставщике контента</a></strong> + </dt> + <dd> + Сведения о доступе к данным в поставщике контента, которые представлены в таблицах. + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> +Создание поставщика контента</a></strong> + </dt> + <dd> + Сведения о создании своего собственного поставщика контента. + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/calendar-provider.html"> +Поставщик календаря</a></strong> + </dt> + <dd> + Сведения о доступе к поставщику календаря, который входит в состав платформы Android. + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/contacts-provider.html"> +Поставщик контактов</a></strong> + </dt> + <dd> + Сведения о доступе к поставщику контактов, который входит в состав платформы Android. + </dd> +</dl> diff --git a/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd new file mode 100644 index 000000000000..c594968490d4 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=Платформа доступа к хранилищу (Storage Access Framework) +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа +<a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle"> + <span class="more">больше информации</span> + <span class="less" style="display:none">меньше информации</span></a></h2> +<ol id="toc44" class="hide-nested"> + <li> + <a href="#overview">Обзор</a> + </li> + <li> + <a href="#flow">Поток управления</a> + </li> + <li> + <a href="#client">Создание клиентского приложения</a> + <ol> + <li><a href="#search">Поиск документов</a></li> + <li><a href="#process">Обработка результатов</a></li> + <li><a href="#metadata">Изучение метаданных документа</a></li> + <li><a href="#open">Открытие документа</a></li> + <li><a href="#create">Создание нового документа</a></li> + <li><a href="#delete">Удаление документа</a></li> + <li><a href="#edit">Редактирование документа</a></li> + <li><a href="#permissions">Удержание прав доступа</a></li> + </ol> + </li> + <li><a href="#custom">Создание собственного поставщика документов</a> + <ol> + <li><a href="#manifest">Манифест</a></li> + <li><a href="#contract">Контракты</a></li> + <li><a href="#subclass">Создание подкласса класса DocumentsProvider</a></li> + <li><a href="#security">Безопасность</a></li> + </ol> + </li> + +</ol> +<h2>Ключевые классы</h2> +<ol> + <li>{@link android.provider.DocumentsProvider}</li> + <li>{@link android.provider.DocumentsContract}</li> +</ol> + +<h2>Видео</h2> + +<ol> + <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4"> +DevBytes: Android 4.4 Storage Access Framework: Поставщик</a></li> + <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ"> +DevBytes: Android 4.4 Storage Access Framework: Клиент</a></li> +</ol> + + +<h2>Примеры кода</h2> + +<ol> + <li><a href="{@docRoot}samples/StorageProvider/index.html"> +Класс StorageProvider</a></li> + <li><a href="{@docRoot}samples/StorageClient/index.html"> +Класс StorageClient</a></li> +</ol> + +<h2>См. также:</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +Основные сведения о поставщике контента +</a> + </li> +</ol> + +</div> +</div> + + +<p>Платформа доступа к хранилищу (Storage Access Framework, SAF) впервые появилась в Android версии 4.4 (API уровня 19). Платформа SAF + облегчает пользователям поиск и открытие документов, изображений и других файлов +в хранилищах всех поставщиков, с которыми они работают. Стандартный удобный интерфейс +позволяет пользователям применять единый для всех приложений и поставщиков способ поиска файлов и доступа к последним добавленным файлам.</p> + +<p>Облачные или локальные службы хранения могут присоединиться к этой экосистеме, реализовав +класс {@link android.provider.DocumentsProvider}, инкапсулирующий их услуги. Клиентские +приложения, которым требуется доступ к документам поставщика, могут интегрироваться с SAF с помощью всего нескольких +строчек кода.</p> + +<p>Платформа SAF включает в себя следующие компоненты:</p> + +<ul> +<li><strong>Поставщик документов</strong>—поставщик контента, позволяющий +службе хранения (например, Диск Google) показывать файлы, которыми он управляет. Поставщик документов +реализуется как подкласс класса{@link android.provider.DocumentsProvider}. +Его схема основана на традиционной файловой иерархии, +однако физический способ хранения данных в поставщике документов остается на усмотрении разработчика. + Платформа Android включает в себя несколько встроенных поставщиков документов, таких как +Загрузки, Изображения и Видео.</li> + +<li><strong>Клиентское приложение</strong>—пользовательское приложение, вызывающее намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} и/или +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} и принимающее +файлы, возвращаемые поставщиками документов.</li> + +<li><strong>Элемент выбора</strong>—системный пользовательский интерфейс, обеспечивающий пользователям доступ к документам у всех +поставщиков документов, которые удовлетворяют критериям поиска, заданным в клиентском приложении.</li> +</ul> + +<p>Платформа SAF в числе прочих предоставляет следующие функции:</p> +<ul> +<li>позволяет пользователям искать контент у всех поставщиков документов, а не только у одного приложения;</li> +<li>обеспечивает приложению возможность долговременного, постоянного доступа к + документам, принадлежащим поставщику документов. Благодаря такому доступу пользователи могут добавлять, редактировать, + сохранять и удалять файлы, хранящиеся у поставщика;</li> +<li>поддерживает несколько учетных записей и временные корневые каталоги, например, поставщики +на USB-накопителях, которые появляются, только когда накопитель вставлен в порт. </li> +</ul> + +<h2 id ="overview">Обзор</h2> + +<p>В центре платформы SAF находится поставщик контента, являющийся +подклассом класса {@link android.provider.DocumentsProvider}. Внутри <em>поставщика документов</em>данные имеют +структуру традиционной файловой иерархии:</p> +<p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p> +<p class="img-caption"><strong>Рисунок 1.</strong> Модель данных поставщика документов. На рисунке Root (Корневой каталог) указывает на один объект Document (Документ), +который затем разветвляется в целое дерево.</p> + +<p>Обратите внимание на следующее.</p> +<ul> + +<li>Каждый поставщик документов предоставляет один или несколько +«корневых каталогов», являющихся отправными точками при обходе дерева документов. +Каждый корневой каталог имеет уникальный идентификатор {@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID} +и указывает на документ (каталог), +представляющий содержимое на уровне ниже корневого. +Корневые каталоги динамичны по своей конструкции, чтобы обеспечивать поддержку таким вариантам использования, как несколько учетных записей, +временные хранилища на USB-нкопителях и возможность для пользователя войти в систему и выйти из нее.</li> + +<li>В каждом корневом каталоге находится один документ. Этот документ указывает на количество документов <em>N</em> +каждый из которых, в свою очередь, может указывать на один или <em>N</em> документов. </li> + +<li>Каждый сервер хранилища показывает +отдельные файлы и каталоги, ссылаясь на них с помощью уникального +идентификатора {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}. +Идентификаторы документов должны быть уникальными и не меняться после присвоения, поскольку они используются для выдачи постоянных +URI, не зависящих от перезагрузки устройства.</li> + + +<li>Документ — это или открываемый файл (имеющий конкретный MIME-тип), или +каталог, содержащий другие документы (с +MIME-типом {@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR}).</li> + +<li>Каждый документ может иметь различные свойства, описываемые флагами +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS}, +такими как{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}, +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} и +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}. +Документ с одним и тем же идентификатором {@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} может находиться +в нескольких каталогах.</li> +</ul> + +<h2 id="flow">Поток управления</h2> +<p>Как было сказано выше, модель данных поставщика документов основана на традиционной +файловой иерархии. Однако физический способ хранения данных остается на усмотрение разработчика, при +условии, что к ним можно обращаться через API-интерфейс {@link android.provider.DocumentsProvider}. Например, можно +использовать для данных облачное хранилище на основе тегов.</p> + +<p>На рисунке 2 показан пример того, как приложение для обработки фотографий может использовать SAF +для доступа к сохраненным данным:</p> +<p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p> + +<p class="img-caption"><strong>Рисунок 2.</strong> Поток управления Storage Access Framework</p> + +<p>Обратите внимание на следующее.</p> +<ul> + +<li>На платформе SAF поставщики и клиенты не взаимодействуют +напрямую. Клиент запрашивает разрешение на взаимодействие +с файлами (то есть, на чтение, редактирование, создание или удаление файлов).</li> + +<li>Взаимодействие начинается, когда приложение (в нашем примере обрабатывающее фотографии) активизирует намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} или {@link android.content.Intent#ACTION_CREATE_DOCUMENT}. Намерение может включать в себя фильтры +для уточнения критериев, например, «предоставить открываемые файлы +с MIME-типом image».</li> + +<li>Когда намерение срабатывает, системный элемент выбора переходит к каждому зарегистрированному поставщику +и показывает пользователю корневые каталоги с контентом, соответствующим запросу.</li> + +<li>Элемент выбора предоставляет пользователю стандартный интерфейс, даже +если поставщики документов значительно различаются. В качестве примера на рисунке 2 +изображены Диск Google, поставщик на USB-накопителе и облачный поставщик.</li> +</ul> + +<p>На рисунке 3 показан элемент выбора, в котором пользователь для поиска изображений выбрал учетную запись +Диск Google:</p> + +<p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> + +<p class="img-caption"><strong>Рисунок 3.</strong> Элемент выбора</p> + +<p>Когда пользователь выбирает Диск Google, изображения отображаются, как показано на +рисунке 4. С этого момента пользователь может взаимодействовать с ними любыми способами, + которые поддерживаются поставщиком и клиентским приложением. + +<p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> + +<p class="img-caption"><strong>Рисунок 4.</strong> Изображения</p> + +<h2 id="client">Создание клиентского приложения</h2> + +<p>В Android версии 4.3 и ниже для того, чтобы приложение могло получать файл от другого +приложения, оно должно активизировать намерение, например, {@link android.content.Intent#ACTION_PICK} +или {@link android.content.Intent#ACTION_GET_CONTENT}. После этого пользователь должен выбрать +какое-либо одно приложение, чтобы получить файл, а оно должно предоставить пользователю +интерфейс, с помощью которого он сможет выбирать и получать файлы. </p> + +<p>Начиная с Android 4.4 и выше, у разработчика имеется дополнительная возможность — намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}, +которое отображает пользовательский интерфейс элемента выбора, управляемого системой. Этот элемент предоставляет пользователю +обзор всех файлов, доступных в других приложениях. Благодаря этому единому интерфейсу, +пользователь может выбрать файл в любом из поддерживаемых приложений.</p> + +<p>Намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} не +является заменой для намерения {@link android.content.Intent#ACTION_GET_CONTENT}. + Разработчику следует использовать то, которое лучше соответствует потребностям приложения:</p> + +<ul> +<li>используйте {@link android.content.Intent#ACTION_GET_CONTENT}, если приложению нужно просто +прочитать или импортировать данные. При таком подходе приложение импортирует копию данных, + например, файл с изображением.</li> + +<li>используйте {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, если +приложению нужна возможность долговременного, постоянного доступа к документам, принадлежащим поставщику +документов. В качестве примера можно назвать редактор фотографий, позволяющий пользователям обрабатывать +изображения, хранящиеся в поставщике документов. </li> + +</ul> + + +<p>В этом разделе показано, как написать клиентское приложение, использующее намерения +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} и +{@link android.content.Intent#ACTION_CREATE_DOCUMENT}.</p> + + +<h3 id="search">Поиск документов</h3> + +<p> +В следующем фрагменте кода намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +используется для поиска поставщиков документов, +содержащих файлы изображений:</p> + +<pre>private static final int READ_REQUEST_CODE = 42; +... +/** + * Fires an intent to spin up the "file chooser" UI and select an image. + */ +public void performFileSearch() { + + // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file + // browser. + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + + // Filter to only show results that can be "opened", such as a + // file (as opposed to a list of contacts or timezones) + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Filter to show only images, using the image MIME data type. + // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". + // To search for all documents available via installed storage providers, + // it would be "*/*". + intent.setType("image/*"); + + startActivityForResult(intent, READ_REQUEST_CODE); +}</pre> + +<p>Обратите внимание на следующее.</p> +<ul> +<li>Когда приложение активизирует намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT} +, оно запускает элемент выбора, отображающий всех поставщиков документов, соответствующих заданным критериям.</li> + +<li>Добавление категории {@link android.content.Intent#CATEGORY_OPENABLE} в +фильтры намерения приводит к отображению только тех документов, которые можно открыть, например, файлов с изображениями.</li> + +<li>Оператор {@code intent.setType("image/*")} выполняет дальнейшую фильтрацию, чтобы +отображались только документы с MIME-типом image.</li> +</ul> + +<h3 id="results">Обработка результатов</h3> + +<p>Когда пользователь выбирает документ в элементе выбора, +вызывается метод {@link android.app.Activity#onActivityResult onActivityResult()}. +Идентификатор URI, указывающий на выбранный документ, содержится в параметре{@code resultData}. + Чтобы извлечь URI, следует вызвать {@link android.content.Intent#getData getData()}. +Этот URI можно использовать для получения документа, нужного пользователю. Например: +</p> + +<pre>@Override +public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + + // The ACTION_OPEN_DOCUMENT intent was sent with the request code + // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the + // response to some other intent, and the code below shouldn't run at all. + + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + // The document selected by the user won't be returned in the intent. + // Instead, a URI to that document will be contained in the return intent + // provided to this method as a parameter. + // Pull that URI using resultData.getData(). + Uri uri = null; + if (resultData != null) { + uri = resultData.getData(); + Log.i(TAG, "Uri: " + uri.toString()); + showImage(uri); + } + } +} +</pre> + +<h3 id="metadata">Изучение метаданных документа</h3> + +<p>Имея в своем распоряжении URI документа, разработчик получает доступ к его метаданным. В следующем +фрагменте кода метаданные документа, определяемого идентификатором URI, считываются и записываются в журнал:</p> + +<pre>public void dumpImageMetaData(Uri uri) { + + // The query, since it only applies to a single document, will only return + // one row. There's no need to filter, sort, or select fields, since we want + // all fields for one document. + Cursor cursor = getActivity().getContentResolver() + .query(uri, null, null, null, null, null); + + try { + // moveToFirst() returns false if the cursor has 0 rows. Very handy for + // "if there's anything to look at, look at it" conditionals. + if (cursor != null && cursor.moveToFirst()) { + + // Note it's called "Display Name". This is + // provider-specific, and might not necessarily be the file name. + String displayName = cursor.getString( + cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + Log.i(TAG, "Display Name: " + displayName); + + int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); + // If the size is unknown, the value stored is null. But since an + // int can't be null in Java, the behavior is implementation-specific, + // which is just a fancy term for "unpredictable". So as + // a rule, check if it's null before assigning to an int. This will + // happen often: The storage API allows for remote files, whose + // size might not be locally known. + String size = null; + if (!cursor.isNull(sizeIndex)) { + // Technically the column stores an int, but cursor.getString() + // will do the conversion automatically. + size = cursor.getString(sizeIndex); + } else { + size = "Unknown"; + } + Log.i(TAG, "Size: " + size); + } + } finally { + cursor.close(); + } +} +</pre> + +<h3 id="open-client">Открытие документа</h3> + +<p>Получив URI документа, разработчик может открывать его и в целом +делать с ним всё, что угодно.</p> + +<h4>Объект растровых изображений</h4> + +<p>Приведем пример кода для открытия объекта {@link android.graphics.Bitmap}:</p> + +<pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException { + ParcelFileDescriptor parcelFileDescriptor = + getContentResolver().openFileDescriptor(uri, "r"); + FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); + parcelFileDescriptor.close(); + return image; +} +</pre> + +<p>Обратите внимание, что не следует производить эту операцию в потоке пользовательского интерфейса. Ее нужно выполнять +в фоне, с помощью {@link android.os.AsyncTask}. Когда файл с растровым изображением откроется, его +можно отобразить в виджете {@link android.widget.ImageView}. +</p> + +<h4>Получение объекта InputStream</h4> + +<p>Далее приведен пример того, как можно получить объект {@link java.io.InputStream} по идентификатору URI. В этом +фрагменте кода строчки файла считываются в объект строкового типа:</p> + +<pre>private String readTextFromUri(Uri uri) throws IOException { + InputStream inputStream = getContentResolver().openInputStream(uri); + BufferedReader reader = new BufferedReader(new InputStreamReader( + inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + fileInputStream.close(); + parcelFileDescriptor.close(); + return stringBuilder.toString(); +} +</pre> + +<h3 id="create">Создание нового документа</h3> + +<p>Приложение может создать новый документ в поставщике документов, используя намерение +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} +. Чтобы создать файл, нужно указать в намерении MIME-тип и имя файла, а затем +запустить его с уникальным кодом запроса. Об остальном позаботится платформа:</p> + + +<pre> +// Here are some examples of how you might call this method. +// The first parameter is the MIME type, and the second parameter is the name +// of the file you are creating: +// +// createFile("text/plain", "foobar.txt"); +// createFile("image/png", "mypicture.png"); + +// Unique request code. +private static final int WRITE_REQUEST_CODE = 43; +... +private void createFile(String mimeType, String fileName) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + + // Filter to only show results that can be "opened", such as + // a file (as opposed to a list of contacts or timezones). + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Create a file with the requested MIME type. + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + startActivityForResult(intent, WRITE_REQUEST_CODE); +} +</pre> + +<p>После создания нового документа можно получить его URI с помощью +метода {@link android.app.Activity#onActivityResult onActivityResult()}, чтобы иметь возможность +записывать в него данные.</p> + +<h3 id="delete">Удаление документа</h3> + +<p>Если у разработчика имеется URI документа, а объект +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +этого документа содержит флаг +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE}, +то документ можно удалить. Например:</p> + +<pre> +DocumentsContract.deleteDocument(getContentResolver(), uri); +</pre> + +<h3 id="edit">Редактирование документа</h3> + +<p>Платформа SAF позволяет редактировать текстовые документы на месте. +В следующем фрагменте кода активизируется +намерение {@link android.content.Intent#ACTION_OPEN_DOCUMENT}, а +категория {@link android.content.Intent#CATEGORY_OPENABLE} используется, чтобы отображались только +документы, которые можно открыть. Затем производится дальнейшая фильтрация, чтобы отображались только текстовые файлы:</p> + +<pre> +private static final int EDIT_REQUEST_CODE = 44; +/** + * Open a file for writing and append some text to it. + */ + private void editDocument() { + // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's + // file browser. + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + + // Filter to only show results that can be "opened", such as a + // file (as opposed to a list of contacts or timezones). + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Filter to show only text files. + intent.setType("text/plain"); + + startActivityForResult(intent, EDIT_REQUEST_CODE); +} +</pre> + +<p>Далее, из метода {@link android.app.Activity#onActivityResult onActivityResult()} +(см. <a href="#results">Обработка результатов</a>) можно вызвать код для выполнения редактирования. +В следующем фрагменте кода объект {@link java.io.FileOutputStream} +получен с помощью объекта класса {@link android.content.ContentResolver}. По умолчанию используется режим записи. +Рекомендуется запрашивать минимально необходимые права доступа, поэтому не следует запрашивать +чтение/запись, если приложению требуется только записать файл:</p> + +<pre>private void alterDocument(Uri uri) { + try { + ParcelFileDescriptor pfd = getActivity().getContentResolver(). + openFileDescriptor(uri, "w"); + FileOutputStream fileOutputStream = + new FileOutputStream(pfd.getFileDescriptor()); + fileOutputStream.write(("Overwritten by MyCloud at " + + System.currentTimeMillis() + "\n").getBytes()); + // Let the document provider know you're done by closing the stream. + fileOutputStream.close(); + pfd.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } +}</pre> + +<h3 id="permissions">Удержание прав доступа</h3> + +<p>Когда приложение открывает файл для чтения или записи, система предоставляет +ему URI-разрешение на этот файл. Разрешение действует вплоть до перезагрузки устройства. +Предположим, что в графическом редакторе требуется, чтобы у пользователя была возможность +открыть непосредственно в этом приложении последние пять изображений, которые он редактировал. Если он +перезапустил устройство, возникает необходимость снова отсылать его к системному элементу выбора для поиска +файлов. Очевидно, это далеко не идеальный вариант.</p> + +<p>Чтобы избежать такой ситуации, разработчик может удержать права доступа, предоставленные системой +его приложению. Приложение фактически принимает постоянное URI-разрешение, +предлагаемое системой. В результате пользователь получает непрерывный доступ к файлам +из приложения, независимо от перезагрузки устройства:</p> + + +<pre>final int takeFlags = intent.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); +// Check for the freshest data. +getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre> + +<p>Остается один заключительный шаг. Можно сохранить последние +URI-идентификаторы, с которыми работало приложение. Однако не исключено, что они потеряют актуальность, поскольку другое приложение +может удалить или модифицировать документ. Поэтому следует всегда вызывать +{@code getContentResolver().takePersistableUriPermission()}, чтобы получать +актуальные данные.</p> + +<h2 id="custom">Создание собственного поставщика документов</h2> + +<p> +При разработке приложения, оказывающего услуги по хранению файлов (например, +службы хранения в облаке), можно предоставить доступ к файлам при помощи +SAF, написав собственный поставщик документов. В этом разделе показано, +как это сделать.</p> + + +<h3 id="manifest">Манифест</h3> + +<p>Чтобы реализовать собственный поставщик документов, необходимо добавить в манифест приложения +следующую информацию:</p> +<ul> + +<li>Целевой API-интерфейс уровня 19 или выше.</li> + +<li>Элемент <code><provider></code>, в котором объявляется нестандартный поставщик + хранилища. </li> + +<li>Имя поставщика, т. е., имя его класса с именем пакета. +Например: <code>com.example.android.storageprovider.MyCloudProvider</code>.</li> + +<li>Имя центра поставщика, т. е. имя пакета (в этом примере — +<code>com.example.android.storageprovider</code>) с типом поставщика контента +(<code>documents</code>). Например,{@code com.example.android.storageprovider.documents}.</li> + +<li>Атрибут <code>android:exported</code>, установленный в значение <code>"true"</code>. + Необходимо экспортировать поставщик, чтобы он был виден другим приложениям.</li> + +<li>Атрибут <code>android:grantUriPermissions</code>, установленный в значение +<code>"true"</code>. Этот параметр позволяет системе предоставлять другим приложениям доступ +к контенту поставщика. Обсуждение того, как следует удерживать права доступа +к конкретному документу см. в разделе <a href="#permissions">Удержание прав доступа</a>.</li> + +<li>Разрешение {@code MANAGE_DOCUMENTS}. По умолчанию поставщик доступен +всем. Добавление этого разрешения в манифест делает поставщик доступным только системе. +Это важно для обеспечения безопасности.</li> + +<li>Атрибут {@code android:enabled}, имеющий логическое значение, определенное в файле +ресурсов. Этот атрибут предназначен для отключения поставщика на устройствах под управлением Android версии 4.3 и ниже. + Например: {@code android:enabled="@bool/atLeastKitKat"}. Помимо +включения этого атрибута в манифест, необходимо сделать следующее: +<ul> +<li>В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values/}, добавить +строчку <pre><bool name="atLeastKitKat">false</bool></pre></li> + +<li>В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values-v19/}, добавить +строчку <pre><bool name="atLeastKitKat">true</bool></pre></li> +</ul></li> + +<li>Фильтр намерения с действием +{@code android.content.action.DOCUMENTS_PROVIDER}, чтобы поставщик +появлялся в элементе выбора, когда система будет искать поставщиков.</li> + +</ul> +<p>Ниже приведены отрывки из образца манифеста, включающего в себя поставщик:</p> + +<pre><manifest... > + ... + <uses-sdk + android:minSdkVersion="19" + android:targetSdkVersion="19" /> + .... + <provider + android:name="com.example.android.storageprovider.MyCloudProvider" + android:authorities="com.example.android.storageprovider.documents" + android:grantUriPermissions="true" + android:exported="true" + android:permission="android.permission.MANAGE_DOCUMENTS" + android:enabled="@bool/atLeastKitKat"> + <intent-filter> + <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> + </intent-filter> + </provider> + </application> + +</manifest></pre> + +<h4 id="43">Поддержка устройств под управлением Android версии 4.3 и ниже</h4> + +<p>Намерение +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} доступно только +на устройствах с Android версии 4.4 и выше. +Если приложение должно поддерживать {@link android.content.Intent#ACTION_GET_CONTENT}, +чтобы обслуживать устройства, работающие под управлением Android 4.3 и ниже, необходимо +отключить фильтр намерения {@link android.content.Intent#ACTION_GET_CONTENT} в +манифесте для устройств с Android версии 4.4 и выше. Поставщик +документов и намерение {@link android.content.Intent#ACTION_GET_CONTENT} следует считать +взаимоисключающими. Если приложение поддерживает их одновременно, оно +будет появляться в пользовательском интерфейсе системного элемента выбора дважды, предлагая два различных способа доступа +к сохраненным данным. Это запутает пользователей.</p> + +<p>Отключать фильтр намерения +{@link android.content.Intent#ACTION_GET_CONTENT} на устройствах +с Android версии 4.4 и выше рекомендуется следующим образом:</p> + +<ol> +<li>В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values/}, добавить +следующую строку: <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> + +<li>В файл ресурсов {@code bool.xml}, расположенный в каталоге {@code res/values-v19/}, добавить +следующую строку: <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> + +<li>Добавить +<a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">псевдоним +операции</a>, чтобы отключить фильтр намерения {@link android.content.Intent#ACTION_GET_CONTENT} +для версий 4.4 (API уровня 19) и выше. Например: + +<pre> +<!-- This activity alias is added so that GET_CONTENT intent-filter + can be disabled for builds on API level 19 and higher. --> +<activity-alias android:name="com.android.example.app.MyPicker" + android:targetActivity="com.android.example.app.MyActivity" + ... + android:enabled="@bool/atMostJellyBeanMR2"> + <intent-filter> + <action android:name="android.intent.action.GET_CONTENT" /> + <category android:name="android.intent.category.OPENABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="image/*" /> + <data android:mimeType="video/*" /> + </intent-filter> +</activity-alias> +</pre> +</li> +</ol> +<h3 id="contract">Контракты</h3> + +<p>Как правило, при создании нестандартного поставщика контента одной из задач +является реализация классов-контрактов, описанная в руководстве для разработчиков +<a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass"> +Поставщики контента</a>. Класс-контракт представляет собой класс {@code public final}, +в котором содержатся определения констант для URI, имен столбцов, типов MIME и +других метаданных поставщика. Платформа SAF +предоставляет разработчику следующие классы-контракты, так что ему не нужно писать +собственные:</p> + +<ul> + <li>{@link android.provider.DocumentsContract.Document}</li> + <li>{@link android.provider.DocumentsContract.Root}</li> +</ul> + +<p>Например, когда +к поставщику документов приходит запрос на документы или корневой каталог, можно возвращать в курсоре следующие столбцы:</p> + +<pre>private static final String[] DEFAULT_ROOT_PROJECTION = + new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, + Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, + Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES,}; +private static final String[] DEFAULT_DOCUMENT_PROJECTION = new + String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, + Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, + Document.COLUMN_FLAGS, Document.COLUMN_SIZE,}; +</pre> + +<h3 id="subclass">Создание подкласса класса DocumentsProvider</h3> + +<p>Следующим шагом в разработке собственного поставщика документов является создание подкласса +абстрактного класса {@link android.provider.DocumentsProvider}. Как минимум, необходимо +реализовать следующие методы:</p> + +<ul> +<li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li> + +<li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li> + +<li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li> + +<li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li> +</ul> + +<p>Это единственные методы, реализация которых строго обязательна, однако существует +намного больше методов, которые, возможно, тоже придется реализовать. Подробности приводятся в описании класса{@link android.provider.DocumentsProvider} +.</p> + +<h4 id="queryRoots">Реализация метода queryRoots</h4> + +<p>Реализация метода {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} должна возвращать объект {@link android.database.Cursor}, указывающий на все +корневые каталоги поставщиков документов, используя столбцы, определенные в +{@link android.provider.DocumentsContract.Root}.</p> + +<p>В следующем фрагменте кода параметр {@code projection} представляет +конкретные поля, нужные вызывающему объекту. В этом коде создается курсор, +и к нему добавляется одна строка, соответствующая одному корневому каталогу (каталогу верхнего уровня), например, +Загрузки или Изображения. Большинство поставщиков имеет только один корневой каталог. Однако ничто не мешает иметь несколько корневых каталогов, +например, при наличии нескольких учетных записей. В этом случае достаточно добавить в +курсор еще одну строку.</p> + +<pre> +@Override +public Cursor queryRoots(String[] projection) throws FileNotFoundException { + + // Create a cursor with either the requested fields, or the default + // projection if "projection" is null. + final MatrixCursor result = + new MatrixCursor(resolveRootProjection(projection)); + + // If user is not logged in, return an empty root cursor. This removes our + // provider from the list entirely. + if (!isUserLoggedIn()) { + return result; + } + + // It's possible to have multiple roots (e.g. for multiple accounts in the + // same app) -- just add multiple cursor rows. + // Construct one row for a root called "MyCloud". + final MatrixCursor.RowBuilder row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, ROOT); + row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); + + // FLAG_SUPPORTS_CREATE means at least one directory under the root supports + // creating documents. FLAG_SUPPORTS_RECENTS means your application's most + // recently used documents will show up in the "Recents" category. + // FLAG_SUPPORTS_SEARCH allows users to search all documents the application + // shares. + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH); + + // COLUMN_TITLE is the root title (e.g. Gallery, Drive). + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); + + // This document id cannot change once it's shared. + row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir)); + + // The child MIME types are used to filter the roots and only present to the + // user roots that contain the desired type somewhere in their file hierarchy. + row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir)); + row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace()); + row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); + + return result; +}</pre> + +<h4 id="queryChildDocuments">Реализация метода queryChildDocuments</h4> + +<p>Реализация метода +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +должна возвращать объект{@link android.database.Cursor}, указывающий на все файлы в +заданном каталоге, используя столбцы, определенные в +{@link android.provider.DocumentsContract.Document}.</p> + +<p>Этот метод вызывается, когда в интерфейсе элемента выбора пользователь выбирает корневой каталог приложения. +Метод получает документы-потомки каталога на уровне ниже корневого. Его можно вызывать на любом уровне +файловой иерархии, а не только в корневом каталоге. В следующем фрагменте кода +создается курсор с запрошенными столбцами. Затем в него заносится информация о +каждом ближайшем потомке родительского каталога. +Потомком может быть изображение, еще один каталог, в общем, любой файл:</p> + +<pre>@Override +public Cursor queryChildDocuments(String parentDocumentId, String[] projection, + String sortOrder) throws FileNotFoundException { + + final MatrixCursor result = new + MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); + for (File file : parent.listFiles()) { + // Adds the file's display name, MIME type, size, and so on. + includeFile(result, null, file); + } + return result; +} +</pre> + +<h4 id="queryDocument">Реализация метода queryDocument</h4> + +<p>Реализация метода +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +должна возвращать объект{@link android.database.Cursor}, указывающий на заданный файл, +используя столбцы, определенные в{@link android.provider.DocumentsContract.Document}. +</p> + +<p>Метод {@link android.provider.DocumentsProvider#queryDocument queryDocument()} +возвращает ту же информацию, которую возвращал +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}, +но для конкретного файла:</p> + + +<pre>@Override +public Cursor queryDocument(String documentId, String[] projection) throws + FileNotFoundException { + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new + MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, documentId, null); + return result; +} +</pre> + +<h4 id="openDocument">Реализация метода openDocument</h4> + +<p>Необходимо реализовать метод {@link android.provider.DocumentsProvider#openDocument +openDocument()}, который возвращает объект {@link android.os.ParcelFileDescriptor}, представляющий +указанный файл. Другие приложения смогут воспользоваться возращенным объектом {@link android.os.ParcelFileDescriptor} +для организации потока данных. Система вызывает этот метод, когда пользователь выбирает файл, +и клиентское приложение запрашивает доступ нему, вызывая +метод {@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()}. +Например:</p> + +<pre>@Override +public ParcelFileDescriptor openDocument(final String documentId, + final String mode, + CancellationSignal signal) throws + FileNotFoundException { + Log.v(TAG, "openDocument, mode: " + mode); + // It's OK to do network operations in this method to download the document, + // as long as you periodically check the CancellationSignal. If you have an + // extremely large file to transfer from the network, a better solution may + // be pipes or sockets (see ParcelFileDescriptor for helper methods). + + final File file = getFileForDocId(documentId); + + final boolean isWrite = (mode.indexOf('w') != -1); + if(isWrite) { + // Attach a close listener if the document is opened in write mode. + try { + Handler handler = new Handler(getContext().getMainLooper()); + return ParcelFileDescriptor.open(file, accessMode, handler, + new ParcelFileDescriptor.OnCloseListener() { + @Override + public void onClose(IOException e) { + + // Update the file with the cloud server. The client is done + // writing. + Log.i(TAG, "A file with id " + + documentId + " has been closed! + Time to " + + "update the server."); + } + + }); + } catch (IOException e) { + throw new FileNotFoundException("Failed to open document with id " + + documentId + " and mode " + mode); + } + } else { + return ParcelFileDescriptor.open(file, accessMode); + } +} +</pre> + +<h3 id="security">Безопасность</h3> + +<p>Предположим, что поставщик документов представляет собой защищенную паролем службу хранения в облаке, +а приложение должно убедиться, что пользователь вошел в систему, прежде чем оно предоставит ему доступ к файлам. +Что должно предпринять приложение, если пользователь не выполнил вход? Решение состоит в том, чтобы +реализация метода {@link android.provider.DocumentsProvider#queryRoots +queryRoots()} не возвращала корневых каталогов. Иными словами, это должен быть пустой корневой курсор:</p> + +<pre> +public Cursor queryRoots(String[] projection) throws FileNotFoundException { +... + // If user is not logged in, return an empty root cursor. This removes our + // provider from the list entirely. + if (!isUserLoggedIn()) { + return result; +} +</pre> + +<p>Следующий шаг состоит в вызове метода {@code getContentResolver().notifyChange()}. +Помните объект {@link android.provider.DocumentsContract}? Воспользуемся им для создания +соответствующего URI. В следующем фрагменте кода система извещается о необходимости опрашивать корневые каталоги +поставщика документов, когда меняется статус входа пользователя в систему. Если пользователь не +выполнил вход, метод {@link android.provider.DocumentsProvider#queryRoots queryRoots()} возвратит +пустой курсор, как показано выше. Это гарантирует, что документы поставщика будут +доступны только пользователям, вошедшим в поставщик.</p> + +<pre>private void onLoginButtonClick() { + loginOrLogout(); + getContentResolver().notifyChange(DocumentsContract + .buildRootsUri(AUTHORITY), null); +} +</pre>
\ No newline at end of file diff --git a/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd new file mode 100644 index 000000000000..3d59ceb292a2 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=Доступ к ресурсам +parent.title=Ресурсы приложения +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Краткое описание</h2> + <ul> + <li>В коде для создания ссылок на ресурсы можно использовать целочисленные переменные из {@code R.java}, такие как +{@code R.drawable.myimage}</li> + <li>Для создания ссылок на ресурсы из самих ресурсов можно использовать особый синтаксис XML, например {@code +@drawable/myimage}</li> + <li>Также для доступа к ресурсам приложения используются методы класса +{@link android.content.res.Resources}</li> + </ul> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.content.res.Resources}</li> + </ol> + + <h2>Содержание документа</h2> + <ol> + <li><a href="#ResourcesFromCode">Доступ к ресурсам из кода</a></li> + <li><a href="#ResourcesFromXml">Доступ к ресурсам из XML</a> + <ol> + <li><a href="#ReferencesToThemeAttributes">Ссылка на атрибуты стиля</a></li> + </ol> + </li> + <li><a href="#PlatformResources">Доступ к ресурсам платформы</a></li> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="providing-resources.html">Предоставление ресурсов</a></li> + <li><a href="available-resources.html">Типы ресурсов</a></li> + </ol> +</div> +</div> + + + + +<p>После того, как вы предоставили ресурс в вашем приложении (см. раздел <a href="providing-resources.html">Предоставление ресурсов</a>), этот ресурс можно применить. Для этого необходимо создать +ссылку на идентификатор ресурса. Для задания всех таких идентификаторов в вашем проекте используется класс {@code R}, который автоматически создается инструментом +{@code aapt}.</p> + +<p>Во время компиляции приложения инструмент {@code aapt} создает класс {@code R}, в котором находятся +идентификаторы для всех ресурсов в каталоге {@code +res/}. Для каждого типа ресурсов предусмотрен подкласс {@code R} (например, +класс {@code R.drawable} для элементов дизайна), а для каждого ресурса указанного типа существует статическая +целочисленная переменная (например, {@code R.drawable.icon}). Эта переменная как раз и служит идентификатором ресурса, которую можно +использовать для его получения.</p> + +<p>Несмотря на то, что в классе {@code R} находятся идентификаторы ресурсов, никогда не обращайтесь к нему +для поиска идентификатора ресурса. Последний состоит из следующих компонентов:</p> +<ul> + <li><em>Тип ресурса</em>: ресурсы объединены по типам, таким как {@code +string}, {@code drawable} и {@code layout}. Дополнительные сведения о различных типах представлены в разделе <a href="available-resources.html">Типы ресурсов</a>. + </li> + <li><em>Имя ресурса</em>, в качестве которого выступает либо имя файла +(без расширения), либо значение атрибута XML {@code android:name} (если +ресурс представляет собой простое значение, например строку).</li> +</ul> + +<p>Существует два способа доступа к ресурсу.</p> +<ul> + <li><strong>Из кода:</strong> с помощью статической целочисленной переменной из подкласса вашего класса {@code R} +, например: + <pre class="classic no-pretty-print">R.string.hello</pre> + <p>{@code string} — это тип ресурса, а {@code hello} — это его имя. Существует множество API-интерфейсов +Android, которые могут получать доступ к ресурсам, идентификаторы которых указаны в этом формате. См. раздел +<a href="#ResourcesFromCode">Доступ к ресурсам из кода</a>.</p> + </li> + <li><strong>Из XML:</strong> с помощью особого синтаксиса XML, который также соответствует +идентификатору ресурса, заданному в классе {@code R}, например: + <pre class="classic no-pretty-print">@string/hello</pre> + <p>{@code string} — это тип ресурса, а {@code hello} — это его имя. Этот синтаксис можно +использовать в любом фрагменте ресурса XML, где ожидается значение, указанное вами в ресурсе. См. раздел <a href="#ResourcesFromXml">Доступ к ресурсам из XML</a></p> + </li> +</ul> + + + +<h2 id="ResourcesFromCode">Доступ к ресурсам из кода </h2> + +<p>Чтобы использовать ресурс в коде, можно передать идентификатор ресурса в виде параметра метода. Например, с +помощью метода {@link android.widget.ImageView#setImageResource(int) setImageResource()} можно указать использование виджетом {@link android.widget.ImageView} ресурса {@code res/drawable/myimage.png}: +</p> +<pre> +ImageView imageView = (ImageView) findViewById(R.id.myimageview); +imageView.setImageResource(<strong>R.drawable.myimage</strong>); +</pre> + +<p>Отдельные ресурсы также можно получать с помощью методов в классе {@link +android.content.res.Resources}, экземпляр которого можно получить с помощью +метода {@link android.content.Context#getResources()}.</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>Доступ к исходным файлам</h2> + +<p>В редких случаях может потребоваться получить доступ к исходным файлам и каталогам. В этом случае просто сохранить +файлы в каталоге {@code res/} будет недостаточно, поскольку обратиться к ресурсу из папки +{@code res/} можно только по его идентификатору. Вместо этого ресурсы можно сохранить в каталоге +{@code assets/}.</p> +<p>Файлам, которые сохранены в каталоге {@code assets/}, <em>не</em> присваиваются идентификаторы +ресурсов, поэтому вам не удастся сослаться на них с помощью класса {@code R} или из ресурсов XML. Вместо этого можно +запросить файлы из каталога {@code assets/}, как в обычной файловой системе, и считать необработанные данные с помощью +{@link android.content.res.AssetManager}.</p> +<p>Однако, если вам требуется всего лишь возможность считать необработанные данные (например, видео- или аудиофайл), + сохраните требуемый файл в каталоге {@code res/raw/} и считайте поток байтов с помощью метода {@link +android.content.res.Resources#openRawResource(int) openRawResource()}.</p> + +</div> +</div> + + +<h3>Синтаксис</h3> + +<p>Ниже представлен синтаксис ссылки на ресурс из кода.</p> + +<pre class="classic no-pretty-print"> +[<em><package_name></em>.]R.<em><resource_type></em>.<em><resource_name></em> +</pre> + +<ul> + <li><em>{@code <package_name>}</em> — это имя пакета, в котором находится ресурс (не +требуется при создании ссылок на ресурсы из вашего собственного пакета).</li> + <li><em>{@code <resource_type>}</em> — это подкласс {@code R} для типа ресурса.</li> + <li><em>{@code <resource_name>}</em> — это либо имя файла +ресурса (без расширения), либо значение атрибута {@code android:name} в элементе XML (для простых +значений).</li> +</ul> +<p>Дополнительные сведения о каждом типе +ресурсов и порядке создания ссылок на них см. в разделе <a href="available-resources.html">Типы ресурсов</a>.</p> + + +<h3>Примеры использования</h3> + +<p>Существует множество методов, которые могут принимать идентификатор ресурса в виде параметра. Для получения ресурсов можно использовать методы, +представленные в классе {@link android.content.res.Resources}. Можно получить экземпляр {@link +android.content.res.Resources} с помощью {@link android.content.Context#getResources +Context.getResources()}.</p> + + +<p>Вот некоторые примеры доступа к ресурсам из кода:</p> + +<pre> +// Load a background for the current screen from a drawable resource +{@link android.app.Activity#getWindow()}.{@link +android.view.Window#setBackgroundDrawableResource(int) +setBackgroundDrawableResource}(<strong>R.drawable.my_background_image</strong>) ; + +// Set the Activity title by getting a string from the Resources object, because +// this method requires a CharSequence rather than a resource ID +{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence) +setTitle}(getResources().{@link android.content.res.Resources#getText(int) +getText}(<strong>R.string.main_title</strong>)); + +// Load a custom layout for the current screen +{@link android.app.Activity#setContentView(int) +setContentView}(<strong>R.layout.main_screen</strong>); + +// Set a slide in animation by getting an Animation from the Resources object +mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation) +setInAnimation}(AnimationUtils.loadAnimation(this, + <strong>R.anim.hyperspace_in</strong>)); + +// Set the text on a TextView object using a resource ID +TextView msgTextView = (TextView) findViewById(<strong>R.id.msg</strong>); +msgTextView.{@link android.widget.TextView#setText(int) +setText}(<strong>R.string.hello_message</strong>); +</pre> + + +<p class="caution"><strong>Внимание!</strong> Никогда не изменяйте файл {@code +R.java} вручную — этот файл создается инструментом {@code aapt} во время компиляции вашего +проекта. Любые внесенные в него изменения перезаписываются после следующей компиляции.</p> + + + +<h2 id="ResourcesFromXml">Доступ к ресурсам из XML</h2> + +<p>Для задания значений для некоторых атрибутов и элементов XML можно использовать +ссылку на существующий ресурс. Это зачастую требуется при создании файлов макета при +указании строк и изображений для виджетов.</p> + +<p>Например, при добавлении в макет элемента {@link android.widget.Button} необходимо использовать +<a href="string-resource.html">строковый ресурс</a> для надписи на кнопке:</p> + +<pre> +<Button + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="<strong>@string/submit</strong>" /> +</pre> + + +<h3>Синтаксис</h3> + +<p>Ниже представлен синтаксис ссылки на ресурс из ресурса XML.</p> + +<pre class="classic no-pretty-print"> +@[<em><package_name></em>:]<em><resource_type></em>/<em><resource_name></em> +</pre> + +<ul> + <li>{@code <package_name>} — это имя пакета, в котором находится ресурс (не +требуется при создании ссылок на ресурсы из одного и того же пакета).</li> + <li>{@code <resource_type>} — это подкласс +{@code R} для типа ресурса.</li> + <li>{@code <resource_name>} — это либо имя файла +ресурса (без расширения), либо значение атрибута {@code android:name} в элементе XML (для простых +значений).</li> +</ul> + +<p>Дополнительные сведения о каждом типе +ресурсов и порядке создания ссылок на них см. в разделе <a href="available-resources.html">Типы ресурсов</a>.</p> + + +<h3>Примеры использования</h3> + +<p>В некоторых случаях ресурс необходимо использовать в качестве значения в элементе XML (например, чтобы применить графическое изображение к +виджету), однако вы также можете использовать ресурс в любом фрагменте XML, где ожидается простое значение. Например, +если имеется следующий файл ресурса, включающий <a href="more-resources.html#Color">цветовой ресурс</a> и <a href="string-resource.html">строковый ресурс</a>:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="opaque_red">#f00</color> + <string name="hello">Hello!</string> +</resources> +</pre> + +<p>Эти ресурсы можно использовать в следующем файле макета для задания цвета текста и +надписи:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<EditText xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:textColor="<strong>@color/opaque_red</strong>" + android:text="<strong>@string/hello</strong>" /> +</pre> + +<p>В этом случае в ссылке на ресурс не нужно указывать имя пакета, поскольку +ресурсы находятся в вашем собственном пакете. Однако для создания +ссылки на системный ресурс вам потребуется указать имя пакета. Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<EditText xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:textColor="<strong>@android:color/secondary_text_dark</strong>" + android:text="@string/hello" /> +</pre> + +<p class="note"><strong>Примечание.</strong> Всегда используйте строковые ресурсы, +поскольку ваше приложение может потребоваться перевести на другие языки. +Сведения о создании альтернативных +ресурсов (например, локализованных строк) представлены в разделе <a href="providing-resources.html#AlternativeResources">Предоставление альтернативных +ресурсов</a>. В разделе <a href="localization.html">Локализация</a> приведено полное руководство по локализации +приложения.</p> + +<p>Вы даже можете использовать ресурсы в XML для создания псевдонимов. Например, можно создать +элемент дизайна, который будет служить псевдонимом для другого элемента дизайна:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/other_drawable" /> +</pre> + +<p>Это может показаться излишним, однако такой подход очень полезен при использовании альтернативных ресурсов. Узнайте подробнее о +<a href="providing-resources.html#AliasResources">создании псевдонимов ресурсов</a>.</p> + + + +<h3 id="ReferencesToThemeAttributes">Ссылка на атрибуты стиля</h3> + +<p>С помощью ресурса атрибута стиля можно создать ссылку на значение +атрибута в текущей примененной теме. Таким образом можно +настраивать внешний вид элементов пользовательского интерфейса, подстраивая их под стандартные варианты элементов оформления +текущей темы, вместо указания жестко заданных значений. Создание ссылки на атрибут стиля +представляет собой своеобразную инструкцию «использовать стиль, заданный этим атрибутом в текущей теме».</p> + +<p>Синтаксис для создания ссылки на атрибут стиля практически идентичен обычному формату +ресурса, только в этом случае вместо символа{@code @} необходимо указать вопросительный знак ({@code ?}), а тип ресурса +вообще необязательно указывать. Например:</p> + +<pre class="classic"> +?[<em><package_name></em>:][<em><resource_type></em>/]<em><resource_name></em> +</pre> + +<p>Ниже представлен пример создания ссылки на атрибут для задания цвета текста в соответствии с +«основным» цветом текста системной темы оформления:</p> + +<pre> +<EditText id="text" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textColor="<strong>?android:textColorSecondary</strong>" + android:text="@string/hello_world" /> +</pre> + +<p>Здесь атрибут {@code android:textColor} служит для задания имени атрибута стиля +в текущей теме. Теперь в Android используется значение, примененное к атрибуту стиля{@code android:textColorSecondary} +в качестве значения для {@code android:textColor} в этом виджете. Поскольку инструменту для работы с системными +ресурсами известно, что в этом контексте ожидается ресурс атрибута, +вам не нужно явно указывать его тип (который должен быть +<code>?android:attr/textColorSecondary</code>) — тип {@code attr} можно исключить.</p> + + + + +<h2 id="PlatformResources">Доступ к ресурсам платформы</h2> + +<p>В Android предусмотрен ряд стандартных ресурсов, например, стилей, тем и макетов. Для +доступа к этим ресурсам укажите в ссылке на ресурс имя пакета +<code>android</code>. Например, в Android имеется ресурс макета, который можно использовать для +элементов списка в виджете {@link android.widget.ListAdapter}:</p> + +<pre> +{@link android.app.ListActivity#setListAdapter(ListAdapter) +setListAdapter}(new {@link +android.widget.ArrayAdapter}<String>(this, <strong>android.R.layout.simple_list_item_1</strong>, myarray)); +</pre> + +<p>В этом примере {@link android.R.layout#simple_list_item_1} представляет собой ресурс макета, определенный +платформой для элементов в виджете {@link android.widget.ListView}. Вы можете использовать его вместо создания +собственных макетов для элементов списка. Дополнительные сведения представлены в руководстве для разработчиков, посвященном +<a href="{@docRoot}guide/topics/ui/layout/listview.html">представлению в виде списка</a>.</p> + diff --git a/docs/html-intl/intl/ru/guide/topics/resources/overview.jd b/docs/html-intl/intl/ru/guide/topics/resources/overview.jd new file mode 100644 index 000000000000..c809c85b784b --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=Обзор ресурсов +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Темы</h2> + <ol> + <li><a href="providing-resources.html">Предоставление ресурсов</a></li> + <li><a href="accessing-resources.html">Доступ к ресурсам</a></li> + <li><a href="runtime-changes.html">Обработка изменений в режиме выполнения</a></li> + <li><a href="localization.html">Локализация</a></li> + </ol> + + <h2>Дополнительные материалы</h2> + <ol> + <li><a href="available-resources.html">Типы ресурсов</a></li> + </ol> +</div> +</div> + + +<p>Необходимо обязательно экспортировать ресурсы, такие как изображения и строки, из кода +приложения, чтобы можно было обрабатывать их независимо. Кроме того, экспорт +ресурсов позволяет предоставлять альтернативные ресурсы для поддержки конфигураций +конкретных устройств, например, различные языки или размеры экранов. Значение этого возрастает по мере того, как +появляется все больше устройств Android с разными конфигурациями. Чтобы обеспечить +совместимость с различными конфигурациями, необходимо организовать ресурсы +в каталоге {@code res/} проекта с использованием различных подкаталогов для группирования ресурсов по типу и +конфигурации.</p> + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram1.png" height="167" alt="" /> +<p class="img-caption"> +<strong>Рисунок 1.</strong> Два разных устройства, каждое из которых использует макет по умолчанию +(приложение не предоставляет альтернативных макетов).</p> +</div> + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram2.png" height="167" alt="" /> +<p class="img-caption"> +<strong>Рисунок 2</strong>. Два разных устройства, каждое из которых использует свой макет, разработанный для +экранов разных размеров.</p> +</div> + +<p>Для ресурсов любого типа можно указать ресурс <em>по умолчанию</em> и несколько +<em>альтернативных</em> ресурсов для приложения:</p> +<ul> + <li>Ресурсы по умолчанию должны использоваться независимо от +конфигурации устройства или в том случае, когда отсутствуют альтернативные ресурсы, соответствующие +текущей конфигурации.</li> + <li>Альтернативные ресурсы предназначены для работы с определенными +конфигурациями. Чтобы указать, что группа ресурсов предназначена для определенной конфигурации, +добавьте соответствующий квалификатор к имени каталога.</li> +</ul> + +<p>Например, несмотря на то, что макет пользовательского интерфейса по умолчанию +сохранен в каталоге {@code res/layout/}, можно указать другой макет для +использования на экране с альбомной ориентацией, сохранив его в каталоге {@code res/layout-land/} +. Android автоматически применяет соответствующие ресурсы, сопоставляя текущую конфигурацию +устройства с именами каталогов ресурсов.</p> + +<p>На рисунке 1 показано, как система применяет одинаковый макет для +двух разных устройств, когда альтернативные ресурсы отсутствуют. На рисунке 2 показано +то же приложение, когда для больших экранов добавлен альтернативный ресурс макета.</p> + +<p>В следующих документах содержится полное руководство по организации ресурсов приложения, +указания альтернативных ресурсов, доступа к ним из приложения и т. д.:</p> + +<dl> + <dt><strong><a href="providing-resources.html">Предоставление ресурсов</a></strong></dt> + <dd>Типы ресурсов, которые можно предоставлять в приложении, место их сохранения и способы создания +альтернативных ресурсов для определенных конфигураций устройств.</dd> + <dt><strong><a href="accessing-resources.html">Доступ к ресурсам</a></strong></dt> + <dd>Способ использования предоставленных ресурсов: путем ссылки на них из кода приложения +или из других ресурсов XML.</dd> + <dt><strong><a href="runtime-changes.html">Обработка изменений в режиме выполнения</a></strong></dt> + <dd>Управление изменениями конфигурации во время выполнения операции.</dd> + <dt><strong><a href="localization.html">Локализация</a></strong></dt> + <dd>Руководство по локализации приложения «снизу вверх» с помощью альтернативных ресурсов. Хотя это лишь +один из примеров использования альтернативных ресурсов, он очень важен для охвата более широкой аудитории +пользователей.</dd> + <dt><strong><a href="available-resources.html">Типы ресурсов</a></strong></dt> + <dd>Ссылка на различные типы ресурсов, которые вы можете предоставлять, с описанием элементов XML, +атрибутов и синтаксиса. Например, эта ссылка показывает, как создать ресурс для меню +, рисунков, анимаций приложения и т. д.</dd> +</dl> + +<!-- +<h2>Raw Assets</h2> + +<p>An alternative to saving files in {@code res/} is to save files in the {@code +assets/} directory. This should only be necessary if you need direct access to original files and +directories by name. Files saved in the {@code assets/} directory will not be given a resource +ID, so you can't reference them through the {@code R} class or from XML resources. Instead, you can +query data in the {@code assets/} directory like an ordinary file system, search through the +directory and +read raw data using {@link android.content.res.AssetManager}. For example, this can be more useful +when dealing with textures for a game. However, if you only need to read raw data from a file +(such as a video or audio file), then you should save files into the {@code res/raw/} directory and +then read a stream of bytes using {@link android.content.res.Resources#openRawResource(int)}. This +is uncommon, but if you need direct access to original files in {@code assets/}, refer to the {@link +android.content.res.AssetManager} documentation.</p> +--> diff --git a/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd new file mode 100644 index 000000000000..be0af952896e --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=Предоставление ресурсов +parent.title=Ресурсы приложения +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Краткое описание</h2> + <ul> + <li>Разные типы ресурсов находятся в разных подкаталогах каталога {@code res/}</li> + <li>Альтернативные ресурсы представляют собой файлы ресурсов для определенных конфигураций</li> + <li>Обязательно включайте ресурсы по умолчанию, чтобы приложение не зависело от конфигураций конкретного +устройства</li> + </ul> + <h2>Содержание документа</h2> + <ol> + <li><a href="#ResourceTypes">Группирование типов ресурсов</a></li> + <li><a href="#AlternativeResources">Предоставление альтернативных ресурсов</a> + <ol> + <li><a href="#QualifierRules">Правила квалификатора имени</a></li> + <li><a href="#AliasResources">Создание псевдонимов ресурсов</a></li> + </ol> + </li> + <li><a href="#Compatibility">Обеспечение оптимальной совместимости устройства с ресурсами</a></li> + <li><a href="#BestMatch">Как Android находит наиболее подходящий ресурс</a></li> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="accessing-resources.html">Доступ к ресурсам</a></li> + <li><a href="available-resources.html">Типы ресурсов</a></li> + <li><a href="{@docRoot}guide/practices/screens_support.html">Поддержка +нескольких экранов</a></li> + </ol> +</div> +</div> + +<p>Обязательно необходимо экспортировать из кода ресурсы приложения, такие как изображения и строки, +для последующей их независимой обработки. Следует также обеспечить альтернативные ресурсы для +определенных конфигураций устройств, группируя их в каталогах ресурсов со специальными именами. В +режиме выполнения Android использует соответствующие ресурсы с учетом текущей конфигурации. Например, +можно предоставлять другой макет пользовательского интерфейса в зависимости от размера экрана или различные +строки в зависимости от настройки языка.</p> + +<p>После выполнения экспорта ресурсов приложения можно обращаться к ним +с помощью идентификаторов ресурсов, которые генерируются в классе {@code R} вашего проекта. Использование +ресурсов в приложении рассмотрено в разделе <a href="accessing-resources.html">Доступ +к ресурсам</a>. В этом документе показано, как группировать ресурсы в проекте Android и +предоставлять альтернативные ресурсы для определенных конфигураций устройств.</p> + + +<h2 id="ResourceTypes">Группирование типов ресурсов</h2> + +<p>Следует поместить ресурсы каждого типа в определенный подкаталог каталога +{@code res/} вашего проекта. В качестве примера приведена иерархия файлов для простого проекта:</p> + +<pre class="classic no-pretty-print"> +MyProject/ + src/ <span style="color:black"> + MyActivity.java </span> + res/ + drawable/ <span style="color:black"> + graphic.png </span> + layout/ <span style="color:black"> + main.xml + info.xml</span> + mipmap/ <span style="color:black"> + icon.png </span> + values/ <span style="color:black"> + strings.xml </span> +</pre> + +<p>Как видно в этом примере, каталог {@code res/} содержит все ресурсы (в +подкаталогах): ресурс-изображение, два ресурса-макета, каталоги {@code mipmap/} для значков +запуска и файл строк. Имена каталогов +ресурсов очень важны и описаны в таблице 1.</p> + +<p class="note"><strong>Примечание.</strong> Подробные сведения об использовании папок множественного отображения см. в разделе +<a href="{@docRoot}tools/projects/index.html#mipmap">Обзор управления проектами</a>.</p> + +<p class="table-caption" id="table1"><strong>Таблица 1.</strong> Каталоги ресурсов, +поддерживаемые в каталоге {@code res/} проекта.</p> + +<table> + <tr> + <th scope="col">Каталог</th> + <th scope="col">Тип ресурсов</th> + </tr> + + <tr> + <td><code>animator/</code></td> + <td>Файлы XML, которые определяют <a href="{@docRoot}guide/topics/graphics/prop-animation.html">анимации +свойств</a>.</td> + </tr> + + <tr> + <td><code>anim/</code></td> + <td>Файлы XML, которые определяют <a href="{@docRoot}guide/topics/graphics/view-animation.html#tween-animation">анимации +преобразований</a>. (Анимации свойств также можно сохранять в этом каталоге, но +для анимаций свойств предпочтительнее использовать каталог {@code animator/}, чтобы различать эти два +типа).</td> + </tr> + + <tr> + <td><code>color/</code></td> + <td>Файлы XML, которые определяют список состояний цветов. См. раздел <a href="color-list-resource.html">Ресурс списка +состояний цветов</a></td> + </tr> + + <tr> + <td><code>drawable/</code></td> + + <td><p>Файлы растровых изображений ({@code .png}, {@code .9.png}, {@code .jpg}, {@code .gif}) или файлы XML, которые +составляют следующие подтипы графических ресурсов:</p> + <ul> + <li>Файлы растровых изображений</li> + <li>Файлы из девяти фрагментов (растровые изображения с возможностью изменения размера)</li> + <li>Списки состояний</li> + <li>Формы</li> + <li>Графические анимации</li> + <li>Другие графические элементы</li> + </ul> + <p>См. раздел <a href="drawable-resource.html">Графические ресурсы</a>.</p> + </td> + </tr> + + <tr> + <td><code>mipmap/</code></td> + <td>Графические файлы для значков запуска с различной плотностью. Подробные сведения об управлении +значками запуска с помощью папок {@code mipmap/} см. в разделе + <a href="{@docRoot}tools/project/index.html#mipmap">Обзор управления проектами</a>.</td> + </tr> + + <tr> + <td><code>layout/</code></td> + <td>Файлы XML, которые определяют макет пользовательского интерфейса. + См. раздел <a href="layout-resource.html">Ресурсы макетов</a>.</td> + </tr> + + <tr> + <td><code>menu/</code></td> + <td>Файлы XML, которые определяют меню приложения, такие как меню параметров, контекстные меню или вложенные +меню. См. раздел <a href="menu-resource.html">Ресурсы меню</a>.</td> + </tr> + + <tr> + <td><code>raw/</code></td> + <td><p>Произвольные файлы для сохранения в исходной форме. Чтобы открыть эти ресурсы с помощью +{@link java.io.InputStream}, вызовите {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()} с идентификатором ресурса, который имеет вид {@code R.raw.<em>filename</em>}.</p> + <p>Однако, если требуется получить доступ к исходным именам файлов и иерархии файлов, можно +сохранять некоторые ресурсы в каталоге {@code +assets/} (вместо каталога {@code res/raw/}). Файлы в каталоге {@code assets/} не получают +идентификатора ресурса, поэтому их чтение возможно только с помощью {@link android.content.res.AssetManager}.</p></td> + </tr> + + <tr> + <td><code>values/</code></td> + <td><p>Файлы XML, которые содержат простые значения, такие как строки, целые числа и цвета.</p> + <p>Тогда как XML-файлы ресурсов в других подкаталогах каталога {@code res/} определяют отдельные ресурсы +на базе имени файла XML, файлы в каталоге {@code values/} описывают несколько ресурсов. +Для файла в этом каталоге каждый дочерний элемент элемента {@code <resources>} определяет один +ресурс. Например, элемент {@code <string>} создает ресурс +{@code R.string}, а элемент {@code <color>} создает ресурс {@code R.color} +.</p> + <p>Так как каждый ресурс определяется с помощью своего собственного элемента XML, можно назначать имя файла +по своему усмотрению и помещать ресурсы разных типов в один файл. Тем не мене, может +появиться необходимость поместить ресурсы отдельных типов в разные файлы. Например, ниже приведены соглашения для имен файлов +ресурсов, которые можно создать в этом каталоге:</p> + <ul> + <li>arrays.xml для ресурсов-массивов (<a href="more-resources.html#TypedArray">массивы с указанием типа</a>)</li> + <li>colors.xml для <a href="more-resources.html#Color">значений цветов</a></li> + <li>dimens.xml для <a href="more-resources.html#Dimension">значений единиц измерений</a></li> + <li>strings.xml для <a href="string-resource.html">строковых +значений</a></li> + <li>styles.xml для <a href="style-resource.html">стилей</a>.</li> + </ul> + <p>См. разделы <a href="string-resource.html">Строковые ресурсы</a>, + <a href="style-resource.html">Ресурсы стиля</a> и + <a href="more-resources.html">Дополнительные типы ресурсов</a>.</p> + </td> + </tr> + + <tr> + <td><code>xml/</code></td> + <td>Произвольные XML-файлы, которые можно читать в режиме выполнения вызовом метода {@link +android.content.res.Resources#getXml(int) Resources.getXML()}. Здесь должны сохраняться различные файлы конфигурации XML, +например, <a href="{@docRoot}guide/topics/search/searchable-config.html">конфигурация с возможностью поиска</a>. +<!-- or preferences configuration. --></td> + </tr> +</table> + +<p class="caution"><strong>Предупреждение!</strong> Не сохраняйте файлы ресурсов непосредственно в +каталоге {@code res/}, так как это вызывает ошибку компилятора.</p> + +<p>Дополнительную информацию об определенных типах ресурсов см. в документации <a href="available-resources.html">Типы ресурсов</a>.</p> + +<p>Ресурсы, сохраненные в подкаталогах, которые описаны в таблице 1, являются ресурсами +«по умолчанию». Таким образом, эти ресурсы определяют дизайн и содержимое приложения по умолчанию. +Однако различные типы устройств Android могут вызывать различные типы ресурсов. +Например, если устройство оснащено экраном больше нормального, следует предоставить +другие ресурсы макета, которые будут использовать преимущества дополнительного места на экране. Или, если устройство +содержит различные языковые настройки, следует предоставить другие строковые ресурсы, содержащие перевод +текста пользовательского интерфейса. Чтобы предоставить разные ресурсы для разных конфигураций устройств, +необходимо предоставить альтернативные ресурсы в дополнение к ресурсам +по умолчанию.</p> + + +<h2 id="AlternativeResources">Предоставление альтернативных ресурсов</h2> + + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram2.png" height="167" alt="" /> +<p class="img-caption"> +<strong>Рисунок 1.</strong> Два разных устройства, которые используют разные ресурсы макета.</p> +</div> + +<p>Почти каждое приложение должно предоставлять альтернативные ресурсы, чтобы поддерживать определенные конфигурации +устройств. Например, необходимо включить альтернативные графические ресурсы для экранов с +разной плотностью растра и альтернативные ресурсы для разных языков. В режиме выполнения Android +определяет конфигурацию устройства и загружает соответствующие +ресурсы для приложения.</p> + +<p>Чтобы указать альтернативы для конкретных конфигураций набора ресурсов, выполните следующие действия:</p> +<ol> + <li>Создайте новый каталог в каталоге {@code res/} с именем следующего вида {@code +<em><имя_ресурса></em>-<em><квалификатор_конфигурации></em>}. + <ul> + <li><em>{@code <resources_name>}</em> – имя каталога соответствующих ресурсов +по умолчанию (определено в таблице 1).</li> + <li><em>{@code <qualifier>}</em> – имя, которое указывает определенную конфигурацию, +для которой должны использоваться эти ресурсы (определено в таблице 2).</li> + </ul> + <p>Можно добавлять несколько квалификаторов <em>{@code <qualifier>}</em>. Разделяйте их +знаком дефиса.</p> + <p class="caution"><strong>Предупреждение!</strong> При добавлении нескольких квалификаторов необходимо +располагать их в том же порядке, в котором они перечислены в таблице 2. Если порядок квалификаторов нарушен, + ресурсы игнорируются.</p> + </li> + <li>Сохраните соответствующие альтернативные ресурсы в этом новом каталоге. Файлы ресурсов должны +иметь имена, точно совпадающие с именами файлов ресурсов по умолчанию.</li> +</ol> + +<p>В качестве примера здесь приведено несколько ресурсов по умолчанию и альтернативных ресурсов:</p> + +<pre class="classic no-pretty-print"> +res/ + drawable/ <span style="color:black"> + icon.png + background.png </span> + drawable-hdpi/ <span style="color:black"> + icon.png + background.png </span> +</pre> + +<p>Квалификатор {@code hdpi} указывает, что ресурсы в этом каталоге предназначены для устройств, оснащенных экраном +высокой плотности. Изображения в каждом из этих каталогов для графических объектов имеют размер для определенной плотности +экрана, но имена файлов полностью +совпадают. Таким образом, идентификатор ресурса, который указывает на изображение {@code icon.png} или {@code +background.png}, всегда одинаков, но Android выбирает +версию каждого ресурса, которая оптимально соответствует текущему устройству, сравнивая информацию о конфигурации устройства +с квалификаторами в имени каталога ресурсов.</p> + +<p>Android поддерживает несколько квалификаторов конфигурации, позволяя +добавлять несколько квалификаторов к одному имени каталога, разделяя квалификаторы дефисом. В таблице 2 +перечислены допустимые квалификаторы конфигурации в порядке приоритета — если используется несколько +квалификаторов для каталога ресурсов, необходимо добавлять их к имени каталога в том порядке, в котором +они перечислены в таблице.</p> + + +<p class="table-caption" id="table2"><strong>Таблица 2.</strong> Имена квалификаторов +конфигурации.</p> +<table> + <tr> + <th>Конфигурация</th> + <th>Значения квалификатора</th> + <th>Описание</th> + </tr> + <tr id="MccQualifier"> + <td>MCC и MNC</td> + <td>Примеры:<br/> + <code>mcc310</code><br/> + <code><nobr>mcc310-mnc004</nobr></code><br/> + <code>mcc208-mnc00</code><br/> + и т. д. + </td> + <td> + <p>Код страны для мобильной связи (MCC), за которым может следовать код сети мобильной связи (MNC) + из SIM-карты устройства. Например, <code>mcc310</code> – код США для любого поставщика услуг, + <code>mcc310-mnc004</code> – код США для Verizon и <code>mcc208-mnc00</code> – код Франции + для Orange.</p> + <p>Если в устройстве используется радиосвязь (телефон GSM), значения MCC и MNC добываются + из SIM-карты.</p> + <p>Можно также использовать только код MCC (например, для включения в приложении разрешенных в стране +ресурсов). Если требуется указать только язык, используйте квалификатор +<em>язык и регион</em> (обсуждается ниже). Если принято решение использовать квалификатор MCC и +MNC, следует делать это с осторожностью и проверить корректность его работы.</p> + <p>См. также поля конфигурации {@link +android.content.res.Configuration#mcc} и {@link +android.content.res.Configuration#mnc}, которые указывают текущий код страны при мобильной связи +и код сети мобильной связи соответственно.</p> + </td> + </tr> + <tr id="LocaleQualifier"> + <td>Язык и регион</td> + <td>Примеры:<br/> + <code>en</code><br/> + <code>fr</code><br/> + <code>en-rUS</code><br/> + <code>fr-rFR</code><br/> + <code>fr-rCA</code><br/> + и т. д. + </td> + <td><p>Язык задается двухбуквенным кодом языка <a href="http://www.loc.gov/standards/iso639-2/php/code_list.php">ISO + 639-1</a>, к которому можно добавить двухбуквенный код региона + <a href="http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html">ISO + 3166-1-alpha-2</a> (которому предшествует строчная буква "{@code r}"). + </p><p> + Коды <em>не</em> зависят от регистра; префикс {@code r} служит для +обозначения кода региона. + Нельзя указывать только код региона.</p> + <p>Он может измениться за время работы +приложения, если пользователь изменяет свой язык в системных настройках. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится информация +о воздействии таких изменений на приложение во время выполнения.</p> + <p>В разделе <a href="localization.html">Локализация</a> приведено полное руководство по локализации +приложения для других языков.</p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#locale}, которое +указывает текущий язык.</p> + </td> + </tr> + <tr id="LayoutDirectionQualifier"> + <td>Направление макета</td> + <td><code>ldrtl</code><br/> + <code>ldltr</code><br/> + </td> + <td><p>Направление макета для приложения. Квалификатор {@code ldrtl} означает «направление макета справа налево». +Квалификатор {@code ldltr} означает «направление макета слева направо» и используется по умолчанию. + </p> + <p>Эти квалификаторы можно применять к любым ресурсам, таким как макеты, графические элементы или значения. + </p> + <p>Например, если требуется предоставить специальный макет для арабского языка и +общий макет для других языков, использующих написание «справа налево» (таких как фарси или иврит), используйте следующий код: + </p> +<pre class="classic no-pretty-print"> +res/ + layout/ <span style="color:black"> + main.xml </span>(Default layout) + layout-ar/ <span style="color:black"> + main.xml </span>(Specific layout for Arabic) + layout-ldrtl/ <span style="color:black"> + main.xml </span>(Any "right-to-left" language, except + for Arabic, because the "ar" language qualifier + has a higher precedence.) +</pre> + <p class="note"><strong>Примечание.</strong> Чтобы включить в приложение функцию макета «справа налево», +необходимо установить для параметра <a href="{@docRoot}guide/topics/manifest/application-element.html#supportsrtl">{@code + supportsRtl}</a> значение {@code "true"} и для параметра <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> значение 17 или больше.</p> + <p><em>Добавлено в API уровня 17.</em></p> + </td> + </tr> + <tr id="SmallestScreenWidthQualifier"> + <td>smallestWidth</td> + <td><code>sw<N>dp</code><br/><br/> + Примеры:<br/> + <code>sw320dp</code><br/> + <code>sw600dp</code><br/> + <code>sw720dp</code><br/> + и т. д. + </td> + <td> + <p>Основной размер экрана, указывающий минимальный размер доступной +области экрана. Точнее говоря, минимальная ширина устройства – это наименьший из двух размеров экрана: +высоты и ширины (можно также называть ее «меньшей стороной» экрана). Этот квалификатор +позволяет гарантировать, что независимо от текущей ориентации экрана +приложение имеет доступную ширину пользовательского интерфейса не менее {@code <N>} пикселов.</p> + <p>Например, если для макета требуется, чтобы минимальный размер области экрана всегда был +не менее 600 пикселов, можно использовать этот квалификатор для создания ресурсов этого макета, {@code +res/layout-sw600dp/}. Система будет использовать эти ресурсы только в том случае, если минимальный размер +доступной области экрана составляет не менее 600 пикселов, независимо от воспринимаемой пользователем +высоты или ширины. Значение минимальной ширины является постоянной характеристикой размера экрана для устройства; <strong>минимальная +ширина устройства не изменяется при изменении ориентации экрана</strong>.</p> + <p>Минимальная ширина устройства учитывает оформление экрана и пользовательский интерфейс системы. Например, +если не экране присутствуют постоянные элементы пользовательского интерфейса, которые занимают пространство вдоль +оси минимальной ширины, система объявляет, что минимальная ширина меньше фактического +размера экрана, так как эти пикселы экрана недоступны для пользовательского интерфейса приложения. Следовательно используемое значение +должно быть фактическим наименьшим размером, <em>который необходим для вашего макета</em> (обычно это значение является +«минимальной шириной», которую поддерживает ваш макет, независимо от текущей ориентации экрана).</p> + <p>Здесь приведены некоторые значения, которые можно использовать для экранов обычных размеров:</p> + <ul> + <li>320 для устройств с конфигурациями экрана: + <ul> + <li>240x320 ldpi (смартфон QVGA)</li> + <li>320x480 mdpi (смартфон)</li> + <li>480x800 hdpi (смартфон высокой плотности)</li> + </ul> + </li> + <li>480 для таких экранов, как 480x800 mdpi (планшет/смартфон).</li> + <li>600 для таких экранов, как 600x1024 mdpi (планшет с диагональю 7").</li> + <li>720 для таких экранов, как 720x1280 mdpi (планшет с диагональю 10").</li> + </ul> + <p>Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +квалификатора «минимальная ширина», система использует квалификатор, ближайший +к минимальной ширине устройства, но не превышающий ее. </p> + <p><em>Добавлено в API уровня 13.</em></p> + <p>См. также атрибут <a href="{@docRoot}guide/topics/manifest/supports-screens-element.html#requiresSmallest">{@code +android:requiresSmallestWidthDp}</a>, который объявляет минимальную ширину, совместимую +с вашим приложением, и поле конфигурации {@link +android.content.res.Configuration#smallestScreenWidthDp}, которое содержит значение +минимальной ширины устройства.</p> + <p>Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика <a href="{@docRoot}guide/practices/screens_support.html">Поддержка +нескольких экранов</a>.</p> + </td> + </tr> + <tr id="ScreenWidthQualifier"> + <td>Доступная ширина</td> + <td><code>w<N>dp</code><br/><br/> + Примеры:<br/> + <code>w720dp</code><br/> + <code>w1024dp</code><br/> + и т. д. + </td> + <td> + <p>Указывает минимальную доступную ширину экрана в единицах {@code dp}, для которой должен использоваться ресурс, +заданный значением <code><N></code>. Это +значение конфигурации будет изменяться в соответствии с текущей фактической шириной +при изменении альбомной/книжной ориентации.</p> + <p>Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +этой конфигурации, система использует ширину, ближайшую к текущей +ширине экрана устройства, но не превышающую ее. Это значение +учитывает оформление экрана, поэтому, если устройство содержит +постоянные элементы пользовательского интерфейса вдоль левого или правого края дисплея, оно +использует значение ширины, которое меньше реального размера экрана: эти элементы +пользовательского интерфейса учитываются и уменьшают пространство, доступное для приложения.</p> + <p><em>Добавлено в API уровня 13.</em></p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#screenWidthDp} +, которое содержит текущую ширину экрана.</p> + <p>Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика <a href="{@docRoot}guide/practices/screens_support.html">Поддержка +нескольких экранов</a>.</p> + </td> + </tr> + <tr id="ScreenHeightQualifier"> + <td>Доступная высота</td> + <td><code>h<N>dp</code><br/><br/> + Примеры:<br/> + <code>h720dp</code><br/> + <code>h1024dp</code><br/> + и т. д. + </td> + <td> + <p>Указывает минимальную доступную высоту экрана в пикселах, для которой должен использоваться ресурс, +заданный значением <code><N></code>. Это +значение конфигурации будет изменяться в соответствии с текущей фактической высотой +при изменении альбомной/книжной ориентации.</p> + <p>Когда приложение предоставляет несколько каталогов ресурсов с разными значениями +этой конфигурации, система использует высоту, ближайшую к текущей +высоте экрана устройства, но не превышающую ее. Это значение +учитывает оформление экрана, поэтому, если устройство содержит +постоянные элементы пользовательского интерфейса вдоль верхнего или нижнего края дисплея, оно +использует значение высоты, которое меньше реального размера экрана: эти элементы +пользовательского интерфейса учитываются и уменьшают пространство, доступное для приложения. Элементы оформления +экрана, которые не являются постоянными (например, строка состояния телефона может быть +скрыта в полноэкранном режиме), здесь <em>не</em> учитываются; также не учитываются такие элементы +оформления окна, как строка заголовка или строка действий, поэтому приложения должны быть готовы к работе с меньшим +пространством, чем указано. + <p><em>Добавлено в API уровня 13.</em></p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#screenHeightDp} +, которое содержит текущую ширину экрана.</p> + <p>Дополнительную информацию о разработке для различных экранов и использовании этого +квалификатора см. в руководстве разработчика <a href="{@docRoot}guide/practices/screens_support.html">Поддержка +нескольких экранов</a>.</p> + </td> + </tr> + <tr id="ScreenSizeQualifier"> + <td>Размер экрана</td> + <td> + <code>small</code><br/> + <code>normal</code><br/> + <code>large</code><br/> + <code>xlarge</code> + </td> + <td> + <ul class="nolist"> + <li>{@code small}: Экраны, подобные по размеру +экрану QVGA низкой плотности. Минимальный размер макета для маленького экрана +составляет приблизительно 320x426 пикселов. Примерами являются экраны QVGA низкой плотности и VGA высокой +плотности.</li> + <li>{@code normal}: Экраны, подобные по размеру +экрану HVGA средней плотности. Минимальный +размер макета для нормального экрана составляет приблизительно 320x470 пикселов. Примерами таких экранов +являются экраны WQVGA низкой плотности, HVGA средней плотности, WVGA +высокой плотности.</li> + <li>{@code large}: Экраны, подобные по размеру +экрану VGA средней плотности. + Минимальный размер макета для большого экрана составляет приблизительно 480x640 пикселов. + Примерами являются экраны VGA и WVGA средней плотности.</li> + <li>{@code xlarge}: Экраны значительно крупнее обычного +экрана HVGA средней плотности. Минимальный размер макета для очень большого экрана составляет +приблизительно 720x960 пикселов. В большинстве случаев устройства с очень большими +экранами слишком велики для карманного использования и, скорее всего, +относятся к планшетам. <em>Добавлено в API уровня 9.</em></li> + </ul> + <p class="note"><strong>Примечание.</strong> Использование квалификатора размера не подразумевает, что +ресурсы предназначены <em>только</em> для экранов этого размера. Если не предусмотрены +альтернативные ресурсы с квалификаторами, лучше подходящими к текущей конфигурации устройства, система может использовать +любые <a href="#BestMatch">наиболее подходящие</a> ресурсы.</p> + <p class="caution"><strong>Предупреждение!</strong> Если все ресурсы используют квалификатор размера, +который <em>превосходит</em> размер текущего экрана, система <strong>не</strong> будет использовать эти ресурсы, и приложение +аварийно завершится во время выполнения (например, если все ресурсы макета отмечены квалификатором {@code +xlarge}, но устройство оснащено экраном нормального размера).</p> + <p><em>Добавлено в API уровня 4.</em></p> + + <p>Дополнительную информацию см. в разделе <a href="{@docRoot}guide/practices/screens_support.html">Поддержка нескольких +экранов</a>.</p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#screenLayout}, которое +указывает тип размера экрана: маленький, нормальный +или большой.</p> + </td> + </tr> + <tr id="ScreenAspectQualifier"> + <td>Формат экрана</td> + <td> + <code>long</code><br/> + <code>notlong</code> + </td> + <td> + <ul class="nolist"> + <li>{@code long}: Длинные экраны, такие как WQVGA, WVGA, FWVGA</li> + <li>{@code notlong}: Недлинные экраны, такие как QVGA, HVGA и VGA</li> + </ul> + <p><em>Добавлено в API уровня 4.</em></p> + <p>Формат основан исключительно на соотношении сторон экрана («длинный» экран шире). Это +не связано с ориентацией экрана.</p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#screenLayout}, которое +указывает, является ли экран длинным.</p> + </td> + </tr> + <tr id="OrientationQualifier"> + <td>Ориентация экрана</td> + <td> + <code>port</code><br/> + <code>land</code> <!-- <br/> + <code>square</code> --> + </td> + <td> + <ul class="nolist"> + <li>{@code port}: Устройство в портретной (вертикальной) ориентации</li> + <li>{@code land}: Устройство в книжной (горизонтальной) ориентации</li> + <!-- Square mode is currently not used. --> + </ul> + <p>Ориентация может измениться за время работы приложения, если +пользователь поворачивает экран. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится информация +о воздействии таких изменений на приложение во время выполнения.</p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#orientation}, которое +указывает текущую ориентацию устройства.</p> + </td> + </tr> + <tr id="UiModeQualifier"> + <td>Режим пользовательского интерфейса</td> + <td> + <code>car</code><br/> + <code>desk</code><br/> + <code>television</code><br/> + <code>appliance</code> + <code>watch</code> + </td> + <td> + <ul class="nolist"> + <li>{@code car}: Устройство подсоединено к автомобильной док-станции</li> + <li>{@code desk}: Устройство подсоединено к настольной док-станции</li> + <li>{@code television}: Устройство подсоединено к телевизору, обеспечивая +взаимодействие с расстояния «три метра», когда пользовательский интерфейс находится на большом экране, +находящемся вдалеке от пользователя, ориентированное на управление с помощью навигационной клавиши или другого +устройства без указателя</li> + <li>{@code appliance}: Устройство служит в качестве прибора без +дисплея</li> + <li>{@code watch}: Устройство с дисплеем для ношения на запястье</li> + </ul> + <p><em>Добавлено в API уровня 8, телевизор добавлен в API 13, часы добавлены в API 20.</em></p> + <p>Информацию о том, как приложение может реагировать на установку устройства в +док-станцию или извлечение из нее, прочитайте документ <a href="{@docRoot}training/monitoring-device-state/docking-monitoring.html">Определение +и мониторинг типа и состояния подключения к док-станции</a>.</p> + <p>Подключение может измениться за время работы приложения, если пользователь помещает устройство +в док-станцию. Некоторые из этих режимов можно включить или отключить с помощью {@link +android.app.UiModeManager}. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится +информация о воздействии таких изменений на приложение во время выполнения.</p> + </td> + </tr> + <tr id="NightQualifier"> + <td>Ночной режим</td> + <td> + <code>night</code><br/> + <code>notnight</code> + </td> + <td> + <ul class="nolist"> + <li>{@code night}: Ночное время</li> + <li>{@code notnight}: Дневное время</li> + </ul> + <p><em>Добавлено в API уровня 8.</em></p> + <p>Этот режим может измениться за время работы, если ночной режим оставлен в +автоматическом режиме (по умолчанию), в котором режим изменяется в зависимости от времени суток. Этот режим можно включить +или отключить с помощью {@link android.app.UiModeManager}. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится информация о воздействии таких +изменений на приложение во время выполнения.</p> + </td> + </tr> + <tr id="DensityQualifier"> + <td>Плотность пикселов на экране (dpi)</td> + <td> + <code>ldpi</code><br/> + <code>mdpi</code><br/> + <code>hdpi</code><br/> + <code>xhdpi</code><br/> + <code>xxhdpi</code><br/> + <code>xxxhdpi</code><br/> + <code>nodpi</code><br/> + <code>tvdpi</code> + </td> + <td> + <ul class="nolist"> + <li>{@code ldpi}: Экраны низкой плотности; приблизительно 120 dpi.</li> + <li>{@code mdpi}: Экраны средней плотности (обычные HVGA); приблизительно +160 dpi.</li> + <li>{@code hdpi}: Экраны высокой плотности; приблизительно 240 dpi.</li> + <li>{@code xhdpi}: Экраны очень высокой плотности; приблизительно 320 dpi. <em>Добавлено в API +уровня 8.</em></li> + <li>{@code xxhdpi}: Экраны сверхвысокой плотности; приблизительно 480 dpi. <em>Добавлено в API +уровня 16.</em></li> + <li>{@code xxxhdpi}: Использование исключительно высокой плотности (только значок запуска, см. +<a href="{@docRoot}guide/practices/screens_support.html#xxxhdpi-note">примечание</a> +в документе <em>Поддержка нескольких экранов</em>); приблизительно 640 dpi. <em>Добавлено в API +уровня 18.</em></li> + <li>{@code nodpi}: Этот режим можно использовать для растровых графических ресурсов, которые не требуется масштабировать +в соответствии с плотностью устройства.</li> + <li>{@code tvdpi}: Экраны промежуточной плотности между mdpi и hdpi; приблизительно 213 dpi. Этот режим не считается +«основной» группой плотности. Он главным образом предназначен для телевизоров, и большинство +приложений в нем не нуждается — при условии, что ресурсов mdpi и hdpi достаточно для большинства приложений, и +система будет масштабировать их при необходимости. Этот квалификатор введен в API уровня 13.</li> + </ul> + <p>Шесть основных уровней плотности соотносятся как 3:4:6:8:12:16 (если игнорировать +плотность tvdpi). Так, растровое изображение 9x9 в ldpi представляется как 12x12 в mdpi, 18x18 в hdpi, 24x24 в xhdpi и т. д. +</p> + <p>Если графические ресурсы выглядят недостаточно хорошо на телевизоре или +других определенных устройствах, и хочется попробовать ресурсы tvdpi, используйте масштабный коэффициент 1,33*mdpi. Например, + изображение 100 x 100 пикселов для экранов mdpi должно иметь размер 133 x 133 пиксела для tvdpi.</p> + <p class="note"><strong>Примечание.</strong> Использование квалификатора плотности не подразумевает, что +ресурсы предназначены <em>только</em> для экранов этой плотности. Если не предусмотрены +альтернативные ресурсы с квалификаторами, лучше подходящими к текущей конфигурации устройства, система может использовать +любые <a href="#BestMatch">наиболее подходящие</a> ресурсы.</p> + <p>Дополнительную информацию о том, как обрабатывать +различные плотности экранов, и как Android может масштабировать растровые изображения в соответствии текущей плотностью, см. в разделе <a href="{@docRoot}guide/practices/screens_support.html">Поддержка нескольких +экранов</a>.</p> + </td> + </tr> + <tr id="TouchscreenQualifier"> + <td>Тип сенсорного экрана</td> + <td> + <code>notouch</code><br/> + <code>finger</code> + </td> + <td> + <ul class="nolist"> + <li>{@code notouch}: Устройство не оснащено сенсорным экраном.</li> + <li>{@code finger}: Устройство оснащено сенсорным экраном, предназначенным +для ввода с помощью пальцев пользователя.</li> + </ul> + <p>См. также поле конфигурации {@link android.content.res.Configuration#touchscreen}, которое +указывает тип сенсорного экрана устройства.</p> + </td> + </tr> + <tr id="KeyboardAvailQualifier"> + <td>Доступность клавиатуры</td> + <td> + <code>keysexposed</code><br/> + <code>keyshidden</code><br/> + <code>keyssoft</code> + </td> + <td> + <ul class="nolist"> + <li>{@code keysexposed}: В устройстве доступна клавиатура. Если в устройстве включена +экранная клавиатура (что весьма вероятно), она может использоваться даже в случае, когда аппаратная клавиатура +<em></em> недоступна пользователю, даже если устройство не имеет аппаратной клавиатуры. Если экранная +клавиатура отсутствует или отключена, это квалификатор используется только в том случае, когда доступна +аппаратная клавиатура.</li> + <li>{@code keyshidden}: Устройство имеет аппаратную клавиатуру, но она +скрыта, <em>и </em> в устройстве <em>не</em> включена экранная клавиатура.</li> + <li>{@code keyssoft}: Экранная клавиатура в устройстве включена независимо от того, +видна она или нет.</li> + </ul> + <p>Если предоставляются ресурсы <code>keysexposed</code>, но не предоставляются ресурсы <code>keyssoft</code>, +система использует ресурсы <code>keysexposed</code> независимо от видимости +клавиатуры, поскольку в системе включена экранная клавиатура.</p> + <p>Это состояние может измениться за время работы приложения, если +пользователь открывает аппаратную клавиатуру. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится информация о воздействии таких +изменений на приложение во время выполнения.</p> + <p>См. также поля конфигурации {@link +android.content.res.Configuration#hardKeyboardHidden} и {@link +android.content.res.Configuration#keyboardHidden}, которые указывают видимость аппаратной +клавиатуры и видимость клавиатуры любого типа (включая экранную), соответственно.</p> + </td> + </tr> + <tr id="ImeQualifier"> + <td>Основной способ ввода текста</td> + <td> + <code>nokeys</code><br/> + <code>qwerty</code><br/> + <code>12key</code> + </td> + <td> + <ul class="nolist"> + <li>{@code nokeys}: В устройстве отсутствуют аппаратные клавиши для ввода текста.</li> + <li>{@code qwerty}: Устройство оснащено аппаратной клавиатурой с раскладкой qwerty, независимо от того, видна она +пользователю +или нет.</li> + <li>{@code 12key}: Устройство оснащено аппаратной клавиатурой с 12 клавишами, независимо от того, видна она пользователю +или нет.</li> + </ul> + <p>См. также поле конфигурации {@link android.content.res.Configuration#keyboard}, которое +указывает основной доступный способ ввода текста.</p> + </td> + </tr> + <tr id="NavAvailQualifier"> + <td>Доступность клавиш перемещения</td> + <td> + <code>navexposed</code><br/> + <code>navhidden</code> + </td> + <td> + <ul class="nolist"> + <li>{@code navexposed}: Клавиши перемещения доступны пользователю.</li> + <li>{@code navhidden}: Клавиши перемещения недоступны (например, находятся за +закрытой крышкой).</li> + </ul> + <p>Это состояние может измениться за время работы приложения, если +пользователь открывает клавиши перемещения. В разделе <a href="runtime-changes.html">Обработка изменений в режиме выполнения</a> содержится +информация о воздействии таких изменений на приложение во время выполнения.</p> + <p>См. также поле конфигурации {@link android.content.res.Configuration#navigationHidden}, которое +указывает, скрыты ли клавиши перемещения.</p> + </td> + </tr> + <tr id="NavigationQualifier"> + <td>Основной несенсорный способ перемещения курсора</td> + <td> + <code>nonav</code><br/> + <code>dpad</code><br/> + <code>trackball</code><br/> + <code>wheel</code> + </td> + <td> + <ul class="nolist"> + <li>{@code nonav}: Устройство не оснащено другими средствами перемещения курсора, кроме +сенсорного экрана.</li> + <li>{@code dpad}: Устройство оснащено навигационной клавишей.</li> + <li>{@code trackball}: Устройство оснащено трекболом.</li> + <li>{@code wheel}: Устройство оснащено навигационным колесиком (редко).</li> + </ul> + <p>См. также поле конфигурации {@link android.content.res.Configuration#navigation}, которое +указывает способ перемещения курсора.</p> + </td> + </tr> +<!-- DEPRECATED + <tr> + <td>Screen dimensions</td> + <td>Examples:<br/> + <code>320x240</code><br/> + <code>640x480</code><br/> + etc. + </td> + <td> + <p>The larger dimension must be specified first. <strong>This configuration is deprecated +and should not be used</strong>. Instead use "screen size," "wider/taller screens," and "screen +orientation" described above.</p> + </td> + </tr> +--> + <tr id="VersionQualifier"> + <td>Версия платформы (уровень API)</td> + <td>Примеры:<br/> + <code>v3</code><br/> + <code>v4</code><br/> + <code>v7</code><br/> + и т. д.</td> + <td> + <p>Уровень API, поддерживаемый устройством. Например, <code>v1</code> для уровня API +1 (устройства с Android 1.0 или выше) и <code>v4</code> для уровня API 4 (устройства с Android +1.6 или выше). В документе <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">Уровни API Android</a> содержится дополнительная информация +об этих значениях.</p> + </td> + </tr> +</table> + + +<p class="note"><strong>Примечание.</strong> Некоторые квалификаторы конфигурации добавлены после версии Android +1.0, поэтому в некоторых версиях Android поддерживаются не все квалификаторы. При использовании нового квалификатора косвенно +добавляется квалификатор версии платформы, чтобы более старые устройства игнорировали его. Например, при использовании +квалификатора <code>w600dp</code> автоматически добавляется квалификатор <code>v13</code>, так как квалификатор +доступной ширины был новым в API уровня 13. Чтобы исключить какие-либо проблемы, всегда включайте набор +ресурсов по умолчанию (набор ресурсов <em>без квалификаторов</em>). Для получения дополнительной информации см. +раздел <a href="#Compatibility">Обеспечение оптимальной совместимости устройства с +ресурсами</a>.</p> + + + +<h3 id="QualifierRules">Правила квалификатора имени</h3> + +<p>Здесь приведены некоторые правила использования имен квалификаторов:</p> + +<ul> + <li>Можно указать несколько квалификаторов для одного набора ресурсов, разделяя их дефисами. Например, + <code>drawable-en-rUS-land</code> применяется к устройствам в США, на английском языке в альбомной +ориентации.</li> + <li>Квалификаторы должны идти в том же порядке, в котором они перечислены в <a href="#table2">таблице 2</a>. Например: + + <ul> + <li>Неправильно: <code>drawable-hdpi-port/</code></li> + <li>Правильно: <code>drawable-port-hdpi/</code></li> + </ul> + </li> + <li>Нельзя использовать вложенные каталоги альтернативных ресурсов. Например, нельзя иметь каталог +<code>res/drawable/drawable-en/</code>.</li> + <li>Значения не зависят от регистра букв. Компилятор ресурсов преобразует имена каталогов +в нижний регистр перед обработкой, чтобы избежать проблем в файловых системах, +не учитывающих регистр. Прописные буквы в именах служат исключительно для удобочитаемости.</li> + <li>Поддерживается только одно значение квалификатора каждого типа. Например, если требуется использовать +одинаковые графические файлы для испанского и французского языков, <em>нельзя</em> создавать +каталог с именем <code>drawable-rES-rFR/</code>. Вместо этого необходимо создать два каталога ресурсов, например, +<code>drawable-rES/</code> и <code>drawable-rFR/</code>, которые содержат соответствующие файлы. +Однако не обязательно фактически копировать одинаковые файлы в оба каталога. Вместо этого +можно создать псевдоним для ресурса. См. раздел <a href="#AliasResources">Создание +псевдонимов ресурсов</a> ниже.</li> +</ul> + +<p>После сохранения альтернативных ресурсов в каталоги с именами +этих квалификаторов Android автоматически применяет ресурсы в приложении на основе текущей конфигурации +устройства. При каждом запросе ресурсов Android проверяет каталоги альтернативных +ресурсов, которые содержат файл запрошенного ресурса, затем <a href="#BestMatch">находят +наиболее подходящий ресурс</a> (обсуждается ниже). Если нет альтернативных ресурсов, которые +соответствуют конкретной конфигурации устройства, Android использует ресурсы по умолчанию (набор +ресурсов для конкретного типа ресурсов, которые не содержат квалификатора +конфигурации).</p> + + + +<h3 id="AliasResources">Создание псевдонимов ресурсов</h3> + +<p>Ресурс, предназначенный для нескольких конфигураций +устройства (но не являющийся ресурсом по умолчанию), следует помещать +только в один каталог альтернативных ресурсов. Вместо этого можно (в некоторых случаях) создать +альтернативный +ресурс, действующий в качестве псевдонима для ресурса, сохраненного в каталоге ресурсов по умолчанию.</p> + +<p class="note"><strong>Примечание.</strong> Не все ресурсы предлагают механизм, позволяющий +создавать псевдоним для другого ресурса. В частности, анимации, меню, необработанные и другие неустановленные +ресурсы в каталоге {@code xml/} не содержат такой возможности.</p> + +<p>Например, представьте, что имеется значок приложения, {@code icon.png}, и требуется иметь уникальные версии +этого значка для разных языков. Однако в двух языках, канадском английском и канадском французском, требуется +использовать одинаковую версию. Можно предположить, что требуется скопировать одно изображение +в каталоги ресурсов для обоих языков, но +это неверно. Вместо этого можно сохранить изображение для обоих языков, как {@code icon_ca.png} (любое +имя, кроме {@code icon.png}), и поместить его +в каталог по умолчанию {@code res/drawable/}. Затем создайте файл {@code icon.xml} в каталогах {@code +res/drawable-en-rCA/} и {@code res/drawable-fr-rCA/} который ссылается на ресурс {@code icon_ca.png} +с помощью элемента {@code <bitmap>}. Это позволяет хранить только одну версию файла PNG +и два маленьких файла XML, которые указывают на него. (Пример файла XML показан ниже.)</p> + + +<h4>Графические объекты</h4> + +<p>Чтобы создать псевдоним для существующего графического объекта, используйте элемент {@code <bitmap>}. +Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/icon_ca" /> +</pre> + +<p>Если сохранить этот файл под именем {@code icon.xml} (в каталоге альтернативных ресурсов, например, +{@code res/drawable-en-rCA/}), он компилируется в ресурс, на который +можно ссылаться с помощью {@code R.drawable.icon}, но фактически он является псевдонимом для ресурса {@code +R.drawable.icon_ca} (который сохранен в каталоге {@code res/drawable/}).</p> + + +<h4>Макет</h4> + +<p>Чтобы создать псевдоним для существующего макета, используйте элемент {@code <include>} +, заключенный в теги {@code <merge>}. Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<merge> + <include layout="@layout/main_ltr"/> +</merge> +</pre> + +<p>Если сохранить этот файл под именем {@code main.xml}, он компилируется в ресурс, на который можно ссылаться +с помощью {@code R.layout.main}, но фактически он является псевдонимом для ресурса {@code R.layout.main_ltr} +.</p> + + +<h4>Строки и другие простые значения</h4> + +<p>Чтобы создать псевдоним для существующей строки используйте идентификатор ресурса нужной +строки в качестве значения для новой строки. Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello</string> + <string name="hi">@string/hello</string> +</resources> +</pre> + +<p>Ресурс {@code R.string.hi} теперь является псевдонимом для {@code R.string.hello}.</p> + +<p> <a href="{@docRoot}guide/topics/resources/more-resources.html">Другие простые значения</a> работают +аналогично. Например, цвет:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="yellow">#f00</color> + <color name="highlight">@color/red</color> +</resources> +</pre> + + + + +<h2 id="Compatibility">Обеспечение оптимальной совместимости устройства с ресурсами</h2> + +<p>Для того чтобы приложение поддерживало несколько конфигураций устройств, очень важно +всегда предоставлять ресурсы по умолчанию для каждого типа ресурсов, используемых приложением.</p> + +<p>Например, если приложение поддерживает несколько языков, всегда включайте каталог {@code +values/} (в котором сохранены строки) <em>без</em> <a href="#LocaleQualifier">квалификатора языка и региона</a>. Если вместо этого поместить все файлы строк +в каталоги с квалификаторами языка и региона, приложение закроется с ошибкой при запуске +на устройстве, на котором установлен язык, отсутствующий в ваших строках. Но как только вы предоставили ресурсы +{@code values/} по умолчанию, приложение будет работать правильно (даже если пользователь не +понимает этого языка, это лучше, чем завершение с ошибкой).</p> + +<p>Таким же образом, если вы предоставляете различные ресурсы макета в зависимости от ориентации экрана, следует +указать одну ориентацию в качестве ориентации по умолчанию. Например, вместо предоставления ресурсов макета в каталоге {@code +layout-land/} для альбомной ориентации и в каталоге {@code layout-port/} для книжной ориентации, оставьте один вариант по умолчанию: например, +{@code layout/} для альбомной и {@code layout-port/} для книжной ориентации.</p> + +<p>Предоставление ресурсов по умолчанию важно не только потому, что приложение сможет работать на конфигурации, +которую вы не предусмотрели, но также и потому, что новые версии Android иногда добавляют +квалификаторы конфигураций, которые не поддерживаются более старыми версиями. Если вы используете новый квалификатор ресурсов, +но поддерживаете совместимость кода с более старыми версиями Android, то при выполнении вашего приложения в более старой версии +Android оно завершится в ошибкой, если вы не предусмотрели ресурсы по умолчанию, так как +оно не может использовать ресурсы, проименованные новым квалификатором. Например, если для параметра <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code +minSdkVersion}</a> установлено значение 4 и вы квалифицировали все графические ресурсы с использованием <a href="#NightQualifier">ночного режима</a> ({@code night} или {@code notnight}, который был добавлен в API +уровня 8), то устройства с API уровня 4 не смогут получить доступ к графическим ресурсам и приложение завершится с ошибкой. В этом +случае, вероятно, следует использовать {@code notnight} в качестве ресурсов по умолчанию и исключить этот +квалификатор, разместив графические ресурсы в каталогах {@code drawable/} или {@code drawable-night/}.</p> + +<p>Поэтому для обеспечения оптимальной совместимости с устройствами обязательно предоставляйте ресурсы +по умолчанию, которые приложение может правильно выполнять. Затем создайте альтернативные +ресурсы для определенных конфигураций устройств с помощью квалификаторов конфигурации.</p> + +<p>Из этого правила есть одно исключение: Если в приложении для параметра <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> установлено значение 4 или +выше, <em>не требуется</em> предоставлять графические ресурсы по умолчанию при предоставлении альтернативных графических +ресурсов с квалификатором <a href="#DensityQualifier">плотность экрана</a>. Даже без графических ресурсов +по умолчанию Android может найти наиболее подходящую альтернативную плотность экрана и масштабировать +растровые изображения при необходимости. Однако для оптимальной работы на устройствах всех типов следует +предоставить альтернативные графические ресурсы для всех трех типов плотности.</p> + + + +<h2 id="BestMatch">Как Android находит наиболее подходящий ресурс</h2> + +<p>Когда вы запрашиваете ресурс, для которого предоставлена альтернатива, Android выбирает +альтернативный ресурс для использования в режиме выполнения в зависимости от текущей конфигурации устройства. Чтобы +продемонстрировать, как Android выбирает альтернативный ресурс, допустим, что имеются следующие каталоги графических ресурсов, +каждый из которых содержит различные версии одинаковых изображений:</p> + +<pre class="classic no-pretty-print"> +drawable/ +drawable-en/ +drawable-fr-rCA/ +drawable-en-port/ +drawable-en-notouch-12key/ +drawable-port-ldpi/ +drawable-port-notouch-12key/ +</pre> + +<p>И допустим, что устройство имеет следующую конфигурацию:</p> + +<p style="margin-left:1em;"> +Язык = <code>en-GB</code> <br/> +Ориентация экрана = <code>port</code> <br/> +Плотность пикселов на экране = <code>hdpi</code> <br/> +Тип сенсорного экрана = <code>notouch</code> <br/> +Основной способ ввода текста = <code>12key</code> +</p> + +<p>Сравнивая конфигурацию устройства с доступными альтернативными ресурсами, Android выбирает +графику из каталога {@code drawable-en-port}.</p> + +<p>Система приходит к решению об используемых ресурсах на основе следующей +логики:</p> + + +<div class="figure" style="width:371px"> +<img src="{@docRoot}images/resources/res-selection-flowchart.png" alt="" height="471" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Как Android находит наиболее подходящий ресурс. +Структурная схема.</p> +</div> + + +<ol> + <li>Исключение файлов ресурсов, которые противоречат конфигурации устройства. + <p>Каталог <code>drawable-fr-rCA/</code> исключается, так как он +противоречит языку <code>en-GB</code>.</p> +<pre class="classic no-pretty-print"> +drawable/ +drawable-en/ +<strike>drawable-fr-rCA/</strike> +drawable-en-port/ +drawable-en-notouch-12key/ +drawable-port-ldpi/ +drawable-port-notouch-12key/ +</pre> +<p class="note"><strong>Исключение.</strong> Квалификатор плотности пикселов на экране не исключается +вследствие противоречия. Хотя плотность экрана устройства hdpi, +каталог <code>drawable-port-ldpi/</code> не исключается, так как на этом этапе любая плотность экрана +считается подходящей. Более подробная информация доступна в документе <a href="{@docRoot}guide/practices/screens_support.html">Поддержка нескольких +экранов</a>.</p></li> + + <li>Указание (следующего) квалификатора с высшим приоритетом в списке (<a href="#table2">таблица 2</a>). +(Начать с MCC, затем двигаться вниз.) </li> + <li>Содержат ли какие-либо каталоги ресурсов этот квалификатор? </li> + <ul> + <li>Если Нет, вернуться к шагу 2 и найти следующий квалификатор. (В нашем примере +получается ответ «нет», пока не достигнут квалификатор языка.)</li> + <li>Если Да, перейти к шагу 4.</li> + </ul> + </li> + + <li>Исключить каталоги ресурсов, которые не содержат этого квалификатора. В данном примере система исключает +все каталоги, которые не содержат квалификатора языка:</li> +<pre class="classic no-pretty-print"> +<strike>drawable/</strike> +drawable-en/ +drawable-en-port/ +drawable-en-notouch-12key/ +<strike>drawable-port-ldpi/</strike> +<strike>drawable-port-notouch-12key/</strike> +</pre> +<p class="note"><strong>Исключение.</strong> Если получен квалификатор плотности пикселов на экране, +Android выбирает вариант, наиболее близко соответствующий плотности экрана устройства. +Как правило, Android предпочитает уменьшать большие исходные изображения, чем увеличивать +мелкие. См. раздел <a href="{@docRoot}guide/practices/screens_support.html">Поддержка нескольких +экранов</a>.</p> + </li> + + <li>Вернуться и повторять шаги 2, 3 и 4, пока не останется только один каталог. В нашем примере следующим +квалификатором, для которого есть совпадения, является ориентация экрана. +Поэтому исключаются ресурсы, не указывающие ориентацию экрана: +<pre class="classic no-pretty-print"> +<strike>drawable-en/</strike> +drawable-en-port/ +<strike>drawable-en-notouch-12key/</strike> +</pre> +<p>Остается каталог {@code drawable-en-port}.</p> + </li> +</ol> + +<p>Хотя эта процедура выполняется для каждого запрошенного ресурса, система дополнительно оптимизирует +некоторые вопросы. Одна из таких оптимизаций состоит в том, что поскольку конфигурация устройства известна, можно +исключить альтернативные ресурсы, которые не могут подойти. Например, если используется конфигурация с английским +языком ("en"), все каталоги ресурсов, для которых установлен другой квалификатор языка, +никогда не включаются в пул проверяемых ресурсов (хотя +каталоги ресурсов <em>без</em> квалификатора языка включаются).</p> + +<p>При выборе ресурсов на основе квалификаторов размера экрана система будет использовать ресурсы +предназначенные для экрана, меньшего чем текущий экран, если нет более подходящих ресурсов +(например, на экранах большого размера при необходимости будут использоваться ресурсы, предназначенные для экранов нормального размера). Однако, если + единственные доступные ресурсы <em>превосходят</em> размер текущего экрана, система +<strong>не</strong> будет использовать эти ресурсы, и приложение аварийно завершится, если нет других ресурсов, соответствующих конфигурации +устройства (например, если все ресурсы макета отмечены квалификатором {@code xlarge}, +но устройство оснащено экраном нормального размера).</p> + +<p class="note"><strong>Примечание.</strong> <em>Приоритет</em> квалификатора (в <a href="#table2">таблице 2</a>) более важен, +чем число квалификаторов, которые точно соответствуют устройству. Например, на шаге 4 выше, последний +вариант в списке содержит три квалификатора, которые точно соответствуют устройству (ориентация, тип +сенсорного экрана и способ ввода), в то время как <code>drawable-en</code> содержит только один подходящий параметр +(язык). Однако язык имеет более высокий приоритет, чем эти остальные квалификаторы, поэтому +<code>drawable-port-notouch-12key</code> вычеркивается.</p> + +<p>Для получения более подробной информации об использовании ресурсов в приложении перейдите к разделу <a href="accessing-resources.html">Доступ к ресурсам</a>.</p> diff --git a/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd new file mode 100644 index 000000000000..5dc59c81559f --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=Обработка изменений в режиме выполнения +page.tags=операция,жизненный цикл +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>Содержание документа</h2> + <ol> + <li><a href="#RetainingAnObject">Сохранение объекта во время изменения конфигурации</a></li> + <li><a href="#HandlingTheChange">Самостоятельная обработка изменения конфигурации</a> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="providing-resources.html">Предоставление ресурсов</a></li> + <li><a href="accessing-resources.html">Доступ к ресурсам</a></li> + <li><a href="http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html">Более +быстрое изменение ориентации экрана</a></li> + </ol> +</div> +</div> + +<p>Некоторые конфигурации устройств могут изменяться в режиме выполнения +(например, ориентация экрана, доступность клавиатуры и язык). Когда происходит такое изменение, +Android перезапускает выполнение +{@link android.app.Activity} (вызывается {@link android.app.Activity#onDestroy()}, затем {@link +android.app.Activity#onCreate(Bundle) onCreate()}). Поведение при перезапуске позволяет +приложению учитывать новые конфигурации путем автоматической перезагрузки в приложение +альтернативных ресурсов, которые соответствуют новой конфигурации устройства.</p> + +<p>Для правильной обработки перезапуска важно, чтобы операция восстанавливала предыдущее +состояние в течение нормального <a href="{@docRoot}guide/components/activities.html#Lifecycle">жизненного цикла +операции</a>, при котором Android вызывает +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} перед уничтожением +операции, чтобы вы могли сохранить данные о состоянии приложения. Затем вы можете восстановить состояние +во время выполнения метода {@link android.app.Activity#onCreate(Bundle) onCreate()} или {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()}.</p> + +<p>Чтобы проверить, перезапускается ли приложение в неизмененном состоянии, следует +вызывать изменение конфигурации (например, изменение ориентации экрана) во время выполнения различных +задач в приложении. Приложение должно перезапускаться в любой момент без потери +пользовательских данных и состояния, чтобы обработать события, например, изменение конфигурации или получение пользователем +входящего телефонного звонка с последующим возвращением в приложение после того, как процесс +приложения мог быть уничтожен. Чтобы узнать, как восстановить состояние операции, прочитайте раздел <a href="{@docRoot}guide/components/activities.html#Lifecycle">Жизненный цикл операции</a>.</p> + +<p>Однако возможна ситуация, при которой перезапуск приложения и +восстановление значительного объема данных может быть слишком ресурсоемким и может создавать неприятное впечатление у пользователя. В такой ситуации +есть два других варианта:</p> + +<ol type="a"> + <li><a href="#RetainingAnObject">Сохранение объекта во время изменения конфигурации</a> + <p>Необходимо позволить перезапуск операции при изменении конфигурации, при этом перенести объект +с сохранением состояния в новый экземпляр операции.</p> + + </li> + <li><a href="#HandlingTheChange">Самостоятельная обработка изменения конфигурации</a> + <p>Не допускайте перезапуска операции системой во время определенных изменений конфигурации, +но получайте обратный вызов при изменении конфигурации, чтобы при необходимости можно было вручную обновить +операцию.</p> + </li> +</ol> + + +<h2 id="RetainingAnObject">Сохранение объекта во время изменения конфигурации</h2> + +<p>Если перезапуск операции требует восстановления больших объемов данных, повторного подключения к сети +или выполнения других масштабных действий, то полный перезапуск вследствие изменения конфигурации +может тормозить работу пользователя. Кроме того, может быть невозможно полное восстановление состояния операции + из объекта {@link android.os.Bundle}, который система сохраняет с помощью обратного вызова {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()}, поскольку он не +предназначен для переноса больших объектов (таких как растровые изображения), и данные в нем должны быть преобразованы в последовательную +форму, а затем обратно, что потребляет много памяти и тормозит изменение конфигурации. В такой ситуации +вы можете облегчить бремя повторной инициализации операции путем сохранения фрагмента {@link +android.app.Fragment} в случае перезапуска операции вследствие изменения конфигурации. Этот фрагмент +может содержать ссылки на объекты с сохранением состояния, которые требуется сохранить.</p> + +<p>Когда система Android завершает операцию вследствие изменения конфигурации, фрагменты +операции, отмеченные для сохранения, не уничтожаются. Такие фрагменты можно добавлять в операцию +для защиты объектов с сохранением состояния.</p> + +<p>Сохранение объектов с сохранением состояния во фрагменте во время изменения конфигурации в режиме выполнения:</p> + +<ol> + <li>Расширьте класс {@link android.app.Fragment} и объявите ссылки на объекты, +сохраняющие состояние.</li> + <li>Вызовите {@link android.app.Fragment#setRetainInstance(boolean)}, когда фрагмент создан. + </li> + <li>Добавьте фрагмент в операцию.</li> + <li>Используйте {@link android.app.FragmentManager} для извлечения фрагмента при перезапуске +операции.</li> +</ol> + +<p>В качестве привмера приведено определение фрагмента следующим образом:</p> + +<pre> +public class RetainedFragment extends Fragment { + + // data object we want to retain + private MyDataObject data; + + // this method is only called once for this fragment + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // retain this fragment + setRetainInstance(true); + } + + public void setData(MyDataObject data) { + this.data = data; + } + + public MyDataObject getData() { + return data; + } +} +</pre> + +<p class="caution"><strong>Внимание!</strong> Хотя можно сохранить любой объект, +не следует передавать объект, связанный с {@link android.app.Activity}, например, {@link +android.graphics.drawable.Drawable}, {@link android.widget.Adapter}, {@link android.view.View} +или любой другой объект, связанный с {@link android.content.Context}. В этом случае произойдет +утечка всех видов и ресурсов исходного экземпляра операции. (Утечка ресурсов +означает, что приложение удерживает их, и система не может очистить от них память, поэтому +может теряться значительный объем памяти).</p> + +<p>Затем используйте {@link android.app.FragmentManager} для добавления фрагмента в операцию. +Можно получить объект данных из фрагмента, когда операция повторно запускается в результате изменения конфигурации +в режиме выполнения. В качестве примера операция определена следующим образом:</p> + +<pre> +public class MyActivity extends Activity { + + private RetainedFragment dataFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // find the retained fragment on activity restarts + FragmentManager fm = getFragmentManager(); + dataFragment = (DataFragment) fm.findFragmentByTag(“data”); + + // create the fragment and data the first time + if (dataFragment == null) { + // add the fragment + dataFragment = new DataFragment(); + fm.beginTransaction().add(dataFragment, “data”).commit(); + // load the data from the web + dataFragment.setData(loadMyData()); + } + + // the data is available in dataFragment.getData() + ... + } + + @Override + public void onDestroy() { + super.onDestroy(); + // store the data in the fragment + dataFragment.setData(collectMyLoadedData()); + } +} +</pre> + +<p>В этом примере {@link android.app.Activity#onCreate(Bundle) onCreate()} добавляет фрагмент +или восстанавливает ссылку на него. Метод {@link android.app.Activity#onCreate(Bundle) onCreate()} также +хранит объект, сохраняющий состояние, внутри экземпляра фрагмента. +Метод {@link android.app.Activity#onDestroy() onDestroy()} обновляет объект, сохраняющий состояние, внутри +сохраненного экземпляра фрагмента.</p> + + + + + +<h2 id="HandlingTheChange">Самостоятельная обработка изменения конфигурации</h2> + +<p>Если приложению не требуется обновление ресурсов во время определенного изменения +конфигурации <em>и</em> имеются ограничения производительности, не разрешающие +перезапуск операции, можно объявить самостоятельную обработку операцией изменения конфигурации +, что запрещает системе перезапускать эту операцию.</p> + +<p class="note"><strong>Примечание.</strong> Самостоятельная обработка изменения конфигурации может +значительно затруднить использование альтернативных ресурсов, так как система не +применяет их автоматически. Этот подход может применяться в крайнем случае, когда необходимо избежать перезапуска в результате +изменения конфигурации, и для большинства приложений его использование не рекомендуется.</p> + +<p>Для объявления самостоятельной обработки изменения конфигурации операцией отредактируйте соответствующий элемент <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +в файле манифеста и включите в него атрибут <a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a> со значением, представляющим конфигурацию, которую +требуется обрабатывать самостоятельно. Возможные значения перечислены в документации для атрибута <a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a> (наиболее часто используются значения {@code "orientation"} для +предотвращения перезапуска при изменении ориентации экрана и {@code "keyboardHidden"} для предотвращения +перезапуска при изменении доступности клавиатуры). Можно объявить несколько значений конфигурации +в атрибуте, разделяя их символом вертикальной черты {@code |}.</p> + +<p>Например, следующий код манифеста объявляет операцию, которая обрабатывает как +изменение ориентации экрана, так и изменение доступности экрана:</p> + +<pre> +<activity android:name=".MyActivity" + android:configChanges="orientation|keyboardHidden" + android:label="@string/app_name"> +</pre> + +<p>Теперь, при изменении одного из этих элементов конфигурации, операция {@code MyActivity} не перезапускается. +Вместо этого {@code MyActivity} получает вызов {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. Этот метод +получает объект {@link android.content.res.Configuration}, в котором указана +новая конфигурация устройства. При считываении полей {@link android.content.res.Configuration} +можно определить новую конфигурацию и внести соответствующие изменения, обновляя +ресурсы, которые используются в интерфейсе. В момент +вызова этого метода объект {@link android.content.res.Resources} операции обновляется +и возвращает ресурсы на основе новой конфигурации, поэтому можно просто +сбросить элементы пользовательского интерфейса без перезапуска операции системой.</p> + +<p class="caution"><strong>Внимание!</strong> Начиная с Android 3.2 (уровень API 13), <strong> +«размер экрана» также изменяется</strong> при переходе между книжной и альбомной +ориентациями устройства. Таким образом, если в режиме выполнения требуется предотвратить перезапуск вследствие изменения ориентации при разработке для +уровня API 13 или выше (как объявлено в атрибутах <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> и <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> +), необходимо включить значение {@code "screenSize"} в дополнение к значению {@code +"orientation"}. То есть необходимо объявить {@code +android:configChanges="orientation|screenSize"}. Однако, если приложение нацелено на уровень API +12 или ниже, ваша операция всегда обрабатывает это изменение конфигурации самостоятельно (это изменение +конфигурации не перезапускает операцию даже при работе на устройстве с Android 3.2 или более поздней версией).</p> + +<p>Например, следующая реализация метода {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} +проверяет текущую ориентацию устройства:</p> + +<pre> +@Override +public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Checks the orientation of the screen + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show(); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ + Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show(); + } +} +</pre> + +<p>Объект {@link android.content.res.Configuration} представляет все текущие +конфигурации, а не только изменившиеся. В большинстве случаев неважно, +как именно изменилась конфигурация, поэтому можно просто переназначить все ресурсы, предоставляющие альтернативу +для обрабатываемой конфигурации. Например, так как объект {@link +android.content.res.Resources} обновлен, можно сбросить +любые виды {@link android.widget.ImageView} с помощью {@link android.widget.ImageView#setImageResource(int) +setImageResource()} +, и после этого будут использоваться ресурсы, соответствующие новой конфигурации (как описано в разделе <a href="providing-resources.html#AlternateResources">Предоставление ресурсов</a>).</p> + +<p>Обратите внимание, что значения из полей {@link +android.content.res.Configuration} являются целыми, которые соответствуют определенным константам +из класса {@link android.content.res.Configuration}. Документацию об используемых константах +для каждого поля см. в соответствующем поле по ссылке {@link +android.content.res.Configuration}.</p> + +<p class="note"><strong>Помните!</strong> При объявлении операции, обрабатывающей изменение +конфигурации, вы отвечаете за сброс любых элементов, для которых вы предоставили альтернативы. Если вы +объявили, что операция обрабатывает изменение ориентации, и у вас есть изображения, которые должны быть заменены при переходе между +альбомной и книжной ориентацией, вы должны переназначить каждый ресурс каждому элементу во время выполнения метода {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}.</p> + +<p>Если вам не требуется обновлять приложение на основе этих изменений +конфигурации, можно вместо этого <em>не</em> реализовывать метод {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}. В этом +случае все ресурсы, использованные до изменения конфигурации, продолжают использоваться +, просто не происходит перезапуск операции. Однако ваше приложение обязательно должно быть +способно выключаться и перезапускаться в предыдущем состоянии, поэтому не следует использовать эту технику +как способ избежать сохранения состояния во время нормального жизненного цикла операции. Не только потому, что +есть другие изменения конфигурации, которые приведут к перезапуску приложения, но +также и потому, что вы должны обрабатывать события, например, когда пользователь покидает приложение, и оно +закрывается до возвращения в него пользователя.</p> + +<p>Более подробную информацию об изменениях конфигурации, которые можно обрабатывать внутри операции, см. в документации <a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a> и описании +класса {@link android.content.res.Configuration}.</p> diff --git a/docs/html-intl/intl/ru/guide/topics/ui/controls.jd b/docs/html-intl/intl/ru/guide/topics/ui/controls.jd new file mode 100644 index 000000000000..62f4c76686f5 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=Элементы управления вводом +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + +<div class="figure" style="margin:0"> + <img src="{@docRoot}images/ui/ui-controls.png" alt="" style="margin:0" /> +</div> + +<p>Элементы управления вводом представляют собой интерактивные компоненты пользовательского интерфейса приложения. В Android имеется +широкий набор элементов управления, которые можно использовать в пользовательском интерфейсе, например, кнопки, текстовые поля, полосы прокрутки, +флажки, кнопки изменения масштаба, переключатели и многие другие элементы.</p> + +<p>Чтобы добавить элемент управления вводом в пользовательский интерфейс, достаточно вставить соответствующий элемент XML в <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML-файл макета</a>. Ниже представлен пример макета +с текстовым полем и кнопкой.</p> + +<pre style="clear:right"> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal"> + <EditText android:id="@+id/edit_message" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:hint="@string/edit_message" /> + <Button android:id="@+id/button_send" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_send" + android:onClick="sendMessage" /> +</LinearLayout> +</pre> + +<p>Каждый элемент управления вводом поддерживает определенный набор событий ввода, чтобы можно было обрабатывать такие события, как ввод +пользователем текста или нажатие кнопки.</p> + + +<h2 id="CommonControls">Часто используемые элементы управления</h2> +<p>Ниже представлен список некоторых часто используемых элементов управления, которые можно использовать в приложении. Чтобы узнать подробнее о каждом элементе управления, +перейдите по соответствующей ссылке.</p> + +<p class="note"><strong>Примечание.</strong> Ниже перечислены далеко не все элементы управления, которые имеются в системе +Android. Все они имеются в пакете {@link android.widget}. Если в вашем приложении требуется реализовать +определенный тип элемента управления вводом, вы можете создать собственные <a href="{@docRoot}guide/topics/ui/custom-components.html">настраиваемые компоненты</a>.</p> + +<table> + <tr> + <th scope="col">Тип элемента управления</th> + <th scope="col">Описание</th> + <th scope="col">Связанные классы</th> + </tr> + <tr> + <td><a href="controls/button.html">Кнопка</a></td> + <td>Кнопка, которую пользователь может нажать для выполнения действия.</td> + <td>{@link android.widget.Button Button} </td> + </tr> + <tr> + <td><a href="controls/text.html">Текстовое поле</a></td> + <td>Редактируемое текстовое поле. Можно воспользоваться виджетом <code>AutoCompleteTextView</code>, чтобы создать виджет для ввода текста с возможностью автозаполнения с помощью подсказок.</td> + <td>{@link android.widget.EditText EditText}, {@link android.widget.AutoCompleteTextView}</td> + </tr> + <tr> + <td><a href="controls/checkbox.html">Флажок</a></td> + <td>Переключатель, которым можно воспользоваться для включения или отключения функции или компонента. Флажки следует использовать при отображении группы доступных для выбора параметров, которые не являются взаимоисключающими.</td> + <td>{@link android.widget.CheckBox CheckBox} </td> + </tr> + <tr> + <td><a href="controls/radiobutton.html">Переключатель</a></td> + <td>Этот элемент управления аналогичен флажку, за исключением того, что в группе элементов можно выбрать только один вариант.</td> + <td>{@link android.widget.RadioGroup RadioGroup} + <br>{@link android.widget.RadioButton RadioButton} </td> + </tr> + <tr> + <td><a href="controls/togglebutton.html" style="white-space:nowrap">Кнопка-переключатель</a></td> + <td>Кнопка включения/отключения с индикатором.</td> + <td>{@link android.widget.ToggleButton ToggleButton} </td> + </tr> + <tr> + <td><a href="controls/spinner.html">Раскрывающийся список</a></td> + <td>Раскрывающийся список параметров, в котором пользователь может выбрать только одно значение.</td> + <td>{@link android.widget.Spinner Spinner} </td> + </tr> + <tr> + <td><a href="controls/pickers.html">Элементы выбора</a></td> + <td>Элементы диалогового окна для выбора одного значения из набора с помощью кнопок со стрелками вверх и вниз или с помощью жеста пролистывания. Для ввода значений даты (дня, месяца, года) используйте виджет <code>DatePicker</code>, а для ввода значений времени (часов, минут и указания времени до полудня или после [AM/PM]) — виджет <code>TimePicker</code>. Формат этих виджетов выбирается автоматически на основе региональных настроек на устройстве.</td> + <td>{@link android.widget.DatePicker}, {@link android.widget.TimePicker}</td> + </tr> +</table> diff --git a/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd new file mode 100644 index 000000000000..71428f6e1cdd --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=Макеты +page.tags=view,viewgroup +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> +<ol> + <li><a href="#write">Создание XML</a></li> + <li><a href="#load">Загрузка ресурса XML</a></li> + <li><a href="#attributes">Атрибуты</a> + <ol> + <li><a href="#id">Идентификатор</a></li> + <li><a href="#layout-params">Параметры макета</a></li> + </ol> + </li> + <li><a href="#Position">Размещение макета</a></li> + <li><a href="#SizePaddingMargins">Размер, отступ и поля</a></li> + <li><a href="#CommonLayouts">Часто используемые макеты</a></li> + <li><a href="#AdapterViews">Создание макетов с помощью адаптера</a> + <ol> + <li><a href="#FillingTheLayout">Заполнение представления адаптера данными</a></li> + <li><a href="#HandlingUserSelections">Обработка нажатий</a></li> + </ol> + </li> +</ol> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.view.View}</li> + <li>{@link android.view.ViewGroup}</li> + <li>{@link android.view.ViewGroup.LayoutParams}</li> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="{@docRoot}training/basics/firstapp/building-ui.html">Создание простого +пользовательского интерфейса</a></li> </div> +</div> + +<p>Макет определяет визуальную структуру пользовательского интерфейса, например, пользовательского интерфейса <a href="{@docRoot}guide/components/activities.html">операции</a> или <a href="{@docRoot}guide/topics/appwidgets/index.html">виджета приложения</a>. +Существует два способа объявить макет:</p> +<ul> +<li><strong>Объявление элементов пользовательского интерфейса в XML</strong>. В Android имеется удобный справочник XML-элементов +для классов View и их подклассов, например таких, которые используются для виджетов и макетов.</li> +<li><strong>Создание экземпляров элементов во время выполнения</strong>. Ваше +приложение может программным образом создавать объекты View и ViewGroup (а также управлять их свойствами). </li> +</ul> + +<p>Платформа Android предоставляет вам гибкость при использовании любого из этих способов для объявления пользовательского интерфейса приложения и его управления. Например, вы можете объявить в XML макеты по умолчанию, включая элементы экрана, которые будут отображаться в макетах, и их свойства. Затем вы можете добавить в приложение код, который позволяет изменять состояние объектов на экране (включая объявленные в XML) во время выполнения. </p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <ul> + <li>В <a href="{@docRoot}tools/sdk/eclipse-adt.html">подключаемом модуле ADT +для Eclipse</a> предусмотрена функция предварительного просмотра созданного вами файла XML — +достаточно открыть файл XML и выбрать вкладку <strong>Layout</strong> (Макет).</li> + <li>Для отладки макетов можно воспользоваться инструментом +<a href="{@docRoot}tools/debugging/debugging-ui.html#hierarchyViewer">Hierarchy Viewer</a> +— с его помощью можно просмотреть значения свойств, +рамки с индикаторами заполнения или полей, а также полностью отрисованные представления +прямо во время отладки приложения в эмуляторе или на устройстве.</li> + <li>С помощью инструмента <a href="{@docRoot}tools/debugging/debugging-ui.html#layoutopt">layoutopt</a> +можно быстро проанализировать макеты и их иерархии на предмет низкой эффективности или иных проблем.</li> +</div> +</div> + +<p>Преимущество объявления пользовательского интерфейса в файле XML заключается в том, что таким образом вы можете более эффективно отделить представление своего приложения от кода, который управляет его поведением. Описания пользовательского интерфейса находятся за пределами кода вашего приложения. Это означает, что вы можете изменять или адаптировать интерфейс без необходимости вносить правки в исходный код и повторно компилировать его. Например, можно создать разные файлы XML макета для экранов разных размеров и разных ориентаций экрана, а также для различных языков. Кроме того, объявление макета в XML упрощает визуализацию структуры пользовательского интерфейса, благодаря чему отладка проблем также становится проще. В данной статье мы научим вас объявлять макет в XML. Если вы +предпочитаете создавать экземпляры объектов View во время выполнения, обратитесь к справочной документации для классов {@link android.view.ViewGroup} и +{@link android.view.View}.</p> + +<p>Как правило, справочник XML-элементов для объявления элементов пользовательского интерфейса точно следует структуре и правилам именования для классов и методов — названия элементов соответствуют названиям классов, а названия атрибутов соответствуют методам. Фактически, соответствие зачастую такое точное, что вы можете с легкостью догадаться, какой атрибут XML соответствует тому или иному методу класса, или какой класс соответствует заданному элементу XML. Однако следует отметить, что не все справочники являются идентичными. В некоторых случаях названия могут несколько отличаться. Например, +у элемента EditText есть атрибут <code>text</code>, который соответствует методу +<code>EditText.setText()</code>. </p> + +<p class="note"><strong>Совет.</strong> Дополнительные сведения о различных типах макетов представлены в разделе +<a href="{@docRoot}guide/topics/ui/layout-objects.html">Часто используемые макеты</a>. Также в инструкциях по построению +<a href="{@docRoot}resources/tutorials/views/index.html">объектов представления Hello </a> изложены сведения о создании различных макетов.</p> + +<h2 id="write">Создание XML</h2> + +<p>С помощью справочника XML-элементов, который имеется в Android, можно быстро и просто создавать макеты пользовательского интерфейса и содержащиеся в нем элементы, точно так же, как при создании веб-страниц в HTML — с помощью вложенных элементов. </p> + +<p>В каждом файле макета должен быть всего один корневой элемент, в качестве которого должен выступать объект представления (View) или представления группы (ViewGroup). После определения корневого элемента можно приступать к добавлению дополнительных объектов макета или виджетов в качестве дочерних элементов для постепенного формирования иерархии представлений, которая определяет ваш макет. Ниже представлен пример макета XML, в котором используется вертикальный объект {@link android.widget.LinearLayout}, +в котором размещены элементы {@link android.widget.TextView} и {@link android.widget.Button}.</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + <TextView android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello, I am a TextView" /> + <Button android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello, I am a Button" /> +</LinearLayout> +</pre> + +<p>После объявления макета в файле XML сохраните файл с расширением <code>.xml</code> +в каталог <code>res/layout/</code> своего проекта Android для последующей компиляции. </p> + +<p>Дополнительные сведения о синтаксисе для файла XML макета представлены в документе <a href="{@docRoot}guide/topics/resources/layout-resource.html">Ресурсы макета</a>.</p> + +<h2 id="load">Загрузка ресурса XML</h2> + +<p>Во время компиляции приложения каждый файл XML макета компилируется в ресурс +{@link android.view.View}. Вам необходимо загрузить ресурс макета в коде приложения в ходе реализации метода обратного вызова +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()}. +Для этого вызовите метод +<code>{@link android.app.Activity#setContentView(int) setContentView()}</code>, передайте в него ссылку на ресурс макета в следующей форме: +<code>R.layout.<em>layout_file_name</em></code>. +Например, если макет XML сохранен как файл <code>main_layout.xml</code>, загрузить его +для вашей операции необходимо следующим образом:</p> +<pre> +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_layout); +} +</pre> + +<p>Метод обратного вызова <code>onCreate()</code> в вашей операции вызывается платформой Android во время +запуска операции (см. сведения о жизненных циклах в +документе +<a href="{@docRoot}guide/components/activities.html#Lifecycle">Операции</a>).</p> + + +<h2 id="attributes">Атрибуты</h2> + +<p>Каждый объект View и ViewGroup поддерживают свои собственные атрибуты XML. +Некоторые атрибуты характерны только для объекта View (например, объект TextView поддерживает атрибут <code>textSize</code>), +однако эти атрибуты также наследуются любыми объектами View, которые могут наследовать этот класс. +Некоторые атрибуты являются общими для всех объектов View, поскольку они наследуются от корневого класса View (такие как атрибут +<code>id</code>). Любые другие атрибуты рассматриваются как «параметры макета». Такие атрибуты +описывают определенные ориентации макета для объекта View, которые заданы +родительским объектом ViewGroup такого объекта.</p> + +<h3 id="id">Идентификатор</h3> + +<p>У любого объекта View может быть связанный с ним целочисленный идентификатор, который служит для обозначения уникальности объекта View в иерархии. +Во время компиляции приложения этот идентификатор используется как целое число, однако идентификатор +обычно назначается в файле XML макета в виде строки в атрибуте <code>id</code>. +Этот атрибут XML является общим для всех объектов View +(определенных классом {@link android.view.View}), который вы будете использовать довольно часто. +Синтаксис для идентификатора внутри тега XML следующий:</p> +<pre>android:id="@+id/my_button"</pre> + +<p>Символ @ в начале строки указывает на то, что обработчику XML следует выполнить синтаксический анализ остальной части +идентификатора, выполнить ее синтаксический анализ и определить ее в качестве ресурса идентификатора. Символ плюса (+) обозначает, что это имя нового ресурса, +который необходимо создать и добавить к нашим ресурсам (в файле <code>R.java</code>). В Android существует ряд других ресурсов +идентификатора. При ссылке на идентификатор ресурса Android вам не нужно указывать символ плюса, +однако необходимо добавить пространство имен пакета <code>android</code>, как указано ниже:</p> +<pre>android:id="@android:id/empty"</pre> +<p>После добавления пространства имен пакета <code>android</code> можно сослаться на идентификатор из класса ресурсов<code>android.R</code>, +а не из локального класса ресурсов.</p> + +<p>Чтобы создать представления и сослаться на них из приложения, обычно следует выполнить указанные ниже действия.</p> +<ol> + <li>Определите представление или виджет в файле макета и присвойте ему уникальный идентификатор: +<pre> +<Button android:id="@+id/my_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/my_button_text"/> +</pre> + </li> + <li>Затем создайте экземпляр объекта представления и выполните его захват из макета +(обычно с помощью метода <code>{@link android.app.Activity#onCreate(Bundle) onCreate()}</code>): +<pre> +Button myButton = (Button) findViewById(R.id.my_button); +</pre> + </li> +</ol> +<p>Определение идентификаторов для объектов представления имеет важное значение при создании объекта {@link android.widget.RelativeLayout}. +В относительном макете универсальные идентификаторы используются для +расположения представлений относительно друг друга.</p> +<p>Идентификатор не обязательно должен быть уникальным в рамках всей иерархии, +а только в той ее части, где вы выполняете поиск (зачастую это может быть как раз вся иерархия, поэтому +при возможности идентификаторы должны быть полностью уникальными).</p> + + +<h3 id="layout-params">Параметры макета</h3> + +<p>Атрибуты макета XML, которые называются <code>layout_<em>something</em></code>, определяют +параметры макета для объекта представления, подходящие для класса ViewGroup, в котором он находится.</p> + +<p>Каждый класс ViewGroup реализует вложенный класс, который наследует {@link +android.view.ViewGroup.LayoutParams}. В этом подклассе +имеются типы свойств, которые определяют размер и положение каждого дочернего представления, +подходящие для его группы. На рисунке 1 показано, что родительская группа +представлений определяет параметры макета для каждого дочернего представления (включая дочернюю группу представлений).</p> + +<img src="{@docRoot}images/layoutparams.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Графическое представление иерархии представления с параметрами +макета каждого представления.</p> + +<p>Обратите внимание, что подкласс LayoutParams имеет собственный синтаксис для задания +значений. Каждый дочерний элемент должен определять LayoutParams, которые подходят для его родительского элемента, +тогда как он сам может определять другие LayoutParams для своих дочерних элементов. </p> + +<p>Все группы представлений включают в себя параметры ширины и высоты (<code>layout_width</code> и +<code>layout_height</code>), и каждое представление должно определять их. Многие +LayoutParams также включают дополнительные параметры полей и границ. <p> + +<p>Для параметров ширины и высоты можно указать точные значения, хотя, возможно, +вам не захочется делать это часто. Обычно для задания значений ширины и высоты используется одна из следующих +констант: </p> + +<ul> + <li><var>wrap_content</var> — размер представления задается по размерам +его содержимого;</li> + <li><var>match_parent</var> (которая до API уровня 8 <var>называлась fill_parent</var> ) + — размер представления определяется ограничениями, задаваемыми его родительской группой представлений.</li> +</ul> + +<p>Как правило, не рекомендуется задавать абсолютные значения ширины и высоты макета +(например, в пикселах). Вместо этого используйте относительные единицы измерения, такие как +пикселы, не зависящие от разрешения экрана (<var>dp</var>), <var>wrap_content</var>или +<var>match_parent</var>Это гарантирует одинаковое отображение +вашего приложения на устройствах с экранами разных размеров. +Принятые типы измерения определены в +документе +<a href="{@docRoot}guide/topics/resources/available-resources.html#dimension">Доступные ресурсы</a>.</p> + + +<h2 id="Position">Размещение макета</h2> + <p> + Представление имеет прямоугольную форму. Расположение представления +определяется его координатами <em>слева</em> и <em>сверху</em>, а его размеры +— параметрами ширины и высоты. Расположение измеряется +в пикселах. + </p> + + <p> + Расположение представления можно получить путем вызова методов +{@link android.view.View#getLeft()} и {@link android.view.View#getTop()}. Первый возвращает координату слева (по оси X) +для прямоугольника представления. Второй возвращает верхнюю координату + (по оси Y) для прямоугольника представления. Оба этих метода +возвращают расположение представления относительно его родительского элемента. Например, +когда метод <code>getLeft()</code> возвращает 20, это означает, что представление находится на расстоянии 20 пикселов +от левого края его непосредственного родительского элемента. + </p> + + <p> + Кроме того, имеется несколько удобных методов +({@link android.view.View#getRight()} и {@link android.view.View#getBottom()}), которые позволяют избежать лишних вычислений. + Эти методы возвращают координаты правого и нижнего краев +прямоугольника представления. Например, вызов метода {@link android.view.View#getRight()} +аналогичен следующему вычислению: <code>getLeft() + getWidth()</code>. + </p> + + +<h2 id="SizePaddingMargins">Размер, отступ и поля</h2> + <p> + Размер представления выражается его шириной и высотой. Фактически, представление +обладает двумя парами значений «ширина-высота». + </p> + + <p> + Первая пара — это <em>измеренная ширина</em> и +<em>измеренная высота</em>. Эти размеры определяют размер представления +в границах своего родительского элемента. Измеренные +размеры можно получить, вызвав методы {@link android.view.View#getMeasuredWidth()} +и{@link android.view.View#getMeasuredHeight()}. + </p> + + <p> + Вторая пара значений — это просто <em>ширина</em> и <em>высота</em> (иногда они называются +<em>чертежная ширина</em> и <em>чертежная высота</em>). Эти размеры +определяют фактический размер представления на экране после разметки во время их +отрисовки. Эти значения могут отличаться от +измеренных ширины и высоты, хотя это и не обязательно. Значения ширины и высоты можно получить, вызвав методы +{@link android.view.View#getWidth()} и {@link android.view.View#getHeight()}. + </p> + + <p> + При измерении своих размеров представление учитывает заполнение. Отступ +выражается в пикселах для левой, верхней, правой и нижней частей представления. + Отступ можно использовать для смещения содержимого представления на определенное количество +пикселов. Например, значение отступа слева, равное 2, приведет к тому, что содержимое представления будет смещено +на 2 пиксела вправо от левого края представления. Для задания отступов можно использовать метод +{@link android.view.View#setPadding(int, int, int, int)}. Чтобы запросить отступ, используются методы +{@link android.view.View#getPaddingLeft()}, {@link android.view.View#getPaddingTop()}, +{@link android.view.View#getPaddingRight()} и {@link android.view.View#getPaddingBottom()}. + </p> + + <p> + Даже если представление может определить отступ, в нем отсутствует поддержка +полей. Такая возможность имеется у группы представлений. Дополнительные сведения представлены в справке по объектам +{@link android.view.ViewGroup} и +{@link android.view.ViewGroup.MarginLayoutParams}. + </p> + + <p>Дополнительные сведения о размерах представлены в разделе +<a href="{@docRoot}guide/topics/resources/more-resources.html#Dimension">Значения размеров</a>. + </p> + + + + + + +<style type="text/css"> +div.layout { + float:left; + width:200px; + margin:0 0 20px 20px; +} +div.layout.first { + margin-left:0; + clear:left; +} +</style> + + + + +<h2 id="CommonLayouts">Часто используемые макеты</h2> + +<p>В каждом подклассе класса {@link android.view.ViewGroup} имеется уникальный способ отображения +вложенных в него представлений. Ниже представлены некоторые из наиболее часто используемых типов макетов, которые входят в состав платформы +Android.</p> + +<p class="note"><strong>Примечание.</strong> Несмотря на то, что для формирования пользовательского интерфейса один +макет может содержать один или несколько вложенных макетов, рекомендуется использовать как можно более простую +иерархию макетов. Чем меньше в макете вложенных элементов, тем быстрее выполняется его отрисовка (горизонтальная иерархия +представлений намного лучше вертикальной).</p> + +<!-- +<h2 id="framelayout">FrameLayout</h2> +<p>{@link android.widget.FrameLayout FrameLayout} is the simplest type of layout +object. It's basically a blank space on your screen that you can +later fill with a single object — for example, a picture that you'll swap in and out. +All child elements of the FrameLayout are pinned to the top left corner of the screen; you cannot +specify a different location for a child view. Subsequent child views will simply be drawn over +previous ones, +partially or totally obscuring them (unless the newer object is transparent). +</p> +--> + + +<div class="layout first"> + <h4><a href="layout/linear.html">Линейный макет</a></h4> + <a href="layout/linear.html"><img src="{@docRoot}images/ui/linearlayout-small.png" alt="" /></a> + <p>Макет, в котором дочерние элементы представлены в горизонтальных или вертикальных столбцах. Если длина окна больше длины экрана, в нем +создается полоса прокрутки.</p> +</div> + +<div class="layout"> + <h4><a href="layout/relative.html">Относительный макет</a></h4> + <a href="layout/relative.html"><img src="{@docRoot}images/ui/relativelayout-small.png" alt="" /></a> + <p>В этом макете можно задавать расположение дочерних объектов относительно друг друга (дочерний элемент А находится +слева от дочернего элемента Б) или относительно родительского объекта (выравнивание относительно верхнего края родительского элемента).</p> +</div> + +<div class="layout"> + <h4><a href="{@docRoot}guide/webapps/webview.html">Представление веб-страницы</a></h4> + <a href="{@docRoot}guide/webapps/webview.html"><img src="{@docRoot}images/ui/webview-small.png" alt="" /></a> + <p>Отображение веб-страниц.</p> +</div> + + + + +<h2 id="AdapterViews" style="clear:left">Создание макетов с помощью адаптера</h2> + +<p>Если содержимое макета является динамическим или не определено заранее, можно использовать макет, который создает подклассы класса +{@link android.widget.AdapterView} для заполнения макета представлениями во время выполнения. Подкласс класса +{@link android.widget.AdapterView} использует {@link android.widget.Adapter} для +привязки данных к своему макету. {@link android.widget.Adapter} выступает в качестве посредника между источником +данных и макетом {@link android.widget.AdapterView} — {@link android.widget.Adapter} +извлекает данные (из источника, например, массива или запроса к базе данных) и преобразует каждую запись +в представление, которое можно добавить в макет {@link android.widget.AdapterView}.</p> + +<p>Часто используемые макеты, для создания которых можно использовать адаптер, включают в себя следующие:</p> + +<div class="layout first"> + <h4><a href="layout/listview.html">Представление в виде списка</a></h4> + <a href="layout/listview.html"><img src="{@docRoot}images/ui/listview-small.png" alt="" /></a> + <p>Отображение списка в один столбец с возможностью прокрутки.</p> +</div> + +<div class="layout"> + <h4><a href="layout/gridview.html">Представление в виде сетки</a></h4> + <a href="layout/gridview.html"><img src="{@docRoot}images/ui/gridview-small.png" alt="" /></a> + <p>Отображение сетки из столбцов и строк с возможностью прокрутки.</p> +</div> + + + +<h3 id="FillingTheLayout" style="clear:left">Заполнение представления адаптера данными</h3> + +<p>Чтобы заполнить адаптер {@link android.widget.AdapterView}, такой как {@link android.widget.ListView} или +{@link android.widget.GridView}, свяжите экземпляр {@link android.widget.AdapterView} с приложением +{@link android.widget.Adapter}, которое извлекает данные из внешнего источника и создает объект {@link +android.view.View}, который представляет каждую запись данных.</p> + +<p>В Android предусмотрено несколько подклассов адаптера {@link android.widget.Adapter}, которые полезно использовать для +извлечения данных различных видов и создания представлений для {@link android.widget.AdapterView}. Вот +два наиболее часто используемых адаптера:</p> + +<dl> + <dt>{@link android.widget.ArrayAdapter}</dt> + <dd>Этот адаптер используется в случае, когда в качестве источника данных выступает массив. По умолчанию {@link +android.widget.ArrayAdapter} создает представление для каждого элемента массива путем вызова метода {@link +java.lang.Object#toString()} для каждого элемента и помещения его содержимого в объект {@link +android.widget.TextView}. + <p>Например, если имеется массив строк, которые необходимо отобразить в объекте {@link +android.widget.ListView}, запустите новый {@link android.widget.ArrayAdapter} с помощью +конструктора, чтобы указать макет для каждой строки и массива строк:</p> +<pre> +ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, + android.R.layout.simple_list_item_1, myStringArray); +</pre> +<p>Аргументы для такого конструктора следующие:</p> +<ul> + <li>ваше приложение {@link android.content.Context};</li> + <li>макет, в котором содержится объект {@link android.widget.TextView} для каждой строки в массиве;</li> + <li>массив строк.</li> +</ul> +<p>Затем достаточно вызвать метод +{@link android.widget.ListView#setAdapter setAdapter()} для своего объекта {@link android.widget.ListView}:</p> +<pre> +ListView listView = (ListView) findViewById(R.id.listview); +listView.setAdapter(adapter); +</pre> + + <p>Чтобы настроить внешний вид каждого элемента, можно переопределить метод {@link +java.lang.Object#toString()} для объектов в массиве. Либо можно создать представление для каждого элемента, +который отличается от{@link android.widget.TextView} (например, если для каждого элемента массива требуется объект +{@link android.widget.ImageView}), наследовать класс {@link +android.widget.ArrayAdapter} и переопределить метод {@link android.widget.ArrayAdapter#getView +getView()}, чтобы возвратить требуемый тип представления для каждого элемента.</p> + +</dd> + + <dt>{@link android.widget.SimpleCursorAdapter}</dt> + <dd>Этот адаптер используется в случае, когда в качестве источника данных выступает объект {@link android.database.Cursor}. Если +используется {@link android.widget.SimpleCursorAdapter}, необходимо указать макет, который будет +использоваться для каждой строки в объекте {@link android.database.Cursor}, а также в какие представления макета необходимо вставить столбцы в объекте {@link android.database.Cursor} +. Например, если необходимо создать список +имен людей с их номерами телефонов, можно выполнить запрос, который возвращает объект {@link +android.database.Cursor}, содержащий строку для каждого человека и столбцы для имен и номеров +телефонов. Затем создается массив строк с указанием столбцов из объекта {@link +android.database.Cursor}, которые необходимо поместить в макет для каждого результата, а также массив целых чисел, в котором указаны +соответствующие представления для вставки каждого столбца:</p> +<pre> +String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, + ContactsContract.CommonDataKinds.Phone.NUMBER}; +int[] toViews = {R.id.display_name, R.id.phone_number}; +</pre> +<p>При создании экземпляра объекта {@link android.widget.SimpleCursorAdapter} передайте в него макет, +который будет использоваться для каждого результата, объект {@link android.database.Cursor} с результатами и два следующих массива:</p> +<pre> +SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, + R.layout.person_name_and_number, cursor, fromColumns, toViews, 0); +ListView listView = getListView(); +listView.setAdapter(adapter); +</pre> +<p>Затем {@link android.widget.SimpleCursorAdapter} создает представление для каждой строки в объекте +{@link android.database.Cursor} с помощью предоставленного макета путем вставки каждого элемента {@code +fromColumns} в соответствующее представление {@code toViews}.</p>.</dd> +</dl> + + +<p>Если в течение жизненного цикла вашего приложения вы вносите изменения в соответствующие данные, чтение которых +выполняет адаптер, вам следует вызвать метод {@link android.widget.ArrayAdapter#notifyDataSetChanged()}. Это позволит +уведомить связанное представление о том, что данные были изменены и ему требуется выполнить обновление.</p> + + + +<h3 id="HandlingUserSelections">Обработка нажатий</h3> + +<p>Чтобы организовать реагирование на нажатие каждого элемента в {@link android.widget.AdapterView}, +реализуйте интерфейс {@link android.widget.AdapterView.OnItemClickListener}. Например:</p> + +<pre> +// Create a message handling object as an anonymous class. +private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, int position, long id) { + // Do something in response to the click + } +}; + +listView.setOnItemClickListener(mMessageClickedHandler); +</pre> + + + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd new file mode 100644 index 000000000000..515ecc6a0130 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=Диалоговые окна +page.tags=alertdialog,dialogfragment + +@jd:body + + + +<div id="qv-wrapper"> + <div id="qv"> + <h2>Содержание документа</h2> +<ol> + <li><a href="#DialogFragment">Создание фрагмента диалогового окна</a></li> + <li><a href="#AlertDialog">Создание диалогового окна оповещения</a> + <ol> + <li><a href="#AddingButtons">Добавление кнопок</a></li> + <li><a href="#AddingAList">Добавление списка</a></li> + <li><a href="#CustomLayout">Создание пользовательского макета</a></li> + </ol> + </li> + <li><a href="#PassingEvents">Передача событий обратно в основное приложение</a></li> + <li><a href="#ShowingADialog">Отображение диалогового окна</a></li> + <li><a href="#FullscreenDialog">Отображение диалогового окна в полноэкранном режиме или в виде встроенного фрагмента</a> + <ol> + <li><a href="#ActivityAsDialog">Отображение операции в качестве диалога на больших экранах</a></li> + </ol> + </li> + <li><a href="#DismissingADialog">Закрытие диалогового окна</a></li> +</ol> + + <h2>Ключевые классы</h2> + <ol> + <li>{@link android.app.DialogFragment}</li> + <li>{@link android.app.AlertDialog}</li> + </ol> + + <h2>См. также:</h2> + <ol> + <li><a href="{@docRoot}design/building-blocks/dialogs.html">Руководство по дизайну диалоговых окон</a></li> + <li><a href="{@docRoot}guide/topics/ui/controls/pickers.html">Элементы выбора</a> (диалоговые окна выбора даты/времени)</li> + </ol> + </div> +</div> + +<p>Диалоговое окно — это небольшое окно, которое предлагает пользователю +принять решение или ввести дополнительную информацию. Диалоговое окно не заполняет весь экран и, +как правило, используется при модальных событиях, для продолжения которых требуется действие пользователя.</p> + +<div class="note design"> +<p><strong>Дизайн диалогового окна</strong></p> + <p>Подробную информацию о дизайне диалоговых окон, включая рекомендации + для вашего языка см. в <a href="{@docRoot}design/building-blocks/dialogs.html">Руководстве</a> по дизайну диалоговых окон.</p> +</div> + +<img src="{@docRoot}images/ui/dialogs.png" /> + +<p>Класс {@link android.app.Dialog} — это базовый класс для создания диалоговых окон, но +реализовывать напрямую класс {@link android.app.Dialog} не рекомендуется. +Вместо этого следует использовать один из следующих подклассов:</p> +<dl> + <dt>{@link android.app.AlertDialog}</dt> + <dd>Диалоговое окно, в котором могут отображаться заголовок, кнопки вплоть до трех штук, список + из выбираемых элементов либо пользовательский макет.</dd> + <dt>{@link android.app.DatePickerDialog} или {@link android.app.TimePickerDialog}</dt> + <dd>Диалоговое окно с предопределенным пользовательским интерфейсом, с помощью которого пользователь указывает значения даты или времени.</dd> +</dl> + +<div class="sidebox"> +<h2>Следует избегать ProgressDialog</h2> +<p>В Android имеется другой класс диалоговых окон под названием +{@link android.app.ProgressDialog}, которые отображают диалоговое окно с индикатором выполнения процесса. Тем не менее, если +необходимо отобразить прогресс загрузки или неопределенный процес, рекомендуется следовать +инструкциям по дизайну <a href="{@docRoot}design/building-blocks/progress.html">Выполнение & +Операции</a>, чтобы использовать {@link android.widget.ProgressBar} в вашем макете.</p> +</div> + +<p>Эти классы определяют стиль и структуру вашего диалогового окна, однако следует +использовать{@link android.support.v4.app.DialogFragment} в качестве контейнера вашего диалогового окна. +Класс {@link android.support.v4.app.DialogFragment} предоставляет все функции, +необходимые для создания диалогового окна и управления его внешним видом, вместо вызова методов +к объекту{@link android.app.Dialog}.</p> + +<p>Использование {@link android.support.v4.app.DialogFragment} для управления диалоговым окном +обеспечивает корректную обработку событий жизненного цикла +, таких как нажатие пользователем кнопки <em>Назад</em> или поворот экрана. С помощью класса {@link +android.support.v4.app.DialogFragment} также происходит повторное использование пользовательского интерфейса диалогового окна в качестве +встраиваемого компонента в пользовательский интерфейс более высокого уровня — подобно традиционному классу {@link +android.support.v4.app.Fragment} (например, когда необходимо различное отображение пользовательского интерфеса диалогового окна +на больших и маленьких экранах).</p> + +<p>В следующих разделах руководства описано использование {@link +android.support.v4.app.DialogFragment} в сочетании с объектом {@link android.app.AlertDialog} +. Если необходимо создать элемент выбора даты или времени, обратитесь к руководству +<a href="{@docRoot}guide/topics/ui/controls/pickers.html">Элементы выбора</a>.</p> + +<p class="note"><strong>Примечание:</strong> +Поскольку класс {@link android.app.DialogFragment} изначально включен в +Android 3.0 (уровень API 11), в настоящем документе описывается использование класса {@link +android.support.v4.app.DialogFragment}, предоставляемого в <a href="{@docRoot}tools/support-library/index.html">Библиотеке поддержки</a>. После добавления этой библиотеки +в приложение появится возможность использовать{@link android.support.v4.app.DialogFragment} и множество других +API на устройствах, работающих на Android 1.6 и выше. Если минимальная версия вашего приложения поддерживает +уровень API 11 и выше, можно использовать фреймворк-версию {@link +android.app.DialogFragment}, но следует иметь в виду, что данный документ ссылается на API + со вспомогательными библиотеками. При использовании вспомогательной библиотеки +необходимо импортировать класс <code>android.support.v4.app.DialogFragment</code> +, а <em>не</em> <code>android.app.DialogFragment</code>.</p> + + +<h2 id="DialogFragment">Создание фрагмента диалогового окна</h2> + +<p>Вы можете реализовать широкие возможности дизайна диалоговых окон, включая создание +пользовательских макетов, а также пути, описанные в руководстве по дизайну <a href="{@docRoot}design/building-blocks/dialogs.html">Диалоговых окон</a> +—путем расширения +{@link android.support.v4.app.DialogFragment} и создания{@link android.app.AlertDialog} +в методе обратного вызова {@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()}.</p> + +<p>Например, имеется базовый класс {@link android.app.AlertDialog}, управляемый в рамках +{@link android.support.v4.app.DialogFragment}:</p> + +<pre> +public class FireMissilesDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.dialog_fire_missiles) + .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // FIRE ZE MISSILES! + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); + // Create the AlertDialog object and return it + return builder.create(); + } +} +</pre> + +<div class="figure" style="width:290px;margin:0 0 0 20px"> +<img src="{@docRoot}images/ui/dialog_buttons.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1.</strong> +Диалоговое окно с сообщением и двумя кнопками действия.</p> +</div> + +<p>Итак, после создания экземпляра этого класса и вызова {@link +android.support.v4.app.DialogFragment#show show()} к этому объекту появляется диалоговое окно, +показанное на рисунке 1.</p> + +<p>В следующем разделе предоставлена более подробная информация об использовании API {@link android.app.AlertDialog.Builder} +для создания диалоговых окон.</p> + +<p>В зависимости от сложности создаваемого диалогового окна возможно реализовать множество других методов +обратного вызова в {@link android.support.v4.app.DialogFragment}, включая все базовые +<a href="{@docRoot}guide/components/fragments.html#Lifecycle">методы жизненных циклов фрагментов</a>. + + + + + +<h2 id="AlertDialog">Создание диалогового окна оповещения</h2> + + +<p>С помощью класса {@link android.app.AlertDialog} создается многообразие решений касательно внешнего вида диалогового окна +, и зачастую этого класса вполне достаточно. +Как показано на рис. 2, диалоговое окно состоит из трех областей:</p> + +<div class="figure" style="width:311px;margin-top:0"> +<img src="{@docRoot}images/ui/dialogs_regions.png" alt="" style="margin-bottom:0" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Макет диалогового окна.</p> +</div> + +<ol> +<li><b>Заголовок</b> + <p>Это дополнительная возможность, которая используется только в случае, если область содержимого + занята подробным сообщением, списком или пользовательским макетом. Если необходимо отобразить + простое сообщение или вопрос (как, например, в диалоге на рисунке 1), заголовок не нужен.</li> +<li><b>Область содержимого</b> + <p>Здесь может отображаться сообщение, список или другой пользовательский макет.</p></li> +<li><b>Кнопки действия</b> + <p>В диалоговом окне не должно содержаться более трех кнопок действия.</p></li> +</ol> + +<p>Класс {@link android.app.AlertDialog.Builder} +предоставляет API, с помощью которых можно создавать {@link android.app.AlertDialog} +с этими видами содержимого, включая пользовательский макет.</p> + +<p>Создание {@link android.app.AlertDialog}:</p> + +<pre> +<b>// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor</b> +AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + +<b>// 2. Chain together various setter methods to set the dialog characteristics</b> +builder.setMessage(R.string.dialog_message) + .setTitle(R.string.dialog_title); + +<b>// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}</b> +AlertDialog dialog = builder.create(); +</pre> + +<p>В следующих главах показано, как определять различные атрибуты диалоговых окон с помощью класса +{@link android.app.AlertDialog.Builder}.</p> + + + + +<h3 id="AddingButtons">Добавление кнопок</h3> + +<p>Для добавления кнопок, изображенных на рисунке 2, +вызывайте методы {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} и +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()}:</p> + +<pre style="clear:right"> +AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); +// Add the buttons +builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User clicked OK button + } + }); +builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); +// Set other dialog properties +... + +// Create the AlertDialog +AlertDialog dialog = builder.create(); +</pre> + +<p>Методы <code>set...Button()</code> предполагают заголовок для кнопки (реализуемый +через <a href="{@docRoot}guide/topics/resources/string-resource.html">строковый ресурс</a>) и +{@link android.content.DialogInterface.OnClickListener}, который определяет действие, +следующее за нажатием кнопки пользователем.</p> + +<p>Реализована возможность добавлять три различных вида кнопок действий:</p> +<dl> + <dt>Положительные</dt> + <dd>Используются для подтверждения и продолжения дейстия (кнопка «ОК»).</dd> + <dt>Отрицательные</dt> + <dd>Используются для отмены действия.</dd> + <dt>Нейтральные</dt> + <dd>Используются в случаях, когда пользователь может не желать продолжить действие, + но при этом необязательно хочет его отменить. Появляется между положительными и отрицательнымиI + кнопками. Примером такого действия может быть «Напомнить позже».</dd> +</dl> + +<p>Можно добавлять только одну кнопку каждого вида в {@link +android.app.AlertDialog}. Это означает, что нельзя использовать более одной «положительной» кнопки.</p> + + + +<div class="figure" style="width:290px;margin:0 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_list.png" alt="" /> +<p class="img-caption"><strong>Рисунок 3.</strong> +Диалоговое окно с заголовком и списком.</p> +</div> + +<h3 id="AddingAList">Добавление списка</h3> + +<p>В API {@link android.app.AlertDialog} реализована возможность использования трех видов списков:</p> +<ul> +<li>Традиционный список с выбором одного варианта</li> +<li>Интерактивный список с выбором одного варианта (переключатели)</li> +<li>Интерактивный список с выбором нескольких вариантов (флажки)</li> +</ul> + +<p>Для создания списка с выбором одного варианта, как на рисунке 3, +используйте метод{@link android.app.AlertDialog.Builder#setItems setItems()}:</p> + +<pre style="clear:right"> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pick_color) + .setItems(R.array.colors_array, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // The 'which' argument contains the index position + // of the selected item + } + }); + return builder.create(); +} +</pre> + +<p>Поскольку список отображается в области содержимого диалогового окна, +диалоговое окно не может показать одновременно сообщение и список, поэтому необходимо задать заголовок +диалогового окна с помощью {@link android.app.AlertDialog.Builder#setTitle setTitle()}. +Для указания элементов списка необходимо вызвать {@link +android.app.AlertDialog.Builder#setItems setItems()}, передающий указатель. +В качестве другого варианта можно указать список с помощью {@link +android.app.AlertDialog.Builder#setAdapter setAdapter()}. Наполнение списка +динамическими данными (например, из базы данных) происходит с помощью {@link android.widget.ListAdapter}.</p> + +<p>Если вы предпочтете реализовать список с помощью {@link android.widget.ListAdapter}, +рекомендуется использовать {@link android.support.v4.content.Loader}, чтобы содержимое загружалось +асинхронно. Подробно этот процесс описан далее в руководстве по +<a href="{@docRoot}guide/topics/ui/declaring-layout.html#AdapterViews">Созданию макетов +с помощью адаптера</a> и <a href="{@docRoot}guide/components/loaders.html">загрузчиков</a> +.</p> + +<p class="note"><strong>Примечание:</strong> По умолчанию нажатие по элементу списка отменяет диалоговое окно, +за исключением случаев, когда используется один из следующих интерактивных списков.</p> + +<div class="figure" style="width:290px;margin:-30px 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_checkboxes.png" /> +<p class="img-caption"><strong>Рисунок 4.</strong> +Список с несколькими вариантами ответов.</p> +</div> + + +<h4 id="Checkboxes">Добавление интерактивного списка с одним или несколькими вариантами ответов</h4> + +<p>Для добавления списка с несколькими вариантами ответов (флажки) или +списка с одним вариантом ответа (переключатели) используйте методы +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} или +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} соответственно.</p> + +<p>Например, таким образом можно создать список с несколькими вариантами ответов, как на +рисунке 4, который сохраняет выбранные +элементы в {@link java.util.ArrayList}:</p> + +<pre style="clear:right"> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + mSelectedItems = new ArrayList(); // Where we track the selected items + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Set the dialog title + builder.setTitle(R.string.pick_toppings) + // Specify the list array, the items to be selected by default (null for none), + // and the listener through which to receive callbacks when items are selected + .setMultiChoiceItems(R.array.toppings, null, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + if (isChecked) { + // If the user checked the item, add it to the selected items + mSelectedItems.add(which); + } else if (mSelectedItems.contains(which)) { + // Else, if the item is already in the array, remove it + mSelectedItems.remove(Integer.valueOf(which)); + } + } + }) + // Set the action buttons + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + // User clicked OK, so save the mSelectedItems results somewhere + // or return them to the component that opened the dialog + ... + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + ... + } + }); + + return builder.create(); +} +</pre> + +<p>Несмотря на то, что и традиционный список, и список с переключателями +предполагают действие по выбору одного элемента, вам необходимо использовать {@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}, чтобы сохранить выбор пользователя. +Это значит, что при повторном открытии диалогового окна будет отображаться текущий выбор пользователя, +а затем создается список с переключателями.</p> + + + + + +<h3 id="CustomLayout">Создание пользовательского макета</h3> + +<div class="figure" style="width:290px;margin:-30px 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_custom.png" alt="" /> +<p class="img-caption"><strong>Рисунок 5</strong>. Пользовательский макет диалогового окна.</p> +</div> + +<p>Если в диалоговом окне необходим пользовательский макет, нужно создать макет и добавить его в +{@link android.app.AlertDialog} путем вызова {@link +android.app.AlertDialog.Builder#setView setView()} в объекте {@link +android.app.AlertDialog.Builder}.</p> + +<p>По умолчанию пользовательский мает заполняет окно диалога, при это все равно можно +использовать методы {@link android.app.AlertDialog.Builder} для добавления кнопок и заголовка.</p> + +<p>В качестве примера на рисунке 5 приведен файл макета для диалогового окна.</p> + +<p style="clear:right" class="code-caption">res/layout/dialog_signin.xml</p> +<pre> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <ImageView + android:src="@drawable/header_logo" + android:layout_width="match_parent" + android:layout_height="64dp" + android:scaleType="center" + android:background="#FFFFBB33" + android:contentDescription="@string/app_name" /> + <EditText + android:id="@+id/username" + android:inputType="textEmailAddress" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="4dp" + android:hint="@string/username" /> + <EditText + android:id="@+id/password" + android:inputType="textPassword" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + android:fontFamily="sans-serif" + android:hint="@string/password"/> +</LinearLayout> +</pre> + +<p class="note"><strong>Совет.</strong> По умолчанию при настройке элемента {@link android.widget.EditText} + для типа ввода {@code "textPassword"} используется семейство шрифтов фиксированной ширины, поэтому +необходимо изменить семейство шрифтов на{@code "sans-serif"}, чтобы в обоих текстовых полях использовались +одинаковые стили шрифта.</p> + +<p>Для применения макета в вашем {@link android.support.v4.app.DialogFragment} +вам понадобится {@link android.view.LayoutInflater} с +{@link android.app.Activity#getLayoutInflater()} и вызов +{@link android.view.LayoutInflater#inflate inflate()}, где первым параметром будет являться +ID ресурса макета, а вторым параметром — исходный вид макета. +Затем можно вызвать{@link android.app.AlertDialog#setView setView()} +для размещения макета в диалоговом окне.</p> + +<pre> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + builder.setView(inflater.inflate(R.layout.dialog_signin, null)) + // Add action buttons + .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + // sign in the user ... + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + LoginDialogFragment.this.getDialog().cancel(); + } + }); + return builder.create(); +} +</pre> + +<div class="note"> +<p><strong>Совет.</strong> Если необходимо пользовательское диалоговое окно, +можно отображать {@link android.app.Activity} в качестве диалога +вместо API {@link android.app.Dialog}. Нужно создать операцию и установить тему +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog} +в элементе манифеста<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<операция>}</a>:</p> + +<pre> +<activity android:theme="@android:style/Theme.Holo.Dialog" > +</pre> +<p>Готово. Операция теперь отображается в диалоговом окне, а не в полноэкранном режиме.</p> +</div> + + + +<h2 id="PassingEvents">Передача событий обратно в основное диалоговое приложение</h2> + +<p>Когда пользователь нажимает одну из кнопок действий в диалоговом окне, либо выбирает элемент из списка, + {@link android.support.v4.app.DialogFragment} может самостоятельно произвести необходимое +действие, однако зачастую вам может понадобиться доставить информацию о событии операции или фрагменту, которые +открыли диалоговое окно. Для этого нобходимо определить интерфейс метода для каждого типа события нажатия. +Затем этот интерфейс применяется в основном компоненте приложения, которое +получает информацию о событиях из диалогового окна.</p> + +<p>Например, {@link android.support.v4.app.DialogFragment} определяет +интерфейс, по который доставляет события обратно в основной компонент операции:</p> + +<pre> +public class NoticeDialogFragment extends DialogFragment { + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + public interface NoticeDialogListener { + public void onDialogPositiveClick(DialogFragment dialog); + public void onDialogNegativeClick(DialogFragment dialog); + } + + // Use this instance of the interface to deliver action events + NoticeDialogListener mListener; + + // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + mListener = (NoticeDialogListener) activity; + } catch (ClassCastException e) { + // The activity doesn't implement the interface, throw exception + throw new ClassCastException(activity.toString() + + " must implement NoticeDialogListener"); + } + } + ... +} +</pre> + +<p>Операция, выполняемая диалоговым окном, создает экземпляр диалогового окна +с помощью конструктора фрагментов диалогового окна и получает события +диалога с помощью реализации интерфейса {@code NoticeDialogListener}:</p> + +<pre> +public class MainActivity extends FragmentActivity + implements NoticeDialogFragment.NoticeDialogListener{ + ... + + public void showNoticeDialog() { + // Create an instance of the dialog fragment and show it + DialogFragment dialog = new NoticeDialogFragment(); + dialog.show(getSupportFragmentManager(), "NoticeDialogFragment"); + } + + // The dialog fragment receives a reference to this Activity through the + // Fragment.onAttach() callback, which it uses to call the following methods + // defined by the NoticeDialogFragment.NoticeDialogListener interface + @Override + public void onDialogPositiveClick(DialogFragment dialog) { + // User touched the dialog's positive button + ... + } + + @Override + public void onDialogNegativeClick(DialogFragment dialog) { + // User touched the dialog's negative button + ... + } +} +</pre> + +<p>Поскольку выполняемая операция реализуется через {@code NoticeDialogListener} +с помощью метода обратного вызова + {@link android.support.v4.app.Fragment#onAttach onAttach()}, во фрагменте диалога могут использоваться +методы обратного вызова интерфейса для доставки событий нажатий к операциям:</p> + +<pre> +public class NoticeDialogFragment extends DialogFragment { + ... + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Build the dialog and set up the button click handlers + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.dialog_fire_missiles) + .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Send the positive button event back to the host activity + mListener.onDialogPositiveClick(NoticeDialogFragment.this); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Send the negative button event back to the host activity + mListener.onDialogNegativeClick(NoticeDialogFragment.this); + } + }); + return builder.create(); + } +} +</pre> + + + +<h2 id="ShowingADialog">Отображение диалогового окна</h2> + +<p>Для отображения диалогового окна необходимо создать экземпляр {@link +android.support.v4.app.DialogFragment} и вызвать {@link android.support.v4.app.DialogFragment#show +show()}, передавая {@link android.support.v4.app.FragmentManager} и наименование тега +для фрагмента диалога.</p> + +<p>Можно получить {@link android.support.v4.app.FragmentManager} путем вызова +{@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} из + {@link android.support.v4.app.FragmentActivity} или {@link +android.support.v4.app.Fragment#getFragmentManager()} из {@link +android.support.v4.app.Fragment}. Пример:</p> + +<pre> +public void confirmFireMissiles() { + DialogFragment newFragment = new FireMissilesDialogFragment(); + newFragment.show(getSupportFragmentManager(), "missiles"); +} +</pre> + +<p>Второй аргумент, {@code "missiles"}, — это уникальное наименование тега, которое система использует для сохранения +и восстановления состояния фрагмента, когда это необходимо. С помощью этого тега также можно управлять +фрагментом путем вызова {@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()}.</p> + + + + +<h2 id="FullscreenDialog">Отображение диалогового окна в полноэкранном режиме или в виде встроенного фрагмента</h2> + +<p>Вам может понадобиться макет пользовательского интерфейса, в котором в некоторых ситуациях часть пользовательского интерфейса должна появляться как диалоговое окно +, отображаемое в полноэкранном режиме либо в виде встроенного фрагмента (возможно, в зависимости от того, +маленький или большой экран у устройства). С помощью класса {@link android.support.v4.app.DialogFragment} +обеспечивается гибкость решения, поскольку он может вести себя как встраиваемый {@link +android.support.v4.app.Fragment}.</p> + +<p>Тем не менее, в этом случае нельзя использовать{@link android.app.AlertDialog.Builder AlertDialog.Builder} +или другие объекты {@link android.app.Dialog} для построения диалогового окна. Если +необходимо сделать {@link android.support.v4.app.DialogFragment} +встраиваемым, нужно определить пользовательский интерфейс диалогового окна в макете методом обратного вызова +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()}.</p> + +<p>В качестве примера приведен {@link android.support.v4.app.DialogFragment}, который появляется либо в виде +диалогового окна, либо в виде встраиваемого фрагмента (используя макет с наименованием <code>purchase_items.xml</code>):</p> + +<pre> +public class CustomDialogFragment extends DialogFragment { + /** The system calls this to get the DialogFragment's layout, regardless + of whether it's being displayed as a dialog or an embedded fragment. */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout to use as dialog or embedded fragment + return inflater.inflate(R.layout.purchase_items, container, false); + } + + /** The system calls this only when creating the layout in a dialog. */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // The only reason you might override this method when using onCreateView() is + // to modify any dialog characteristics. For example, the dialog includes a + // title by default, but your custom layout might not need it. So here you can + // remove the dialog title, but you must call the superclass to get the Dialog. + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } +} +</pre> + +<p>Приведен пример кода, реализующего принятие решения об отображении фргмента в качестве диалогового окна +или полноэкранного пользовательского интерфейса на основе размера экрана:</p> + +<pre> +public void showDialog() { + FragmentManager fragmentManager = getSupportFragmentManager(); + CustomDialogFragment newFragment = new CustomDialogFragment(); + + if (mIsLargeLayout) { + // The device is using a large layout, so show the fragment as a dialog + newFragment.show(fragmentManager, "dialog"); + } else { + // The device is smaller, so show the fragment fullscreen + FragmentTransaction transaction = fragmentManager.beginTransaction(); + // For a little polish, specify a transition animation + transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + // To make it fullscreen, use the 'content' root view as the container + // for the fragment, which is always the root view for the activity + transaction.add(android.R.id.content, newFragment) + .addToBackStack(null).commit(); + } +} +</pre> + +<p>Подробные сведения о выполнении операций с фрагментами приведены в руководстве +<a href="{@docRoot}guide/components/fragments.html">Фрагменты</a>.</p> + +<p>В приведенном примере <code>mIsLargeLayout</code> булеан указывает, должно ли текущее устройство использовать + большой макет приложения (и отображать фрагмент как диалоговое окно, а не +в полноэкранном режиме). Лучшим способом установить такой вид булеана является объявление +<a href="{@docRoot}guide/topics/resources/more-resources.html#Bool">значения булевой переменной</a> +с <a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">альтернативным</a> значением для других размеров экранов. В качестве примера приведены два +варианта булевых ресурсов для различных размеров экранов:</p> + +<p class="code-caption">res/values/bools.xml</p> +<pre> +<!-- Default boolean values --> +<resources> + <bool name="large_layout">false</bool> +</resources> +</pre> + +<p class="code-caption">res/values-large/bools.xml</p> +<pre> +<!-- Large screen boolean values --> +<resources> + <bool name="large_layout">true</bool> +</resources> +</pre> + +<p>Затем можно инизиализировать значение {@code mIsLargeLayout} в течение выполнения метода операции +{@link android.app.Activity#onCreate onCreate()}:</p> + +<pre> +boolean mIsLargeLayout; + +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mIsLargeLayout = getResources().getBoolean(R.bool.large_layout); +} +</pre> + + + +<h3 id="ActivityAsDialog">Отображение операции в качестве диалога на больших экранах</h3> + +<p>Вместо отображения диалогового окна в полноэкранном режиме на экранах малого размера можно +отображать {@link android.app.Activity} в качестве диалогового окна на +экранах большого размера. Выбор зависит от дизайна приложения, но +отображение операции в качестве диалогового окна имеет смысл, когда приложение предназначено для использования на малых +экранах, и необходимо улучшить взаимодейтсвие с ним на планшетах, показывая кратковременные операции +в качестве диалогового окна.</p> + +<p>Для отображения операции в качестве диалогового окна только на больших экранах +необходимо применить тему {@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +к элементу манифеста <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<операция>}</a>:</p> + +<pre> +<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" > +</pre> + +<p>Подробная информация о темах операций приведена в руководстве <a href="{@docRoot}guide/topics/ui/themes.html">Стили и темы</a>.</p> + + + +<h2 id="DismissingADialog">Закрытие диалогового окна</h2> + +<p>Когда пользователь нажимает кнопки, созданные с помощью +{@link android.app.AlertDialog.Builder}, система закрывает диалоговое окно самостоятельно.</p> + +<p>Система также закрывает диалоговое окно, когда пользователь нажимает на элемент списка в диалоговом окне, за исключением +списков с переключателями или флажками. В иных случаях можно вручную закрыть диалоговое окно +путем вызова {@link android.support.v4.app.DialogFragment#dismiss()} в {@link +android.support.v4.app.DialogFragment}.</p> + +<p>В случае, если необходимо произвести определенные +действия после закрытия диалогового окна, можно реализовать метод {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} в {@link +android.support.v4.app.DialogFragment}.</p> + +<p>Также можно <em>отменить</em> диалоговое окно. Это особое событие, возникающее, когда пользователь +покинул диалоговое окно, не завершив задачу. Так происходит, когда пользователь нажимает кнопку +<em>Назад</em>, касается экрана за областью диалогового окна, +либо когда задано {@link android.app.Dialog#cancel()} в {@link +android.app.Dialog} (например, в качестве отклика на нажатие кнопки «Отмена» в диалоговом окне).</p> + +<p>Как показано в примере выше, можно ответить на событие отмены с помощью +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} в классе {@link +android.support.v4.app.DialogFragment}.</p> + +<p class="note"><strong>Примечание:</strong> Система вызывает +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} при каждом событии, +которое вызывается методом обратного вызова{@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Тем не менее, +при вызове{@link android.app.Dialog#dismiss Dialog.dismiss()} или {@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()}, +система вызывает {@link android.support.v4.app.DialogFragment#onDismiss onDismiss()} <em>, а +не</em> {@link android.support.v4.app.DialogFragment#onCancel onCancel()}. Поэтому в общих случаях +вызов{@link android.support.v4.app.DialogFragment#dismiss dismiss()} производится при нажатии пользователем +<em>положительной</em> кнопки в диалоговом окне, а после диалоговое окно закрывается.</p> + + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/menus.jd b/docs/html-intl/intl/ru/guide/topics/ui/menus.jd new file mode 100644 index 000000000000..2f3ce1eb9580 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=Меню +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> +<ol> + <li><a href="#xml">Определение меню в файле XML</a></li> + <li><a href="#options-menu">Создание меню параметров</a> + <ol> + <li><a href="#RespondingOptionsMenu">Обработка нажатий</a></li> + <li><a href="#ChangingTheMenu">Изменение пунктов меню во время выполнения</a></li> + </ol> + </li> + <li><a href="#context-menu">Создание контекстного меню</a> + <ol> + <li><a href="#FloatingContextMenu">Создание плавающего контекстного меню</a></li> + <li><a href="#CAB">Использование режима контекстных действий</a></li> + </ol> + </li> + <li><a href="#PopupMenu">Создание всплывающего меню</a> + <ol> + <li><a href="#PopupEvents">Обработка нажатий</a></li> + </ol> + </li> + <li><a href="#groups">Создание групп меню</a> + <ol> + <li><a href="#checkable">Использование пунктов меню, которые можно пометить</a></li> + </ol> + </li> + <li><a href="#intents">Добавление пунктов меню на основе объектов Intent</a> + <ol> + <li><a href="#AllowingToAdd">Предоставление возможности добавить свою операцию в другие меню</a></li> + </ol> + </li> +</ol> + + <h2>Основные классы</h2> + <ol> + <li>{@link android.view.Menu}</li> + <li>{@link android.view.MenuItem}</li> + <li>{@link android.view.ContextMenu}</li> + <li>{@link android.view.ActionMode}</li> + </ol> + + <h2>См. также</h2> + <ol> + <li><a href="{@docRoot}guide/topics/ui/actionbar.html">Строка действий</a></li> + <li><a href="{@docRoot}guide/topics/resources/menu-resource.html">Ресурс меню</a></li> + <li><a href="http://android-developers.blogspot.com/2012/01/say-goodbye-to-menu-button.html">Попрощайтесь +с кнопкой "Меню"</a></li> + </ol> +</div> +</div> + +<p>Меню являются стандартным компонентом пользовательского интерфейса в приложениях многих типов. Для обеспечения привычной +и единообразной технологии работы с приложением следует представлять действия пользователя и другие варианты выбора в своих операциях +с помощью API-интерфейсов класса {@link android.view.Menu}.</p> + +<p>Начиная с версии Android 3.0 (уровень API 11) в устройствах, работающих под управлением Android, +наличие отдельной кнопки <em>Меню</em> больше не требуется. С учетом этого изменения приложения для Android должны перестать +зависеть от традиционной панели меню из 6 пунктов. Вместо нее в них должна быть строка действий с часто используемыми +действиями пользователя.</p> + +<p>Несмотря на то что оформление и поведение некоторых пунктов меню изменились, семантика для определения +набора действий и вариантов по-прежнему основана на API-интерфейсах класса {@link android.view.Menu}. В этом +руководстве рассказывается, как создавать три основополагающих типа меню или представлений действий в системе +Android всех версий:</p> + +<dl> + <dt><strong>Меню параметров и строка действий</strong></dt> + <dd>Пункты <a href="#options-menu">меню параметров</a> представляют собой основные варианты выбора действий в пределах +операции. Именно здесь следует размещать действия, которые затрагивают приложение в целом, например: +"Поиск", "Составить сообщение эл. почты" и "Настройки". + <p>При разработке приложений для версии Android 2.3 или более ранних версий пользователи могут +открыть панель меню параметров нажатием кнопки <em>Меню</em>.</p> + <p>В версии Android 3.0 и последующих версиях пункты меню параметров размещаются в <a href="{@docRoot}guide/topics/ui/actionbar.html">строке действий</a> в виде сочетания отображаемых на экране вариантов +действий и раскрывающегося списка дополнительных вариантов выбора. Начиная с Android 3.0 кнопка <em>Меню</em> больше не используется (на некоторых +устройствах +ее нет), поэтому для предоставления доступа к действиям и другим вариантам выбора вам следует перейти к использованию +строки действий.</p> + <p>См. раздел <a href="#options-menu">Создание меню параметров</a></p> + </dd> + + <dt><strong>Контекстное меню и режим контекстных действий</strong></dt> + + <dd>Контекстное меню ― это <a href="#FloatingContextMenu">плавающее меню</a>, которое открывается, когда +пользователь длительно нажимает на элемент. В нем содержатся действия, которые затрагивают выбранный контент или +контекстный кадр. + <p>При разработке приложения для версии Android 3.0 или выше вместо этого для обеспечения действий с выбранным контентом следует использовать <a href="#CAB">режим контекстных действий</a>. В этом режиме +в строке, расположенной вверху экрана, отображаются пункты действий, затрагивающие выбранный контент, причем пользователь может +выбрать сразу несколько элементов.</p> + <p>См. раздел <a href="#context-menu">Создание контекстного меню</a></p> +</dd> + + <dt><strong>Всплывающее меню</strong></dt> + <dd>Во всплывающем меню отображается вертикальный список пунктов, который привязан к представлению, +вызвавшему меню. Он хорошо подходит для предоставления возможности дополнительных вариантов действий, относящихся к определенному контенту или +для выдачи вариантов для второй части команды. Действия во всплывающем меню +<strong>не</strong> должны напрямую затрагивать соответствующий контент — для этого предназначены контекстные +действия. Всплывающее меню предназначено для расширенных действий, относящихся к областям контента в вашей +операции. + <p>См. раздел <a href="#PopupMenu">Создание всплывающего меню</a></p> +</dd> +</dl> + + + +<h2 id="xml">Определение меню в файле XML</h2> + +<p>Для определения пунктов меню всех типов в Android используется стандартный формат XML. +Вместо того чтобы создавать меню в коде своей операции, определять меню и все его пункты следует в + <a href="{@docRoot}guide/topics/resources/menu-resource.html">ресурсе меню</a> формата XML. После этого +ресурс меню можно будет загружать как объект {@link android.view.Menu} в свои операции или +фрагменты.</p> + +<p>Использовать ресурсы меню рекомендуется по нескольким причинам:</p> +<ul> + <li>в XML проще визуализировать структуру меню;</li> + <li>это позволяет отделить контент для меню от кода, определяющего работу приложения;</li> + <li>это позволяет создавать альтернативные варианты меню для разных версий платформы, +размеров экрана и других конфигураций путем использования структуры <a href="{@docRoot}guide/topics/resources/index.html">ресурсов приложения</a>.</li> +</ul> + +<p>Чтобы определить меню, создайте файл XML в папке <code>res/menu/</code> +вашего проекта и постройте меню со следующими элементами:</p> +<dl> + <dt><code><menu></code></dt> + <dd>Определяет класс {@link android.view.Menu}, который является контейнером для пунктов меню. Элемент +<code><menu></code> должен быть корневым узлом файла, в котором может находиться один или несколько элементов +<code><item></code> и <code><group></code>.</dd> + + <dt><code><item></code></dt> + <dd>Создает класс {@link android.view.MenuItem}, который представляет один пункт меню. Этот +элемент может содержать вложенный элемент <code><menu></code> для создания вложенных меню.</dd> + + <dt><code><group></code></dt> + <dd>Необязательный, невидимый контейнер для элементов {@code <item>}. Он позволяет +разделять пункты меню на категории и назначать им одинаковые свойства, такие как активное состояние и видимость. Подробные +сведения изложены в разделе <a href="#groups">Создание групп меню</a>.</dd> +</dl> + + +<p>Вот пример меню с именем <code>game_menu.xml</code>:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/new_game" + android:icon="@drawable/ic_new_game" + android:title="@string/new_game" + android:showAsAction="ifRoom"/> + <item android:id="@+id/help" + android:icon="@drawable/ic_help" + android:title="@string/help" /> +</menu> +</pre> + +<p>Элемент <code><item></code> поддерживает несколько атрибутов, с помощью которых можно определить внешний вид и +поведение пункта меню. Пункты приведенного выше меню имеют следующие атрибуты:</p> + +<dl> + <dt>{@code android:id}</dt> + <dd>Идентификатор ресурса, который является уникальным для этого пункта, что позволяет приложению распознавать пункт, +когда его выбирает пользователь.</dd> + <dt>{@code android:icon}</dt> + <dd>Ссылка на графический элемент, который будет использоваться в качестве значка пункта меню.</dd> + <dt>{@code android:title}</dt> + <dd>Ссылка на строку, которая будет использоваться в качестве названия пункта меню.</dd> + <dt>{@code android:showAsAction}</dt> + <dd>Указывает, когда и как этот пункт должен отображаться в <a href="{@docRoot}guide/topics/ui/actionbar.html">строке действий</a>.</dd> +</dl> + +<p>Это самые важные атрибуты, которые следует использовать, но есть также множество других атрибутов. +Сведения обо всех поддерживаемых атрибутах см. в документе <a href="{@docRoot}guide/topics/resources/menu-resource.html">Ресурс меню</a>.</p> + +<p>К пункту любого меню (кроме вложенного меню) можно прикрепить вложенное меню, добавив элемент {@code <menu>} +в качестве дочернего элемента {@code <item>}. Вложенные меню полезны, когда в приложении имеется множество +функций, которые можно разделить на категории подобно строке меню приложения для ПК ("Файл", +"Правка", "Вид" и т. д.). Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/file" + android:title="@string/file" > + <!-- "file" submenu --> + <menu> + <item android:id="@+id/create_new" + android:title="@string/create_new" /> + <item android:id="@+id/open" + android:title="@string/open" /> + </menu> + </item> +</menu> +</pre> + +<p>Для использования меню в операции необходимо загрузить ресурс меню (преобразовать ресурс XML +в программируемый объект) с помощью метода {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()}. В приведенных далее разделах рассказывается, как загружать меню +каждого типа.</p> + + + +<h2 id="options-menu">Создание меню параметров</h2> + +<div class="figure" style="width:200px;margin:0"> + <img src="{@docRoot}images/options_menu.png" height="333" alt="" /> + <p class="img-caption"><strong>Рисунок 1.</strong> Меню параметров в +браузере на Android 2.3.</p> +</div> + +<p>В меню параметров следует размещать действия и другие варианты выбора, которые имеют отношение к +контексту текущей операции, например: "Поиск", "Составить сообщение эл. почты" и "Настройки".</p> + +<p>Место, где отображаются на экране пункты вашего меню параметров, определяется версией платформы, для которой +разработано приложение.</p> + +<ul> + <li>Если приложение написано для версии <strong>Android 2.3.x (уровень API 10) или +более ранней</strong>, содержимое вашего меню параметров отображается внизу экрана, когда пользователь +нажимает кнопку <em>Меню</em>, как показано на рисунке 1. Когда меню открывается, первой видимой частью является меню +значков, +в котором имеется шесть пунктов. Если в вашем меню больше шести пунктов, система Android разместит +шестой и остальные пункты в дополнительном меню, которое пользователь может открыть, выбрав вариант + <em>Еще</em>.</li> + + <li>Если приложение предназначено для версии <strong>Android 3.0 (уровень API 11) и +более поздних</strong>, пункты меню параметров будут отображаться в <a href="{@docRoot}guide/topics/ui/actionbar.html">строке действий</a>. По умолчанию система +размещает все действия на панели дополнительных вариантов, которые пользователь может открыть с помощью значка дополнительных действий, расположенного +с правой стороны строки действий (либо нажатием кнопки <em>Меню</em>, если на устройстве есть такая кнопка). Чтобы +обеспечить +быстрый доступ к важным действиям, можно принудительно разместить несколько пунктов меню в строке действий, добавив +{@code android:showAsAction="ifRoom"} к соответствующим элементам {@code <item>} (см. рисунок +2). <p>Подробные сведения о пунктах действий и других особенностях строки действий см. в руководстве <a href="{@docRoot}guide/topics/ui/actionbar.html">Строка действий</a>. </p> +<p class="note"><strong>Примечание.</strong> Даже если ваше приложение <em>не</em> предназначено для работы в версии Android 3.0 или +более поздней, можно создать собственный макет со строкой действий, чтобы реализовать похожий эффект. Пример того, как можно +поддерживать работу строки действий в старых версиях Android см. в образце кода, иллюстрирующем + <a href="{@docRoot}resources/samples/ActionBarCompat/index.html">Совместимость строки действий</a>.</p> +</li> +</ul> + +<img src="{@docRoot}images/ui/actionbar.png" alt="" /> +<p class="img-caption"><strong>Рисунок 2.</strong> Строка действий из приложения <a href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a>, содержащая +вкладки навигации и пункт включения камеры (а также кнопку открытия дополнительной панели действий).</p> + +<p>Объявлять пункты меню параметров можно либо из подкласса {@link android.app.Activity}, +либо из подкласса {@link android.app.Fragment}. Если и ваша операция, и фрагменты +объявляют пункты меню параметров, в пользовательском интерфейсе они объединяются. Сначала отображаются пункты +операции, а за ними следуют пункты фрагментов в том порядке, в котором каждый фрагмент добавляется в +операцию. При необходимости можно изменить порядок следования пунктов меню с помощью атрибута {@code android:orderInCategory}, +указываемого в каждом {@code <item>}, который требуется переместить.</p> + +<p>Чтобы указать меню параметров для операции, переопределите {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} (фрагменты предоставляют собственный обратный +вызов {@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()}). В этом +методе можно загрузить собственный ресурс меню (<a href="#xml">определенный в XML</a>) в класс {@link +android.view.Menu}, имеющийся в обратном вызове. Например:</p> + +<pre> +@Override +public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = {@link android.app.Activity#getMenuInflater()}; + inflater.inflate(R.menu.game_menu, menu); + return true; +} +</pre> + +<p>Пункты меню также можно добавлять с помощью {@link android.view.Menu#add(int,int,int,int) +add()}, а получать их с помощью {@link android.view.Menu#findItem findItem()} для пересмотра их +свойств с помощью API-интерфейсов {@link android.view.MenuItem}.</p> + +<p>Если ваше приложение предназначено для версии Android 2.3.x или более ранней, система вызывает метод {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} для создания меню параметров, +когда пользователь открывает это меню впервые. Если приложение предназначено для версии Android 3.0 и или более поздней, система +вызывает метод{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} при +запуске операции, чтобы отобразить пункты в строке действий.</p> + + + +<h3 id="RespondingOptionsMenu">Обработка нажатий</h3> + +<p>Когда пользователь выбирает пункт меню параметров (в том числе пункты действий из строки действий), +система вызывает метод {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} вашей операции. Этот метод передает выбранный класс {@link android.view.MenuItem}. Идентифицировать +пункт меню можно, вызвав метод {@link android.view.MenuItem#getItemId()}, который возвращает уникальный +идентификатор пункта меню (определенный атрибутом {@code android:id} из ресурса меню или +целым числом, переданным методу {@link android.view.Menu#add(int,int,int,int) add()}). Этот идентификатор +можно сопоставить с известными пунктами меню, чтобы выполнить соответствующее действие. Например:</p> + +<pre> +@Override +public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.new_game: + newGame(); + return true; + case R.id.help: + showHelp(); + return true; + default: + return super.onOptionsItemSelected(item); + } +} +</pre> + +<p>Когда пункт меню успешно обработан, возвращается {@code true}. Если пункт меню не +обрабатывается, следует вызвать реализацию суперкласса {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} (реализация +по умолчанию возвращает значение false).</p> + +<p>Если в вашей операции имеются фрагменты, система сначала вызовет метод {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} для операции, а затем +будет вызывать этот метод для каждого фрагмента (в том порядке, в котором они были добавлены), пока он не возвратит +значение{@code true} или не закончатся фрагменты.</p> + +<p class="note"><strong>Совет.</strong> В Android 3.0 появилась возможность определять в XML поведение +при нажатии для пунктов меню с помощью атрибута {@code android:onClick}. Значением этого +атрибута должно быть имя метода, определенное операцией с помощью меню. Этот метод +должен быть общедоступным и принимать один параметр {@link android.view.MenuItem}, — когда система +вызывает этот метод, она передает ему выбранный пункт меню. Подробные сведения и пример см. в документе <a href="{@docRoot}guide/topics/resources/menu-resource.html">Ресурс меню</a>.</p> + +<p class="note"><strong>Совет.</strong> Если в приложении предусмотрено несколько операций и +в некоторых из них имеются одинаковые меню параметров, рассмотрите возможность создания +операции, которая будет использовать исключительно методы {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} и {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()}. Затем распространите этот класс на все операции, у которых должно быть +одинаковое меню параметров. Таким образом можно управлять одним набором кода для обработки действий +меню, а каждый класс-потомок при этом будет наследовать режимы работы меню. +Если требуется добавить пункты меню в одну из операций-потомков, +переопределите метод {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} в этой операции. Вызовите метод {@code super.onCreateOptionsMenu(menu)}, с тем чтобы +создать первоначальные пункты меню, а затем добавьте новые пункты меню с помощью метода {@link +android.view.Menu#add(int,int,int,int) menu.add()}. Также можно переопределять режимы работы +суперкласса для отдельных пунктов меню.</p> + + +<h3 id="ChangingTheMenu">Изменение пунктов меню во время выполнения</h3> + +<p>После того как система вызовет метод {@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}, она сохранит заполненный вами экземпляр {@link android.view.Menu} и +будет вызывать метод {@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +,только если меню по каким-то причинам станет некорректным. Однако метод {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} следует использовать только для создания начального +состояния меню, а не для внесения в него изменений в течение жизненного цикла операции.</p> + +<p>Если вам требуется изменять меню параметров в зависимости от +событий, которые возникают в течение жизненного цикла операции, сделать это можно в +методе{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}. Этот +метод передает объект {@link android.view.Menu} в том виде, в котором он в данный момент существует. Его-то и можно изменить +путем, например, добавления, удаления или отключения пунктов меню. (Фрагменты также предоставляют обратный вызов {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()}.)</p> + +<p>В версии Android 2.3.x или более ранней система вызывает метод {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()} каждый раз, когда пользователь открывает меню параметров (нажимает кнопку <em>Меню</em> +).</p> + +<p>В версии Android 3.0 и последующих версиях считается, что меню параметров всегда открыто, когда пункты меню +приведены в строке действий. Когда возникает событие и требуется обновить меню, следует +вызвать метод {@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()}, чтобы запросить у +системы вызов метода {@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}.</p> + +<p class="note"><strong>Примечание.</strong> +Никогда не следует изменять пункты меню параметров с учетом класса {@link android.view.View}, действующего +в данный момент. В сенсорном режиме (когда пользователь не использует трекбол или кнопки направления движения) фокус +не может переводиться на представления, поэтому никогда не следует использовать фокус в качестве основы для изменения +пунктов меню параметров. Если вы желаете предоставить пункты меню, которые зависят от контекста {@link +android.view.View}, используйте <a href="#context-menu">контекстное меню</a>.</p> + + + + +<h2 id="context-menu">Создание контекстного меню</h2> + +<div class="figure" style="width:420px;margin-top:-1em"> + <img src="{@docRoot}images/ui/menu-context.png" alt="" /> + <p class="img-caption"><strong>Рисунок 3</strong>. Снимки экрана с плавающим контекстным меню (слева) +и строкой контекстных действий (справа).</p> +</div> + +<p>В контекстном меню содержатся действия, которые затрагивают определенный элемент или контекстный кадр в пользовательском интерфейсе. Контекстное +меню можно создать для любого представления, но чаще всего они используются для элементов из {@link +android.widget.ListView}, {@link android.widget.GridView} или других групп представлений, в которых +пользователь может выполнять действия непосредственно с каждым элементом.</p> + +<p>Существует два способа предоставления возможности контекстных действий:</p> +<ul> + <li>В <a href="#FloatingContextMenu">плавающем контекстном меню</a>. Меню отображается в виде +плавающего списка пунктов меню (наподобие диалогового окна), когда пользователь длительно нажимает на экран (нажимает и +удерживает нажатым) в представлении, которое объявляет поддержку контекстного меню. Пользователи могут каждый раз выполнять контекстное +действие только с одним элементом.</li> + + <li>В <a href="#CAB">режиме контекстных действий</a>. Этот режим является системной реализацией +{@link android.view.ActionMode}, которая отображает <em>строку контекстных действий</em> вверху +экрана с пунктами действий, которые затрагивают выбранные элементы. Когда этот режим активен, пользователи +могут одновременно выполнять действие с несколькими элементами (если это допускается приложением).</li> +</ul> + +<p class="note"><strong>Примечание.</strong> Режим контекстных действий поддерживается в версии Android 3.0 (уровень API +11) и последующих версиях. Если этот режим предусмотрен, именно его рекомендуется использовать для отображения контекстных +действий. Если ваше приложение поддерживает версии ниже 3.0, то для этих устройств следует вернуться к плавающему +контекстному меню.</p> + + +<h3 id="FloatingContextMenu">Создание плавающего контекстного меню</h3> + +<p>Программирование плавающего контекстного меню</p> +<ol> + <li>Зарегистрируйте класс {@link android.view.View}, с которым следует связать контекстное меню, +вызвав метод {@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()} и передав +ему {@link android.view.View}. + <p>Если операция использует {@link android.widget.ListView} или {@link android.widget.GridView} и +требуется, чтобы каждый элемент предоставлял одинаковое контекстное меню, зарегистрируйте все элементы для контекстного меню, +передав {@link android.widget.ListView} или {@link android.widget.GridView} методу {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()}.</p> +</li> + + <li>Реализуйте метод {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} в + {@link android.app.Activity} или {@link android.app.Fragment}. + <p>Когда зарегистрированное представление примет событие длительного нажатия, система вызовет ваш метод {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +. Именно здесь определяются пункты меню. Делается это обычно путем загрузки ресурса меню. Например: +</p> +<pre> +@Override +public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); +} +</pre> + +<p>{@link android.view.MenuInflater} позволяет загружать контекстное меню из <a href="{@docRoot}guide/topics/resources/menu-resource.html">ресурса меню</a>. В число параметров метода +обратного вызова входят {@link android.view.View}, +выбранный пользователем, и объект{@link android.view.ContextMenu.ContextMenuInfo}, который предоставляет +дополнительную информацию о выбранном элементе. Если в вашей операции есть несколько представлений и все они предоставляют +разные контекстные меню, то с помощью этих параметров можно определять, какое контекстное меню +загружать.</p> +</li> + +<li>Реализуйте метод {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}. + <p>Когда пользователь выбирает пункт меню, система вызывает этот метод, с тем чтобы вы могли выполнить +соответствующее действие. Например:</p> + +<pre> +@Override +public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case R.id.edit: + editNote(info.id); + return true; + case R.id.delete: + deleteNote(info.id); + return true; + default: + return super.onContextItemSelected(item); + } +} +</pre> + +<p>Метод {@link android.view.MenuItem#getItemId()} запрашивает идентификатор для +выбранного пункта меню. Идентификаторы должны быть назначены каждому пункту меню в файле XML с помощью атрибута {@code +android:id}, как описано в разделе <a href="#xml">Определение меню в файле +XML</a>.</p> + +<p>Когда пункт меню успешно обработан, возвращается {@code true}. Если пункт меню не +обрабатывается, следует передать его реализации суперкласса. Если в вашей операции есть фрагменты, +то она получит этот обратный вызов первой. Вызывая суперкласс, когда пункт меню не обрабатывается, система +передает событие соответствующему методу обратного вызова в каждом фрагменте по одному (в порядке +добавления каждого фрагмента), пока не будет возвращено значение {@code true} или {@code false}. (Реализация +{@link android.app.Activity} и {@code android.app.Fragment} по умолчанию возвращает {@code +false}, поэтому, когда событие не обрабатывается, следует всегда вызывать суперкласс.)</p> +</li> +</ol> + + +<h3 id="CAB">Использование режима контекстных действий</h3> + +<p>Режим контекстных действий представляет собой системную реализацию класса {@link android.view.ActionMode}, которая +направляет пользователя на выполнение контекстных действий при взаимодействии с приложением. Когда +пользователь использует этот режим, выбирая элемент, вверху экрана открывается <em>строка контекстных действий</em>, +содержащая действия, которые пользователь может выполнить с выбранными в данный момент элементами. В этом режиме +пользователь может выбирать несколько элементов (если это допускается приложением), снимать выделения с элементов и продолжать +навигацию в операции (в тех пределах, в которых это поддерживается приложением). Режим контекстных действий отключается, +а строка контекстных действий исчезает, когда пользователь снимет выделение со всех элементов, нажмет кнопку НАЗАД +или выберет действие <em>Готово</em>, расположенное с левой стороны строки.</p> + +<p class="note"><strong>Примечание.</strong> Строка контекстных действий не обязательно бывает +связана со <a href="{@docRoot}guide/topics/ui/actionbar.html">строкой действий</a>. Они работают +независимо друг от друга, даже несмотря на то, что визуально строка контекстных действий занимает положение +строки действий.</p> + +<p>Если вы разрабатываете приложение для версии Android 3.0 (уровень API 11) или последующих версий, то +обычно вместо <a href="#FloatingContextMenu">плавающего контекстного меню</a> вам следует использовать контекстные действия.</p> + +<p>Для представлений, которые предоставляют возможность контекстные действия, обычно следует вызывать режим контекстных действий +при возникновении одного из двух (или сразу обоих) событий:</p> +<ul> + <li>пользователь длительно нажимает в представлении;</li> + <li>пользователь устанавливает флажок или выбирает другой подобный компонент пользовательского интерфейса в представлении.</li> +</ul> + +<p>То, каким образом ваше представление вызывает режим контекстных действий и определяет поведение каждого +действия, зависит от вас. Есть два базовых варианта:</p> +<ul> + <li>для контекстных действий в отдельных, произвольных представлениях;</li> + <li>для пакетных контекстных действий с группами элементов в {@link +android.widget.ListView} или {@link android.widget.GridView} (что позволяет пользователю выбирать по несколько +элементов и выполнять действие с ними всеми).</li> +</ul> + +<p>В следующих разделах описывается конфигурация, необходимая для каждого из этих вариантов.</p> + + +<h4 id="CABforViews">Включение режима контекстных действий для отдельных представлений</h4> + +<p>Если вам требуется вызывать режим контекстных действий, только когда пользователь выбирает определенные +представления, то вам следует:</p> +<ol> + <li>Реализовать интерфейс {@link android.view.ActionMode.Callback}. В его методах обратного вызова вы +можете указать действия для строки контекстных действий, реагировать на нажатия пунктов действий и +обрабатывать другие события жизненного цикла для режима действий.</li> + <li>Вызывайте {@link android.app.Activity#startActionMode startActionMode()}, когда требуется показать +строку (например, когда пользователь выполняет длительное нажатие представления).</li> +</ol> + +<p>Например:</p> + +<ol> + <li>Реализуйте интерфейс {@link android.view.ActionMode.Callback ActionMode.Callback}: +<pre> +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + // Called when the action mode is created; startActionMode() was called + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate a menu resource providing context menu items + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); + return true; + } + + // Called each time the action mode is shown. Always called after onCreateActionMode, but + // may be called multiple times if the mode is invalidated. + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; // Return false if nothing is done + } + + // Called when the user selects a contextual menu item + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + shareCurrentItem(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + // Called when the user exits the action mode + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + } +}; +</pre> + +<p>Обратите внимание, что эти обратные вызовы событий почти точно такие же, как и обратные вызовы для <a href="#options-menu">меню параметров</a>. Отличаются они только тем, что каждый из них также передает объект {@link +android.view.ActionMode}, связанный с событием. С помощью API-интерфейсов {@link +android.view.ActionMode} можно вносить различные изменения в CAB, например, указывать другой заголовок и +подзаголовок с помощью {@link android.view.ActionMode#setTitle setTitle()} и {@link +android.view.ActionMode#setSubtitle setSubtitle()} (удобно для указания количества выбранных +элементов).</p> + +<p>Также обратите внимание, что приведенный выше образец кода задает для переменной {@code mActionMode} значение null, когда +режим действия прекращает свое существование. Далее вы узнаете, каким образом он инициализируется и чем может быть +полезно сохранение составной переменной в операции или фрагменте.</p> +</li> + + <li>Для включения режима контекстных действий, когда это необходимо, +например, в ответ на длительное нажатие {@link +android.view.View}, вызывайте {@link android.app.Activity#startActionMode startActionMode()}:</p> + +<pre> +someView.setOnLongClickListener(new View.OnLongClickListener() { + // Called when the user long-clicks on someView + public boolean onLongClick(View view) { + if (mActionMode != null) { + return false; + } + + // Start the CAB using the ActionMode.Callback defined above + mActionMode = getActivity().startActionMode(mActionModeCallback); + view.setSelected(true); + return true; + } +}); +</pre> + +<p>При вызове метода {@link android.app.Activity#startActionMode startActionMode()} система возвращает +созданный класс {@link android.view.ActionMode}. Сохранив его в составной переменной, вы сможете +вносить изменения в строку контекстных действий в ответ на другие события. В приведенном выше образце кода +{@link android.view.ActionMode} используется для того, чтобы экземпляр {@link android.view.ActionMode} +не создавался повторно, если он уже активен. Достигается это путем проверки, имеет ли элемент значение null перед запуском +режима действий.</p> +</li> +</ol> + + + +<h4 id="CABforListView">Включение пакетных контекстных действий в ListView или GridView</h4> + +<p>Если при наличии набора элементов в {@link android.widget.ListView} или {@link +android.widget.GridView} (либо другом расширении {@link android.widget.AbsListView}) требуется +разрешить пользователям выполнять пакетные действия, следует:</p> + +<ul> + <li>реализовать интерфейс {@link android.widget.AbsListView.MultiChoiceModeListener} и задать его +для группы представлений с помощью метода {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()}; в методах обратного вызова приемника событий вы можете указывать действия +для строки контекстных действий, реагировать на нажатия пунктов действий и обрабатывать другие обратные вызовы, +унаследованные от интерфейса {@link android.view.ActionMode.Callback};</li> + + <li>вызвать метод {@link android.widget.AbsListView#setChoiceMode setChoiceMode()} с аргументом {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL}.</li> +</ul> + +<p>Например:</p> + +<pre> +ListView listView = getListView(); +listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); +listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) { + // Here you can do something when items are selected/de-selected, + // such as update the title in the CAB + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // Respond to clicks on the actions in the CAB + switch (item.getItemId()) { + case R.id.menu_delete: + deleteSelectedItems(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate the menu for the CAB + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context, menu); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Here you can make any necessary updates to the activity when + // the CAB is removed. By default, selected items are deselected/unchecked. + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Here you can perform updates to the CAB due to + // an {@link android.view.ActionMode#invalidate} request + return false; + } +}); +</pre> + +<p>Готово. Теперь, когда пользователь выберет элемент с помощью длительного нажатия, система вызовет метод {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +и отобразит строку контекстных действий с указанными действиями. Пока строка +контекстных действий отображается, пользователи могут выбирать дополнительные элементы.</p> + +<p>В некоторых случаях, когда контекстные действия содержат общие пункты, можно +добавить флажок или другой подобный элемент пользовательского интерфейса, с помощью которого пользователи могут выбирать пункты, поскольку они +могут не обнаружить варианта с длительным нажатием. Когда пользователь устанавливает флажок, можно +вызывать режим контекстных действий, переводя соответствующий элемент списка в выбранное +состояние с помощью {@link android.widget.AbsListView#setItemChecked setItemChecked()}.</p> + + + + +<h2 id="PopupMenu">Создание всплывающего меню</h2> + +<div class="figure" style="width:220px"> +<img src="{@docRoot}images/ui/popupmenu.png" alt="" /> +<p><strong>Рисунок 4</strong>. Всплывающее меню в приложении Gmail, привязанное к расположенной вверху справа кнопке открытия панели +дополнительных пунктов.</p> +</div> + +<p>{@link android.widget.PopupMenu} является модальным меню, привязанным к {@link android.view.View}. +Оно отображается ниже представления, к которому привязано, если там есть место, либо поверх него. Варианты использования:</p> +<ul> + <li>Предоставление меню с дополнительными пунктами для действий, которые <em>относятся к</em> определенному контенту (например, +к заголовкам сообщений Gmail, показанным на рисунке 4). + <p class="note"><strong>Примечание.</strong> Оно отличается от контекстного меню, которое +обычно используется для действий, <em>затрагивающих</em> выбранный контент. Для действий, которые затрагивают выбранный +контент, используйте <a href="#CAB">режим контекстных действий</a> или <a href="#FloatingContextMenu">плавающее контекстное меню</a>.</p></li> + <li>Предоставление второй части командной последовательности (например, кнопки, обозначенной как "Добавить", +которая открывает всплывающее меню с различными вариантами добавления);</li> + <li>Предоставление раскрывающегося меню наподобие {@link android.widget.Spinner}, которое не сохраняет +постоянное выделение.</li> +</ul> + + +<p class="note"><strong>Примечание.</strong> Использование класса {@link android.widget.PopupMenu} поддерживается на уровне API + 11 и выше.</p> + +<p>Если для <a href="#xml">определения меню используется XML</a>, вот каким образом можно показать всплывающее меню:</p> +<ol> + <li>Создайте экземпляр класса {@link android.widget.PopupMenu} с помощью его конструктора, принимающий +текущие {@link android.content.Context} и {@link android.view.View} приложения, к которым +должно быть привязано меню.</li> + <li>С помощью {@link android.view.MenuInflater} загрузите свой ресурс меню в объект {@link +android.view.Menu}, возвращенный методом {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()}. На API уровня 14 и выше вместо этого можно использовать +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}.</li> + <li>Вызовите метод {@link android.widget.PopupMenu#show() PopupMenu.show()}.</li> +</ol> + +<p>Например, вот кнопка с атрибутом {@link android.R.attr#onClick android:onClick}, +которая показывает всплывающее меню:</p> + +<pre> +<ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_overflow_holo_dark" + android:contentDescription="@string/descr_overflow_button" + android:onClick="showPopup" /> +</pre> + +<p>После этого операция сможет показать вот такое всплывающее меню:</p> + +<pre> +public void showPopup(View v) { + PopupMenu popup = new PopupMenu(this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.actions, popup.getMenu()); + popup.show(); +} +</pre> + +<p>На API уровня 14 и выше можно объединить две строки, которые загружают меню, с помощью {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()}.</p> + +<p>Меню закрывается, когда пользователь выбирает один из пунктов или касается экрана за пределами области +меню. Прослушивать событие закрытия меню можно с помощью {@link +android.widget.PopupMenu.OnDismissListener}.</p> + +<h3 id="PopupEvents">Обработка нажатий</h3> + +<p>Для выполнения +действия, когда пользователь выбирает пункт меню, необходимо реализовать интерфейс {@link +android.widget.PopupMenu.OnMenuItemClickListener} и зарегистрировать его в своем {@link +android.widget.PopupMenu}, вызвав метод {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()}. Когда пользователь выбирает пункт меню, система выполняет обратный вызов {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} в +вашем интерфейсе.</p> + +<p>Например:</p> + +<pre> +public void showMenu(View v) { + PopupMenu popup = new PopupMenu(this, v); + + // This activity implements OnMenuItemClickListener + popup.setOnMenuItemClickListener(this); + popup.inflate(R.menu.actions); + popup.show(); +} + +@Override +public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.archive: + archive(item); + return true; + case R.id.delete: + delete(item); + return true; + default: + return false; + } +} +</pre> + + +<h2 id="groups">Создание групп меню</h2> + +<p>Группа меню ― это набор пунктов меню с рядом общих характеристик. С помощью группы +можно:</p> +<ul> + <li>показывать или скрывать все пункты с помощью {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()};</li> + <li>включать или отключать все пункты с помощью {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()};</li> + <li>Указывать, можно ли помечать все пункты, с помощью {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()}.</li> +</ul> + +<p>Для создания группы необходимо вложить элементы {@code <item>} в элемент {@code <group>} +в своем ресурсе меню либо указать идентификатор группы с помощью метода {@link +android.view.Menu#add(int,int,int,int) add()}.</p> + +<p>Вот пример ресурса меню, в котором имеется группа:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_save" + android:icon="@drawable/menu_save" + android:title="@string/menu_save" /> + <!-- menu group --> + <group android:id="@+id/group_delete"> + <item android:id="@+id/menu_archive" + android:title="@string/menu_archive" /> + <item android:id="@+id/menu_delete" + android:title="@string/menu_delete" /> + </group> +</menu> +</pre> + +<p>Элементы, находящиеся в группе, отображаются на одном уровне с первым элементом — все три пункта +меню являются элементами одного уровня. Однако можно изменить характеристики двух +пунктов из группы, указав ссылку на идентификатор группы и воспользовавшись приведенными выше методами. Кроме того, +система никогда не будет разделять сгруппированные пункты. Например, если объявить {@code +android:showAsAction="ifRoom"} для каждого пункта, то они оба будут отображены либо в строке +действий, либо в дополнительных действиях.</p> + + +<h3 id="checkable">Использование пунктов меню, которые можно пометить</h3> + +<div class="figure" style="width:200px"> + <img src="{@docRoot}images/radio_buttons.png" height="333" alt="" /> + <p class="img-caption"><strong>Рисунок 5</strong>. Снимок экрана с вложенным меню, пункты которого можно +пометить.</p> +</div> + +<p>Такое меню можно использовать в качестве интерфейса для включения и отключения тех или иных параметров. При этом для автономных +параметров используется флажок, а для групп +взаимоисключающих вариантов ― переключатель. На рисунке 5 показано вложенное меню с пунктами, которые можно выбирать с помощью +переключателей.</p> + +<p class="note"><strong>Примечание.</strong> Пункты в меню значков (из меню параметров) не могут +отображать флажки или переключатели. Если вы решите сделать так, чтобы пункты меню значков можно было помечать, +вам необходимо будет вручную указать помеченное состояние путем замены значка и/или теста +при каждом изменении состояния.</p> + +<p>Определять возможность помечать отдельные пункты меню можно с помощью атрибута {@code +android:checkable} в элементе {@code <item>}, а для всей группы это делается с помощью +атрибута {@code android:checkableBehavior} в элементе {@code <group>}. Например +, все пункты этой группы меню можно помечать с помощью переключателей:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <group android:checkableBehavior="single"> + <item android:id="@+id/red" + android:title="@string/red" /> + <item android:id="@+id/blue" + android:title="@string/blue" /> + </group> +</menu> +</pre> + +<p>Атрибут {@code android:checkableBehavior} принимает один из трех параметров: +<dl> + <dt>{@code single}</dt> + <dd>Только один пункт из группы можно пометить (переключатель)</dd> + <dt>{@code all}</dt> + <dd>Все пункты можно пометить (флажки)</dd> + <dt>{@code none}</dt> + <dd>Пометить нельзя ни один пункт</dd> +</dl> + +<p>Для того чтобы применить к пункту помеченное состояние по умолчанию, служит атрибут {@code android:checked} в +элементе {@code <item>}, а изменить его в коде можно с помощью метода {@link +android.view.MenuItem#setChecked(boolean) setChecked()}.</p> + +<p>Когда выбирается пункт, который может быть помечен, система вызывает соответствующий ему метод обратного вызова +(например {@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}). Именно +здесь необходимо задать состояние флажка, поскольку флажок или переключатель не +изменяет свое состояние автоматически. Запросить текущее состояние пункта (в котором он находился до того, как был +выбран пользователем) можно с помощью{@link android.view.MenuItem#isChecked()}, а затем задать помеченное состояние с помощью +{@link android.view.MenuItem#setChecked(boolean) setChecked()}. Например:</p> + +<pre> +@Override +public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.vibrate: + case R.id.dont_vibrate: + if (item.isChecked()) item.setChecked(false); + else item.setChecked(true); + return true; + default: + return super.onOptionsItemSelected(item); + } +} +</pre> + +<p>Если помеченное состояние не установить этим способом, то, когда пользователь выберет пункт, его отображаемое состояние (флажок или +переключатель) не +изменится. Если же помеченное состояние установить, операция сохранит его +для пункта, с тем чтобы, когда пользователь откроет это меню, он увидел, +что галочка поставлена.</p> + +<p class="note"><strong>Примечание.</strong> +Пункты меню, которые можно пометить галочкой, предназначены для использования только в рамках одного сеанса. Они не сохраняются после +прекращения существования приложения. Если имеются настройки приложения, которые требуется сохранить для пользователя, +делать это следует с помощью <a href="{@docRoot}guide/topics/data/data-storage.html#pref">общих настроек</a>.</p> + + + +<h2 id="intents">Добавление пунктов меню на основе объектов Intent</h2> + +<p>Иногда требуется, чтобы пункт меню запускал операцию с помощью объекта {@link android.content.Intent} +(это может быть операция как из вашего, так и из другого приложения). Когда вам известен объект Intent, который +требуется использовать, и у вас есть определенный пункт меню, который должен инициировать этот объект Intent, можно выполнить объект +Intent с помощью {@link android.app.Activity#startActivity(Intent) startActivity()} во время +выполнения соответствующего метода обратного вызова, запускаемого при выборе пункта меню (например, обратного вызова {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()}).</p> + +<p>Однако если вы не уверены, что на устройстве пользователя +есть приложение, которое может обработать этот объект Intent, добавление пункта меню, который его вызывает, может привести +к тому, что он не будет работать, поскольку объект Intent может не быть передан в +операцию. Чтобы решить эту проблему, Android позволяет динамически добавлять в меню пункты, +когда система Android обнаруживает на устройстве операции, которые могут обработать ваш объект Intent.</p> + +<p>Добавление пунктов меню на основе имеющихся операций, которые принимают объект Intent:</p> +<ol> + <li>Определите объект +с категорией {@link android.content.Intent#CATEGORY_ALTERNATIVE} и/или +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE}, а также с любыми другими условиями.</li> + <li>Вызовите метод {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}. Затем Android выполнит поиск приложений, которые могут выполнить этот объект Intent, +и добавит их в ваше меню.</li> +</ol> + +<p>При отсутствии установленных приложений, +которые удовлетворяют требованиям объекта Intent, ни одного пункта меню добавлено не будет.</p> + +<p class="note"><strong>Примечание.</strong> +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} используется для обработки элемента, выбранного +в данный момент на экране. Поэтому его следует использовать только при создании меню в {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()}.</p> + +<p>Например:</p> + +<pre> +@Override +public boolean onCreateOptionsMenu(Menu menu){ + super.onCreateOptionsMenu(menu); + + // Create an Intent that describes the requirements to fulfill, to be included + // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE. + Intent intent = new Intent(null, dataUri); + intent.addCategory(Intent.CATEGORY_ALTERNATIVE); + + // Search and populate the menu with acceptable offering applications. + menu.addIntentOptions( + R.id.intent_group, // Menu group to which new items will be added + 0, // Unique item ID (none) + 0, // Order for the items (none) + this.getComponentName(), // The current activity name + null, // Specific items to place first (none) + intent, // Intent created above that describes our requirements + 0, // Additional flags to control items (none) + null); // Array of MenuItems that correlate to specific items (none) + + return true; +}</pre> + +<p>Каждая обнаруженная операция, в которой имеется фильтр Intent, соответствующий данному объекту Intent, добавляется в виде +пункта меню. Для этого значение из элемента <code>android:label</code> фильтра Intent используется в качестве +заголовка пункта меню, а значок приложения ― в качестве значка этого пункта меню. Метод +{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} возвращает количество добавленных пунктов меню.</p> + +<p class="note"><strong>Примечание.</strong> При вызове метода {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} он переопределяет все пункты меню по группе меню, указанной в первом +аргументе.</p> + + +<h3 id="AllowingToAdd">Предоставление возможности добавить свою операцию в другие меню</h3> + +<p>Вы также можете предоставить другим приложениям возможность воспользоваться своей операцией, с тем чтобы ваше +приложение могло быть включено в меню других приложений (процедура, обратная описанной выше).</p> + +<p>Чтобы операция могла быть включена в меню других приложений, необходимо определить фильтр +Intent обычным образом, но непременно включить в него значения {@link android.content.Intent#CATEGORY_ALTERNATIVE} +и/или {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} для категории фильтра +Intent. Например:</p> +<pre> +<intent-filter label="@string/resize_image"> + ... + <category android:name="android.intent.category.ALTERNATIVE" /> + <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> + ... +</intent-filter> +</pre> + +<p>Подробные сведения о написании фильтров Intent см. в документе +<a href="/guide/components/intents-filters.html">Объекты Intent и фильтры объектов Intent</a>.</p> + +<p>Образец приложения, в котором используется эта методика, см. в образце кода +<a href="{@docRoot}resources/samples/NotePad/src/com/example/android/notepad/NoteEditor.html">Note +Pad</a>.</p> diff --git a/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 000000000000..d072b77eae24 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=Уведомления +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> +<h2>Содержание документа</h2> +<ol> + <li><a href="#Design">Рекомендации по разработке</a></li> + <li><a href="#CreateNotification">Создание уведомления</a> + <ol> + <li><a href="#Required">Обязательное содержимое уведомления</a></li> + <li><a href="#Optional">Необязательные содержимое и настройки уведомления</a></li> + <li><a href="#Actions">Действия уведомлений</a></li> + <li><a href="#Priority">Приоритет уведомлений</a></li> + <li><a href="#SimpleNotification">Создание простого уведомления</a></li> + <li><a href="#ApplyStyle">Применение расширенного макета к уведомлению</a></li> + <li><a href="#Compatibility">Вопросы совместимости</a></li> + </ol> + </li> + <li><a href="#Managing">Управление уведомлениями</a> + <ol> + <li><a href="#Updating">Обновление уведомлений</a></li> + <li><a href="#Removing">Удаление уведомлений</a></li> + </ol> + </li> + <li><a href="#NotificationResponse">Сохранение навигации при запуске операции</a> + <ol> + <li><a href="#DirectEntry">Настройка PendingIntent обычной операции</a></li> + <li><a href="#ExtendedNotification">Настройка PendingIntent особой операции</a></li> + </ol> + </li> + <li><a href="#Progress">Отображение хода выполнения в уведомлении</a> + <ol> + <li><a href="#FixedProgress">Отображение индикатора хода выполнения фиксированной продолжительности</a></li> + <li><a href="#ActivityIndicator">Отображение непрерывного индикатора операции</a></li> + </ol> + </li> + <li><a href="#metadata">Метаданные уведомления</a></li> + <li><a href="#Heads-up">Уведомления heads-up</a></li> + <li><a href="#lockscreenNotification">Уведомления на экране блокировки</a></li> + <ol> + <li><a href="#visibility">Настройка видимости</a></li> + <li><a href="#controllingMedia">Управление воспроизведением мультимедиа на экране блокировки</a></li> + </ol> + <li><a href="#CustomNotification">Нестандартные макеты уведомлений</a></li> +</ol> + + <h2>Основные классы</h2> + <ol> + <li>{@link android.app.NotificationManager}</li> + <li>{@link android.support.v4.app.NotificationCompat}</li> + </ol> + <h2>Видеоролики</h2> + <ol> + <li> + <a href="http://www.youtube.com/watch?v=Yc8YrVc47TI&feature=player_detailpage#t=1672s"> + Уведомления в 4.1</a> + </li> + </ol> +<h2>См. также</h2> +<ol> + <li> + <a href="{@docRoot}design/patterns/notifications.html">Дизайн Android: уведомления</a> + </li> +</ol> +</div> +</div> +<p> + Уведомление ― это сообщение, которое может быть выведено на экран за пределами обычного пользовательского + интерфейса приложения. Когда вы сообщаете системе о необходимости выдать уведомление, оно сначала отображается в виде значка в + <strong>области уведомлений</strong>. Чтобы просмотреть подробные сведения об уведомлении, пользователь открывает + <strong>панель уведомлений</strong>. И областью уведомлений, и панелью уведомлений + управляет система, а пользователь может их просматривать в любое время. +</p> +<img id="figure1" src="{@docRoot}images/ui/notifications/notification_area.png" height="" alt="" /> +<p class="img-caption"> + <strong>Рисунок 1.</strong> Уведомления в области уведомлений. +</p> +<img id="figure2" src="{@docRoot}images/ui/notifications/notification_drawer.png" width="280px" alt="" /> +<p class="img-caption"> + <strong>Рисунок 2.</strong> Уведомления в панели уведомлений. +</p> + +<p class="note"><strong>Примечание.</strong> Если не указано иное, в этом руководстве речь идет о классе +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +в версии 4 <a href="{@docRoot}tools/support-library/index.html">вспомогательной библиотеки</a>. +Класс{@link android.app.Notification.Builder Notification.Builder} был добавлен в Android +3.0 (уровень API 11).</p> + +<h2 id="Design">Рекомендации по разработке</h2> + +<p>Поскольку уведомления являются как важной составной частью пользовательского интерфейса Android, для них имеются собственные инструкции по проектированию. +Появившиеся в Android 5.0 (уровень API 21) значительные изменения дизайна имеют особо важное +значение, поэтому для получения более подробной информации вам следует ознакомиться с учебником по интерфейсу <a href="{@docRoot}training/material/index.html">Material Design</a> +. Чтобы узнать, как проектировать уведомления и взаимодействие с ними, прочитайте руководство по проектированию +<a href="{@docRoot}design/patterns/notifications.html">Уведомления</a>.</p> + +<h2 id="CreateNotification">Создание уведомления</h2> + +<p>Информация о пользовательском интерфейсе и действия для уведомления указываются в объекте +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. +Чтобы создать само уведомление, вызывается метод +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()}, +который возвращает объект {@link android.app.Notification}, содержащий заданные вами спецификации. Чтобы выдать +уведомление, объект {@link android.app.Notification} передается в систему путем вызова метода +{@link android.app.NotificationManager#notify NotificationManager.notify()}.</p> + +<h3 id="Required">Обязательное содержимое уведомления</h3> +<p> + Объект {@link android.app.Notification} <em>должен</em> содержать следующие элементы: +</p> +<ul> + <li> + небольшой значок, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()}; + </li> + <li> + заголовок, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}; + </li> + <li> + подробный текст, заданный с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()}. + </li> +</ul> +<h3 id="Optional">Необязательные содержимое и настройки уведомления</h3> +<p> + Все прочие настройки и содержимое уведомления являются необязательными. Подробные сведения о них + см. в справочной документации по {@link android.support.v4.app.NotificationCompat.Builder}. +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Actions">Действия уведомлений</h3> +<p> + Несмотря на то что действия не являются обязательными, в уведомление необходимо добавить хотя бы одно из них. + Действие позволяет пользователю перейти из уведомления прямо к + операции {@link android.app.Activity} из вашего приложения, где они могут просмотреть одно или несколько событий + либо выполнить какую-либо другую работу. +</p> +<p> + Уведомление может предоставлять возможность выполгить несколько действий. Следует всегда определять действие, которое + вызывается, когда пользователь нажимает уведомление; обычно это действие открывает + операцию {@link android.app.Activity} из вашего приложения. В уведомление также можно добавлять кнопки, + которые выполняют дополнительные действия, например отключение сигнала будильника или немедленный ответ на текстовое + сообщение; эта функция поддерживается начиная с версии Android 4.1. Если используются дополнительные кнопки действий, то также + необходимо сделать их функции доступными в операции {@link android.app.Activity} из вашего приложения (подробные + сведения см. в разделе <a href="#Compatibility">Вопросы совместимости</a>). +</p> +<p> + Внутри класса {@link android.app.Notification} само действие определяется + объектом {@link android.app.PendingIntent}, содержащим объект + {@link android.content.Intent}, который запускает + операцию {@link android.app.Activity} из вашего приложения. Чтобы связать объект + {@link android.app.PendingIntent} с жестом, вызовите соответствующий метод + {@link android.support.v4.app.NotificationCompat.Builder}. Например, если вам требуется запустить + операцию {@link android.app.Activity}, когда пользователь нажимает текст уведомления в + панели уведомлений, вы добавляете объект {@link android.app.PendingIntent} путем вызова метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()}. +</p> +<p> + Запуск операции {@link android.app.Activity}, когда пользователь нажимает уведомление, является + наиболее распространенным вариантом действия. Операцию {@link android.app.Activity} также можно запускать, когда пользователь + закрывает уведомление. В версии Android 4.1 или более поздних версиях запускать + операцию {@link android.app.Activity} можно с помощью кнопки действия. Подробные сведения см. в справочном руководстве по классу + {@link android.support.v4.app.NotificationCompat.Builder}. +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Priority">Приоритет уведомлений</h3> +<p> + При желании уведомлению можно задать приоритет. Приоритет действует + как подсказка пользовательскому интерфейсу устройства о том, каким образом следует выводить уведомление. + Чтобы задать приоритет уведомления, вызовите метод {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} и передайте ему одну из констант приоритетов {@link + android.support.v4.app.NotificationCompat}. Имеется + пять уровней приоритета, начиная от {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) и до {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2). Если приоритет не задан, + то по умолчанию он будет иметь значение {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0). +</p> +<p> Сведения о присвоении подходящего уровня приоритета см. в разделе "Правильная настройка + приоритета уведомления и управление им" в руководстве "Разработка <a href="{@docRoot}design/patterns/notifications.html">уведомлений</a>" +. +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="SimpleNotification">Создание простого уведомления</h3> +<p> + Следующий фрагмент кода иллюстрирует простое уведомление, указывающее операцию, которую нужно будет открыть, когда + пользователь нажмет уведомление. Обратите внимание, что этот код создает объект + {@link android.support.v4.app.TaskStackBuilder} и использует его для создания объекта + {@link android.app.PendingIntent} для действия. Более подробно этот шаблон описан + в разделе <a href="#NotificationResponse"> + "Сохранение навигации при запуске операции"</a>: +</p> +<pre> +NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification_icon) + .setContentTitle("My notification") + .setContentText("Hello World!"); +// Creates an explicit intent for an Activity in your app +Intent resultIntent = new Intent(this, ResultActivity.class); + +// The stack builder object will contain an artificial back stack for the +// started Activity. +// This ensures that navigating backward from the Activity leads out of +// your application to the Home screen. +TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); +// Adds the back stack for the Intent (but not the Intent itself) +stackBuilder.addParentStack(ResultActivity.class); +// Adds the Intent that starts the Activity to the top of the stack +stackBuilder.addNextIntent(resultIntent); +PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); +mBuilder.setContentIntent(resultPendingIntent); +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// mId allows you to update the notification later on. +mNotificationManager.notify(mId, mBuilder.build()); +</pre> +<p>Готово. Теперь пользователь получит уведомление.</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ApplyStyle">Применение расширенного макета к уведомлению</h3> +<p> + Чтобы уведомление отображалось в расширенном виде, сначала создайте объект + {@link android.support.v4.app.NotificationCompat.Builder} с требуемыми параметрами + обычного представления. Затем вызовите метод {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()}, первым аргументом которого должен быть объект расширенного макета. +</p> +<p> + Помните, что расширенные уведомления не поддерживаются на платформах версии более ранней, чем Android 4.1. Сведения + о том, как обрабатывать уведомления для версий платформы Android 4.1 и более ранних, см. в + разделе <a href="#Compatibility">Вопросы совместимости</a>. +</p> +<p> + Например, следующий фрагмент кода демонстрирует, каким образом следует изменить уведомление, созданное + в предыдущем фрагменте, чтобы использовать расширенный макет: +</p> +<pre> +NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification_icon) + .setContentTitle("Event tracker") + .setContentText("Events received") +NotificationCompat.InboxStyle inboxStyle = + new NotificationCompat.InboxStyle(); +String[] events = new String[6]; +// Sets a title for the Inbox in expanded layout +inboxStyle.setBigContentTitle("Event tracker details:"); +... +// Moves events into the expanded layout +for (int i=0; i < events.length; i++) { + + inboxStyle.addLine(events[i]); +} +// Moves the expanded layout object into the notification object. +mBuilder.setStyle(inBoxStyle); +... +// Issue the notification here. +</pre> + +<h3 id="Compatibility">Вопросы совместимости</h3> + +<p> + Не все функции уведомлений поддерживаются в той или иной версии платформы, даже несмотря на то, что + методы для их задания имеются в классе вспомогательной библиотеки + {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder}. + Например, кнопки действий, которые зависят от расширенных уведомлений, отображаются только в версии Android + 4.1 и последующих версиях, поскольку сами расширенные уведомления поддерживаются только начиная с версии + Android 4.1. +</p> +<p> + Для обеспечения наилучшей совместимости создавать уведомления следует с помощью класса + {@link android.support.v4.app.NotificationCompat NotificationCompat} и его подклассов, + в частности {@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder}. Кроме того, при реализации уведомления придерживайтесь вот такого процесса: +</p> +<ol> + <li> + Предоставляйте все функции уведомления всем пользователям независимо от используемой ими + версии. Для этого убедитесь, что все функции вызываются из + операции {@link android.app.Activity} в вашем приложении. Возможно, для этого вам потребуется добавить новый объект + {@link android.app.Activity}. + <p> + Например, если требуется использовать + {@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} для + создания элемента управления, который останавливает и запускает воспроизведение мультимедиа, сначала реализуйте этот + элемент управления в операции {@link android.app.Activity} из вашего приложения. + </p> + </li> + <li> + Обеспечьте доступ к этой функции операции {@link android.app.Activity} всех пользователей, + сделав так, чтобы эта операция запускалась, когда пользователь нажимает уведомление. Для этого + создайте объект {@link android.app.PendingIntent} + для операции {@link android.app.Activity}. Вызовите + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}, чтобы добавить объект {@link android.app.PendingIntent} в уведомление. + </li> + <li> + Затем добавьте в свое уведомление требуемые функции расширенного уведомления. Помните, + что любая добавляемая функция должна быть предусмотрена в операции {@link android.app.Activity}, + которая запускается, когда пользователь нажимает уведомление. + </li> +</ol> + + +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="Managing">Управление уведомлениями</h2> +<p> + Когда уведомление требуется выдать несколько раз для однотипных событий, не следует + создавать совершенно новое уведомление. Вместо этого рассмотрите возможность обновить + предыдущее уведомление либо путем изменения некоторых его значений, либо путем добавления в него значений, либо обоими этими способами. +</p> +<p> + Например, Gmail уведомляет пользователя о поступлении новых сообщений электронной почты путем увеличения счетчика + непрочитанных сообщений и добавления в уведомления кратких сведений о каждом из них. Это называется + "укладкой уведомлений в стопку" (stacking) и подробно описано в руководстве + "Разработка <a href="{@docRoot}design/patterns/notifications.html">уведомлений</a>". +</p> +<p class="note"> + <strong>Примечание.</strong> Для этой функции Gmail требуется расширенный макет папки входящих сообщений, который + входит в состав функции расширенных уведомлений, поддержка которой предусмотрена начиная с версии Android 4.1. +</p> +<p> + В разделах ниже описано, как обновлять уведомления, а также как удалять их. +</p> +<h3 id="Updating">Обновление уведомлений</h3> +<p> + Чтобы настроить уведомление таким образом, что его впоследствии можно было обновлять, его следует выдавать с идентификатором уведомления путем + вызова метода {@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()}. + Чтобы изменить это уведомление, после того как оно выдано, + обновите или создайте объект {@link android.support.v4.app.NotificationCompat.Builder}, + постройте на его основе объект {@link android.app.Notification} и выдайте + объект {@link android.app.Notification} с тем же идентификатором, который использовался ранее. Если + предыдущее уведомление все еще отображается на экране, система обновит его с использованием содержимого + объекта{@link android.app.Notification}. Если предыдущее уведомление было закрыто, то + вместо него будет создано новое уведомление. +</p> +<p> + Следующий фрагмент кода демонстрирует уведомление, которое обновляется с учетом + количества произошедших событий. Он накладывает уведомления друг на друга (укладывает в стопку), отображая сводную информацию: +</p> +<pre> +mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// Sets an ID for the notification, so it can be updated +int notifyID = 1; +mNotifyBuilder = new NotificationCompat.Builder(this) + .setContentTitle("New Message") + .setContentText("You've received new messages.") + .setSmallIcon(R.drawable.ic_notify_status) +numMessages = 0; +// Start of a loop that processes data and then notifies the user +... + mNotifyBuilder.setContentText(currentText) + .setNumber(++numMessages); + // Because the ID remains unchanged, the existing notification is + // updated. + mNotificationManager.notify( + notifyID, + mNotifyBuilder.build()); +... +</pre> + +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Removing">Удаление уведомлений</h3> +<p> + Уведомления отображаются на экране, пока не произойдет одно из следующих событий: +</p> +<ul> + <li> + пользователь закроет уведомления по одному или командой "Очистить все" (если + уведомление можно очистить); + </li> + <li> + пользователь нажмет уведомление, а вы вызвали метод + {@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()}, когда + создавали уведомление; + </li> + <li> + вы вызовете метод {@link android.app.NotificationManager#cancel(int) cancel()} для уведомления с определенным + идентификатором. Кроме того, этот метод удаляет текущие уведомления; + </li> + <li> + вы вызовете метод {@link android.app.NotificationManager#cancelAll() cancelAll()}, который удаляет + все ранее выданные вами уведомления. + </li> +</ul> +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="NotificationResponse">Сохранение навигации при запуске операции</h2> +<p> + Когда вы запускаете операцию {@link android.app.Activity} из уведомления, вам необходимо сохранить + привычные пользователю приемы навигации. При нажатии <i>"Назад"</i> пользователь должен возвращаться назад в + обычном потоке операций приложения вплоть до главного экрана, а при нажатии <i>"Последние"</i> операция + {@link android.app.Activity} должна быть отображена как отдельная задача. Чтобы сохранить приемы навигации, операцию + {@link android.app.Activity} следует запускать в новой задаче. Как настроить объект + {@link android.app.PendingIntent} таким образом, чтобы получить новую задачу, зависит от типа операции + {@link android.app.Activity}, которую вы запускаете. В целом есть две ситуации: +</p> +<dl> + <dt> + Обычная операция + </dt> + <dd> + Вы запускаете операцию {@link android.app.Activity}, которая является частью обычного потока + работы приложения. В этом случае настройте объект {@link android.app.PendingIntent} на + запуск новой задачи и предоставьте объекту {@link android.app.PendingIntent} стек переходов назад, + который воспроизводит обычную работу приложения в ситуации, когда пользователь нажимает <i>"Назад"</i> . + <p> + Это можно увидеть на примере уведомлений приложения Gmail. При нажатии уведомления для + одного сообщения электронной почты отображается само сообщение. При нажатии <b>Назад</b> вы переходите + назад по представлениям Gmail вплоть до главного экрана точно так же, как если бы вы вошли в Gmail с + главного экрана, а не из уведомления. + </p> + <p> + Происходит это независимо от того, в каком приложении вы находились в тот момент, когда нажали + уведомление. Например, если при составлении сообщения в Gmail вы нажмете + уведомление об одном сообщении электронной почты, вы сразу же перейдете в это сообщение. При нажатии <i>"Назад"</i> + вы перейдете в папку входящих сообщений, а затем на главный экран, а не в + сообщение, которое составляли. + </p> + </dd> + <dt> + Особая операция + </dt> + <dd> + Пользователь может увидеть эту операцию {@link android.app.Activity}, только если она запущена из уведомления. + В некотором смысле операция {@link android.app.Activity} расширяет уведомление путем предоставления + информации, которую было бы сложно отобразить в самом уведомлении. В этом случае + настройте объект {@link android.app.PendingIntent} на запуск в новой задаче. При этом создавать + стек переходов назад не требуется, поскольку запущенная операция {@link android.app.Activity} не является частью + потока операций приложения. При нажатии <i>"Назад"</i> пользователь все же перейдет на + главный экран. + </dd> +</dl> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="DirectEntry">Настройка PendingIntent обычной операции</h3> +<p> + Чтобы настроить объект {@link android.app.PendingIntent}, который непосредственно запускает операцию + {@link android.app.Activity}, выполните следующие шаги: +</p> +<ol> + <li> + Определите иерархию операций {@link android.app.Activity} своего приложения в файле манифеста. + <ol style="list-style-type: lower-alpha;"> + <li> + Добавьте поддержку для версии Android 4.0.3 и более ранних версий. Для этого укажите родительский объект операции + {@link android.app.Activity}, которую запускаете, добавив элемент +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> + в качестве дочернего для элемента +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>. + <p> + Для этого элемента задайте +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a>="android.support.PARENT_ACTIVITY"</code>. + Задайте +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#val">android:value</a>="<parent_activity_name>"</code>, + где <code><parent_activity_name></code> ― это значение +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a></code> + для родительского элемента +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +. В качестве примера см. следующий код XML. + </p> + </li> + <li> + Также добавьте поддержку для версии Android 4.1 и более поздних версий. Для этого добавьте атрибут +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#parent">android:parentActivityName</a></code> + в элемент +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> + запускаемой операции{@link android.app.Activity}. + </li> + </ol> + <p> + Итоговый код XML должен выглядеть следующим образом: + </p> +<pre> +<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> +<activity + android:name=".ResultActivity" + android:parentActivityName=".MainActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".MainActivity"/> +</activity> +</pre> + </li> + <li> + Создайте стек переходов назад, основанный на объекте {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}: + <ol style="list-style-type: lower-alpha;"> + <li> + Создайте объект {@link android.content.Intent}, который запускает операцию {@link android.app.Activity}. + </li> + <li> + Создайте построитель стека, вызвав метод {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()}. + </li> + <li> + Добавьте стек переходов назад в построитель стеков путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()}. + Для каждой операции {@link android.app.Activity} из иерархии, определенной в + файле манифеста, в стеке переходов назад имеется объект {@link android.content.Intent}, который + запускает {@link android.app.Activity}. Этот метод также добавляет флаги, которые запускают + стек в новой задаче. + <p class="note"> + <strong>Примечание.</strong> Несмотря на то что аргумент + {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} + является ссылкой на запускаемую операцию {@link android.app.Activity}, при вызове этого метода + не добавляется объект {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}. Это делается на следующем шаге. + </p> + </li> + <li> + Добавьте объект {@link android.content.Intent}, который запускает операцию {@link android.app.Activity} + из уведомления, путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + Передайте объект {@link android.content.Intent}, созданный вами на первом шаге, в качестве + аргумента методу + {@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()}. + </li> + <li> + При необходимости добавьте аргументы в объекты {@link android.content.Intent} из + стека путем вызова метода {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()}. Иногда это требуется для того, чтобы + целевая операция {@link android.app.Activity} отображала значимые данные, когда пользователь переходит + в нее с помощью действия <i>"Назад"</i>. + </li> + <li> + Получите объект {@link android.app.PendingIntent} для этого стека переходов назад путем вызова метода + {@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()}. + Затем этот объект {@link android.app.PendingIntent} можно будет использовать в качестве аргумента для метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. + </li> + </ol> + </li> +</ol> +<p> + Следующий фрагмент кода демонстрирует этот процесс: +</p> +<pre> +... +Intent resultIntent = new Intent(this, ResultActivity.class); +TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); +// Adds the back stack +stackBuilder.addParentStack(ResultActivity.class); +// Adds the Intent to the top of the stack +stackBuilder.addNextIntent(resultIntent); +// Gets a PendingIntent containing the entire back stack +PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); +... +NotificationCompat.Builder builder = new NotificationCompat.Builder(this); +builder.setContentIntent(resultPendingIntent); +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +mNotificationManager.notify(id, builder.build()); +</pre> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ExtendedNotification">Настройка PendingIntent особой операции</h3> +<p> + В приведенном далее разделе описывается настройка объекта + {@link android.app.PendingIntent} для особой операции. +</p> +<p> + Особой операции {@link android.app.Activity} не требуется стек перехода назад, поэтому не нужно + определять иерархию объектов {@link android.app.Activity} в файле манифеста и + вызывать + метод {@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} для построения + стека перехода назад. Вместо этого в файле манифеста задайте параметры задачи {@link android.app.Activity} + и создайте объект {@link android.app.PendingIntent} путем вызова метода + {@link android.app.PendingIntent#getActivity getActivity()}: +</p> +<ol> + <li> + Добавьте следующие атрибуты в элемент +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> + в файле манифеста для операции {@link android.app.Activity} + <dl> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">android:name</a>="<i>activityclass</i>"</code> + </dt> + <dd> + Полное имя класса операции. + </dd> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">android:taskAffinity</a>=""</code> + </dt> + <dd> + В сочетании с + флагом {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK}, + который вы задали в коде, это гарантирует, что данная операция {@link android.app.Activity} не + перейдет в задачу приложения, используемую по умолчанию. Любые существующие задачи, имеющие + отношение к используемой по умолчанию задаче приложения, затронуты не будут. + </dd> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#exclude">android:excludeFromRecents</a>="true"</code> + </dt> + <dd> + Исключает новую задачу из списка <i>"Последние",</i>с тем чтобы пользователь не мог случайно + вернуться в нее. + </dd> + </dl> + <p> + Данный элемент показан в этом фрагменте кода: + </p> +<pre> +<activity + android:name=".ResultActivity" +... + android:launchMode="singleTask" + android:taskAffinity="" + android:excludeFromRecents="true"> +</activity> +... +</pre> + </li> + <li> + Построение и выдача уведомления: + <ol style="list-style-type: lower-alpha;"> + <li> + Создайте объект {@link android.content.Intent}, который запускает операцию + {@link android.app.Activity}. + </li> + <li> + Настройте операцию {@link android.app.Activity}, запускаемую в новой пустой задаче, путем вызова метода + {@link android.content.Intent#setFlags setFlags()} с флагами + {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} + и + {@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK}. + </li> + <li> + Задайте для объекта {@link android.content.Intent} любые другие требуемые параметры. + </li> + <li> + Создайте объект {@link android.app.PendingIntent} из объекта {@link android.content.Intent} + путем вызова метода {@link android.app.PendingIntent#getActivity getActivity()}. + Затем этот объект {@link android.app.PendingIntent} можно будет использовать в качестве аргумента для метода + {@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()}. + </li> + </ol> + <p> + Следующий фрагмент кода демонстрирует этот процесс: + </p> +<pre> +// Instantiate a Builder object. +NotificationCompat.Builder builder = new NotificationCompat.Builder(this); +// Creates an Intent for the Activity +Intent notifyIntent = + new Intent(this, ResultActivity.class); +// Sets the Activity to start in a new, empty task +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); +// Creates the PendingIntent +PendingIntent notifyPendingIntent = + PendingIntent.getActivity( + this, + 0, + notifyIntent, + PendingIntent.FLAG_UPDATE_CURRENT +); + +// Puts the PendingIntent into the notification builder +builder.setContentIntent(notifyPendingIntent); +// Notifications are issued by sending them to the +// NotificationManager system service. +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// Builds an anonymous Notification object from the builder, and +// passes it to the NotificationManager +mNotificationManager.notify(id, builder.build()); +</pre> + </li> +</ol> +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="Progress">Отображение хода выполнения в уведомлении</h2> +<p> + Уведомления могут содержать индикатор хода выполнения с эффектом анимации, который показывает пользователям состояние + текущей операции. Если имеется возможность оценить, сколько времени занимает операция и какой процент ее объема + уже завершен, используйте "определенную" форму индикатора + (индикатор хода выполнения). Если продолжительность операции оценить невозможно, используйте + "неопределенную" форму индикатора (индикатор операции). +</p> +<p> + Индикаторы хода выполнения отображаются с помощью реализации класса + {@link android.widget.ProgressBar} платформы. +</p> +<p> + Для использования индикатора хода выполнения на платформах начиная с Android 4.0 вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. Для + предыдущих версий необходимо будет создать собственный нестандартный макет уведомлений, который + содержит представление {@link android.widget.ProgressBar}. +</p> +<p> + В приведенных далее разделах описывается отображение хода выполнения в уведомлении с помощью + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}. +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="FixedProgress">Отображение индикатора хода выполнения фиксированной продолжительности</h3> +<p> + Чтобы вывести на экран определенный индикатор хода выполнения, добавьте его в свое уведомление, вызвав метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)}, а затем выдайте уведомление. По мере выполнения + увеличивайте значение <code>progress</code> и обновляйте уведомление. По окончании операции + <code>progress</code> должен быть равен <code>max</code>. Стандартный способ вызова метода + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} + заключается в следующем: задать значение <code>max</code> равным 100 с последующим увеличением <code>progress</code> в виде + величины "процента выполнения" операции. +</p> +<p> + По окончании операции можно оставить этот индикатор хода выполнения на экране или удалить его. В + любом случае не забывайте обновить текст уведомления, чтобы указать, что операция выполнена. + Чтобы удалить индикатор выполнения, вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}. Например: +</p> +<pre> +... +mNotifyManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +mBuilder = new NotificationCompat.Builder(this); +mBuilder.setContentTitle("Picture Download") + .setContentText("Download in progress") + .setSmallIcon(R.drawable.ic_notification); +// Start a lengthy operation in a background thread +new Thread( + new Runnable() { + @Override + public void run() { + int incr; + // Do the "lengthy" operation 20 times + for (incr = 0; incr <= 100; incr+=5) { + // Sets the progress indicator to a max value, the + // current completion percentage, and "determinate" + // state + mBuilder.setProgress(100, incr, false); + // Displays the progress bar for the first time. + mNotifyManager.notify(0, mBuilder.build()); + // Sleeps the thread, simulating an operation + // that takes time + try { + // Sleep for 5 seconds + Thread.sleep(5*1000); + } catch (InterruptedException e) { + Log.d(TAG, "sleep failure"); + } + } + // When the loop is finished, updates the notification + mBuilder.setContentText("Download complete") + // Removes the progress bar + .setProgress(0,0,false); + mNotifyManager.notify(ID, mBuilder.build()); + } + } +// Starts the thread by calling the run() method in its Runnable +).start(); +</pre> + +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ActivityIndicator">Отображение непрерывного индикатора операции</h3> +<p> + Для отображения неопределенного индикатора операции добавьте его в свое уведомление с помощью метода + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} + (два первых аргумента игнорируются) и выдайте уведомление. В результате будет показан индикатор, + который будет иметь такой же стиль, что и индикатор хода выполнения, за исключением того, что его анимация будет непрерывной. +</p> +<p> + Выдайте уведомление в начале операции. Анимация будет воспроизводиться до тех пор, пока вы не + измените уведомление. Когда операция будет выполнена, вызовите метод + {@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)}, + после чего обновите уведомление, удалив индикатор операции. + Всегда поступайте таким образом. В противном случае анимация будет воспроизводиться и после завершения операции. Также + не забывайте изменить текст уведомления, чтобы указать, что операция выполнена. +</p> +<p> + Чтобы посмотреть, как работают индикаторы операций, перейдите к предыдущему фрагменту кода. Найдите следующие строки: +</p> +<pre> +// Sets the progress indicator to a max value, the current completion +// percentage, and "determinate" state +mBuilder.setProgress(100, incr, false); +// Issues the notification +mNotifyManager.notify(0, mBuilder.build()); +</pre> +<p> + Замените их вот этими строками: +</p> +<pre> + // Sets an activity indicator for an operation of indeterminate length +mBuilder.setProgress(0, 0, true); +// Issues the notification +mNotifyManager.notify(0, mBuilder.build()); +</pre> + +<h2 id="metadata">Метаданные уведомления</h2> + +<p>Уведомления можно сортировать в соответствии с метаданными, которые назначаются с помощью +следующих методов {@link android.support.v4.app.NotificationCompat.Builder}:</p> + +<ul> + <li>метод {@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} + информирует систему о том, как обрабатывать уведомления вашего приложения, когда устройство находится в режиме приоритета + (например, если ваше уведомление сообщает о входящем вызове, мгновенном сообщении или сигнале будильника);</li> + <li>метод{@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} вызывает + отображение уведомлений, в поле приоритета которых задано значение {@code PRIORITY_MAX} или {@code PRIORITY_HIGH} +, в небольшом плавающем окне, если уведомление также сопровождается звуковым сигналом или вибрацией;</li> + <li>метод {@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} + позволяет добавить к уведомлению список людей. С его помощью ваше уведомление может сигнализировать + системе о том, что она должна сгруппировать уведомления от указанных людей или считать уведомления + от этих людей более важными.</li> +</ul> + +<div class="figure" style="width:230px"> + <img src="{@docRoot}images/ui/notifications/heads-up.png" alt="" width="" height="" id="figure3" /> + <p class="img-caption"> + <strong>Рисунок 3</strong>. Полноэкранная операция, отображающая уведомление heads-up + </p> +</div> + +<h2 id="Heads-up">Уведомления heads-up</h2> + +<p>В Android 5.0 (уровень API 21) уведомления могут отображаться в небольшом плавающем окне +(оно также называется <em>уведомлением heads-up</em>), когда устройство активно +(то есть устройство разблокировано и его экран включен). Эти уведомления +выглядят так же, как компактная форма вашего уведомления, за исключением того, что в +уведомлениях heads-up также отображаются кнопки действий. Пользователи могут выполнять действия или закрывать +уведомление heads-up, не покидая текущее приложение.</p> + +<p>Примеры ситуаций, в которых могут быть вызваны уведомления heads-up:</p> + +<ul> + <li>операция пользователя выполняется в полноэкранном режиме (приложение использует +{@link android.app.Notification#fullScreenIntent}) или;</li> + <li>уведомление имеет высокий приоритет и использует рингтоны или + вибрацию.</li> +</ul> + +<h2 id="lockscreenNotification">Уведомления на экране блокировки</h2> + +<p>С выходом версии Android 5.0 (уровень API 21) уведомления теперь могут отображаться на экране +блокировки. С помощью этой функции можно выводит на экран элементы управления мультимедиа и другие стандартные +действия. В настройках пользователи могут выбрать, следует ли отображать уведомления на экране блокировки, а +вы можете указать, будет ли уведомление из вашего приложения видно на нем.</p> + +<h3 id="visibility">Настройка видимости</h3> + +<p>Ваше приложение может определять объем информации, отображаемой в уведомлениях, которые выводятся на экране +блокировки. Метод {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +вызывается для того, чтобы указать одно из следующих значений:</p> + +<ul> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} показывает полное содержимое + уведомления;</li> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} не отображает какую-либо часть + этого уведомления на экране блокировки;</li> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} показывает базовую информацию, + такую как значок уведомления и заголовок его содержимого, но скрывает полное содержимое уведомления.</li> +</ul> + +<p>Когда задается значение {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE}, вы также можете +предоставить альтернативную версию содержимого уведомления, в который скрыты некоторые сведения. Например, +приложение по работе с СМС может выводить уведомление, в котором говорится <em>У вас 3 новых текстовых сообщения</em>, а содержимое + и отправители сообщений скрыты. Чтобы предоставить возможность такого альтернативного уведомления, сначала создайте его +с помощью {@link android.support.v4.app.NotificationCompat.Builder}. При создании +частного объекта уведомления прикрепите к нему альтернативное уведомление, воспользовавшись методом +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +.</p> + +<h3 id="controllingMedia">Управление воспроизведением мультимедиа на экране блокировки</h3> + +<p>В версии Android 5.0 (API уровня 21) на экране блокировки больше не отображаются элементы управления воспроизведением мультимедиа, +основанные на классе {@link android.media.RemoteControlClient}, использование которого прекращено. Вместо этого используйте +шаблон {@link android.app.Notification.MediaStyle} с методом +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +, который преобразует действия в доступные для нажатия значки.</p> + +<p class="note"><strong>Примечание.</strong> Шаблон и метод {@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +не входят в состав вспомогательной библиотеки, поэтому эти функции работают только в версии Android 5.0 и +последующих версиях.</p> + +<p>Чтобы отобразить элементы управления мультимедиа на экране блокировки в Android 5.0, задайте для видимости +значение {@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC}, выполнив описанную выше процедуру. Затем добавьте +действия и задайте шаблон {@link android.app.Notification.MediaStyle}, как описано в +следующем образце кода:</p> + +<pre> +Notification notification = new Notification.Builder(context) + // Show controls on lock screen even when user hides sensitive content. + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setSmallIcon(R.drawable.ic_stat_player) + // Add media control buttons that invoke intents in your media service + .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 + .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 + .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 + // Apply the media style template + .setStyle(new Notification.MediaStyle() + .setShowActionsInCompactView(1 /* #1: pause button */) + .setMediaSession(mMediaSession.getSessionToken()) + .setContentTitle("Wonderful music") + .setContentText("My Awesome Band") + .setLargeIcon(albumArtBitmap) + .build(); +</pre> + +<p class="note"><strong>Примечание.</strong> Прекращение использования класса {@link android.media.RemoteControlClient} +имеет и другие последствия для управления мультимедиа. Подробные сведения о новых API-интерфейсах для управления сеансами воспроизведения мультимедиа см. в разделе +<a href="{@docRoot}about/versions/android-5.0.html#MediaPlaybackControl">Управление воспроизведением мультимедиа</a> +.</p> + + +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="CustomNotification">Нестандартные макеты уведомлений</h2> +<p> + Платформа уведомлений позволяет задавать нестандартные макеты уведомлений, которые + определяют внешний вид уведомлений в объекте {@link android.widget.RemoteViews}. + Уведомления с нестандартным макетом — такие же, как обычные уведомления, но в их основе лежит класс + {@link android.widget.RemoteViews}, определенный в файле XML макета. +</p> +<p> + Высота изображения, которую можно получить с использованием нестандартного макета уведомления, зависит от представления уведомления. Обычные + макеты представления ограничены по высоте 64 пикселами, а расширенные — 256 пикселами. +</p> +<p> + Чтобы определить нестандартный макет уведомления, начните с инициализации объекта + {@link android.widget.RemoteViews}, который загружает файл XML макета. Затем, + вместо вызова методов, например + {@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()}, + вызовите {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Чтобы задать + сведения о содержимом в нестандартном уведомлении, используйте методы из + {@link android.widget.RemoteViews}, чтобы определить значения для дочерних представлений: +</p> +<ol> + <li> + Создайте макет XML для уведомления в отдельном файле. Можно использовать любое имя файла + по своему усмотрению, однако расширение должно быть <code>.xml</code> + </li> + <li> + В своем приложении с помощью методов {@link android.widget.RemoteViews} определите значки и текст + уведомления. Поместите этот объект {@link android.widget.RemoteViews} в свой + {@link android.support.v4.app.NotificationCompat.Builder}, вызвав метод + {@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()}. Не следует + задавать фон {@link android.graphics.drawable.Drawable} для объекта + {@link android.widget.RemoteViews}, поскольку цвет текста может стать нечитаемым. + </li> +</ol> +<p> + В классе {@link android.widget.RemoteViews} также имеются методы, с помощью которых можно легко + добавить {@link android.widget.Chronometer} или {@link android.widget.ProgressBar} + в свой макет уведомления. Подробные сведения о создании нестандартных макетов для + уведомлений см. в справочной документации по {@link android.widget.RemoteViews}. +</p> +<p class="caution"> + <strong>Внимание!</strong> При использовании нестандартного макета уведомлений следует особое внимание уделять + обеспечению совместимости такого макета с разными вариантами ориентации устройства и разрешения экрана. Этот + совет относится ко всем макетам View, но особенно важен он для уведомлений, поскольку + размер панели уведомлений весьма ограничен. Не следует делать свои нестандартные макеты слишком + сложными и обязательно нужно тестировать их на различных конфигурациях устройств. +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h4>Использование ресурсов стиля для текста нестандартного уведомления</h4> +<p> + Для форматирования текста нестандартного уведомления всегда используйте ресурсы стиля. Цвет фона + уведомления может меняться на разных устройствах и в разных версиях системы, а использование ресурсов стиля + позволяет это учесть. Начиная с версии Android 2.3 система определяет стиль для + текста уведомления со стандартным макетом. Если вы используете тот же стиль в приложениях, предназначенных для версии Android + 2.3 и последующих версий, то вы гарантируете видимость своего текста на фоне экрана. +</p> diff --git a/docs/html-intl/intl/ru/guide/topics/ui/overview.jd b/docs/html-intl/intl/ru/guide/topics/ui/overview.jd new file mode 100644 index 000000000000..0e9628b6d016 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=Обзор пользовательского интерфейса +@jd:body + + +<p>Все элементы интерфейса пользователя в приложении Android создаются с помощью объектов {@link android.view.View} и +{@link android.view.ViewGroup}. Объект {@link android.view.View} формирует +на экране элемент, с которым пользователь может взаимодействовать. Объект {@link android.view.ViewGroup} содержит +другие объекты {@link android.view.View} (и {@link android.view.ViewGroup}) для +определения макета интерфейса.</p> + +<p>Android предоставляет коллекцию подклассов {@link android.view.View} и {@link +android.view.ViewGroup}, которая включает в себя обычные элементы ввода (такие как кнопки и текстовые +поля) и различные модели макет (такие как линейный или относительный макет).</p> + + +<h2 id="Layout">Макеты пользовательского интерфейса</h2> + +<p>Пользовательский интерфейс для каждого компонента вашего приложения определяется с помощью иерархии объектов {@link +android.view.View} и {@link android.view.ViewGroup}, как показано на рисунке 1. Каждая группа просмотра +представляет собой невидимый контейнер, в котором объединены дочерние виды, причем дочерние виды могут представлять собой элементы +ввода или другие виджеты, которые +составляют часть пользовательского интерфейса. Эта древовидная иерархия может быть настолько простой или сложной, насколько +требуется (чем проще, тем лучше для производительности).</p> + +<img src="{@docRoot}images/viewgroup.png" alt="" /> +<p class="img-caption"><strong>Рисунок 1</strong>. Иллюстрация иерархии, которая определяет +макет интерфейса.</p> + +<p>Чтобы объявить свой макет, можно создать экземпляры объектов {@link android.view.View} в коде и запустить построение +дерева, но самый простой и наиболее эффективный способ — определение макета с помощью файла XML. +XML позволяет создавать удобочитаемую структуру макета, подобно HTML.</p> + +<p>Имя элемента XML для вида соответствует классу Android, к которому от относится. Так, элемент +<code><TextView></code> создает виджет {@link android.widget.TextView} в пользовательском интерфейсе, +а элемент <code><LinearLayout></code> создает группу просмотра {@link android.widget.LinearLayout} +. </p> + +<p>Например, простой вертикальный макет с текстом и кнопкой выглядит следующим образом:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + <TextView android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="I am a TextView" /> + <Button android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="I am a Button" /> +</LinearLayout> +</pre> + +<p>При загрузке ресурсов макетов в приложение Android инициализирует каждый узел макета в +объект режима выполнения, который можно использовать для определения дополнительного поведения, запроса состояния объекта или изменения +макета.</p> + +<p>Полное руководство по созданию макета пользовательского интерфейса см. в документе <a href="declaring-layout.html">Макеты +XML</a>. + + +<h2 id="UIComponents">Компоненты пользовательского интерфейса</h2> + +<p>Не обязательно создавать все элементы пользовательского интерфейса с помощью объектов {@link android.view.View} и {@link +android.view.ViewGroup}. Android предоставляет несколько компонентов приложений, которые содержат +стандартный макет пользовательского интерфейса, где остается лишь определить содержимое. Каждый из этих компонентов пользовательского интерфейса +содержит уникальный набор API, который описан в соответствующих документах, таких как <a href="{@docRoot}guide/topics/ui/actionbar.html">Строка действий</a>, <a href="{@docRoot}guide/topics/ui/dialogs.html">Диалоги</a> и <a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">Уведомления о состоянии</a>.</p> + + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/settings.jd b/docs/html-intl/intl/ru/guide/topics/ui/settings.jd new file mode 100644 index 000000000000..4325439721de --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=Настройки +page.tags=preference,preferenceactivity,preferencefragment + +@jd:body + + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>Содержание документа</h2> +<ol> + <li><a href="#Overview">Обзор</a> + <ol> + <li><a href="#SettingTypes">Предпочтения</a></li> + </ol> + </li> + <li><a href="#DefiningPrefs">Определение предпочтений в XML</a> + <ol> + <li><a href="#Groups">Создание групп настроек</a></li> + <li><a href="#Intents">Использование намерений</a></li> + </ol> + </li> + <li><a href="#Activity">Создание операции предпочтений</a></li> + <li><a href="#Fragment">Использование фрагментов предпочтений</a></li> + <li><a href="#Defaults">Значения настроек по умолчанию</a></li> + <li><a href="#PreferenceHeaders">Использование заголовков предпочтений</a> + <ol> + <li><a href="#CreateHeaders">Создание файла заголовков</a></li> + <li><a href="#DisplayHeaders">Отображение заголовков</a></li> + <li><a href="#BackCompatHeaders">Поддержка старых версий посредством заголовков предпочтений</a></li> + </ol> + </li> + <li><a href="#ReadingPrefs">Чтение предпочтений</a> + <ol> + <li><a href="#Listening">Отслеживание изменений предпочтений</a></li> + </ol> + </li> + <li><a href="#NetworkUsage">Контроль использования сети</a></li> + <li><a href="#Custom">Построение пользовательского предпочтения</a> + <ol> + <li><a href="#CustomSelected">Указание пользовательского интерфейса</a></li> + <li><a href="#CustomSave">Сохранение значения настройки</a></li> + <li><a href="#CustomInitialize">Инициализация текущего значения</a></li> + <li><a href="#CustomDefault">Предоставление значения по умолчанию</a></li> + <li><a href="#CustomSaveState">Сохранение и восстановление состояния предпочтений</a></li> + </ol> + </li> +</ol> + +<h2>Основные классы</h2> +<ol> + <li>{@link android.preference.Preference}</li> + <li>{@link android.preference.PreferenceActivity}</li> + <li>{@link android.preference.PreferenceFragment}</li> +</ol> + + +<h2>См. также:</h2> +<ol> + <li><a href="{@docRoot}design/patterns/settings.html">Руководство по дизайну настроек</a></li> +</ol> +</div> +</div> + + + + +<p>В приложениях часто содержатся настройки, которые позволяют пользователю изменять возможности и поведение приложения. Например, +некоторые приложения позволяют пользователям включать и выключать уведомления или указывать частоту синхронизации +данных приложения с облаком.</p> + +<p>Если вы хотите предоставить настройки для вашего приложения, вы должны использовать +API-интерфейсы {@link android.preference.Preference} системы Android для построения интерфейса, согласованного с +привычным для пользователей других приложений Android (включая системные настройки). В этом документе показано, +как построить настройки вашего приложения посредством API-интерфейсов {@link android.preference.Preference}.</p> + +<div class="note design"> +<p><strong>Дизайн настроек</strong></p> + <p>Подробную информацию о дизайне настроек см. в руководстве по дизайну <a href="{@docRoot}design/patterns/settings.html">настроек</a>.</p> +</div> + + +<img src="{@docRoot}images/ui/settings/settings.png" alt="" width="435" /> +<p class="img-caption"><strong>Рисунок 1.</strong> Снимки экранов настроек приложения Android +для обмена сообщениями. Выбор элемента, заданного посредством {@link android.preference.Preference}, +открывает интерфейс для изменения значения.</p> + + + + +<h2 id="Overview">Обзор</h2> + +<p>Вместо использования отображаемых объектов {@link android.view.View} для построения пользовательского интерфейса, настройки создаются +с помощью различных подклассов класса {@link android.preference.Preference}, который вы +объявляете в XML-файле.</p> + +<p>Объект {@link android.preference.Preference} является строительным блоком для отдельной +настройки. Каждый объект {@link android.preference.Preference} отображается в виде элемента в списке и предоставляет +соответствующий пользовательский интерфейс для изменения настройки пользователями. Например, {@link +android.preference.CheckBoxPreference} создает элемент списка, который показывает флажок, а {@link +android.preference.ListPreference} создает элемент, который открывает диалоговое окно со списком вариантов для выбора.</p> + +<p>Каждый добавляемый вами объект {@link android.preference.Preference} имеет соответствующую пару «ключ-значение», +которую система использует для сохранения настройки в файле {@link android.content.SharedPreferences} +значений настроек вашего приложения по умолчанию. Когда пользователь изменяет настройку, система обновляет соответствующее +значение в файле {@link android.content.SharedPreferences}. Вам потребуется +напрямую взаимодействовать с файлом, связанным с {@link android.content.SharedPreferences}, только в случае, +когда нужно прочитать значение для определения поведения вашего приложения на основе пользовательских настроек.</p> + +<p>Значение, сохраненное в {@link android.content.SharedPreferences} для каждой настройки, может относиться к одному из +следующих типов данных:</p> + +<ul> + <li>Логическое значение</li> + <li>Число с плавающей точкой</li> + <li>Целое число</li> + <li>Длинное целое число</li> + <li>Строка</li> + <li>Строка {@link java.util.Set}</li> +</ul> + +<p>Поскольку пользовательский интерфейс настроек вашего приложения создается посредством объектов {@link android.preference.Preference}, +а не +объектов {@link android.view.View}, вам потребуется использовать специализированные подклассы {@link android.app.Activity} или +{@link android.app.Fragment} для отображения настроек из списка:</p> + +<ul> + <li>Если ваше приложение поддерживает версии Android старше 3.0 (API уровня 10 и ниже), для построения операции +необходимо наследовать класс {@link android.preference.PreferenceActivity}.</li> + <li>В операционных системах Android 3.0 и более поздних версиях вы должны вместо этого использовать традиционный класс {@link android.app.Activity}, +который содержит объект {@link android.preference.PreferenceFragment} для отображения настроек вашего приложения. +Однако, когда у вас есть несколько групп настроек, вы можете также +использовать {@link android.preference.PreferenceActivity} для создания макета с двумя панелями для больших экранов.</li> +</ul> + +<p>Настройка объекта {@link android.preference.PreferenceActivity} и экземпляров {@link +android.preference.PreferenceFragment} описана в разделах <a href="#Activity">Создание операции предпочтения</a> и <a href="#Fragment">Использование +фрагментов предпочтений</a>.</p> + + +<h3 id="SettingTypes">Предпочтения</h3> + +<p>Каждая настройка для вашего приложения представлена конкретным подклассом класса {@link +android.preference.Preference}. Каждый подкласс содержит набор основных свойств, которые позволяют вам +указывать, например, заголовок для настройки и ее значение по умолчанию. Каждый подкласс также содержит +собственные специализированные свойства и пользовательский интерфейс. В качестве примера на рисунке 1 показан снимок экрана настроек +приложения Android для обмена сообщениями. Каждый элемент списка на экране настроек возвращается отдельным объектом {@link +android.preference.Preference}.</p> + +<p>Ниже приведены самые распространенные предпочтения:</p> + +<dl> + <dt>{@link android.preference.CheckBoxPreference}</dt> + <dd>Отображает элемент с флажком для настройки, которая может быть включена или выключена. Сохраненное +значение является логическим (<code>true</code>, если флажок установлен).</dd> + + <dt>{@link android.preference.ListPreference}</dt> + <dd>Открывает диалоговое окно со списком переключателей. Сохраненное значение +может относиться к одному из поддерживаемых типов значений (перечисленных выше).</dd> + + <dt>{@link android.preference.EditTextPreference}</dt> + <dd>Открывает диалоговое окно с виджетом {@link android.widget.EditText}. Сохраненное значение — {@link +java.lang.String}.</dd> +</dl> + +<p>См. класс {@link android.preference.Preference}, который содержит список всех остальных подклассов и их +соответствующих свойств.</p> + +<p>Конечно, встроенные классы не обеспечивают всех потребностей, и вашему приложению может понадобиться +что-либо более специализированное. Например, в настоящее время система не предоставляет класс {@link +android.preference.Preference} для выбора числа или даты. Поэтому вам может потребоваться определить +свой собственный подкласс {@link android.preference.Preference}. См. раздел <a href="#Custom">Построение пользовательского предпочтения</a>.</p> + + + +<h2 id="DefiningPrefs">Определение предпочтений в XML</h2> + +<p>Хотя вы можете создавать новые экземпляры объектов {@link android.preference.Preference} в режиме выполнения, вы должны +определить список настроек в файле XML с иерархией +объектов {@link android.preference.Preference}. Использование файла XML для определения вашей коллекции настроек предпочтительней, поскольку файл +обладает удобочитаемой структурой, которую легко обновлять. Кроме того, настройки вашего приложения +обычно определены заранее, хотя у вас сохраняется возможность изменять коллекцию в режиме выполнения.</p> + +<p>Каждый подкласс класса {@link android.preference.Preference} может быть объявлен посредством элемента XML, +который соответствует имени класса, например, {@code <CheckBoxPreference>}.</p> + +<p>Вы должны сохранить файл XML в каталоге {@code res/xml/}. Хотя вы можете назвать файл любым +именем, традиционно его называют {@code preferences.xml}. Обычно вам требуется лишь один файл, +поскольку ветви иерархии (которые открывают собственный список настроек) объявлены с помощью вложенных +экземпляров {@link android.preference.PreferenceScreen}.</p> + +<p class="note"><strong>Примечание.</strong> Если вы хотите создать макет с несколькими панелями для ваших +настроек, вам потребуются отдельные файлы XML для каждого фрагмента.</p> + +<p>Корневой узел XML-файла должен быть элементом {@link android.preference.PreferenceScreen +<PreferenceScreen>}. Внутри этого элемента вы добавляете каждый элемент {@link +android.preference.Preference}. Каждый дочерний элемент, который вы добавляете внутри элемента +{@link android.preference.PreferenceScreen <PreferenceScreen>}, отображается в виде одного +пункта в списке настроек.</p> + +<p>Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <CheckBoxPreference + android:key="pref_sync" + android:title="@string/pref_sync" + android:summary="@string/pref_sync_summ" + android:defaultValue="true" /> + <ListPreference + android:dependency="pref_sync" + android:key="pref_syncConnectionType" + android:title="@string/pref_syncConnectionType" + android:dialogTitle="@string/pref_syncConnectionType" + android:entries="@array/pref_syncConnectionTypes_entries" + android:entryValues="@array/pref_syncConnectionTypes_values" + android:defaultValue="@string/pref_syncConnectionTypes_default" /> +</PreferenceScreen> +</pre> + +<p>В этом примере есть {@link android.preference.CheckBoxPreference} и {@link +android.preference.ListPreference}. Оба содержат следующие три атрибута:</p> + +<dl> + <dt>{@code android:key}</dt> + <dd>Этот атрибут необходим для предпочтений, которые сохраняют значение данных. Он задает уникальный +ключ (строку), который использует система при сохранении значения этой настройки в {@link +android.content.SharedPreferences}. + <p>Этот атрибут <em>не является обязательным</em> только когда предпочтение представляет собой +{@link android.preference.PreferenceCategory} или {@link android.preference.PreferenceScreen}, либо +предпочтение указывает намерение {@link android.content.Intent} для вызова (посредством элемента <a href="#Intents">{@code <intent>}</a>) или фрагмент {@link android.app.Fragment} для отображения (с помощью атрибута <a href="{@docRoot}reference/android/preference/Preference.html#attr_android:fragment">{@code +android:fragment}</a>).</p> + </dd> + <dt>{@code android:title}</dt> + <dd>Этот атрибут предоставляет имя настройки, отображаемое для пользователя.</dd> + <dt>{@code android:defaultValue}</dt> + <dd>Этот атрибут указывает исходное значение, которое система должна установить в файле {@link +android.content.SharedPreferences}. Вы должны указать значения по умолчанию для всех +настроек.</dd> +</dl> + +<p>Для получения информации обо всех других поддерживаемых атрибутов см. документацию {@link +android.preference.Preference} (и соответствующий подкласс).</p> + + +<div class="figure" style="width:300px"> + <img src="{@docRoot}images/ui/settings/settings-titles.png" alt="" /> + <p class="img-caption"><strong>Рисунок 2.</strong> Создание категорий + с заголовками. <br/><b>1.</b> Категория задана элементом {@link +android.preference.PreferenceCategory <PreferenceCategory>}. <br/><b>2.</b> Заголовок +задан посредством атрибута {@code android:title}.</p> +</div> + + +<p>Когда список ваших настроек содержит более 10 элементов, вы, вероятно, захотите добавить заголовки для +определения групп настроек или отобразить эти группы +на отдельном экране. Эти возможности описаны в следующих разделах.</p> + + +<h3 id="Groups">Создание групп настроек</h3> + +<p>Если вы представляете список из 10 или более настроек, пользователям +может быть трудно их просматривать, воспринимать и обрабатывать. Это можно исправить, +разделив некоторые или все настройки на группы, что эффективно преобразует один длинный список в несколько +более коротких списков. Группа связанных настроек может быть представлена одним из двух способов:</p> + +<ul> + <li><a href="#Titles">Использование заголовков</a></li> + <li><a href="#Subscreens">Использование подэкранов</a></li> +</ul> + +<p>Вы можете пользоваться одним или обоими из этих методов группировки для организации настроек в вашем приложении. Принимая +решение об используемом варианте и о разделении настроек на группы, вы должны следовать инструкциям в разделе +<a href="{@docRoot}design/patterns/settings.html">Настройки</a> руководства «Дизайн для Android».</p> + + +<h4 id="Titles">Использование заголовков</h4> + +<p>Если вы хотите создать разделители с заголовками между группами настроек (как показано на рисунке 2), +поместите каждую группу объектов {@link android.preference.Preference} внутри {@link +android.preference.PreferenceCategory}.</p> + +<p>Например:</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <PreferenceCategory + android:title="@string/pref_sms_storage_title" + android:key="pref_key_storage_settings"> + <CheckBoxPreference + android:key="pref_key_auto_delete" + android:summary="@string/pref_summary_auto_delete" + android:title="@string/pref_title_auto_delete" + android:defaultValue="false"... /> + <Preference + android:key="pref_key_sms_delete_limit" + android:dependency="pref_key_auto_delete" + android:summary="@string/pref_summary_delete_limit" + android:title="@string/pref_title_sms_delete"... /> + <Preference + android:key="pref_key_mms_delete_limit" + android:dependency="pref_key_auto_delete" + android:summary="@string/pref_summary_delete_limit" + android:title="@string/pref_title_mms_delete" ... /> + </PreferenceCategory> + ... +</PreferenceScreen> +</pre> + + +<h4 id="Subscreens">Использование подэкранов</h4> + +<p>Если вы хотите поместить группу настроек на подэкран (как показано на рисунке 3), поместите каждую группу +объектов {@link android.preference.Preference} внутри {@link +android.preference.PreferenceScreen}.</p> + +<img src="{@docRoot}images/ui/settings/settings-subscreen.png" alt="" /> +<p class="img-caption"><strong>Рисунок 3.</strong> Создание подэкранов. Элемент {@code +<PreferenceScreen>} +создает пункт, при выборе которого открывается отдельный список вложенных настроек.</p> + +<p>Например:</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- opens a subscreen of settings --> + <PreferenceScreen + android:key="button_voicemail_category_key" + android:title="@string/voicemail" + android:persistent="false"> + <ListPreference + android:key="button_voicemail_provider_key" + android:title="@string/voicemail_provider" ... /> + <!-- opens another nested subscreen --> + <PreferenceScreen + android:key="button_voicemail_setting_key" + android:title="@string/voicemail_settings" + android:persistent="false"> + ... + </PreferenceScreen> + <RingtonePreference + android:key="button_voicemail_ringtone_key" + android:title="@string/voicemail_ringtone_title" + android:ringtoneType="notification" ... /> + ... + </PreferenceScreen> + ... +</PreferenceScreen> +</pre> + + +<h3 id="Intents">Использование намерений</h3> + +<p>В некоторых случаях может потребоваться, чтобы элемент предпочтений открывал другую операцию, а не +экран настроек, например, веб-браузер для просмотра веб-страницы. Чтобы вызвать {@link +android.content.Intent}, когда пользователь выбирает элемент предпочтений, добавьте элемент {@code <intent>} +в качестве дочернего элемента соответствующего элемента {@code <Preference>}.</p> + +<p>Например, здесь показано использование элемента предпочтений для открытия веб-страницы:</p> + +<pre> +<Preference android:title="@string/prefs_web_page" > + <intent android:action="android.intent.action.VIEW" + android:data="http://www.example.com" /> +</Preference> +</pre> + +<p>Вы можете создавать неявные и явные намерения с помощью следующих атрибутов:</p> + +<dl> + <dt>{@code android:action}</dt> + <dd>Назначаемое действие, как +в методе {@link android.content.Intent#setAction setAction()}.</dd> + <dt>{@code android:data}</dt> + <dd>Назначаемые данные, как в методе {@link android.content.Intent#setData setData()}.</dd> + <dt>{@code android:mimeType}</dt> + <dd>Назначаемый тип MIME, как +в методе {@link android.content.Intent#setType setType()}.</dd> + <dt>{@code android:targetClass}</dt> + <dd>Часть имени компонента, означающая класс, как в методе {@link android.content.Intent#setComponent +setComponent()}.</dd> + <dt>{@code android:targetPackage}</dt> + <dd>Пакетная часть имени компонента, как в методе {@link +android.content.Intent#setComponent setComponent()}.</dd> +</dl> + + + +<h2 id="Activity">Создание операции предпочтений</h2> + +<p>Для отображения ваших настроек в операции наследуйте класс {@link +android.preference.PreferenceActivity}. Это наследование традиционного класса {@link +android.app.Activity}, который отображает список настроек на основе иерархии объектов {@link +android.preference.Preference}. {@link android.preference.PreferenceActivity} +автоматически сохраняет настройки, связанные с каждым объектом {@link +android.preference.Preference}, когда пользователь вносит изменения.</p> + +<p class="note"><strong>Примечание.</strong> При разработке приложения для версии Android 3.0 +или выше вместо этого следует использовать {@link android.preference.PreferenceFragment}. Прочитайте следующий раздел +<a href="#Fragment">Использование фрагментов предпочтений</a>.</p> + +<p>Запомните самое важное: не загружайте макет отображаемых объектов во время обратного вызова {@link +android.preference.PreferenceActivity#onCreate onCreate()}. Вместо этого вызовите {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} для +добавления предпочтений, объявленных в XML-файле для операции. Например, здесь приведен + минимальный код, необходимый для работы {@link android.preference.PreferenceActivity}:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } +} +</pre> + +<p>Этого кода действительно достаточно для некоторых приложений, поскольку как только пользователь изменяет предпочтение, +система сохраняет изменения в файле {@link android.content.SharedPreferences} по умолчанию, который +другие компоненты вашего приложения могут читать, когда требуется проверить пользовательские настройки. Однако многим приложениям +требуется немного больше кода, чтобы отслеживать изменения, происходящие с предпочтениями. +Информацию об отслеживании изменений в файле {@link android.content.SharedPreferences} +см. в разделе <a href="#ReadingPrefs">Чтение предпочтений</a>.</p> + + + + +<h2 id="Fragment">Использование фрагментов предпочтений</h2> + +<p>При разработке приложений для Android 3.0 (API уровня 11) и более поздних версий необходимо использовать {@link +android.preference.PreferenceFragment} для отображения списка +объектов {@link android.preference.Preference}. Вы можете добавить {@link android.preference.PreferenceFragment} в любую операцию, при этом +необязательно использовать {@link android.preference.PreferenceActivity}.</p> + +<p><a href="{@docRoot}guide/components/fragments.html">Фрагменты</a> обеспечивают более +универсальную архитектуру для вашего приложения по сравнению с использованием отдельных операций, вне зависимости от типа +создаваемой операции. Фактически, для управления отображением ваших настроек мы предлагаем вам использовать {@link +android.preference.PreferenceFragment} вместо {@link +android.preference.PreferenceActivity} при каждой возможности.</p> + +<p>Ваша реализация {@link android.preference.PreferenceFragment} может содержать просто +определение метода {@link android.preference.PreferenceFragment#onCreate onCreate()} для загрузки +файла предпочтений посредством {@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()}. Например:</p> + +<pre> +public static class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.preferences); + } + ... +} +</pre> + +<p>Затем вы можете добавить этот фрагмент в операцию {@link android.app.Activity}, как вы сделали бы это для любого другого фрагмента +{@link android.app.Fragment}. Например:</p> + +<pre> +public class SettingsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SettingsFragment()) + .commit(); + } +} +</pre> + +<p class="note"><strong>Примечание.</strong> Фрагмент {@link android.preference.PreferenceFragment} не содержит +собственного объекта {@link android.content.Context}. Если вам требуется объект {@link android.content.Context}, +вы можете вызвать{@link android.app.Fragment#getActivity()}. Однако разработчик должен быть внимательным и вызывать метод +{@link android.app.Fragment#getActivity()} только в том случае, когда фрагмент прикреплен к операции. Если +фрагмент еще не прикреплен или был откреплен в конце его жизненного цикла, метод {@link +android.app.Fragment#getActivity()} вернет null.</p> + + +<h2 id="Defaults">Установка значений по умолчанию</h2> + +<p>Вероятно, создаваемые вами предпочтения определяют важное поведение вашего приложения, поэтому +необходимо инициализировать соответствующий файл {@link android.content.SharedPreferences}, +записав в него значения по умолчанию для каждого предпочтения {@link android.preference.Preference} при первом запуске вашего +приложения пользователем.</p> + +<p>В первую очередь необходимо указать значение по умолчанию для каждого объекта {@link +android.preference.Preference} +в вашем XML-файле посредством атрибута {@code android:defaultValue}. Значение может относиться к любому +типу данных, подходящему для соответствующего объекта {@link android.preference.Preference}. Например: +</p> + +<pre> +<!-- default value is a boolean --> +<CheckBoxPreference + android:defaultValue="true" + ... /> + +<!-- default value is a string --> +<ListPreference + android:defaultValue="@string/pref_syncConnectionTypes_default" + ... /> +</pre> + +<p>Затем из метода {@link android.app.Activity#onCreate onCreate()} основной операции вашего приложения +(и из любой другой операции, через которую пользователь может войти в ваше приложение +в первый раз) вызовите {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}:</p> + +<pre> +PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false); +</pre> + +<p>Вызов этого метода при выполнении {@link android.app.Activity#onCreate onCreate()} гарантирует, что ваше +приложение правильно инициализируется и получит настройки по умолчанию, которые могут потребоваться вашему приложению +для определенного поведения (например, следует ли загружать данные при работе +в сотовой сети).</p> + +<p>Этот метод имеет три аргумента:</p> +<ul> + <li>{@link android.content.Context} вашего приложения.</li> + <li>Идентификатор ресурса для XML-файла предпочтений, для которого вы хотите установить значения по умолчанию.</li> + <li>Логическое значение, которое указывает, требуется ли значения по умолчанию устанавливать более одного раза. +<p>При значении <code>false</code> система устанавливает значения по умолчанию только в том случае, если этот метод никогда не вызывался ранее +(или атрибут {@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES} +в файле общих предпочтений по умолчанию имеет значение false).</p></li> +</ul> + +<p>Когда для третьего аргумента установлено значение <code>false</code>, вы можете вызывать этот метод +при каждом запуске операции, не опасаясь перезаписи сохраненных пользовательских предпочтений из-за их сброса в состояние +по умолчанию. Однако, если установить для этого аргумента значение <code>true</code>, вы будете перезаписывать все предыдущие +значения значениями по умолчанию.</p> + + + +<h2 id="PreferenceHeaders">Использование заголовков предпочтений</h2> + +<p>В редких случаях может потребоваться такая структура настроек, при которой на первом экране отображается только +список <a href="#Subscreens">подэкранов</a> (например, как в приложении системных настроек, +показанных на рисунках 4 и 5). При разработке такого дизайна для Android 3.0 и более поздних версий вы должны +использовать новую возможность Android 3.0 — «заголовки», вместо создания подэкранов посредством вложенных +элементов {@link android.preference.PreferenceScreen}.</p> + +<p>Чтобы создать настройки с заголовками, выполните следующие действия:</p> +<ol> + <li>Выделите каждую группу настроек в отдельный экземпляр {@link +android.preference.PreferenceFragment}. Таким образом, каждая группа настроек должна иметь отдельный +XML-файл.</li> + <li>Создайте XML-файл заголовков, в котором перечислены все группы настроек и объявления, какой фрагмент +содержит соответствующий список настроек.</li> + <li>Наследуйте класс {@link android.preference.PreferenceActivity}, который будет содержать ваши настройки.</li> + <li>Реализуйте обратный вызов {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}, чтобы указать +файл заголовков.</li> +</ol> + +<p>Огромное преимущество использования этого дизайна состоит в том, что при запуске на больших экранах {@link android.preference.PreferenceActivity} +автоматически создает макет с двумя панелями, показанный на рисунке 4.</p> + +<p>Даже если ваше приложение поддерживает версии Android старше 3.0, вы можете создать +приложение, использующее {@link android.preference.PreferenceFragment} для двухпанельного представления на +новых устройствах и поддерживающее традиционную многоэкранную иерархию на более старых +устройствах (см. раздел <a href="#BackCompatHeaders">Поддержка старых версий посредством +заголовков предпочтений</a>).</p> + +<img src="{@docRoot}images/ui/settings/settings-headers-tablet.png" alt="" /> +<p class="img-caption"><strong>Рисунок 4.</strong> Двухпанельный макет с заголовками. <br/><b>1.</b> Заголовки +определяются посредством XML-файла заголовков. <br/><b>2.</b> Каждая группа настроек определяется с помощью фрагмента +{@link android.preference.PreferenceFragment}, который указывается элементом {@code <header>} +в файле заголовков.</p> + +<img src="{@docRoot}images/ui/settings/settings-headers-handset.png" alt="" /> +<p class="img-caption"><strong>Рисунок 5.</strong> Смартфон с заголовками настроек. При выборе +пункта вместо заголовков отображается соответствующий +фрагмент {@link android.preference.PreferenceFragment}.</p> + + +<h3 id="CreateHeaders" style="clear:left">Создание файла заголовков</h3> + +<p>Каждая группа настроек в вашем списке заголовков указывается отдельным элементом {@code <header>} +внутри корневого элемента {@code <preference-headers>}. Например:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> + <header + android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne" + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" /> + <header + android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo" + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" > + <!-- key/value pairs can be included as arguments for the fragment. --> + <extra android:name="someKey" android:value="someHeaderValue" /> + </header> +</preference-headers> +</pre> + +<p>Посредством атрибута {@code android:fragment} каждый заголовок объявляет экземпляр фрагмента {@link +android.preference.PreferenceFragment}, который должен открываться при выборе этого заголовка пользователем.</p> + +<p>Элемент {@code <extras>} позволяет передавать пары «ключ-значение» фрагменту в объекте {@link +android.os.Bundle}. Фрагмент может извлекать аргументы путем вызова метода {@link +android.app.Fragment#getArguments()}. Вы можете передавать аргументы фрагменту по различным причинам, +но хорошим поводом является повторное использование одного и того же подкласса {@link +android.preference.PreferenceFragment} для каждой группы и использование аргументов для указания +XML-файла предпочтений, который должен быть загружен фрагментом.</p> + +<p>Например, здесь приведен фрагмент, который можно использовать повторно для нескольких групп настроек, когда каждый +заголовок определяет аргумент {@code <extra>} с ключом {@code "settings"}:</p> + +<pre> +public static class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String settings = getArguments().getString("settings"); + if ("notifications".equals(settings)) { + addPreferencesFromResource(R.xml.settings_wifi); + } else if ("sync".equals(settings)) { + addPreferencesFromResource(R.xml.settings_sync); + } + } +} +</pre> + + + +<h3 id="DisplayHeaders">Отображение заголовков</h3> + +<p>Чтобы отобразить заголовки предпочтений, вы должны реализовать метод обратного вызова {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} и вызвать +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}. Например:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity { + @Override + public void onBuildHeaders(List<Header> target) { + loadHeadersFromResource(R.xml.preference_headers, target); + } +} +</pre> + +<p>Когда пользователь выбирает пункт в списке заголовков, система открывает связанный {@link +android.preference.PreferenceFragment}.</p> + +<p class="note"><strong>Примечание.</strong> При использовании заголовков предпочтений ваш подкласс {@link +android.preference.PreferenceActivity} не должен реализовывать метод {@link +android.preference.PreferenceActivity#onCreate onCreate()}, поскольку единственной обязательной задачей +операции является загрузка заголовков.</p> + + +<h3 id="BackCompatHeaders">Поддержка старых версий с заголовками предпочтений</h3> + +<p>Если ваше приложение поддерживает версии Android старше 3.0, вы можете использовать заголовки для +предоставления двухпанельного макета при работе на Android 3.0 или более поздней версии. Достаточно создать +дополнительный XML-файл настроек, использующий базовые элементы {@link android.preference.Preference +<Preference>}, которые ведут себя аналогично пунктам заголовка (для использования в более старых версиях +Android).</p> + +<p>Вместо открытия новых экранов {@link android.preference.PreferenceScreen} каждый из элементов {@link +android.preference.Preference <Preference>} отправляет намерение {@link android.content.Intent} в +{@link android.preference.PreferenceActivity} с указанием XML-файла предпочтений +для загрузки.</p> + +<p>В качестве примера приведен XML-файл для заголовков предпочтений, который используется в Android версии 3.0 +и более поздних версий ({@code res/xml/preference_headers.xml}):</p> + +<pre> +<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> + <header + android:fragment="com.example.prefs.SettingsFragmentOne" + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" /> + <header + android:fragment="com.example.prefs.SettingsFragmentTwo" + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" /> +</preference-headers> +</pre> + +<p>А здесь представлен файл предпочтений, который содержит те же самые заголовки для версий старше +Android 3.0 ({@code res/xml/preference_headers_legacy.xml}):</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <Preference + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" > + <intent + android:targetPackage="com.example.prefs" + android:targetClass="com.example.prefs.SettingsActivity" + android:action="com.example.prefs.PREFS_ONE" /> + </Preference> + <Preference + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" > + <intent + android:targetPackage="com.example.prefs" + android:targetClass="com.example.prefs.SettingsActivity" + android:action="com.example.prefs.PREFS_TWO" /> + </Preference> +</PreferenceScreen> +</pre> + +<p>Так как поддержка {@code <preference-headers>} была добавлена в версии Android 3.0, система вызывает +{@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} в методе {@link +android.preference.PreferenceActivity} только при работе в Android версии 3.0 или более поздней версии. Чтобы загрузить +«старый» файл заголовков ({@code preference_headers_legacy.xml}), вы должны проверить версию Android +и, если версия старше Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}), вызвать {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} для +загрузки старого файла заголовков. Например:</p> + +<pre> +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // Load the legacy preferences headers + addPreferencesFromResource(R.xml.preference_headers_legacy); + } +} + +// Called only on Honeycomb and later +@Override +public void onBuildHeaders(List<Header> target) { + loadHeadersFromResource(R.xml.preference_headers, target); +} +</pre> + +<p>Остается обработать намерение {@link android.content.Intent}, переданное +в операцию, чтобы идентифицировать файл предпочтений для загрузки. Поэтому извлеките операцию намерения и сравните ее +с известными строками действия, которые вы использовали в тегах {@code <intent>} XML-файла предпочтений:</p> + +<pre> +final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE"; +... + +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String action = getIntent().getAction(); + if (action != null && action.equals(ACTION_PREFS_ONE)) { + addPreferencesFromResource(R.xml.preferences); + } + ... + + else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // Load the legacy preferences headers + addPreferencesFromResource(R.xml.preference_headers_legacy); + } +} +</pre> + +<p>При этом помните, что последующие вызовы {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} будут помещать +все предпочтения в один список, поэтому обязательно используйте операторы else-if, чтобы обеспечить только +однократный вызов метода при изменении условий.</p> + + + + + +<h2 id="ReadingPrefs">Чтение предпочтений</h2> + +<p>По умолчанию все предпочтения вашего приложения сохраняются в файле, который доступен из любого места +вашего приложения посредством вызова статического метода {@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()}. Он возвращает объект {@link +android.content.SharedPreferences}, содержащий все пары «ключ-значение», связанные +с объектами {@link android.preference.Preference}, использованными в вашей операции {@link +android.preference.PreferenceActivity}.</p> + +<p>В качестве примера показано чтение одного из значений предпочтений из любой другой операции в вашем +приложении:</p> + +<pre> +SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); +String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, ""); +</pre> + + + +<h3 id="Listening">Отслеживание изменений предпочтений</h3> + +<p>Существует несколько причин, по которым вы можете захотеть получать уведомления, как только пользователь изменяет одно из +предпочтений. Чтобы получать обратный вызов при изменении любого из предпочтений, +реализуйте интерфейс {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} и зарегистрируйте приемник для объекта +{@link android.content.SharedPreferences} посредством вызова {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}.</p> + +<p>Этот интерфейс содержит только один метод обратного вызова, {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()}, и вы, вероятно, сочтете его самым простым способом реализации интерфейса в составе своей +операции. Например:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity + implements OnSharedPreferenceChangeListener { + public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType"; + ... + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals(KEY_PREF_SYNC_CONN)) { + Preference connectionPref = findPreference(key); + // Set summary to be the user-description for the selected value + connectionPref.setSummary(sharedPreferences.getString(key, "")); + } + } +} +</pre> + +<p>В этом примере метод проверяет, выполнено ли изменение настройки для известного ключа предпочтений. Он +вызывает {@link android.preference.PreferenceActivity#findPreference findPreference()} для получения объекта +{@link android.preference.Preference}, который был изменен, поэтому он может изменить сводку пункта +, описывающего выбор пользователя. То есть, когда настройка представляет собой {@link +android.preference.ListPreference} или другую настройку с несколькими вариантами выбора, при изменении этой настройки вы должны вызвать {@link +android.preference.Preference#setSummary setSummary()} для отображения +текущего состояния (например, настройка спящего режима, показанная на рисунке 5).</p> + +<p class="note"><strong>Примечание.</strong> В соответствии с рекомендациями раздела <a href="{@docRoot}design/patterns/settings.html">Настройки</a> руководства «Дизайн для Android», мы рекомендуем вам обновлять +сводку для {@link android.preference.ListPreference} при каждом изменении предпочтения пользователем, +чтобы описать текущую настройку.</p> + +<p>Для правильного управления жизненным циклом в операции мы рекомендуем вам регистрировать или отменять регистрацию +вашего приемника {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener} во время выполнения обратных вызовов {@link +android.app.Activity#onResume} и {@link android.app.Activity#onPause} соответственно:</p> + +<pre> +@Override +protected void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); +} + +@Override +protected void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); +} +</pre> + +<p class="caution"><strong>Внимание!</strong> Когда вы вызываете приемник {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()}, диспетчер предпочтений не +сохраняет строгую ссылку на приемник. Вы должны сохранить строгую +ссылку на приемник, в противном случае она будет чувствительной к очистке памяти. Мы +рекомендуем хранить ссылку на приемник в данных экземпляра объекта +, который будет существовать, пока вам нужен приемник.</p> + +<p>Например, в следующем коде вызывающий объект не сохраняет +ссылку на приемник. В результате этого приемник будет удален при очистке памяти +и через некоторое время приведет к сбою:</p> + +<pre> +prefs.registerOnSharedPreferenceChangeListener( + // Bad! The listener is subject to garbage collection! + new SharedPreferences.OnSharedPreferenceChangeListener() { + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + // listener implementation + } +}); +</pre> + +<p>Вместо этого сохраните ссылку на приемник в поле данных экземпляра объекта +, который будет существовать, пока нужен приемник:</p> + +<pre> +SharedPreferences.OnSharedPreferenceChangeListener listener = + new SharedPreferences.OnSharedPreferenceChangeListener() { + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + // listener implementation + } +}; +prefs.registerOnSharedPreferenceChangeListener(listener); +</pre> + +<h2 id="NetworkUsage">Контроль использования сети</h2> + + +<p>Начиная с версии Android 4.0, системное приложение «Настройки» позволяет пользователям просматривать использование +сетевых данных приложениями, работающими на переднем плане и в фоновом режиме. После этого пользователи могут +отключить использование данных в фоновом режиме для отдельных приложений. Для того, чтобы пользователи не отключали доступ вашего приложения к данным +в фоновом режиме, вы должны эффективно использовать подключение в режиме передачи данных и предоставить +пользователям возможность настройки использования данных вашим приложением посредством настроек приложения.<p> + +<p>Например, вы можете позволить пользователям управлять частотой синхронизации данных приложения, выполнением загрузки +только в режиме подключения по Wi-Fi, использованием данных в роуминге и т. д. Когда +эти возможности управления доступны, пользователи с меньшей вероятностью отключат доступ вашего приложения к данным, +когда оно достигает установленных в системных настройках лимитов, поскольку вместо отключения они могут +точно контролировать объем данных, который использует ваше приложение.</p> + +<p>После добавления необходимых предпочтений в вашу операцию {@link android.preference.PreferenceActivity} +для управления поведением вашего приложения в отношении данных вы должны добавить фильтр намерений для {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} в вашем файле манифеста. Например:</p> + +<pre> +<activity android:name="SettingsActivity" ... > + <intent-filter> + <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> +</activity> +</pre> + +<p>Этот фильтр манифеста указывает системе, что эта операция управляет использованием +данных вашим приложением. Так, когда пользователь проверяет объем использованных приложением данных в системном приложении +«Настройки», отображается кнопка <em>Просмотреть настройки приложения</em>, которая запускает вашу операцию +{@link android.preference.PreferenceActivity}, чтобы пользователь мог уточнить, сколько данных использует +ваше приложение.</p> + + + + + + + +<h2 id="Custom">Построение пользовательского предпочтения</h2> + +<p>Система Android содержит множество подклассов {@link android.preference.Preference}, которые +позволяют вам строить пользовательский интерфейс для нескольких различных типов настроек. +Тем не менее, вы можете обнаружить, что для нужной вам настройки нет встроенного решения, например, для выбора +числа или даты. В таком случае вам потребуется создать нестандартное предпочтение путем наследования +класса {@link android.preference.Preference} или одного из других подклассов.</p> + +<p>При наследовании класса {@link android.preference.Preference} нужно выполнить несколько +важных пунктов:</p> + +<ul> + <li>Укажите пользовательский интерфейс, который должен отображаться при выборе этой настройки пользователем.</li> + <li>При необходимости сохраните значение настройки.</li> + <li>Инициализируйте {@link android.preference.Preference} текущим значением (или значением по умолчанию), +когда предпочтение отображается.</li> + <li>Укажите значение по умолчанию в ответ на запрос системы.</li> + <li>Если {@link android.preference.Preference} содержит свой собственный пользовательский интерфейс (например, диалоговое окно), сохраните +и восстановите состояние для обработки изменений жизненного цикла (например, когда пользователь поворачивает экран).</li> +</ul> + +<p>В следующих разделах описано выполнение каждой из этих задач.</p> + + + +<h3 id="CustomSelected">Указание пользовательского интерфейса</h3> + + <p>Если вы наследуете класс {@link android.preference.Preference} непосредственно, вы должны реализовать метод +{@link android.preference.Preference#onClick()}, чтобы задать действие, происходящее при выборе +пункта пользователем. Однако большая часть нестандартных настроек наследует {@link android.preference.DialogPreference}, чтобы +отобразить диалоговое окно, что упрощает процедуру. Когда вы наследуете {@link +android.preference.DialogPreference}, вы должны вызвать {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()}, находясь в конструкторе +класса, чтобы указать макет диалогового окна.</p> + + <p>В качестве примера показан конструктор нестандартного диалогового окна {@link +android.preference.DialogPreference}, в котором объявляется макет и указывается текст для +положительной и отрицательной кнопок диалога по умолчанию:</p> + +<pre> +public class NumberPickerPreference extends DialogPreference { + public NumberPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setDialogLayoutResource(R.layout.numberpicker_dialog); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + + setDialogIcon(null); + } + ... +} +</pre> + + + +<h3 id="CustomSave">Сохранение значения настройки</h3> + +<p>Вы можете сохранить значение настройки в любой момент, вызвав один из методов {@code persist*()} класса {@link +android.preference.Preference}, например, {@link +android.preference.Preference#persistInt persistInt()}, если настройка имеет целое значение, или +{@link android.preference.Preference#persistBoolean persistBoolean()} для сохранения логического значения.</p> + +<p class="note"><strong>Примечание.</strong> Каждое предпочтение {@link android.preference.Preference} может сохранять только один +тип данных, поэтому вы должны использовать метод {@code persist*()}, соответствующий типу данных, используемых вашим +пользовательским предпочтением {@link android.preference.Preference}.</p> + +<p>Выбор метода сохранения настройки может зависеть от наследованного класса {@link +android.preference.Preference}. Если вы наследуете {@link +android.preference.DialogPreference}, вы должны сохранять значение только при закрытии диалога +с положительным результатом (пользователь нажал кнопку «OK»).</p> + +<p>Когда {@link android.preference.DialogPreference} закрывается, система вызывает метод {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()}. Этот метод содержит +логический аргумент, который указывает, является ли результат пользователя «положительным» — если аргумент имеет значение +<code>true</code>, значит пользователь выбрал положительную кнопку и вы должны сохранить новое значение. Например: +</p> + +<pre> +@Override +protected void onDialogClosed(boolean positiveResult) { + // When the user selects "OK", persist the new value + if (positiveResult) { + persistInt(mNewValue); + } +} +</pre> + +<p>В этом примере <code>mNewValue</code> — это член класса, который содержит текущее значение +настройки. При вызове {@link android.preference.Preference#persistInt persistInt()} значение сохраняется +в файл {@link android.content.SharedPreferences} (с автоматическим использованием ключа, +указанного в XML-файле для этого предпочтения {@link android.preference.Preference}).</p> + + +<h3 id="CustomInitialize">Инициализация текущего значения</h3> + +<p>Когда система добавляет ваше предпочтение {@link android.preference.Preference} на экран, она +вызывает метод {@link android.preference.Preference#onSetInitialValue onSetInitialValue()}, чтобы уведомить вас, +имеет ли настройка сохраненное значение. Если сохраненного значения нет, этот вызов предоставляет вам +значение по умолчанию.</p> + +<p>Метод {@link android.preference.Preference#onSetInitialValue onSetInitialValue()} передает +логическое значение, <code>restorePersistedValue</code>, чтобы показать, было ли уже сохранено значение +для настройки. Если значение равно <code>true</code>, вы должны извлечь сохраненное значение, вызвав +один из методов {@code getPersisted*()} класса {@link +android.preference.Preference}, например, {@link +android.preference.Preference#getPersistedInt getPersistedInt()} для целого значения. Обычно +требуется извлечь сохраненное значение, чтобы можно было правильно обновить пользовательский интерфейс для отражения +ранее сохраненного значения.</p> + +<p>Если <code>restorePersistedValue</code> имеет значение <code>false</code>, вы +должны использовать значение по умолчанию, которое передается во втором аргументе.</p> + +<pre> +@Override +protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + if (restorePersistedValue) { + // Restore existing state + mCurrentValue = this.getPersistedInt(DEFAULT_VALUE); + } else { + // Set default state from the XML attribute + mCurrentValue = (Integer) defaultValue; + persistInt(mCurrentValue); + } +} +</pre> + +<p>Каждый метод {@code getPersisted*()} содержит аргумент, который указывает +значение по умолчанию на случай, когда действительно нет сохраненного значения, или не существует ключ. В +приведенном выше примере локальная константа служит для указания значения по умолчанию на случай, если {@link +android.preference.Preference#getPersistedInt getPersistedInt()} не может вернуть сохраненное значение.</p> + +<p class="caution"><strong>Внимание!</strong> Вы <strong>не можете</strong> использовать +<code>defaultValue</code> в качестве значения по умолчанию в методе {@code getPersisted*()}, так как +его значение всегда равно null, когда <code>restorePersistedValue</code> имеет значение <code>true</code>.</p> + + +<h3 id="CustomDefault">Предоставление значения по умолчанию</h3> + +<p>Если экземпляр вашего класса {@link android.preference.Preference} указывает значение по умолчанию +(с помощью атрибута {@code android:defaultValue}), +система вызывает {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()}, когда она создает экземпляр объекта для извлечения значения. Вы должны реализовать +этот метод, чтобы сохранить значение по умолчанию для системы в {@link +android.content.SharedPreferences}. Например:</p> + +<pre> +@Override +protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInteger(index, DEFAULT_VALUE); +} +</pre> + +<p>Аргументы метода предоставляют все необходимое: массив атрибутов и указатель +положения {@code android:defaultValue}, который вы должны извлечь. Причина, по которой вы должны +реализовать этот метод, чтобы извлечь значение по умолчанию из атрибута, состоит в том, что вы должны указать +локальное значение по умолчанию для атрибута в случае, когда значение не определено.</p> + + + +<h3 id="CustomSaveState">Сохранение и восстановление состояния предпочтений</h3> + +<p>Как и {@link android.view.View} в макете, ваш подкласс {@link android.preference.Preference} +отвечает за сохранение и восстановление своего состояния в случае перезапуска операции или фрагмента +(например, когда пользователь поворачивает экран). Чтобы правильно сохранять и восстанавливать +состояние вашего класса {@link android.preference.Preference}, вы должны реализовать +методы обратного вызова жизненного цикла {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} и {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}.</p> + +<p>Состояние вашего {@link android.preference.Preference} определяется объектом, который реализует +интерфейс {@link android.os.Parcelable}. Система Android предоставляет вам такой объект +в качестве начальной точки для определения вашего объекта состояния: класс {@link +android.preference.Preference.BaseSavedState}.</p> + +<p>Чтобы определить, как ваш класс {@link android.preference.Preference} сохраняет свое состояние, вы должны +наследовать класс {@link android.preference.Preference.BaseSavedState}. Вы должны переопределить лишь + несколько методов и определить объект +{@link android.preference.Preference.BaseSavedState#CREATOR}.</p> + +<p>Для большинства приложений вы можете скопировать следующую реализацию и просто изменить строки, которые +обрабатывают {@code value}, если ваш подкласс {@link android.preference.Preference} сохраняет типы +данных, отличные от целых.</p> + +<pre> +private static class SavedState extends BaseSavedState { + // Member that holds the setting's value + // Change this data type to match the type saved by your Preference + int value; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + // Get the current preference's value + value = source.readInt(); // Change this to read the appropriate data type + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + // Write the preference's value + dest.writeInt(value); // Change this to write the appropriate data type + } + + // Standard creator object using an instance of this class + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; +} +</pre> + +<p>После добавления показанной выше реализации {@link android.preference.Preference.BaseSavedState} +в ваше приложение (обычно в качестве подкласса вашего подкласса {@link android.preference.Preference}), вам +потребуется реализовать методы {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} и {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} для вашего +подкласса {@link android.preference.Preference}.</p> + +<p>Например:</p> + +<pre> +@Override +protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + // Check whether this Preference is persistent (continually saved) + if (isPersistent()) { + // No need to save instance state since it's persistent, + // use superclass state + return superState; + } + + // Create instance of custom BaseSavedState + final SavedState myState = new SavedState(superState); + // Set the state's value with the class member that holds current + // setting value + myState.value = mNewValue; + return myState; +} + +@Override +protected void onRestoreInstanceState(Parcelable state) { + // Check whether we saved the state in onSaveInstanceState + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save the state, so call superclass + super.onRestoreInstanceState(state); + return; + } + + // Cast state to custom BaseSavedState and pass to superclass + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + // Set this Preference's widget to reflect the restored state + mNumberPicker.setValue(myState.value); +} +</pre> + diff --git a/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd new file mode 100644 index 000000000000..cd2b4813cf83 --- /dev/null +++ b/docs/html-intl/intl/ru/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=События ввода +parent.title=Пользовательский интерфейс +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>Содержание документа</h2> + <ol> + <li><a href="#EventListeners">Приемники событий</a></li> + <li><a href="#EventHandlers">Обработчики событий</a></li> + <li><a href="#TouchMode">Режим касания</a></li> + <li><a href="#HandlingFocus">Фокус обработки</a></li> + </ol> + +</div> +</div> + +<p>В системе Android предусмотрено несколько способов перехвата событий взаимодействия пользователя с вашим приложением. +Что касается событий в пользовательском интерфейсе, подход состоит в том, чтобы захватывать события из +определенного представления объекта, с которым взаимодействует пользователь. Класс отображаемых объектов (View) содержит средства, позволяющие делать это.</p> + +<p>В различных классах View, которые вы будете использовать для создания макета, вы заметите несколько общедоступных методов обратного вызова, +полезных для работы с событиями пользовательского интерфейса. Эти методы вызываются платформой Android, когда +с этим объектом выполняется соответствующее действие. Например, когда пользователь касается отображаемого объекта (кнопки), +вызывается метод <code>onTouchEvent()</code> этого объекта. Однако для перехвата этого события вы должны +наследовать класс и переопределить метод. Однако наследование каждого отображаемого объекта +для обработки такого события было бы неудобно. Именно поэтому класс View также содержит +набор вложенных интерфейсов с обратными вызовами, которые можно задать значительно проще. Эти интерфейсы, +которые называются <a href="#EventListeners">приемниками событий</a>, и служат перехватчиками действий пользователя с вашим пользовательским интерфейсом.</p> + +<p>Несмотря на то, что вы будете чаще использовать приемники событий для перехвата действий пользователя, может +наступить момент, когда вам не захочется наследовать класс View, чтобы создать нестандартный компонент. +Возможно, вы захотите наследовать класс {@link android.widget.Button}, +чтобы сделать нечто более необычное. В этом случае вы сможете определить поведение события по умолчанию для своего +класса с помощью <a href="#EventHandlers">обработчиков событий</a> класса.</p> + + +<h2 id="EventListeners">Приемники событий</h2> + +<p>Приемник событий — это интерфейс в классе {@link android.view.View}, который содержит один +метод обратного вызова. Эти методы будут вызываться платформой Android, когда в результате взаимодействия пользователя с объектом +пользовательского интерфейса срабатывает отображаемый объект View, в котором зарегистрирован приемник.</p> + +<p>Интерфейсы, включенные в приемник события, представляют собой следующие методы обратного вызова:</p> + +<dl> + <dt><code>onClick()</code></dt> + <dd>Из объекта {@link android.view.View.OnClickListener}. + Этот метод вызывается, когда пользователь касается элемента + (в режиме касания), или переводит фокус на элемент с помощью клавиш перемещения или трекбола и +нажимает соответствующую клавишу «ввода» или нажимает на трекбол.</dd> + <dt><code>onLongClick()</code></dt> + <dd>Из объекта {@link android.view.View.OnLongClickListener}. + Этот метод вызывается, когда пользователь касается элемента и удерживает его (в режиме касания), +или переводит фокус на элемент с помощью клавиш перемещения или трекбола и +нажимает и удерживает соответствующую клавишу «ввода» или трекбол (в течение одной секунды).</dd> + <dt><code>onFocusChange()</code></dt> + <dd>Из объекта {@link android.view.View.OnFocusChangeListener}. + Этот метод вызывается, когда пользователь перемещается в элемент или из него с помощью клавиш перемещения или трекбола.</dd> + <dt><code>onKey()</code></dt> + <dd>Из объекта {@link android.view.View.OnKeyListener}. + Этот метод вызывается, когда пользователь переносит фокус на элемент и нажимает или отпускает аппаратную клавишу на устройстве.</dd> + <dt><code>onTouch()</code></dt> + <dd>Из объекта {@link android.view.View.OnTouchListener}. + Этот метод вызывается, когда пользователь выполняет действие, считающееся событием касания, например, нажимает, отпускает +или выполняет любой жест на экране (в пределах границ элемента).</dd> + <dt><code>onCreateContextMenu()</code></dt> + <dd>Из объекта {@link android.view.View.OnCreateContextMenuListener}. +Этот метод вызывается, когда создается контекстное меню (в результате длительного «длительного нажатия»). См. обсуждение +контекстных меню в руководстве +для разработчиков <a href="{@docRoot}guide/topics/ui/menus.html#context-menu">Меню</a>.</dd> +</dl> + +<p>Эти методы являются единственными составными частями соответствующих интерфейсов. Чтобы определить один из этих методов +и обрабатывать события, реализуйте вложенный интерфейс в вашем процесс или определите его, как анонимный класс. +Затем передайте экземпляр реализации +в соответствующий метод <code>View.set...Listener()</code>. (Например, вызовите +<code>{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()}</code> +и передайте ему свою реализацию {@link android.view.View.OnClickListener OnClickListener}.)</p> + +<p>В следующем примере показано, как зарегистрировать приемник события «по клику» (on-click) для кнопки. </p> + +<pre> +// Create an anonymous implementation of OnClickListener +private OnClickListener mCorkyListener = new OnClickListener() { + public void onClick(View v) { + // do something when the button is clicked + } +}; + +protected void onCreate(Bundle savedValues) { + ... + // Capture our button from layout + Button button = (Button)findViewById(R.id.corky); + // Register the onClick listener with the implementation above + button.setOnClickListener(mCorkyListener); + ... +} +</pre> + +<p>Возможно, еще удобнее реализовать OnClickListener как часть вашей операции. +При этом исключается дополнительная нагрузка класса и выделение объекта. Например:</p> +<pre> +public class ExampleActivity extends Activity implements OnClickListener { + protected void onCreate(Bundle savedValues) { + ... + Button button = (Button)findViewById(R.id.corky); + button.setOnClickListener(this); + } + + // Implement the OnClickListener callback + public void onClick(View v) { + // do something when the button is clicked + } + ... +} +</pre> + +<p>Обратите внимание, что обратный вызов <code>onClick()</code> в примере выше не +содержит возвращаемого значения, но некоторые другие методы приемника события должны возвращать логическое значение. Причина +зависит от события. Для некоторых методов, которые возвращают значения, причина описана ниже:</p> +<ul> + <li><code>{@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()}</code> — +этот метод возвращает логическое значение, указывающее, что вы обработали это событие и его более не следует хранить. +А именно, верните значение <em>true</em>, чтобы указать, что вы обработали событие и его следует остановить; +верните значение <em>false</em>, если вы не обработали его и/или событие должно продолжаться для любых других +приемников события on-click.</li> + <li><code>{@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()}</code> — +этот метод возвращает логическое значение, указывающее, что вы обработали это событие и его более не следует хранить. + А именно, верните значение <em>true</em>, чтобы указать, что вы обработали событие и его следует остановить; +верните значение <em>false</em>, если вы не обработали его и/или событие должно продолжаться для любых других +приемников события on-click.</li> + <li><code>{@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()}</code> — +этот метод возвращает логическое значение, указывающее, обработал ли ваш приемник это событие. Важно, что +это событие может повлечь несколько действий, следующих друг за другом. Поэтому, если вы возвращаете <em>ложь</em> при приеме + события нажатия, вы указываете, что вы не обработали событие и не +интересуетесь последующими действиями в результате этого события. Соответственно, этот метод не будет вызываться для любых других действий +в рамках этого события, таких как жесты или возможное действие отпускания.</li> +</ul> + +<p>Помните, что события аппаратных клавиш всегда попадают в отображаемый объект View, который находится в фокусе. Они отправляются, начиная с верха +в иерархии отображаемых объектов, затем ниже, пока не достигнут соответствующего назначения. Если ваш отображаемый объект (или дочерний объект вашего отображаемого объекта) +находится в фокусе, вы можете видеть, как событие перемещается сквозь метод <code>{@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()}</code>. В качестве альтернативы к перехвату событий клавиш через отображаемый объект View можно также получать +все события внутри вашей операции с помощью методов <code>{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}</code> +и <code>{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}</code>.</p> + +<p>Кроме того, размышляя о вводе текста для приложения, помните, что многие устройства поддерживают только программные методы +ввода. Такие методы не обязательно основаны на нажатиях клавиш. Некоторые могут использовать голосовой ввод, рукописный ввод и т. д. Даже если +метод ввода представлен интерфейсом, подобным клавиатуре, он обычно <strong>не</strong> приводит к запуску семейства +событий<code>{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}</code>. Не следует создавать +пользовательский интерфейс, для управления которым требуется нажимать определенные клавиши, кроме случаев, когда вы хотите ограничить использование приложения только устройствами +с аппаратной клавиатурой. В частности, не полагайтесь на эти методы для подтверждения ввода, когда пользователь нажимает +клавишу «Ввод». Вместо этого используйте такие действия, как {@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE}, чтобы подать методу ввода сигнал +о том, какой реакции ожидает приложение, чтобы он мог соответственно изменить свой пользовательский интерфейс. Избегайте предположений +о работе программного метода ввода и доверьте ему ввод уже отформатированного текста в ваше приложение.</p> + +<p class="note"><strong>Примечание.</strong> Система Android сначала будет вызывать обработчики событий, а затем соответствующие обработчики по умолчанию +из определения класса. Поэтому возврат значения <em>истина</em> из этих приемников событий будет останавливать +передачу события остальным приемникам событий, а также будет блокировать обратный вызов +обработчика событий по умолчанию в отображаемом объекте. Поэтому проверьте, что вы хотите прервать событие, когда вы возвращаете значение <em>true</em>.</p> + + +<h2 id="EventHandlers">Обработчики событий</h2> + +<p>Если вы создаете нестандартный компонент из отображаемого объекта, вы сможете определить несколько методов обратного вызова, +используемых как обработчики события по умолчанию. +В документе <a href="{@docRoot}guide/topics/ui/custom-components.html">Нестандартные +компоненты</a> можно узнать о некоторых общих обратных вызовах, используемых для обработки событий, +включая следующие:</p> +<ul> + <li><code>{@link android.view.View#onKeyDown}</code> — вызывается при возникновении нового события клавиши.</li> + <li><code>{@link android.view.View#onKeyUp}</code> — вызывается при возникновении события отпускания клавиши.</li> + <li><code>{@link android.view.View#onTrackballEvent}</code> — вызывается при возникновении события перемещения трекбола.</li> + <li><code>{@link android.view.View#onTouchEvent}</code> — вызывается при возникновении события жеста на сенсорном экране.</li> + <li><code>{@link android.view.View#onFocusChanged}</code> — вызывается, когда отображаемый объект получает или теряет фокус.</li> +</ul> +<p>Существует несколько других методов, о которых следует знать и которые не являются частью класса View, +но могут напрямую влиять на доступные вам способы обработки событий. Поэтому, управляя более сложными событиями внутри +макета, учитывайте и другие методы:</p> +<ul> + <li><code>{@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)}</code> — этот метод позволяет вашей операции {@link + android.app.Activity} перехватывать все события касаний перед их отправкой в окно.</li> + <li><code>{@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)}</code> — этот метод позволяет объекту {@link + android.view.ViewGroup} просматривать события перед их отправкой в дочерние отображаемые объекты.</li> + <li><code>{@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)}</code> — вызовите этот метод +в родительском отображаемом объекте, чтобы указать ему, что он не должен перехватывать события касания с помощью <code>{@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}</code>.</li> +</ul> + +<h2 id="TouchMode">Режим касания</h2> +<p> +Когда пользователь перемещается по пользовательскому интерфейсу с помощью клавиш перемещения или трекбола, необходимо +передавать фокус действующим элементам (таким как кнопки), чтобы пользователь мог видеть, +какой элемент будет принимать ввод. Однако, если устройство поддерживает сенсорный ввод, и пользователь +начинает взаимодействовать с интерфейсом, прикасаясь к его элементам, исчезает необходимость +выделять элементы или передавать фокус определенному отображаемому объекту. Следовательно, существует режим +взаимодействия, который называется «режимом касания». +</p> +<p> +Как только пользователь касается экрана, устройство, поддерживающее сенсорный ввод, +переходит в режим касания. Начиная с этого момента, фокус передается только тем отображаемым объектам, для которых +{@link android.view.View#isFocusableInTouchMode} имеет значение true, таким как виджеты редактирования текста. +Другие отображаемые объекты, которых можно коснуться, например, кнопки, не будут получать фокус при касании. Нажатие будет +просто запускать их приемники событий on-click. +</p> +<p> +В любой момент, когда пользователь нажимает клавишу перемещения или выполняет прокрутку трекболом, устройство +выходит из режима касания и находит отображаемый объект, которому передается фокус. Теперь пользователь может возобновить взаимодействие +с пользовательским интерфейсом без касания экрана. +</p> +<p> +Состояние режима касания поддерживается во всей системе (для всех окон и операций). +Чтобы узнать текущее состояние, можно вызвать +{@link android.view.View#isInTouchMode} и посмотреть, находится ли устройство в режиме касания. +</p> + + +<h2 id="HandlingFocus">Фокус обработки</h2> + +<p>В ответ на пользовательский ввод система обрабатывает обычное перемещение фокуса. +Сюда относится изменение фокуса, когда отображаемые объекты удаляются или скрываются, а также когда становятся доступными +новые отображаемые объекты. Отображаемые объекты сообщают о своей готовности получить фокус +с помощью метода <code>{@link android.view.View#isFocusable()}</code>. Чтобы изменить способность объекта View получать +фокус, вызовите <code>{@link android.view.View#setFocusable(boolean) setFocusable()}</code>. В режиме касания +можно узнать способность отображаемого объекта View получать фокус с помощью метода <code>{@link android.view.View#isFocusableInTouchMode()}</code>. +Для изменения вызовите <code>{@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}</code>. +</p> + +<p>Перемещение фокуса основано на алгоритме, который находит ближайший элемент +в заданном направлении. В редких случаях алгоритм по умолчанию может не совпадать +с поведением, которое предполагает разработчик. В этих ситуациях можно задать +явное переопределение с помощью следующих атрибутов XML в файле макета: +<var>nextFocusDown</var>, <var>nextFocusLeft</var>, <var>nextFocusRight</var>и +<var>nextFocusUp</var>. Добавьте один из этих атрибутов в отображаемый объект View, <em>из</em> которого +выходит фокус. Задайте значение атрибута для идентификатора объекта View, +<em>в</em> который следует передать фокус. Например:</p> +<pre> +<LinearLayout + android:orientation="vertical" + ... > + <Button android:id="@+id/top" + android:nextFocusUp="@+id/bottom" + ... /> + <Button android:id="@+id/bottom" + android:nextFocusDown="@+id/top" + ... /> +</LinearLayout> +</pre> + +<p>Обычно в этом вертикальном макете перемещение вверх из первой кнопки не должно приводить к перемещению, +как и перемещение вниз из второй кнопки. Теперь, когда верхняя кнопка +задает для нижней кнопки атрибут <var>nextFocusUp</var> (и наоборот), фокус будет перемещаться +циклически сверху вниз и снизу вверх.</p> + +<p>Если вы хотите объявить, что отображаемый объект в вашем пользовательском интерфейсе способен получать фокус (тогда как обычно он не может получать фокус), +добавьте XML-атрибут <code>android:focusable</code> в объект View в объявлении макета. +Установите для него значение <var>true</var>. Можно также объявить объект View, +способным получать фокус в режиме касания с помощью <code>android:focusableInTouchMode</code>.</p> +<p>Чтобы запросить передачу фокуса определенному отображаемому объекту, вызовите <code>{@link android.view.View#requestFocus()}</code>.</p> +<p>Чтобы перехватывать события фокуса (получать уведомления, когда отображаемый объект получает или теряет фокус), используйте метод +<code>{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}</code>, +который обсуждается в разделе <a href="#EventListeners">Приемники событий</a> выше.</p> + + + +<!-- +<h2 is="EventCycle">Event Cycle</h2> + <p>The basic cycle of a View is as follows:</p> + <ol> + <li>An event comes in and is dispatched to the appropriate View. The View + handles the event and notifies any listeners.</li> + <li>If, in the course of processing the event, the View's bounds may need + to be changed, the View will call {@link android.view.View#requestLayout()}.</li> + <li>Similarly, if in the course of processing the event the View's appearance + may need to be changed, the View will call {@link android.view.View#invalidate()}.</li> + <li>If either {@link android.view.View#requestLayout()} or {@link android.view.View#invalidate()} were called, + the framework will take care of measuring, laying out, and drawing the tree + as appropriate.</li> + </ol> + + <p class="note"><strong>Note:</strong> The entire View tree is single threaded. You must always be on + the UI thread when calling any method on any View. + If you are doing work on other threads and want to update the state of a View + from that thread, you should use a {@link android.os.Handler}. + </p> +--> |