| 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> |
| --> |