diff options
Diffstat (limited to 'docs/html-intl/intl/zh-cn/guide/components')
11 files changed, 6216 insertions, 0 deletions
diff --git a/docs/html-intl/intl/zh-cn/guide/components/activities.jd b/docs/html-intl/intl/zh-cn/guide/components/activities.jd new file mode 100644 index 000000000000..efc1fb1ba711 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/activities.jd @@ -0,0 +1,756 @@ +page.title=Activity +page.tags=Activity, Intent +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> +<h2>本文内容</h2> +<ol> + <li><a href="#Creating">创建 Activity</a> + <ol> + <li><a href="#UI">实现用户界面</a></li> + <li><a href="#Declaring">在清单文件中声明 Activity</a></li> + </ol> + </li> + <li><a href="#StartingAnActivity">启动 Activity</a> + <ol> + <li><a href="#StartingAnActivityForResult">启动 Activity 以获得结果</a></li> + </ol> + </li> + <li><a href="#ShuttingDown">结束 Activity</a></li> + <li><a href="#Lifecycle">管理 Activity 生命周期</a> + <ol> + <li><a href="#ImplementingLifecycleCallbacks">实现生命周期回调</a></li> + <li><a href="#SavingActivityState">保存 Activity 状态</a></li> + <li><a href="#ConfigurationChanges">处理配置变更</a></li> + <li><a href="#CoordinatingActivities">协调 Activity</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} +是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 +每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。 + +</p> + +<p> 一个应用通常由多个彼此松散联系的 Activity 组成。 +一般会指定应用中的某个 Activity 为“主” Activity,即首次启动应用时呈现给用户的那个 Activity。 +而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。 +每次新 Activity 启动时,前一 Activity 便会停止,但系统会在堆栈(“返回栈”)中保留该 Activity。 + +当新 Activity 启动时,系统会将其推送到返回栈上,并取得用户焦点。 +返回栈遵循“后进先出”堆栈机制,因此,当用户完成当前 Activity 并按“返回” +<em></em> +按钮时,系统会从堆栈中将其弹出(并销毁),然后恢复前一 Activity。(<a href="{@docRoot}guide/components/tasks-and-back-stack.html">任务和返回栈</a>文档中对返回栈有更详细的阐述。) + +</p> + +<p>当一个 Activity 因某个新 Activity 启动而停止时,系统会通过该 Activity 的生命周期回调方法通知其这一状态变化。Activity 因状态变化—系统是创建 Activity、停止 Activity、恢复 Activity 还是销毁 Activity— 而收到的回调方法可能有若干种,每一种回调方法都会为您提供执行与该状态变化相应的特定操作的机会。 + + + + +例如,停止时,您的 Activity 应释放任何大型对象,例如网络或数据库连接。 +当 Activity 恢复时,您可以重新获取所需资源,并恢复执行中断的操作。 +这些状态转变都是 Activity 生命周期的一部分。 +</p> + +<p>本文的其余部分阐述有关如何创建和使用 Activity 的基础知识(包括对 Activity 生命周期工作方式的全面阐述),以便您正确管理各种 Activity 状态之间的转变。 + +</p> + + + +<h2 id="Creating">创建 Activity</h2> + +<p>要创建 Activity,您必须创建 {@link android.app.Activity} +的子类(或使用其现有子类)。您需要在子类中实现 Activity 在其生命周期的各种状态之间转变时(例如创建 Activity、停止 Activity、恢复 Activity 或销毁 Activity 时)系统调用的回调方法。 + +两个最重要的回调方法是: +</p> + +<dl> + <dt>{@link android.app.Activity#onCreate onCreate()}</dt> + <dd>您必须实现此方法。系统会在创建您的 +Activity 时调用此方法。您应该在实现内初始化 Activity 的必需组件。 + + 最重要的是,您必须在此方法内调用 {@link android.app.Activity#setContentView + setContentView()},以定义 Activity 用户界面的布局。</dd> + <dt>{@link android.app.Activity#onPause onPause()}</dt> + <dd>系统将此方法作为用户离开 Activity 的第一个信号(但并不总是意味着 Activity 会被销毁)进行调用。 +您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。 + +</dd> +</dl> + +<p>您还应使用几种其他生命周期回调方法,以便提供流畅的 Activity 间用户体验,以及处理导致您的 Activity 停止甚至被销毁的意外中断。 + +后文的<a href="#Lifecycle">管理 Activity 生命周期</a>部分对所有生命周期回调方法进行了阐述。 +</p> + + + +<h3 id="UI">实现用户界面</h3> + +<p> Activity 的用户界面是由层级式视图—衍生自 {@link android.view.View} +类的对象—提供的。每个视图都控制 Activity 窗口内的特定矩形空间,可对用户交互作出响应。 +例如,视图可以是在用户触摸时启动某项操作的按钮。 +</p> + +<p>您可以利用 Android +提供的许多现成视图设计和组织您的布局。“小工具”是提供按钮、文本字段、复选框或仅仅是一幅图像等屏幕视觉(交互式)元素的视图。 +“布局”是衍生自 {@link +android.view.ViewGroup} +的视图,为其子视图提供唯一布局模型,例如线性布局、网格布局或相对布局。您还可以为 {@link android.view.View} 类和 +{@link android.view.ViewGroup} +类创建子类(或使用其现有子类)来自行创建小工具和布局,然后将它们应用于您的 Activity 布局。</p> + +<p>利用视图定义布局的最常见方法是借助保存在您的应用资源内的 XML +布局文件。这样一来,您就可以将用户界面的设计与定义 Activity 行为的源代码分开维护。 +您可以通过 {@link android.app.Activity#setContentView(int) setContentView()} +将布局设置为 Activity 的 UI,从而传递布局的资源 +ID。不过,您也可以在 Activity 代码中创建新 +{@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">在清单文件中声明 Activity</h3> + +<p>您必须在清单文件中声明您的 Activity,这样系统才能访问它。 +要声明您的 Activity,请打开您的清单文件,并将 <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>您还可以在此元素中加入几个其他特性,以定义 Activity 标签、Activity 图标或风格主题等用于设置 Activity +UI +风格的属性。<a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">{@code android:name}</a> +特性是唯一的必需特性—它指定 Activity 的类名。应用一旦发布,即不应更改此类名,否则,可能会破坏诸如应用快捷方式等一些功能(请阅读博客文章 +<a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things +That Cannot Change</a> +[不能更改的内容])。</p> + +<p>请参阅 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +元素参考文档,了解有关在清单文件中声明 Activity 的详细信息。</p> + + +<h4>使用 Intent 过滤器</h4> + +<p><a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a> 元素还可指定各种 Intent 过滤器—使用 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<Intent-filter>}</a> +元素—以声明其他应用组件激活它的方法。</p> + +<p>当您使用 Android SDK 工具创建新应用时,系统自动为您创建的存根 Activity 包含一个 Intent 过滤器,其中声明了该 Activity 响应“主”操作且应置于“launcher”类别内。 + + Intent 过滤器的内容与以下所示类似: +</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> +元素指定此 Activity 应列入系统的应用启动器内(以便用户启动该 Activity)。</p> + +<p>如果您打算让应用成为独立应用,不允许其他应用激活其 Activity,则您不需要任何其他 Intent 过滤器。 +正如前例所示,只应有一个 Activity 具有“主”操作和“launcher”类别。 +您不想提供给其他应用的 Activity 不应有任何 Intent 过滤器,您可以利用显式 Intent 自行启动它们(下文对此做了阐述)。 + +</p> + +<p>不过,如果您想让 Activity 对衍生自其他应用(以及您的自有应用)的隐式 Intent 作出响应,则必须为 Activity 定义其他 Intent 过滤器。 + +对于您想要作出响应的每一个 Intent 类型,您都必须加入相应的 <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> 元素。这些元素指定您的 Activity 可以响应的 Intent 类型。 +</p> + +<p>如需了解有关您的 Activity 如何响应 Intent 的详细信息,请参阅<a href="{@docRoot}guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>文档。 +</p> + + + +<h2 id="StartingAnActivity">启动 Activity</h2> + +<p>您可以通过调用 {@link android.app.Activity#startActivity + startActivity()},并将其传递给描述您想启动的 Activity 的 {@link android.content.Intent} +来启动另一个 Activity。Intent 对象会指定您想启动的具体 Activity 或描述您想执行的操作类型(系统会为您选择合适的 Activity,甚至是来自其他应用的 Activity)。 + + +Intent 对象还可能携带少量供所启动 Activity 使用的数据。 +</p> + +<p>在您的自有应用内工作时,您经常只需要启动某个已知 Activity。 + 您可以通过使用类名创建一个显式定义您想启动的 Activity 的 Intent 对象来实现此目的。 +例如,可以通过以下代码让一个 Activity 启动另一个名为 {@code +SignInActivity} 的 Activity:</p> + +<pre> +Intent intent = new Intent(this, SignInActivity.class); +startActivity(intent); +</pre> + +<p>不过,您的应用可能还需要利用您的 Activity 数据执行某项操作,例如发送电子邮件、短信或状态更新。 +在这种情况下,您的应用自身可能不具有执行此类操作所需的 Activity,因此您可以改为利用设备上其他应用提供的 Activity 为您执行这些操作。 + +这便是 Intent 对象的真正价值所在—您可以创建一个 Intent 对象,对您想执行的操作进行描述,系统会从其他应用启动相应的 Activity。 + + +如果有多个 Activity 可以处理 Intent,则用户可以选择要使用哪一个。 +例如,如果您想允许用户发送电子邮件,可以创建以下 Intent 对象: + +</p> + +<pre> +Intent intent = new Intent(Intent.ACTION_SEND); +intent.putExtra(Intent.EXTRA_EMAIL, recipientArray); +startActivity(intent); +</pre> + +<p>添加到 Intent 中的 {@link android.content.Intent#EXTRA_EMAIL} extra 是一个字符串数组,其中包含应将电子邮件发送到的电子邮件地址。 +当电子邮件应用响应此 Intent 时,它会读取 extra 中提供的字符串数组,并将它们放入电子邮件撰写窗体的“收件人”字段。 + +在这种情况下,电子邮件应用的 Activity 启动,并且当用户完成操作时,您的 Activity 会恢复执行。 +</p> + + + + +<h3 id="StartingAnActivityForResult">启动 Activity 以获得结果</h3> + +<p>有时,您可能需要从启动的 Activity 获得结果。在这种情况下,请通过调用 {@link android.app.Activity#startActivityForResult + startActivityForResult()}(而非 {@link android.app.Activity#startActivity + startActivity()})来启动 Activity。 +要想在随后收到后续 Activity 的结果,请实现 +{@link android.app.Activity#onActivityResult onActivityResult()} 回调方法。 +当后续 Activity 完成时,它会使用 {@link +android.content.Intent} 向您的 {@link android.app.Activity#onActivityResult onActivityResult()} +方法返回结果。</p> + +<p>例如,您可能希望用户选取其中一位联系人,以便您的 Activity 对该联系人中的信息执行某项操作。 +您可以通过以下代码创建此类 Intent 并处理结果: +</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>上例显示的是,您在处理 Activity 结果时应该在 {@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} 参数)从该处开始处理 Activity 结果。 +</p> + +<p>实际情况是,{@link +android.content.ContentResolver} 对一个内容提供程序执行查询,后者返回一个 +{@link android.database.Cursor},让查询的数据能够被读取。如需了解详细信息,请参阅<a href="{@docRoot}guide/topics/providers/content-providers.html">内容提供程序</a>文档。 +</p> + +<p>如需了解有关 Intent 用法的详细信息,请参阅<a href="{@docRoot}guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>文档。 +</p> + + +<h2 id="ShuttingDown">结束 Activity</h2> + +<p>您可以通过调用 Activity 的 {@link android.app.Activity#finish +finish()} 方法来结束该 Activity。您还可以通过调用 +{@link android.app.Activity#finishActivity finishActivity()} 结束您之前启动的另一个 Activity。</p> + +<p class="note"><strong>注:</strong>在大多数情况下,您不应使用这些方法显式结束 Activity。 +正如下文有关 Activity 生命周期的部分所述,Android 系统会为您管理 Activity 的生命周期,因此您无需完成自己的 Activity。 + +调用这些方法可能对预期的用户体验产生不良影响,因此只应在您确实不想让用户返回此 Activity 实例时使用。 + +</p> + + +<h2 id="Lifecycle">管理 Activity 生命周期</h2> + +<p>通过实现回调方法管理 Activity 的生命周期对开发强大而又灵活的应用至关重要。 + +Activity 的生命周期会直接受到 Activity 与其他 Activity、其任务及返回栈的关联性的影响。 +</p> + +<p>Activity 基本上以三种状态存在:</p> + +<dl> + <dt><i>已继续</i></dt> + <dd>此 Activity 位于屏幕前台并具有用户焦点。(有时也将此状态称作“运行中”。) +</dd> + + <dt><i>已暂停</i></dt> + <dd>另一个 Activity 位于屏幕前台并具有用户焦点,但此 Activity 仍可见。也就是说,另一个 Activity 显示在此 Activity 上方,并且该 Activity 部分透明或未覆盖整个屏幕。 + +已暂停的 Activity 处于完全 Activity 状态({@link android.app.Activity} +对象保留在内存中,它保留了所有状态和成员信息,并与窗口管理器保持连接),但在内存极度不足的情况下,可能会被系统终止。 +</dd> + + <dt><i>已停止</i></dt> + <dd>该 Activity 被另一个 Activity 完全遮盖(该 Activity 目前位于“后台”)。 +已停止的 Activity 同样仍处于 Activity 状态({@link android.app.Activity} +对象保留在内存中,它保留了所有状态和成员信息,但<em>未</em>与窗口管理器连接)。 +不过,它对用户不再可见,在他处需要内存时可能会被系统终止。 +</dd> +</dl> + +<p>如果 Activity 处于暂停或停止状态,系统可通过要求其结束(调用其 +{@link android.app.Activity#finish finish()} +方法)或直接终止其进程,将其从内存中删除。(将其结束或终止后)再次打开 Activity 时,必须重建。 +</p> + + + +<h3 id="ImplementingLifecycleCallbacks">实现生命周期回调</h3> + +<p>当一个 Activity 转入和转出上述不同状态时,系统会通过各种回调方法向其发出通知。 +所有回调方法都是挂钩,您可以在 Activity 状态发生变化时替代这些挂钩来执行相应操作。 +以下框架 Activity 包括每一个基本生命周期方法: +</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>这些方法共同定义 Activity 的整个生命周期。您可以通过实现这些方法监控 Activity 生命周期中的三个嵌套循环: + </p> + +<ul> +<li>Activity 的<b>整个生命周期</b>发生在 {@link +android.app.Activity#onCreate onCreate()} 调用与 {@link +android.app.Activity#onDestroy} 调用之间。您的 Activity 应在 {@link android.app.Activity#onCreate onCreate()} +中执行“全局”状态设置(例如定义布局),并释放 {@link android.app.Activity#onDestroy} +中的所有其余资源。例如,如果您的 Activity 有一个在后台运行的线程,用于从网络上下载数据,它可能会在 +{@link android.app.Activity#onCreate onCreate()} 中创建该线程,然后在 +{@link +android.app.Activity#onDestroy} 中停止该线程。</li> + +<li><p>Activity 的<b>可见生命周期</b>发生在 {@link +android.app.Activity#onStart onStart()} 调用与 {@link +android.app.Activity#onStop onStop()} 调用之间。在这段时间,用户可以在屏幕上看到 Activity 并与其交互。 +例如,当一个新 Activity 启动,并且此 Activity 不再可见时,系统会调用 +{@link android.app.Activity#onStop onStop()}。您可以在调用这两个方法之间保留向用户显示 Activity 所需的资源。 +例如,您可以在 +{@link +android.app.Activity#onStart onStart()} 中注册一个 {@link android.content.BroadcastReceiver} 以监控影响 UI +的变化,并在用户无法再看到您显示的内容时在 {@link android.app.Activity#onStop onStop()} +中将其取消注册。在 Activity 的整个生命周期,当 Activity 在对用户可见和隐藏两种状态中交替变化时,系统可能会多次调用 {@link android.app.Activity#onStart onStart()} 和 {@link +android.app.Activity#onStop onStop()}。 +</p></li> + +<li><p>Activity 的<b>前台生命周期</b>发生在 {@link +android.app.Activity#onResume onResume()} 调用与 {@link android.app.Activity#onPause +onPause()} 调用之间。在这段时间,Activity 位于屏幕上的所有其他 Activity 之前,并具有用户输入焦点。 +Activity 可频繁转入和转出前台—例如,当设备转入休眠状态或出现对话框时,系统会调用 {@link android.app.Activity#onPause onPause()}。 + +由于此状态可能经常发生转变,因此这两个方法中应采用适度轻量级的代码,以避免因转变速度慢而让用户等待。 +</p></li> +</ul> + +<p>图 1 说明了这些循环以及 Activity 在状态转变期间可能经过的路径。矩形表示回调方法,当 Activity 在不同状态之间转变时,您可以实现这些方法来执行操作。 + + <p> + +<img src="{@docRoot}images/activity_lifecycle.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>Activity 生命周期。</p> + +<p>表 1 列出了相同的生命周期回调方法,其中对每一种回调方法做了更详细的描述,并说明了每一种方法在 Activity 整个生命周期内的位置,包括在回调方法完成后系统能否终止 Activity。 + + +</p> + +<p class="table-caption"><strong>表 1. </strong>Activity 生命周期回调方法汇总表。 +</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>首次创建 Activity 时调用。 + 您应该在此方法中执行所有正常的静态设置— +创建视图、将数据绑定到列表等等。系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的<a href="#actstate">保存 Activity 状态</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>在 Activity 已停止并即将再次启动前调用。 + + <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>在 Activity 即将对用户可见之前调用。 + <p>如果 Activity 转入前台,则后接 {@code onResume()},如果 Activity 转入隐藏状态,则后接 {@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>在 Activity 即将开始与用户进行交互之前调用。 +此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。 + + <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>当系统即将开始继续另一个 Activity 时调用。 +此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 + +它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。 + + <p>如果 Activity 返回前台,则后接 {@code onResume()},如果 Activity 转入对用户不可见状态,则后接 {@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>Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。 + + + <p>如果 Activity 恢复与用户的交互,则后接 {@code onRestart()},如果 Activity 被销毁,则后接 +{@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>在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 +当 Activity 结束(有人调用 Activity 上的 <code>{@link android.app.Activity#finish + finish()}</code>),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 + +您可以通过 + <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>名为“是否能事后终止?”的列表示系统是否能在不执行另一行 Activity 代码的情况下,在<em>方法返回后</em>随时终止承载 Activity 的进程。 + +有三个方法带有“是”标记:({@link +android.app.Activity#onPause +onPause()}、{@link android.app.Activity#onStop onStop()} 和 {@link android.app.Activity#onDestroy +onDestroy()})。由于 {@link android.app.Activity#onPause onPause()} +是这三个方法中的第一个,因此 Activity 创建后,{@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()} +调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个 Activity 的转变并拖慢用户体验。 +</p> + +<p> 在<b>是否能在事后终止?</b>列中标记为“否”的方法可从系统调用它们的一刻起防止承载 Activity 的进程被终止。 +因此,在从 {@link android.app.Activity#onPause onPause()} +返回的时间到 {@link android.app.Activity#onResume onResume()} +被调用的时间,系统可以终止 Activity。在 {@link android.app.Activity#onPause onPause()} +被再次调用并返回前,将无法再次终止 Activity。 </p> + +<p class="note"><strong>注:</strong>根据表 +1 +中的定义属于技术上无法“终止”的 Activity 仍可能被系统终止—但这种情况只有在无任何其他资源的极端情况下才会发生。<a href="{@docRoot}guide/components/processes-and-threads.html">进程和线程处理</a>文档对可能会终止 Activity 的情况做了更详尽的阐述。 + +</p> + + +<h3 id="SavingActivityState">保存 Activity 状态</h3> + +<p><a href="#Lifecycle">管理 Activity 生命周期</a>的引言部分简要提及,当 Activity 暂停或停止时,Activity 的状态会得到保留。 + +确实如此,因为当 Activity 暂停或停止时,{@link android.app.Activity} +对象仍保留在内存中 — 有关其成员和当前状态的所有信息仍处于 Activity 状态。 +因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台(当它“继续”)时,这些更改仍然存在。 + +</p> + +<p>不过,当系统为了恢复内存而销毁某项 Activity 时,{@link +android.app.Activity} +对象也会被销毁,因此系统在继续 Activity 时根本无法让其状态保持完好,而是必须在用户返回Activity时重建 {@link android.app.Activity} +对象。但用户并不知道系统销毁 Activity 后又对其进行了重建,因此他们很可能认为 Activity 状态毫无变化。 + +在这种情况下,您可以实现另一个回调方法对有关 Activity 状态的信息进行保存,以确保有关 Activity 状态的重要信息得到保留:{@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()}。 + +</p> + +<p>系统会先调用 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()},然后再使 Activity 变得易于销毁。系统会向该方法传递一个 +{@link android.os.Bundle},您可以在其中使用 +{@link +android.os.Bundle#putString putString()} 和 {@link +android.os.Bundle#putInt putInt()} 等方法以名称-值对形式保存有关 Activity 状态的信息。然后,如果系统终止您的应用进程,并且用户返回您的 Activity,则系统会重建该 Activity,并将 +{@link android.os.Bundle} 同时传递给 +{@link android.app.Activity#onCreate onCreate()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}。您可以使用上述任一方法从 +{@link android.os.Bundle} +提取您保存的状态并恢复该 Activity 状态。如果没有状态信息需要恢复,则传递给您的 {@link +android.os.Bundle} +是空值(如果是首次创建该 Activity,就会出现这种情况)。</p> + +<img src="{@docRoot}images/fundamentals/restore_instance.png" alt="" /> +<p class="img-caption"><strong>图 2. </strong>在两种情况下,Activity 重获用户焦点时可保持状态完好:系统在销毁 Activity 后重建 Activity,Activity 必须恢复之前保存的状态;系统停止 Activity 后继续执行 Activity,并且 Activity 状态保持完好。 + + +</p> + +<p class="note"><strong>注:</strong>无法保证系统会在销毁您的 Activity 前调用 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()},因为存在不需要保存状态的情况(例如用户使用“返回” +<em></em> +按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 + +如果系统调用 {@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} +类的 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 默认实现也会恢复部分 Activity 状态。具体地讲,默认实现会为布局中的每个 +{@link +android.view.View} 调用相应的 {@link +android.view.View#onSaveInstanceState onSaveInstanceState()} +方法,让每个视图都能提供有关自身的应保存信息。Android +框架中几乎每个小工具都会根据需要实现此方法,以便在重建 Activity 时自动保存和恢复对 UI +所做的任何可见更改。例如,{@link android.widget.EditText} +小工具保存用户输入的任何文本,{@link android.widget.CheckBox} +小工具保存复选框的选中或未选中状态。您只需为想要保存其状态的每个小工具提供一个唯一的 ID(通过 <a href="{@docRoot}guide/topics/resources/layout-resource.html#idvalue">{@code android:id}</a> +属性)。如果小工具没有 ID,则系统无法保存其状态。 +</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<p>您还可以通过将 +{@link android.R.attr#saveEnabled android:saveEnabled} 属性设置为 {@code "false"} 或通过调用 {@link android.view.View#setSaveEnabled setSaveEnabled()} +方法显式阻止布局内的视图保存其状态。您通常不应将该属性禁用,但如果您想以不同方式恢复 Activity UI +的状态,就可能需要这样做。</p> +</div> +</div> + +<p>尽管 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} +的默认实现会保存有关您的Activity UI +的有用信息,您可能仍需替代它以保存更多信息。例如,您可能需要保存在 Activity 生命周期内发生了变化的成员值(它们可能与 +UI 中恢复的值有关联,但默认情况下系统不会恢复储存这些 UI +值的成员)。</p> + +<p>由于 {@link +android.app.Activity#onSaveInstanceState onSaveInstanceState()} 的默认实现有助于保存 UI 的状态, +因此如果您为了保存更多状态信息而重写该方法,应始终先调用 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +的超类实现,然后再执行任何操作。同样,如果您替代 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()} +方法,也应调用它的超类实现,以便默认实现能够恢复视图状态。</p> + +<p class="note"><strong>注:</strong>由于无法保证系统会调用 +{@link android.app.Activity#onSaveInstanceState +onSaveInstanceState()},因此您只应利用它来记录 Activity 的瞬态(UI +的状态)—切勿使用它来存储持久性数据,而应使用 {@link +android.app.Activity#onPause onPause()} +在用户离开 Activity 后存储持久性数据(例如应保存到数据库的数据)。</p> + +<p>您只需旋转设备,让屏幕方向发生变化,就能有效地测试您的应用的状态恢复能力。 +当屏幕方向变化时,系统会销毁并重建 Activity,以便应用可供新屏幕配置使用的备用资源。 + +单凭这一理由,您的 Activity 在重建时能否完全恢复其状态就显得非常重要,因为用户在使用应用时经常需要旋转屏幕。 + +</p> + + +<h3 id="ConfigurationChanges">处理配置变更</h3> + +<p>有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 +发生此类变化时,Android 会重建运行中的 Activity(系统调用 +{@link android.app.Activity#onDestroy},然后立即调用 {@link +android.app.Activity#onCreate onCreate()})。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。 + + +</p> + +<p>如果您对 Activity 进行了适当设计,让它能够按以上所述处理屏幕方向变化带来的重启并恢复 Activity 状态,那么在遭遇 Activity 生命周期中的其他意外事件时,您的应用将具有更强的适应性。 + +</p> + +<p>正如上文所述,处理此类重启的最佳方法 +是利用 {@link + android.app.Activity#onSaveInstanceState onSaveInstanceState()} 和 {@link +android.app.Activity#onRestoreInstanceState onRestoreInstanceState()}(或 {@link +android.app.Activity#onCreate onCreate()})保存并恢复 Activity 的状态。</p> + +<p>如需了解有关运行时发生的配置变更以及应对方法的详细信息,请阅读<a href="{@docRoot}guide/topics/resources/runtime-changes.html">处理运行时变更</a>指南。 + +</p> + + + +<h3 id="CoordinatingActivities">协调 Activity</h3> + + <p>当一个 Activity 启动另一个 Activity 时,它们都会体验到生命周期转变。第一个 Activity 暂停并停止(但如果它在后台仍然可见,则不会停止)时,系统会创建另一个 Activity。 + +如果这些 Activity 共用保存到磁盘或其他地方的数据,必须了解的是,在创建第二个 Activity 前,第一个 Activity 不会完全停止。更确切地说,启动第二个 Activity 的过程与停止第一个 Activity 的过程存在重叠。 + + +</p> + +<p>生命周期回调的顺序经过明确定义,当两个 Activity 位于同一进程,并且由一个 Activity 启动另一个 Activity 时,其定义尤其明确。 +以下是当 Activity A +启动 Activity B 时一系列操作的发生顺序: </p> + +<ol> +<li>Activity A 的 {@link android.app.Activity#onPause onPause()} 方法执行。</li> + +<li>Activity B 的 {@link android.app.Activity#onCreate onCreate()}、{@link +android.app.Activity#onStart onStart()} 和 {@link android.app.Activity#onResume onResume()} +方法依次执行。(Activity B 现在具有用户焦点。)</li> + +<li>然后,如果 Activity A 在屏幕上不再可见,则其 {@link +android.app.Activity#onStop onStop()} 方法执行。</li> +</ol> + + <p>您可以利用这种可预测的生命周期回调顺序管理从一个 Activity 到另一个 Activity 的信息转变。 +例如,如果您必须在第一个 Activity 停止时向数据库写入数据,以便下一个 Activity 能够读取该数据,则应在 +{@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/zh-cn/guide/components/bound-services.jd b/docs/html-intl/intl/zh-cn/guide/components/bound-services.jd new file mode 100644 index 000000000000..ed6aaf6ecbf8 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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>绑定服务是客户端-服务器接口中的服务器。绑定服务可让组件(例如 Activity)绑定到服务、发送请求、接收响应,甚至执行进程间通信 (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()},但有时需要同时实现这两者。例如,音乐播放器可能发现让其服务无限期运行并同时提供绑定很有用处。 +这样一来,Activity 便可启动服务进行音乐播放,即使用户离开应用,音乐播放也不会停止。 +然后,当用户返回应用时,Activity 可绑定到服务,重新获得回放控制权。 +</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} 上的 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()},向客户端传递用来与服务通信的 +{@link android.os.IBinder}。</p> + +<p>多个客户端可同时连接到一个服务。不过,只有在第一个客户端绑定时,系统才会调用服务的 +{@link android.app.Service#onBind onBind()} +方法来检索 {@link android.os.IBinder}。系统随后无需再次调用 +{@link android.app.Service#onBind onBind()},便可将同一 {@link android.os.IBinder} 传递至任何其他绑定的客户端。</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.Message} 对象的 {@link android.os.Handler}。此 {@link android.os.Handler} +是 {@link android.os.Messenger} 的基础,后者随后可与客户端分享一个 {@link android.os.IBinder},从而让客户端能利用 {@link +android.os.Message} +对象向服务发送命令。此外,客户端还可定义自有 +{@link android.os.Messenger},以便服务回传消息。 + <p>这是执行进程间通信 (IPC) 的最简单方法,因为 {@link +android.os.Messenger} +会在单一线程中创建包含所有请求的队列,这样您就不必对服务进行线程安全设计。</p> + </dd> + + <dt>使用 AIDL</dt> + <dd>AIDL(Android +接口定义语言)执行所有将对象分解成原语的工作,操作系统可以识别这些原语并将它们编组到各进程中,以执行 +IPC。之前采用 {@link android.os.Messenger} 的方法实际上是以 AIDL +作为其底层结构。如上所述,{@link android.os.Messenger} +会在单一线程中创建包含所有客户端请求的队列,以便服务一次接收一个请求。不过,如果您想让服务同时处理多个请求,则可直接使用 +AIDL。 +在此情况下,您的服务必须具备多线程处理能力,并采用线程安全式设计。 + <p>如需直接使用 +AIDL,您必须创建一个定义编程接口的 {@code .aidl} 文件。Android SDK +工具利用该文件生成一个实现接口并处理 IPC +的抽象类,您随后可在服务内对其进行扩展。</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>此方法只有在客户端和服务位于同一应用和进程内这一最常见的情况下方才有效。 +例如,对于需要将 Activity 绑定到在后台播放音乐的自有服务的音乐应用,此方法非常有效。 + +</p> + +<p>以下是具体的设置方法:</p> +<ol> + <li>在您的服务中,创建一个可满足下列任一要求的 {@link android.os.Binder} 实例: + <ul> + <li>包含客户端可调用的公共方法</li> + <li>返回当前 {@link android.app.Service} +实例,其中包含客户端可调用的公共方法</li> + <li>或返回由服务承载的其他类的实例,其中包含客户端可调用的公共方法 +</li> + </ul> + <li>从 {@link +android.app.Service#onBind onBind()} 回调方法返回此 {@link android.os.Binder} 实例。</li> + <li>在客户端中,从 {@link +android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回调方法接收 +{@link android.os.Binder},并使用提供的方法调用绑定服务。</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>点击按钮时,以下这个 Activity 会绑定到 {@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>上例并未显式取消与服务的绑定,但所有客户端都应在适当的时间(例如当 Activity 暂停时)取消绑定。 +</p> + +<p>如需查看更多示例代码,请参阅 <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a> 中的 <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> 类。</p> + + + + + +<h3 id="Messenger">使用 Messenger</h3> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h4>与 AIDL 比较</h4> + <p>当您需要执行 IPC 时,为您的接口使用 {@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} +为您的服务提供接口。利用此方法,您无需使用 +AIDL 便可执行进程间通信 (IPC)。</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.Handler} 中(具体地讲,是在 {@link android.os.Handler#handleMessage +handleMessage()} 方法中)接收每个 {@link android.os.Message}</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} +的 {@link android.os.Handler#handleMessage handleMessage()} 方法中接收传入的 +{@link android.os.Message},并根据 {@link android.os.Message#what} 成员决定下一步操作。</p> + +<p>客户端只需根据服务返回的 {@link +android.os.IBinder} 创建一个 {@link android.os.Messenger},然后利用 {@link +android.os.Messenger#send send()} 发送一条消息。例如,以下就是一个绑定到服务并向服务传递 +{@code MSG_SAY_HELLO} 消息的简单 Activity:</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#send send()} 方法的 {@link android.os.Message#replyTo} +参数中包含客户端的 {@link android.os.Messenger}。</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>只有 Activity、服务和内容提供程序可以绑定到服务—您<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.app.Service#onBind onBind()} +方法返回的 {@link android.os.IBinder}。</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>当您的客户端被销毁时,它将取消与服务的绑定,但您应该始终在完成与服务的交互时或您的 Activity 暂停时取消绑定,以便服务能够在未被占用时关闭。 + +(下文更详细地阐述了绑定和取消绑定的适当时机。) +</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},用于显式命名要绑定的服务(但 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>您通常应该在客户端生命周期的匹配引入 (bring-up) 和退出 (tear-down) 时刻期间配对绑定和取消绑定。 +例如: + <ul> + <li>如果您只需要在 Activity 可见时与服务交互,则应在 {@link android.app.Activity#onStart onStart()} +期间绑定,在 {@link +android.app.Activity#onStop onStop()} 期间取消绑定。</li> + <li>如果您希望 Activity 在后台停止运行状态下仍可接收响应,则可在 +{@link android.app.Activity#onCreate onCreate()} 期间绑定,在 {@link android.app.Activity#onDestroy onDestroy()} +期间取消绑定。请注意,这意味着您的 Activity 在其整个运行过程中(甚至包括后台运行期间)都需要使用服务,因此如果服务位于其他进程内,那么当您提高该进程的权重时,系统终止该进程的可能性会增加 + + +</li> + </ul> + <p class="note"><strong>注:</strong>通常情况下,<strong>切勿</strong>在 Activity 的 {@link android.app.Activity#onResume onResume()} +和 {@link +android.app.Activity#onPause onPause()} +期间绑定和取消绑定,因为每一次生命周期转换都会发生这些回调,您应该使发生在这些转换期间的处理保持在最低水平。此外,如果您的应用内的多个 Activity 绑定到同一服务,并且其中两个 Activity 之间发生了转换,则如果当前 Activity 在下一次绑定(恢复期间)之前取消绑定(暂停期间),系统可能会销毁服务并重建服务。 + + +(<a href="{@docRoot}guide/components/activities.html#CoordinatingActivities">Activity</a>文档中介绍了这种有关 Activity 如何协调其生命周期的 Activity 转换。) + +</p> +</ul> + +<p>如需查看更多显示如何绑定到服务的示例代码,请参阅 <a href="{@docRoot}resources/samples/ApiDemos/index.html">ApiDemos</a> 中的 <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/RemoteService.html">{@code +RemoteService.java}</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()} 方法时,如果您想在客户端下一次绑定到服务时接收 +{@link android.app.Service#onRebind +onRebind()} 调用(而不是接收 {@link +android.app.Service#onBind onBind()} 调用),则可选择返回 +{@code true}。{@link android.app.Service#onRebind +onRebind()} 返回空值,但客户端仍在其 {@link android.content.ServiceConnection#onServiceConnected onServiceConnected()} 回调中接收 +{@link android.os.IBinder}。下文图 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/zh-cn/guide/components/fragments.jd b/docs/html-intl/intl/zh-cn/guide/components/fragments.jd new file mode 100644 index 000000000000..a4c2cbb82499 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/fragments.jd @@ -0,0 +1,812 @@ +page.title=片段 +parent.title=Activity +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">创建片段 + <ol> + <li><a href="#UI">添加用户界面</a></li> + <li><a href="#Adding">向 Activity 添加片段</a></li> + </ol> + </li> + <li><a href="#Managing">管理片段</a></li> + <li><a href="#Transactions">执行片段事务</a></li> + <li><a href="#CommunicatingWithActivity">与 Activity 通信</a> + <ol> + <li><a href="#EventCallbacks">创建对 Activity 的事件回调</a></li> + <li><a href="#ActionBar">向操作栏添加项目</a></li> + </ol> + </li> + <li><a href="#Lifecycle">处理片段生命周期</a> + <ol> + <li><a href="#CoordinatingWithActivity">与 Activity 生命周期协调一致</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">利用片段构建动态 UI</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} 中的行为或用户界面部分。您可以将多个片段组合在一个 Activity 中来构建多窗格 +UI,以及在多个 Activity 中重复使用某个片段。您可以将片段视为 Activity 的模块化组成部分,它具有自己的生命周期,能接收自己的输入事件,并且您可以在 Activity 运行时添加或删除片段(有点像您可以在不同 Activity 中重复使用的“子 Activity”)。 + + +</p> + +<p>片段必须始终嵌入在 Activity 中,其生命周期直接受宿主 Activity 生命周期的影响。 +例如,当 Activity 暂停时,其中的所有片段也会暂停;当 Activity 被销毁时,所有片段也会被销毁。 +不过,当 Activity 正在运行(处于<em>已恢复</em><a href="{@docRoot}guide/components/activities.html#Lifecycle">生命周期状态</a>)时,您可以独立操纵每个片段,如添加或移除它们。 + +当您执行此类片段事务时,您也可以将其添加到由 Activity 管理的返回栈—Activity 中的每个返回栈条目都是一条已发生片段事务的记录。 + + +返回栈让用户可以通过按“返回”<em></em>按钮撤消片段事务(后退)。 +</p> + +<p>当您将片段作为 Activity 布局的一部分添加时,它存在于 Activity 视图层次结构的某个 {@link +android.view.ViewGroup} +内部,并且片段会定义其自己的视图布局。您可以通过在 Activity 的布局文件中声明片段,将其作为 +{@code <fragment>} +元素插入您的 Activity 布局中,或者通过将其添加到某个现有 +{@link android.view.ViewGroup},利用应用代码进行插入。不过,片段并非必须成为 Activity 布局的一部分;您还可以将没有自己 +UI +的片段用作 Activity 的不可见工作线程。</p> + +<p>本文描述如何在开发您的应用时使用片段,包括将片段添加到 Activity 返回栈时如何保持其状态、如何与 Activity 及 Activity 中的其他片段共享事件、如何为 Activity 的操作栏发挥作用等等。 + + +</p> + + +<h2 id="Design">设计原理</h2> + +<p>Android 在 Android 3.0(API 11 级)中引入了片段,主要是为了给大屏幕(如平板电脑)上更加动态和灵活的 UI +设计提供支持。由于平板电脑的屏幕比手机屏幕大得多,因此可用于组合和交换 +UI +组件的空间更大。利用片段实现此类设计时,您无需管理对视图层次结构的复杂更改。 +通过将 Activity 布局分成片段,您可以在运行时修改 Activity 的外观,并在由 Activity 管理的返回栈中保留这些更改。 + +</p> + +<p>例如,新闻应用可以使用一个片段在左侧显示文章列表,使用另一个片段在右侧显示文章—两个片段并排显示在一个 Activity 中,每个片段都具有自己的一套生命周期回调方法,并各自处理自己的用户输入事件。 + + +因此,用户不需要使用一个 Activity 来选择文章,然后使用另一个 Activity 来阅读文章,而是可以在同一个 Activity 内选择文章并进行阅读,如图 +1 +中的平板电脑布局所示。</p> + +<p>您应该将每个片段都设计为可重复使用的模块化 Activity 组件。也就是说,由于每个片段都会通过各自的生命周期回调来定义其自己的布局和行为,您可以将一个片段加入多个 Activity,因此,您应该采用可复用式设计,避免直接从某个片段直接操纵另一个片段。 + + +这特别重要,因为模块化片段让您可以通过更改片段的组合方式来适应不同的屏幕尺寸。 +在设计可同时支持平板电脑和手机的应用时,您可以在不同的布局配置中重复使用您的片段,以根据可用的屏幕空间优化用户体验。 + +例如,在手机上,如果不能在同一 Activity 内储存多个片段,可能必须利用单独片段来实现单窗格 +UI。 +</p> + +<img src="{@docRoot}images/fundamentals/fragments.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>有关由片段定义的两个 +UI +模块如何适应不同设计的示例:通过组合成一个 Activity 来适应平板电脑设计,通过单独片段来适应手机设计。</p> + +<p>例如—仍然以新闻应用为例—在平板电脑尺寸的设备上运行时,该应用可以在<em>Activity +A</em> 中嵌入两个片段。不过,在手机尺寸的屏幕上,没有足以储存两个片段的空间,因此<em>Activity +A</em> +只包括用于显示文章列表的片段,当用户选择文章时,它会启动<em>Activity +B</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>片段的生命周期(其 Activity 运行时)。 +</p> +</div> + +<p>要想创建片段,您必须创建 {@link android.app.Fragment} +的子类(或已有其子类)。{@link android.app.Fragment} 类的代码与 +{@link android.app.Activity} 非常相似。它包含与 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 +应用转换为使用片段,可能只需将代码从 Activity 的回调方法移入片段相应的回调方法中。 +</p> + +<p>通常,您至少应实现以下生命周期方法:</p> + +<dl> + <dt>{@link android.app.Fragment#onCreate onCreate()}</dt> + <dd>系统会在创建片段时调用此方法。您应该在实现内初始化您想在片段暂停或停止后恢复时保留的必需片段组件。 + +</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>系统会在片段首次绘制其用户界面时调用此方法。 +要想为您的片段绘制 +UI,您从此方法中返回的 {@link android.view.View} 必须是片段布局的根视图。如果片段未提供 +UI,您可以返回 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} +类中的对话框帮助程序方法,因为您可以将片段对话框纳入由 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}。这在为您的应用创建“设置” Activity 时很有用处。 +</dd> +</dl> + + +<h3 id="UI">添加用户界面</h3> + +<p>片段通常用作 Activity 用户界面的一部分,将其自己的布局融入 Activity。 +</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.app.Fragment#onCreateView onCreateView()} 返回一个 {@link android.widget.ListView},因此您无需实现它。</p> + +<p>要想从 {@link +android.app.Fragment#onCreateView onCreateView()} 返回布局,您可以通过 XML 中定义的<a href="{@docRoot}guide/topics/resources/layout-resource.html">布局资源</a>来扩展布局。为帮助您执行此操作,{@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>传递至 {@link android.app.Fragment#onCreateView +onCreateView()} 的 {@code container} +参数是您的片段布局将插入到的父 +{@link android.view.ViewGroup}(来自 Activity 的布局)。{@code savedInstanceState} +参数是在恢复片段时,提供上一片段实例相关数据的 +{@link android.os.Bundle}(<a href="#Lifecycle">处理片段生命周期</a>部分对恢复状态做了详细阐述)。 +</p> + +<p>{@link android.view.LayoutInflater#inflate(int,ViewGroup,boolean) inflate()} +方法带有三个参数:</p> +<ul> + <li>您想要扩展的布局的资源 ID;</li> + <li>将作为扩展布局父项的 {@link android.view.ViewGroup}。传递 {@code +container} +对系统向扩展布局的根视图(由其所属的父视图指定)应用布局参数具有重要意义;</li> + <li>指示是否应该在扩展期间将扩展布局附加至 {@link +android.view.ViewGroup}(第二个参数)的布尔值。(在本例中,其值为 +false,因为系统已经将扩展布局插入 {@code +container}—传递 true 值会在最终布局中创建一个多余的视图组。)</li> +</ul> + +<p>现在,您已经了解了如何创建提供布局的片段。接下来,您需要将该片段添加到您的 Activity 中。 +</p> + + + +<h3 id="Adding">向 Activity 添加片段</h3> + +<p>通常,片段向宿主 Activity 贡献一部分 +UI,作为 Activity 总体视图层次结构的一部分嵌入到 Activity 中。可以通过两种方式向 Activity 布局添加片段: +</p> + +<ul> + <li><b>在 Activity 的布局文件内声明片段</b> +<p>在本例中,您可以将片段当作视图来为其指定布局属性。 +例如,以下是一个具有两个片段的 Activity 的布局文件: +</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 <fragment>} 中的 {@code android:name} 属性指定要在布局中实例化的 {@link +android.app.Fragment} 类。</p> + +<p>当系统创建此 Activity 布局时,会实例化在布局中指定的每个片段,并为每个片段调用 +{@link android.app.Fragment#onCreateView onCreateView()} +方法,以检索每个片段的布局。系统会直接插入片段返回的 {@link android.view.View} 来替代 +{@code <fragment>} 元素。</p> + +<div class="note"> + <p><strong>注:</strong>每个片段都需要一个唯一的标识符,重启 Activity 时,系统可以使用该标识符来恢复片段(您也可以使用该标识符来捕获片段以执行某些事务,如将其删除)。 + +可以通过三种方式为片段提供 +ID:</p> + <ul> + <li>为 {@code android:id} 属性提供唯一 ID</li> + <li>为 {@code android:tag} 属性提供唯一字符串</li> + <li>如果您未给以上两个属性提供值,系统会使用容器视图的 ID +</li> + </ul> +</div> + </li> + + <li><b>或者通过编程方式将片段添加到某个现有 {@link android.view.ViewGroup}</b> +<p>您可以在 Activity 运行期间随时将片段添加到 Activity 布局中。您只需指定要将片段放入哪个 +{@link +android.view.ViewGroup}。</p> + <p>要想在您的 Activity 中执行片段事务(如添加、删除或替换片段),您必须使用 +{@link android.app.FragmentTransaction} 中的 API。您可以像下面这样从 +{@link android.app.Activity} 获取一个 {@link android.app.FragmentTransaction} 实例:</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},即应该放置片段的位置,由资源 +ID 指定,第二个参数是要添加的片段。</p> + <p>一旦您通过 +{@link android.app.FragmentTransaction} 做出了更改,就必须调用 +{@link android.app.FragmentTransaction#commit} 以使更改生效。</p> + </li> +</ul> + + +<h4 id="AddingWithoutUI">添加没有 UI 的片段</h4> + +<p>上例展示了如何向您的 Activity 添加片段以提供 +UI。不过,您还可以使用片段为 Activity 提供后台行为,而不显示额外 +UI。</p> + +<p>要想添加没有 UI 的片段,请使用 {@link +android.app.FragmentTransaction#add(Fragment,String)} 从 Activity 添加片段(为片段提供一个唯一的字符串“标记”,而不是视图 +ID)。这会添加片段,但由于它并不与 Activity 布局中的视图关联,因此不会收到对 +{@link +android.app.Fragment#onCreateView onCreateView()} 的调用。因此,您不需要实现该方法。</p> + +<p>并非只能为非 UI 片段提供字符串标记—您也可以为具有 UI +的片段提供字符串标记—但如果片段没有 +UI,则字符串标记将是标识它的唯一方式。如果您想稍后从 Activity 中获取片段,则需要使用 +{@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}。</p> + +<p>如需查看将没有 UI 的片段用作后台工作线程的示例 Activity,请参阅 {@code +FragmentRetainInstance.java} 示例,该示例包括在 SDK 示例(通过 +Android SDK 管理器提供)中,以 +<code><sdk_root>/APIDemos/app/src/main/java/com/example/android/apis/app/FragmentRetainInstance.java</code> 形式位于您的系统中。</p> + + + +<h2 id="Managing">管理片段</h2> + +<p>要想管理您的 Activity 中的片段,您需要使用 {@link android.app.FragmentManager}。要想获取它,请从您的 Activity 调用 +{@link android.app.Activity#getFragmentManager()}。</p> + +<p>您可以使用 {@link android.app.FragmentManager} 执行的操作包括:</p> + +<ul> + <li>通过 {@link +android.app.FragmentManager#findFragmentById findFragmentById()}(对于在 Activity 布局中提供 UI +的片段)或 {@link android.app.FragmentManager#findFragmentByTag +findFragmentByTag()}(对于提供或不提供 UI 的片段)获取 Activity 中存在的片段</li> + <li>通过 {@link +android.app.FragmentManager#popBackStack()}(模拟用户发出的 <em>Back</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>在 Activity 中使用片段的一大优点是,可以根据用户行为通过它们执行添加、删除、替换以及其他操作。 +您提交给 Activity 的每组更改都称为事务,您可以使用 +{@link +android.app.FragmentTransaction} 中的 API 来执行一项事务。您也可以将每个事务保存到由 Activity 管理的返回栈内,从而让用户能够回退片段更改(类似于回退 Activity)。 + +</p> + +<p>您可以像下面这样从 {@link +android.app.FragmentManager} 获取一个 {@link android.app.FragmentTransaction} 实例:</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()} +等方法为给定事务设置您想要执行的所有更改。然后,要想将事务应用到 Activity,您必须调用 +{@link android.app.FragmentTransaction#commit()}。</p> +</dl> + +<p>不过,在您调用 {@link +android.app.FragmentTransaction#commit()} 之前,您可能想调用 {@link +android.app.FragmentTransaction#addToBackStack addToBackStack()},以将事务添加到片段事务返回栈。 +该返回栈由 Activity 管理,允许用户通过按“返回” +<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} ID 所标识的布局容器中的任何片段(如有)。通过调用 {@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()} +不会立即执行事务,而是在 Activity 的 UI +线程(“主”线程)可以执行该操作时再安排其在线程上运行。不过,如有必要,您也可以从 UI 线程调用 {@link +android.app.FragmentManager#executePendingTransactions()} 以立即执行 +{@link android.app.FragmentTransaction#commit()} 提交的事务。通常不必这样做,除非其他线程中的作业依赖该事务。 +</p> + +<p class="caution"><strong>注意:</strong>您只能在 Activity<a href="{@docRoot}guide/components/activities.html#SavingActivityState">保存其状态</a>(用户离开 Activity)之前使用 {@link +android.app.FragmentTransaction#commit commit()} +提交事务。如果您试图在该时间点后提交,则会引发异常。 +这是因为如需恢复 Activity,则提交后的状态可能会丢失。 +对于丢失提交无关紧要的情况,请使用 {@link +android.app.FragmentTransaction#commitAllowingStateLoss()}。</p> + + + + +<h2 id="CommunicatingWithActivity">与 Activity 通信</h2> + +<p>尽管 {@link android.app.Fragment} 是作为独立于 +{@link android.app.Activity} +的对象实现,并且可在多个 Activity 内使用,但片段的给定实例会直接绑定到包含它的 Activity。</p> + +<p>具体地说,片段可以通过 {@link +android.app.Fragment#getActivity()} +访问 {@link android.app.Activity} 实例,并轻松地执行在 Activity 布局中查找视图等任务。</p> + +<pre> +View listView = {@link android.app.Fragment#getActivity()}.{@link android.app.Activity#findViewById findViewById}(R.id.list); +</pre> + +<p>同样地,您的 Activity 也可以使用 +{@link +android.app.FragmentManager#findFragmentById findFragmentById()} 或 {@link +android.app.FragmentManager#findFragmentByTag findFragmentByTag()},通过从 {@link android.app.FragmentManager} 获取对 {@link android.app.Fragment} 的引用来调用片段中的方法。例如:</p> + +<pre> +ExampleFragment fragment = (ExampleFragment) getFragmentManager().findFragmentById(R.id.example_fragment); +</pre> + + +<h3 id="EventCallbacks">创建对 Activity 的事件回调</h3> + +<p>在某些情况下,您可能需要通过片段与 Activity 共享事件。执行此操作的一个好方法是,在片段内定义一个回调接口,并要求宿主 Activity 实现它。 + +当 Activity 通过该接口收到回调时,可以根据需要与布局中的其他片段共享这些信息。 +</p> + +<p>例如,如果一个新闻应用的 Activity 有两个片段 —一个用于显示文章列表(片段 A),另一个用于显示文章(片段 B)—,那么片段 A必须在列表项被选定后告知 Activity,以便它告知片段 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>然后,该片段的宿主 Activity 会实现 +{@code OnArticleSelectedListener} +接口并替代 {@code onArticleSelected()},将来自片段 A +的事件通知片段 B。为确保宿主 Activity 实现此界面,片段 A 的 {@link +android.app.Fragment#onAttach onAttach()} +回调方法(系统在向 Activity 添加片段时调用的方法)会通过转换传递到 +{@link android.app.Fragment#onAttach +onAttach()} 中的 {@link android.app.Activity} 来实例化 {@code OnArticleSelectedListener} 的实例:</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>如果 Activity 未实现界面,则片段会引发 +{@link java.lang.ClassCastException}。实现时,{@code mListener} +成员会保留对 Activity 的 {@code OnArticleSelectedListener} 实现的引用,以便片段 A 可以通过调用 +{@code OnArticleSelectedListener} +界面定义的方法与 Activity 共享事件。例如,如果片段 A 是 +{@link android.app.ListFragment} +的一个扩展,则用户每次点击列表项时,系统都会调用片段中的 {@link android.app.ListFragment#onListItemClick +onListItemClick()},然后该方法会调用 {@code onArticleSelected()} +以与 Activity 共享事件:</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>传递到 {@link +android.app.ListFragment#onListItemClick onListItemClick()} 的 {@code id} 参数是被点击项的行 +ID,即 Activity(或其他片段)用来从应用的 {@link +android.content.ContentProvider} 获取文章的 ID。</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>您的片段可以通过实现 +{@link android.app.Fragment#onCreateOptionsMenu(Menu,MenuInflater) onCreateOptionsMenu()} 向 Activity 的<a href="{@docRoot}guide/topics/ui/menus.html#options-menu">选项菜单</a>(并因此向<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>)贡献菜单项。不过,为了使此方法能够收到调用,您必须在 +{@link +android.app.Fragment#onCreate(Bundle) onCreate()} 期间调用 {@link +android.app.Fragment#setHasOptionsMenu(boolean) setHasOptionsMenu()},以指示片段想要向选项菜单添加菜单项(否则,片段将不会收到对 +{@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>尽管您的片段会收到与其添加的每个菜单项对应的菜单项选定回调,但当用户选择菜单项时,Activity 会首先收到相应的回调。 + +如果 Activity 对菜单项选定回调的实现不会处理选定的菜单项,则系统会将事件传递到片段的回调。 +这适用于选项菜单和上下文菜单。 +</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>Activity 生命周期对片段生命周期的影响。 +</p> +</div> + +<p>管理片段生命周期与管理 Activity 生命周期很相似。和 Activity 一样,片段也以三种状态存在: +</p> + +<dl> + <dt><i>恢复</i></dt> + <dd>片段在运行中的 Activity 中可见。</dd> + + <dt><i>暂停</i></dt> + <dd>另一个 Activity 位于前台并具有焦点,但此片段所在的 Activity 仍然可见(前台 Activity 部分透明,或未覆盖整个屏幕)。 + +</dd> + + <dt><i>停止</i></dt> + <dd>片段不可见。宿主 Activity 已停止,或片段已从 Activity 中删除,但已添加到返回栈。 +停止片段仍然处于活动状态(系统会保留所有状态和成员信息)。 +不过,它对用户不再可见,如果 Activity 被终止,它也会被终止。 +</dd> +</dl> + +<p>同样与 Activity 一样,假使 Activity 的进程被终止,而您需要在重建 Activity 时恢复片段状态,您也可以使用 {@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">Activity</a>文档。 + +</p> + +<p>Activity 生命周期与片段生命周期之间的最显著差异在于它们在其各自返回栈中的存储方式。 +默认情况下,Activity 停止时会被放入由系统管理的 Activity 返回栈(以便用户通过“返回” +<em></em> +按钮回退到Activity,<a href="{@docRoot}guide/components/tasks-and-back-stack.html">任务和返回栈</a>对此做了阐述)。不过,仅当您在删除片段的事务执行期间通过调用 +{@link +android.app.FragmentTransaction#addToBackStack(String) addToBackStack()} +显式请求保存实例时,系统才会将片段放入由宿主 Activity 管理的返回栈。 +</p> + +<p>在其他方面,管理片段生命周期与管理 Activity 生命周期非常相似。 +因此,<a href="{@docRoot}guide/components/activities.html#Lifecycle">管理 Activity 生命周期</a>的做法同样适用于片段。 +但您还需要了解 Activity 的生命周期对片段生命周期的影响。 +</p> + +<p class="caution"><strong>注意:</strong>如需 {@link android.app.Fragment} +内的某个 {@link android.content.Context} +对象,可以调用 {@link android.app.Fragment#getActivity()}。但要注意,请仅在片段附加到 Activity 时调用 +{@link android.app.Fragment#getActivity()}。如果片段尚未附加,或在其生命周期结束期间分离,则 +{@link android.app.Fragment#getActivity()} 将返回 null。</p> + + +<h3 id="CoordinatingWithActivity">与 Activity 生命周期协调一致</h3> + +<p>片段所在的 Activity 的生命周期会影响片段的生命周期,其表现为,Activity 的每次生命周期回调都会引发每个片段的类似回调。 + +例如,当 Activity 收到 {@link android.app.Activity#onPause} 时,Activity 中的每个片段也会收到 +{@link android.app.Fragment#onPause}。</p> + +<p>不过,片段还有几个额外的生命周期回调,用于处理与 Activity 的唯一交互,以执行构建和销毁片段 UI 等操作。这些额外的回调方法是: + +</p> + +<dl> + <dt>{@link android.app.Fragment#onAttach onAttach()}</dt> + <dd>在片段已与 Activity 关联时调用({@link +android.app.Activity} 传递到此方法内)。</dd> + <dt>{@link android.app.Fragment#onCreateView onCreateView()}</dt> + <dd>调用它可创建与片段关联的视图层次结构。</dd> + <dt>{@link android.app.Fragment#onActivityCreated onActivityCreated()}</dt> + <dd>在 Activity 的 {@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>在取消片段与 Activity 的关联时调用。</dd> +</dl> + +<p>图 3 +图示说明了受其宿主 Activity 影响的片段生命周期流。在该图中,您可以看到 Activity 的每个连续状态如何决定片段可以收到的回调方法。 +例如,当 Activity 收到其 {@link +android.app.Activity#onCreate onCreate()} 回调时,Activity 中的片段只会收到 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} 回调。</p> + +<p>一旦 Activity 达到恢复状态,您就可以意向 Activity 添加片段和删除其中的片段。 +因此,只有当 Activity 处于恢复状态时,片段的生命周期才能独立变化。 +</p> + +<p>不过,当 Activity 离开恢复状态时,片段会在 Activity 的推动下再次经历其生命周期。 +</p> + + + + +<h2 id="Example">示例</h2> + +<p>为了将本文阐述的所有内容融会贯通,以下提供了一个示例,其中的 Activity 使用两个片段来创建一个双窗格布局。 +下面的 Activity 包括两个片段:一个用于显示莎士比亚戏剧标题列表,另一个用于从列表中选定戏剧时显示其摘要。 + +此外,它还展示了如何根据屏幕配置提供不同的片段配置。 +</p> + +<p class="note"><strong>注:</strong><a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.html">{@code +FragmentLayout.java}</a> +中提供了此 Activity 的完整源代码。</p> + +<p>主 Activity 会在 {@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>通过使用此布局,系统可在 Activity 加载布局时立即实例化 +{@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}。这意味着,当设备纵向显示时,只有戏剧标题列表可见。 +因此,当用户在此配置中点击某个列表项时,应用会启动一个新 Activity 来显示摘要,而不是加载另一个片段。 + +</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} +Activity 以显示该项目的内容。</p> + +<p>以下是 {@code DetailsActivity},它简单地嵌入了 +{@code DetailsFragment},以在屏幕为纵向时显示所选的戏剧摘要:</p> + +{@sample development/samples/ApiDemos/src/com/example/android/apis/app/FragmentLayout.java +details_activity} + +<p>请注意,如果配置为横向,则此 Activity 会自行完成,以便主 Activity 可以接管并沿 {@code TitlesFragment} +显示 +{@code DetailsFragment}。如果用户在纵向显示时启动 +{@code DetailsActivity},但随后旋转为横向(这会重启当前 Activity),就可能出现这种情况。</p> + + +<p>如需查看使用片段的更多示例(以及本示例的完整源文件),请参阅 +<a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/app/index.html#Fragment"> +ApiDemos</a>(可从<a href="{@docRoot}resources/samples/get.html">示例 SDK 组件</a>下载)中提供的 API Demos 示例应用。</p> + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/fundamentals.jd b/docs/html-intl/intl/zh-cn/guide/components/fundamentals.jd new file mode 100644 index 000000000000..4ff22b64acbe --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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 +工具将您的代码—连同任何数据和资源文件—编译到一个 APK: <i>Android 软件包</i>,即带有 +{@code .apk} 后缀的存档文件中。一个 APK 文件包含 +Android 应用的所有内容,它是基于 Android 系统的设备用来安装应用的文件。</p> + +<p>安装到设备后,每个 Android 应用都运行在自己的安全沙箱内: </p> + +<ul> + <li>Android 操作系统是一种多用户 Linux +系统,其中的每个应用都是一位不同的用户;</li> + +<li>默认情况下,系统会为每个应用分配一个唯一的 Linux 用户 ID(该 ID +仅由系统使用,应用并不知晓)。系统为应用中的所有文件设置权限,使得只有分配给该应用的用户 ID +才能访问这些文件; </li> + +<li>每个进程都具有自己的虚拟机 +(VM),因此应用代码是在与其他应用隔离的环境中运行;</li> + +<li>默认情况下,每个应用都在其自己的 Linux 进程内运行。Android +会在需要执行任何应用组件时启动该进程,然后在不再需要该进程或系统必须为其他应用恢复内存时关闭该进程。 +</li> +</ul> + +<p>Android 系统可以通过这种方式实现<em>最小权限原则</em>。也就是说,默认情况下,每个应用都只能访问执行其工作所需的组件,而不能访问其他组件。 + +这样便营造出一个非常安全的环境,在这个环境中,应用无法访问系统中其未获得权限的部分。 +</p> + +<p>不过,应用仍然可以通过一些途径与其他应用共享数据以及访问系统服务: +</p> + +<ul> + <li>可以安排两个应用共享同一 Linux 用户 +ID,在这种情况下,它们能够相互访问彼此的文件。为了节省系统资源,可以安排具有相同用户 ID +的应用在同一 Linux 进程中运行,并共享同一 +VM(应用还必须使用相同的证书签署);</li> + <li>应用可以请求访问设备数据(如用户的联系人、短信、可装入存储装置 +[SD 卡]、相机、蓝牙等)的权限。所有应用权限都必须由用户在安装时授予。 +</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>Activity</b></dt> + +<dd><i>Activity</i>表示具有用户界面的单一屏幕。例如,电子邮件应用可能具有一个显示新电子邮件列表的 Activity、一个用于撰写电子邮件的 Activity 以及一个用于阅读电子邮件的 Activity。 + +尽管这些 Activity 通过协作在电子邮件应用中形成了一种具有凝聚力的用户体验,但每一个 Activity 都独立于其他 Activity 而存在。 + +因此,其他应用可以启动其中任何一个 Activity(如果电子邮件应用允许)。 +例如,相机应用可以启动电子邮件应用内用于撰写新电子邮件的 Activity,以便用户共享图片。 + + +<p>Activity 作为 +{@link android.app.Activity} +的子类实现,您可以在<a href="{@docRoot}guide/components/activities.html">Activity</a>开发者指南中了解有关它的更多详情。</p> +</dd> + + +<dt><b>服务</b></dt> + +<dd><i>服务</i> 是一种在后台运行的组件,用于执行长时间运行的操作或为远程进程执行作业。 +服务不提供用户界面。 +例如,当用户位于其他应用中时,服务可能在后台播放音乐或者通过网络获取数据,但不会阻断用户与 Activity 的交互。 + +诸如 Activity 等其他组件可以启动服务,让其运行或与其绑定以便与其进行交互。 + + +<p>服务作为 +{@link android.app.Service} +的子类实现,您可以在<a href="{@docRoot}guide/components/services.html">服务</a>开发者指南中了解有关它的更多详情。</p> +</dd> + + +<dt><b>内容提供程序</b></dt> + +<dd>内容<i>提供程序</i> 管理一组共享的应用数据。您可以将数据存储在文件系统、SQLite +数据库、Web +上或您的应用可以访问的任何其他永久性存储位置。其他应用可以通过内容提供程序查询数据,甚至修改数据(如果内容提供程序允许)。 +例如,Android +系统可提供管理用户联系人信息的内容提供程序。因此,任何具有适当权限的应用都可以查询内容提供程序的某一部分(如 +{@link +android.provider.ContactsContract.Data}),以读取和写入有关特定人员的信息。 + +<p>内容提供程序也适用于读取和写入您的应用不共享的私有数据。 +例如,<a href="{@docRoot}resources/samples/NotePad/index.html">记事本</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>广播接收器<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 +系统设计的独特之处在于,任何应用都可以启动其他应用的组件。例如,如果您想让用户使用设备的相机拍摄照片,很可能有另一个应用可以执行该操作,那么您的应用就可以利用该应用,而不是开发一个 Activity 来自行拍摄照片。 + +您不需要集成甚至链接到该相机应用的代码,而是只需在拍摄照片的相机应用中启动该 Activity。 + + +完成拍摄时,系统甚至会将照片返回您的应用,以便您使用。对用户而言,就好像相机真正是您应用的组成部分。 +</p> + +<p>当系统启动某个组件时,会启动该应用的进程(如果尚未运行),并实例化该组件所需的类。 +例如,如果您的应用启动相机应用中拍摄照片的 Activity,则该 Activity 会在属于相机应用的进程,而不是您的应用的进程中运行。因此,与大多数其他系统上的应用不同,Android +应用并没有单一入口点(例如,没有 +{@code main()} +功能)。 +</p> + +<p>由于系统在单独的进程中运行每个应用,且其文件权限会限制对其他应用的访问,因此您的应用无法直接启动其他应用中的组件,但 +Android +系统却可以。因此,要想启动其他应用中的组件,您必须向系统传递一则消息,说明您想启动特定组件的 +<em> Intent</em>。 +系统随后便会为您启动该组件。</p> + + +<h3 id="ActivatingComponents">启动组件</h3> + +<p>四种组件类型中的三种—Activity、服务和广播接收器—通过名为 +<em> Intent </em> +的异步消息进行启动。 Intent 会在运行时将各个组件相互绑定(您可以将 Intent 视为从其他组件请求操作的信使),无论组件属于您的应用还是其他应用。 + +</p> + +<p>Intent 使用 {@link android.content.Intent} +对象创建,它定义的消息用于启动特定组件或特定<em>类型</em>的组件— Intent 可以是显式的,也可以是隐式的。 +</p> + +<p>对于 Activity 和服务, Intent 定义要执行的操作(例如,“查看”或“发送”某个内容),并且可以指定要执行操作的数据的 +URI(以及正在启动的组件可能需要了解的信息)。 +例如, Intent 传达的请求可以是启动一个显示图像或打开网页的 Activity。 +在某些情况下,您可以启动 Activity 来接收结果,在这种情况下,Activity 也会在 {@link android.content.Intent} +中返回结果(例如,您可以发出一个 Intent,让用户选取某位联系人并将其返回给您 — 返回 Intent 包括指向所选联系人的 +URI)。 + +</p> + +<p>对于广播接收器, Intent 只会定义要广播的通知(例如,指示设备电池电量不足的广播只包括指示“电池电量不足”的已知操作字符串)。 + +</p> + +<p> Intent 不会启动另一个组件类型 - 内容提供程序,后者会在成为 +{@link android.content.ContentResolver} 的请求目标时启动。内容解析程序通过内容提供程序处理所有直接事务,使得通过提供程序执行事务的组件可以无需执行事务,而是改为在 +{@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()}(当您想让 Activity 返回结果时)来启动 Activity(或为其安排新任务);</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.ContentResolver} 上调用 {@link +android.content.ContentProvider#query query()} 来对内容提供程序执行查询。</li> +</ul> + +<p>如需了解有关 Intent 用法的详细信息,请参阅 <a href="{@docRoot}guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>文档。 +以下文档中还提供了有关启动特定组件的详细信息: +<a href="{@docRoot}guide/components/activities.html">Activity</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>根据应用使用的 +API,声明应用所需的最低<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API 级别</a></li> + <li>声明应用使用或需要的硬件和软件功能,如相机、蓝牙服务或多点触摸屏幕 +</li> + <li>应用需要链接的 API 库(Android 框架 +API 除外),如 <a href="http://code.google.com/android/add-ons/google-apis/maps-overview.html">Google Maps API +库</a></li> + <li>其他功能</li> +</ul> + + +<h3 id="DeclaringComponents">声明组件</h3> + +<p>清单文件的主要任务是告知系统有关应用组件的信息。例如,清单文件可以想下面这样声明 Activity: + </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><a +href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +元素中,{@code android:icon} +属性指向标识应用的图标所对应的资源。</p> + +<p>在 <code><a +href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +元素中,{@code android:name} 属性指定 {@link +android.app.Activity} 子类的完全限定类名,{@code android:label} +属性指定用作 Activity 的用户可见标签的字符串。</p> + +<p>您必须通过以下方式声明所有应用组件:</p> +<ul> + <li>Activity 的 <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>您包括在源代码中,但未在清单文件中声明的 Activity、服务和内容提供程序对系统不可见,因此也永远不会运行。 +不过,广播接收器可以在清单文件中声明或在代码中动态创建(如 +{@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} 来启动 Activity、服务和广播接收器。您可以通过在 Intent 中显式命名目标组件(使用组件类名)来执行此操作。 +不过,Intent 的真正强大之处在于<em>隐式 Intent </em> 概念。 +隐式 Intent 的作用无非是描述要执行的操作类型(还可选择描述您想执行的操作所针对的数据),让系统能够在设备上找到可执行该操作的组件,并启动该组件。 + + +如果有多个组件可以执行 Intent 所描述的操作,则由用户选择使用哪一个组件。 +</p> + +<p>系统通过将接收到的 Intent 与设备上的其他应用的清单文件中提供的 Intent 过滤器进行比较来确定可以响应 Intent 的组件。 +<i></i> +</p> + +<p>当您在应用的清单文件中声明 Activity 时,可以选择性地加入声明 Activity 功能的 Intent 过滤器,以便响应来自其他应用的 Intent。 + +您可以通过将 +<a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code +<intent-filter>}</a> 元素作为组件声明元素的子项进行添加来为您的组件声明 Intent 过滤器。</p> + +<p>例如,如果您开发的一个电子邮件应用包含一个用于撰写新电子邮件的 Activity,则可以像下面这样声明一个 Intent 过滤器来响应“send” Intent(以发送新电子邮件): +</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>然后,如果另一个应用创建了一个包含 +{@link +android.content.Intent#ACTION_SEND} 操作的 Intent,并将其传递到 {@link android.app.Activity#startActivity +startActivity()},则系统可能会启动您的 Activity,以便用户能够草拟并发送电子邮件。</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>例如,如果您的应用需要相机,并使用 Android 2.1(<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">API</a> 7 级)中引入的 +API,您应该像下面这样在清单文件中以要求形式声明这些信息:</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>现在,<em>没有</em>相机且 +Android 版本<em>低于</em> 2.1 的设备将无法从 Google Play 安装您的应用。</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 +文件定义 Activity 用户界面的动画、菜单、样式、颜色和布局。使用应用资源能够在不修改代码的情况下轻松地更新应用的各种特性,并可通过提供备用资源集让您能够针对各种设备配置(如不同的语言和屏幕尺寸)优化您的应用。 + + +</p> + +<p>对于您的 Android 项目中包括的每一项资源,SDK +构建工具都会定义一个唯一的整型 ID,您可以利用它来引用应用代码或 XML +中定义的其他资源中的资源。例如,如果您的应用包含一个名为 {@code +logo.png} 的图像文件(保存在 {@code res/drawable/} 目录中),则 SDK 工具会生成一个名为 +{@code R.drawable.logo} 的资源 +ID,您可以利用它来引用该图像并将其插入您的用户界面。</p> + +<p>提供与源代码分离的资源的其中一个最重要优点在于,您可以提供针对不同设备配置的备用资源。 + +例如,通过在 XML 中定义 UI +字符串,您可以将字符串翻译为其他语言,并将这些字符串保存在单独的文件中。然后,Android +系统会根据向资源目录名称追加的语言<em>限定符</em>(如为法语字符串值追加 {@code res/values-fr/})和用户的语言设置,对您的 +UI +应用相应的语言字符串。</p> + +<p>Android 支持许多不同的备用资源<em>限定符</em>。限定符是一种加入到资源目录名称中,用来定义这些资源适用的设备配置的简短字符串。 + +再举一例,您应该经常会根据设备的屏幕方向和尺寸为 Activity 创建不同的布局。 + +例如,当设备屏幕为纵向(长型)时,您可能想要一种垂直排列按钮的布局;但当屏幕为横向(宽型)时,应按水平方向排列按钮。 + +要想根据方向更改布局,您可以定义两种不同的布局,然后对每个布局的目录名称应用相应的限定符。 + +然后,系统会根据当前设备方向自动应用相应的布局。 +</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>有关如何使用 {@link android.content.Intent} API 来启动应用组件(如 Activity 和服务)以及如何使您的应用组件可供其他应用使用的信息。 + +</dd> + <dt><a href="{@docRoot}guide/components/activities.html">Activity</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/zh-cn/guide/components/index.jd b/docs/html-intl/intl/zh-cn/guide/components/index.jd new file mode 100644 index 000000000000..53e81849c550 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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>使用 DialogFragments</h4> + <p>在这篇帖子中,我将介绍如何使用带有 v4 支持库(旨在支持 Honeycomb 之前的设备实现向后兼容)的 DialogFragments 显示一个简单的编辑对话框,并使用一个接口向调用 Activity 返回一个结果。</p> + </a> + + <a href="http://android-developers.blogspot.com/2011/03/fragments-for-all.html"> + <h4>通用片段</h4> + <p>今天,我们已发布一个展示相同 Fragments API 的静态库(以及新的 LoaderManager 和其他几个类)。因此,与 Android 1.6 或更高版本兼容的应用可以使用 Fragment 创建与平板电脑兼容的用户界面。 </p> + </a> + + <a href="http://android-developers.blogspot.com/2010/07/multithreading-for-performance.html"> + <h4>多线程处理,性能卓越</h4> + <p>创建快速响应的应用的有效方法是:确保最大程度地减少主 UI +线程的工作负载。任何可能会导致应用挂起的、耗时较长的任务均应在其他线程中进行处理。 +</p> + </a> + </div> + + <div class="col-6"> + <h3>培训</h3> + + <a href="http://developer.android.com/training/basics/activity-lifecycle/index.html"> + <h4>管理 Activity 生命周期</h4> + <p>本课程介绍每个 Activity 实例将收到的重要生命周期回调方法,阐述可以如何利用这些方法使 Activity 达到用户预期,且避免它们在 Activity 不需要使用时消耗系统资源。 + +</p> + </a> + + <a href="http://developer.android.com/training/basics/fragments/index.html"> + <h4>利用片段构建动态 UI</h4> + <p>本课程向您介绍如何利用片段创造动态的用户体验、针对不同屏幕尺寸的设备优化应用的用户体验,以及在实现以上目的的同时继续为运行低至 +Android 1.6 版本 +系统的设备提供支持。</p> + </a> + + <a href="http://developer.android.com/training/sharing/index.html"> + <h4>共享内容</h4> + <p>本课程阐述您可以通过使用 Intent API +和 ActionProvider 对象在应用之间收发内容的一些常见方法。</p> + </a> + </div> + +</div> diff --git a/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd b/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd new file mode 100644 index 000000000000..7853eb103e85 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/intents-filters.jd @@ -0,0 +1,901 @@ +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>启动 Activity</b>: +<p>{@link android.app.Activity} 表示应用中的一个屏幕。通过将 +{@link android.content.Intent} 传递给 {@link android.content.Context#startActivity startActivity()},您可以启动新的 +{@link android.app.Activity} 实例。{@link android.content.Intent} +描述了要启动的 Activity,并携带了任何必要的数据。</p> + +<p>如果您希望在 Activity 完成后收到结果,请调用 +{@link android.app.Activity#startActivityForResult +startActivityForResult()}。在 Activity 的 {@link +android.app.Activity#onActivityResult onActivityResult()} 回调中,您的 Activity 将结果作为单独的 +{@link android.content.Intent} +对象接收。如需了解详细信息,请参阅<a href="{@docRoot}guide/components/activities.html">Activity</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 +来启动组件,这是因为您知道要启动的 Activity 或服务的类名。例如,启动新 Activity 以响应用户操作,或者启动服务以在后台下载文件。 + +</li> + +<li><b>隐式 + Intent </b>:不会指定特定的组件,而是声明要执行的常规操作,从而允许其他应用中的组件来处理它。例如,如需在地图上向用户显示位置,则可以使用隐式 + Intent,请求另一具有此功能的应用在地图上显示指定的位置。 +</li> +</ul> + +<p>创建显式 Intent 启动 Activity 或服务时,系统将立即启动 +{@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 如何通过系统传递以启动其他 Activity 的图解:<b>[1]</b> Activity A 创建包含操作描述的 +{@link android.content.Intent},并将其传递给 {@link +android.content.Context#startActivity startActivity()}。<b>[2]</b> +Android 系统搜索所有应用中与 Intent 匹配的 Intent 过滤器。<em></em>找到匹配项之后,<b>[3]</b> 该系统通过调用匹配 Activity(Activity +B)的 {@link +android.app.Activity#onCreate onCreate()} 方法并将其传递给 {@link android.content.Intent},以此启动匹配 Activity。<em></em> +</p> +</div> + +<p>创建隐式 Intent +时,Android 系统通过将 Intent 的内容与在设备上其他应用的<a href="{@docRoot}guide/topics/manifest/manifest-intro.html">清单文件</a>中声明的 + Intent 过滤器进行比较,从而找到要启动的相应组件。<em></em>{@link android.content.Intent}如果 Intent 与 Intent +过滤器匹配,则系统将启动该组件,并将其传递给对象。如果多个 + Intent 过滤器兼容,则系统会显示一个对话框,支持用户选取要使用的应用。</p> + +<p>Intent +过滤器是应用清单文件中的一个表达式,它指定该组件要接收的 Intent +类型。例如,通过为 Activity 声明 Intent 过滤器,您可以使其他应用能够直接使用某一特定类型的 + Intent 启动 Activity。同样,如果您没有为 Activity 声明任何 + Intent 过滤器,则 Activity 只能通过显式 + Intent 启动。<em></em></p> + +<p class="caution"><strong>警告:</strong>为了确保应用的安全性,启动 +{@link android.app.Service} 时,请始终使用显式 + Intent,且不要为服务声明 Intent 过滤器。使用隐式 + Intent 启动服务存在安全隐患,因为您无法确定哪些服务将响应 + Intent,且用户无法看到哪些服务已启动。从 Android 5.0(API 级别 21)开始,如果使用隐式 + Intent +调用 {@link android.content.Context#bindService bindService()},系统会抛出异常。</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>这是可选项,但也是构建<b>显式</b> + Intent 的一项重要信息,这意味着 + Intent 应当仅传递给由组件名称定义的应用组件。如果没有组件名称,则 + Intent 是<b>隐式</b>的,且系统将根据其他 + Intent 信息(例如,以下所述的操作、数据和类别)决定哪个组件应当接收 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></em><em></em> + +<p>对于广播 + Intent,这是指已发生且正在报告的操作。操作在很大程度上决定了其余 Intent 的构成,特别是数据和 +extra 中包含的内容。 + +<p>您可以指定自己的操作,供 + Intent 在您的应用内使用(或者供其他应用在您的应用中调用组件)。但是,您通常应该使用由 +{@link android.content.Intent} 类或其他框架类定义的操作常量。以下是一些用于启动 Activity 的常见操作: +</p> + +<dl> +<dt>{@link android.content.Intent#ACTION_VIEW}</dt> + <dd>如果您拥有一些某项 Activity 可向用户显示的信息(例如,要使用图库应用查看的照片;或者要使用地图应用查找的地址),请使用 + Intent +将此操作与 {@link +android.content.Context#startActivity startActivity()} 结合使用。</dd> + +<dt>{@link android.content.Intent#ACTION_SEND}</dt> + <dd>这也称为“共享” Intent。如果您拥有一些用户可通过其他应用(例如,电子邮件应用或社交共享应用)共享的数据,则应使用 Intent 中将此操作与 +{@link +android.content.Context#startActivity startActivity()} 结合使用。</dd> +</dl> + +<p>有关更多定义通用操作的常量,请参阅{@link android.content.Intent}类引用。 +其他操作在 +Android 框架中的其他位置定义。例如,对于在系统的设置应用中打开特定屏幕的操作,将在 {@link android.provider.Settings} +中定义。</p> + +<p>您可以使用 {@link android.content.Intent#setAction +setAction()} 或 {@link android.content.Intent} 构造函数为 Intent 指定操作。</p> + +<p>如果定义自己的操作,请确保将应用的软件包名称作为前缀。 +例如:</p> +<pre>static final String ACTION_TIMETRAVEL = "com.example.action.TIMETRAVEL";</pre> +</dd> + +<dt><b>数据</b></dt> +<dd>引用待操作数据和/或该数据 +MIME 类型的 URI({@link android.net.Uri} 对象)。提供的数据类型通常由 Intent 的操作决定。例如,如果操作是 +{@link android.content.Intent#ACTION_EDIT},则数据应包含待编辑文档的 +URI。 + +<p>创建 + Intent 时,除了指定 +URI +以外,指定数据类型(其 +MIME 类型)往往也很重要。例如,能够显示图像的Activity可能无法播放音频文件,即便 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()},因为它们会互相抵消彼此的值。请始终使用 +{@link android.content.Intent#setDataAndType setDataAndType()} 同时设置 +URI 和 MIME 类型。</p> +</dd> + +<p><dt><b>类别</b></dt> +<dd>一个包含应处理 Intent +组件类型的附加信息的字符串。您可以将任意数量的类别描述放入一个 + Intent 中,但大多数 + Intent 均不需要类别。以下是一些常见类别: + +<dl> +<dt>{@link android.content.Intent#CATEGORY_BROWSABLE}</dt> + <dd>目标 Activity 允许本身通过 Web +浏览器启动,以显示链接引用的数据,如图像或电子邮件。 + </dd> +<dt>{@link android.content.Intent#CATEGORY_LAUNCHER}</dt> + <dd>该 Activity 是任务的初始 Activity,在系统的应用启动器中列出。 + + </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>Extra</b></dt> +<dd>携带完成请求操作所需的附加信息的键值对。正如某些操作使用特定类型的数据 +URI 一样,有些操作也使用特定的附加数据。 + +<p>您可以使用各种 +{@link android.content.Intent#putExtra putExtra()} 方法添加附加数据,每种方法均接受两个参数:键名和值。您还可以创建一个包含所有附加数据的 {@link android.os.Bundle} +对象,然后使用 {@link +android.content.Intent#putExtras putExtras()} 将 +{@link android.os.Bundle} 插入 {@link android.content.Intent} 中。</p> + +<p>例如,使用 +{@link android.content.Intent#ACTION_SEND} 创建用于发送电子邮件的 Intent 时,可以使用 +{@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 +系统如何启动 Activity(例如,Activity 应属于哪个 +<a href="{@docRoot}guide/components/tasks-and-back-stack.html">任务</a> +),以及启动之后如何处理(例如,它是否属于最近的 Activity 列表)。 + +<p>如需了解详细信息,请参阅 {@link android.content.Intent#setFlags setFlags()} 方法。</p> +</dd> + +</dl> + + + + +<h3 id="ExampleExplicit">显式 Intent 示例</h3> + +<p>显式 + Intent 是指用于启动某个特定应用组件(例如,应用中的某个特定 Activity 或服务)的 Intent。要创建显式 Intent,请为 +{@link android.content.Intent} 对象定义组件名称。Intent +的所有其他属性均为可选属性。</p> + +<p>例如,如果在应用中构建了一个名为 +{@code DownloadService}、旨在从 Web 中下载文件的服务,则可使用以下代码启动该服务:</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>例如,如果您希望用户与他人共享您的内容,请使用 +{@link android.content.Intent#ACTION_SEND} +操作创建 Intent,并添加指定共享内容的 Extra。使用该 + Intent 调用 +{@link android.content.Context#startActivity startActivity()} 时,用户可以选取共享内容所使用的应用。</p> + +<p class="caution"><strong>警告:</strong>用户可能没有任何应用处理您发送到 +{@link android.content.Context#startActivity +startActivity()} 的隐式 Intent。<em></em>如果出现这种情况,则调用将会失败,且应用会崩溃。要验证 Activity 是否会接收 + Intent,请对 {@link android.content.Intent} 对象调用 {@link android.content.Intent#resolveActivity +resolveActivity()}。如果结果为非空,则至少有一个应用能够处理该 + Intent,且可以安全调用 +{@link android.content.Context#startActivity startActivity()}。如果结果为空,则不应使用该 + 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({@link + org.apache.http.protocol.HTTP#PLAIN_TEXT_TYPE + HTTP.PLAIN_TEXT_TYPE}); // "text/plain" MIME type + +// Verify that the intent will resolve to an activity +if (sendIntent.resolveActivity(getPackageManager()) != null) { + startActivity(sendIntent); +} +</pre> + +<p class="note"><strong>注意:</strong>在这种情况下,系统并没有使用 URI,但已声明 + Intent 的数据类型,用于指定 Extra 携带的内容。</p> + + +<p>调用 {@link android.content.Context#startActivity startActivity()} +时,系统将检查已安装的所有应用,确定哪些应用能够处理这种 Intent(即:含 {@link android.content.Intent#ACTION_SEND} +操作并携带“文本/纯”数据的 + Intent )。如果只有一个应用能够处理,则该应用将立即打开并提供给 + Intent。如果多个 Activity 接受 + 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,则用户可以选择要使用的应用,并将其设置为该操作的默认选项。 +如果用户可能希望今后一直使用相同的应用执行某项操作(例如,打开网页时,用户往往倾向于仅使用一种 +Web +浏览器),则这一点十分有用。</p> + +<p>但是,如果多个应用可以响应 + Intent,且用户可能希望每次使用不同的应用,则应采用显式方式显示选择器对话框。选择器对话框要求用户选择每次操作要使用的应用(用户无法为该操作选择默认应用)。 + +例如,当应用使用 {@link +android.content.Intent#ACTION_SEND} +操作执行“共享”时,用户根据目前的状况可能需要使用另一不同的应用,因此应当始终使用选择器对话框,如图 2 中所示。</p> + + + + +<p>要显示选择器,请使用 {@link +android.content.Intent#createChooser createChooser()} 创建 {@link android.content.Intent},并将其传递给 {@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>这将显示一个对话框,其中包含响应传递给 {@link +android.content.Intent#createChooser createChooser()} +方法的 Intent 的应用列表,并使用提供的文本作为对话框标题。</p> + + + + + + + + + +<h2 id="Receiving">接收隐式 Intent </h2> + +<p>要公布应用可以接收哪些隐式 + Intent,请在<a href="{@docRoot}guide/topics/manifest/manifest-intro.html">清单文件</a>中使用 <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html">{@code <intent-filter>}</a> +元素为每个应用组件声明一个或多个 Intent 过滤器。每个 + Intent 过滤器均根据 Intent 的操作、数据和类别指定自身接受的 + Intent 类型。仅当隐式 + Intent 可以通过 Intent 过滤器之一传递时,系统才会将该 Intent 传递给应用组件。</p> + +<p class="note"><strong>注意:</strong>显式 + Intent 始终会传递给其目标,无论组件声明的 Intent 过滤器如何均是如此。</p> + +<p>应用组件应当为自身可执行的每个独特作业声明单独的过滤器。例如,图像库应用中的一个 Activity 可能会有两个过滤器,分别用于查看图像和编辑图像。 + +当 Activity 启动时,它将检查 +{@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>在 {@code name} 属性中,声明接受的 Intent 操作。该值必须是操作的文本字符串值,而不是类常量。 +</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>在 {@code name} 属性中,声明接受的 Intent 类别。该值必须是操作的文本字符串值,而不是类常量。 + + + <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()} 将按照已申明 {@link android.content.Intent#CATEGORY_DEFAULT} 类别的方式处理所有 + Intent。 + 如果未在 Intent 过滤器中声明此类别,则隐式 + Intent 不会解析为您的 Activity。</p> + </dd> +</dl> + +<p>例如,以下是一个使用 Intent 过滤器进行的 Activity 声明,当数据类型为文本时,系统将接收 +{@link android.content.Intent#ACTION_SEND} Intent :</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 启动您的应用组件。如果必须确保只有您自己的应用才能启动您的某一组件,请针对该组件将 <a href="{@docRoot}guide/topics/manifest/activity-element.html#exported">{@code +exported}</a> +属性设置为 {@code "false"}。<em></em> +</p> +</div> +</div> + +<p>系统通过将 + Intent 与所有这三个元素进行比较,根据过滤器测试隐式 Intent。隐式 Intent 若要传递给组件,必须通过所有这三项测试。如果 + Intent 甚至无法匹配其中任何一项测试,则 Android +系统不会将其传递给组件。但是,由于一个组件可能有多个 + 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> +对于所有 Activity,您必须在清单文件中声明 + 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>第一个 Activity {@code MainActivity} +是应用的主要入口点。当用户最初使用启动器图标启动应用时,该 Activity 将打开:</p> +<ul> + <li>{@link android.content.Intent#ACTION_MAIN} 操作指示这是主要入口点,且不要求输入任何 + Intent 数据。</li> + <li>{@link android.content.Intent#CATEGORY_LAUNCHER} +类别指示此 Activity 的图标应放入系统的应用启动器。如果 +<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>这两个元素必须配对使用,Activity 才会显示在应用启动器中。</p> + +<p>第二个 Activity {@code ShareActivity} +旨在便于共享文本和媒体内容。尽管用户可以通过从 {@code MainActivity} 导航进入此 Activity,但也可以从发出隐式 + Intent(与两个 + Intent 过滤器之一匹配)的另一应用中直接进入 {@code ShareActivity}。</p> + +<p class="note"><strong>注意:</strong>MIME 类型 +<a href="https://developers.google.com/panorama/android/">{@code +application/vnd.google.panorama360+jpg}</a> 是一个指定全景照片的特殊 +数据类型,您可以使用 <a href="{@docRoot}reference/com/google/android/gms/panorama/package-summary.html">Google +全景</a> API 对其进行处理。</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>声明用户使用您的<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">通知</a>执行操作时所要执行的 + Intent(Android 系统的 {@link android.app.NotificationManager} +执行 {@link android.content.Intent})。 + <li>声明用户使用您的 +<a href="{@docRoot}guide/topics/appwidgets/index.html">应用小工具</a>执行操作时要执行的 + Intent(主屏幕应用执行 {@link android.content.Intent})。 + <li>声明未来某一特定时间要执行的 Intent(Android +系统的 {@link android.app.AlarmManager} 执行 {@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 时,应用不会使用调用(如 {@link android.content.Context#startActivity +startActivity()})执行该 Intent。相反,通过调用相应的创建器方法创建 +{@link android.app.PendingIntent} 时,您必须声明所需的组件类型:</p> + +<ul> + <li>{@link android.app.PendingIntent#getActivity PendingIntent.getActivity()},适用于启动 +{@link android.app.Activity} 的 {@link android.content.Intent}。</li> + <li>{@link android.app.PendingIntent#getService PendingIntent.getService()},适用于启动 +{@link android.app.Service} 的 {@link android.content.Intent}。</li> + <li>{@link android.app.PendingIntent#getBroadcast PendingIntent.getBroadcast()},适用于启动 +{@link android.content.BroadcastReceiver} 的 {@link android.content.Intent}。</li> +</ul> + +<p>除非您的应用正在从其他应用中接收待定 + Intent,否则上述用于创建 {@link android.app.PendingIntent} +的方法可能是您所需的唯一 {@link android.app.PendingIntent} 方法。<em></em></p> + +<p>每种方法均会提取当前的应用 {@link android.content.Context}、您要包装的 +{@link android.content.Intent} 以及一个或多个指定应如何使用该 Intent 的标志(例如,是否可以多次使用该 + Intent)。</p> + +<p>如需了解有关使用待定 + Intent 的详细信息,请参阅<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">通知</a> +和<a href="{@docRoot}guide/topics/appwidgets/index.html">应用小工具</a> API 指南等手册中每个相应用例的相关文档。</p> + + + + + + + +<h2 id="Resolution"> Intent 解析</h2> + + +<p>当系统收到隐式 + Intent 以启动 Activity 时,它根据以下三个方面将该 Intent 与 Intent 过滤器进行比较,搜索该 Intent 的最佳 Activity:</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} +类别应用于传递给 {@link +android.content.Context#startActivity startActivity()} 和 {@link +android.app.Activity#startActivityForResult startActivityForResult()} 的所有隐式 + Intent。因此,如需 Activity 接收隐式 Intent,则必须将 {@code "android.intent.category.DEFAULT"} 的类别包括在其 Intent 过滤器中(如上文的 +{@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 介质类型)。URI 的每个部分均包含单独的 +{@code scheme}、{@code host}、{@code port} +和 {@code path} 属性: +</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>将 + Intent 中的 URI 与过滤器中的 URI 规范进行比较时,它仅与过滤器中包含的部分 URI 进行比较。例如:</p> +<ul> + <li>如果过滤器仅指定架构,则具有该架构的所有 +URI 均与该过滤器匹配。</li> + <li>如果过滤器指定架构和权限、但未指定路径,则具有相同架构和权限的所有 URI +都会通过过滤器,无论其路径如何均是如此。</li> + <li>如果过滤器指定架构、权限和路径,则仅具有相同架构、权限和路径 +的 URI 才会通过过滤器。</li> +</ul> + +<p class="note"><strong>注意:</strong>路径规范可以包含星号通配符 +(*),因此仅需部分匹配路径名即可。</p> + +<p>数据测试会将 Intent 中的 URI 和 MIME +类型与过滤器中指定的 URI 和 MIME 类型进行比较。规则如下: +</p> + +<ol type="a"> +<li>仅当过滤器未指定任何 URI 或 MIME 类型时,不含 URI 和 MIME 类型的 + Intent 才会通过测试。</li> + +<li>对于包含 URI、但不含 MIME 类型(既未显式声明,也无法通过 +URI 推断得出)的 Intent,仅当其 URI +与过滤器的 URI 格式匹配、且过滤器同样未指定 MIME 类型时,才会通过测试。</li> + +<li>仅当过滤器列出相同的 MIME 类型且未指定 URI 格式时,包含 MIME +类型、但不含 URI 的 Intent 才会通过测试。</li> + +<li>仅当 MIME 类型与过滤器中列出的类型匹配时,包含 +URI 和 MIME 类型(通过显式声明,或可以通过 +URI 推断得出)的 Intent 才会通过测试的 MIME 类型部分。如果 Intent 的 URI 与过滤器中的 +URI 匹配,或者如果 Intent 具有 {@code content:} +或 {@code file:} URI +且过滤器未指定 URI,则 Intent 会通过测试的 URI 部分。换而言之,如果过滤器仅列出 MIME 类型,则假定组件支持 +{@code content:} 和 {@code file:} 数据。<em></em></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,这不仅有助于发现要激活的目标组件,还有助于发现设备上组件集的相关信息。例如,主页应用通过使用指定 +{@link android.content.Intent#ACTION_MAIN} 操作和 +{@link android.content.Intent#CATEGORY_LAUNCHER} 类别的 + Intent 过滤器查找所有 Activity,以此填充应用启动器。</p> + +<p>您的应用可以采用类似的方式使用 + Intent 匹配。{@link android.content.pm.PackageManager} 提供了一整套 {@code query...()} +方法来返回所有能够接受特定 Intent +的组件。此外,它还提供了一系列类似的 {@code resolve...()} 方法来确定响应 Intent +的最佳组件。例如,{@link android.content.pm.PackageManager#queryIntentActivities +queryIntentActivities()} +将返回能够执行那些作为参数传递的 + Intent 的所有 Activity 列表,而 +{@link +android.content.pm.PackageManager#queryIntentServices +queryIntentServices()} +则可返回类似的服务列表。这两种方法均不会激活组件,而只是列出能够响应的组件。对于广播接收器,有一种类似的方法: +{@link android.content.pm.PackageManager#queryBroadcastReceivers +queryBroadcastReceivers()}。 +</p> + + + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/loaders.jd b/docs/html-intl/intl/zh-cn/guide/components/loaders.jd new file mode 100644 index 000000000000..d8427b0cd109 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/loaders.jd @@ -0,0 +1,494 @@ +page.title=加载器 +parent.title=Activity +parent.link=activities.html +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文内容</h2> + <ol> + <li><a href="#summary">Loader 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 中引入了加载器,支持轻松在 Activity 或片段中异步加载数据。 +加载器具有以下特征:</p> + <ul> + <li>可用于每个 {@link android.app.Activity} 和 {@link +android.app.Fragment}。</li> + <li>支持异步加载数据。</li> + <li>监控其数据源并在内容变化时传递新结果。 +</li> + <li>在某一配置更改后重建加载器时,会自动重新连接上一个加载器的 Cursor。 +因此,它们无需重新查询其数据。 +</li> + </ul> + +<h2 id="summary">Loader 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 /> + 每个 Activity 或片段中只有一个 {@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},但您也可以实现自己的子类。加载器处于Activity状态时,应监控其数据源并在内容变化时传递新结果。 + + </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}。此类采用标准方式为查询 Cursor 实现 {@link +android.content.Loader} +协议。它是以 {@link android.content.AsyncTaskLoader} +为基础而构建,在后台线程中执行 Cursor 查询,因此不会阻塞应用的 UI。使用此加载器是从 +{@link +android.content.ContentProvider} +异步加载数据的最佳方式,而不用通过片段或 Activity 的 API 来执行托管查询。</td> + </tr> +</table> + +<p>上表中的类和接口是您在应用中用于实现加载器的基本组件。 +并非您创建的每个加载器都要用到上述所有类和接口。但是,为了初始化加载器以及实现一个 {@link android.content.Loader} +类(如 {@link +android.content.CursorLoader}),您始终需要要引用 +{@link +android.app.LoaderManager}。下文将为您展示如何在应用中使用这些类和接口。 +</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.CursorLoader} 时的数据源,如 {@link android.content.ContentProvider}。</li> +</ul> +<h3 id="starting">启动加载器</h3> + +<p>{@link android.app.LoaderManager} 可在 {@link android.app.Activity} 或 +{@link android.app.Fragment} 内管理一个或多个 {@link +android.content.Loader} 实例。每个 Activity 或片段只有一个 {@link +android.app.LoaderManager}。</p> + +<p>通常,您会使用 Activity 的 {@link +android.app.Activity#onCreate onCreate()} 方法或片段的 +{@link android.app.Fragment#onActivityCreated onActivityCreated()} +方法初始化 {@link android.content.Loader}。您执行操作如下: +</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>用于标识加载器的唯一 ID。在此示例中,ID 为 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()} +调用确保加载器已初始化且处于Activity状态。这可能会出现两种结果:</p> +<ul> + <li>如果 ID +指定的加载器已存在,则将重复使用上次创建的加载器。</li> + <li>如果 +ID 指定的加载器不存在,则 {@link android.app.LoaderManager#initLoader initLoader()} 将触发 +{@link android.app.LoaderManager.LoaderCallbacks} +方法 {@link android.app.LoaderManager.LoaderCallbacks#onCreateLoader onCreateLoader()}。在此方法中,您可以实现代码以实例化并返回新加载器。有关详细介绍,请参阅 <a href="#onCreateLoader">onCreateLoader</a> 部分。<em></em> +</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()} +时(如上所述),它将使用含有指定 ID 的现有加载器(如有)。如果没有,则它会创建一个。但有时,您想放弃这些旧数据并重新开始。 +</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})在停止运行后,仍需保留其数据。这样,应用即可保留 Activity 或片段的 +{@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()}:针对指定的 +ID 进行实例化并返回新的 {@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()}),该方法将检查是否已存在由该 ID 指定的加载器。如果没有,它将触发 {@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>:您可以在 selection 中包含 ?s,它将按照在 selection 中显示的顺序替换为 +<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} 的一个 Cursor,则您不应手动对其调用 {@link +android.database.Cursor#close close()}。如果 Cursor 放置在 +{@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>此实现调用值为 <code>null</code> +的{@link android.widget.SimpleCursorAdapter#swapCursor swapCursor()}: +</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/zh-cn/guide/components/processes-and-threads.jd b/docs/html-intl/intl/zh-cn/guide/components/processes-and-threads.jd new file mode 100644 index 000000000000..c88ecf4b81e8 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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 用户 ID 并使用相同的证书进行签署。 + + +</p> + +<p>此外,<a href="{@docRoot}guide/topics/manifest/application-element.html">{@code +<application>}</a> 元素还支持 {@code android:process} +属性,以设置适用于所有组件的默认值。</p> + +<p>如果内存不足,而其他为用户提供更紧急服务的进程又需要内存时,Android +可能会决定在某一时刻关闭某一进程。在被终止进程中运行的应用组件也会随之销毁。 +当这些组件需要再次运行时,系统将为它们重启进程。 +</p> + +<p>决定终止哪个进程时,Android +系统将权衡它们对用户的相对重要程度。例如,相对于托管可见 Activity 的进程而言,它更有可能关闭托管屏幕上不再可见的 Activity 进程。 +因此,是否终止某个进程的决定取决于该进程中所运行组件的状态。 +下面,我们介绍决定终止进程所用的规则。 + </p> + + +<h3 id="Lifecycle">进程生命周期</h3> + +<p>Android 系统将尽量长时间地保持应用进程,但为了新建进程或运行更重要的进程,最终需要清除旧进程来回收内存。 +为了确定保留或终止哪些进程,系统会根据进程中正在运行的组件以及这些组件的状态,将每个进程放入“重要性层次结构”中。 + + +必要时,系统会首先消除重要性最低的进程,然后是重要性略逊的进程,依此类推,以回收系统资源。 + +</p> + +<p>重要性层次结构一共有 5 级。以下列表按照重要程度列出了各类进程(第一个进程最重要,将是最后一个被终止的进程): +<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},后者绑定到用户正在交互的 Activity +</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#onReceive onReceive()} 方法的 {@link android.content.BroadcastReceiver}</li> + </ul> + + <p>通常,在任意给定时间前台进程都为数不多。只有在内在不足以支持它们同时继续运行这一万不得已的情况下,系统才会终止它们。 +此时,设备往往已达到内存分页状态,因此需要终止一些前台进程来确保用户界面正常响应。 + +</p></li> + + <li><b>可见进程</b> + <p>没有任何前台组件、但仍会影响用户在屏幕上所见内容的进程。 +如果一个进程满足以下任一条件,即视为可见进程: +</p> + + <ul> + <li>托管不在前台、但仍对用户可见的 +{@link android.app.Activity}(已调用其 {@link android.app.Activity#onPause onPause()} 方法)。例如,如果前台 Activity 启动了一个对话框,允许在其后显示上一 Activity,则有可能会发生这种情况 + +</li> + + <li>托管绑定到可见(或前台)Activity 的 +{@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>包含目前对用户不可见的 Activity 的进程(已调用 Activity 的 +{@link android.app.Activity#onStop onStop()} 方法)。这些进程对用户体验没有直接影响,系统可能随时终止它们,以回收内存供前台进程、可见进程或服务进程使用。 + + +通常会有很多后台进程在运行,因此它们会保存在 +LRU +(最近最少使用)列表中,以确保包含用户最近查看的 Activity 的进程最后一个被终止。如果某个 Activity 正确实现了生命周期方法,并保存了其当前状态,则终止其进程不会对用户体验产生明显影响,因为当用户导航回该 Activity 时,Activity 会恢复其所有可见状态。 + + +有关保存和恢复状态的信息,请参阅<a href="{@docRoot}guide/components/activities.html#SavingActivityState">Activity</a>文档。 +</p> + </li> + + <li><b>空进程</b> + <p>不含任何活动应用组件的进程。保留这种进程的的唯一目的是用作缓存,以缩短下次在其中运行组件所需的启动时间。 + +为使总体系统资源在进程缓存和底层内核缓存之间保持平衡,系统往往会终止这些进程。 +</p> + </li> +</ol> + + + <p>根据进程中当前活动组件的重要程度,Android +会将进程评定为它可能达到的最高级别。例如,如果某进程托管着服务和可见 Activity,则会将此进程评定为可见进程,而不是服务进程。 +</p> + + <p>此外,一个进程的级别可能会因其他进程对它的依赖而有所提高,即服务于另一进程的进程其级别永远不会低于其所服务的进程。 + +例如,如果进程 A +中的内容提供程序为进程 B 中的客户端提供服务,或者如果进程 A 中的服务绑定到进程 B 中的组件,则进程 A 始终被视为至少与进程 +B 同样重要。</p> + + <p>由于运行服务的进程其级别高于托管后台 Activity 的进程,因此启动长时间运行操作的 Activity 最好为该操作启动<a href="{@docRoot}guide/components/services.html">服务</a>,而不是简单地创建工作线程,当操作有可能比 Activity 更加持久时尤要如此。例如,正在将图片上传到网站的 Activity 应该启动服务来执行上传,这样一来,即使用户退出 Activity,仍可在后台继续执行上传操作。使用服务可以保证,无论 Activity 发生什么情况,该操作至少具备“服务进程”优先级。 + + + + + +同理,广播接收器也应使用服务,而不是简单地将耗时冗长的操作放入线程中。 +</p> + + + + +<h2 id="Threads">线程</h2> + +<p>应用启动时,系统会为应用创建一个名为“主线程”的执行线程。 +此线程非常重要,因为它负责将事件分派给相应的用户界面小工具,其中包括绘图事件。 +此外,它也是应用与 +Android UI 工具包组件(来自 {@link +android.widget} 和 {@link android.view} 软件包的组件)进行交互的线程。因此,主线程有时也称为 +UI 线程。</p> + +<p>系统绝对不会为每个组件实例创建单独的线程。<em></em>运行于同一进程的所有组件均在 +UI +线程中实例化,并且对每个组件的系统调用均由该线程进行分派。因此,响应系统回调的方法(例如,报告用户操作的 +{@link android.view.View#onKeyDown onKeyDown()} +或生命周期回调方法)始终在进程的 UI 线程中运行。</p> + +<p>例如,当用户触摸屏幕上的按钮时,应用的 +UI +线程会将触摸事件分派给小工具,而小工具反过来又设置其按下状态,并将无效请求发布到事件队列中。UI +线程从队列中取消该请求并通知小工具应该重绘自身。</p> + +<p>在应用执行繁重的任务以响应用户交互时,除非正确实施应用,否则这种单线程模式可能会导致性能低下。 +特别地,如果 +UI +线程需要处理所有任务,则执行耗时很长的操作(例如,网络访问或数据库查询)将会阻塞整个 +UI。一旦线程被阻塞,将无法分派任何事件,包括绘图事件。从用户的角度来看,应用显示为挂起。 +更糟糕的是,如果 UI +线程被阻塞超过几秒钟时间(目前大约是 5 秒钟),用户就会看到一个让人厌烦的“<a href="http://developer.android.com/guide/practices/responsiveness.html">应用无响应</a>”(ANR) +对话框。如果引起用户不满,他们可能就会决定退出并卸载此应用。 +</p> + +<p>此外,Android UI 工具包并非线程安全工具包。<em></em>因此,您不得通过工作线程操纵 +UI,而只能通过 +UI 线程操纵用户界面。因此,Android 的单线程模式必须遵守两条规则:</p> + +<ol> +<li>不要阻塞 UI 线程 +<li>不要在 UI 线程之外访问 Android UI 工具包 +</ol> + +<h3 id="WorkerThreads">工作线程</h3> + +<p>根据上述单线程模式,要保证应用 UI +的响应能力,关键是不能阻塞 UI 线程。如果执行的操作不能很快完成,则应确保它们在单独的线程(“后台”或“工作”线程)中运行。 + +</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>乍看起来,这段代码似乎运行良好,因为它创建了一个新线程来处理网络操作。 +但是,它违反了单线程模式的第二条规则:不要在 +UI 线程之外访问 Android UI 工具包<em></em>—此示例从工作线程(而不是 UI 线程)修改了 {@link +android.widget.ImageView}。这可能导致出现不明确、不可预见的行为,但要跟踪此行为困难而又费时。 +</p> + +<p>为解决此问题,Android 提供了几种途径来从其他线程访问 UI +线程。以下列出了几种有用的方法:</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>现在,上述实现属于线程安全型:在单独的线程中完成网络操作,而在 UI 线程中操纵 +{@link android.widget.ImageView}。</p> + +<p>但是,随着操作日趋复杂,这类代码也会变得复杂且难以维护。 +要通过工作线程处理更复杂的交互,可以考虑在工作线程中使用 +{@link android.os.Handler} +处理来自 UI 线程的消息。当然,最好的解决方案或许是扩展 {@link android.os.AsyncTask} 类,此类简化了与 +UI 进行交互所需执行的工作线程任务。</p> + + +<h4 id="AsyncTask">使用 AsyncTask</h4> + +<p>{@link android.os.AsyncTask} +允许对用户界面执行异步操作。它会先阻塞工作线程中的操作,然后在 UI +线程中发布结果,而无需您亲自处理线程和/或处理程序。</p> + +<p>要使用它,必须创建 {@link android.os.AsyncTask} 子类并实现 {@link +android.os.AsyncTask#doInBackground doInBackground()} +回调方法,该方法将在后台线程池中运行。要更新 UI,必须实现 {@link +android.os.AsyncTask#onPostExecute onPostExecute()} 以传递 {@link +android.os.AsyncTask#doInBackground doInBackground()} 返回的结果并在 UI 线程中运行,这样,您即可安全更新 UI。稍后,您可以通过从 UI 线程调用 +{@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>现在 UI 是安全的,代码也得到简化,因为任务分解成了两部分:一部分应在工作线程内完成,另一部分应在 +UI 线程内完成。</p> + +<p>下面简要概述了 AsyncTask 的工作方法,但要全面了解如何使用此类,您应阅读 {@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()} 均在 UI 线程中调用</li> +<li>{@link android.os.AsyncTask#doInBackground doInBackground()} 返回的值将发送到 +{@link android.os.AsyncTask#onPostExecute onPostExecute()}</li> +<li>您可以随时在 {@link +android.os.AsyncTask#doInBackground doInBackground()} 中调用{@link android.os.AsyncTask#publishProgress publishProgress()},以在 UI 线程中执行 {@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>(例如,用户更改了屏幕方向)导致 Activity 意外重启,这可能会销毁工作线程。 + +要了解如何在这种重启情况下坚持执行任务,以及如何在 Activity 被销毁时正确地取消任务,请参阅<a href="http://code.google.com/p/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} +的同一进程,则该方法在调用方的线程中执行。但是,如果调用源自其他进程,则该方法将在从线程池选择的某个线程中执行(而不是在进程的 UI 线程中执行),线程池由系统在与 {@link android.os.IBinder +IBinder} 相同的进程中维护。例如,即使服务的 +{@link android.app.Service#onBind onBind()} 方法将从服务进程的 UI +线程调用,在 +{@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()} 方法)将从内容提供程序所在进程的线程池中调用,而不是从进程的 +UI 线程调用。由于这些方法可能会同时从任意数量的线程调用,因此它们也必须实现为线程安全方法。 + </p> + + +<h2 id="IPC">进程间通信</h2> + +<p>Android 利用远程过程调用 +(RPC) 提供了一种进程间通信 +(IPC) +机制,通过这种机制,由 Activity 或其他应用组件调用的方法将(在其他进程中)远程执行,而所有结果将返回给调用方。这就要求把方法调用及其数据分解至操作系统可以识别的程度,并将其从本地进程和地址空间传输至远程进程和地址空间,然后在远程进程中重新组装并执行该调用。 + +然后,返回值将沿相反方向传输回来。 +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/zh-cn/guide/components/recents.jd b/docs/html-intl/intl/zh-cn/guide/components/recents.jd new file mode 100644 index 000000000000..2bf1a5bd4ff3 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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">使用 Activity 属性添加任务</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>概览屏幕(也称为最新动态屏幕、最近任务列表或最近使用的应用)是一个系统级别 +UI,其中列出了最近访问过的<a href="{@docRoot}guide/components/activities.html">Activity</a>和<a href="{@docRoot}guide/components/tasks-and-back-stack.html">任务</a>。 +用户可以浏览该列表并选择要恢复的任务,也可以通过滑动清除任务将其从列表中删除。 + +对于 +Android 5.0 版本(API 级别 21),包含多个文档的同一 Activity 的多个实例可能会以任务的形式显示在概览屏幕中。例如,Google Drive 可能对多个 +Google 文档中的每个文档均执行一个任务。每个文档均以任务的形式显示在概览屏幕中。 +</p> + +<img src="{@docRoot}images/components/recents.png" alt="" width="284" /> +<p class="img-caption"><strong>图 1. </strong>显示了三个 +Google Drive 文档的概览屏幕,每个文档分别以一个单独的任务表示。</p> + +<p>通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。不过,应用可以确定 Activity 在概览屏幕中的显示方式和时间。 + +您可以使用 {@link android.app.ActivityManager.AppTask} +类来管理任务,使用 +{@link android.content.Intent} +类的 Activity 标志来指定某 Activity 添加到概览屏幕或从中删除的时间。此外,您也可以使用 <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>为 Activity 创建新文档时,可调用 +{@link android.app.ActivityManager.AppTask} +类的 {@link android.app.ActivityManager.AppTask#startActivity(android.content.Context, android.content.Intent, android.os.Bundle) startActivity()} +方法,以向其传递启动 Activity 的 Intent。要插入逻辑换行符以便系统将 Activity 视为新任务显示在概览屏幕中,可在启动 Activity 的 +{@link android.content.Intent} +的 {@link android.content.Intent#addFlags(int) addFlags()} 方法中传递 {@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +标志。</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} +标志,则系统始终会以目标 Activity 作为根创建新任务。此设置允许同一文档在多个任务中打开。以下代码演示了主 Activity 如何执行此操作: +</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} +标志启动的 Activity 必须具有在清单文件中设置的 {@code android:launchMode="standard"} +属性值(默认)。</p> + +<p>当主 Activity 启动新 Activity 时,系统会搜遍现有任务,看看是否有任务的 Intent 与 Activity 的 Intent 组件名称和 Intent 数据相匹配。 +如果未找到任务或者 Intent 包含 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +标志,则会以该 Activity 作为其根创建新任务。如果找到的话,则会将该任务转到前台并将新 + Intent +传递给 +{@link android.app.Activity#onNewIntent onNewIntent()}。新 Activity 将获得 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">使用 Activity 属性添加任务</h3> + +<p>此外,Activity 还可以在其清单文件中指定始终通过使用 +<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>该 Activity 会对文档重复使用现有任务。这与不设置 +{@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} +标志、但设置 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} 标志所产生的效果相同,如上文的<a href="#flag-new-doc">使用 Intent 标志添加任务</a>中所述。<em></em></dd> + + <dt>“{@code always}”</dt> + <dd>该 Activity 为文档创建新任务,即便文档已打开也是如此。使用此值与同时设置 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +和 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 标志所产生的效果相同。</dd> + + <dt>“{@code none”}</dt> + <dd>该 Activity 不会为文档创建新任务。概览屏幕将按其默认方式对待此 Activity:为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。 + +</dd> + + <dt>“{@code never}”</dt> + <dd>该 Activity 不会为文档创建新任务。设置此值会替代 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_DOCUMENT} +和 {@link android.content.Intent#FLAG_ACTIVITY_MULTIPLE_TASK} 标志的行为(如果在 + Intent +中设置了其中一个标志),并且概览屏幕将为应用显示单个任务,该任务将从用户上次调用的任意 Activity 开始继续执行。</dd> +</dl> + +<p class="note"><strong>注:</strong>对于除 {@code none} 和 {@code never} +以外的值,必须使用 {@code launchMode="standard"} 定义 Activity。如果未指定此属性,则使用 +{@code documentLaunchMode="none"}。</p> + +<h2 id="removing">删除任务</h2> + +<p>默认情况下,在 Activity 结束后,文档任务会从概览屏幕中自动删除。 +您可以使用 {@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>在于概览屏幕创建新任务的 Activity 中,您可以通过调用 +{@link android.app.ActivityManager.AppTask#finishAndRemoveTask() finishAndRemoveTask()} +方法指定何时删除该任务以及结束所有与之相关的 Activity。</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>若要将任务保留在概览屏幕中(即使其 Activity 已完成),可在启动 Activity 的 + Intent 的 {@link android.content.Intent#addFlags(int) addFlags()} 方法中传递 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 标志。</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}。文档 Activity 的默认值为 +{@code true},常规 Activity 的默认值为 {@code false}。如前所述,使用此属性替代 +{@link android.content.Intent#FLAG_ACTIVITY_RETAIN_IN_RECENTS} 标志。</p> + + + + + + + diff --git a/docs/html-intl/intl/zh-cn/guide/components/services.jd b/docs/html-intl/intl/zh-cn/guide/components/services.jd new file mode 100644 index 000000000000..9a00e704fa38 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/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">扩展服务类</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)。 +例如,服务可以处理网络事务、播放音乐,执行文件 I/O +或与内容提供程序交互,而所有这一切均可在后台进行。 +</p> + +<p>服务基本上分为两种形式:</p> + +<dl> + <dt>启动</dt> + <dd>当应用组件(如 Activity)通过调用 +{@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>在单独的进程中运行(除非另行指定)。 + +这意味着,如果服务将执行任何 +CPU 密集型工作或阻止性操作(例如 MP3 +播放或联网),则应在服务内创建新线程来完成这项工作。通过使用单独的线程,可以降低发生“应用无响应”(ANR) +错误的风险,而应用的主线程仍可继续专注于运行用户与 Activity 之间的交互。 +</p> + + +<h2 id="Basics">基础知识</h2> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <h3>您应使用服务还是线程?</h3> + <p>简单地说,服务是一种即使用户未与应用交互,但它仍可以在后台运行的组件。 +因此,您应仅在必要时才创建服务。 +</p> + <p>如需在主线程外部执行工作,不过只是在用户正在与应用交互时才有此需要,则应创建新线程而非服务。 +例如,如果您只是想在 Activity 运行的同时播放一些音乐,则可在 +{@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>当另一个组件(如 Activity)通过调用 +{@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()} +与服务绑定(例如执行 RPC)时,系统将调用此方法。在此方法的实现中,您必须通过返回 +{@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()} 来创建服务(且<em>未</em>调用 +{@link +android.app.Service#onStartCommand onStartCommand()}),则服务只会在该组件与其绑定时运行。一旦该服务与所有客户端之间的绑定全部取消,系统便会销毁它。 +</p> + +<p>仅当内存过低且必须回收系统资源以供具有用户焦点的 Activity 使用时,Android +系统才会强制停止服务。如果将服务绑定到具有用户焦点的 Activity,则它不太可能会终止;如果将服务声明为<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>如同 Activity(以及其他组件)一样,您必须在应用的清单文件中声明所有服务。 +</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> +属性是唯一必需的属性,用于指定服务的类名。应用一旦发布,即不应更改此类名,如若不然,可能会存在因依赖显式 Intent 启动或绑定服务而破坏代码的风险(请阅读博客文章<a href="http://android-developers.blogspot.com/2011/06/things-that-cannot-change.html">Things That Cannot Change</a>[不能更改的内容])。 + + + + +<p>为了确保应用的安全性,<strong>请始终使用显式 Intent 启动或绑定 {@link android.app.Service}</strong>,且不要为服务声明 Intent 过滤器。 +启动哪个服务存在一定的不确定性,而如果对这种不确定性的考量非常有必要,则可为服务提供 Intent 过滤器并从 +{@link +android.content.Intent} +中排除相应的组件名称,但随后必须使用 {@link +android.content.Intent#setPackage setPackage()} + 方法设置 Intent 的软件包,这样可以充分消除目标服务的不确定性。</p> + +<p>此外,还可以通过添加 +<a href="{@docRoot}guide/topics/manifest/service-element.html#exported">{@code android:exported}</a> +属性并将其设置为 {@code "false"},确保服务仅适用于您的应用。这可以有效阻止其他应用启动您的服务,即便在使用显式 Intent 时也如此。 +</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>应用组件(如 Activity)可以通过调用 {@link +android.content.Context#startService startService()} 方法并传递 {@link android.content.Intent} 对象 +(指定服务并包含待使用服务的所有数据)来启动服务。服务通过 +{@link android.app.Service#onStartCommand +onStartCommand()} 方法接收此 {@link android.content.Intent}。</p> + +<p>例如,假设某 Activity 需要将一些数据保存到在线数据库中。该 Activity 可以启动一个协同服务,并通过向 +{@link +android.content.Context#startService startService()} 传递一个 Intent,为该服务提供要保存的数据。服务通过 +{@link +android.app.Service#onStartCommand onStartCommand()} 接收 Intent,连接到 Internet 并执行数据库事务。事务完成之后,服务会自行停止运行并随即被销毁。 +</p> + +<p class="caution"><strong>注意:</strong>默认情况下,服务与服务声明所在的应用运行于同一进程,而且运行于该应用的主线程中。 +因此,如果服务在用户与来自同一应用的 Activity 进行交互时执行密集型或阻止性操作,则会降低 Activity 性能。 + +为了避免影响应用性能,您应在服务内启动新线程。 +</p> + +<p>从传统上讲,您可以扩展两个类来创建启动服务:</p> +<dl> + <dt>{@link android.app.Service}</dt> + <dd>这是适用于所有服务的基类。扩展此类时,必须创建一个用于执行所有服务工作的新线程,因为默认情况下,服务将使用应用的主线程,这会降低应用正在运行的所有 Activity 的性能。 + + +</dd> + <dt>{@link android.app.IntentService}</dt> + <dd>这是 +{@link android.app.Service} 的子类,它使用工作线程逐一处理所有启动请求。如果您不要求服务同时处理多个请求,这是最好的选择。 +您只需实现 {@link +android.app.IntentService#onHandleIntent onHandleIntent()} +方法即可,该方法会接收每个启动请求的 Intent,使您能够执行后台工作。</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()} +的所有 Intent。</li> + <li>创建工作队列,用于将一个 Intent 逐一传递给 {@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()} 的默认实现,可将 Intent 依次发送到工作队列和 {@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()} +必须返回默认实现(即,如何将 Intent 传递给 {@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">扩展服务类</h3> + +<p>正如上一部分中所述,使用 +{@link android.app.IntentService} 显著简化了启动服务的实现。但是,若要求服务执行多线程(而不是通过工作队列处理启动请求),则可扩展 +{@link android.app.Service} +类来处理每个 Intent。</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()} 返回后终止服务,则除非有挂起 Intent 要传递,否则系统不会重建服务。<em></em>这是最安全的选项,可以避免在不必要时以及应用能够轻松重启所有未完成的作业时运行服务。 +</dd> + <dt>{@link android.app.Service#START_STICKY}</dt> + <dd>如果系统在 +{@link android.app.Service#onStartCommand +onStartCommand()} 返回后终止服务,则会重建服务并调用 +{@link +android.app.Service#onStartCommand onStartCommand()},但绝对不会重新传递最后一个 Intent。相反,除非有挂起 Intent 要启动服务(在这种情况下,将传递这些 Intent ),否则系统会通过空 Intent 调用 +{@link android.app.Service#onStartCommand onStartCommand()}。<em></em>这适用于不执行命令、但无限期运行并等待作业的媒体播放器(或类似服务)。 +</dd> + <dt>{@link android.app.Service#START_REDELIVER_INTENT}</dt> + <dd>如果系统在 {@link android.app.Service#onStartCommand +onStartCommand()} 返回后终止服务,则会重建服务,并通过传递给服务的最后一个 Intent 调用 +{@link +android.app.Service#onStartCommand onStartCommand()}。任何挂起 Intent 均依次传递。这适用于主动执行应该立即恢复的作业(例如下载文件)的服务。 +</dd> +</dl> +<p>有关这些返回值的更多详细信息,请查阅每个常量链接的参考文档。 +</p> + + + +<h3 id="StartingAService">启动服务</h3> + +<p>您可以通过将 +{@link android.content.Intent}(指定要启动的服务)传递给 {@link +android.content.Context#startService startService()},从 Activity 或其他应用组件启动服务。Android 系统调用服务的 {@link +android.app.Service#onStartCommand onStartCommand()} 方法,并向其传递 {@link +android.content.Intent}。(切勿直接调用 {@link android.app.Service#onStartCommand +onStartCommand()}。)</p> + +<p>例如,Activity 可以结合使用显式 Intent 与 {@link android.content.Context#startService +startService()},启动上文中的示例服务 ({@code +HelloSevice}):</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()} 传递的 Intent 是应用组件与服务之间唯一的通信模式。但是,如果您希望服务返回结果,则启动服务的客户端可以为广播创建一个 +{@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)} 时,传递与停止请求的 ID 对应的启动请求的 ID(传递给 {@link android.app.Service#onStartCommand onStartCommand()} +的 <code>startId</code>) +。然后,如果在您能够调用 {@link +android.app.Service#stopSelf(int)} 之前服务收到了新的启动请求, ID 就不匹配,服务也就不会停止。</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()} 与其绑定,以便创建长期连接(通常不允许组件通过调用 +{@link +android.content.Context#startService startService()} 来启动它)。<em></em></p> + +<p>如需与 Activity 和其他应用组件中的服务进行交互,或者需要通过进程间通信 (IPC) 向其他应用公开某些应用功能,则应创建绑定服务。 + +</p> + +<p>要创建绑定服务,必须实现 +{@link +android.app.Service#onBind onBind()} 回调方法以返回 {@link android.os.IBinder},用于定义与服务通信的接口。然后,其他应用组件可以调用 +{@link android.content.Context#bindService bindService()} +来检索该接口,并开始对服务调用方法。服务只用于与其绑定的应用组件,因此如果没有组件绑定到服务,则系统会销毁服务(您不必按通过 +{@link android.app.Service#onStartCommand onStartCommand()} +启动的服务那样来停止绑定服务)。 +<em></em></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">Toast 通知</a>或<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">状态栏通知</a>来通知用户所发生的事件。</p> + +<p>Toast 通知是指出现在当前窗口的表面、片刻随即消失不见的消息,而状态栏通知则在状态栏提供内含消息的图标,用户可以选择该图标来采取操作(例如启动 Activity)。 + +</p> + +<p>通常,当某些后台工作已经完成(例如文件下载完成)且用户现在可以对其进行操作时,状态栏通知是最佳方法。 + +当用户从展开视图中选定通知时,通知即可启动 Activity(例如查看已下载的文件)。 +</p> + +<p>如需了解详细信息,请参阅 +<a href="{@docRoot}guide/topics/ui/notifiers/toasts.html">Toast 通知</a>或<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">状态栏通知</a>开发者指南。</p> + + + +<h2 id="Foreground">在前台运行服务</h2> + +<p>前台服务被认为是用户主动意识到的一种服务,因此在内存不足时,系统也不会考虑将其终止。 +前台服务必须为状态栏提供通知,状态栏位于“正在进行”标题下方,这意味着除非服务停止或从前台删除,否则不能清除通知。 + + +</p> + +<p>例如,应该将从服务播放音乐的音乐播放器设置为在前台运行,这是因为用户明确意识到其操作。 + +状态栏中的通知可能表示正在播放的歌曲,并允许用户启动 Activity 来与音乐播放器进行交互。 +</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>提供给 {@link +android.app.Service#startForeground startForeground()} 的整型 ID 不得为 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>服务的生命周期比 Activity 的生命周期要简单得多。但是,密切关注如何创建和销毁服务反而更加重要,因为服务可以在用户没有意识到的情况下运行于后台。 + +</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.Intent}(标识要播放的音乐)调用 {@link android.content.Context#startService +startService()} 来启动后台音乐服务。随后,可能在用户需要稍加控制播放器或获取有关当前播放歌曲的信息时,Activity 可以通过调用 +{@link +android.content.Context#bindService bindService()} +绑定到服务。在这种情况下,除非所有客户端均取消绑定,否则 {@link +android.content.Context#stopService stopService()} 或 {@link android.app.Service#stopSelf +stopSelf()} 不会真正停止服务。 </p> + + +<h3 id="LifecycleCallbacks">实现生命周期回调</h3> + +<p>与 Activity 类似,服务也拥有生命周期回调方法,您可以实现这些方法来监控服务状态的变化并适时执行工作。 +以下框架服务展示了每种生命周期方法: +</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>与 Activity 生命周期回调方法不同,您不需要调用这些回调方法的超类实现。 +<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} 返回时结束。与 Activity 类似,服务也在 +{@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.content.Context#startService startService()} +还是 {@link +android.content.Context#bindService bindService()} 创建,都会为所有服务调用 {@link android.app.Service#onCreate onCreate()} 和 {@link android.app.Service#onDestroy +onDestroy()} 方法。</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>文档,该文档的<a href="{@docRoot}guide/components/bound-services.html#Lifecycle">管理绑定服务的生命周期</a>部分提供了有关 +{@link android.app.Service#onRebind onRebind()} +回调方法的更多信息。 +</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/zh-cn/guide/components/tasks-and-back-stack.jd b/docs/html-intl/intl/zh-cn/guide/components/tasks-and-back-stack.jd new file mode 100644 index 000000000000..07fdf6e80a02 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/components/tasks-and-back-stack.jd @@ -0,0 +1,578 @@ +page.title=任务和返回栈 +parent.title=Activity +parent.link=activities.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文内容</h2> +<ol> +<li><a href="#ActivityState">保存 Activity 状态</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">Activity</a>。每个 Activity 均应围绕用户可以执行的特定操作设计,并且能够启动其他 Activity。 + +例如,电子邮件应用可能有一个 Activity 显示新邮件的列表。用户选择某邮件时,会打开一个新 Activity 以查看该邮件。 +</p> + +<p>一个 Activity 甚至可以启动设备上其他应用中存在的 Activity。例如,如果应用想要发送电子邮件,则可将 Intent 定义为执行“发送”操作并加入一些数据,如电子邮件地址和电子邮件。 + +然后,系统将打开其他应用中声明自己处理此类 + Intent 的 Activity。在这种情况下, Intent +是要发送电子邮件,因此将启动电子邮件应用的“撰写”Activity(如果多个 Activity 支持相同 + Intent,则系统会让用户选择要使用的 Activity)。发送电子邮件时,Activity 将恢复,看起来好像电子邮件 Activity 是您的应用的一部分。 +即使这两个 Activity 可能来自不同的应用,但是 +Android 仍会将 Activity 保留在相同的任务中,以维护这种无缝的用户体验。<em></em> +</p> + +<p>任务是指在执行特定作业时与用户交互的一系列 Activity。 +这些 Activity 按照各自的打开顺序排列在堆栈(即“返回栈”)中。<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>设备主屏幕是大多数任务的起点。当用户触摸应用启动器中的图标(或主屏幕上的快捷键)时,该应用的任务将出现在前台。 + +如果应用不存在任务(应用最近未曾使用),则会创建一个新任务,并且该应用的“主”Activity 将作为堆栈中的根 Activity 打开。 + + </p> + +<p>当前 Activity 启动另一个 Activity 时,该新 Activity 会被推送到堆栈顶部,成为焦点所在。 +前一个 Activity 仍保留在堆栈中,但是处于停止状态。Activity 停止时,系统会保持其用户界面的当前状态。 +用户按“返回”按钮时,当前 Activity 会从堆栈顶部弹出(Activity 被销毁),而前一个 Activity 恢复执行(恢复其 UI 的前一状态)。<em></em> + + +堆栈中的 Activity 永远不会重新排列,仅推入和弹出堆栈:由当前 Activity 启动时推入堆栈;用户使用“返回”按钮退出时弹出堆栈。<em></em> + +因此,返回栈以“后进先出”对象结构运行。 + +图 1 通过时间线显示 Activity 之间的进度以及每个时间点的当前返回栈,直观呈现了这种行为。 + +</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>显示任务中的每个新 Activity 如何向返回栈添加项目。 +用户按“返回”按钮时,当前 Activity 随即被销毁,而前一个 Activity 恢复执行。<em></em> + +</p> + + +<p>如果用户继续按“返回”,堆栈中的相应 Activity 就会弹出,以显示前一个 Activity,直到用户返回主屏幕为止(或者,返回任务开始时正在运行的任意 Activity)。<em></em> + + +当所有 Activity 均从堆栈中删除后,任务即不复存在。</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>一个 Activity 将多次实例化。</p> +</div> + +<p>任务是一个有机整体,当用户开始新任务或通过“主页”按钮转到主屏幕时,可以移动到“后台”。<em></em> +尽管在后台时,该任务中的所有 Activity 全部停止,但是任务的返回栈仍旧不变,也就是说,当另一个任务发生时,该任务仅仅失去焦点而已,如图 2 中所示。 + + +然后,任务可以返回到“前台”,用户就能够回到离开时的状态。 +例如,假设当前任务(任务 A)的堆栈中有三个 Activity,即当前 Activity 下方还有两个 Activity。 +用户先按“主页”按钮,然后从应用启动器启动新应用。<em></em> + +显示主屏幕时,任务 A +进入后台。新应用启动时,系统会使用自己的 Activity 堆栈为该应用启动一个任务(任务 +B)。与该应用交互之后,用户再次返回主屏幕并选择最初启动任务 A 的应用。现在,任务 A 出现在前台,其堆栈中的所有三个 Activity 保持不变,而位于堆栈顶部的 Activity 则会恢复执行。 + + + +此时,用户还可以通过转到主屏幕并选择启动该任务的应用(或者,通过从<a href="{@docRoot}guide/components/recents.html">概览屏幕</a>选择该应用的任务)切换回任务 B。这是 Android 系统中的一个多任务示例。 + + + +</p> + +<p class="note"><strong>注:</strong>后台可以同时运行多个任务。但是,如果用户同时运行多个后台任务,则系统可能会开始销毁后台 Activity,以回收内存资源,从而导致 Activity 状态丢失。请参阅下面有关<a href="#ActivityState"> Activity 状态</a>的部分。 + + +</p> + +<p>由于返回栈中的 Activity 永远不会重新排列,因此如果应用允许用户从多个 Activity 中启动特定 Activity,则会创建该 Activity 的新实例并推入堆栈中(而不是将 Activity 的任一先前实例置于顶部)。 + + +因此,应用中的一个 Activity 可能会多次实例化(即使 Activity 来自不同的任务),如图 3 所示。 +因此,如果用户使用“返回”按钮向后导航,则会按 Activity 每个实例的打开顺序显示这些实例(每个实例的 UI 状态各不相同)。<em></em> + + +但是,如果您不希望 Activity 多次实例化,则可修改此行为。 +具体操作方法将在后面的<a href="#ManagingTasks">管理任务</a>部分中讨论。</p> + + +<p>Activity 和任务的默认行为总结如下:</p> + +<ul> + <li>当 Activity A 启动 Activity B 时,Activity A 将会停止,但系统会保留其状态(例如,滚动位置和已输入表单中的文本)。如果用户在处于 Activity B 时按“返回”按钮,则 Activity A 将恢复其状态,继续执行。<em></em> + + +</li> + <li>用户通过按“主页”按钮离开任务时,当前 Activity 将停止且其任务会进入后台。<em></em> + +系统将保留任务中每个 Activity 的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将出现在前台并恢复执行堆栈顶部的 Activity。 + +</li> + <li>如果用户按“返回”按钮,则当前 Activity 会从堆栈弹出并被销毁。<em></em> + +堆栈中的前一个 Activity 恢复执行。销毁 Activity 时,系统绝对不会保留该 Activity 的状态。<em></em> +</li> + <li>即使来自其他任务,Activity 也可以多次实例化。</li> +</ul> + + +<div class="note design"> +<p><strong>导航设计</strong></p> + <p>如需了解有关 Android 应用导航工作方式的详细信息,请阅读 Android 设计的<a href="{@docRoot}design/patterns/navigation.html">导航</a>指南。</p> +</div> + + +<h2 id="ActivityState">保存 Activity 状态</h2> + +<p>正如上文所述,当 Activity 停止时,系统的默认行为会保留其状态。 +这样一来,当用户导航回到上一个 Activity 时,其用户界面与用户离开时一样。 +但是,在 Activity 被销毁且必须重建时,您可以而且<strong>应当</strong>主动使用回调方法保留 Activity 的状态。 + +</p> + +<p>系统停止您的一个 Activity 时(例如,新 Activity 启动或任务转到前台),如果系统需要回收系统内存资源,则可能会完全销毁该 Activity。 + +发生这种情况时,有关该 Activity 状态的信息将会丢失。如果发生这种情况,系统仍会知道该 Activity 存在于返回栈中,但是当该 Activity 被置于堆栈顶部时,系统一定会重建 Activity(而不是恢复 Activity)。 + + +为了避免用户的工作丢失,您应主动通过在 Activity 中实现 +{@link android.app.Activity#onSaveInstanceState onSaveInstanceState()} +回调方法来保留工作。 +</p> + +<p>如需了解有关如何保存 Activity 状态的详细信息,请参阅<a href="{@docRoot}guide/components/activities.html#SavingActivityState">Activity</a>文档。 +</p> + + + +<h2 id="ManagingTasks">管理任务</h2> + +<p>Android 管理任务和返回栈的方式(如上所述,即:将所有连续启动的 Activity 放入同一任务和“后进先出”堆栈中)非常适用于大多数应用,而您不必担心 Activity 如何与任务关联或者如何存在于返回栈中。 + + +但是,您可能会决定要中断正常行为。 +也许您希望应用中的 Activity 在启动时开始新任务(而不是放置在当前任务中);或者,当启动 Activity 时,您希望将其现有实例上移一层(而不是在返回栈的顶部创建新实例);或者,您希望在用户离开任务时,清除返回栈中除根 Activity 以外的所有其他 Activity。 + + + +</p> + +<p>通过使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 清单文件元素中的属性和传递给 {@link android.app.Activity#startActivity startActivity()} +的 Intent 中的标志,您可以执行所有这些操作以及其他操作。 + +</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>您可以使用的主要 Intent 标志包括:</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>在下文中,您将了解如何使用这些清单文件属性和 Intent +标志定义 Activity 与任务的关联方式,以及 Activity 在返回栈中的行为方式。</p> + +<p>此外,我们还单独介绍了有关如何在概览屏幕中显示和管理任务与 Activity 的注意事项。 +如需了解详细信息,请参阅<a href="{@docRoot}guide/components/recents.html">概览屏幕</a>。 +通常,您应该允许系统定义任务和 Activity 在概览屏幕中的显示方法,并且无需修改此行为。 +</p> + +<p class="caution"><strong>注意:</strong>大多数应用都不得中断 Activity 和任务的默认行为: +如果确定您的 Activity 必须修改默认行为,当使用“返回”按钮从其他 Activity 和任务导航回到该 Activity 时,请务必要谨慎并确保在启动期间测试该 Activity 的可用性。请确保测试导航行为是否有可能与用户的预期行为冲突。<em></em> + + +</p> + + +<h3 id="TaskLaunchModes">定义启动模式</h3> + +<p>启动模式允许您定义 Activity 的新实例如何与当前任务关联。 +您可以通过两种方法定义不同的启动模式:</p> +<ul class="nolist"> + <li><a href="#ManifestForTasks">使用清单文件</a> + <p>在清单文件中声明 Activity 时,您可以指定 Activity 在启动时应该如何与任务关联。 +</li> + <li><a href="#IntentFlagsForTasks">使用 Intent 标志</a> + <p>调用 {@link android.app.Activity#startActivity startActivity()} +时,可以在 {@link android.content.Intent} +中加入一个标志,用于声明新 Activity 如何(或是否)与当前任务关联。</p></li> +</ul> + +<p>因此,如果 Activity +A 启动 Activity B,则 Activity B 可以在其清单文件中定义它应该如何与当前任务关联(如果可能),并且 Activity A 还可以请求 Activity B +应该如何与当前任务关联。如果这两个 Activity 均定义 Activity B +应该如何与任务关联,则 Activity A 的请求(如 Intent 中所定义)优先级要高于 Activity +B 的请求(如其清单文件中所定义)。</p> + +<p class="note"><strong>注:</strong>某些适用于清单文件的启动 +模式不可用作 Intent 标志,同样,某些可用作 Intent +标志的启动模式无法在清单文件中定义。</p> + + +<h4 id="ManifestForTasks">使用清单文件</h4> + +<p>在清单文件中声明 Activity 时,您可以使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +元素的 <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> +属性指定 Activity 应该如何与任务关联。</p> + +<p><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code +launchMode}</a> +属性指定有关应如何将 Activity 启动到任务中的指令。您可以分配给 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">launchMode</a></code> +属性的启动模式共有四种:</p> + +<dl> +<dt>{@code "standard"}(默认模式)</dt> + <dd>默认。系统在启动 Activity 的任务中创建 Activity 的新实例并向其传送 + Intent。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例。 +</dd> +<dt>{@code "singleTop"}</dt> + <dd>如果当前任务的顶部已存在 Activity 的一个实例,则系统会通过调用该实例的 +{@link +android.app.Activity#onNewIntent onNewIntent()} +方法向其传送 Intent,而不是创建 Activity 的新实例。Activity 可以多次实例化,而每个实例均可属于不同的任务,并且一个任务可以拥有多个实例(但前提是位于返回栈顶部的 Activity 并不是 Activity 的现有实例)。<em></em> + + + <p>例如,假设任务的返回栈包含根 Activity A 以及 Activity B、C +和位于顶部的 D(堆栈是 A-B-C-D;D 位于顶部)。收到针对 D 类 Activity 的 Intent。如果 D +具有默认的 {@code "standard"} 启动模式,则会启动该类的新实例,且堆栈会变成 A-B-C-D-D。但是,如果 D 的启动模式是 {@code "singleTop"},则 D +的现有实例会通过 {@link +android.app.Activity#onNewIntent onNewIntent()} 接收 Intent,因为它位于堆栈的顶部;而堆栈仍为 +A-B-C-D。但是,如果收到针对 A 类 Activity 的 Intent,则会向堆栈添加 B 的新实例,即便其启动模式为 {@code "singleTop"} 也是如此。 + +</p> + <p class="note"><strong>注:</strong>为某个 Activity 创建新实例时,用户可以按“返回”按钮返回到前一个 Activity。<em></em> +但是,当 Activity 的现有实例处理新 + Intent 时,则在新 Intent 到达 +{@link android.app.Activity#onNewIntent +onNewIntent()} +之前,用户无法按“返回”<em></em>按钮返回到 Activity 的状态。 +</p> +</dd> + +<dt>{@code "singleTask"}</dt> + <dd>系统创建新任务并实例化位于新任务底部的 Activity。但是,如果该 Activity 的一个实例已存在于一个单独的任务中,则系统会通过调用现有实例的 {@link +android.app.Activity#onNewIntent +onNewIntent()} 方法向其传送 + Intent,而不是创建新实例。一次只能存在 Activity 的一个实例。 + + <p class="note"><strong>注:</strong>尽管 Activity 在新任务中启动,但是用户按“返回”<em></em>按钮仍会返回到前一个 Activity。 +</p></dd> +<dt>{@code "singleInstance"}。</dt> + <dd>与 +{@code "singleTask"} 相同,只是系统不会将任何其他 Activity 启动到包含实例的任务中。该 Activity 始终是其任务唯一仅有的成员;由此 Activity 启动的任何 Activity 均在单独的任务中打开。 +</dd> +</dl> + + +<p>我们再来看另一示例,Android 浏览器 +应用声明 Web 浏览器 Activity 应始终在其自己的任务中打开(通过在 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> 元素中指定 {@code singleTask} +启动模式)。这意味着,如果您的应用发出打开 +Android 浏览器的 + Intent,则其 Activity 与您的应用位于不同的任务中。<em></em>相反,系统会为浏览器启动新任务,或者如果浏览器 +已有任务正在后台运行,则会将该任务上移一层以处理新 + Intent。</p> + +<p>无论 Activity 是在新任务中启动,还是在与启动 Activity 相同的任务中启动,用户按“返回”<em></em>按钮始终会转到前一个 Activity。 +但是,如果启动指定 +{@code singleTask} +启动模式的 Activity,则当某后台任务中存在该 Activity 的实例时,整个任务都会转移到前台。此时,返回栈包括上移到堆栈顶部的任务中的所有 Activity。 + +图 4 显示了这种情况。</p> + +<img src="{@docRoot}images/fundamentals/diagram_backstack_singletask_multiactivity.png" alt="" /> +<p class="img-caption"><strong>图 4. </strong>显示如何将启动模式为“singleTask”的 Activity 添加到返回栈。 +如果 Activity 已经是某个拥有自己的返回栈的后台任务的一部分,则整个返回栈也会上移到当前任务的顶部。 + +</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> +属性为 Activity 指定的行为可由 Intent +附带的 Activity 启动标志替代,下文将对此进行讨论。</p> + + + +<h4 id="#IntentFlagsForTasks">使用 Intent 标志</h4> + +<p>启动 Activity 时,您可以通过在传递给 {@link +android.app.Activity#startActivity startActivity()} 的 Intent +中加入相应的标志,修改 Activity 与其任务的默认关联方式。可用于修改默认行为的标志包括: +</p> + +<p> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK}</dt> + <dd>在新任务中启动 Activity。如果已为正在启动的 Activity 运行任务,则该任务会转到前台并恢复其最后状态,同时 Activity 会在 +{@link android.app.Activity#onNewIntent onNewIntent()} 中收到新 + Intent。 + <p>正如前文所述,这会产生与 {@code "singleTask"} <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> +值相同的行为。</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_SINGLE_TOP}</dt> + <dd>如果正在启动的 Activity 是当前 Activity(位于返回栈的顶部),则 +现有实例会接收对 {@link android.app.Activity#onNewIntent onNewIntent()} +的调用,而不是创建 Activity 的新实例。 + <p>正如前文所述,这会产生与 {@code "singleTop"} <a href="{@docRoot}guide/topics/manifest/activity-element.html#lmode">{@code launchMode}</a> +值相同的行为。</p></dd> + <dt>{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TOP}</dt> + <dd>如果正在启动的 Activity 已在当前任务中运行,则会销毁当前任务顶部的所有 Activity,并通过 {@link android.app.Activity#onNewIntent onNewIntent()} +将此 Intent 传递给 Activity 已恢复的实例(现在位于顶部),而不是启动该 Activity 的新实例。 + + + <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} +结合使用。一起使用时,通过这些标志,可以找到其他任务中的现有 Activity,并将其放入可从中响应 Intent +的位置。 </p> + <p class="note"><strong>注:</strong>如果指定 Activity 的启动模式为 +{@code "standard"},则该 Activity 也会从堆栈中删除,并在其位置启动一个新实例,以便处理传入的 Intent。 + +这是因为当启动模式为 {@code "standard"} +时,将始终为新 Intent 创建新实例。 </p> +</dd> +</dl> + + + + + +<h3 id="Affinities">处理关联</h3> + +<p>“关联”指示 Activity 优先属于哪个任务。<em></em>默认情况下,同一应用中的所有 Activity 彼此关联。 +因此,默认情况下,同一应用中的所有 Activity 优先位于相同任务中。 +不过,您可以修改 Activity 的默认关联。 +在不同应用中定义的 Activity 可以共享关联,或者可为在同一应用中定义的 Activity 分配不同的任务关联。 + +</p> + +<p>可以使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +元素的 +<a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> 属性修改任何给定 Activity 的关联。</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>启动 Activity 的 Intent 包含 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +标志。 + +<p>默认情况下,新 Activity 会启动到调用 +{@link android.app.Activity#startActivity startActivity()} 的 Activity 任务中。它将推入与调用方相同的返回栈。 +但是,如果传递给 +{@link android.app.Activity#startActivity startActivity()} +的 Intent 包含 {@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK} +标志,则系统会寻找其他任务来储存新 Activity。这通常是新任务,但未做强制要求。 +如果现有任务与新 Activity 具有相同关联,则会将 Activity 启动到该任务中。 +否则,将开始新任务。</p> + +<p>如果此标志导致 Activity 开始新任务,且用户按“主页”按钮离开,则必须为用户提供导航回任务的方式。<em></em> + +有些实体(如通知管理器)始终在外部任务中启动 Activity,而从不作为其自身的一部分启动 Activity,因此它们始终将 +{@code FLAG_ACTIVITY_NEW_TASK} 放入传递给 +{@link android.app.Activity#startActivity startActivity()} +的 Intent 中。请注意,如果 Activity 能够由可以使用此标志的外部实体调用,则用户可以通过独立方式返回到启动的任务,例如,使用启动器图标(任务的根 Activity 具有 +{@link android.content.Intent#CATEGORY_LAUNCHER} + Intent 过滤器;请参阅下面的<a href="#Starting">启动任务</a>部分)。 + +</p> +</li> + + <li>Activity 将其<a href="{@docRoot}guide/topics/manifest/activity-element.html#reparent"> +{@code allowTaskReparenting}</a> 属性设置为 {@code "true"}。 + <p>在这种情况下,Activity 可以从其启动的任务移动到与其具有关联的任务(如果该任务出现在前台)。 +</p> + <p>例如,假设将报告所选城市天气状况的 Activity 定义为旅行应用的一部分。 +它与同一应用中的其他 Activity 具有相同的关联(默认应用关联),并允许利用此属性重定父级。当您的一个 Activity 启动天气预报 Activity 时,它最初所属的任务与您的 Activity 相同。 + + +但是,当旅行应用的任务出现在前台时,系统会将天气预报 Activity 重新分配给该任务并显示在其中。 +</p> +</li> +</ul> + +<p class="note"><strong>提示</strong>:如果从用户的角度来看,一个 {@code .apk} +文件包含多个“应用”,则您可能需要使用 <a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">{@code taskAffinity}</a> +属性将不同关联分配给与每个“应用”相关的 Activity。</p> + + + +<h3 id="Clearing">清理返回栈</h3> + +<p>如果用户长时间离开任务,则系统会清除所有 Activity 的任务,根任务除外。 +当用户再次返回到任务时,仅恢复根 Activity。系统这样做的原因是,经过很长一段时间后,用户可能已经放弃之前执行的操作,返回到任务是要开始执行新的操作。 + + </p> + +<p>您可以使用下列几个 Activity 属性修改此行为: </p> + +<dl> +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#always">alwaysRetainTaskState</a></code> +</dt> +<dd>如果在任务的根 Activity 中将此属性设置为 {@code "true"},则不会发生刚才所述的默认行为。即使在很长一段时间后,任务仍将所有 Activity 保留在其堆栈中。 + +</dd> + +<dt><code><a +href="{@docRoot}guide/topics/manifest/activity-element.html#clear">clearTaskOnLaunch</a></code></dt> +<dd>如果在任务的根 Activity 中将此属性设置为 {@code "true"},则每当用户离开任务然后返回时,系统都会将堆栈清除到只剩下根 Activity。 + +换而言之,它与<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>,但它对单个 Activity 起作用,而非整个任务。 +此外,它还有可能会导致任何 Activity 停止,包括根 Activity。 +设置为 {@code "true"} +时,Activity 仍是任务的一部分,但是仅限于当前会话。如果用户离开然后返回任务,则任务将不复存在。 +</dd> +</dl> + + + + +<h3 id="Starting">启动任务</h3> + +<p>通过为 Activity 提供一个以 {@code "android.intent.action.MAIN"} +为指定操作、以{@code "android.intent.category.LAUNCHER"} +为指定类别的 Intent 过滤器,您可以将活动设置为任务的入口点。 +例如:</p> + +<pre> +<activity ... > + <intent-filter ... > + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> + ... +</activity> +</pre> + +<p>此类 Intent 过滤器会使 Activity 的图标和标签显示在应用启动器中,让用户能够启动 Activity 并在启动之后随时返回到创建的任务中。 + + +</p> + +<p>第二个功能非常重要:用户必须能够在离开任务后,再使用此 Activity 启动器返回该任务。 +因此,只有在 Activity 具有 +{@link android.content.Intent#ACTION_MAIN} +和 {@link android.content.Intent#CATEGORY_LAUNCHER} +过滤器时,才应该使用将 Activity 标记为“始终启动任务”的两种<a href="#LaunchModes">启动模式</a>,即 {@code "singleTask"} 和 +{@code "singleInstance"}。例如,我们可以想像一下如果缺少过滤器会发生什么情况: + Intent 启动一个 {@code "singleTask"} +Activity,从而启动一个新任务,并且用户花了些时间处理该任务。然后,用户按<em>主页</em>按钮。 +任务现已发送到后台,而且不可见。现在,用户无法返回到任务,因为该任务未显示在应用启动器中。 +</p> + +<p>如果您并不想用户能够返回到 Activity,对于这些情况,请将 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +元素的 +<a href="{@docRoot}guide/topics/manifest/activity-element.html#finish">{@code finishOnTaskLaunch}</a> +设置为 {@code "true"}(请参阅<a href="#Clearing">清理堆栈</a>)。</p> + +<p>有关如何在概览屏幕中显示和管理任务与 Activity 的更多信息,请参阅<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> +--> |