diff options
Diffstat (limited to 'docs/html-intl/intl/zh-cn/guide')
31 files changed, 20550 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> +--> diff --git a/docs/html-intl/intl/zh-cn/guide/index.jd b/docs/html-intl/intl/zh-cn/guide/index.jd new file mode 100644 index 000000000000..01ab3478a0f6 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/index.jd @@ -0,0 +1,74 @@ +page.title=Android 简介 + +@jd:body + + +<div class="sidebox" style="width:220px"><!-- width to match col-4 below --> +<p>要了解应用的工作原理,请从<a href="{@docRoot}guide/components/fundamentals.html">应用基础知识</a>开始入手。 +</p> +<p>要立即开始编码,请仔细阅读<a href="{@docRoot}training/basics/firstapp/index.html">开发您的第一款应用</a>。</p> +</div> + +<p>Android +提供了一个内容丰富的应用框架,支持您在 Java 语言环境中为移动设备开发创新应用和游戏。在左侧导航窗格列出的文档中,提供了有关如何使用各种 +Android API 开发应用的详细信息。</p> + +<p>如果您是 Android 应用开发新手,则需了解以下有关 +Android 应用框架的基本概念,这一点至关重要:</p> + + +<div class="landing-banner"> + +<div class="col-6"> + +<h4>应用提供多个入口点</h4> + +<p>Android +应用都是将各种可单独调用的不同组件加以组合构建而成。例如,组件可以是为用户界面提供一个屏幕的单个“Activity”,也可以是在后台独立执行工作的“服务”。 + +<em></em><em></em></p> + +<p>您可以使用<em> Intent </em>从一个组件启动另一个组件。甚至,您还可以启动不同应用中的组件,例如,启动地图应用中的 Activity 以显示地址。 +此模式可为单个应用提供多个入口点,并使任何应用均能够像用户“默认设置”一样处理其他应用可能调用的操作。 + +</p> + + +<p><b>了解详情</b>:</p> +<ul class="nolist"> +<li><a href="{@docRoot}guide/components/fundamentals.html">应用基础知识</a> +<li><a href="{@docRoot}guide/components/intents-filters.html">Intent 和 Intent 过滤器</a> +<li><a href="{@docRoot}guide/components/activities.html">Activity</a> +</ul> + +</div> + + +<div class="col-6"> + +<h4>应用可适应不同的设备</h4> + +<p>Android +提供了一个自适应应用框架,您可以利用它为不同的设备配置提供独特的资源。例如,您可以针对不同的屏幕尺寸创建不同的 +XML +布局文件,系统将根据当前设备的屏幕尺寸确定要应用的布局。</p> + +<p>如有任何应用功能需要相机等特定的硬件,则可在运行时查询设备功能的可用性。 +如有必要,您还可以声明您的应用所必需的功能,使 +Google Play +商店等应用市场不得在不支持这些功能的设备上安装您的应用。</p> + + +<p><b>了解详情</b>:</p> +<ul class="nolist"> +<li><a href="{@docRoot}guide/practices/compatibility.html">设备兼容性</a> +<li><a href="{@docRoot}guide/topics/resources/overview.html">资源概览</a> +<li><a href="{@docRoot}guide/topics/ui/overview.html">用户界面概览</a> +</ul> + +</div> + +</div><!-- end landing-banner --> + + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd b/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd new file mode 100644 index 000000000000..c7ade4f392db --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/manifest/manifest-intro.jd @@ -0,0 +1,517 @@ +page.title=应用清单文件 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文内容</h2> +<ol> +<li><a href="#filestruct">清单文件结构</a></li> +<li><a href="#filec">文件约定</a> +<li><a href="#filef">文件功能</a> + <ol> + <li><a href="#ifs"> Intent 过滤器</a></li> + <li><a href="#iconlabel">图标和标签</a></li> + <li><a href="#perms">权限</a></li> + <li><a href="#libs">库</a></li> + </ol></li> +</ol> +</div> +</div> + +<p> + 每个应用的根目录中都必须包含一个 +AndroidManifest.xml 文件(且文件名精确无误)。 <span itemprop="description">清单文件为 +Android 系统提供有关您的应用的基本信息,系统必须获得这些信息才能运行任意应用代码。 + +</span> 此外,清单文件还可执行以下操作: +</p> + +<ul> +<li>为应用的 Java +软件包命名。软件包名称充当应用的唯一标识符</li> + +<li>描述应用的各个组件,即:构成应用的 Activity、服务、广播接收器和内容提供程序。 + +为实现每个组件的类命名并发布其功能(例如,它们可以处理的 +{@link android.content.Intent +Intent} 消息)。根据这些声明,Android +系统可以了解这组件具体是什么,以及在什么条件下可以启动它们</li> + +<li>确定将托管应用组件的进程</li> + +<li>声明应用必须具备哪些权限才能访问 +API 中受保护的部分并与其他应用交互</li> + +<li>还声明其他应用与该应用组件交互所需具备的权限 +</li> + +<li>列出 {@link android.app.Instrumentation} +类,这些类可在应用运行期间提供分析和其他信息。这些声明只会在应用处在开发和测试阶段时出现在清单文件中;它们会在应用发布之前被删除 + +</li> + +<li>声明应用所需的最低 Android API +级别</li> + +<li>列出应用必须链接到的库</li> +</ul> + + +<h2 id="filestruct">清单文件结构</h2> + +<p> +下图显示了清单文件的通用结构及其可包含的每个元素。 +每个元素及其所有属性全部记录在一个单独的文件中。 +要查看有关任何元素的详细信息,请点击该图中或其后按字母顺序排列的元素列表中相应的元素名称,或者点击任何其他地方提到的相应元素名称。 + + + +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> + +<a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a> + + <a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission /></a> + <a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission /></a> + <a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree /></a> + <a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group /></a> + <a href="{@docRoot}guide/topics/manifest/instrumentation-element.html"><instrumentation /></a> + <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><uses-sdk /></a> + <a href="{@docRoot}guide/topics/manifest/uses-configuration-element.html"><uses-configuration /></a> <!-- ##api level 3## --> + <a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><uses-feature /></a> <!-- ##api level 4## --> + <a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><supports-screens /></a> <!-- ##api level 4## --> + <a href="{@docRoot}guide/topics/manifest/compatible-screens-element.html"><compatible-screens /></a> <!-- ##api level 9## --> + <a href="{@docRoot}guide/topics/manifest/supports-gl-texture-element.html"><supports-gl-texture /></a> <!-- ##api level 11## --> + + <a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a> + + <a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/action-element.html"><action /></a> + <a href="{@docRoot}guide/topics/manifest/category-element.html"><category /></a> + <a href="{@docRoot}guide/topics/manifest/data-element.html"><data /></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/activity-element.html"></activity></a> + + <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"></activity-alias></a> + + <a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data/></a> + <a href="{@docRoot}guide/topics/manifest/service-element.html"></service></a> + + <a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a> + <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a> . . . <a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"></intent-filter></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/receiver-element.html"></receiver></a> + + <a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a> + <a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission /></a> + <a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data /></a> + <a href="{@docRoot}guide/topics/manifest/path-permission-element.html"><path-permission /></a> + <a href="{@docRoot}guide/topics/manifest/provider-element.html"></provider></a> + + <a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library /></a> + + <a href="{@docRoot}guide/topics/manifest/application-element.html"></application></a> + +<a href="{@docRoot}guide/topics/manifest/manifest-element.html"></manifest></a> +</pre> + +<p> +可出现在清单文件中的所有元素按字母顺序罗列如下。 +这些是仅有的合法元素;您无法添加自己的元素或属性。 + +</p> + +<p style="margin-left: 2em"> +<code><a href="{@docRoot}guide/topics/manifest/action-element.html"><action></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/category-element.html"><category></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/data-element.html"><data></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/instrumentation-element.html"><instrumentation></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/supports-screens-element.html"><supports-screens></a></code> <!-- ##api level 4## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-configuration-element.html"><uses-configuration></a></code> <!-- ##api level 3## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-feature-element.html"><uses-feature></a></code> <!-- ##api level 4## --> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +<br/><code><a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html"><uses-sdk></a></code> +</p> + + + + +<h2 id="filec">文件约定</h2> + +<p> +有些约定和规则普遍适用于清单文件中的所有元素和属性: + +</p> + +<dl> +<dt><b>元素</b></dt> +<dd>只有 +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> 和 +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +元素是必需的,它们都必须存在并且只能出现一次。其他大部分元素可以出现多次或者根本不出现,即便清单文件中必须至少存在其中某些元素才能完成任何有意义的操作也是如此。 + + + + +<p> +如果一个元素包含某些内容,也就包含其他元素。所有值均通过属性进行设置,而不是通过元素内的字符数据设置。 + +</p> + +<p> +同一级别的元素通常不分先后顺序。例如,<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>、<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +和 +<code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code> +元素可以按任何顺序混合在一起。 +(<code><a href="{@docRoot}guide/topics/manifest/activity-alias-element.html"><activity-alias></a></code> +元素不适用此规则: +它必须跟在别名所指的 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +之后。) +</p></dd> + +<dt><b>属性</b></dt> +<dd>从某种意义上说,所有属性都是可选的。但是,有些属性必须指定给元素以实现其目的。 +请使用本文档作为参考。 +对于真正可选的属性,它将指定默认值或声明缺乏规范时将执行何种操作。 + + +<p>除了根 +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> +元素的一些属性外,所有属性名称均以 {@code android:} +前缀开头,例如,{@code android:alwaysRetainTaskState}。由于该前缀是通用的,因此在按名称引用属性时,本文档通常会将其忽略。 + +</p></dd> + +<dt><b>声明类名</b></dt> +<dd>许多元素对应于 +Java +对象,其中包括应用本身的元素(<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +元素)及其主要组件 +— +Activity +(<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>)、服务 +(<code><a href="{@docRoot}guide/topics/manifest/service-element.html"><service></a></code>)、广播接收器 +(<code><a href="{@docRoot}guide/topics/manifest/receiver-element.html"><receiver></a></code>) +以及内容提供程序 +(<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code>)。 + +<p> +如果按照您针对组件类({@link android.app.Activity}、{@link android.app.Service}、{@link android.content.BroadcastReceiver} +和 +{@link android.content.ContentProvider})几乎一直采用的方式来定义子类,则该子类需通过 {@code name} 属性来声明。 +该名称必须包含完整的软件包名称。 +例如,{@link android.app.Service} +子类可能会声明如下: +</p> + +<pre><manifest . . . > + <application . . . > + <service android:name="com.example.project.SecretService" . . . > + . . . + </service> + . . . + </application> +</manifest></pre> + +<p> +但是,为了简便起见,如果字符串的第一个字符是句点,则字符串将追加到应用的软件包名称(正如 +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html"><manifest></a></code> +元素的 +<code><a href="{@docRoot}guide/topics/manifest/manifest-element.html#package">package</a></code> +属性中所指定)。 +以下赋值与上述方法相同: +</p> + +<pre><manifest package="com.example.project" . . . > + <application . . . > + <service android:name=".SecretService" . . . > + . . . + </service> + . . . + </application> +</manifest></pre> + +<p> +当启动组件时,Android 会创建已命名子类的实例。如果未指定子类,则会创建基类的实例。 + +</p></dd> + +<dt><b>多个值</b></dt> +<dd>如果可以指定多个值,则几乎总是在重复此元素,而不是列出单个元素内的多个值。 +例如,Intent 过滤器可以列出多个操作: + + +<pre><intent-filter . . . > + <action android:name="android.intent.action.EDIT" /> + <action android:name="android.intent.action.INSERT" /> + <action android:name="android.intent.action.DELETE" /> + . . . +</intent-filter></pre></dd> + +<dt><b>资源值</b></dt> +<dd>某些属性的值可以显示给用户,例如,Activity 的标签和图标。 +这些属性的值应该本地化,因此需要通过资源或主题进行设置。 +资源值用以下格式表示: +</p> + +<p style="margin-left: 2em">{@code @[<i>package</i>:]<i>type</i>:<i>name</i>}</p> + +<p> +其中,如果资源与应用位于同一软件包中,则可忽略 <i>package</i> 名称; + <i>type</i> 是资源类型,如“字串符”或“图片”; + <i>name</i> 是标识特定资源的名称。例如: + +</p> + +<pre><activity android:icon="@drawable/smallPic" . . . ></pre> + +<p> +主题中的值用类似的方法表示,但是以“{@code ?}”开头而不是“{@code @}”: + +</p> + +<p style="margin-left: 2em">{@code ?[<i>package</i>:]<i>type</i>:<i>name</i>} +</p></dd> + +<dt><b>字串符值</b></dt> +<dd>如果属性值为字串符,则必须使用双反斜杠(“{@code \\}”) +转义字符。例如,使用“{@code \\n}”表示换行符或使用“{@code \\uxxxx}”表示 +Unicode 字符)。</dd> +</dl> + + +<h2 id="filef">文件功能</h2> + +<p> +下文介绍如何在清单文件中利用某些 +Android 特性。 +</p> + + +<h3 id="ifs"> Intent 过滤器</h3> + +<p> +应用的核心组件(其 Activity、服务和广播接收器)由 + <i>Intent </i>激活。Intent 是一系列用于描述所需操作的信息({@link android.content.Intent} +对象),其中包括要执行操作的数据、应执行操作的组件类别以及其他相关说明。 + +Android +会找到合适的组件来响应 + Intent,根据需要启动组件的新实例,并将其传递到 + Intent 对象。 +</p> + +<p> +组件将通过“Intent 过滤器”公布其功能,即它们可响应的 Intent 类型。 + <i></i> 由于 +Android 系统在启动某组件之前必须了解该组件可以处理哪些 Intent,因此 Intent 过滤器在清单文件中被指定为 +<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> +元素。 +一个组件可能有任意数量的过滤器,其中每个过滤器描述一种不同的功能。 + +</p> + +<p> +显式命名目标组件的 + Intent 将激活该组件;过滤器不起作用。但是,不按名称指定目标的 + Intent +只有在能够通过组件的一个过滤器时才可激活该组件。 +</p> + +<p> +有关如何根据 + Intent 过滤器测试 + Intent 对象的信息,请参阅单独的文档 +<a href="{@docRoot}guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>。 +</p> + + +<h3 id="iconlabel">图标和标签</h3> + +<p> +对于可以显示给用户的小图标和文本标签,大量元素具有 {@code icon} 和 {@code label} +属性。此外,对于同样可以显示在屏幕上的较长说明文本,某些元素还具有 +{@code description} +属性。例如,<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素具有所有这三个属性。因此,当系统询问用户是否授权给请求获得权限的应用时,权限图标、权限名称以及所需信息的说明均可呈现给用户。 + + + + +</p> + +<p> +无论何种情况下,在包含元素中设置的图标和标签都将成为所有容器子元素的默认 +{@code icon} 和 {@code label} 设置。因此,在 +<code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> +元素中设置的图标和标签是每个应用组件的默认图标和标签。 +同样,为组件(例如,<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +元素)设置的图标和标签是组件每个 +<code><a href="{@docRoot}guide/topics/manifest/intent-filter-element.html"><intent-filter></a></code> +元素的默认设置。 + +如果 <code><a href="{@docRoot}guide/topics/manifest/application-element.html"><application></a></code> 元素设置标签,但是 Activity 及其 + Intent 过滤器不执行此操作,则应用标签将被视为 Activity 和 + Intent 过滤器的标签。 + + +</p> + +<p> +在实现 Intent 过滤器公布的功能时,只要向用户呈现组件,系统便会使用为过滤器设置的图标和标签表示该组件。 + +例如,具有“{@code android.intent.action.MAIN}”和“{@code android.intent.category.LAUNCHER}”设置的过滤器将 Activity 公布为可启动应用的功能,即,公布为应显示在应用启动器中的功能。 + + + +因此,在过滤器中设置的图标和标签就是显示在启动器中的图标和标签。 + +</p> + + +<h3 id="perms">权限</h3> + +<p> + <i>权限</i> 是一种限制,用于限制对部分代码或设备上数据的访问。 + 施加限制是为了保护可能被误用以致破坏或损害用户体验的关键数据和代码。 + +</p> + +<p> +每种权限均由一个唯一的标签标识。标签通常指示受限制的操作。 +例如,以下是由 +Android 定义的一些权限: +</p> + +<p style="margin-left: 2em">{@code android.permission.CALL_EMERGENCY_NUMBERS} +<br/>{@code android.permission.READ_OWNER_DATA} +<br/>{@code android.permission.SET_WALLPAPER} +<br/>{@code android.permission.DEVICE_POWER}</p> + +<p> +一个功能最多只能由一种权限保护。 +</p> + +<p> +如果应用需要访问受权限保护的功能,则必须在清单文件中使用 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +元素声明应用需要该权限。 +但是,将应用安装到设备上之后,安装程序会通过检查签署应用证书的颁发机构并(在某些情况下)询问用户,确定是否授予请求的权限。 + + +如果授予权限,则应用能够使用受保护的功能。 + +否则,其访问这些功能的尝试将会失败,并且不会向用户发送任何通知。 + +</p> + +<p> +此外,应用也可以使用权限保护自己的组件(Activity、服务、广播接收器和内容提供程序)。 +它可以采用由 +Android +定义(如 +{@link android.Manifest.permission android.Manifest.permission} 中所列)或由其他应用声明的任何权限。或者,它也可以定义自己的权限。新权限用 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素来声明。 +例如,Activity 可受到如下保护: +</p> + +<pre> +<manifest . . . > + <permission android:name="com.example.project.DEBIT_ACCT" . . . /> + <uses-permission android:name="com.example.project.DEBIT_ACCT" /> + . . . + <application . . .> + <activity android:name="com.example.project.FreneticActivity" + android:permission="com.example.project.DEBIT_ACCT" + . . . > + . . . + </activity> + </application> +</manifest> +</pre> + +<p> +请注意,在此示例中,{@code DEBIT_ACCT} +权限不仅是通过 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素来声明,而且其使用也是通过 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> +元素来请求。要让应用的其他组件也能够启动受保护的 Activity,就必须请求其使用权限,即便保护是由应用本身施加的亦如此。 + + +</p> + +<p> +同样还是在此示例中,如果将 +{@code permission} +属性设置为在其他位置(例如,{@code android.permission.CALL_EMERGENCY_NUMBERS})声明的权限,则无需使用 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素再次声明。 +但是,仍有必要通过 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> 请求使用它。 +</p> + +<p> +<code><a href="{@docRoot}guide/topics/manifest/permission-tree-element.html"><permission-tree></a></code> +元素为一组将在代码中定义的权限声明命名空间。 + +同时, +<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +为一组权限(包括在清单文件中使用 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素声明的权限以及在其他位置声明的权限)定义标签。它只影响如何对提供给用户的权限进行分组。 +<code><a href="{@docRoot}guide/topics/manifest/permission-group-element.html"><permission-group></a></code> +元素并不指定哪些权限属于该组,而只是为组提供名称。 + +通过向 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html"><permission></a></code> +元素的 +<code><a href="{@docRoot}guide/topics/manifest/permission-element.html#pgroup">permissionGroup</a></code> +属性分配组名,将权限放入组中。 + +</p> + + +<h3 id="libs">库</h3> + +<p> +每个应用均链接到默认的 Android 库,该库中包括构建应用(以及通用类,如 Activity、服务、 Intent 、视图、按钮、应用、ContentProvider 等)的基本软件包。 + + + +</p> + +<p> +但是,某些软件包驻留在自己的库中。如果应用使用来自其中任一软件包的代码,则必须明确要求其链接到这些软件包。 + +清单文件必须包含单独的 +<code><a href="{@docRoot}guide/topics/manifest/uses-library-element.html"><uses-library></a></code> +元素来命名其中每个库。(库名称可在软件包的文档中找到。) + +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd new file mode 100644 index 000000000000..59682843897d --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/calendar-provider.jd @@ -0,0 +1,1184 @@ +page.title=日历提供程序 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文内容</h2> + <ol> + <li><a href="#overview">基础知识</a></li> + <li><a href="#manifest">用户权限</a></li> + <li><a href="#calendar">日历表</a> +<ol> + <li><a href="#query">查询日历</a></li> + <li><a href="#modify-calendar">修改日历</a></li> + <li><a href="#insert-calendar">插入日历</a></li> + </ol> + </li> + <li><a href="#events">事件表</a> +<ol> + <li><a href="#add-event">添加事件</a></li> + <li><a href="#update-event">更新事件</a></li> + <li><a href="#delete-event">删除事件</a></li> + </ol> + </li> + <li><a href="#attendees">参加者表</a> +<ol> + <li><a href="#add-attendees">添加参加者</a></li> + </ol> + </li> + <li><a href="#reminders">提醒表</a> +<ol> + <li><a href="#add-reminders">添加提醒</a></li> + </ol> + </li> + <li><a href="#instances">实例表</a> + <ol> + <li><a href="#query-instances">查询实例表</a></li> + </ol></li> + <li><a href="#intents">日历 Intent 对象</a> + <ol> + <li><a href="#intent-insert">使用 Intent 对象插入事件</a></li> + <li><a href="#intent-edit">使用 Intent 对象编辑事件</a></li> + <li><a href="#intent-view">使用 Intent 对象查看日历数据</a></li> + </ol> + </li> + + <li><a href="#sync-adapter">同步适配器</a></li> +</ol> + + <h2>关键类</h2> + <ol> + <li>{@link android.provider.CalendarContract.Calendars}</li> + <li>{@link android.provider.CalendarContract.Events}</li> + <li>{@link android.provider.CalendarContract.Attendees}</li> + <li>{@link android.provider.CalendarContract.Reminders}</li> + </ol> +</div> +</div> + +<p>日历提供程序是用户日历事件的存储库。您可以利用 +Calendar Provider API +对日历、事件、参加者、提醒等执行查询、插入、更新和删除操作。</p> + + +<p>Calender Provider API 可供应用和同步适配器使用。规则因进行调用的程序类型而异。 +本文主要侧重于介绍使用 +Calendar Provider API 作为应用的情况。如需了解对各类同步适配器差异的阐述,请参阅<a href="#sync-adapter">同步适配器</a>。 + +</p> + + +<p>正常情况下,要想读取或写入日历数据,应用的清单文件必须包括<a href="#manifest">用户权限</a>中所述的适当权限。 + +为简化常见操作的执行,日历提供程序提供了一组 Intent 对象,<a href="#intents">日历 Intent 对象</a>中对这些 Intent 做了说明。 + +这些 Intent 对象会将用户转到日历应用,执行插入事件、查看事件和编辑事件操作。 +用户与日历应用交互,然后返回原来的应用。 +因此,您的应用不需要请求权限,也不需要提供用于查看事件或创建事件的用户界面。 +</p> + +<h2 id="overview">基础知识</h2> + +<p><a href="{@docRoot}guide/topics/providers/content-providers.html">内容提供程序</a>存储数据并使其可供应用访问。 +Android +平台提供的内容提供程序(包括日历提供程序)通常以一组基于关系数据库模型的表格形式公开数据,在这个表格中,每一行都是一条记录,每一列都是特定类型和含义的数据。 +应用和同步适配器可以通过 +Calendar Provider API +获得对储存用户日历数据的数据库表的读取/写入权限。</p> + +<p>每一个内容提供程序都会公开一个对其数据集进行唯一标识的公共 +URI(包装成一个 {@link android.net.Uri} 对象)。 +控制多个数据集(多个表)的内容提供程序会为每个数据集公开单独的 URI。 +所有提供程序 URI 都以字符串“content://”开头。 +这表示数据受内容提供程序的控制。 +日历提供程序会为其每个类(表)定义 +URI 常量。这些 +URI 的格式为 <code><em><class></em>.CONTENT_URI</code>。例如,{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI}。 +</p> + +<p>图 1 是对日历提供程序数据模型的图形化表示。它显示了将彼此链接在一起的主要表和字段。 +</p> + +<img src="{@docRoot}images/providers/datamodel.png" alt="Calendar Provider Data Model" /> +<p class="img-caption"><strong>图 1. </strong>日历提供程序数据模型。</p> + +<p>用户可以有多个日历,可将不同类型的日历与不同类型的帐户(Google 日历、Exchange 等)关联。</p> + +<p>{@link android.provider.CalendarContract} 定义了日历和事件相关信息的数据模型。这些数据存储在以下所列的若干表中。</p> + +<table> + <tr> + <th>表(类)</th> + <th>描述</th> + </tr> + <tr> + <td><p>{@link android.provider.CalendarContract.Calendars}</p></td> + + <td>此表储存日历特定信息。 +此表中的每一行都包含一个日历的详细信息,例如名称、颜色、同步信息等。 +</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Events}</td> + + <td>此表储存事件特定信息。 +此表中的每一行都包含一个事件的信息—例如事件名称、地点、开始时间、结束时间等。 + +事件可一次性发生,也可多次重复发生。参加者、提醒和扩展属性存储在单独的表内。 +它们各自具有一个 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID},用于引用 Events 表中的 {@link android.provider.BaseColumns#_ID}。 + +</td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances}</td> + + <td>此表储存每个事件实例的开始时间和结束时间。 +此表中的每一行都表示一个事件实例。 +对于一次性事件,实例与事件为 1:1 +映射。对于重复事件,会自动生成多个行,分别对应多个事件实例。 +</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Attendees}</td> + + <td>此表储存事件参加者(来宾)信息。 +每一行都表示事件的一位来宾。 +它指定来宾的类型以及事件的来宾出席响应。 +</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Reminders}</td> + + <td>此表储存提醒/通知数据。 +每一行都表示事件的一个提醒。一个事件可以有多个提醒。 +每个事件的最大提醒数量在 + + +{@link android.provider.CalendarContract.CalendarColumns#MAX_REMINDERS} +中指定,后者由拥有给定日历的同步适配器设置。提醒以事件发生前的分钟数形式指定,其具有一个可决定用户提醒方式的方法。 +</td> + </tr> + +</table> + +<p>Calendar Provider API 以灵活、强大为设计宗旨。提供良好的最终用户体验以及保护日历及其数据的完整性也同样重要。 + +因此,请在使用该 API +时牢记以下要点:</p> + +<ul> + +<li><strong>插入、更新和查看日历事件。</strong>要想直接从日历提供程序插入事件、修改事件以及读取事件,您需要具备相应<a href="#manifest">权限</a>。不过,如果您开发的并不是完备的日历应用或同步适配器,则无需请求这些权限。您可以改用 Android 的日历应用支持的 Intent 对象将读取操作和写入操作转到该应用执行。当您使用 Intent 对象时,您的应用会将用户转到日历应用,在一个预填充表单中执行所需操作。 +完成操作后,用户将返回您的应用。通过将您的应用设计为通过日历执行常见操作,可以为用户提供一致、可靠的用户界面。 + +这是推荐您采用的方法。 +如需了解详细信息,请参阅<a href="#intents">日历 Intent 对象</a>。 +</p> + + +<li><strong>同步适配器。</strong>同步适配器用于将用户设备上的日历数据与其他服务器或数据源同步。 +在 +{@link android.provider.CalendarContract.Calendars} 和 + +{@link android.provider.CalendarContract.Events} +表中,预留了一些供同步适配器使用的列。提供程序和应用不应修改它们。实际上,除非以同步适配器形式进行访问,否则它们处于不可见状态。 +如需了解有关同步适配器的详细信息,请参阅<a href="#sync-adapter">同步适配器</a>。 +</li> + +</ul> + + +<h2 id="manifest">用户权限</h2> + +<p>如需读取日历数据,应用必须在其清单文件中加入 {@link +android.Manifest.permission#READ_CALENDAR} 权限。文件中必须包括用于删除、插入或更新日历数据的 +{@link android.Manifest.permission#WRITE_CALENDAR} +权限:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<manifest xmlns:android="http://schemas.android.com/apk/res/android"...> + <uses-sdk android:minSdkVersion="14" /> + <uses-permission android:name="android.permission.READ_CALENDAR" /> + <uses-permission android:name="android.permission.WRITE_CALENDAR" /> + ... +</manifest> +</pre> + + +<h2 id="calendar">日历表</h2> + +<p>{@link android.provider.CalendarContract.Calendars} 表包含各日历的详细信息。 +应用和<a href="#sync-adapter">同步适配器</a>均可写入下列日历列。 +如需查看所支持字段的完整列表,请参阅 + +{@link android.provider.CalendarContract.Calendars} 参考资料。</p> +<table> + <tr> + <th>常量</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#NAME}</td> + <td>日历的名称。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#CALENDAR_DISPLAY_NAME}</td> + <td>该日历显示给用户时使用的名称。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Calendars#VISIBLE}</td> + + <td>表示是否选择显示该日历的布尔值。值为 0 表示不应显示与该日历关联的事件。 + +值为 1 +表示应该显示与该日历关联的事件。此值影响 {@link +android.provider.CalendarContract.Instances} 表中行的生成。</td> + + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.CalendarColumns#SYNC_EVENTS}</td> + + <td>一个布尔值,表示是否应同步日历并将其事件存储在设备上。 +值为 0 +表示不同步该日历,也不将其事件存储在设备上。值为 1 +表示同步该日历的事件,并将其事件存储在设备上。</td> + </tr> +</table> + +<h3 id="query">查询日历</h3> + +<p>以下示例说明了如何获取特定用户拥有的日历。 +为了简便起见,在此示例中,查询操作显示在用户界面线程(“主线程”)中。 +实际上,此操作应该在一个异步线程而非主线程中完成。 +如需查看更详细的介绍,请参阅<a href="{@docRoot}guide/components/loaders.html">加载器</a>。 +如果您的目的不只是读取数据,还要修改数据,请参阅 {@link android.content.AsyncQueryHandler}。 + +</p> + + +<pre> +// Projection array. Creating indices for this array instead of doing +// dynamic lookups improves performance. +public static final String[] EVENT_PROJECTION = new String[] { + Calendars._ID, // 0 + Calendars.ACCOUNT_NAME, // 1 + Calendars.CALENDAR_DISPLAY_NAME, // 2 + Calendars.OWNER_ACCOUNT // 3 +}; + +// The indices for the projection array above. +private static final int PROJECTION_ID_INDEX = 0; +private static final int PROJECTION_ACCOUNT_NAME_INDEX = 1; +private static final int PROJECTION_DISPLAY_NAME_INDEX = 2; +private static final int PROJECTION_OWNER_ACCOUNT_INDEX = 3;</pre> + + +<div class="sidebox-wrapper"> <div class="sidebox"> <h3>为何必须加入 +ACCOUNT_TYPE?</h3> <p>如果您查询 {@link +android.provider.CalendarContract.Calendars#ACCOUNT_NAME +Calendars.ACCOUNT_NAME},还必须将 +{@link android.provider.CalendarContract.Calendars#ACCOUNT_TYPE Calendars.ACCOUNT_TYPE} +加入选定范围。这是因为,对于给定帐户,只有在同时指定其 +<code>ACCOUNT_NAME</code> 及其 <code>ACCOUNT_TYPE</code> +的情况下,才能将其视为唯一帐户。<code>ACCOUNT_TYPE</code> +字符串对应于在 {@link android.accounts.AccountManager} +处注册帐户时使用的帐户验证器。还有一种名为 {@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +的特殊帐户类型,用于未关联设备帐户的日历。{@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +帐户不会进行同步。</p> </div> </div> + + +<p> 在示例的下一部分,您需要构建查询。选定范围指定查询的条件。 +在此示例中,查询寻找的是 +<code>ACCOUNT_NAME</code> +为“sampleuser@google.com”、<code>ACCOUNT_TYPE</code> +为“com.google”、<code>OWNER_ACCOUNT</code> +为“sampleuser@google.com”的日历。如果您想查看用户查看过的所有日历,而不只是用户拥有的日历,请省略 +<code>OWNER_ACCOUNT</code>。您可以利用查询返回的 +{@link android.database.Cursor} +对象遍历数据库查询返回的结果集。 +如需查看有关在内容提供程序中使用查询的详细介绍,请参阅<a href="{@docRoot}guide/topics/providers/content-providers.html">内容提供程序</a>。 +</p> + + +<pre>// Run query +Cursor cur = null; +ContentResolver cr = getContentResolver(); +Uri uri = Calendars.CONTENT_URI; +String selection = "((" + Calendars.ACCOUNT_NAME + " = ?) AND (" + + Calendars.ACCOUNT_TYPE + " = ?) AND (" + + Calendars.OWNER_ACCOUNT + " = ?))"; +String[] selectionArgs = new String[] {"sampleuser@gmail.com", "com.google", + "sampleuser@gmail.com"}; +// Submit the query and get a Cursor object back. +cur = cr.query(uri, EVENT_PROJECTION, selection, selectionArgs, null);</pre> + +<p>以下后续部分使用游标单步调试结果集。它使用在示例开头设置的常量来返回每个字段的值。 + +</p> + +<pre>// Use the cursor to step through the returned records +while (cur.moveToNext()) { + long calID = 0; + String displayName = null; + String accountName = null; + String ownerName = null; + + // Get the field values + calID = cur.getLong(PROJECTION_ID_INDEX); + displayName = cur.getString(PROJECTION_DISPLAY_NAME_INDEX); + accountName = cur.getString(PROJECTION_ACCOUNT_NAME_INDEX); + ownerName = cur.getString(PROJECTION_OWNER_ACCOUNT_INDEX); + + // Do something with the values... + + ... +} +</pre> + +<h3 id="modify-calendar">修改日历</h3> + +<p>如需执行日历更新,您可以通过 +URI 追加 +ID ({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 或第一个选定项形式提供日历的 + +{@link +android.provider.BaseColumns#_ID}。选定范围应以 +<code>"_id=?"</code> 开头,并且第一个 +<code>selectionArg</code> 应为日历的 {@link +android.provider.BaseColumns#_ID}。 +您还可以通过在 URI +中编码 ID 来执行更新。下例使用 ({@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) 方法更改日历的显示名称: + +</p> + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long calID = 2; +ContentValues values = new ContentValues(); +// The new display name for the calendar +values.put(Calendars.CALENDAR_DISPLAY_NAME, "Trevor's Calendar"); +Uri updateUri = ContentUris.withAppendedId(Calendars.CONTENT_URI, calID); +int rows = getContentResolver().update(updateUri, values, null, null); +Log.i(DEBUG_TAG, "Rows updated: " + rows);</pre> + +<h3 id="insert-calendar">插入日历</h2> + +<p>日历设计为主要由同步适配器进行管理,因此您只应以同步适配器形式插入新日历。 +在大多数情况下,应用只能对日历进行一些表面更改,如更改显示名称。 +如果应用需要创建本地日历,可以利用 +{@link +android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +的 +{@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE},通过以同步适配器形式执行日历插入来实现目的。{@link android.provider.CalendarContract#ACCOUNT_TYPE_LOCAL} +是一种特殊的帐户类型,用于未关联设备帐户的日历。 +这种类型的日历不与服务器同步。如需了解对同步适配器的阐述,请参阅<a href="#sync-adapter">同步适配器</a>。 +</p> + +<h2 id="events">事件表</h2> + +<p>{@link android.provider.CalendarContract.Events} +表包含各事件的详细信息。要想添加、更新或删除事件,应用必须在其<a href="#manifest">清单文件</a>中加入 +{@link android.Manifest.permission#WRITE_CALENDAR} +权限。</p> + +<p>应用和同步适配器均可写入下列事件列。 +如需查看所支持字段的完整列表,请参阅 {@link +android.provider.CalendarContract.Events} 参考资料。</p> + +<table> + <tr> + <th>常量</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#CALENDAR_ID}</td> + <td>事件所属日历的 {@link android.provider.BaseColumns#_ID}。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#ORGANIZER}</td> + <td>事件组织者(所有者)的电子邮件。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#TITLE}</td> + <td>事件的名称。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION}</td> + <td>事件的发生地点。 </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION}</td> + <td>事件的描述。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DTSTART}</td> + <td>事件开始时间,以从公元纪年开始计算的协调世界时毫秒数表示。 </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DTEND}</td> + <td>事件结束时间,以从公元纪年开始计算的协调世界时毫秒数表示。 </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}</td> + <td>事件的时区。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_END_TIMEZONE}</td> + <td>事件结束时间的时区。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DURATION}</td> + + <td><a href="http://tools.ietf.org/html/rfc5545#section-3.8.2.5">RFC5545</a> +格式的事件持续时间。例如,值为 +<code>"PT1H"</code> 表示事件应持续一小时,值为 +<code>"P2W"</code> 表示持续 2 周。 </td> + + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#ALL_DAY}</td> + + <td>值为 1 +表示此事件占用一整天(按照本地时区的定义)。值为 0 +表示它是常规事件,可在一天内的任何时间开始和结束。</td> + + + </tr> + + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#RRULE}</td> + + <td>事件的重复发生规则格式。例如,<code>"FREQ=WEEKLY;COUNT=10;WKST=SU"</code>。 +您可以在<a href="http://tools.ietf.org/html/rfc5545#section-3.8.5.3">此处</a>找到更多示例。 +</td> + + </tr> + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#RDATE}</td> + <td>事件的重复发生日期。 +{@link android.provider.CalendarContract.EventsColumns#RDATE} +与 {@link android.provider.CalendarContract.EventsColumns#RRULE} +通常联合用于定义一组聚合重复实例。 +如需查看更详细的介绍,请参阅 <a href="http://tools.ietf.org/html/rfc5545#section-3.8.5.2">RFC5545 规范</a>。</td> +</tr> + + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY}</td> + + <td>将此事件视为忙碌时间还是可调度的空闲时间。 + </td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_MODIFY}</td> + <td>来宾是否可修改事件。 </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_INVITE_OTHERS}</td> + <td>来宾是否可邀请其他来宾。 </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#GUESTS_CAN_SEE_GUESTS}</td> + <td>来宾是否可查看参加者列表。</td> + </tr> +</table> + +<h3 id="add-event">添加事件</h3> + +<p>当您的应用插入新事件时,我们建议您按照<a href="#intent-insert">使用 Intent 对象插入事件</a>中所述使用 +{@link android.content.Intent#ACTION_INSERT INSERT} Intent 对象。不过,如需,也可直接插入事件。 +本节描述如何执行此操作。 +</p> + + +<p>以下是插入新事件的规则: </p> +<ul> + + <li>您必须加入 {@link +android.provider.CalendarContract.EventsColumns#CALENDAR_ID} 和 {@link +android.provider.CalendarContract.EventsColumns#DTSTART}。</li> + +<li>您必须加入 {@link +android.provider.CalendarContract.EventsColumns#EVENT_TIMEZONE}。如需获取系统中已安装时区 ID +的列表,请使用 {@link +java.util.TimeZone#getAvailableIDs()}。请注意,如果您按<a href="#intent-insert">使用 Intent 对象插入事件</a>中所述通过 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 对象插入事件,则此规则不适用—在该情形下,系统会提供默认时区。 +</li> + + <li>对于非重复事件,您必须加入 {@link +android.provider.CalendarContract.EventsColumns#DTEND}。 </li> + + + <li>对于重复事件,您必须加入 {@link +android.provider.CalendarContract.EventsColumns#DURATION} 以及 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 或 {@link +android.provider.CalendarContract.EventsColumns#RDATE}。请注意,如果您按<a href="#intent-insert">使用 Intent 对象插入事件</a>中所述通过 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 对象插入事件,则此规则不适用—在该情形下,您可以将 {@link +android.provider.CalendarContract.EventsColumns#RRULE} 与 {@link android.provider.CalendarContract.EventsColumns#DTSTART} 和 {@link android.provider.CalendarContract.EventsColumns#DTEND} +联用,日历应用会自动将其转换为持续时间。 +</li> + +</ul> + +<p>以下是一个插入事件的示例。为了简便起见,此操作是在 UI +线程内执行的。实际上,应该在异步线程中完成插入和更新,以便将操作移入后台线程。 +如需了解详细信息,请参阅 +{@link android.content.AsyncQueryHandler}。</p> + + +<pre> +long calID = 3; +long startMillis = 0; +long endMillis = 0; +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2012, 9, 14, 7, 30); +startMillis = beginTime.getTimeInMillis(); +Calendar endTime = Calendar.getInstance(); +endTime.set(2012, 9, 14, 8, 45); +endMillis = endTime.getTimeInMillis(); +... + +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Events.DTSTART, startMillis); +values.put(Events.DTEND, endMillis); +values.put(Events.TITLE, "Jazzercise"); +values.put(Events.DESCRIPTION, "Group workout"); +values.put(Events.CALENDAR_ID, calID); +values.put(Events.EVENT_TIMEZONE, "America/Los_Angeles"); +Uri uri = cr.insert(Events.CONTENT_URI, values); + +// get the event ID that is the last element in the Uri +long eventID = Long.parseLong(uri.getLastPathSegment()); +// +// ... do something with event ID +// +//</pre> + +<p class="note"><strong>注:</strong>请注意以上示例如何在事件创建后捕获事件 +ID。这是获取事件 ID +的最简单方法。您经常需要使用事件 ID +来执行其他日历操作—例如,向事件添加参加者或提醒。</p> + + +<h3 id="update-event">更新事件</h3> + +<p>当您的应用想允许用户编辑事件时,我们建议您按照<a href="#intent-edit">使用 Intent 对象编辑事件</a>中所述使用 +{@link android.content.Intent#ACTION_EDIT EDIT} + Intent 对象。不过,如需,也可以直接编辑事件。 +如需执行事件更新,您可以通过 URI 追加 ID ({@link + +android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()}) + +或第一个选定项形式提供事件的 <code>_ID</code>。 +选定范围应以 <code>"_id=?"</code> 开头,并且第一个<code>selectionArg</code> 应为事件的 +<code>_ID</code>。您还可以使用不含 +ID +的选定范围执行更新。以下是一个更新事件的示例。它使用 + {@link android.content.ContentUris#withAppendedId(android.net.Uri,long) withAppendedId()} +方法更改事件的名称:</p> + + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long eventID = 188; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +Uri updateUri = null; +// The new title for the event +values.put(Events.TITLE, "Kickboxing"); +updateUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +int rows = getContentResolver().update(updateUri, values, null, null); +Log.i(DEBUG_TAG, "Rows updated: " + rows); </pre> + +<h3 id="delete-event">删除事件</h3> + +<p>您可以通过将事件 {@link +android.provider.BaseColumns#_ID} 作为 URI 追加 ID +或通过使用标准选定范围来删除事件。如果您使用追加 +ID,则将无法同时使用选定范围。共有两个版本的删除:应用删除和同步适配器删除。应用删除将 +<em>deleted</em> 列设置为 1。此标志告知同步适配器该行已删除,并且应将此删除操作传播至服务器。 + +同步适配器删除会将事件连同其所有关联数据从数据库中删除。 +以下是一个应用通过事件 {@link android.provider.BaseColumns#_ID} +删除事件的示例:</p> + + +<pre>private static final String DEBUG_TAG = "MyActivity"; +... +long eventID = 201; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +Uri deleteUri = null; +deleteUri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +int rows = getContentResolver().delete(deleteUri, null, null); +Log.i(DEBUG_TAG, "Rows deleted: " + rows); +</pre> + +<h2 id="attendees">参加者表</h2> + +<p>{@link android.provider.CalendarContract.Attendees} +表的每一行都表示事件的一位参加者或来宾。调用 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +会返回一个参加者列表,其中包含具有给定 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +的事件的参加者。 +此 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +必须匹配特定事件的 {@link +android.provider.BaseColumns#_ID}。</p> + +<p>下表列出了可写入的字段。 +插入新参加者时,您必须加入除 <code>ATTENDEE_NAME</code> 之外的所有字段。 + +</p> + + +<table> + <tr> + <th>常量</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID}</td> + <td>事件的 ID。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_NAME}</td> + <td>参加者的姓名。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_EMAIL}</td> + <td>参加者的电子邮件地址。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_RELATIONSHIP}</td> + <td><p>参加者与事件的关系。下列值之一:</p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ATTENDEE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_NONE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_ORGANIZER}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_PERFORMER}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#RELATIONSHIP_SPEAKER}</li> + </ul> + </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_TYPE}</td> + <td><p>参加者的类型。下列值之一: </p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#TYPE_REQUIRED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#TYPE_OPTIONAL}</li> + </ul></td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS}</td> + <td><p>参加者的出席状态。下列值之一:</p> + <ul> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_ACCEPTED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_DECLINED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_INVITED}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_NONE}</li> + <li>{@link android.provider.CalendarContract.AttendeesColumns#ATTENDEE_STATUS_TENTATIVE}</li> + </ul></td> + </tr> +</table> + +<h3 id="add-attendees">添加参加者</h3> + +<p>以下是一个为事件添加一位参加者的示例。请注意,{@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} +是必填项: +</p> + +<pre> +long eventID = 202; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Attendees.ATTENDEE_NAME, "Trevor"); +values.put(Attendees.ATTENDEE_EMAIL, "trevor@example.com"); +values.put(Attendees.ATTENDEE_RELATIONSHIP, Attendees.RELATIONSHIP_ATTENDEE); +values.put(Attendees.ATTENDEE_TYPE, Attendees.TYPE_OPTIONAL); +values.put(Attendees.ATTENDEE_STATUS, Attendees.ATTENDEE_STATUS_INVITED); +values.put(Attendees.EVENT_ID, eventID); +Uri uri = cr.insert(Attendees.CONTENT_URI, values); +</pre> + +<h2 id="reminders">提醒表</h2> + +<p>{@link android.provider.CalendarContract.Reminders} +表的每一行都表示事件的一个提醒。调用 +{@link android.provider.CalendarContract.Reminders#query(android.content.ContentResolver, long, java.lang.String[]) query()} +会返回一个提醒列表,其中包含具有给定 {@link android.provider.CalendarContract.AttendeesColumns#EVENT_ID} 的事件的提醒。 +</p> + + +<p>下表列出了提醒的可写入字段。插入新提醒时,必须加入所有字段。 +请注意,同步适配器指定它们在 +{@link +android.provider.CalendarContract.Calendars} 表中支持的提醒类型。详情请参阅 +{@link android.provider.CalendarContract.CalendarColumns#ALLOWED_REMINDERS} +。</p> + + +<table> + <tr> + <th>常量</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#EVENT_ID}</td> + <td>事件的 ID。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#MINUTES}</td> + <td>事件发生前的分钟数,应在达到该时间时发出提醒。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.RemindersColumns#METHOD}</td> + <td><p>服务器上设置的提醒方法。下列值之一:</p> + <ul> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_ALERT}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_DEFAULT}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_EMAIL}</li> + <li>{@link android.provider.CalendarContract.RemindersColumns#METHOD_SMS}</li> + </ul></td> + </tr> +</table> + +<h3 id="add-reminders">添加提醒</h3> + +<p>下例显示如何为事件添加提醒。提醒在事件发生前 15 +分钟发出。</p> +<pre> +long eventID = 221; +... +ContentResolver cr = getContentResolver(); +ContentValues values = new ContentValues(); +values.put(Reminders.MINUTES, 15); +values.put(Reminders.EVENT_ID, eventID); +values.put(Reminders.METHOD, Reminders.METHOD_ALERT); +Uri uri = cr.insert(Reminders.CONTENT_URI, values);</pre> + +<h2 id="instances">实例表</h2> + +<p> +{@link android.provider.CalendarContract.Instances} +表储存事件实例的开始时间和结束时间。此表中的每一行都表示一个事件实例。 +实例表无法写入,只提供查询事件实例的途径。 + </p> + +<p>下表列出了一些您可以执行实例查询的字段。请注意, +时区由 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_TYPE} +和 +{@link android.provider.CalendarContract.CalendarCache#KEY_TIMEZONE_INSTANCES} 定义。</p> + + +<table> + <tr> + <th>常量</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#BEGIN}</td> + <td>实例的开始时间,以协调世界时毫秒数表示。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END}</td> + <td>实例的结束时间,以协调世界时毫秒数表示。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END_DAY}</td> + + <td>与日历时区相应的实例儒略历结束日。 + + +</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#END_MINUTE}</td> + + <td>从日历时区午夜开始计算的实例结束时间(分钟)。 +</td> + + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#EVENT_ID}</td> + <td>该实例对应事件的 <code>_ID</code>。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#START_DAY}</td> + <td>与日历时区相应的实例儒略历开始日。 + </td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.Instances#START_MINUTE}</td> + + <td>从日历时区午夜开始计算的实例开始时间(分钟)。 + +</td> + + </tr> + +</table> + +<h3 id="query-instances">查询实例表</h3> + +<p>如需查询实例表,您需要在 URI +中指定查询的时间范围。在以下示例中,{@link android.provider.CalendarContract.Instances} +通过其 {@link android.provider.CalendarContract.EventsColumns} 接口实现获得对 {@link +android.provider.CalendarContract.EventsColumns#TITLE} +字段的访问权限。 +换言之,{@link +android.provider.CalendarContract.EventsColumns#TITLE} 是通过数据库视图,而不是通过查询原始 +{@link +android.provider.CalendarContract.Instances} 表返回的。</p> + +<pre> +private static final String DEBUG_TAG = "MyActivity"; +public static final String[] INSTANCE_PROJECTION = new String[] { + Instances.EVENT_ID, // 0 + Instances.BEGIN, // 1 + Instances.TITLE // 2 + }; + +// The indices for the projection array above. +private static final int PROJECTION_ID_INDEX = 0; +private static final int PROJECTION_BEGIN_INDEX = 1; +private static final int PROJECTION_TITLE_INDEX = 2; +... + +// Specify the date range you want to search for recurring +// event instances +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2011, 9, 23, 8, 0); +long startMillis = beginTime.getTimeInMillis(); +Calendar endTime = Calendar.getInstance(); +endTime.set(2011, 10, 24, 8, 0); +long endMillis = endTime.getTimeInMillis(); + +Cursor cur = null; +ContentResolver cr = getContentResolver(); + +// The ID of the recurring event whose instances you are searching +// for in the Instances table +String selection = Instances.EVENT_ID + " = ?"; +String[] selectionArgs = new String[] {"207"}; + +// Construct the query with the desired date range. +Uri.Builder builder = Instances.CONTENT_URI.buildUpon(); +ContentUris.appendId(builder, startMillis); +ContentUris.appendId(builder, endMillis); + +// Submit the query +cur = cr.query(builder.build(), + INSTANCE_PROJECTION, + selection, + selectionArgs, + null); + +while (cur.moveToNext()) { + String title = null; + long eventID = 0; + long beginVal = 0; + + // Get the field values + eventID = cur.getLong(PROJECTION_ID_INDEX); + beginVal = cur.getLong(PROJECTION_BEGIN_INDEX); + title = cur.getString(PROJECTION_TITLE_INDEX); + + // Do something with the values. + Log.i(DEBUG_TAG, "Event: " + title); + Calendar calendar = Calendar.getInstance(); + calendar.setTimeInMillis(beginVal); + DateFormat formatter = new SimpleDateFormat("MM/dd/yyyy"); + Log.i(DEBUG_TAG, "Date: " + formatter.format(calendar.getTime())); + } + }</pre> + +<h2 id="intents">日历 Intent 对象</h2> +<p>您的应用不需要读取和写入日历数据的<a href="#manifest">权限</a>。它可以改用 Android 的日历应用支持的 Intent 对象将读取和写入操作转到该应用执行。下表列出了日历提供程序支持的 Intent 对象:</p> +<table> + <tr> + <th>操作</th> + <th>URI</th> + + <th>描述</th> + <th>Extra</th> + </tr> + <tr> + <td><br> + {@link android.content.Intent#ACTION_VIEW VIEW} <br></td> + <td><p><code>content://com.android.calendar/time/<ms_since_epoch></code></p> + 您还可以通过 +{@link android.provider.CalendarContract#CONTENT_URI CalendarContract.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅<a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-view">使用 Intent 对象查看日历数据</a>。 + + </td> + <td>打开日历后定位到 <code><ms_since_epoch></code> 指定的时间。</td> + <td>无。</td> + </tr> + <tr> + <td><p>{@link android.content.Intent#ACTION_VIEW VIEW} </p> + + </td> + <td><p><code>content://com.android.calendar/events/<event_id></code></p> + + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅<a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-view">使用 Intent 对象查看日历数据</a>。 + + </td> + <td>查看 <code><event_id></code> 指定的事件。</td> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}<br> + <br> + <br> + {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}</td> + </tr> + + <tr> + <td>{@link android.content.Intent#ACTION_EDIT EDIT} </td> + <td><p><code>content://com.android.calendar/events/<event_id></code></p> + + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅<a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-edit">使用 Intent 对象编辑事件</a>。 + + + </td> + <td>编辑 <code><event_id></code> 指定的事件。</td> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME CalendarContract.EXTRA_EVENT_BEGIN_TIME}<br> + <br> + <br> + {@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME CalendarContract.EXTRA_EVENT_END_TIME}</td> + </tr> + + <tr> + <td>{@link android.content.Intent#ACTION_EDIT EDIT} <br> + <br> + {@link android.content.Intent#ACTION_INSERT INSERT} </td> + <td><p><code>content://com.android.calendar/events</code></p> + + 您还可以通过 +{@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} 引用 URI。 +如需查看使用该 Intent 对象的示例,请参阅<a href="{@docRoot}guide/topics/providers/calendar-provider.html#intent-insert">使用 Intent 对象插入事件</a>。 + + </td> + + <td>创建事件。</td> + <td>下表列出的任一 Extra。</td> + </tr> +</table> + +<p>下表列出了日历提供程序支持的 Intent Extra: +</p> +<table> + <tr> + <th> Intent Extra</th> + <th>描述</th> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#TITLE Events.TITLE}</td> + <td>事件的名称。</td> + </tr> + <tr> + + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME}</td> + <td>事件开始时间,以从公元纪年开始计算的毫秒数表示。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME}</td> + + <td>事件结束时间,以从公元纪年开始计算的毫秒数表示。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract#EXTRA_EVENT_ALL_DAY +CalendarContract.EXTRA_EVENT_ALL_DAY}</td> + + <td>一个布尔值,表示事件属于全天事件。值可以是 +<code>true</code> 或 <code>false</code>。</td> </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#EVENT_LOCATION +Events.EVENT_LOCATION}</td> + + <td>事件的地点。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#DESCRIPTION +Events.DESCRIPTION}</td> + + <td>事件描述。</td> + </tr> + <tr> + <td> + {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL}</td> + <td>逗号分隔值形式的受邀者电子邮件地址列表。</td> + </tr> + <tr> + <td> + {@link android.provider.CalendarContract.EventsColumns#RRULE Events.RRULE}</td> + <td>事件的重复发生规则。</td> + </tr> + <tr> + <td> + {@link android.provider.CalendarContract.EventsColumns#ACCESS_LEVEL +Events.ACCESS_LEVEL}</td> + + <td>事件是私人性质还是公共性质。</td> + </tr> + <tr> + <td>{@link android.provider.CalendarContract.EventsColumns#AVAILABILITY +Events.AVAILABILITY}</td> + + <td>将此事件视为忙碌时间还是可调度的空闲时间。</td> + +</table> +<p>下文描述如何使用这些 Intent 对象。</p> + + +<h3 id="intent-insert">使用 Intent 对象插入事件</h3> + +<p>您的应用可以利用 {@link android.content.Intent#ACTION_INSERT INSERT} + Intent 对象将事件插入任务转到日历应用执行。使用此方法时,您的应用甚至不需要在其<a href="#manifest">清单文件</a>中加入 +{@link +android.Manifest.permission#WRITE_CALENDAR} 权限。</p> + + +<p>当用户运行使用此方法的应用时,应用会将其转到日历来完成事件添加操作。 +{@link +android.content.Intent#ACTION_INSERT INSERT} Intent 利用 extra +字段为表单预填充日历中事件的详细信息。用户随后可取消事件、根据需要编辑表单或将事件保存到日历中。 + +</p> + + + +<p>以下是一个代码段,用于安排一个在 2012 年 1 月 19 日上午 +7:30 开始、8:30 结束的事件。请注意该代码段中的以下内容:</p> + +<ul> + <li>它将 {@link android.provider.CalendarContract.Events#CONTENT_URI Events.CONTENT_URI} +指定为 URI。</li> + + <li>它使用 {@link +android.provider.CalendarContract#EXTRA_EVENT_BEGIN_TIME +CalendarContract.EXTRA_EVENT_BEGIN_TIME} 和 {@link +android.provider.CalendarContract#EXTRA_EVENT_END_TIME +CalendarContract.EXTRA_EVENT_END_TIME} extra +字段为表单预填充事件的时间。这些时间的值必须以从公元纪年开始计算的协调世界时毫秒数表示。 +</li> + + <li>它使用 {@link android.content.Intent#EXTRA_EMAIL Intent.EXTRA_EMAIL} +extra 字段提供以逗号分隔的受邀者电子邮件地址列表。</li> + +</ul> +<pre> +Calendar beginTime = Calendar.getInstance(); +beginTime.set(2012, 0, 19, 7, 30); +Calendar endTime = Calendar.getInstance(); +endTime.set(2012, 0, 19, 8, 30); +Intent intent = new Intent(Intent.ACTION_INSERT) + .setData(Events.CONTENT_URI) + .putExtra(CalendarContract.EXTRA_EVENT_BEGIN_TIME, beginTime.getTimeInMillis()) + .putExtra(CalendarContract.EXTRA_EVENT_END_TIME, endTime.getTimeInMillis()) + .putExtra(Events.TITLE, "Yoga") + .putExtra(Events.DESCRIPTION, "Group class") + .putExtra(Events.EVENT_LOCATION, "The gym") + .putExtra(Events.AVAILABILITY, Events.AVAILABILITY_BUSY) + .putExtra(Intent.EXTRA_EMAIL, "rowan@example.com,trevor@example.com"); +startActivity(intent); +</pre> + +<h3 id="intent-edit">使用 Intent 对象编辑事件</h3> + +<p>您可以按<a href="#update-event">更新事件</a>中所述直接更新事件。但使用 {@link +android.content.Intent#ACTION_EDIT EDIT} Intent 可以让不具有事件编辑权限的应用将事件编辑操作转到日历应用执行。当用户在日历中完成事件编辑后,将会返回原来的应用。 + + +</p> <p>以下是一个 Intent 对象的示例,它为指定事件设置新名称,并允许用户在日历中编辑事件。 +</p> + + +<pre>long eventID = 208; +Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +Intent intent = new Intent(Intent.ACTION_EDIT) + .setData(uri) + .putExtra(Events.TITLE, "My New Title"); +startActivity(intent);</pre> + +<h3 id="intent-view">使用 Intent 对象查看日历数据</h3> +<p>日历提供程序提供了两种不同的 {@link android.content.Intent#ACTION_VIEW VIEW} Intent 对象使用方法:</p> +<ul> + <li>打开日历并定位到特定日期。</li> + <li>查看事件。</li> + +</ul> +<p>下例显示如何打开日历并定位到特定日期:</p> +<pre>// A date-time specified in milliseconds since the epoch. +long startMillis; +... +Uri.Builder builder = CalendarContract.CONTENT_URI.buildUpon(); +builder.appendPath("time"); +ContentUris.appendId(builder, startMillis); +Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(builder.build()); +startActivity(intent);</pre> + +<p>下例显示如何打开事件进行查看:</p> +<pre>long eventID = 208; +... +Uri uri = ContentUris.withAppendedId(Events.CONTENT_URI, eventID); +Intent intent = new Intent(Intent.ACTION_VIEW) + .setData(uri); +startActivity(intent); +</pre> + + +<h2 id="sync-adapter">同步适配器</h2> + + +<p>应用和同步适配器在访问日历提供程序的方式上只存在微小差异: +</p> + +<ul> + <li>同步适配器需要通过将 {@link android.provider.CalendarContract#CALLER_IS_SYNCADAPTER} 设置为 <code>true</code> 来表明它是同步适配器。</li> + + + <li>同步适配器需要提供 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_NAME} 和 {@link +android.provider.CalendarContract.SyncColumns#ACCOUNT_TYPE} 作为 URI 中的查询参数。 </li> + + <li>与应用或小工具相比,同步适配器拥有写入权限的列更多。 + 例如,应用只能修改日历的少数几种特性, +例如其名称、显示名称、能见度设置以及是否同步日历。 +相比之下,同步适配器不仅可以访问这些列,还能访问许多其他列, +例如日历颜色、时区、访问级别、地点等等。不过,同步适配器受限于它指定的 +<code>ACCOUNT_NAME</code> 和 +<code>ACCOUNT_TYPE</code>。</li> </ul> + +<p>您可以利用以下 helper 方法返回供与同步适配器一起使用的 URI:</p> +<pre> static Uri asSyncAdapter(Uri uri, String account, String accountType) { + return uri.buildUpon() + .appendQueryParameter(android.provider.CalendarContract.CALLER_IS_SYNCADAPTER,"true") + .appendQueryParameter(Calendars.ACCOUNT_NAME, account) + .appendQueryParameter(Calendars.ACCOUNT_TYPE, accountType).build(); + } +</pre> +<p>如需查看同步适配器的实现示例(并非仅限与日历有关的实现),请参阅 +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">SampleSyncAdapter</a>。 diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd new file mode 100644 index 000000000000..37d472d40deb --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/contacts-provider.jd @@ -0,0 +1,2356 @@ +page.title=联系人提供程序 +@jd:body +<div id="qv-wrapper"> +<div id="qv"> +<h2>内容快览</h2> +<ul> + <li>Android 有关联系人的信息存储库。</li> + <li> + 与 Web 同步。 + </li> + <li> + 集成社交流数据。 + </li> +</ul> +<h2>本文内容</h2> +<ol> + <li> + <a href="#InformationTypes">联系人提供程序组织</a> + </li> + <li> + <a href="#RawContactBasics">原始联系人</a> + </li> + <li> + <a href="#DataBasics">数据</a> + </li> + <li> + <a href="#ContactBasics">联系人</a> + </li> + <li> + <a href="#Sources">来自同步适配器的数据</a> + </li> + <li> + <a href="#Permissions">所需权限</a> + </li> + <li> + <a href="#UserProfile">用户个人资料</a> + </li> + <li> + <a href="#ContactsProviderMetadata">联系人提供程序元数据</a> + </li> + <li> + <a href="#Access">联系人提供程序访问</a> + <li> + </li> + <li> + <a href="#SyncAdapters">联系人提供程序同步适配器</a> + </li> + <li> + <a href="#SocialStream">社交流数据</a> + </li> + <li> + <a href="#AdditionalFeatures">其他联系人提供程序功能</a> + </li> +</ol> +<h2>关键类</h2> +<ol> + <li>{@link android.provider.ContactsContract.Contacts}</li> + <li>{@link android.provider.ContactsContract.RawContacts}</li> + <li>{@link android.provider.ContactsContract.Data}</li> + <li>{@link android.provider.ContactsContract.StreamItems}</li> +</ol> +<h2>相关示例</h2> +<ol> + <li> + <a href="{@docRoot}resources/samples/ContactManager/index.html"> +联系人管理器 +</a> + </li> + <li> + <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html"> +示例同步适配器</a> + </li> +</ol> +<h2>另请参阅</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +内容提供程序基础知识 +</a> + </li> +</ol> +</div> +</div> +<p> + 联系人提供程序是一个强大而又灵活的 Android 组件,用于管理设备上有关联系人数据的中央存储库。 +联系人提供程序是您在设备的联系人应用中看到的数据源,您也可以在自己的应用中访问其数据,并可在设备与在线服务之间传送数据。 + +提供程序储存有多种数据源,由于它会试图为每个联系人管理尽可能多的数据,因此造成其组织结构非常复杂。 + +为此,该提供程序的 API 包含丰富的协定类和接口,为数据检索和修改提供便利。 + + +</p> +<p> + 本指南介绍下列内容: +</p> + <ul> + <li> + 提供程序基本结构 + </li> + <li> + 如何从提供程序检索数据 + </li> + <li> + 如何修改提供程序中的数据 + </li> + <li> + 如何编写用于同步服务器数据与联系人提供程序数据的同步适配器。 + + </li> + </ul> +<p> + 本指南假定您了解 Android 内容提供程序的基础知识。如需了解有关 Android 内容提供程序的更多信息,请阅读 +<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">内容提供程序基础知识</a>指南。 + + +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用是一个示例,展示如何使用同步适配器在联系人提供程序与 Google 网络服务托管的一个示例应用之间传送数据。 + + +</p> +<h2 id="InformationTypes">联系人提供程序组织</h2> +<p> + 联系人提供程序是 Android 内容提供程序的一个组件。它保留了三种类型的联系人数据,每一种数据都对应提供程序提供的一个表,如图 1 所示: + + +</p> +<img src="{@docRoot}images/providers/contacts_structure.png" alt="" height="364" id="figure1" /> +<p class="img-caption"> + <strong>图 1. </strong>联系人提供程序表结构。 +</p> +<p> + 这三个表通常以其协定类的名称命名。这些类定义表所使用的内容 URI、列名称及列值相应的常量: + +</p> +<dl> + <dt> + {@link android.provider.ContactsContract.Contacts} 表 + </dt> + <dd> + 表示不同联系人的行,基于聚合的原始联系人行。 + </dd> + <dt> + {@link android.provider.ContactsContract.RawContacts} 表 + </dt> + <dd> + 包含联系人数据摘要的行,针对特定用户帐户和类型。 + </dd> + <dt> + {@link android.provider.ContactsContract.Data} 表 + </dt> + <dd> + 包含原始联系人详细信息(例如电子邮件地址或电话号码)的行。 + </dd> +</dl> +<p> + 由 {@link android.provider.ContactsContract} +中的协定类表示的其他表是辅助表,联系人提供程序利用它们来管理其操作,或为设备的联系人或电话应用中的特定功能提供支持。 + +</p> +<h2 id="RawContactBasics">原始联系人</h2> +<p> + 一个原始联系人表示来自某一帐户类型和帐户名称、有关某个联系人的数据。 +由于联系人提供程序允许将多个在线服务作为某一联系人的数据源,因此它允许同一联系人对应多个原始联系人。 + + 借助支持多个原始联系人的特性,用户还可以将某一联系人在帐户类型相同的多个帐户中的数据进行合并。 + +</p> +<p> + 原始联系人的大部分数据并不存储在 +{@link android.provider.ContactsContract.RawContacts} 表内,而是存储在 +{@link android.provider.ContactsContract.Data} 表中的一行或多行内。每个数据行都有一个 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID Data.RAW_CONTACT_ID} 列,其中包含其父级 {@link android.provider.ContactsContract.RawContacts} 行的 {@link android.provider.BaseColumns#_ID RawContacts._ID} 值。 + + +</p> +<h3 id="RawContactsColumns">重要的原始联系人列</h3> +<p> + 表 1 列出了 {@link android.provider.ContactsContract.RawContacts} 表中的重要列。 +请阅读表后的说明: +</p> +<p class="table-caption" id="table1"> + <strong>表 1. </strong>重要的原始联系人列。 +</p> +<table> + <tr> + <th scope="col">列名称</th> + <th scope="col">用途</th> + <th scope="col">备注</th> + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_NAME} + </td> + <td> + 作为该原始联系人来源的帐户类型的帐户名称。 + 例如,Google 帐户的帐户名称是设备所有者的某个 Gmail +地址。如需了解详细信息,请参阅有关 + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} 的下一条目。 + + </td> + <td> + 此名称的格式专用于其帐户类型。它不一定是电子邮件地址。 + + </td> + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.SyncColumns#ACCOUNT_TYPE} + </td> + <td> + 作为该原始联系人来源的帐户类型。例如,Google 帐户的帐户类型是 <code>com.google</code>。 +请务必使用您拥有或控制的域的域标识符限定您的帐户类型。 +这可以确保您的帐户类型具有唯一性。 + + </td> + <td> + 提供联系人数据的帐户类型通常关联有同步适配器,用于与联系人提供程序进行同步。 + + </tr> + <tr> + <td> + {@link android.provider.ContactsContract.RawContactsColumns#DELETED} + </td> + <td> + 原始联系人的“已删除”标志。 + </td> + <td> + 此标志让联系人提供程序能够在内部保留该行,直至同步适配器能够从服务器删除该行,然后再从存储库中最终删除该行。 + + + </td> + </tr> +</table> +<h4>说明</h4> +<p> + 以下是关于 + {@link android.provider.ContactsContract.RawContacts} 表的重要说明: +</p> +<ul> + <li> + 原始联系人的姓名并不存储其在 +{@link android.provider.ContactsContract.RawContacts} 中的行内,而是存储在 +{@link android.provider.ContactsContract.Data} 表的 +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行内。一个原始联系人在 {@link android.provider.ContactsContract.Data} 表中只有一个该类型的行。 + + </li> + <li> + <strong>注意:</strong>要想在原始联系人行中使用您自己的帐户数据,必须先在 {@link android.accounts.AccountManager} 中注册帐户。 +为此,请提示用户将帐户类型及其帐户名称添加到帐户列表。 +如果您不这样做,联系人提供程序将自动删除您的原始联系人行。 + + <p> + 例如,如果您想让您的应用为您域名为 {@code com.example.dataservice}、基于 Web 的服务保留联系人数据,并且您的服务的用户帐户是 {@code becky.sharp@dataservice.example.com},则用户必须先添加帐户“类型”({@code com.example.dataservice}) 和帐户“名称”({@code becky.smart@dataservice.example.com}),然后您的应用才能添加原始联系人行。 + + + + + 您可以在文档中向用户解释这项要求,也可以提示用户添加类型和名称,或者同时采用这两种措施。 +下文对帐户类型和帐户名称做了更详尽的描述。 + + </li> +</ul> +<h3 id="RawContactsExample">原始联系人数据来源</h3> +<p> + 为理解原始联系人的工作方式,假设有一位用户“Emily Dickinson”,她的设备上定义了以下三个用户帐户: + +</p> +<ul> + <li><code>emily.dickinson@gmail.com</code></li> + <li><code>emilyd@gmail.com</code></li> + <li>Twitter 帐户“belle_of_amherst”</li> +</ul> +<p> + 该用户已在 <em>Accounts</em> 设置中为全部三个帐户启用了 +<em>Sync Contacts</em>。 +</p> +<p> + 假定 Emily Dickinson 打开一个浏览器窗口,以 +<code>emily.dickinson@gmail.com</code> 身份登录 Gmail,然后打开 +“联系人”,并添加“Thomas Higginson”。后来,她以 +<code>emilyd@gmail.com</code> 身份登录 Gmail,并向“Thomas Higginson”发送一封电子邮件,此操作会自动将他添加为联系人。 +她还在 Twitter 上关注了“colonel_tom”(Thomas Higginson 的 Twitter ID)。 + +</p> +<p> + 以上操作的结果是,联系人提供程序会创建以下这三个原始联系人: +</p> +<ol> + <li> + 第一个原始联系人对应“Thomas Higginson”,关联帐户 <code>emily.dickinson@gmail.com</code>。 + 用户帐户类型是 Google。 + </li> + <li> + 第二个原始联系人对应“Thomas Higginson”,关联帐户 <code>emilyd@gmail.com</code>。 + 用户帐户类型也是 Google。由于添加的联系人对应的用户帐户不同,因此尽管名称与前一名称完全相同,也只能作为第二个原始联系人。 + + + </li> + <li> + 第三个原始联系人对应“Thomas Higginson”,关联帐户“belle_of_amherst”。用户帐户类型是 Twitter。 + + </li> +</ol> +<h2 id="DataBasics">数据</h2> +<p> + 如前文所做的说明,原始联系人的数据存储在一个 +{@link android.provider.ContactsContract.Data} 行中,该行链接到原始联系人的 +<code>_ID</code> 值。这使一位原始联系人可以拥有多个具有相同数据类型的实例,例如电子邮件地址或电话号码。 +例如,如果对应 +{@code emilyd@gmail.com} 的“Thomas Higginson”(关联 Google 帐户 <code>emilyd@gmail.com</code> 的 Thomas Higginson +的原始联系人行)的住宅电子邮件地址为 +<code>thigg@gmail.com</code>,办公电子邮件地址为 +<code>thomas.higginson@gmail.com</code>,则联系人提供程序会存储这两个电子邮件地址行,并将它们都链接到原始联系人。 + +</p> +<p> + 请注意,这个表中存储了不同类型的数据。显示姓名、电话号码、电子邮件、邮政地址、照片以及网站明细行都可以在 {@link android.provider.ContactsContract.Data} 表中找到。 + +为便于管理这些数据, +{@link android.provider.ContactsContract.Data} 表为一些列使用了描述性名称,为其他列使用了通用名称。 +使用描述性名称的列的内容具有相同的含义,与行中数据的类型无关,而使用通用名称的列的内容则会随数据类型的不同而具有不同的含义。 + + +</p> +<h3 id="DescriptiveColumns">描述性列名称</h3> +<p> + 以下是一些描述性列名称的示例: +</p> +<dl> + <dt> + {@link android.provider.ContactsContract.Data#RAW_CONTACT_ID} + </dt> + <dd> + 该数据对应的原始联系人 <code>_ID</code> 列的值。 + </dd> + <dt> + {@link android.provider.ContactsContract.Data#MIMETYPE} + </dt> + <dd> + 该行中存储的数据类型,以自定义 MIME(多用途互联网邮件扩展)类型表示。联系人提供程序使用了 +{@link android.provider.ContactsContract.CommonDataKinds} 子类中定义的 MIME 类型。 +这些 MIME 类型为开源类型,可供与联系人提供程序协作的任何应用或同步适配器使用。 + + </dd> + <dt> + {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} + </dt> + <dd> + 如果一个原始联系人可能具有多个这种类型的数据行, +{@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列会标记 +包含该类型主要数据的数据行。例如,如果用户长按某个联系人的电话号码,并选择 <strong>Set default</strong>,则包含该号码的 {@link android.provider.ContactsContract.Data} 行会将其 {@link android.provider.ContactsContract.DataColumns#IS_PRIMARY} 列设置为一个非零值。 + + + + + </dd> +</dl> +<h3 id="GenericColumns">通用列名称</h3> +<p> + 有 15 个通用列命名为 <code>DATA1</code> 至 +<code>DATA15</code>,可普遍适用;还有四个通用列命名为 <code>SYNC1</code> 至 <code>SYNC4</code>,只应由同步适配器使用。 + +通用列名称常量始终有效,与行包含的数据类型无关。 + +</p> +<p> + <code>DATA1</code> 列为索引列。联系人提供程序总是在此列中存储其预期会成为最频繁查询目标的数据。 +例如,在一个电子邮件行中,此列包含实际电子邮件地址。 + +</p> +<p> + 按照惯例,<code>DATA15</code> 为预留列,用于存储照片缩略图等二进制大型对象 +(BLOB) 数据。 +</p> +<h3 id="TypeSpecificNames">类型专用列名称</h3> +<p> + 为便于处理特定类型行的列,联系人提供程序还提供了 +{@link android.provider.ContactsContract.CommonDataKinds} 子类中定义的类型专用列名称常量。 +这些常量只是为同一列名称提供不同的常量名称,这有助于您访问特定类型行中的数据。 + + +</p> +<p> + 例如,{@link android.provider.ContactsContract.CommonDataKinds.Email} 类为 {@link android.provider.ContactsContract.Data} 行定义类型专用列名称常量,该行的 MIME 类型为 {@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE}。 + + +该类包含电子邮件地址列的 +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} +常量。 +{@link android.provider.ContactsContract.CommonDataKinds.Email#ADDRESS} 的实际值为“data1”,这与列的通用名称相同。 + +</p> +<p class="caution"> + <strong>注意:</strong>请勿使用具有提供程序某个预定义 MIME 类型的行向 +{@link android.provider.ContactsContract.Data} 表中添加您自己的自定义数据。 +否则您可能会丢失数据,或导致提供程序发生故障。 +例如,如果某一行具有 MIME 类型 +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_ITEM_TYPE + Email.CONTENT_ITEM_TYPE},并且 +<code>DATA1</code> 列包含的是用户名而不是电子邮件地址,您就不应添加该行。如果您为该行使用自定义的 MIME 类型,则可自由定义您的自定义类型专用的列名称,并随心所欲地使用这些列。 + +</p> +<p> + 图 2 显示的是描述性列和数据列在 +{@link android.provider.ContactsContract.Data} 行中的显示情况,以及类型专用列名称“覆盖”通用列名称的情况 + +</p> +<img src="{@docRoot}images/providers/data_columns.png" alt="How type-specific column names map to generic column names" height="311" id="figure2" /> +<p class="img-caption"> + <strong>图 2. </strong>类型专用列名称和通用列名称。 +</p> +<h3 id="ColumnMaps">类型专用列名称类</h3> +<p> + 表 2 列出了最常用的类型专用列名称类: +</p> +<p class="table-caption" id="table2"> + <strong>表 2. </strong>类型专用列名称类</p> +<table> + <tr> + <th scope="col">映射类</th> + <th scope="col">数据类型</th> + <th scope="col">备注</th> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.StructuredName}</td> + <td>与该数据行关联的原始联系人的姓名数据。</td> + <td>一位原始联系人只有其中一行。</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.Photo}</td> + <td>与该数据行关联的原始联系人的主要照片。</td> + <td>一位原始联系人只有其中一行。</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.Email}</td> + <td>与该数据行关联的原始联系人的电子邮件地址。</td> + <td>一位原始联系人可有多个电子邮件地址。</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal}</td> + <td>与该数据行关联的原始联系人的邮政地址。</td> + <td>一位原始联系人可有多个邮政地址。</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership}</td> + <td>将原始联系人链接到联系人提供程序内其中一组的标识符。</td> + <td> + 组是帐户类型和帐户名称的一项可选功能。<a href="#Groups">联系人组</a>部分对其做了更详尽的描述。 + + </td> + </tr> +</table> +<h3 id="ContactBasics">联系人</h3> +<p> + 联系人提供程序通过将所有帐户类型和帐户名称的原始联系人行合并来形成<strong>联系人</strong>。 +这可以为显示和修改用户针对某一联系人收集的所有数据提供便利。 +联系人提供程序管理新联系人行的创建,以及原始联系人与现有联系人行的合并。 +系统不允许应用或同步适配器添加联系人,并且联系人行中的某些列是只读列。 + +</p> +<p class="note"> + <strong>注:</strong>如果您试图通过 +{@link android.content.ContentResolver#insert(Uri,ContentValues) insert()} 向联系人提供程序添加联系人,会引发一个 {@link java.lang.UnsupportedOperationException} 异常。 +如果您试图更新一个列为“只读”的列,更新会被忽略。 + +</p> +<p> + 如果添加的新原始联系人不匹配任何现有联系人,联系人提供程序会相应地创建新联系人。 +如果某个现有原始联系人的数据发生了变化,不再匹配其之前关联的联系人,则提供程序也会执行此操作。 + +如果应用或同步适配器创建的新原始联系人“的确”<em></em>匹配某位现有联系人,则新原始联系人将与现有联系人合并。 + + +</p> +<p> + 联系人提供程序通过 {@link android.provider.ContactsContract.Contacts Contacts} 表中联系人行的 +<code>_ID</code> 列将联系人行与其各原始联系人行链接起来。 +原始联系人表 {@link android.provider.ContactsContract.RawContacts} 的 <code>CONTACT_ID</code> 列包含对应于每个原始联系人行所关联联系人行的 <code>_ID</code> 值。 + + +</p> +<p> + {@link android.provider.ContactsContract.Contacts} 表还有一个 +{@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 列,它是一个指向联系人行的“永久性”链接。 +由于联系人提供程序会自动维护联系人,因此可能会在合并或同步时相应地更改联系人行的 {@link android.provider.BaseColumns#_ID} 值。 + +即使发生这种情况,合并了联系人 +{@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 的内容 URI +{@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI} 仍将指向联系人行,这样,您就能使用 +{@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} +保持指向“最喜爱”联系人的链接,以及执行其他操作。 +该列具有其自己的格式,与 {@link android.provider.BaseColumns#_ID} 列的格式无关。 + +</p> +<p> + 图 3 显示的是这三个主要表的相互关系。 +</p> +<img src="{@docRoot}images/providers/contacts_tables.png" alt="Contacts provider main tables" height="514" id="figure4" /> +<p class="img-caption"> + <strong>图 3. </strong>联系人表、原始联系人表与详细信息表之间的关系。 +</p> +<h2 id="Sources">来自同步适配器的数据</h2> +<p> + 虽然用户是直接将联系人数据输入到设备中,但这些数据也会通过<strong>同步适配器</strong>从 Web 服务流入联系人提供程序中,这些同步适配器可自动化设备与服务之间的数据传送。 + +同步适配器在系统控制下在后台运行,它们会调用 {@link android.content.ContentResolver} 方法来管理数据。 + + +</p> +<p> + 在 Android 中,与同步适配器协作的 Web 服务通过帐户类型加以标识。 + 每个同步适配器都与一个帐户类型协作,但它可以支持该类型的多个帐户名称。 +<a href="#RawContactsExample">原始联系人数据来源</a>部分对帐户类型和帐户名称做了简要描述。 +下列定义提供了更多详细信息,并描述了帐户类型及帐户名称与同步适配器及服务之间的关系。 + +</p> +<dl> + <dt> + 帐户类型 + </dt> + <dd> + 表示用户在其中存储数据的服务。在大多数时候,用户需要向服务验证身份。 +例如,Google Contacts 是一个以代码 <code>google.com</code> 标识的帐户类型。 +该值对应于 +{@link android.accounts.AccountManager} 使用的帐户类型。 + </dd> + <dt> + 帐户名称 + </dt> + <dd> + 表示某个帐户类型的特定帐户或登录名。Google Contacts 帐户与 Google 帐户相同,都是以电子邮件地址作为帐户名称。 + + 其他服务可能使用一个单词的用户名或数字 ID。 + </dd> +</dl> +<p> + 帐户类型不必具有唯一性。用户可以配置多个 Google Contacts 帐户并将它们的数据下载到联系人提供程序;如果用户为个人帐户名称和工作帐户名称分别设置了一组联系人,就可能发生这种情况。 + +帐户名称通常具有唯一性。 +它们共同标识联系人提供程序与外部服务之间的特定数据流。 + +</p> +<p> + 如果您想将服务的数据传送到联系人提供程序,则需编写您自己的同步适配器。 +<a href="#SyncAdapters">联系人提供程序同步适配器</a>部分对此做了更详尽的描述。 + +</p> +<p> + 图 4 显示的是联系人提供程序如何融入联系人数据的流动。 +在名为“同步适配器”的方框中,每个适配器都以其帐户类型命名。 +</p> +<img src="{@docRoot}images/providers/ContactsDataFlow.png" alt="Flow of data about people" height="252" id="figure5" /> +<p class="img-caption"> + <strong>图 4. </strong>联系人提供程序数据流。 +</p> +<h2 id="Permissions">所需权限</h2> +<p> + 想要访问联系人提供程序的应用必须请求以下权限: + +</p> +<dl> + <dt>对一个或多个表的读取权限</dt> + <dd> + {@link android.Manifest.permission#READ_CONTACTS},在 +<code>AndroidManifest.xml</code> 中指定,使用 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> 元素作为 +<code><uses-permission android:name="android.permission.READ_CONTACTS"></code>。 + </dd> + <dt>对一个或多个表的写入权限</dt> + <dd> + {@link android.Manifest.permission#WRITE_CONTACTS},在 +<code>AndroidManifest.xml</code> 中指定,使用 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"> + <uses-permission></a></code> 元素作为 +<code><uses-permission android:name="android.permission.WRITE_CONTACTS"></code>。 + </dd> +</dl> +<p> + 这些权限不适用于用户个人资料数据。下面的<a href="#UserProfile">用户个人资料</a>部分对用户个人资料及其所需权限做了阐述。 + + +</p> +<p> + 请切记,用户的联系人数据属于个人敏感数据。用户关心其隐私权,因此不希望应用收集有关其自身的数据或其联系人的数据。 + + 如需权限来访问其联系人数据的理由并不充分,用户可能给您的应用作出差评或干脆拒绝安装。 + +</p> +<h2 id="UserProfile">用户个人资料</h2> +<p> + {@link android.provider.ContactsContract.Contacts} 表有一行包含设备用户的个人资料数据。 +这些数据描述设备的 <code>user</code> 而不是用户的其中一位联系人。 +对于每个使用个人资料的系统,该个人资料联系人行都链接到某个原始联系人行。 + + 每个个人资料原始联系人行可具有多个数据行。{@link android.provider.ContactsContract.Profile} 类中提供了用于访问用户个人资料的常量。 + +</p> +<p> + 访问用户个人资料需要特殊权限。除了进行读取和写入所需的 +{@link android.Manifest.permission#READ_CONTACTS} 和 +{@link android.Manifest.permission#WRITE_CONTACTS} 权限外,如果想访问用户个人资料,还分别需要 {@link android.Manifest.permission#READ_PROFILE} 和 +{@link android.Manifest.permission#WRITE_PROFILE} 权限进行读取和写入访问。 + + +</p> +<p> + 请切记,您应该将用户的个人资料视为敏感数据。{@link android.Manifest.permission#READ_PROFILE} 权限让您可以访问设备用户的个人身份识别数据。 + +请务必在您的应用的描述中告知用户您需要用户个人资料访问权限的原因。 + +</p> +<p> + 要检索包含用户个人资料的联系人行,请调用 {@link android.content.ContentResolver#query(Uri,String[], String, String[], String) +ContentResolver.query()}。 +将内容 URI 设置为 +{@link android.provider.ContactsContract.Profile#CONTENT_URI} 并且不要提供任何选择条件。 +您还可以使用该内容 URI 作为检索原始联系人或个人资料数据的基本 URI。 +例如,以下代码段用于检索个人资料数据: +</p> +<pre> +// Sets the columns to retrieve for the user profile +mProjection = new String[] + { + Profile._ID, + Profile.DISPLAY_NAME_PRIMARY, + Profile.LOOKUP_KEY, + Profile.PHOTO_THUMBNAIL_URI + }; + +// Retrieves the profile from the Contacts Provider +mProfileCursor = + getContentResolver().query( + Profile.CONTENT_URI, + mProjection , + null, + null, + null); +</pre> +<p class="note"> + <strong>注:</strong>如果您要检索多个联系人行并想要确定其中一个是否为用户个人资料,请测试该行的 +{@link android.provider.ContactsContract.ContactsColumns#IS_USER_PROFILE} 列。 +如果该联系人是用户个人资料,则此列设置为“1”。 + +</p> +<h2 id="ContactsProviderMetadata">联系人提供程序元数据</h2> +<p> + 联系人提供程序管理用于追踪存储库中联系人数据状态的数据。 +这些有关存储库的元数据存储在各处,其中包括原始联系人表行、数据表行和联系人表行、 +{@link android.provider.ContactsContract.Settings} 表以及 +{@link android.provider.ContactsContract.SyncState} 表。 +下表显示的是每一部分元数据的作用: + +</p> +<p class="table-caption" id="table3"> + <strong>表 3. </strong>联系人提供程序中的元数据</p> +<table> + <tr> + <th scope="col">表</th> + <th scope="col">列</th> + <th scope="col">值</th> + <th scope="col">含义</th> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.RawContacts}</td> + <td rowspan="2">{@link android.provider.ContactsContract.SyncColumns#DIRTY}</td> + <td>“0”:上次同步以来未发生变化。</td> + <td rowspan="2"> + 标记设备上因发生变化而需要同步回服务器的原始联系人。 +当 Android 应用更新行时,联系人提供程序会自动设置该值。 + + <p> + 修改原始联系人表或数据表的同步适配器应始终向他们使用的内容 URI 追加字符串 {@link android.provider.ContactsContract#CALLER_IS_SYNCADAPTER}。 + +这可以防止提供程序将行标记为已更新。 + 否则,即使服务器是修改的来源,同步适配器修改仍显示为本地修改,并会发送到服务器。 + + </p> + </td> + </tr> + <tr> + <td>“1”:上次同步以来发生了变化,需要同步回服务器。</td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.RawContacts}</td> + <td>{@link android.provider.ContactsContract.SyncColumns#VERSION}</td> + <td>此行的版本号。</td> + <td> + 每当行或其相关数据发生变化时,联系人提供程序都会自动增加此值。 + + </td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.Data}</td> + <td>{@link android.provider.ContactsContract.DataColumns#DATA_VERSION}</td> + <td>此行的版本号。</td> + <td> + 每当数据行发生变化时,联系人提供程序都会自动增加此值。 + + </td> + </tr> + <tr> + <td>{@link android.provider.ContactsContract.RawContacts}</td> + <td>{@link android.provider.ContactsContract.SyncColumns#SOURCE_ID}</td> + <td> + 一个字符串值,用于在创建此原始联系人的帐户中对该联系人进行唯一标识。 + + </td> + <td> + 当同步适配器创建新原始联系人时,此列应设置为该原始联系人在服务器中的唯一 ID。 +当 Android 应用创建新原始联系人时,应将此列留空。 +这是为了向同步适配器表明,它应该在服务器上创建新原始联系人,并获取 + {@link android.provider.ContactsContract.SyncColumns#SOURCE_ID} 的值。 + + <p> + 具体地讲,对于每个帐户类型,该源 ID 都必须是<strong>唯一</strong>的,并且应在所有同步中保持稳定: + + </p> + <ul> + <li> + 唯一:帐户的每个原始联系人都必须有自己的源 ID。如果您不强制执行此要求,会在联系人应用中引发问题。 + + 请注意,帐户<em>类型</em>相同的两个原始联系人可以具有相同的源 ID。 +例如,允许帐户 {@code emily.dickinson@gmail.com} 的原始联系人“Thomas Higginson”与帐户 +{@code emilyd@gmail.com} 的原始联系人“Thomas Higginson”具有相同的源 ID。 + + + </li> + <li> + 稳定:源 ID 是该原始联系人在在线服务中的数据的永久性组成部分。 +例如,如果用户从应用设置中清除存储的联系人数据并重新同步,则恢复的原始联系人的源 ID 应与以前相同。 + +如果您不强制执行此要求,快捷方式将停止工作。 + + </li> + </ul> + </td> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.Groups}</td> + <td rowspan="2">{@link android.provider.ContactsContract.GroupsColumns#GROUP_VISIBLE}</td> + <td>“0”:此组中的联系人在 Android 应用 UI 中不应处于可见状态。</td> + <td> + 此列用于兼容那些允许用户隐藏特定组中联系人的服务器。 + + </td> + </tr> + <tr> + <td>“1”:系统允许此组中的联系人在应用 UI 中处于可见状态。</td> + </tr> + <tr> + <td rowspan="2">{@link android.provider.ContactsContract.Settings}</td> + <td rowspan="2"> + {@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE}</td> + <td> + “0”:对于此帐户和帐户类型,未归入组的联系人在 Android 应用 UI 中处于不可见状态。 + + </td> + <td rowspan="2"> + 默认情况下,如果联系人的所有原始联系人都未归入组,则它们将处于不可见状态(原始联系人的组成员身份通过 {@link android.provider.ContactsContract.Data} 表中的一个或多个 +{@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} 行指示)。 + + + 通过在 {@link android.provider.ContactsContract.Settings} 表行中为帐户类型和帐户设置此标志,您可以强制未归入组的联系人处于可见状态。 + + 此标志的一个用途是显示不使用组的服务器上的联系人。 + </td> + </tr> + <tr> + <td> + “1”:对于此帐户和帐户类型,未归入组的联系人在应用 UI 中处于可见状态。 + + </td> + + </tr> + <tr> + <td>{@link android.provider.ContactsContract.SyncState}</td> + <td>(所有列)</td> + <td> + 此表用于存储同步适配器的元数据。 + </td> + <td> + 利用此表,您可以将同步状态及其他同步相关数据持久地存储在设备中。 + + </td> + </tr> +</table> +<h2 id="Access">联系人提供程序访问</h2> +<p> + 本节描述访问联系人提供程序中数据的准则,侧重于阐述以下内容: + +</p> +<ul> + <li> + 实体查询。 + </li> + <li> + 批量修改。 + </li> + <li> + 通过 Intent 执行检索和修改。 + </li> + <li> + 数据完整性。 + </li> +</ul> +<p> + <a href="#SyncAdapters">联系人提供程序同步适配器</a>部分也对通过同步适配器进行修改做了更详尽的阐述。 + +</p> +<h3 id="Entities">查询实体</h3> +<p> + 由于联系人提供程序表是以层级形式组织,因此对于检索某一行以及与其链接的所有“子”行,往往很有帮助。 +例如,要想显示某位联系人的所有信息,您可能需要检索某个 +{@link android.provider.ContactsContract.Contacts} 行的所有{@link android.provider.ContactsContract.RawContacts} 行,或者检索某个 +{@link android.provider.ContactsContract.RawContacts} 行的所有 +{@link android.provider.ContactsContract.CommonDataKinds.Email} 行。 + +为便于执行此操作,联系人提供程序提供了<strong>实体</strong>构造,其作用类似于表间的数据库连接。 + + +</p> +<p> + 实体类似于一个表,由父表及其子表中的选定列组成。 + 当您查询实体时,需要根据实体中的可用列提供投影和搜索条件。 +结果会得到一个 {@link android.database.Cursor},检索的每个子表行在其中都有一行与之对应。 +例如,如果您在 +{@link android.provider.ContactsContract.Contacts.Entity} 中查询某个联系人姓名以及该姓名所有原始联系人的所有 {@link android.provider.ContactsContract.CommonDataKinds.Email} 行,您会获得一个 {@link android.database.Cursor},每个 {@link android.provider.ContactsContract.CommonDataKinds.Email} 行在其中都有一行与之对应。 + + + +</p> +<p> + 实体简化了查询。使用实体时,您可以一次性检索联系人或原始联系人的所有联系人数据,而不必先通过查询父表获得ID,然后通过该 ID 查询子表。此外,联系人提供程序可通过单一事务处理实体查询,这确保了所检索数据的内部一致性。 + + + + +</p> +<p class="note"> + <strong>注:</strong>实体通常不包含父表和子表的所有列。 +如果您试图使用的列名称并未出现在实体的列名称常量列表中,则会引发一个 {@link java.lang.Exception}。 + +</p> +<p> + 以下代码段说明如何检索某位联系人的所有原始联系人行。该代码段是一个大型应用的组成部分,包含“主”和“详”两个 Activity。 +主 Activity 显示一个联系人行列表;当用户选择一行时,该 Activity 会将其 ID 发送至详 Activity。 + +详 Activity 使用 {@link android.provider.ContactsContract.Contacts.Entity} 显示与所选联系人关联的所有原始联系人中的所有数据行。 + + +</p> +<p> + 以下代码段摘自“detail”Activity: +</p> +<pre> +... + /* + * Appends the entity path to the URI. In the case of the Contacts Provider, the + * expected URI is content://com.google.contacts/#/entity (# is the ID value). + */ + mContactUri = Uri.withAppendedPath( + mContactUri, + ContactsContract.Contacts.Entity.CONTENT_DIRECTORY); + + // Initializes the loader identified by LOADER_ID. + getLoaderManager().initLoader( + LOADER_ID, // The identifier of the loader to initialize + null, // Arguments for the loader (in this case, none) + this); // The context of the activity + + // Creates a new cursor adapter to attach to the list view + mCursorAdapter = new SimpleCursorAdapter( + this, // the context of the activity + R.layout.detail_list_item, // the view item containing the detail widgets + mCursor, // the backing cursor + mFromColumns, // the columns in the cursor that provide the data + mToViews, // the views in the view item that display the data + 0); // flags + + // Sets the ListView's backing adapter. + mRawContactList.setAdapter(mCursorAdapter); +... +@Override +public Loader<Cursor> onCreateLoader(int id, Bundle args) { + + /* + * Sets the columns to retrieve. + * RAW_CONTACT_ID is included to identify the raw contact associated with the data row. + * DATA1 contains the first column in the data row (usually the most important one). + * MIMETYPE indicates the type of data in the data row. + */ + String[] projection = + { + ContactsContract.Contacts.Entity.RAW_CONTACT_ID, + ContactsContract.Contacts.Entity.DATA1, + ContactsContract.Contacts.Entity.MIMETYPE + }; + + /* + * Sorts the retrieved cursor by raw contact id, to keep all data rows for a single raw + * contact collated together. + */ + String sortOrder = + ContactsContract.Contacts.Entity.RAW_CONTACT_ID + + " ASC"; + + /* + * Returns a new CursorLoader. The arguments are similar to + * ContentResolver.query(), except for the Context argument, which supplies the location of + * the ContentResolver to use. + */ + return new CursorLoader( + getApplicationContext(), // The activity's context + mContactUri, // The entity content URI for a single contact + projection, // The columns to retrieve + null, // Retrieve all the raw contacts and their data rows. + null, // + sortOrder); // Sort by the raw contact ID. +} +</pre> +<p> + 加载完成时,{@link android.app.LoaderManager} 会调用一个 +{@link android.app.LoaderManager.LoaderCallbacks#onLoadFinished(Loader, D) +onLoadFinished()} 回调。此方法的传入参数之一是一个 +{@link android.database.Cursor},其中包含查询的结果。在您自己的应用中,您可以从该 {@link android.database.Cursor} 获取数据,以进行显示或做进一步处理。 + +</p> +<h3 id="Transactions">批量修改</h3> +<p> + 您应尽可能地通过创建一个 {@link android.content.ContentProviderOperation} 对象 {@link java.util.ArrayList} +并调用 {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()},以“批处理模式”在联系人提供程序中插入、更新和删除数据。 + +由于联系人提供程序是在 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 中通过单一事务执行所有操作,因此您的修改绝不会使联系人存储库出现不一致问题。 + + +此外,批量修改还有便于同时插入原始联系人及其明细数据。 + +</p> +<p class="note"> + <strong>注:</strong>要修改<em>单个</em>原始联系人,可以考虑向设备的联系人应用发送一个 Intent,而不是在您的应用中处理修改。<a href="#Intents">通过 Intent 执行检索和修改</a>部分对此操作做了更详尽的描述。 + + + +</p> +<h4>屈服点</h4> +<p> + 一个包含大量操作的批量修改可能会阻断其他进程,导致糟糕的总体用户体验。 +要将您想执行的所有修改组织到尽可能少的单独列表中,同时防止它们阻断系统,则应为一项或多项操作设置<strong>屈服点</strong>。 + + + 屈服点是一个 {@link android.content.ContentProviderOperation} 对象,其 +{@link android.content.ContentProviderOperation#isYieldAllowed()} 值设置为 +<code>true</code>。当联系人提供程序遇到屈服点时,它会暂停其工作,让其他进程运行,并关闭当前事务。 +当提供程序再次启动时,它会继续执行 {@link java.util.ArrayList} 中的下一项操作,并启动一个新的事务。 + + +</p> +<p> + 屈服点会导致每次调用 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 会产生多个事务。因此,您应该为针对一组相关行的最后一项操作设置屈服点。 + + 例如,您应该为一组操作中添加原始联系人行及其关联数据行的最后一项操作,或者针对一组与一位联系人相关的行的最后一项操作设置屈服点。 + + +</p> +<p> + 屈服点也是一个原子操作单元。两个屈服点之间所有访问的成功或失败都将以一个单元的形式出现。 +如果您不设置任何屈服点,则最小的原子操作是整个批量操作。 +如果您使用了屈服点,则可以防止操作降低系统性能,还可确保一部分操作是原子操作。 + + +</p> +<h4>修改向后引用</h4> +<p> + 当您将一个新原始联系人行及其关联的数据行作为一组 +{@link android.content.ContentProviderOperation} 对象插入时,需要通过将原始联系人的 +{@link android.provider.BaseColumns#_ID} 值作为 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} 值插入,将数据行链接到原始联系人行。 +不过,当您为数据行创建 +{@link android.content.ContentProviderOperation} 时,该值不可用,因为您尚未对原始联系人行应用 +{@link android.content.ContentProviderOperation}。 +为解决此问题, +{@link android.content.ContentProviderOperation.Builder} 类使用了 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} 方法。 + 该方法让您可以插入或修改包含上一操作结果的列。 + +</p> +<p> + {@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +方法具有两个参数: +</p> + <dl> + <dt> + <code>key</code> + </dt> + <dd> + 键-值对的键。此参数的值应为您要修改的表中某一列的名称。 + + </dd> + <dt> + <code>previousResult</code> + </dt> + <dd> + {@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 中 +{@link android.content.ContentProviderResult} 对象数组内某一值以 0 开始的索引。 +应用批处理操作时,每个操作的结果都存储在一个中间结果数组内。 + +<code>previousResult</code> 值是其中一个结果的索引,它通过 <code>key</code> +值进行检索和存储。 +这样,您就可以插入一条新的原始联系人记录,并取回其 +{@link android.provider.BaseColumns#_ID} 值,然后在添加 {@link android.provider.ContactsContract.Data} 行时“向后引用”该值。 + + <p> + 系统会在您首次调用 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 时创建整个结果数组,其大小与您提供的 {@link android.content.ContentProviderOperation} 对象的 {@link java.util.ArrayList} 大小相等。 + +不过,结果数组中的所有元素都设置为 <code>null</code>,如果您试图向后引用某个尚未应用的操作的结果, +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +会引发一个 {@link java.lang.Exception}。 + + + + </p> + </dd> + </dl> +<p> + 以下代码段说明如何批量插入新原始联系人和数据。代码段中包括用于建立屈服点和使用向后引用的代码。 +这些代码段是扩展版本的 <code>createContacEntry()</code> 方法,该方法是 <code><a href="{@docRoot}resources/samples/ContactManager/index.html"> + Contact Manager</a></code> 示例应用中 <code>ContactAdder</code> 类的组成部分。 + + + +</p> +<p> + 第一个代码段用于检索 UI 中的联系人数据。此时,用户已经选择了应添加新原始联系人的帐户。 + +</p> +<pre> +// Creates a contact entry from the current UI values, using the currently-selected account. +protected void createContactEntry() { + /* + * Gets values from the UI + */ + String name = mContactNameEditText.getText().toString(); + String phone = mContactPhoneEditText.getText().toString(); + String email = mContactEmailEditText.getText().toString(); + + int phoneType = mContactPhoneTypes.get( + mContactPhoneTypeSpinner.getSelectedItemPosition()); + + int emailType = mContactEmailTypes.get( + mContactEmailTypeSpinner.getSelectedItemPosition()); +</pre> +<p> + 下一个代码段用于创建将该原始联系人行插入 +{@link android.provider.ContactsContract.RawContacts} 表的操作: +</p> +<pre> + /* + * Prepares the batch operation for inserting a new raw contact and its data. Even if + * the Contacts Provider does not have any data for this person, you can't add a Contact, + * only a raw contact. The Contacts Provider will then add a Contact automatically. + */ + + // Creates a new array of ContentProviderOperation objects. + ArrayList<ContentProviderOperation> ops = + new ArrayList<ContentProviderOperation>(); + + /* + * Creates a new raw contact with its account type (server type) and account name + * (user's account). Remember that the display name is not stored in this row, but in a + * StructuredName data row. No other data is required. + */ + ContentProviderOperation.Builder op = + ContentProviderOperation.newInsert(ContactsContract.RawContacts.CONTENT_URI) + .withValue(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()) + .withValue(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); +</pre> +<p> + 接着,代码会创建显示姓名行、电话行和电子邮件行的数据行。 +</p> +<p> + 每个操作生成器对象都使用 +{@link android.content.ContentProviderOperation.Builder#withValueBackReference(String, int) withValueBackReference()} +来获取 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID}。引用指回来自第一次操作的 {@link android.content.ContentProviderResult} 对象,第一次操作就是添加原始联系人行并返回其新 {@link android.provider.BaseColumns#_ID} +值。 + +结果是,每个数据行都通过其 +{@link android.provider.ContactsContract.DataColumns#RAW_CONTACT_ID} +自动链接到其所属的 {@link android.provider.ContactsContract.RawContacts} 行。 +</p> +<p> + 添加电子邮件行的 {@link android.content.ContentProviderOperation.Builder} 对象带有 +{@link android.content.ContentProviderOperation.Builder#withYieldAllowed(boolean) +withYieldAllowed()} 标志,用于设置屈服点: +</p> +<pre> + // Creates the display name for the new raw contact, as a StructuredName data row. + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * withValueBackReference sets the value of the first argument to the value of + * the ContentProviderResult indexed by the second argument. In this particular + * call, the raw contact ID column of the StructuredName data row is set to the + * value of the result returned by the first operation, which is the one that + * actually adds the raw contact row. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to StructuredName + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.StructuredName.CONTENT_ITEM_TYPE) + + // Sets the data row's display name to the name in the UI. + .withValue(ContactsContract.CommonDataKinds.StructuredName.DISPLAY_NAME, name); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); + + // Inserts the specified phone number and type as a Phone data row + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * Sets the value of the raw contact id column to the new raw contact ID returned + * by the first operation in the batch. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to Phone + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE) + + // Sets the phone number and type + .withValue(ContactsContract.CommonDataKinds.Phone.NUMBER, phone) + .withValue(ContactsContract.CommonDataKinds.Phone.TYPE, phoneType); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); + + // Inserts the specified email and type as a Phone data row + op = + ContentProviderOperation.newInsert(ContactsContract.Data.CONTENT_URI) + /* + * Sets the value of the raw contact id column to the new raw contact ID returned + * by the first operation in the batch. + */ + .withValueBackReference(ContactsContract.Data.RAW_CONTACT_ID, 0) + + // Sets the data row's MIME type to Email + .withValue(ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE) + + // Sets the email address and type + .withValue(ContactsContract.CommonDataKinds.Email.ADDRESS, email) + .withValue(ContactsContract.CommonDataKinds.Email.TYPE, emailType); + + /* + * Demonstrates a yield point. At the end of this insert, the batch operation's thread + * will yield priority to other threads. Use after every set of operations that affect a + * single contact, to avoid degrading performance. + */ + op.withYieldAllowed(true); + + // Builds the operation and adds it to the array of operations + ops.add(op.build()); +</pre> +<p> + 最后一个代码段显示的是 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 调用,用于插入新原始联系人行和数据行。 + +</p> +<pre> + // Ask the Contacts Provider to create a new contact + Log.d(TAG,"Selected account: " + mSelectedAccount.getName() + " (" + + mSelectedAccount.getType() + ")"); + Log.d(TAG,"Creating contact: " + name); + + /* + * Applies the array of ContentProviderOperation objects in batch. The results are + * discarded. + */ + try { + + getContentResolver().applyBatch(ContactsContract.AUTHORITY, ops); + } catch (Exception e) { + + // Display a warning + Context ctx = getApplicationContext(); + + CharSequence txt = getString(R.string.contactCreationFailure); + int duration = Toast.LENGTH_SHORT; + Toast toast = Toast.makeText(ctx, txt, duration); + toast.show(); + + // Log exception + Log.e(TAG, "Exception encountered while inserting contact: " + e); + } +} +</pre> +<p> + 此外,您还可以利用批处理操作实现<strong>乐观并发控制</strong>,这是一种无需锁定底层存储库便可应用修改事务的控制方法。 + + 要使用此方法,您需要应用事务,然后检查是否存在可能已同时做出的其他修改。 +如果您发现了不一致的修改,请回滚事务并重试。 + +</p> +<p> + 乐观并发控制对于移动设备很有用,因为在移动设备上,同一时间只有一位用户,并且同时访问数据存储库的情况很少见。 +由于未使用锁定功能,因此不用浪费时间设置锁定或等待其他事务解除锁定。 + +</p> +<p> + 要在更新某个 +{@link android.provider.ContactsContract.RawContacts} 行时使用乐观并发控制,请按以下步骤操作: +</p> +<ol> + <li> + 检索原始联系人的 {@link android.provider.ContactsContract.SyncColumns#VERSION} +列以及要检索的其他数据。 + </li> + <li> + 创建一个适合使用 +{@link android.content.ContentProviderOperation#newAssertQuery(Uri)} 方法强制执行约束 +的 {@link android.content.ContentProviderOperation.Builder} 对象。对于内容 URI,请使用追加有原始联系人 {@link android.provider.BaseColumns#_ID} 的 {@link android.provider.ContactsContract.RawContacts#CONTENT_URI +RawContacts.CONTENT_URI} +。 + + </li> + <li> + 对于 {@link android.content.ContentProviderOperation.Builder} 对象,请调用 +{@link android.content.ContentProviderOperation.Builder#withValue(String, Object) +withValue()},对 {@link android.provider.ContactsContract.SyncColumns#VERSION} +列与您刚检索的版本号进行比较。 + </li> + <li> + 对于同一 {@link android.content.ContentProviderOperation.Builder},请调用 +{@link android.content.ContentProviderOperation.Builder#withExpectedCount(int) +withExpectedCount()},确保此断言只对一行进行测试。 + </li> + <li> + 调用 {@link android.content.ContentProviderOperation.Builder#build()} 创建 +{@link android.content.ContentProviderOperation} 对象,然后将此对象添加为要传递至 +{@link android.content.ContentResolver#applyBatch(String, ArrayList) applyBatch()} 的 {@link java.util.ArrayList} 中的第一个对象。 + + </li> + <li> + 应用批处理事务。 + </li> +</ol> +<p> + 如果在您读取原始联系人行到您试图对其进行修改这段时间有另一项操作更新了该行,“断言”{@link android.content.ContentProviderOperation} +将会失败,系统将终止整个批处理操作。 +此情况下,您可以选择重新执行批处理操作,或执行其他某操作。 + +</p> +<p> + 以下代码段演示如何在使用 {@link android.content.CursorLoader} 查询一位原始联系人后创建一个“断言” +{@link android.content.ContentProviderOperation}: + +</p> +<pre> +/* + * The application uses CursorLoader to query the raw contacts table. The system calls this method + * when the load is finished. + */ +public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { + + // Gets the raw contact's _ID and VERSION values + mRawContactID = cursor.getLong(cursor.getColumnIndex(BaseColumns._ID)); + mVersion = cursor.getInt(cursor.getColumnIndex(SyncColumns.VERSION)); +} + +... + +// Sets up a Uri for the assert operation +Uri rawContactUri = ContentUris.withAppendedId(RawContacts.CONTENT_URI, mRawContactID); + +// Creates a builder for the assert operation +ContentProviderOperation.Builder assertOp = ContentProviderOperation.netAssertQuery(rawContactUri); + +// Adds the assertions to the assert operation: checks the version and count of rows tested +assertOp.withValue(SyncColumns.VERSION, mVersion); +assertOp.withExpectedCount(1); + +// Creates an ArrayList to hold the ContentProviderOperation objects +ArrayList ops = new ArrayList<ContentProviderOperationg>; + +ops.add(assertOp.build()); + +// You would add the rest of your batch operations to "ops" here + +... + +// Applies the batch. If the assert fails, an Exception is thrown +try + { + ContentProviderResult[] results = + getContentResolver().applyBatch(AUTHORITY, ops); + + } catch (OperationApplicationException e) { + + // Actions you want to take if the assert operation fails go here + } +</pre> +<h3 id="Intents">通过 Intent 执行检索和修改</h3> +<p> + 通过向设备的联系人应用发送 Intent,您可以间接访问联系人提供程序。 + Intent 会启动设备的联系人应用 UI,用户可以在其中执行与联系人有关的操作。 +通过这种访问方式,用户可以: + <ul> + <li>从列表中选取一位联系人并将其返回给您的应用以执行进一步操作。</li> + <li>编辑现有联系人的数据。</li> + <li>为其任一帐户插入新原始联系人。</li> + <li>删除联系人或联系人数据。</li> + </ul> +<p> + 如果用户要插入或更新数据,您可以先收集数据,然后将其作为 Intent 的一部分发送。 + +</p> +<p> + 当您使用 Intent 通过设备的联系人应用访问联系人提供程序时,您无需自行编写用于访问该提供程序的 UI 或代码。 +您也无需请求对提供程序的读取或写入权限。 +设备的联系人应用可以将联系人读取权限授予给您,而且您是通过另一个应用对该提供程序进行修改,不需要拥有写入权限。 + + +</p> +<p> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">内容提供程序基础知识</a>指南“通过 Intent 访问数据”部分详细描述了通过发送 Intent 来访问某提供程序的一般过程。 + +表 4 汇总了您为可用任务使用的操作、MIME 类型以及数据值,{@link android.provider.ContactsContract.Intents.Insert} 参考文档列出了您可用于{@link android.content.Intent#putExtra(String, String) putExtra()} 的 Extra 值: + + + + +</p> +<p class="table-caption" id="table4"> + <strong>表 4. </strong>联系人提供程序 Intent。 +</p> +<table style="width:75%"> + <tr> + <th scope="col" style="width:10%">任务</th> + <th scope="col" style="width:5%">操作</th> + <th scope="col" style="width:10%">数据</th> + <th scope="col" style="width:10%">MIME 类型</th> + <th scope="col" style="width:25%">备注</th> + </tr> + <tr> + <td><strong>从列表中选取一位联系人</strong></td> + <td>{@link android.content.Intent#ACTION_PICK}</td> + <td> + 下列值之一: + <ul> + <li> +{@link android.provider.ContactsContract.Contacts#CONTENT_URI Contacts.CONTENT_URI},显示联系人列表。 + + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.Phone#CONTENT_URI Phone.CONTENT_URI},显示原始联系人的电话号码列表。 + + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.StructuredPostal#CONTENT_URI +StructuredPostal.CONTENT_URI},显示原始联系人的邮政地址列表。 + + </li> + <li> +{@link android.provider.ContactsContract.CommonDataKinds.Email#CONTENT_URI Email.CONTENT_URI},显示原始联系人的电子邮件地址列表。 + + </li> + </ul> + </td> + <td> + 未使用 + </td> + <td> + 显示原始联系人列表或一位原始联系人的数据列表,具体取决于您提供的内容 URI 类型。 + + <p> + 调用 + {@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} 方法,该方法返回所选行的内容 URI。 +该 URI 的形式为:追加有该行 <code>LOOKUP_ID</code> 的表的内容 URI。 + + 设备的联系人应用会在 Activity 的生命周期内将读取和写入权限授予给此内容 URI。 +如需了解更多详细信息,请参阅<a href="{@docRoot}guide/topics/providers/content-provider-basics.html">内容提供程序基础知识</a>指南。 + + + </p> + </td> + </tr> + <tr> + <td><strong>插入新原始联系人</strong></td> + <td>{@link android.provider.ContactsContract.Intents.Insert#ACTION Insert.ACTION}</td> + <td>不适用</td> + <td> + {@link android.provider.ContactsContract.RawContacts#CONTENT_TYPE +RawContacts.CONTENT_TYPE},用于一组原始联系人的 MIME 类型。 + </td> + <td> + 显示设备联系人应用的<strong>添加联系人</strong>屏幕。系统会显示您添加到 Intent 中的 Extra 值。 +如果是随 +{@link android.app.Activity#startActivityForResult(Intent, int) startActivityForResult()} + 发送,系统会将新添加的原始联系人的内容 URI 传回给 +{@link android.app.Activity#onActivityResult(int, int, Intent) onActivityResult()} +回调方法并作为后者 {@link android.content.Intent} 参数的“data”字段。 +要获取该值,请调用 {@link android.content.Intent#getData()}。 + </td> + </tr> + <tr> + <td><strong>编辑联系人</strong></td> + <td>{@link android.content.Intent#ACTION_EDIT}</td> + <td> + 该联系人的 {@link android.provider.ContactsContract.Contacts#CONTENT_LOOKUP_URI}。 +该编辑器 Activity 让用户能够对任何与该联系人关联的数据进行编辑。 + + </td> + <td> + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE +Contacts.CONTENT_ITEM_TYPE},一位联系人。</td> + <td> + 显示联系人应用中的“编辑联系人”屏幕。系统会显示您添加到 Intent 中的 Extra 值。 +当用户点击<strong>完成</strong>保存编辑时,您的 Activity 会返回前台。 + + </td> + </tr> + <tr> + <td><strong>显示一个同样可以添加数据的选取器。</strong></td> + <td>{@link android.content.Intent#ACTION_INSERT_OR_EDIT}</td> + <td> + 不适用 + </td> + <td> + {@link android.provider.ContactsContract.Contacts#CONTENT_ITEM_TYPE} + </td> + <td> + 此 Intent 始终显示联系人应用的选取器屏幕。用户可以选取要编辑的联系人,或添加新联系人。 +根据用户的选择,系统会显示编辑屏幕或添加屏幕,还会显示您使用 Intent 传递的 Extra 数据。 + +如果您的应用显示电子邮件或电话号码等联系人数据,请使用此 Intent 来允许用户向现有联系人添加数据。 + + + <p class="note"> + <strong>注:</strong>不需要通过此 Intent 的 Extra 发送姓名值,因为用户总是会选取现有姓名或添加新姓名。 +此外,如果您发送姓名,并且用户选择执行编辑操作,则联系人应用将显示您发送的姓名,该姓名将覆盖以前的值。 + +如果用户未注意这一情况便保存了编辑,原有值将会丢失。 + + </p> + </td> + </tr> +</table> +<p> + 设备的联系人应用不允许您使用 Intent 删除原始联系人或其任何数据。 +因此,要删除原始联系人,请使用 +{@link android.content.ContentResolver#delete(Uri, String, String[]) ContentResolver.delete()} +或 {@link android.content.ContentProviderOperation#newDelete(Uri) +ContentProviderOperation.newDelete()}。 +</p> +<p> + 以下代码段说明如何构建和发送一个插入新原始联系人和数据的 Intent: + +</p> +<pre> +// Gets values from the UI +String name = mContactNameEditText.getText().toString(); +String phone = mContactPhoneEditText.getText().toString(); +String email = mContactEmailEditText.getText().toString(); + +String company = mCompanyName.getText().toString(); +String jobtitle = mJobTitle.getText().toString(); + +// Creates a new intent for sending to the device's contacts application +Intent insertIntent = new Intent(ContactsContract.Intents.Insert.ACTION); + +// Sets the MIME type to the one expected by the insertion activity +insertIntent.setType(ContactsContract.RawContacts.CONTENT_TYPE); + +// Sets the new contact name +insertIntent.putExtra(ContactsContract.Intents.Insert.NAME, name); + +// Sets the new company and job title +insertIntent.putExtra(ContactsContract.Intents.Insert.COMPANY, company); +insertIntent.putExtra(ContactsContract.Intents.Insert.JOB_TITLE, jobtitle); + +/* + * Demonstrates adding data rows as an array list associated with the DATA key + */ + +// Defines an array list to contain the ContentValues objects for each row +ArrayList<ContentValues> contactData = new ArrayList<ContentValues>(); + + +/* + * Defines the raw contact row + */ + +// Sets up the row as a ContentValues object +ContentValues rawContactRow = new ContentValues(); + +// Adds the account type and name to the row +rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_TYPE, mSelectedAccount.getType()); +rawContactRow.put(ContactsContract.RawContacts.ACCOUNT_NAME, mSelectedAccount.getName()); + +// Adds the row to the array +contactData.add(rawContactRow); + +/* + * Sets up the phone number data row + */ + +// Sets up the row as a ContentValues object +ContentValues phoneRow = new ContentValues(); + +// Specifies the MIME type for this data row (all data rows must be marked by their type) +phoneRow.put( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Phone.CONTENT_ITEM_TYPE +); + +// Adds the phone number and its type to the row +phoneRow.put(ContactsContract.CommonDataKinds.Phone.NUMBER, phone); + +// Adds the row to the array +contactData.add(phoneRow); + +/* + * Sets up the email data row + */ + +// Sets up the row as a ContentValues object +ContentValues emailRow = new ContentValues(); + +// Specifies the MIME type for this data row (all data rows must be marked by their type) +emailRow.put( + ContactsContract.Data.MIMETYPE, + ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE +); + +// Adds the email address and its type to the row +emailRow.put(ContactsContract.CommonDataKinds.Email.ADDRESS, email); + +// Adds the row to the array +contactData.add(emailRow); + +/* + * Adds the array to the intent's extras. It must be a parcelable object in order to + * travel between processes. The device's contacts app expects its key to be + * Intents.Insert.DATA + */ +insertIntent.putParcelableArrayListExtra(ContactsContract.Intents.Insert.DATA, contactData); + +// Send out the intent to start the device's contacts app in its add contact activity. +startActivity(insertIntent); +</pre> +<h3 id="DataIntegrity">数据完整性</h3> +<p> + 联系人存储库包含用户认为是正确且是最新的重要敏感数据,因此联系人提供程序具有规定清晰的数据完整性规则。 +您有责任在修改联系人数据时遵守这些规则。 +以下列出了其中的重要规则: + +</p> +<dl> + <dt> + 务必为您添加的每个 {@link android.provider.ContactsContract.RawContacts} 行添加一个 {@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行。 + + </dt> + <dd> + 如果 {@link android.provider.ContactsContract.Data} 表中的 +{@link android.provider.ContactsContract.RawContacts} 行没有 +{@link android.provider.ContactsContract.CommonDataKinds.StructuredName} 行,可能会在聚合时引发问题。 + + </dd> + <dt> + 务必将新 {@link android.provider.ContactsContract.Data} 行链接到其父 +{@link android.provider.ContactsContract.RawContacts} 行。 + </dt> + <dd> + 如果 {@link android.provider.ContactsContract.Data} 行未链接到 +{@link android.provider.ContactsContract.RawContacts},则其在设备的联系人应用中将处于不可见状态,而且这可能会导致同步适配器出现问题。 + + </dd> + <dt> + 请仅更改您拥有的那些原始联系人的数据。 + </dt> + <dd> + 请切记,联系人提供程序所管理的数据通常来自多个不同帐户类型/在线服务。 +您需要确保您的应用仅修改或删除归您所有的行的数据,并且仅通过您控制的帐户类型和帐户名称插入数据。 + + + </dd> + <dt> + 务必使用在 {@link android.provider.ContactsContract} 及其子类中为权限、内容 URI、URI 路径、列名称、MIME 类型以及 +{@link android.provider.ContactsContract.CommonDataKinds.CommonColumns#TYPE} 值定义的常量。 + + </dt> + <dd> + 使用这些常量有助于您避免错误。如有任何常量被弃用,您还会从编译器警告收到通知。 + + </dd> +</dl> +<h3 id="CustomData">自定义数据行</h3> +<p> + 通过创建和使用自己的自定义 MIME 类型,您可以在 {@link android.provider.ContactsContract.Data} 表中插入、编辑、删除和检索您的自有数据行。 +这些行仅限使用 {@link android.provider.ContactsContract.DataColumns} 中定义的列,但您可以将您自己的类型专用列名称映射到默认列名称。 + + +在设备的联系人应用中,会显示这些行的数据,但无法对其进行编辑或删除,用户也无法添加其他数据。 + +要允许用户修改您的自定义数据行,您必须在自己的应用中提供编辑器 Activity。 + +</p> +<p> + 要显示您的自定义数据,请提供一个 <code>contacts.xml</code> 文件,其中须包含一个 +<code><ContactsAccountType></code> 元素,及其一个或多个 +<code><ContactsDataKind></code> 子元素。<a href="#SocialStreamDataKind"><code><ContactsDataKind> element</code></a> 部分对此做了更详尽的描述。 + +</p> +<p> + 如需了解有关自定义 MIME 类型的更多信息,请阅读<a href="{@docRoot}guide/topics/providers/content-provider-creating.html">创建内容提供程序</a>指南。 + + +</p> +<h2 id="SyncAdapters">联系人提供程序同步适配器</h2> +<p> + 联系人提供程序专门设计用于处理设备与在线服务之间的联系人数据<strong>同步</strong>。 +借助同步功能,用户可以将现有数据下载到新设备,以及将现有数据上传到新帐户。 + + 此外,同步还能确保用户掌握最新数据,无需考虑数据增加和更改的来源。 +同步的另一个优点是,即使设备未连接网络,联系人数据同样可用。 + +</p> +<p> + 虽然您可以通过各种方式实现同步,不过 Android 系统提供了一个插件同步框架,可自动化完成下列任务: + + <ul> + + <li> + 检查网络可用性。 + </li> + <li> + 根据用户偏好安排和执行同步。 + </li> + <li> + 重启已停止的同步。 + </li> + </ul> +<p> + 要使用此框架,您需要提供一个同步适配器插件。每个同步适配器都专用于某个服务和内容提供程序,但可以处理同一服务的多个帐户名称。 +该框架还允许同一服务和提供程序具有多个同步适配器。 + +</p> +<h3 id="SyncClassesFiles">同步适配器类和文件</h3> +<p> + 您需要将同步适配器作为 +{@link android.content.AbstractThreadedSyncAdapter} 的子类进行实现,并作为 Android +应用的一部分进行安装。系统通过您的应用清单文件中的元素以及由清单文件指向的一个特殊 XML 文件了解有关同步适配器的信息。 +该 XML 文件定义在线服务的帐户类型和内容提供程序的权限,它们共同对适配器进行唯一标识。 + +用户为同步适配器的帐户类型添加一个帐户,并为与同步适配器同步的内容提供程序启用同步后,同步适配器才会激活。 + +激活后,系统将开始管理适配器,并在必要时调用它,以在内容提供程序与服务器之间同步数据。 + +</p> +<p class="note"> + <strong>注:</strong>将帐户类型用作同步适配器标识的一部分让系统可以发现从同一组织访问不同服务的同步适配器,并将它们组合在一起。 + +例如,Google 在线服务的同步适配器都具有相同的帐户类型 <code>com.google</code>。 +当用户向其设备添加 Google 帐户时,已安装的所有 Google 服务同步适配器将一起列出;列出的每个同步适配器都与设备上不同的内容提供程序同步。 + + +</p> +<p> + 大多数服务都要求用户验证身份后才能访问数据,为此,Android 系统提供了一个身份验证框架,该框架与同步适配器框架类似,并且经常与其联用。 + +该身份验证框架使用的插件身份验证器是 +{@link android.accounts.AbstractAccountAuthenticator} 的子类。 +身份验证器通过下列步骤验证用户的身份: + + <ol> + <li> + 收集用户名、用户密码或类似信息(用户的<strong>凭据</strong>)。 + + </li> + <li> + 将凭据发送给服务 + </li> + <li> + 检查服务的回复。 + </li> + </ol> +<p> + 如果服务接受了凭据,身份验证器便可存储凭据以供日后使用。 +由于插件身份验证器框架的存在,{@link android.accounts.AccountManager} 可以提供对身份验证器支持并选择公开的任何身份验证令牌(例如 OAuth2 身份验证令牌)的访问。 + + +</p> +<p> + 尽管身份验证并非必需,但大多数联系人服务都会使用它。 + 不过,您不一定要使用 Android 身份验证框架进行身份验证。 +</p> +<h3 id="SyncAdapterImplementing">同步适配器实现</h3> +<p> + 要为联系人提供程序实现同步适配器,您首先要创建一个包含以下内容的 +Android 应用: +</p> + <dl> + <dt> + 一个 {@link android.app.Service} 组件,用于响应系统发出的绑定到同步适配器的请求。 + + </dt> + <dd> + 当系统想要运行同步时,它会调用服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法,为同步适配器获取一个 +{@link android.os.IBinder}。这样,系统便可跨进程调用适配器的方法。 + + <p> + 在<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用中,该服务的类名是 <code>com.example.android.samplesync.syncadapter.SyncService</code>。 + + + </p> + </dd> + <dt> + 作为 +{@link android.content.AbstractThreadedSyncAdapter} 具体子类实现的实际同步适配器。 + </dt> + <dd> + 此类的作用是从服务器下载数据、从设备上传数据以及解决冲突。 +适配器的主要工作是在方法 {@link android.content.AbstractThreadedSyncAdapter#onPerformSync( +Account, Bundle, String, ContentProviderClient, SyncResult) +onPerformSync()} 中完成的。 +必须将此类实例化为单一实例。 + <p> + 在<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用中,同步适配器是在 <code>com.example.android.samplesync.syncadapter.SyncAdapter</code> 类中定义的。 + + + </p> + </dd> + <dt> + {@link android.app.Application} 的子类。 + </dt> + <dd> + 此类充当同步适配器单一实例的工厂。使用 +{@link android.app.Application#onCreate()} 方法实例化同步适配器,并提供一个静态“getter”方法,使单一实例返回同步适配器服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法。 + + + </dd> + <dt> + <strong>可选:</strong>一个 {@link android.app.Service} 组件,用于响应系统发出的用户身份验证请求。 + + </dt> + <dd> + {@link android.accounts.AccountManager} 会启动此服务以开始身份验证流程。 +该服务的 {@link android.app.Service#onCreate()} 方法会将一个身份验证器对象实例化。 +当系统想要对应用同步适配器的用户帐户进行身份验证时,它会调用该服务的 +{@link android.app.Service#onBind(Intent) onBind()} 方法,为该身份验证器获取一个 +{@link android.os.IBinder}。 +这样,系统便可跨进程调用身份验证器的方法。 + + <p> + 在<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用中,该服务的类名是 <code>com.example.android.samplesync.authenticator.AuthenticationService</code>。 + + + </p> + </dd> + <dt> + <strong>可选:</strong>一个用于处理身份验证请求的 +{@link android.accounts.AbstractAccountAuthenticator} 具体子类。 + + </dt> + <dd> + {@link android.accounts.AccountManager} 就是调用此类所提供的方法向服务器验证用户的凭据。 +详细的身份验证过程会因服务器所采用技术的不同而有很大差异。 +您应该参阅服务器软件的文档,了解有关身份验证的更多信息。 + + <p> + 在<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用中,身份验证器是在 <code>com.example.android.samplesync.authenticator.Authenticator</code> 类中定义的。 + + + </p> + </dd> + <dt> + 用于定义系统同步适配器和身份验证器的 XML 文件。 + </dt> + <dd> + 之前描述的同步适配器和身份验证器服务组件都是在应用清单文件中的 +<code><<a href="{@docRoot}guide/topics/manifest/service-element.html">service</a>></code> +元素内定义的。 +这些元素包含以下用于向系统提供特定数据的 +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +子元素: + + + <ul> + <li> + 同步适配器服务的 +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +元素指向 +XML 文件 <code>res/xml/syncadapter.xml</code>。而该文件则指定将与联系人提供程序同步的 Web 服务的 URI,以及指定该 Web 服务的帐户类型。 + + + </li> + <li> + <strong>可选:</strong>身份验证器的 +<code><<a href="{@docRoot}guide/topics/manifest/meta-data-element.html">meta-data</a>></code> +元素指向 XML 文件 +<code>res/xml/authenticator.xml</code>。而该文件则指定此身份验证器所支持的帐户类型,以及指定身份验证过程中出现的 UI 资源。 + +在此元素中指定的帐户类型必须与为同步适配器指定的帐户类型相同。 + + + </li> + </ul> + </dd> + </dl> +<h2 id="SocialStream">社交流数据</h2> +<p> + {@link android.provider.ContactsContract.StreamItems} 表和 +{@link android.provider.ContactsContract.StreamItemPhotos} 表管理来自社交网络的传入数据。 +您可以编写一个同步适配器,用其将您自己社交网络中的流数据添加到这些表中,也可以从这些表读取流数据并将其显示在您的自有应用中,或者同时采用这两种方法。 + +利用这些功能,可以将您的社交网络服务和应用集成到 Android 的社交网络体验之中。 + +</p> +<h3 id="StreamText">社交流文本</h3> +<p> + 流项目始终与原始联系人关联。 +{@link android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID} 链接到原始联系人的 <code>_ID</code> 值。 +原始联系人的帐户类型和帐户名称也存储在流项目行中。 + +</p> +<p> + 将您的流数据存储在以下列: +</p> +<dl> + <dt> + {@link android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_TYPE} + </dt> + <dd> + <strong>必备。</strong>与该流项目关联的原始联系人对应的用户帐户类型。 +请记得在插入流项目时设置此值。 + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemsColumns#ACCOUNT_NAME} + </dt> + <dd> + <strong>必备。</strong>与该流项目关联的原始联系人对应的用户帐户名称。 +请记得在插入流项目时设置此值。 + </dd> + <dt> + 标识符列 + </dt> + <dd> + <strong>必备。</strong>您必须在插入流项目时插入下列标识符列: + + <ul> + <li> + {@link android.provider.ContactsContract.StreamItemsColumns#CONTACT_ID}:此流项目关联的联系人的 +{@link android.provider.BaseColumns#_ID} 值。 + + </li> + <li> + {@link android.provider.ContactsContract.StreamItemsColumns#CONTACT_LOOKUP_KEY}:此流项目关联的联系人的 +{@link android.provider.ContactsContract.ContactsColumns#LOOKUP_KEY} 值。 + + </li> + <li> + {@link android.provider.ContactsContract.StreamItemsColumns#RAW_CONTACT_ID}:此流项目关联的原始联系人的 +{@link android.provider.BaseColumns#_ID} 值。 + + </li> + </ul> + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemsColumns#COMMENTS} + </dt> + <dd> + 可选。存储可在流项目开头显示的摘要信息。 + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemsColumns#TEXT} + </dt> + <dd> + 流项目的文本,或为项目来源发布的内容,或是对生成流项目的某项操作的描述。 +此列可包含可由 +{@link android.text.Html#fromHtml(String) fromHtml()} 渲染的任何格式设置和嵌入式资源图像。 +提供程序可能会截断或省略较长内容,但它会尽力避免破坏标记。 + + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} + </dt> + <dd> + 一个包含流项目插入时间或更新时间的文本字符串,以从公元纪年开始计算的<em>毫秒数</em>形式表示。 +此列由插入或更新流项目的应用负责维护;联系人提供程序不会自动对其进行维护。 + + + </dd> +</dl> +<p> + 要显示您的流项目的标识信息,请使用 +{@link android.provider.ContactsContract.StreamItemsColumns#RES_ICON}、 +{@link android.provider.ContactsContract.StreamItemsColumns#RES_LABEL} 和 +{@link android.provider.ContactsContract.StreamItemsColumns#RES_PACKAGE} 链接到您的应用中的资源。 + +</p> +<p> + {@link android.provider.ContactsContract.StreamItems} 表还包含供同步适配器专用的列 +{@link android.provider.ContactsContract.StreamItemsColumns#SYNC1} 至 +{@link android.provider.ContactsContract.StreamItemsColumns#SYNC4}。 + +</p> +<h3 id="StreamPhotos">社交流照片</h3> +<p> + {@link android.provider.ContactsContract.StreamItemPhotos} 表存储与流项目关联的照片。 +该表的 +{@link android.provider.ContactsContract.StreamItemPhotosColumns#STREAM_ITEM_ID}列链接到 {@link android.provider.ContactsContract.StreamItems} 表 {@link android.provider.BaseColumns#_ID} 列中的值。 + +照片引用存储在表中的以下列: + +</p> +<dl> + <dt> + {@link android.provider.ContactsContract.StreamItemPhotos#PHOTO} 列(一个二进制大型对象)。 + </dt> + <dd> + 照片的二进制表示,为便于存储和显示,由提供程序调整了尺寸。 + 此列可用于向后兼容使用它来存储照片的旧版本联系人提供程序。 +不过,在当前版本中,您不应使用此列来存储照片, +而应使用 +{@link android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} 或 +{@link android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI}(下文对两者都做了描述)将照片存储在一个文件内。 +此列现在包含可用于读取的照片缩略图。 + + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_FILE_ID} + </dt> + <dd> + 原始联系人照片的数字标识符。将此值追加到常量 +{@link android.provider.ContactsContract.DisplayPhoto#CONTENT_URI DisplayPhoto.CONTENT_URI},获取指向单一照片文件的内容 URI,然后调用 +{@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 来获取照片文件的句柄。 + + </dd> + <dt> + {@link android.provider.ContactsContract.StreamItemPhotosColumns#PHOTO_URI} + </dt> + <dd> + 一个内容 URI,直接指向此行所表示的照片的照片文件。 + 通过此 URI 调用 {@link android.content.ContentResolver#openAssetFileDescriptor(Uri, String) +openAssetFileDescriptor()} 以获得照片文件的句柄。 + </dd> +</dl> +<h3 id="SocialStreamTables">使用社交流表</h3> +<p> + 这些表的工作方式与联系人提供程序中的其他主表基本相同,不同的是: +</p> + <ul> + <li> + 这些表需要额外的访问权限。要读取它们的数据,您的应用必须具有 {@link android.Manifest.permission#READ_SOCIAL_STREAM} 权限。 +要修改它们,您的应用必须具有 +{@link android.Manifest.permission#WRITE_SOCIAL_STREAM} 权限。 + + </li> + <li> + 对于 {@link android.provider.ContactsContract.StreamItems} 表,为每一位原始联系人存储的行数有限。 +一旦达到该限制,联系人提供程序即会自动删除 +{@link android.provider.ContactsContract.StreamItemsColumns#TIMESTAMP} 最早的行,为新流项目行腾出空间。 + +要获取该限制,请发出对内容 URI +{@link android.provider.ContactsContract.StreamItems#CONTENT_LIMIT_URI} 的查询。 +您可以将内容 URI 以外的所有其他参数保持设置为 <code>null</code>。 +查询会返回一个 Cursor,其中包含一行,并且只有 +{@link android.provider.ContactsContract.StreamItems#MAX_ITEMS} 一列。 + + </li> + </ul> + +<p> + {@link android.provider.ContactsContract.StreamItems.StreamItemPhotos} 类定义了 + {@link android.provider.ContactsContract.StreamItemPhotos} 的一个子表,其中包含某个流项目的照片行。 + +</p> +<h3 id="SocialStreamInteraction">社交流交互</h3> +<p> + 通过将联系人提供程序管理的社交流数据与设备的联系人应用相结合,可以在您的社交网络系统与现有联系人之间建立起有效的连接。 + +这种结合实现了下列功能: +</p> + <ul> + <li> + 您可以通过同步适配器让您的社交网络服务与联系人提供程序同步,检索用户联系人的近期 Activity,并将其存储在 + {@link android.provider.ContactsContract.StreamItems} 表和 +{@link android.provider.ContactsContract.StreamItemPhotos} 表中,以供日后使用。 + + </li> + <li> + 除了定期同步外,您还可以在用户选择某位联系人进行查看时触发您的同步适配器以检索更多数据。 +这样,您的同步适配器便可检索该联系人的高分辨率照片和最近流项目。 + + </li> + <li> + 通过在设备的联系人应用以及联系人提供程序中注册通知功能,您可以在用户查看联系人时<em>收到</em>一个 Intent,并在那时通过您的服务更新联系人的状态。 + +与通过同步适配器执行完全同步相比,此方法可能更快速,占用的带宽也更少。 + + </li> + <li> + 用户可以在查看设备联系人应用中的联系人时,将其添加到您的社交网络服务。 +您可以通过“邀请联系人”功能实现此目的,而该功能则是通过将 Activity 与 XML 文件结合使用来实现的,前者将现有联系人添加到您的社交网络,后者为设备的联系人应用以及联系人提供程序提供有关您的应用的详细信息。 + + + + </li> + </ul> +<p> + 流项目与联系人提供程序的定期同步与其他同步相同。 +如需了解有关同步的更多信息,请参阅 +<a href="#SyncAdapters">联系人提供程序同步适配器</a>部分。接下来的两节介绍如何注册通知和邀请联系人。 + +</p> +<h4>通过注册处理社交网络查看</h4> +<p> + 要注册您的同步适配器,以便在用户查看由您的同步适配器管理的联系人时收到通知,请执行以下步骤: + +</p> +<ol> + <li> + 在您项目的 <code>res/xml/</code> 目录中创建一个名为 <code>contacts.xml</code> 的文件。 +如果您已有该文件,可跳过此步骤。 + </li> + <li> + 在该文件中添加元素 +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>。 + 如果该元素已存在,可跳过此步骤。 + </li> + <li> + 要注册一项服务,以便在用户于设备的联系人应用中打开某位联系人的详细信息页面时通知该服务,请为该元素添加 +<code>viewContactNotifyService="<em>serviceclass</em>"</code> 属性,其中 +<code><em>serviceclass</em></code> 是该服务的完全限定类名,应由该服务接收来自设备联系人应用的 Intent。 + +对于这个通知程序服务,请使用一个扩展 {@link android.app.IntentService} 的类,以让该服务能够接收 Intent。 + +传入 Intent 中的数据包含用户点击的原始联系人的内容 URI。 +您可以通过通知程序服务绑定到您的同步适配器,然后调用同步适配器来更新原始联系人的数据。 + + </li> +</ol> +<p> + 要注册需要在用户点击流项目或照片(或同时点击这两者)时调用的 Activity,请执行以下步骤: +</p> +<ol> + <li> + 在您项目的 <code>res/xml/</code> 目录中创建一个名为 <code>contacts.xml</code> 的文件。 +如果您已有该文件,可跳过此步骤。 + </li> + <li> + 在该文件中添加元素 +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>。 + 如果该元素已存在,可跳过此步骤。 + </li> + <li> + 要注册某个 Activity,以处理用户在设备联系人应用中点击某个流项目的操作,请为该元素添加 +<code>viewStreamItemActivity="<em>activityclass</em>"</code> 属性,其中 +<code><em>activityclass</em></code> 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。 + + + </li> + <li> + 要注册某个 Activity,以处理用户在设备联系人应用中点击某个流照片的操作,请为该元素添加 +<code>viewStreamItemPhotoActivity="<em>activityclass</em>"</code> 属性,其中 +<code><em>activityclass</em></code> 是该 Activity 的完全限定类名,应由该 Activity 接收来自设备联系人应用的 Intent。 + + + </li> +</ol> +<p> + <a href="#SocialStreamAcctType"><ContactsAccountType> 元素</a>部分对 <code><ContactsAccountType></code> 元素做了更详尽的描述。 + +</p> +<p> + 传入 Intent 包含用户点击的项目或照片的内容 URI。 + 要让文本项目和照片具有独立的 Activity,请在同一文件中使用这两个属性。 +</p> +<h4>与您的社交网络服务交互</h4> +<p> + 用户不必为了邀请联系人到您的社交网络网站而离开设备的联系人应用。 +取而代之是,您可以让设备的联系人应用发送一个 Intent,将联系人 +邀请到您的 Activity 之一。要设置此功能,请执行以下步骤: +</p> +<ol> + <li> + 在您项目的 <code>res/xml/</code> 目录中创建一个名为 <code>contacts.xml</code> 的文件。 +如果您已有该文件,可跳过此步骤。 + </li> + <li> + 在该文件中添加元素 +<code><ContactsAccountType xmlns:android="http://schemas.android.com/apk/res/android"></code>。 + 如果该元素已存在,可跳过此步骤。 + </li> + <li> + 添加以下属性: + <ul> + <li><code>inviteContactActivity="<em>activityclass</em>"</code></li> + <li> + <code>inviteContactActionLabel="@string/<em>invite_action_label</em>"</code> + </li> + </ul> + <code><em>activityclass</em></code> 值是应该接收该 Intent 的 Activity 的完全限定类名。 +<code><em>invite_action_label</em></code> +值是一个文本字符串,将显示在设备联系人应用的 <strong>Add Connection</strong> 菜单中。 + + </li> +</ol> +<p class="note"> + <strong>注:</strong><code>ContactsSource</code> 是 +<code>ContactsAccountType</code> 的一个已弃用的标记名称。 +</p> +<h3 id="ContactsFile">contacts.xml 引用</h3> +<p> + 文件 <code>contacts.xml</code> 包含一些 XML 元素,这些元素控制您的同步适配器和应用与联系人应用及联系人提供程序的交互。 +下文对这些元素做了描述。 + +</p> +<h4 id="SocialStreamAcctType"><ContactsAccountType> 元素</h4> +<p> + <code><ContactsAccountType></code> 元素控制您的应用与联系人应用的交互。 +它采用了以下语法: +</p> +<pre> +<ContactsAccountType + xmlns:android="http://schemas.android.com/apk/res/android" + inviteContactActivity="<em>activity_name</em>" + inviteContactActionLabel="<em>invite_command_text</em>" + viewContactNotifyService="<em>view_notify_service</em>" + viewGroupActivity="<em>group_view_activity</em>" + viewGroupActionLabel="<em>group_action_text</em>" + viewStreamItemActivity="<em>viewstream_activity_name</em>" + viewStreamItemPhotoActivity="<em>viewphotostream_activity_name</em>"> +</pre> +<p> + <strong>包含它的文件:</strong> +</p> +<p> + <code>res/xml/contacts.xml</code> +</p> +<p> + <strong>可能包含的内容:</strong> +</p> +<p> + <strong><code><ContactsDataKind></code></strong> +</p> +<p> + <strong>描述:</strong> +</p> +<p> + 声明 Android 组件和 UI 标签,让用户能够邀请他们的一位联系人加入社交网络,在他们的某个社交网络流更新时通知用户,以及执行其他操作。 + + +</p> +<p> + 请注意,对 <code><ContactsAccountType></code> 的属性而言,属性前缀 <code>android:</code> 并非必需的。 + +</p> +<p> + <strong>属性:</strong> +</p> +<dl> + <dt>{@code inviteContactActivity}</dt> + <dd> + 您的应用中某个 Activity 的完全限定类名,您想要在用户于设备的联系人应用中选择 <strong>Add connection</strong> 时激活该 Activity。 + + + </dd> + <dt>{@code inviteContactActionLabel}</dt> + <dd> + <strong>Add connection</strong> 菜单中为 +{@code inviteContactActivity} 中指定的 Activity 显示的文本字符串。 + 例如,您可以使用字符串“Follow in my network”。您可以为此标签使用字符串资源标识符。 + + </dd> + <dt>{@code viewContactNotifyService}</dt> + <dd> + 您的应用中某项服务的完全限定类名,当用户查看联系人时,应由该服务接收通知。 +此通知由设备的联系人应用发送;您的应用可以根据通知将数据密集型操作推迟到必要时再执行。 + +例如,您的应用对此通知的响应可以是:读入并显示联系人的高分辨率照片和最近的社交流项目。 + +<a href="#SocialStreamInteraction">社交流交互</a>部分对此功能做了更详尽的描述。 +您可以在 +<a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">SampleSyncAdapter</a> 示例应用的 <code>NotifierService.java</code> 文件中查看通知服务的示例。 + + + </dd> + <dt>{@code viewGroupActivity}</dt> + <dd> + 您的应用中某个可显示组信息的 Activity 的完全限定类名。 +当用户点击设备联系人应用中的组标签时,将显示此 Activity 的 UI。 + + </dd> + <dt>{@code viewGroupActionLabel}</dt> + <dd> + 联系人应用为某个 UI 控件显示的标签,用户可通过该控件查看您的应用中的组。 + + <p> + 例如,如果您在设备上安装了 Google+ 应用,并将 +Google+ 与联系人应用同步,就会看到 Google+ 圈子以组的形式出现在您的联系人应用的 <strong>Groups</strong> 选项卡内。 +如果您点击某个 +Google+ 圈子,就会看到该圈子内的联系人以“组”的形式列出。在该显示页面的顶部,您会看到一个 Google+ 图标;如果您点击它,控制权将切换给 +Google+ 应用。联系人应用以 Google+ 图标作为 {@code viewGroupActionLabel} 的值,通过 +{@code viewGroupActivity} 来实现此目的。 + + + </p> + <p> + 允许使用字符串资源标识符作为该属性的值。 + </p> + </dd> + <dt>{@code viewStreamItemActivity}</dt> + <dd> + 您的应用中某个 Activity 的完全限定类名,设备的联系人应用会在用户点击原始联系人的流项目时启动该 Activity。 + + </dd> + <dt>{@code viewStreamItemPhotoActivity}</dt> + <dd> + 您的应用中某个 Activity 的完全限定类名,设备的联系人应用会在用户点击原始联系人流项目中的照片时启动该 Activity。 + + + </dd> +</dl> +<h4 id="SocialStreamDataKind"><ContactsDataKind> 元素</h4> +<p> + <code><ContactsDataKind></code> 元素控制您的应用的自定义数据行在联系人应用 UI 中的显示。它采用了以下语法: + +</p> +<pre> +<ContactsDataKind + android:mimeType="<em>MIMEtype</em>" + android:icon="<em>icon_resources</em>" + android:summaryColumn="<em>column_name</em>" + android:detailColumn="<em>column_name</em>"> +</pre> +<p> + <strong>包含它的文件:</strong> +</p> +<code><ContactsAccountType></code> +<p> + <strong>描述:</strong> +</p> +<p> + 此元素用于让联系人应用将自定义数据行的内容显示为原始联系人详细信息的一部分。 +<code><ContactsAccountType></code> 的每个 <code><ContactsDataKind></code> 子元素都代表您的同步适配器向 {@link android.provider.ContactsContract.Data} 表添加的某个自定义数据行类型。 + +请为您使用的每个自定义 MIME 类型添加一个 <code><ContactsDataKind></code> 元素。 +如果您不想显示任何自定义数据行的数据,则无需添加该元素。 + +</p> +<p> + <strong>属性:</strong> +</p> +<dl> + <dt>{@code android:mimeType}</dt> + <dd> + 您为 +{@link android.provider.ContactsContract.Data} 表中某个自定义数据行类型定义的自定义 MIME 类型。例如,可将值 +<code>vnd.android.cursor.item/vnd.example.locationstatus</code> 作为记录联系人最后已知位置的数据行的自定义 MIME 类型。 + + </dd> + <dt>{@code android:icon}</dt> + <dd> + 联系人应用在您的数据旁显示的 Android +<a href="{@docRoot}guide/topics/resources/drawable-resource.html"> Drawable资源</a>。 +它用于向用户指示数据来自您的服务。 + + </dd> + <dt>{@code android:summaryColumn}</dt> + <dd> + 从数据行检索的两个值中第一个值的列名。该值显示为该数据行的第一个输入行。 +第一行专用作数据摘要,不过它是可选项。 +另请参阅 +<a href="#detailColumn">android:detailColumn</a>。 + </dd> + <dt>{@code android:detailColumn}</dt> + <dd> + 从数据行检索的两个值中第二个值的列名。该值显示为该数据行的第二个输入行。 +另请参阅 +{@code android:summaryColumn}。 + </dd> +</dl> +<h2 id="AdditionalFeatures">其他联系人提供程序功能</h2> +<p> + 除了上文描述的主要功能外,联系人提供程序还为处理联系人数据提供了下列有用的功能: + +</p> + <ul> + <li>联系人组</li> + <li>照片功能</li> + </ul> +<h3 id="Groups">联系人组</h3> +<p> + 联系人提供程序可以选择性地为相关联系人集合添加<strong>组</strong>数据标签。 +如果与某个用户帐户关联的服务器想要维护组,则与该帐户的帐户类型对应的同步适配器应在联系人提供程序与服务器之间传送组数据。 + +当用户向服务器添加一个新联系人,然后将该联系人放入一个新组时,同步适配器必须将这个新组添加到 + {@link android.provider.ContactsContract.Groups} 表中。 +原始联系人所属的一个或多个组使用 {@link android.provider.ContactsContract.CommonDataKinds.GroupMembership} MIME 类型存储在 {@link android.provider.ContactsContract.Data} 表内。 + + +</p> +<p> + 如果您设计的同步适配器会将服务器中的原始联系人数据添加到联系人提供程序,并且您不使用组,则需要指示提供程序让您的数据可见。 + +在用户向设备添加帐户时执行的代码中,更新联系人提供程序为该帐户添加的 {@link android.provider.ContactsContract.Settings} +行。 +在该行中,将 +{@link android.provider.ContactsContract.SettingsColumns#UNGROUPED_VISIBLE +Settings.UNGROUPED_VISIBLE} 列的值设置为 1。执行此操作后,即使您不使用组,联系人提供程序也会让您的联系人数据始终可见。 + +</p> +<h3 id="Photos">联系人照片</h3> +<p> + {@link android.provider.ContactsContract.Data} 表通过 MIME 类型 +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 以行的形式存储照片。该行的 +{@link android.provider.ContactsContract.RawContactsColumns#CONTACT_ID} 列链接到其所属原始联系人的 +{@link android.provider.BaseColumns#_ID} 列。 + {@link android.provider.ContactsContract.Contacts.Photo} 类定义了一个 +{@link android.provider.ContactsContract.Contacts} 子表,其中包含联系人主要照片(联系人的主要原始联系人的主要照片)的照片信息。 +同样, +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 类定义了一个 {@link android.provider.ContactsContract.RawContacts} 子表,其中包含原始联系人主要照片的照片信息。 + + +</p> +<p> + {@link android.provider.ContactsContract.Contacts.Photo} 和 +{@link android.provider.ContactsContract.RawContacts.DisplayPhoto} 参考文档包含检索照片信息的示例。 +并没有可用来检索原始联系人主要缩略图的实用类,但您可以向 +{@link android.provider.ContactsContract.Data} 表发送查询,从而通过选定原始联系人的 +{@link android.provider.BaseColumns#_ID}、 +{@link android.provider.ContactsContract.CommonDataKinds.Photo#CONTENT_ITEM_TYPE +Photo.CONTENT_ITEM_TYPE} 以及 {@link android.provider.ContactsContract.Data#IS_PRIMARY} +列,找到原始联系人的主要照片行。 + +</p> +<p> + 联系人的社交流数据也可能包含照片。这些照片存储在 +{@link android.provider.ContactsContract.StreamItemPhotos} 表中,<a href="#StreamPhotos">社交流照片</a>部分对该表做了更详尽的描述。 + +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd new file mode 100644 index 000000000000..4c91d3a19c96 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-basics.jd @@ -0,0 +1,1196 @@ +page.title=内容提供程序基础知识 +@jd:body +<div id="qv-wrapper"> +<div id="qv"> +<!-- In this document --> +<h2>本文内容</h2> +<ol> + <li> + <a href="#Basics">概览</a> + <ol> + <li> + <a href="#ClientProvider">访问提供程序</a> + </li> + <li> + <a href="#ContentURIs">内容 URI</a> + </li> + </ol> + </li> + <li> + <a href="#SimpleQuery">从提供程序检索数据</a> + <ol> + <li> + <a href="#RequestPermissions">请求读取访问权限</a> + </li> + <li> + <a href="#Query">构建查询</a> + </li> + <li> + <a href="#DisplayResults">显示查询结果</a> + </li> + <li> + <a href="#GettingResults">从查询结果中获取数据</a> + </li> + </ol> + </li> + <li> + <a href="#Permissions">内容提供程序权限</a> + </li> + <li> + <a href="#Modifications">插入、更新和删除数据</a> + <ol> + <li> + <a href="#Inserting">插入数据</a> + </li> + <li> + <a href="#Updating">更新数据</a> + </li> + <li> + <a href="#Deleting">删除数据</a> + </li> + </ol> + </li> + <li> + <a href="#DataTypes">提供程序数据类型</a> + </li> + <li> + <a href="#AltForms">提供程序访问的替代形式</a> + <ol> + <li> + <a href="#Batch">批量访问</a> + </li> + <li> + <a href="#Intents">通过 Intent 访问数据</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClasses">协定类</a> + </li> + <li> + <a href="#MIMETypeReference">MIME 类型引用</a> + </li> +</ol> + + <!-- Key Classes --> +<h2>关键类</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.content.ContentResolver} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> + + <!-- Related Samples --> +<h2>相关示例</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> +游标(联系人)</a> + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> +游标(电话)</a> + </li> + </ol> + + <!-- See also --> +<h2>另请参阅</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> +创建内容提供程序</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> +日历提供程序</a> + </li> + </ol> +</div> +</div> + + <!-- Intro paragraphs --> +<p> + 内容提供程序管理对中央数据存储库的访问。提供程序是 Android 应用的一部分,通常提供自己的 UI 来使用数据。 + +但是,内容提供程序主要旨在供其他应用使用,这些应用使用提供程序客户端对象来访问提供程序。 +提供程序与提供程序客户端共同提供一致的标准数据界面,该界面还可处理跨进程通信并保护数据访问的安全性。 + + +</p> +<p> + 本主题介绍了以下基础知识: +</p> + <ul> + <li>内容提供程序的工作方式。</li> + <li>用于从内容提供程序检索数据的 API。</li> + <li>用于在内容提供程序中插入、更新或删除数据的 API。</li> + <li>其他有助于使用提供程序的 API 功能。</li> + </ul> + + <!-- Basics --> +<h2 id="Basics">概览</h2> +<p> + 内容提供程序以一个或多个表(与在关系数据库中找到的表类似)的形式将数据呈现给外部应用。 +行表示提供程序收集的某种数据类型的实例,行中的每个列表示为实例收集的每条数据。 + + +</p> +<p> + 例如,Android 平台的内置提供程序之一是用户字典,它会存储用户想要保存的非标准字词的拼写。 +表 1 描述了数据在此提供程序表中的显示情况: + +</p> +<p class="table-caption"> + <strong>表 1:</strong>用户字典示例表格。 +</p> +<table id="table1" style="width: 50%;"> + <tr> + <th style="width:20%" align="center" scope="col">字词</th> + <th style="width:20%" align="center" scope="col">应用 id</th> + <th style="width:20%" align="center" scope="col">频率</th> + <th style="width:20%" align="center" scope="col">区域设置</th> + <th style="width:20%" align="center" scope="col">_ID</th> + </tr> + <tr> + <td align="center" scope="row">mapreduce</td> + <td align="center">user1</td> + <td align="center">100</td> + <td align="center">en_US</td> + <td align="center">1</td> + </tr> + <tr> + <td align="center" scope="row">precompiler</td> + <td align="center">user14</td> + <td align="center">200</td> + <td align="center">fr_FR</td> + <td align="center">2</td> + </tr> + <tr> + <td align="center" scope="row">applet</td> + <td align="center">user2</td> + <td align="center">225</td> + <td align="center">fr_CA</td> + <td align="center">3</td> + </tr> + <tr> + <td align="center" scope="row">const</td> + <td align="center">user1</td> + <td align="center">255</td> + <td align="center">pt_BR</td> + <td align="center">4</td> + </tr> + <tr> + <td align="center" scope="row">int</td> + <td align="center">user5</td> + <td align="center">100</td> + <td align="center">en_UK</td> + <td align="center">5</td> + </tr> +</table> +<p> + 在表 1 中,每行表示可能无法在标准词典中找到的字词实例。 +每列表示该字词的某些数据,如该字词首次出现时的区域设置。 +列标题是存储在提供程序中的列名称。 +要引用行的区域设置,需要引用其 <code>locale</code> 列。对于此提供程序,<code>_ID</code> 列充当由提供程序自动维护的“主键”列。 + + +</p> +<p class="note"> + <strong>注</strong>:提供程序无需具有主键,也无需将 <code>_ID</code> 用作其主键的列名称(如果存在主键)。 +但是,如果您要将来自提供程序的数据与 {@link android.widget.ListView} 绑定,则其中一个列名称必须是 <code>_ID</code>。 + +<a href="#DisplayResults">显示查询结果</a>部分详细说明了此要求。 + +</p> +<h3 id="ClientProvider">访问提供程序</h3> +<p> + 应用从具有 {@link android.content.ContentResolver} 客户端对象的内容提供程序访问数据。 +此对象具有调用提供程序对象({@link android.content.ContentProvider} 的某个具体子类的实例)中同名方法的方法。 + + +{@link android.content.ContentResolver} 方法可提供持续存储的基本“CRUD”(创建、检索、更新和删除)功能。 + +</p> +<p> + 客户端应用进程中的 {@link android.content.ContentResolver} 对象和拥有提供程序的应用中的 {@link android.content.ContentProvider} 对象可自动处理跨进程通信。 +{@link android.content.ContentProvider} 还可充当其数据存储库和表格形式的数据外部显示之间的抽象层。 + + + +</p> +<p class="note"> + <strong>注</strong>:要访问提供程序,您的应用通常需要在其清单文件中请求特定权限。 +<a href="#Permissions">内容提供程序权限</a>部分详细介绍了此内容。 + +</p> +<p> + 例如,要从用户字典提供程序中获取字词及其区域设置的列表,则需调用 {@link android.content.ContentResolver#query ContentResolver.query()}。 + + {@link android.content.ContentResolver#query query()} 方法会调用用户字典提供程序所定义的 +{@link android.content.ContentProvider#query ContentProvider.query()} 方法。 +以下代码行显示了 +{@link android.content.ContentResolver#query ContentResolver.query()} 调用: +<p> +<pre> +// Queries the user dictionary and returns results +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Selection criteria + mSelectionArgs, // Selection criteria + mSortOrder); // The sort order for the returned rows +</pre> +<p> + 表 2 显示了 + {@link android.content.ContentResolver#query + query(Uri,projection,selection,selectionArgs,sortOrder)} 的参数如何匹配 SQL SELECT 语句: +</p> +<p class="table-caption"> + <strong>表 2:</strong>Query() 与 SQL 查询对比。 +</p> +<table id="table2" style="width: 75%;"> + <tr> + <th style="width:25%" align="center" scope="col">query() 参数</th> + <th style="width:25%" align="center" scope="col">SELECT 关键字/参数</th> + <th style="width:50%" align="center" scope="col">备注</th> + </tr> + <tr> + <td align="center"><code>Uri</code></td> + <td align="center"><code>FROM <em>table_name</em></code></td> + <td><code>Uri</code> 映射至名为 <em>table_name</em> 的提供程序中的表。</td> + </tr> + <tr> + <td align="center"><code>projection</code></td> + <td align="center"><code><em>col,col,col,...</em></code></td> + <td> + <code>projection</code> 是应该为检索到的每个行包含的列的数组。 + + </td> + </tr> + <tr> + <td align="center"><code>selection</code></td> + <td align="center"><code>WHERE <em>col</em> = <em>value</em></code></td> + <td><code>selection</code> 会指定选择行的条件。</td> + </tr> + <tr> + <td align="center"><code>selectionArgs</code></td> + <td align="center"> + (没有完全等效项。选择参数会替换选择子句中 <code>?</code> 的占位符。) + + </td> + </tr> + <tr> + <td align="center"><code>sortOrder</code></td> + <td align="center"><code>ORDER BY <em>col,col,...</em></code></td> + <td> + <code>sortOrder</code> 指定行在返回的 + {@link android.database.Cursor} 中的显示顺序。 + </td> + </tr> +</table> +<h3 id="ContentURIs">内容 URI</h3> +<p> + <strong>内容 URI</strong> 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其<strong>权限</strong>)和一个指向表的名称(<strong>路径</strong>)。 + +当您调用客户端方法来访问提供程序中的表时,该表的内容 URI 将是其参数之一。 + + +</p> +<p> + 在前面的代码行中,常量 + {@link android.provider.UserDictionary.Words#CONTENT_URI} 包含用户字典的“字词”表的内容 URI。 +{@link android.content.ContentResolver} + 对象会分析出 URI 的授权,并通过将该授权与已知提供程序的系统表进行比较,来“解析”提供程序。 +然后, +{@link android.content.ContentResolver} 可以将查询参数分派给正确的提供程序。 + +</p> +<p> + {@link android.content.ContentProvider} 使用内容 URI 的路径部分来选择要访问的表。 +提供程序通常会为其公开的每个表显示一条<strong>路径</strong>。 +</p> +<p> + 在前面的代码行中,“字词”表的完整 URI 是: +</p> +<pre> +content://user_dictionary/words +</pre> +<p> + 其中,<code>user_dictionary</code> 字符串是提供程序的授权, +<code>words</code> 字符串是表的路径。字符串 + <code>content://</code>(<strong>架构</strong>)始终显示,并将此标识为内容 URI。 + +</p> +<p> + 许多提供程序都允许您通过将 ID 值追加到 URI 末尾来访问表中的单个行。例如,要从用户字典中检索 <code>_ID</code> 为 + <code>4</code> 的行,则可使用此内容 URI: + +</p> +<pre> +Uri singleUri = ContentUris.withAppendedId(UserDictionary.Words.CONTENT_URI,4); +</pre> +<p> + 在检索到一组行后想要更新或删除其中某一行时通常会用到 ID 值。 + +</p> +<p class="note"> + <strong>注</strong>:{@link android.net.Uri} 和 {@link android.net.Uri.Builder} 类 +包含根据字符串构建格式规范的 URI 对象的便利方法。 +{@link android.content.ContentUris} 包含一些可以将 ID 值轻松追加到 + URI 后的方法。前面的代码段就是使用 {@link android.content.ContentUris#withAppendedId + withAppendedId()} 将 ID 追加到用户字典内容 URI 后。 +</p> + + + <!-- Retrieving Data from the Provider --> +<h2 id="SimpleQuery">从提供程序检索数据</h2> +<p> + 本节将以用户字典提供程序为例,介绍如何从提供程序中检索数据。 + +</p> +<p class="note"> + 为了明确进行说明,本节中的代码段将在“UI 线程”上调用 + {@link android.content.ContentResolver#query ContentResolver.query()}。但在实际代码中,您应该在单独线程上异步执行查询。 +执行此操作的方式之一是使用 {@link android.content.CursorLoader} 类,<a href="{@docRoot}guide/components/loaders.html">加载器</a>指南中对此有更为详细的介绍。 + + +此外,前述代码行只是代码段;它们不会显示整个应用。 + +</p> +<p> + 要从提供程序中检索数据,请按照以下基本步骤执行操作: +</p> +<ol> + <li> + 请求对提供程序的读取访问权限。 + </li> + <li> + 定义将查询发送至提供程序的代码。 + </li> +</ol> +<h3 id="RequestPermissions">请求读取访问权限</h3> +<p> + 要从提供程序检索数据,您的应需要具备对提供程序的“读取访问”权限。 +您无法在运行时请求此权限;相反,您需要使用<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code>元素和提供程序定义的准确权限名称,在清单文件中指明您需要此权限。 + + + +在您的清单文件中指定此元素后,您将有效地为应用“请求”此权限。 +用户安装您的应用时,会隐式授予允许此请求。 + +</p> +<p> + 要找出您正在使用的提供程序的读取访问权限的准确名称,以及提供程序使用的其他访问权限的名称,请查看提供程序的文档。 + + +</p> +<p> + <a href="#Permissions">内容提供程序权限</a>部分详细介绍了权限在访问提供程序过程中的作用。 + +</p> +<p> + 用户字典提供程序在其清单文件中定义了权限 + <code>android.permission.READ_USER_DICTIONARY</code>,因此希望从提供程序中进行读取的应用必需请求此权限。 + +</p> +<!-- Constructing the query --> +<h3 id="Query">构建查询</h3> +<p> + 从提供程序中检索数据的下一步是构建查询。第一个代码段定义某些用于访问用户字典提供程序的变量: + +</p> +<pre class="prettyprint"> + +// A "projection" defines the columns that will be returned for each row +String[] mProjection = +{ + UserDictionary.Words._ID, // Contract class constant for the _ID column name + UserDictionary.Words.WORD, // Contract class constant for the word column name + UserDictionary.Words.LOCALE // Contract class constant for the locale column name +}; + +// Defines a string to contain the selection clause +String mSelectionClause = null; + +// Initializes an array to contain selection arguments +String[] mSelectionArgs = {""}; + +</pre> +<p> + 下一个代码段以用户字典提供程序为例,显示了如何使用 + {@link android.content.ContentResolver#query ContentResolver.query()}。 +提供程序客户端查询与 SQL 查询类似,并且包含一组要返回的列、一组选择条件和排序顺序。 + +</p> +<p> + 查询应该返回的列集被称为<strong>投影</strong>(变量 <code>mProjection</code>)。 + +</p> +<p> + 用于指定要检索的行的表达式分割为选择子句和选择参数。 +选择子句是逻辑和布尔表达式、列名称和值(变量 <code>mSelectionClause</code>)的组合。 +如果您指定了可替换参数 <code>?</code> 而非值,则查询方法会从选择参数数组(变量 <code>mSelectionArgs</code>)中检索值。 + + +</p> +<p> + 在下一个代码段中,如果用户未输入字词,则选择子句将设置为 <code>null</code>,而且查询会返回提供程序中的所有字词。 +如果用户输入了字词,选择子句将设置为 <code>UserDictionary.Words.WORD + " = ?"</code> 且选择参数数组的第一个元素将设置为用户输入的字词。 + + +</p> +<pre class="prettyprint"> +/* + * This defines a one-element String array to contain the selection argument. + */ +String[] mSelectionArgs = {""}; + +// Gets a word from the UI +mSearchString = mSearchWord.getText().toString(); + +// Remember to insert code here to check for invalid or malicious input. + +// If the word is the empty string, gets everything +if (TextUtils.isEmpty(mSearchString)) { + // Setting the selection clause to null will return all words + mSelectionClause = null; + mSelectionArgs[0] = ""; + +} else { + // Constructs a selection clause that matches the word that the user entered. + mSelectionClause = UserDictionary.Words.WORD + " = ?"; + + // Moves the user's input string to the selection arguments. + mSelectionArgs[0] = mSearchString; + +} + +// Does a query against the table and returns a Cursor object +mCursor = getContentResolver().query( + UserDictionary.Words.CONTENT_URI, // The content URI of the words table + mProjection, // The columns to return for each row + mSelectionClause // Either null, or the word the user entered + mSelectionArgs, // Either empty, or the string the user entered + mSortOrder); // The sort order for the returned rows + +// Some providers return null if an error occurs, others throw an exception +if (null == mCursor) { + /* + * Insert code here to handle the error. Be sure not to use the cursor! You may want to + * call android.util.Log.e() to log this error. + * + */ +// If the Cursor is empty, the provider found no matches +} else if (mCursor.getCount() < 1) { + + /* + * Insert code here to notify the user that the search was unsuccessful. This isn't necessarily + * an error. You may want to offer the user the option to insert a new row, or re-type the + * search term. + */ + +} else { + // Insert code here to do something with the results + +} +</pre> +<p> + 此查询与 SQL 语句相似: +</p> +<pre> +SELECT _ID, word, locale FROM words WHERE word = <userinput> ORDER BY word ASC; +</pre> +<p> + 在此 SQL 语句中,会使用实际的列名称而非协定类常量。 +</p> +<h4 id="Injection">防止恶意输入</h4> +<p> + 如果内容提供程序管理的数据位于 SQL 数据库中,将不受信任的外部数据包括在原始 SQL 语句中可能会导致 SQL 注入。 + +</p> +<p> + 考虑此选择子句: +</p> +<pre> +// Constructs a selection clause by concatenating the user's input to the column name +String mSelectionClause = "var = " + mUserInput; +</pre> +<p> + 如果您执行此操作,则会允许用户将恶意 SQL 串连到 SQL 语句上。 + 例如,用户可以为 <code>mUserInput</code> 输入“nothing; DROP TABLE *;”,这会生成选择子句 <code>var = nothing; DROP TABLE *;</code>。 +由于选择子句是作为 SQL 语句处理,因此这可能会导致提供程序擦除基础 SQLite 数据库中的所有表(除非提供程序设置为可捕获 <a href="http://en.wikipedia.org/wiki/SQL_injection">SQL 注入</a>尝试)。 + + + +</p> +<p> + 要避免此问题,可使用一个用于将 <code>?</code> 作为可替换参数的选择子句以及一个单独的选择参数数组。 +执行此操作时,用户输入直接受查询约束,而不解释为 SQL 语句的一部分。 + + 由于用户输入未作为 SQL 处理,因此无法注入恶意 SQL。请使用此选择子句,而不要使用串连来包括用户输入: + +</p> +<pre> +// Constructs a selection clause with a replaceable parameter +String mSelectionClause = "var = ?"; +</pre> +<p> + 按如下所示设置选择参数数组: +</p> +<pre> +// Defines an array to contain the selection arguments +String[] selectionArgs = {""}; +</pre> +<p> + 按如下所示将值置于选择参数数组中: +</p> +<pre> +// Sets the selection argument to the user's input +selectionArgs[0] = mUserInput; +</pre> +<p> + 一个用于将 <code>?</code> 用作可替换参数的选择子句和一个选择参数数组是指定选择的首选方式,即使提供程序并未基于 SQL 数据库。 + + +</p> +<!-- Displaying the results --> +<h3 id="DisplayResults">显示查询结果</h3> +<p> + {@link android.content.ContentResolver#query ContentResolver.query()} 客户端方法始终会返回符合以下条件的 {@link android.database.Cursor}:包含查询的投影为匹配查询选择条件的行指定的列。 + + +{@link android.database.Cursor} 对象为其包含的行和列提供随机读取访问权限。 +通过使用 {@link android.database.Cursor} 方法,您可以循环访问结果中的行、确定每个列的数据类型、从列中获取数据,并检查结果的其他属性。 + +某些 {@link android.database.Cursor} 实现会在提供程序的数据发生更改时自动更新对象和/或在 {@link android.database.Cursor} 更改时触发观察程序对象中的方法。 + + +</p> +<p class="note"> + <strong>注</strong>:提供程序可能会根据发出查询的对象的性质来限制对列的访问。 +例如,联系人提供程序会限定只有同步适配器才能访问某些列,因此不会将它们返回至 Activity 或服务。 + +</p> +<p> + 如果没有与选择条件匹配的行,则提供程序会返回 {@link android.database.Cursor#getCount Cursor.getCount()} 为 0(空游标)的 {@link android.database.Cursor} 对象。 + + +</p> +<p> + 如果出现内部错误,查询结果将取决于具体的提供程序。它可能会选择返回 <code>null</code>,或抛出 {@link java.lang.Exception}。 + +</p> +<p> + 由于 {@link android.database.Cursor} 是行“列表”,因此显示 {@link android.database.Cursor} 内容的良好方式是通过 {@link android.widget.SimpleCursorAdapter} 将其与 {@link android.widget.ListView} 关联。 + + +</p> +<p> + 以下代码段将延续上一代码段的代码。它会创建一个包含由查询检索到的 {@link android.database.Cursor} 的 {@link android.widget.SimpleCursorAdapter} 对象,并将此对象设置为 {@link android.widget.ListView} 的适配器: + + + +</p> +<pre class="prettyprint"> +// Defines a list of columns to retrieve from the Cursor and load into an output row +String[] mWordListColumns = +{ + UserDictionary.Words.WORD, // Contract class constant containing the word column name + UserDictionary.Words.LOCALE // Contract class constant containing the locale column name +}; + +// Defines a list of View IDs that will receive the Cursor columns for each row +int[] mWordListItems = { R.id.dictWord, R.id.locale}; + +// Creates a new SimpleCursorAdapter +mCursorAdapter = new SimpleCursorAdapter( + getApplicationContext(), // The application's Context object + R.layout.wordlistrow, // A layout in XML for one row in the ListView + mCursor, // The result from the query + mWordListColumns, // A string array of column names in the cursor + mWordListItems, // An integer array of view IDs in the row layout + 0); // Flags (usually none are needed) + +// Sets the adapter for the ListView +mWordList.setAdapter(mCursorAdapter); +</pre> +<p class="note"> + <strong>注</strong>:要通过 {@link android.database.Cursor} 支持 {@link android.widget.ListView},游标必需包含名为 <code>_ID</code> 的列。 + + 正因如此,前文显示的查询会为“字词”表检索 <code>_ID</code> 列,即使 {@link android.widget.ListView} 未显示该列。 + + 此限制也解释了为什么大多数提供程序的每个表都具有 <code>_ID</code> 列。 + +</p> + + <!-- Getting data from query results --> +<h3 id="GettingResults">从查询结果中获取数据</h3> +<p> + 您可以将查询结果用于其他任务,而不是仅显示它们。例如,您可以从用户字典中检索拼写,然后在其他提供程序中查找它们。 + +要执行此操作,您需要在 {@link android.database.Cursor} 中循环访问行: +</p> +<pre class="prettyprint"> + +// Determine the column index of the column named "word" +int index = mCursor.getColumnIndex(UserDictionary.Words.WORD); + +/* + * Only executes if the cursor is valid. The User Dictionary Provider returns null if + * an internal error occurs. Other providers may throw an Exception instead of returning null. + */ + +if (mCursor != null) { + /* + * Moves to the next row in the cursor. Before the first movement in the cursor, the + * "row pointer" is -1, and if you try to retrieve data at that position you will get an + * exception. + */ + while (mCursor.moveToNext()) { + + // Gets the value from the column. + newWord = mCursor.getString(index); + + // Insert code here to process the retrieved word. + + ... + + // end of while loop + } +} else { + + // Insert code here to report an error if the cursor is null or the provider threw an exception. +} +</pre> +<p> + {@link android.database.Cursor} 实现包含多个用于从对象中检索不同类型的数据的“获取”方法。 +例如,上一个代码段使用 {@link android.database.Cursor#getString getString()}。 +它们还具有 {@link android.database.Cursor#getType getType()} 方法,该方法会返回指示列的数据类型的值。 + + +</p> + + + <!-- Requesting permissions --> +<h2 id="Permissions">内容提供程序权限</h2> +<p> + 提供程序的应用可以指定其他应用访问提供程序的数据所必需的权限。 +这些权限可确保用户了解应用将尝试访问的数据。 +根据提供程序的要求,其他应用会请求它们访问提供程序所需的权限。 +最终用户会在安装应用时看到所请求的权限。 + +</p> +<p> + 如果提供程序的应用未指定任何权限,则其他应用将无权访问提供程序的数据。 +但是,无论指定权限为何,提供程序的应用中的组件始终具有完整的读取和写入访问权限。 + +</p> +<p> + 如前所述,用户字典提供程序需要 + <code>android.permission.READ_USER_DICTIONARY</code> 权限才能从中检索数据。 + 提供程序具有用于插入、更新或删除数据的单独 <code>android.permission.WRITE_USER_DICTIONARY</code> 权限。 + +</p> +<p> + 要获取访问提供程序所需的权限,应用将通过其清单文件中的 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> + 元素来请求这些权限。Android 软件包管理器安装应用时,用户必须批准该应用请求的所有权限。 +如果用户批准所有权限,软件包管理器将继续安装;如果用户未批准这些权限,软件包管理器将中止安装。 + + +</p> +<p> + 以下 +<code><a href="{@docRoot}guide/topics/manifest/uses-permission-element.html"><uses-permission></a></code> 元素会请求对用户字典提供程序的读取访问权限: + +</p> +<pre> + <uses-permission android:name="android.permission.READ_USER_DICTIONARY"> +</pre> +<p> + +<a href="{@docRoot}guide/topics/security/security.html">安全与权限</a>指南中详细介绍了权限对提供程序访问的影响。 +</p> + + +<!-- Inserting, Updating, and Deleting Data --> +<h2 id="Modifications">插入、更新和删除数据</h2> +<p> + 与从提供程序检索数据的方式相同,也可以通过提供程序客户端和提供程序 {@link android.content.ContentProvider} 之间的交互来修改数据。 + + 您通过传递到 {@link android.content.ContentProvider} 的对应方法的参数来调用 {@link android.content.ContentResolver} 方法。 +提供程序和提供程序客户端会自动处理安全性和跨进程通信。 + +</p> +<h3 id="Inserting">插入数据</h3> +<p> + 要将数据插入提供程序,可调用 + {@link android.content.ContentResolver#insert ContentResolver.insert()} + 方法。此方法会在提供程序中插入新行并为该行返回内容 URI。 + 此代码段显示如何将新字词插入用户字典提供程序: +</p> +<pre class="prettyprint"> +// Defines a new Uri object that receives the result of the insertion +Uri mNewUri; + +... + +// Defines an object to contain the new values to insert +ContentValues mNewValues = new ContentValues(); + +/* + * Sets the values of each column and inserts the word. The arguments to the "put" + * method are "column name" and "value" + */ +mNewValues.put(UserDictionary.Words.APP_ID, "example.user"); +mNewValues.put(UserDictionary.Words.LOCALE, "en_US"); +mNewValues.put(UserDictionary.Words.WORD, "insert"); +mNewValues.put(UserDictionary.Words.FREQUENCY, "100"); + +mNewUri = getContentResolver().insert( + UserDictionary.Word.CONTENT_URI, // the user dictionary content URI + mNewValues // the values to insert +); +</pre> +<p> + 新行的数据会进入单个 {@link android.content.ContentValues} 对象中,该对象在形式上与单行游标类似。 +此对象中的列不需要具有相同的数据类型,如果您不想指定值,则可以使用 {@link android.content.ContentValues#putNull ContentValues.putNull()} 将列设置为 <code>null</code>。 + + +</p> +<p> + 代码段不会添加 <code>_ID</code> 列,因为系统会自动维护此列。 +提供程序会向添加的每个行分配唯一的 <code>_ID</code> 值。 +通常,提供程序会将此值用作表的主键。 +</p> +<p> + <code>newUri</code> 中返回的内容 URI 会按照以下格式标识新添加的行: + +</p> +<pre> +content://user_dictionary/words/<id_value> +</pre> +<p> + <code><id_value></code> 是新行的 <code>_ID</code> 内容。 + 大多数提供程序都能自动检测这种格式的内容 URI,然后在该特定行上执行请求的操作。 + +</p> +<p> + 要从返回的 {@link android.net.Uri} 中获取 <code>_ID</code> 的值,请调用 + {@link android.content.ContentUris#parseId ContentUris.parseId()}。 +</p> +<h3 id="Updating">更新数据</h3> +<p> + 要更新行,请按照执行插入的方式使用具有更新值的 {@link android.content.ContentValues} 对象,并按照执行查询的方式使用选择条件。 + + 您使用的客户端方法是 + {@link android.content.ContentResolver#update ContentResolver.update()}。您只需将值添加至您要更新的列的 {@link android.content.ContentValues} 对象。 +如果您要清除列的内容,请将值设置为 <code>null</code>。 + +</p> +<p> + 以下代码段会将区域设置具有语言“en”的所有行的区域设置更改为 <code>null</code>。 +返回值是已更新的行数: +</p> +<pre> +// Defines an object to contain the updated values +ContentValues mUpdateValues = new ContentValues(); + +// Defines selection criteria for the rows you want to update +String mSelectionClause = UserDictionary.Words.LOCALE + "LIKE ?"; +String[] mSelectionArgs = {"en_%"}; + +// Defines a variable to contain the number of updated rows +int mRowsUpdated = 0; + +... + +/* + * Sets the updated value and updates the selected words. + */ +mUpdateValues.putNull(UserDictionary.Words.LOCALE); + +mRowsUpdated = getContentResolver().update( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mUpdateValues // the columns to update + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + 您还应该在调用 + {@link android.content.ContentResolver#update ContentResolver.update()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读<a href="#Injection">防止恶意输入</a>部分。 + +</p> +<h3 id="Deleting">删除数据</h3> +<p> + 删除行与检索行数据类似:为要删除的行指定选择条件,客户端方法会返回已删除的行数。 + + 以下代码段会删除应用 ID 与“用户”匹配的行。该方法会返回已删除的行数。 + +</p> +<pre> + +// Defines selection criteria for the rows you want to delete +String mSelectionClause = UserDictionary.Words.APP_ID + " LIKE ?"; +String[] mSelectionArgs = {"user"}; + +// Defines a variable to contain the number of rows deleted +int mRowsDeleted = 0; + +... + +// Deletes the words that match the selection criteria +mRowsDeleted = getContentResolver().delete( + UserDictionary.Words.CONTENT_URI, // the user dictionary content URI + mSelectionClause // the column to select on + mSelectionArgs // the value to compare to +); +</pre> +<p> + 您还应该在调用 + {@link android.content.ContentResolver#delete ContentResolver.delete()} 时检查用户输入。如需了解有关此内容的更多详情,请阅读<a href="#Injection">防止恶意输入</a>部分。 + +</p> +<!-- Provider Data Types --> +<h2 id="DataTypes">提供程序数据类型</h2> +<p> + 内容提供程序可以提供多种不同的数据类型。用户字典提供程序仅提供文本,但提供程序也能提供以下格式: + +</p> + <ul> + <li> + 整型 + </li> + <li> + 长整型(长) + </li> + <li> + 浮点型 + </li> + <li> + 长浮点型(双倍) + </li> + </ul> +<p> + 提供程序经常使用的另一种数据类型是作为 64KB 字节的数组实施的二进制大型对象 (BLOB)。 +您可以通过查看 + {@link android.database.Cursor} 类“获取”方法看到可用数据类型。 +</p> +<p> + 提供程序文档中通常都列出了其每个列的数据类型。 + 用户字典提供程序的数据类型列在其协定类 {@link android.provider.UserDictionary.Words} 参考文档中(<a href="#ContractClasses">协定类</a>部分对协定类进行了介绍)。 + + + 您也可以通过调用 {@link android.database.Cursor#getType + Cursor.getType()} 来确定数据类型。 +</p> +<p> + 提供程序还会维护其定义的每个内容 URI 的 MIME(多用途互联网邮件扩展)数据类型信息。您可以使用 MIME 类型信息查明应用是否可以处理提供程序提供的数据,或根据 MIME 类型选择处理类型。 + +在使用包含复杂数据结构或文件的提供程序时,通常需要 MIME 类型。 + +例如,联系人提供程序中的 {@link android.provider.ContactsContract.Data} + 表会使用 MIME 类型来标记每行中存储的联系人数据类型。 +要获取与内容 URI 对应的 MIME 类型,请调用 + {@link android.content.ContentResolver#getType ContentResolver.getType()}。 +</p> +<p> + <a href="#MIMETypeReference">MIME 类型引用</a>部分介绍了标准和自定义 MIME 类型的语法。 + +</p> + + +<!-- Alternative Forms of Provider Access --> +<h2 id="AltForms">提供程序访问的替代形式</h2> +<p> + 提供程序访问的三种替代形式在应用开发过程中十分重要: +</p> +<ul> + <li> + <a href="#Batch">批量访问</a>:您可以通过 + {@link android.content.ContentProviderOperation} 类中的方法创建一批访问调用,然后通过 + {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} 应用它们。 + </li> + <li> + 异步查询:您应该在单独线程中执行查询。执行此操作的方式之一是使用 {@link android.content.CursorLoader} 对象。 +<a href="{@docRoot}guide/components/loaders.html">加载器</a>指南中的示例展示了如何执行此操作。 + + + </li> + <li> + <a href="#Intents">通过 Intent 访问数据</a>:尽管您无法直接向提供程序发送 Intent,但可以向提供程序的应用发送 Intent,后者通常具有修改提供程序数据的最佳配置。 + + + </li> +</ul> +<p> + 下文将介绍批量访问和修改。 +</p> +<h3 id="Batch">批量访问</h3> +<p> + 批量访问提供程序适用于插入大量行,或通过同一方法调用在多个表中插入行,或者通常用于跨进程界限将一组操作作为事务处理(原子操作)执行。 + + +</p> +<p> + 要在“批量模式”下访问提供程序, +您可以创建 {@link android.content.ContentProviderOperation} 对象数组,然后使用 {@link android.content.ContentResolver#applyBatch ContentResolver.applyBatch()} +将其分派给内容提供程序。 +您需将内容提供程序的<em>授权</em>传递给此方法,而不是特定内容 URI。这样可使数组中的每个 {@link android.content.ContentProviderOperation} 对象都能适用于其他表。 + + +调用 {@link android.content.ContentResolver#applyBatch + ContentResolver.applyBatch()} 会返回结果数组。 +</p> +<p> + {@link android.provider.ContactsContract.RawContacts} 协定类 +的说明包括展示批量注入的代码段。 +<a href="{@docRoot}resources/samples/ContactManager/index.html">联系人管理器</a>示例应用包含在其 <code>ContactAdder.java</code> + 源文件中进行批量访问的示例。 + +</p> +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>使用帮助程序应用显示数据</h2> +<p> + 如果您的应用<em>具有</em>访问权限,您可能仍想使用 Intent 对象在其他应用中显示数据。 +例如,日历应用接受 + {@link android.content.Intent#ACTION_VIEW} Intent 对象,用于显示特定的日期或事件。 + 这样,您可以在不创建自己的 UI 的情况下显示日历信息。如需了解有关此功能的详情,请参见<a href="{@docRoot}guide/topics/providers/calendar-provider.html">日历提供程序</a>指南。 + + +</p> +<p> + 您向其发送 Intent 对象的应用不一定要是与提供程序关联的应用。 +例如,您可以从联系人提供程序中检索联系人,然后将包含联系人图像的内容 URI 的 {@link android.content.Intent#ACTION_VIEW} Intent 发送至图像查看器。 + + +</p> +</div> +</div> +<h3 id="Intents">通过 Intent 访问数据</h3> +<p> + Intent 可以提供对内容提供程序的间接访问。即使您的应用不具备访问权限,您也可以通过以下方式允许用户访问提供程序中的数据:从具有权限的应用中获取回结果 Intent,或者通过激活具有权限的应用,然后让用户在其中工作。 + + + +</p> +<h4>通过临时权限获取访问权限</h4> +<p> + 即使您没有适当的访问权限,也可以通过以下方式访问内容提供程序中的数据:将 Intent 发送至具有权限的应用,然后接收回包含“URI”权限的结果 Intent。 + + + 这些是特定内容 URI 的权限,将持续至接收该权限的 Activity 结束。 +具有永久权限的应用将通过在结果 Intent 中设置标志来授予临时权限: + +</p> +<ul> + <li> + <strong>读取权限:</strong> +{@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} + </li> + <li> + <strong>写入权限:</strong> +{@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} + </li> +</ul> +<p class="note"> + <strong>注</strong>:这些标志不会为其授权包含在内容 URI 中的提供程序 +提供常规的读取或写入访问权限。访问权限仅适用于 URI 本身。 +</p> +<p> + 提供程序使用 +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +元素的 +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn">android:grantUriPermission</a></code> +属性以及 +<code><a href="{@docRoot}guide/topics/manifest/provider-element.html"><provider></a></code> +元素的 +<code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"><grant-uri-permission></a></code> +子元素在其清单文件中定义内容 URI 的 URI 权限。<a href="{@docRoot}guide/topics/security/security.html">安全与权限</a>指南中“URI 权限”部分更加详细地说明了 URI 权限机制。 + + +</p> +<p> + 例如,即使您没有 + {@link android.Manifest.permission#READ_CONTACTS} 权限,也可以在联系人提供程序中检索联系人的数据。您可能希望在向联系人发送电子生日祝福的应用中执行此操作。 +您更愿意让用户控制应用所使用的联系人,而不是请求 {@link android.Manifest.permission#READ_CONTACTS},让您能够访问用户的所有联系人及其信息。 + + +要执行此操作,您需要使用以下进程: +</p> +<ol> + <li> + 您的应用会使用方法 {@link android.app.Activity#startActivityForResult + startActivityForResult()} 发送包含操作 + {@link android.content.Intent#ACTION_PICK} 和“联系人”MIME 类型 + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE} 的 Intent 对象。 + + </li> + <li> + 由于此 Intent 与“联系人”应用的“选择” Activity 的 Intent 过滤器相匹配,因此 Activity 会显示在前台。 + + </li> + <li> + 在选择 Activity 中,用户选择要更新的联系人。 +发生此情况时,选择 Activity 会调用 + {@link android.app.Activity#setResult setResult(resultcode, intent)} + 以设置用于返回至应用的 Intent。 Intent 包含用户选择的联系人的内容 URI,以及“extras”标志 + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION}。 +这些标志会为您的应用授予读取内容 URI 所指向的联系人的数据的 URI + 权限。然后,选择 Activity 会调用 {@link android.app.Activity#finish()} 以返回对应用的控制。 + + + </li> + <li> + 您的 Activity 会返回至前台,系统会调用您的 Activity 的 + {@link android.app.Activity#onActivityResult onActivityResult()} + 方法。此方法会收到“联系人”应用中选择 Activity 所创建的结果 Intent。 + + </li> + <li> + 通过来自结果 Intent 的内容 URI,您可以读取来自联系人提供程序的联系人数据,即使您未在清单文件中请求对该提供程序的永久读取访问权限。 + +您可以获取联系人的生日信息或其电子邮件地址,然后发送电子祝福。 + + </li> +</ol> +<h4>使用其他应用</h4> +<p> + 允许用户修改您无权访问的数据的简单方法是激活具有权限的应用,让用户在其中执行工作。 + +</p> +<p> + 例如,日历应用接受 + {@link android.content.Intent#ACTION_INSERT} Intent,这让您可以激活应用的插入 UI。您可以在此 Intent(应用将使用该 Intent 来预先填充 UI)中传递“额外”数据,由于定期事件具有复杂的语法,因此将事件插入日历提供程序的首选方式是激活具有 + {@link android.content.Intent#ACTION_INSERT} 的日历应用,然后让用户在其中插入事件。 + + + +</p> +<!-- Contract Classes --> +<h2 id="ContractClasses">协定类</h2> +<p> + 协定类定义帮助应用使用内容 URI、列名称、 Intent 操作以及内容提供程序的其他功能的常量。 +协定类未自动包含在提供程序中;提供程序的开发者需要定义它们,然后使其可用于其他开发者。 + +Android + 平台中包含的许多提供程序都在软件包 {@link android.provider} 中具有对应的协定类。 +</p> +<p> + 例如,用户字典提供程序具有包含内容 URI 和列名称常量的协定类 {@link android.provider.UserDictionary}。 + +“字词”表的内容 URI 在常量 + {@link android.provider.UserDictionary.Words#CONTENT_URI UserDictionary.Words.CONTENT_URI} 中定义。 + {@link android.provider.UserDictionary.Words} 类也包含列名称常量,本指南的示例代码段中就使用了该常量。 +例如,查询投影可以定义为: + +</p> +<pre> +String[] mProjection = +{ + UserDictionary.Words._ID, + UserDictionary.Words.WORD, + UserDictionary.Words.LOCALE +}; +</pre> +<p> + 联系人提供程序的 {@link android.provider.ContactsContract} 也是一个协定类。 + 此类的参考文档包括示例代码段。其子类之一 {@link android.provider.ContactsContract.Intents.Insert} 是包含 Intent 和 Intent 数据的协定类。 + + +</p> + + +<!-- MIME Type Reference --> +<h2 id="MIMETypeReference">MIME 类型引用</h2> +<p> + 内容提供程序可以返回标准 MIME 媒体类型和/或自定义 MIME 类型字符串。 +</p> +<p> + MIME 类型具有格式 +</p> +<pre> +<em>type</em>/<em>subtype</em> +</pre> +<p> + 例如,众所周知的 MIME 类型 <code>text/html</code> 具有 <code>text</code> 类型和 + <code>html</code> 子类型。如果提供程序为 URI 返回此类型,则意味着使用该 URI 的查询会返回包含 HTML 标记的文本。 + +</p> +<p> + 自定义 MIME 类型字符串(也称为“特定于供应商”的 MIME 类型)具有更加复杂的<em>类型</em>和<em>子类型</em>值。 +<em>类型</em>值始终为 +</p> +<pre> +vnd.android.cursor.<strong>dir</strong> +</pre> +<p> + (多行)或 +</p> +<pre> +vnd.android.cursor.<strong>item</strong> +</pre> +<p> + (单行)。 +</p> +<p> + <em>子类型</em>特定于提供程序。Android 内置提供程序通常具有简单的子类型。 +例如,当联系人应用为电话号码创建行时,它会在行中设置以下 MIME 类型: + +</p> +<pre> +vnd.android.cursor.item/phone_v2 +</pre> +<p> + 请注意,子类型值只是 <code>phone_v2</code>。 +</p> +<p> + 其他提供程序开发者可能会根据提供程序的授权和表名称创建自己的子类型模式。 +例如,假设提供程序包含列车时刻表。 + 提供程序的授权是 <code>com.example.trains</code>,并包含表 + Line1、Line2 和 Line3。在响应表 Line1 的内容 URI +</p> +<p> +<pre> +content://com.example.trains/Line1 +</pre> +<p> + 时,提供程序会返回 MIME 类型 +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.example.line1 +</pre> +<p> + 在响应表 Line2 中第 5 行的内容 URI +</p> +<pre> +content://com.example.trains/Line2/5 +</pre> +<p> + 时,提供程序会返回 MIME 类型 +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.example.line2 +</pre> +<p> + 大多数内容提供程序都会为其使用的 MIME 类型定义协定类常量。例如,联系人提供程序协定类 {@link android.provider.ContactsContract.RawContacts} 会为单个原始联系人行的 MIME 类型定义常量 + {@link android.provider.ContactsContract.RawContacts#CONTENT_ITEM_TYPE}。 + + + +</p> +<p> + +<a href="#ContentURIs">内容 URI</a> 部分介绍了单个行的内容 URI。 +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd new file mode 100644 index 000000000000..6da57435f43d --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-provider-creating.jd @@ -0,0 +1,1214 @@ +page.title=创建内容提供程序 +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + +<h2>本文内容</h2> +<ol> + <li> + <a href="#DataStorage">设计数据存储</a> + </li> + <li> + <a href="#ContentURI">设计内容 URI</a> + </li> + <li> + <a href="#ContentProvider">实现 ContentProvider 类</a> + <ol> + <li> + <a href="#RequiredAccess">必需方法</a> + </li> + <li> + <a href="#Query">实现 query() 方法</a> + </li> + <li> + <a href="#Insert">实现 insert() 方法</a> + </li> + <li> + <a href="#Delete">实现 delete() 方法</a> + </li> + <li> + <a href="#Update">实现 update() 方法</a> + </li> + <li> + <a href="#OnCreate">实现 onCreate() 方法</a> + </li> + </ol> + </li> + <li> + <a href="#MIMETypes">实现内容提供程序 MIME 类型</a> + <ol> + <li> + <a href="#TableMIMETypes">表的 MIME 类型</a> + </li> + <li> + <a href="#FileMIMETypes">文件的 MIME 类型</a> + </li> + </ol> + </li> + <li> + <a href="#ContractClass">实现协定类</a> + </li> + <li> + <a href="#Permissions">实现内容提供程序权限</a> + </li> + <li> + <a href="#ProviderElement"><Provider> 元素</a> + </li> + <li> + <a href="#Intents"> Intent 和数据访问</a> + </li> +</ol> +<h2>关键类</h2> + <ol> + <li> + {@link android.content.ContentProvider} + </li> + <li> + {@link android.database.Cursor} + </li> + <li> + {@link android.net.Uri} + </li> + </ol> +<h2>相关示例</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/NotePad/index.html"> + 记事本示例应用 + </a> + </li> + </ol> +<h2>另请参阅</h2> + <ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + 内容提供程序基础知识</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html"> + 日历提供程序</a> + </li> + </ol> +</div> +</div> + + +<p> + 内容提供程序管理对中央数据存储库的访问。您将 + 提供程序作为 Android 应用中的一个或多个类(连同清单文件 + 中的元素)实现。其中一个类会实现子类 + {@link android.content.ContentProvider},即您的提供程序与 + 其他应用之间的界面。尽管内容提供程序旨在向其他应用提供 + 数据,但您的应用中必定有这样一些 Activity,它们允许用户 + 查询和修改由提供程序管理的数据。 +</p> +<p> + 本主题的其余部分列出了开发内容提供程序的基本步骤和 + 需要使用的 API。 +</p> + + +<!-- Before You Start Building --> +<h2 id="BeforeYouStart">着手开发前的准备工作</h2> +<p> + 请在着手开发提供程序之前执行以下操作: +</p> +<ol> + <li> + <strong>决定您是否需要内容提供程序</strong>。如果您想提供下列一项或多项功能,则需要开发内容 + 提供程序: + <ul> + <li>您想为其他应用提供复杂的数据或文件</li> + <li>您想允许用户将复杂的数据从您的应用复制到其他应用中</li> + <li>您想使用搜索框架提供自定义搜索建议</li> + </ul> + <p> + 如果完全是在 + 您自己的应用中使用,则“根本不”<em></em>需要提供程序即可使用 SQLite 数据库。 + </p> + </li> + <li> + 如果您尚未完成此项操作,请阅读 + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + 内容提供程序基础知识</a>主题,了解有关提供程序的详情。 + </li> +</ol> +<p> + 接下来,请按照以下步骤开发您的提供程序: +</p> +<ol> + <li> + 为您的数据设计原始存储。内容提供程序以两种方式提供数据: + <dl> + <dt> + 文件数据 + </dt> + <dd> + 通常存储在文件中的数据,如 + 照片、音频或视频。将文件存储在您的应用的私有 + 空间内。您的提供程序可以应其他应用发出的文件请求 + 提供文件句柄。 + </dd> + <dt> + “结构化”数据 + </dt> + <dd> + 通常存储在数据库、数组或类似结构中的数据。 + 以兼容行列表的形式存储数据。行 + 表示实体,如人员或库存项目。列表示 + 实体的某项数据,如人员的姓名或商品的价格。此类数据通常 + 存储在 SQLite 数据库中,但您可以使用任何类型的 + 持久存储。如需了解有关 + Android 系统中提供的存储类型的更多信息,请参阅<a href="#DataStorage"> + 设计数据存储</a>部分。 + </dd> + </dl> + </li> + <li> + 定义 {@link android.content.ContentProvider} 类及其 + 所需方法的具体实现。此类是您的数据与 + Android 系统其余部分之间的界面。如需了解有关此类的详细信息,请参阅 + <a href="#ContentProvider">实现 ContentProvider 类</a>部分。 + </li> + <li> + 定义提供程序的权限字符串、其内容 URI 以及列名称。如果您想让 + 提供程序的应用处理 Intent,则还要定义 Intent 操作、Extra 数据 + 以及标志。此外,还要定义想要访问您的数据的应用必须具备的权限。 +您应该考虑在一个单独的协定类中将所有这些值定义为常量;以后您可以将此类公开给其他开发者。 +如需了解有关内容 URI 的详细信息,请参阅<a href="#ContentURI">设计内容 URI</a> 部分。 + + + 如需了解有关 Intent 的详细信息,请参阅<a href="#Intents"> Intent 和数据访问</a>部分。 + + </li> + <li> + 添加其他可选部分,如示例数据或可以在提供程序与云数据之间同步数据的 {@link android.content.AbstractThreadedSyncAdapter} 实现。 + + + </li> +</ol> + + +<!-- Designing Data Storage --> +<h2 id="DataStorage">设计数据存储</h2> +<p> + 内容提供程序是用于访问以结构化格式保存的数据的界面。在您创建该界面之前,必须决定如何存储数据。 +您可以按自己的喜好以任何形式存储数据,然后根据需要设计读写数据的界面。 + +</p> +<p> + 以下是 Android 中提供的一些数据存储技术: +</p> +<ul> + <li> + Android 系统包括一个 SQLite 数据库 API,Android 自己的提供程序使用它来存储面向表的数据。 +{@link android.database.sqlite.SQLiteOpenHelper} 类可帮助您创建数据库,{@link android.database.sqlite.SQLiteDatabase} 类是用于访问数据库的基类。 + + + + <p> + 请记住,您不必使用数据库来实现存储库。提供程序在外部表现为一组表,与关系数据库类似,但这并不是对提供程序内部实现的要求; + + + </p> + </li> + <li> + 对于存储文件数据,Android 提供了各种面向文件的 API。 + 如需了解有关文件存储的更多信息,请阅读<a href="{@docRoot}guide/topics/data/data-storage.html">数据存储</a>主题。 +如果您要设计提供媒体相关数据(如音乐或视频)的提供程序,则可开发一个合并了表数据和文件的提供程序; + + + </li> + <li> + 要想使用基于网络的数据,请使用 {@link java.net} 和 {@link android.net} 中的类。 +您也可以将基于网络的数据与本地数据存储(如数据库)同步,然后以表或文件的形式提供数据。 + + <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html">示例同步适配器</a>示例应用展示了这类同步。 + + </li> +</ul> +<h3 id="DataDesign"> + 数据设计考虑事项 +</h3> +<p> + 以下是一些设计提供程序数据结构的技巧: +</p> +<ul> + <li> + 表数据应始终具有一个“主键”列,提供程序将其作为与每行对应的唯一数字值加以维护。 +您可以使用此值将该行链接到其他表中的相关行(将其用作“外键”)。 +尽管您可以为此列使用任何名称,但使用 {@link android.provider.BaseColumns#_ID BaseColumns._ID} 是最佳选择,因为将提供程序查询的结果链接到 {@link android.widget.ListView} 的条件是,检索到的其中一个列的名称必须是 <code>_ID</code>; + + + + + </li> + <li> + 如果您想提供位图图像或其他非常庞大的文件导向型数据,请将数据存储在一个文件中,然后间接提供这些数据,而不是直接将其存储在表中。 + +如果您执行了此操作,则需要告知提供程序的用户,他们需要使用 {@link android.content.ContentResolver} 文件方法来访问数据; + + </li> + <li> + 使用二进制大型对象 (BLOB) 数据类型存储大小或结构会发生变化的数据。 +例如,您可以使用 BLOB 列来存储<a href="http://code.google.com/p/protobuf">协议缓冲区</a>或 <a href="http://www.json.org">JSON 结构</a>。 + + + <p> + 您也可以使用 BLOB 来实现<em>独立于架构</em>的表。在这类表中,您需要以 BLOB 形式定义一个主键列、一个 MIME 类型列以及一个或多个通用列。 + +这些 BLOB 列中数据的含义通过 MIME 类型列中的值指示。 +这样一来,您就可以在同一表中存储不同类型的行。 +举例来说,联系人提供程序的“数据”表 {@link android.provider.ContactsContract.Data} 便是一个独立于架构的表。 + + + </p> + </li> +</ul> +<!-- Designing Content URIs --> +<h2 id="ContentURI">设计内容 URI</h2> +<p> + <strong>内容 URI</strong> 是用于在提供程序中标识数据的 URI。内容 URI 包括整个提供程序的符号名称(其<strong>权限</strong>)和一个指向表或文件的名称(<strong>路径</strong>)。 + +可选 ID 部分指向 + 表中的单个行。 + {@link android.content.ContentProvider} 的每一个数据访问方法都将内容 URI 作为参数;您可以利用这一点确定要访问的表、行或文件。 + +</p> +<p> + + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html">内容提供程序基础知识</a>主题中描述了内容 URI 的基础知识。 + +</p> +<h3>设计权限</h3> +<p> + 提供程序通常具有单一权限,该权限充当其 Android 内部名称。为避免与其他提供程序发生冲突,您应该使用 Internet 域所有权(反向)作为提供程序权限的基础。 + +由于此建议也适用于 Android 软件包名称,因此您可以将提供程序权限定义为包含该提供程序的软件包名称的扩展名。 + +例如,如果您的 Android 软件包名称为 + <code>com.example.<appname></code>,则应为提供程序授予权限 <code>com.example.<appname>.provider</code>。 + +</p> +<h3>设计路径结构</h3> +<p> + 开发者通常通过追加指向单个表的路径来根据权限创建内容 URI。 +例如,如果您有两个表:<em>table1</em> 和 + <em>table2</em>,则可以通过合并上一示例中的权限来生成 + 内容 URI + <code>com.example.<appname>.provider/table1</code> 和 + <code>com.example.<appname>.provider/table2</code>。路径并不限定于单个段,也无需为每一级路径都创建一个表。 + +</p> +<h3>处理内容 URI ID</h3> +<p> + 按照惯例,提供程序通过接受末尾具有行所对应 ID 值的内容 URI 来提供对表中单个行的访问。同样按照惯例,提供程序会将该 ID 值与表的 <code>_ID</code> 列进行匹配,并对匹配的行执行请求的访问。 + + + +</p> +<p> + 这一惯例为访问提供程序的应用的常见设计模式提供了便利。应用会对提供程序执行查询,并使用 {@link android.widget.CursorAdapter} 以 {@link android.widget.ListView} 显示生成的 {@link android.database.Cursor}。 + + + 定义 {@link android.widget.CursorAdapter} 的条件是, + {@link android.database.Cursor} 中的其中一个列必须是 <code>_ID</code> +</p> +<p> + 用户随后从 UI 上显示的行中选取其中一行,以查看或修改数据。 +应用会从支持 + {@link android.widget.ListView} 的 {@link android.database.Cursor} 中获取对应行,获取该行的 <code>_ID</code> 值,将其追加到内容 URI,然后向提供程序发送访问请求。 +然后,提供程序便可对用户选取的特定行执行查询或修改。 + +</p> +<h3>内容 URI 模式</h3> +<p> + 为帮助您选择对传入的内容 URI 执行的操作,提供程序 API 加入了实用类 {@link android.content.UriMatcher},它会将内容 URI“模式”映射到整型值。 + +您可以在一个 <code>switch</code> 语句中使用这些整型值,为匹配特定模式的一个或多个内容 URI 选择所需操作。 + +</p> +<p> + 内容 URI 模式使用通配符匹配内容 URI: +</p> + <ul> + <li> + <strong><code>*</code></strong>:匹配由任意长度的任何有效字符组成的字符串 + </li> + <li> + <strong><code>#</code></strong>:匹配由任意长度的数字字符组成的字符串 + </li> + </ul> +<p> + 以设计和编码内容 URI 处理为例,假设一个具有权限 <code>com.example.app.provider</code> 的提供程序能识别以下指向表的内容 URI: + + +</p> +<ul> + <li> + <code>content://com.example.app.provider/table1</code>:一个名为 <code>table1</code> 的表 + </li> + <li> + <code>content://com.example.app.provider/table2/dataset1</code>:一个名为 + <code>dataset1</code> 的表 + </li> + <li> + <code>content://com.example.app.provider/table2/dataset2</code>:一个名为 + <code>dataset2</code> 的表 + </li> + <li> + <code>content://com.example.app.provider/table3</code>:一个名为 <code>table3</code> 的表 + </li> +</ul> +<p> + 提供程序也能识别追加了行 ID 的内容 URI,例如,<code>content://com.example.app.provider/table3/1</code> 对应由 <code>table3</code> 中 <code>1</code> 标识的行的内容 URI。 + + +</p> +<p> + 可以使用以下内容 URI 模式: +</p> +<dl> + <dt> + <code>content://com.example.app.provider/*</code> + </dt> + <dd> + 匹配提供程序中的任何内容 URI。 + </dd> + <dt> + <code>content://com.example.app.provider/table2/*</code>: + </dt> + <dd> + 匹配表 <code>dataset1</code> + 和表 <code>dataset2</code> 的内容 URI,但不匹配 <code>table1</code> 或 + <code>table3</code> 的内容 URI。 + </dd> + <dt> + <code>content://com.example.app.provider/table3/#</code>:匹配 + <code>table3</code> 中单个行的内容 URI,如 <code>content://com.example.app.provider/table3/6</code> 对应由 + <code>6</code> 标识的行的内容 URI。 + + </dt> +</dl> +<p> + 以下代码段说明了 {@link android.content.UriMatcher} 中方法的工作方式。 + 此代码采用不同方式处理整个表的 URI 与单个行的 URI,它为表使用的内容 URI 模式是 + <code>content://<authority>/<path></code>,为单个行使用的内容 URI 模式则是 + <code>content://<authority>/<path>/<id></code>。 + +</p> +<p> + 方法 {@link android.content.UriMatcher#addURI(String, String, int) addURI()} 会将权限和路径映射到一个整型值。 +方法 {@link android.content.UriMatcher#match(Uri) + match()} 会返回 URI 的整型值。<code>switch</code> 语句会在查询整个表与查询单个记录之间进行选择: + +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider { +... + // Creates a UriMatcher object. + private static final UriMatcher sUriMatcher; +... + /* + * The calls to addURI() go here, for all of the content URI patterns that the provider + * should recognize. For this snippet, only the calls for table 3 are shown. + */ +... + /* + * Sets the integer value for multiple rows in table 3 to 1. Notice that no wildcard is used + * in the path + */ + sUriMatcher.addURI("com.example.app.provider", "table3", 1); + + /* + * Sets the code for a single row to 2. In this case, the "#" wildcard is + * used. "content://com.example.app.provider/table3/3" matches, but + * "content://com.example.app.provider/table3 doesn't. + */ + sUriMatcher.addURI("com.example.app.provider", "table3/#", 2); +... + // Implements ContentProvider.query() + public Cursor query( + Uri uri, + String[] projection, + String selection, + String[] selectionArgs, + String sortOrder) { +... + /* + * Choose the table to query and a sort order based on the code returned for the incoming + * URI. Here, too, only the statements for table 3 are shown. + */ + switch (sUriMatcher.match(uri)) { + + + // If the incoming URI was for all of table3 + case 1: + + if (TextUtils.isEmpty(sortOrder)) sortOrder = "_ID ASC"; + break; + + // If the incoming URI was for a single row + case 2: + + /* + * Because this URI was for a single row, the _ID value part is + * present. Get the last path segment from the URI; this is the _ID value. + * Then, append the value to the WHERE clause for the query + */ + selection = selection + "_ID = " uri.getLastPathSegment(); + break; + + default: + ... + // If the URI is not recognized, you should do some error handling here. + } + // call the code to actually do the query + } +</pre> +<p> + 另一个类 {@link android.content.ContentUris} 会提供一些工具方法,用于处理内容 URI 的 <code>id</code> 部分。 +{@link android.net.Uri} 类和 + {@link android.net.Uri.Builder} 类包括一些工具方法,用于解析现有 + {@link android.net.Uri} 对象和构建新对象。 +</p> + +<!-- Implementing the ContentProvider class --> +<h2 id="ContentProvider">实现 ContentProvider 类</h2> +<p> + {@link android.content.ContentProvider} 实例通过处理来自其他应用的请求来管理对结构化数据集的访问。 +所有形式的访问最终都会调用 {@link android.content.ContentResolver},后者接着调用 + {@link android.content.ContentProvider} 的具体方法来获取访问权限。 + +</p> +<h3 id="RequiredAccess">必需方法</h3> +<p> + 抽象类 {@link android.content.ContentProvider} 定义了六个抽象方法,您必须将这些方法作为自己具体子类的一部分加以实现。 +所有这些方法( + {@link android.content.ContentProvider#onCreate() onCreate()} 除外)都由一个尝试访问您的内容提供程序的客户端应用调用: + +</p> +<dl> + <dt> + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + query()} + </dt> + <dd> + 从您的提供程序检索数据。使用参数选择要查询的表、要返回的行和列以及结果的排序顺序。 + + 将数据作为 {@link android.database.Cursor} 对象返回。 + </dd> + <dt> + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} + </dt> + <dd> + 在您的提供程序中插入一个新行。使用参数选择目标表并获取要使用的列值。 +返回新插入行的内容 URI。 + + </dd> + <dt> + {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} + </dt> + <dd> + 更新您提供程序中的现有行。使用参数选择要更新的表和行,并获取更新后的列值。 +返回已更新的行数。 + </dd> + <dt> + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} + </dt> + <dd> + 从您的提供程序中删除行。使用参数选择要删除的表和行。 +返回已删除的行数。 + </dd> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + 返回内容 URI 对应的 MIME 类型。<a href="#MIMETypes">实现内容提供程序 MIME 类型</a>部分对此方法做了更详尽的描述。 + + </dd> + <dt> + {@link android.content.ContentProvider#onCreate() onCreate()} + </dt> + <dd> + 初始化您的提供程序。Android 系统会在创建您的提供程序后立即调用此方法。 +请注意, + {@link android.content.ContentResolver} 对象尝试访问您的提供程序时,系统才会创建它。 + </dd> +</dl> +<p> + 请注意,这些方法的签名与同名的 + {@link android.content.ContentResolver} 方法相同。 +</p> +<p> + 您在实现这些方法时应考虑以下事项: +</p> +<ul> + <li> + 所有这些方法({@link android.content.ContentProvider#onCreate() onCreate()} + 除外)都可由多个线程同时调用,因此它们必须是线程安全方法。如需了解有关多个线程的更多信息,请参阅<a href="{@docRoot}guide/components/processes-and-threads.html">进程和线程</a>主题; + + + + </li> + <li> + 避免在 {@link android.content.ContentProvider#onCreate() + onCreate()} 中执行长时间操作。将初始化任务推迟到实际需要时进行。 + <a href="#OnCreate">实现 onCreate() 方法</a>部分对此做了更详尽的描述; + + </li> + <li> + 尽管您必须实现这些方法,但您的代码只需返回要求的数据类型,无需执行任何其他操作。 +例如,您可能想防止其他应用向某些表插入数据。 +要实现此目的,您可以忽略 + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 调用并返回 0。 + + </li> +</ul> +<h3 id="Query">实现 query() 方法</h3> +<p> + + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 方法必须返回 {@link android.database.Cursor} 对象。如果失败,则会引发 {@link java.lang.Exception}。 +如果您使用 SQLite 数据库作为数据存储,则只需返回由 {@link android.database.sqlite.SQLiteDatabase} 类的其中一个 + <code>query()</code> 方法返回的 {@link android.database.Cursor}。 + + 如果查询不匹配任何行,您应该返回一个 {@link android.database.Cursor} + 实例(其 {@link android.database.Cursor#getCount()} 方法返回 0)。 + 只有当查询过程中出现内部错误时,您才应该返回 <code>null</code>。 +</p> +<p> + 如果您不使用 SQLite 数据库作为数据存储,请使用 {@link android.database.Cursor} 的其中一个具体子类。 +例如,在 {@link android.database.MatrixCursor} 类实现的游标中,每一行都是一个 {@link java.lang.Object} 数组。 +对于此类,请使用 {@link android.database.MatrixCursor#addRow(Object[]) addRow()} 来添加新行。 + +</p> +<p> + 请记住,Android 系统必须能够跨进程边界传播 {@link java.lang.Exception}。 +Android 可以为以下异常执行此操作,这些异常可能有助于处理查询错误: + +</p> +<ul> + <li> + {@link java.lang.IllegalArgumentException}(您可以选择在提供程序收到无效的内容 URI 时引发此异常) + + </li> + <li> + {@link java.lang.NullPointerException} + </li> +</ul> +<h3 id="Insert">实现 insert() 方法</h3> +<p> + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 方法会使用 {@link android.content.ContentValues} + 参数中的值向相应表中添加新行。 +如果 {@link android.content.ContentValues} 参数中未包含列名称,您可能想在您的提供程序代码或数据库架构中提供其默认值。 + + +</p> +<p> + 此方法应该返回新行的内容 URI。要想构建此方法,请使用 + {@link android.content.ContentUris#withAppendedId(Uri, long) withAppendedId()} 向表的内容 URI 追加新行的 <code>_ID</code>(或其他主键)值。 + +</p> +<h3 id="Delete">实现 delete() 方法</h3> +<p> + {@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 方法不需要从您的数据存储中实际删除行。 +如果您将同步适配器与提供程序一起使用,应该考虑为已删除的行添加“删除”标志,而不是将行整个移除。 + +同步适配器可以检查是否存在已删除的行,并将它们从服务器中移除,然后再将它们从提供程序中删除。 + +</p> +<h3 id="Update">实现 update() 方法</h3> +<p> + {@link android.content.ContentProvider#update(Uri, ContentValues, String, String[]) + update()} 方法采用 + {@link android.content.ContentProvider#insert(Uri, ContentValues) insert()} 所使用的相同 {@link android.content.ContentValues} 参数,以及 +{@link android.content.ContentProvider#delete(Uri, String, String[]) delete()} 和 + {@link android.content.ContentProvider#query(Uri, String[], String, String[], String) + ContentProvider.query()} 所使用的相同 <code>selection</code> 和 <code>selectionArgs</code> 参数。 +这样一来,您就可以在这些方法之间重复使用代码。 +</p> +<h3 id="OnCreate">实现 onCreate() 方法</h3> +<p> + Android 系统会在启动提供程序时调用 {@link android.content.ContentProvider#onCreate() + onCreate()}。您只应在此方法中执行运行快速的初始化任务,并将数据库创建和数据加载推迟到提供程序实际收到数据请求时进行。 + +如果您在 + {@link android.content.ContentProvider#onCreate() onCreate()} 中执行长时间的任务,则会减慢提供程序的启动速度, +进而减慢提供程序对其他应用的响应速度。 + +</p> +<p> + 例如,如果您使用 SQLite 数据库,可以在 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 中创建一个新的 {@link android.database.sqlite.SQLiteOpenHelper} 对象,然后在首次打开数据库时创建 SQL 表。 + +为简化这一过程,在您首次调用 {@link android.database.sqlite.SQLiteOpenHelper#getWritableDatabase + getWritableDatabase()} 时,它会自动调用 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 方法。 + +</p> +<p> + 以下两个代码段展示了 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 与 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 之间的交互。第一个代码段是 + {@link android.content.ContentProvider#onCreate() ContentProvider.onCreate()} 的实现: +</p> +<pre class="prettyprint"> +public class ExampleProvider extends ContentProvider + + /* + * Defines a handle to the database helper object. The MainDatabaseHelper class is defined + * in a following snippet. + */ + private MainDatabaseHelper mOpenHelper; + + // Defines the database name + private static final String DBNAME = "mydb"; + + // Holds the database object + private SQLiteDatabase db; + + public boolean onCreate() { + + /* + * Creates a new helper object. This method always returns quickly. + * Notice that the database itself isn't created or opened + * until SQLiteOpenHelper.getWritableDatabase is called + */ + mOpenHelper = new MainDatabaseHelper( + getContext(), // the application context + DBNAME, // the name of the database) + null, // uses the default SQLite cursor + 1 // the version number + ); + + return true; + } + + ... + + // Implements the provider's insert method + public Cursor insert(Uri uri, ContentValues values) { + // Insert code here to determine which table to open, handle error-checking, and so forth + + ... + + /* + * Gets a writeable database. This will trigger its creation if it doesn't already exist. + * + */ + db = mOpenHelper.getWritableDatabase(); + } +} +</pre> +<p> + 下一个代码段是 + {@link android.database.sqlite.SQLiteOpenHelper#onCreate(SQLiteDatabase) + SQLiteOpenHelper.onCreate()} 的实现,其中包括一个帮助程序类: +</p> +<pre class="prettyprint"> +... +// A string that defines the SQL statement for creating a table +private static final String SQL_CREATE_MAIN = "CREATE TABLE " + + "main " + // Table's name + "(" + // The columns in the table + " _ID INTEGER PRIMARY KEY, " + + " WORD TEXT" + " FREQUENCY INTEGER " + + " LOCALE TEXT )"; +... +/** + * Helper class that actually creates and manages the provider's underlying data repository. + */ +protected static final class MainDatabaseHelper extends SQLiteOpenHelper { + + /* + * Instantiates an open helper for the provider's SQLite data repository + * Do not do database creation and upgrade here. + */ + MainDatabaseHelper(Context context) { + super(context, DBNAME, null, 1); + } + + /* + * Creates the data repository. This is called when the provider attempts to open the + * repository and SQLite reports that it doesn't exist. + */ + public void onCreate(SQLiteDatabase db) { + + // Creates the main table + db.execSQL(SQL_CREATE_MAIN); + } +} +</pre> + + +<!-- Implementing ContentProvider MIME Types --> +<h2 id="MIMETypes">实现 ContentProvider MIME 类型</h2> +<p> + {@link android.content.ContentProvider} 类具有两个返回 MIME 类型的方法: +</p> +<dl> + <dt> + {@link android.content.ContentProvider#getType(Uri) getType()} + </dt> + <dd> + 您必须为任何提供程序实现的必需方法之一。 + </dd> + <dt> + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + </dt> + <dd> + 系统在您的提供程序提供文件时要求实现的方法。 + </dd> +</dl> +<h3 id="TableMIMETypes">表的 MIME 类型</h3> +<p> + {@link android.content.ContentProvider#getType(Uri) getType()} 方法会返回一个 MIME 格式的 + {@link java.lang.String},后者描述内容 + URI 参数返回的数据类型。{@link android.net.Uri} 参数可以是模式,而不是特定 URI;在这种情况下,您应该返回与匹配该模式的内容 URI 关联的数据类型。 + + +</p> +<p> + 对于文本、HTML 或 JPEG 等常见数据类型, + {@link android.content.ContentProvider#getType(Uri) getType()} 应该为该数据返回标准 + MIME 类型。 + <a href="http://www.iana.org/assignments/media-types/index.htm">IANA MIME Media Types</a> + 网站上提供了这些标准类型的完整列表。 +</p> +<p> + 对于指向一个或多个表数据行的内容 URI, + {@link android.content.ContentProvider#getType(Uri) getType()} 应该以 Android 供应商特有 MIME 格式返回 + MIME 类型: +</p> +<ul> + <li> + 类型部分:<code>vnd</code> + </li> + <li> + 子类型部分: + <ul> + <li> + 如果 URI 模式用于单个行:<code>android.cursor.<strong>item</strong>/</code> + </li> + <li> + 如果 URI 模式用于多个行:<code>android.cursor.<strong>dir</strong>/</code> + </li> + </ul> + </li> + <li> + 提供程序特有部分:<code>vnd.<name></code>.<code><type></code> + <p> + 您提供 <code><name></code> 和 <code><type></code>。 + <code><name></code> 值应具有全局唯一性, + <code><type></code> 值应在对应的 URI + 模式中具有唯一性。适合选择贵公司的名称或您的应用 Android 软件包名称的某个部分作为 <code><name></code>。 +适合选择 URI 关联表的标识字符串作为 <code><type></code>。 + + + </p> + + </li> +</ul> +<p> + 例如,如果提供程序的权限是 + <code>com.example.app.provider</code>,并且它公开了一个名为 + <code>table1</code> 的表,则 <code>table1</code> 中多个行的 MIME 类型是: +</p> +<pre> +vnd.android.cursor.<strong>dir</strong>/vnd.com.example.provider.table1 +</pre> +<p> + 对于 <code>table1</code> 的单个行,MIME 类型是: +</p> +<pre> +vnd.android.cursor.<strong>item</strong>/vnd.com.example.provider.table1 +</pre> +<h3 id="FileMIMETypes">文件的 MIME 类型</h3> +<p> + 如果您的提供程序提供文件,请实现 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()}。 + 该方法会为您的提供程序可以为给定内容 URI 返回的文件返回一个 MIME 类型 {@link java.lang.String} 数组。您应该通过 MIME 类型过滤器参数过滤您提供的 MIME 类型,以便只返回客户端想处理的那些 MIME 类型。 + + +</p> +<p> + 例如,假设提供程序以 <code>.jpg</code>、 + <code>.png</code> 和 <code>.gif</code> 格式文件形式提供照片图像。 + 如果应用调用 {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} 时使用了过滤器字符串 <code>image/*</code>(任何是“图像”的内容),则 {@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} 方法应返回数组: + + +</p> +<pre> +{ "image/jpeg", "image/png", "image/gif"} +</pre> +<p> + 如果应用只对 <code>.jpg</code> 文件感兴趣,则可以在调用 + {@link android.content.ContentResolver#getStreamTypes(Uri, String) + ContentResolver.getStreamTypes()} 时使用过滤器字符串 <code>*\/jpeg</code>,{@link android.content.ContentProvider#getStreamTypes(Uri, String) + ContentProvider.getStreamTypes()} 应返回: + +<pre> +{"image/jpeg"} +</pre> +<p> + 如果您的提供程序未提供过滤器字符串中请求的任何 MIME 类型,则 + {@link android.content.ContentProvider#getStreamTypes(Uri, String) getStreamTypes()} + 应返回 <code>null</code>。 +</p> + + +<!-- Implementing a Contract Class --> +<h2 id="ContractClass">实现协定类</h2> +<p> + 协定类是一种 <code>public final</code> 类,其中包含对 URI、列名称、MIME 类型以及其他与提供程序有关的元数据的常量定义。 +该类可确保即使 URI、列名称等数据的实际值发生变化,也可以正确访问提供程序,从而在提供程序与其他应用之间建立合同。 + + + +</p> +<p> + 协定类对开发者也有帮助,因为其常量通常采用助记名称,因此可以降低开发者为列名称或 URI 使用错误值的可能性。 +由于它是一种类,因此可以包含 Javadoc 文档。 +集成开发环境(如 + Eclipse)可以根据协定类自动完成常量名称,并为常量显示 Javadoc。 + +</p> +<p> + 开发者无法从您的应用访问协定类的类文件,但他们可以通过您提供的 <code>.jar</code> 文件将其静态编译到其应用内。 + +</p> +<p> + 举例来说,{@link android.provider.ContactsContract} 类及其嵌套类便属于协定类。 + +</p> +<h2 id="Permissions">实现内容提供程序权限</h2> +<p> + <a href="{@docRoot}guide/topics/security/security.html">安全与权限</a>主题中详细描述了 Android 系统各个方面的权限和访问。 + + <a href="{@docRoot}guide/topics/data/data-storage.html">数据存储</a>主题也描述了各类存储实行中的安全与权限。 + + 其中的要点简述如下: +</p> +<ul> + <li> + 默认情况下,存储在设备内部存储上的数据文件是您的应用和提供程序的私有数据文件; + + </li> + <li> + 您创建的 {@link android.database.sqlite.SQLiteDatabase} 数据库是您的应用和提供程序的私有数据库; + + </li> + <li> + 默认情况下,您保存到外部存储的数据文件是<em>公用</em>并<em>可全局读取</em>的数据文件。 +您无法使用内容提供程序来限制对外部存储内文件的访问,因为其他应用可以使用其他 API 调用来对它们执行读取和写入操作; + + </li> + <li> + 用于在您的设备的内部存储上打开或创建文件或 SQLite 数据库的方法调用可能会为所有其他应用同时授予读取和写入访问权限。 +如果您将内部文件或数据库用作提供程序的存储库,并向其授予“可全局读取”或“可全局写入”访问权限,则您在清单文件中为提供程序设置的权限不会保护您的数据。 + + +内部存储中文件和数据库的默认访问权限是“私有”,对于提供程序的存储库,您不应更改此权限。 + + </li> +</ul> +<p> + 如果您想使用内容提供程序权限来控制对数据的访问,则应将数据存储在内部文件、SQLite 数据库或“云”中(例如,远程服务器上),而且您应该保持文件和数据库为您的应用所私有。 + + +</p> +<h3>实现权限</h3> +<p> + 即使底层数据为私有数据,所有应用仍可从您的提供程序读取数据或向其写入数据,因为在默认情况下,您的提供程序未设置权限。 +要想改变这种情况,请使用属性或 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的子元素在您的清单文件中为您的提供程序设置权限。 +您可以设置适用于整个提供程序、特定表、甚至特定记录的权限,或者设置同时适用于这三者的权限。 + +</p> +<p> + 您可以通过清单文件中的一个或多个 + <code><a href="{@docRoot}guide/topics/manifest/permission-element.html"> + <permission></a></code> 元素为您的提供程序定义权限。要使权限对您的提供程序具有唯一性,请为 + <code><a href="{@docRoot}guide/topics/manifest/permission-element.html#nm"> + android:name</a></code> 属性使用 Java 风格作用域。 +例如,将读取权限命名为 + <code>com.example.app.provider.permission.READ_PROVIDER</code>。 + +</p> +<p> + 以下列表描述了提供程序权限的作用域,从适用于整个提供程序的权限开始,然后逐渐细化。 + + 更细化的权限优先于作用域较大的权限: +</p> +<dl> + <dt> + 统一读写提供程序级别权限 + </dt> + <dd> + 一个同时控制对整个提供程序读取和写入访问的权限,通过 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的 <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code> 属性指定。 + + </dd> + <dt> + 单独的读取和写入提供程序级别权限 + </dt> + <dd> + 针对整个提供程序的读取权限和写入权限。您可以通过 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的 <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code> 属性和 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code> 属性 + 指定它们。它们优先于 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code> 所需的权限。 + </dd> + <dt> + 路径级别权限 + </dt> + <dd> + 针对提供程序中内容 URI 的读取、写入或读取/写入权限。您可以通过 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的 <code><a href="{@docRoot}guide/topics/manifest/path-permission-element.html"> + <path-permission></a></code> 子元素指定您想控制的每个 URI。 + +对于您指定的每个内容 URI,您都可以指定读取/写入权限、读取权限或写入权限,或同时指定所有三种权限。 +读取权限和写入权限优先于读取/写入权限。 +此外,路径级别权限优先于提供程序级别权限。 + + </dd> + <dt> + 临时权限 + </dt> + <dd> + 一种权限级别,即使应用不具备通常需要的权限,该级别也能授予对应用的临时访问权限。 +临时访问功能可减少应用需要在其清单文件中请求的权限数量。 + +当您启用临时权限时,只有持续访问您的所有数据的应用才需要“永久性”提供程序访问权限。 + + + <p> + 假设您需要实现电子邮件提供程序和应用的权限,如果您想允许外部图像查看器应用显示您的提供程序中的照片附件, + +为了在不请求权限的情况下为图像查看器提供必要的访问权限,可以为照片的内容 URI 设置临时权限。 +对您的电子邮件应用进行相应设计,使应用能够在用户想要显示照片时向图像查看器发送一个 Intent,其中包含照片的内容 URI 以及权限标志。 + +图像查看器可随后查询您的电子邮件提供程序以检索照片,即使查看器不具备对您提供程序的正常读取权限,也不受影响。 + + + </p> + <p> + 要想启用临时权限,请设置 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> 属性,或者向您的 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素添加一个或多个 + <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code> 子元素。如果您使用了临时权限,则每当您从提供程序中移除对某个内容 URI 的支持,并且该内容 URI 关联了临时权限时,都需要调用 + {@link android.content.Context#revokeUriPermission(Uri, int) + Context.revokeUriPermission()}。 + + </p> + <p> + 该属性的值决定可访问的提供程序范围。 + 如果该属性设置为 <code>true</code>,则系统会向整个提供程序授予临时权限,该权限将替代您的提供程序级别或路径级别权限所需的任何其他权限。 + + + </p> + <p> + 如果此标志设置为 <code>false</code>,则您必须向 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素添加 + <code><a href="{@docRoot}guide/topics/manifest/grant-uri-permission-element.html"> + <grant-uri-permission></a></code> 子元素。每个子元素都指定授予的临时权限所对应的一个或多个内容 URI。 + + </p> + <p> + 要向应用授予临时访问权限, Intent 必须包含 + {@link android.content.Intent#FLAG_GRANT_READ_URI_PERMISSION} 和/或 + {@link android.content.Intent#FLAG_GRANT_WRITE_URI_PERMISSION} 标志。它们通过 {@link android.content.Intent#setFlags(int) setFlags()} 方法进行设置。 + + </p> + <p> + 如果 <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermissions</a></code> 属性不存在,则假设其为 + <code>false</code>。 + </p> + </dd> +</dl> + + + +<!-- The Provider Element --> +<h2 id="ProviderElement"><Provider> 元素</h2> +<p> + 与 {@link android.app.Activity} 和 {@link android.app.Service} 组件类似,必须使用 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素在清单文件中为其应用定义 + {@link android.content.ContentProvider} 的子类。 +Android 系统会从该元素获取以下信息: + +<dl> + <dt> + 权限 + (<a href="{@docRoot}guide/topics/manifest/provider-element.html#auth">{@code + android:authorities}</a>) + </dt> + <dd> + 用于在系统内标识整个提供程序的符号名称。<a href="#ContentURI">设计内容 URI</a> 部分对此属性做了更详尽的描述。 + + + </dd> + <dt> + 提供程序类名 + (<code> +<a href="{@docRoot}guide/topics/manifest/provider-element.html#nm">android:name</a> + </code>) + </dt> + <dd> + 实现 {@link android.content.ContentProvider} 的类。<a href="#ContentProvider">实现 ContentProvider 类</a>中对此类做了更详尽的描述。 + + + </dd> + <dt> + 权限 + </dt> + <dd> + 指定其他应用访问提供程序的数据所必须具备权限的属性: + + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#gprmsn"> + android:grantUriPermssions</a></code>:临时权限标志 + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#prmsn"> + android:permission</a></code>:统一提供程序范围读取/写入权限 + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#rprmsn"> + android:readPermission</a></code>:提供程序范围读取权限 + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#wprmsn"> + android:writePermission</a></code>:提供程序范围写入权限 + </li> + </ul> + <p> + <a href="#Permissions">实现内容提供程序权限</a>部分对权限及其对应属性做了更详尽的描述。 + + + </p> + </dd> + <dt> + 启动和控制属性 + </dt> + <dd> + 这些属性决定 Android 系统如何以及何时启动提供程序、提供程序的进程特性以及其他运行时设置: + + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#enabled"> + android:enabled</a></code>:允许系统启动提供程序的标志。 + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#exported"> + android:exported</a></code>:允许其他应用使用此提供程序的标志。 + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#init"> + android:initOrder</a></code>:此提供程序相对于同一进程中其他提供程序的启动顺序。 + + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#multi"> + android:multiProcess</a></code>:允许系统在与调用客户端相同的进程中启动提供程序的标志。 + + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#proc"> + android:process</a></code>:应在其中运行提供程序的进程的名称。 + + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#sync"> + android:syncable</a></code>:指示提供程序的数据将与服务器上的数据同步的标志。 + + </li> + </ul> + <p> + 开发指南中针对 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> + 元素的主题提供了这些属性的完整资料。 + </p> + </dd> + <dt> + 信息属性 + </dt> + <dd> + 提供程序的可选图标和标签: + <ul> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#icon"> + android:icon</a></code>:包含提供程序图标的 Drawable 资源。 + 该图标出现在<em>设置</em> > <em>应用</em> > <em>全部</em> 中应用列表内的提供程序标签旁; + + </li> + <li> + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html#label"> + android:label</a></code>:描述提供程序和/或其数据的信息标签。 +该标签出现在<em>设置</em> > <em>应用</em> > <em>全部</em>中的应用列表内。 + + </li> + </ul> + <p> + 开发指南中针对 + <code><a href="{@docRoot}guide/topics/manifest/provider-element.html"> + <provider></a></code> 元素的主题提供了这些属性的完整资料。 + </p> + </dd> +</dl> + +<!-- Intent Access --> +<h2 id="Intents">Intent 和数据访问</h2> +<p> + 应用可以通过 {@link android.content.Intent} 间接访问内容提供程序。 + 应用不会调用 {@link android.content.ContentResolver} 或 + {@link android.content.ContentProvider} 的任何方法,而会发送一个启动 Activity 的 Intent,该 Activity 通常是提供程序自身应用的一部分。 +目标 Activity 负责检索和显示其 UI 中的数据。视 Intent 中的操作而定,目标 Activity 可能还会提示用户对提供程序的数据进行修改。 + + + Intent 可能还包含目标 Activity 在 UI 中显示的“extra”数据;用户随后可以选择更改此数据,然后使用它来修改提供程序中的数据。 + + +</p> +<p> + +</p> +<p> + 您可能想使用 Intent 访问权限来帮助确保数据完整性。您的提供程序可能依赖于根据严格定义的业务逻辑插入、更新和删除数据。 +如果是这种情况,则允许其他应用直接修改您的数据可能会导致无效的数据。 + +如果您想让开发者使用 Intent 访问权限,请务必为其提供详尽的参考资料。 + 向他们解释为什么使用自身应用 UI 的 Intent 访问比尝试通过代码修改数据更好。 + +</p> +<p> + 处理想要修改您的提供程序数据的传入 Intent 与处理其他 Intent 没有区别。 +您可以通过阅读<a href="{@docRoot}guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>主题了解有关 Intent 用法的更多信息。 + +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd new file mode 100644 index 000000000000..80c778aad4bf --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/content-providers.jd @@ -0,0 +1,108 @@ +page.title=内容提供程序 +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + + +<!-- In this document --> +<h2>主题</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + 内容提供程序基础知识</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> + 创建内容提供程序</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/calendar-provider.html">日历提供程序</a> + </li> + <li> + <a href="{@docRoot}guide/topics/providers/contacts-provider.html">联系人提供程序</a> + </li> +</ol> + + <!-- Related Samples --> +<h2>相关示例</h2> + <ol> + <li> + <a href="{@docRoot}resources/samples/ContactManager/index.html"> + 联系人管理器</a>应用 + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List2.html"> + “游标(联系人)” + </a> + </li> + <li> + <a href="{@docRoot}resources/samples/ApiDemos/src/com/example/android/apis/view/List7.html"> + “游标(电话)”</a> + </li> + <li> + <a href="{@docRoot}resources/samples/SampleSyncAdapter/index.html"> + 示例同步适配器</a> + </li> + </ol> +</div> +</div> +<p> + 内容提供程序管理对结构化数据集的访问。它们封装数据,并提供用于定义数据安全性的机制。 +内容提供程序是连接一个进程中的数据与另一个进程中运行的代码的标准界面。 + +</p> +<p> + 如果您想要访问内容提供程序中的数据,可以将应用的 {@link android.content.Context} 中的 {@link android.content.ContentResolver} 对象用作客户端来与提供程序通信。 + + + {@link android.content.ContentResolver} 对象会与提供程序对象(即实现 {@link android.content.ContentProvider} 的类实例)通信。 +提供程序对象从客户端接收数据请求,执行请求的操作并返回结果。 + + +</p> +<p> + 如果您不打算与其他应用共享数据,则无需开发自己的提供程序。 +不过,您需要通过自己的提供程序在您自己的应用中提供自定义搜索建议。 +如果您想将复杂的数据或文件从您的应用复制并粘贴到其他应用中,也需要创建您自己的提供程序。 + +</p> +<p> + Android 本身包括的内容提供程序可管理音频、视频、图像和个人联系信息等数据。 +<code><a href="{@docRoot}reference/android/provider/package-summary.html">android.provider</a> + </code> 软件包参考文档中列出了部分提供程序。 + +任何 Android 应用都可以访问这些提供程序,但会受到某些限制。 + +</p><p> + 以下主题对内容提供程序做了更详尽的描述: +</p> +<dl> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> + 内容提供程序基础知识</a></strong> + </dt> + <dd> + 如何访问内容提供程序中以表形式组织的数据。 + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/content-provider-creating.html"> + 创建内容提供程序</a></strong> + </dt> + <dd> + 如何创建您自己的内容提供程序。 + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/calendar-provider.html"> + 日历提供程序</a></strong> + </dt> + <dd> + 如何访问作为 Android 平台一部分的日历提供程序。 + </dd> + <dt> + <strong><a href="{@docRoot}guide/topics/providers/contacts-provider.html"> + 联系人提供程序</a></strong> + </dt> + <dd> + 如何访问作为 Android 平台一部分的联系人提供程序。 + </dd> +</dl> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd b/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd new file mode 100644 index 000000000000..fd36e2963606 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/providers/document-provider.jd @@ -0,0 +1,916 @@ +page.title=存储访问框架 +@jd:body +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文内容 +<a href="#" onclick="hideNestedItems('#toc44',this);return false;" class="header-toggle"> + <span class="more">显示详细信息</span> + <span class="less" style="display:none">显示精简信息</span></a></h2> +<ol id="toc44" class="hide-nested"> + <li> + <a href="#overview">概览</a> + </li> + <li> + <a href="#flow">控制流</a> + </li> + <li> + <a href="#client">编写客户端应用</a> + <ol> + <li><a href="#search">搜索文档</a></li> + <li><a href="#process">处理结果</a></li> + <li><a href="#metadata">检查文档元数据</a></li> + <li><a href="#open">打开文档</a></li> + <li><a href="#create">创建新文档</a></li> + <li><a href="#delete">删除文档</a></li> + <li><a href="#edit">编辑文档</a></li> + <li><a href="#permissions">保留权限</a></li> + </ol> + </li> + <li><a href="#custom">编写自定义文档提供程序</a> + <ol> + <li><a href="#manifest">清单文件</a></li> + <li><a href="#contract">协定类</a></li> + <li><a href="#subclass">为 DocumentsProvider 创建子类</a></li> + <li><a href="#security">安全性</a></li> + </ol> + </li> + +</ol> +<h2>关键类</h2> +<ol> + <li>{@link android.provider.DocumentsProvider}</li> + <li>{@link android.provider.DocumentsContract}</li> +</ol> + +<h2>视频</h2> + +<ol> + <li><a href="http://www.youtube.com/watch?v=zxHVeXbK1P4"> +DevBytes:Android 4.4 存储访问框架:提供程序</a></li> + <li><a href="http://www.youtube.com/watch?v=UFj9AEz0DHQ"> +DevBytes:Android 4.4 存储访问框架:客户端</a></li> +</ol> + + +<h2>代码示例</h2> + +<ol> + <li><a href="{@docRoot}samples/StorageProvider/index.html"> +存储提供程序</a></li> + <li><a href="{@docRoot}samples/StorageClient/index.html"> +StorageClient</a></li> +</ol> + +<h2>另请参阅</h2> +<ol> + <li> + <a href="{@docRoot}guide/topics/providers/content-provider-basics.html"> +内容提供程序基础知识 +</a> + </li> +</ol> + +</div> +</div> + + +<p>Android 4.4(API 19 级)引入了存储访问框架 (SAF)。SAF +让用户能够在其所有首选文档存储提供程序中方便地浏览并打开文档、图像以及其他文件。 +用户可以通过易用的标准 UI,以统一方式在所有应用和提供程序中浏览文件和访问最近使用的文件。 +</p> + +<p>云存储服务或本地存储服务可以通过实现封装其服务的 +{@link android.provider.DocumentsProvider} 参与此生态系统。只需几行代码,便可将需要访问提供程序文档的客户端应用与 +SAF +集成。</p> + +<p>SAF 包括以下内容:</p> + +<ul> +<li><strong>文档提供程序</strong>—一种内容提供程序,允许存储服务(如 Google +云端硬盘)显示其管理的文件。文档提供程序作为 +{@link android.provider.DocumentsProvider} +类的子类实现。文档提供程序的架构基于传统文件层次结构,但其实际数据存储方式由您决定。Android +平台包括若干内置文档提供程序,如 Downloads、Images 和 Videos; + +</li> + +<li><strong>客户端应用</strong>—一种自定义应用,它调用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和/或 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} Intent 并接收文档提供程序返回的文件; +</li> + +<li><strong>选取器</strong>—一种系统 +UI,允许用户访问所有满足客户端应用搜索条件的文档提供程序内的文档。</li> +</ul> + +<p>SAF 提供的部分功能如下:</p> +<ul> +<li>允许用户浏览所有文档提供程序而不仅仅是单个应用中的内容;</li> +<li>让您的应用获得对文档提供程序所拥有文档的长期、持久性访问权限。 +用户可以通过此访问权限添加、编辑、保存和删除提供程序上的文件; +</li> +<li>支持多个用户帐户和临时根目录,如只有在插入驱动器后才会出现的 USB +存储提供程序。 </li> +</ul> + +<h2 id ="overview">概览</h2> + +<p>SAF 围绕的内容提供程序是 +{@link android.provider.DocumentsProvider} 类的一个子类。在<em>文档提供程序</em>内,数据结构采用传统的文件层次结构: +</p> +<p><img src="{@docRoot}images/providers/storage_datamodel.png" alt="data model" /></p> +<p class="img-caption"><strong>图 1. </strong>文档提供程序数据模型。根目录指向单个文档,后者随即启动整个结构树的扇出。 +</p> + +<p>请注意以下事项:</p> +<ul> + +<li>每个文档提供程序都会报告一个或多个作为探索文档结构树起点的“根目录”。每个根目录都有一个唯一的 +{@link android.provider.DocumentsContract.Root#COLUMN_ROOT_ID},并且指向表示该根目录下内容的文档(目录)。根目录采用动态设计,以支持多个帐户、临时 +USB +存储设备或用户登录/注销等用例; + + +</li> + +<li>每个根目录下都有一个文档。该文档指向 1 至 <em>N</em> +个文档,而其中每个文档又可指向 1 至 <em>N</em> 个文档; </li> + +<li>每个存储后端都会通过使用唯一的 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID} 引用各个文件和目录来显示它们。文档 +ID +必须具有唯一性,一旦发放便不得更改,因为它们用于所有设备重启过程中的永久性 +URI 授权;</li> + + +<li>文档可以是可打开的文件(具有特定 MIME +类型)或包含附加文档的目录(具有 +{@link android.provider.DocumentsContract.Document#MIME_TYPE_DIR} MIME 类型);</li> + +<li>每个文档都可以具有不同的功能,如 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS COLUMN_FLAGS} 所述。例如,{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_WRITE}、{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE} +和 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_THUMBNAIL}。多个目录中可以包含相同的 +{@link android.provider.DocumentsContract.Document#COLUMN_DOCUMENT_ID}。 + +</li> +</ul> + +<h2 id="flow">控制流</h2> +<p>如前文所述,文档提供程序数据模型基于传统文件层次结构。 +不过,只要可以通过 +{@link android.provider.DocumentsProvider} API +访问数据,您实际上可以按照自己喜好的方式存储数据。例如,您可以使用基于标记的云存储来存储数据。</p> + +<p>图 2 中的示例展示的是照片应用如何利用 SAF +访问存储的数据:</p> +<p><img src="{@docRoot}images/providers/storage_dataflow.png" alt="app" /></p> + +<p class="img-caption"><strong>图 2. </strong>存储访问框架流</p> + +<p>请注意以下事项:</p> +<ul> + +<li>在 SAF +中,提供程序和客户端并不直接交互。客户端请求与文件交互(即读取、编辑、创建或删除文件)的权限; +</li> + +<li>交互在应用(在本示例中为照片应用)触发 Intent +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 或 {@link android.content.Intent#ACTION_CREATE_DOCUMENT} 后开始。Intent 可能包括进一步细化条件的过滤器—例如,“为我提供所有 MIME +类型为‘图像’的可打开文件”; +</li> + +<li>Intent 触发后,系统选取器将检索每个已注册的提供程序,并向用户显示匹配的内容根目录; +</li> + +<li>选取器会为用户提供一个标准的文档访问界面,但底层文档提供程序可能与其差异很大。 +例如,图 2 +显示了一个 Google 云端硬盘提供程序、一个 USB 提供程序和一个云提供程序。</li> +</ul> + +<p>图 3 显示了一个选取器,一位搜索图像的用户在其中选择了一个 +Google 云端硬盘帐户:</p> + +<p><img src="{@docRoot}images/providers/storage_picker.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> + +<p class="img-caption"><strong>图 3. </strong>选取器</p> + +<p>当用户选择 Google +云端硬盘时,系统会显示图像,如图 4 所示。从这时起,用户就可以通过提供程序和客户端应用支持的任何方式与它们进行交互。 + + +<p><img src="{@docRoot}images/providers/storage_photos.png" width="340" alt="picker" style="border:2px solid #ddd" /></p> + +<p class="img-caption"><strong>图 4. </strong>图像</p> + +<h2 id="client">编写客户端应用</h2> + +<p>对于 Android 4.3 +及更低版本,如果您想让应用从其他应用中检索文件,它必须调用 {@link android.content.Intent#ACTION_PICK} +或 {@link android.content.Intent#ACTION_GET_CONTENT} 等 Intent。然后,用户必须选择一个要从中选取文件的应用,并且所选应用必须提供一个用户界面,以便用户浏览和选取可用文件。 + + </p> + +<p>对于 Android 4.4 及更高版本,您还可以选择使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent,后者会显示一个由系统控制的选取器 +UI,用户可以通过它浏览其他应用提供的所有文件。用户只需通过这一个 UI +便可从任何受支持的应用中选取文件。</p> + +<p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +并非设计用于替代 {@link android.content.Intent#ACTION_GET_CONTENT}。应使用的 Intent 取决于应用的需要: +</p> + +<ul> +<li>如果您只想让应用读取/导入数据,请使用 +{@link android.content.Intent#ACTION_GET_CONTENT}。使用此方法时,应用会导入数据(如图像文件)的副本; +</li> + +<li>如果您想让应用获得对文档提供程序所拥有文档的长期、持久性访问权限,请使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT}。 +例如,允许用户编辑存储在文档提供程序中的图像的照片编辑应用。 + </li> + +</ul> + + +<p>本节描述如何编写基于 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} 和 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} Intent 的客户端应用。</p> + + +<h3 id="search">搜索文档</h3> + +<p> +以下代码段使用 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} +来搜索包含图像文件的文档提供程序:</p> + +<pre>private static final int READ_REQUEST_CODE = 42; +... +/** + * Fires an intent to spin up the "file chooser" UI and select an image. + */ +public void performFileSearch() { + + // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's file + // browser. + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + + // Filter to only show results that can be "opened", such as a + // file (as opposed to a list of contacts or timezones) + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Filter to show only images, using the image MIME data type. + // If one wanted to search for ogg vorbis files, the type would be "audio/ogg". + // To search for all documents available via installed storage providers, + // it would be "*/*". + intent.setType("image/*"); + + startActivityForResult(intent, READ_REQUEST_CODE); +}</pre> + +<p>请注意以下事项:</p> +<ul> +<li>当应用触发 {@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent 时,后者会启动一个选取器来显示所有匹配的文档提供程序</li> + +<li>在 Intent 中添加类别 {@link android.content.Intent#CATEGORY_OPENABLE} +可对结果进行过滤,以仅显示可以打开的文档(如图像文件)</li> + +<li>语句 {@code intent.setType("image/*")} 可做进一步过滤,以仅显示 +MIME 数据类型为图像的文档</li> +</ul> + +<h3 id="results">处理结果</h3> + +<p>用户在选取器中选择文档后,系统就会调用 +{@link android.app.Activity#onActivityResult onActivityResult()}。指向所选文档的 URI +包含在 {@code resultData} +参数中。使用 {@link android.content.Intent#getData getData()} +提取 URI。获得 URI 后,即可使用它来检索用户想要的文档。例如: +</p> + +<pre>@Override +public void onActivityResult(int requestCode, int resultCode, + Intent resultData) { + + // The ACTION_OPEN_DOCUMENT intent was sent with the request code + // READ_REQUEST_CODE. If the request code seen here doesn't match, it's the + // response to some other intent, and the code below shouldn't run at all. + + if (requestCode == READ_REQUEST_CODE && resultCode == Activity.RESULT_OK) { + // The document selected by the user won't be returned in the intent. + // Instead, a URI to that document will be contained in the return intent + // provided to this method as a parameter. + // Pull that URI using resultData.getData(). + Uri uri = null; + if (resultData != null) { + uri = resultData.getData(); + Log.i(TAG, "Uri: " + uri.toString()); + showImage(uri); + } + } +} +</pre> + +<h3 id="metadata">检查文档元数据</h3> + +<p>获得文档的 URI 后,即可获得对其元数据的访问权限。以下代码段用于获取 URI +所指定文档的元数据并将其记入日志:</p> + +<pre>public void dumpImageMetaData(Uri uri) { + + // The query, since it only applies to a single document, will only return + // one row. There's no need to filter, sort, or select fields, since we want + // all fields for one document. + Cursor cursor = getActivity().getContentResolver() + .query(uri, null, null, null, null, null); + + try { + // moveToFirst() returns false if the cursor has 0 rows. Very handy for + // "if there's anything to look at, look at it" conditionals. + if (cursor != null && cursor.moveToFirst()) { + + // Note it's called "Display Name". This is + // provider-specific, and might not necessarily be the file name. + String displayName = cursor.getString( + cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)); + Log.i(TAG, "Display Name: " + displayName); + + int sizeIndex = cursor.getColumnIndex(OpenableColumns.SIZE); + // If the size is unknown, the value stored is null. But since an + // int can't be null in Java, the behavior is implementation-specific, + // which is just a fancy term for "unpredictable". So as + // a rule, check if it's null before assigning to an int. This will + // happen often: The storage API allows for remote files, whose + // size might not be locally known. + String size = null; + if (!cursor.isNull(sizeIndex)) { + // Technically the column stores an int, but cursor.getString() + // will do the conversion automatically. + size = cursor.getString(sizeIndex); + } else { + size = "Unknown"; + } + Log.i(TAG, "Size: " + size); + } + } finally { + cursor.close(); + } +} +</pre> + +<h3 id="open-client">打开文档</h3> + +<p>获得文档的 URI +后,即可打开文档或对其执行任何其他您想要执行的操作。</p> + +<h4>位图</h4> + +<p>以下示例展示了如何打开 {@link android.graphics.Bitmap}:</p> + +<pre>private Bitmap getBitmapFromUri(Uri uri) throws IOException { + ParcelFileDescriptor parcelFileDescriptor = + getContentResolver().openFileDescriptor(uri, "r"); + FileDescriptor fileDescriptor = parcelFileDescriptor.getFileDescriptor(); + Bitmap image = BitmapFactory.decodeFileDescriptor(fileDescriptor); + parcelFileDescriptor.close(); + return image; +} +</pre> + +<p>请注意,您不应在 UI 线程上执行此操作。请使用 +{@link android.os.AsyncTask} 在后台执行此操作。打开位图后,即可在 +{@link android.widget.ImageView} 中显示它。 +</p> + +<h4>获取 InputStream</h4> + +<p>以下示例展示了如何从 URI 中获取 +{@link java.io.InputStream}。在此代码段中,系统将文件行读取到一个字符串中:</p> + +<pre>private String readTextFromUri(Uri uri) throws IOException { + InputStream inputStream = getContentResolver().openInputStream(uri); + BufferedReader reader = new BufferedReader(new InputStreamReader( + inputStream)); + StringBuilder stringBuilder = new StringBuilder(); + String line; + while ((line = reader.readLine()) != null) { + stringBuilder.append(line); + } + fileInputStream.close(); + parcelFileDescriptor.close(); + return stringBuilder.toString(); +} +</pre> + +<h3 id="create">创建新文档</h3> + +<p>您的应用可以使用 +{@link android.content.Intent#ACTION_CREATE_DOCUMENT} + Intent 在文档提供程序中创建新文档。要想创建文件,请为您的 Intent 提供一个 MIME +类型和文件名,然后通过唯一的请求代码启动它。系统会为您执行其余操作:</p> + + +<pre> +// Here are some examples of how you might call this method. +// The first parameter is the MIME type, and the second parameter is the name +// of the file you are creating: +// +// createFile("text/plain", "foobar.txt"); +// createFile("image/png", "mypicture.png"); + +// Unique request code. +private static final int WRITE_REQUEST_CODE = 43; +... +private void createFile(String mimeType, String fileName) { + Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); + + // Filter to only show results that can be "opened", such as + // a file (as opposed to a list of contacts or timezones). + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Create a file with the requested MIME type. + intent.setType(mimeType); + intent.putExtra(Intent.EXTRA_TITLE, fileName); + startActivityForResult(intent, WRITE_REQUEST_CODE); +} +</pre> + +<p>创建新文档后,即可在 +{@link android.app.Activity#onActivityResult onActivityResult()} 中获取其 +URI,以便继续向其写入内容。</p> + +<h3 id="delete">删除文档</h3> + +<p>如果您获得了文档的 +URI,并且文档的 +{@link android.provider.DocumentsContract.Document#COLUMN_FLAGS Document.COLUMN_FLAGS} +包含 +{@link android.provider.DocumentsContract.Document#FLAG_SUPPORTS_DELETE SUPPORTS_DELETE},便可以删除该文档。例如:</p> + +<pre> +DocumentsContract.deleteDocument(getContentResolver(), uri); +</pre> + +<h3 id="edit">编辑文档</h3> + +<p>您可以使用 SAF +就地编辑文本文档。以下代码段会触发 +{@link android.content.Intent#ACTION_OPEN_DOCUMENT} Intent +并使用类别 {@link android.content.Intent#CATEGORY_OPENABLE} +以仅显示可以打开的文档。它会进一步过滤以仅显示文本文件:</p> + +<pre> +private static final int EDIT_REQUEST_CODE = 44; +/** + * Open a file for writing and append some text to it. + */ + private void editDocument() { + // ACTION_OPEN_DOCUMENT is the intent to choose a file via the system's + // file browser. + Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + + // Filter to only show results that can be "opened", such as a + // file (as opposed to a list of contacts or timezones). + intent.addCategory(Intent.CATEGORY_OPENABLE); + + // Filter to show only text files. + intent.setType("text/plain"); + + startActivityForResult(intent, EDIT_REQUEST_CODE); +} +</pre> + +<p>接下来,您可以从 +{@link android.app.Activity#onActivityResult onActivityResult()}(请参阅<a href="#results">处理结果</a>)调用代码以执行编辑。以下代码段可从 +{@link android.content.ContentResolver} 获取 +{@link java.io.FileOutputStream}。默认情况下,它使用“写入”模式。最佳做法是请求获得所需的最低限度访问权限,因此如果您只需要写入权限,就不要请求获得读取/写入权限。 + +</p> + +<pre>private void alterDocument(Uri uri) { + try { + ParcelFileDescriptor pfd = getActivity().getContentResolver(). + openFileDescriptor(uri, "w"); + FileOutputStream fileOutputStream = + new FileOutputStream(pfd.getFileDescriptor()); + fileOutputStream.write(("Overwritten by MyCloud at " + + System.currentTimeMillis() + "\n").getBytes()); + // Let the document provider know you're done by closing the stream. + fileOutputStream.close(); + pfd.close(); + } catch (FileNotFoundException e) { + e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } +}</pre> + +<h3 id="permissions">保留权限</h3> + +<p>当您的应用打开文件进行读取或写入时,系统会为您的应用提供针对该文件的 +URI 授权。该授权将一直持续到用户设备重启时。但假定您的应用是图像编辑应用,而且您希望用户能够直接从应用中访问他们编辑的最后 +5 +张图像。如果用户的设备已经重启,您就需要将用户转回系统选取器以查找这些文件,这显然不是理想的做法。 + +</p> + +<p>为防止出现这种情况,您可以保留系统为您的应用授予的权限。您的应用实际上是“获取”了系统提供的持久 +URI +授权。这使用户能够通过您的应用持续访问文件,即使设备已重启也不受影响: +</p> + + +<pre>final int takeFlags = intent.getFlags() + & (Intent.FLAG_GRANT_READ_URI_PERMISSION + | Intent.FLAG_GRANT_WRITE_URI_PERMISSION); +// Check for the freshest data. +getContentResolver().takePersistableUriPermission(uri, takeFlags);</pre> + +<p>还有最后一个步骤。您可能已经保存了应用最近访问的 +URI,但它们可能不再有效—另一个应用可能已删除或修改了文档。 +因此,您应该始终调用 +{@code getContentResolver().takePersistableUriPermission()} +以检查有无最新数据。</p> + +<h2 id="custom">编写自定义文档提供程序</h2> + +<p> +如果您要开发为文件提供存储服务(如云保存服务)的应用,可以通过编写自定义文档提供程序,通过 +SAF +提供您的文件。本节描述如何执行此操作。 +</p> + + +<h3 id="manifest">清单文件</h3> + +<p>要想实现自定义文档提供程序,请将以下内容添加到您的应用的清单文件: +</p> +<ul> + +<li>一个 API 19 级或更高级别的目标;</li> + +<li>一个声明自定义存储提供程序的 +<code><provider></code> 元素; </li> + +<li>提供程序的名称(即其类名),包括软件包名称。例如:<code>com.example.android.storageprovider.MyCloudProvider</code>; +</li> + +<li>权限的名称,即您的软件包名称(在本例中为 +<code>com.example.android.storageprovider</code>)加内容提供程序的类型 +(<code>documents</code>)。例如,{@code com.example.android.storageprovider.documents};</li> + +<li>属性 <code>android:exported</code> 设置为 +<code>"true"</code>。您必须导出提供程序,以便其他应用可以看到;</li> + +<li>属性 <code>android:grantUriPermissions</code> 设置为 +<code>"true"</code>。此设置允许系统向其他应用授予对提供程序中内容的访问权限。 +如需查看有关保留对特定文档授权的阐述,请参阅<a href="#permissions">保留权限</a>; +</li> + +<li>{@code MANAGE_DOCUMENTS} 权限。默认情况下,提供程序对所有人可用。 +添加此权限将限定您的提供程序只能供系统使用。此限制具有重要的安全意义; +</li> + +<li>{@code android:enabled} +属性设置为在资源文件中定义的一个布尔值。此属性的用途是,在运行 Android 4.3 +或更低版本的设备上禁用提供程序。例如,{@code android:enabled="@bool/atLeastKitKat"}。除了在清单文件中加入此属性外,您还需要执行以下操作; + +<ul> +<li>在 {@code res/values/} 下的 {@code bool.xml} +资源文件中,添加以下行 <pre><bool name="atLeastKitKat">false</bool></pre></li> + +<li>在 {@code res/values-v19/} 下的 {@code bool.xml} +资源文件中,添加以下行 <pre><bool name="atLeastKitKat">true</bool></pre></li> +</ul></li> + +<li>一个包括 +{@code android.content.action.DOCUMENTS_PROVIDER} 操作的 Intent +过滤器,以便在系统搜索提供程序时让您的提供程序出现在选取器中。</li> + +</ul> +<p>以下是从一个包括提供程序的示例清单文件中摘录的内容:</p> + +<pre><manifest... > + ... + <uses-sdk + android:minSdkVersion="19" + android:targetSdkVersion="19" /> + .... + <provider + android:name="com.example.android.storageprovider.MyCloudProvider" + android:authorities="com.example.android.storageprovider.documents" + android:grantUriPermissions="true" + android:exported="true" + android:permission="android.permission.MANAGE_DOCUMENTS" + android:enabled="@bool/atLeastKitKat"> + <intent-filter> + <action android:name="android.content.action.DOCUMENTS_PROVIDER" /> + </intent-filter> + </provider> + </application> + +</manifest></pre> + +<h4 id="43">支持运行 Android 4.3 及更低版本的设备</h4> + +<p>{@link android.content.Intent#ACTION_OPEN_DOCUMENT} + Intent 仅可用于运行 +Android 4.4 及更高版本的设备。如果您想让应用支持 {@link android.content.Intent#ACTION_GET_CONTENT} +以适应运行 Android 4.3 +及更低版本的设备,则应在您的清单文件中为运行 Android 4.4 +或更高版本的设备禁用 {@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器。应将文档提供程序和 +{@link android.content.Intent#ACTION_GET_CONTENT} +视为具有互斥性。如果您同时支持这两者,您的应用将在系统选取器 +UI +中出现两次,提供两种不同的方式来访问您存储的数据。这会给用户造成困惑。</p> + +<p>建议按照以下步骤为运行 Android 4.4 版或更高版本的设备禁用 +{@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器:</p> + +<ol> +<li>在 {@code res/values/} 下的 {@code bool.xml} +资源文件中,添加以下行 <pre><bool name="atMostJellyBeanMR2">true</bool></pre></li> + +<li>在 {@code res/values-v19/} 下的 {@code bool.xml} +资源文件中,添加以下行 <pre><bool name="atMostJellyBeanMR2">false</bool></pre></li> + +<li>添加一个<a href="{@docRoot}guide/topics/manifest/activity-alias-element.html">Activity别名</a>,为 +4.4 版(API 19 +级)或更高版本禁用 {@link android.content.Intent#ACTION_GET_CONTENT} Intent +过滤器。例如: + +<pre> +<!-- This activity alias is added so that GET_CONTENT intent-filter + can be disabled for builds on API level 19 and higher. --> +<activity-alias android:name="com.android.example.app.MyPicker" + android:targetActivity="com.android.example.app.MyActivity" + ... + android:enabled="@bool/atMostJellyBeanMR2"> + <intent-filter> + <action android:name="android.intent.action.GET_CONTENT" /> + <category android:name="android.intent.category.OPENABLE" /> + <category android:name="android.intent.category.DEFAULT" /> + <data android:mimeType="image/*" /> + <data android:mimeType="video/*" /> + </intent-filter> +</activity-alias> +</pre> +</li> +</ol> +<h3 id="contract">协定类</h3> + +<p>通常,当您编写自定义内容提供程序时,其中一项任务是实现协定类,如<a href="{@docRoot}guide/topics/providers/content-provider-creating.html#ContractClass">内容提供程序</a>开发者指南中所述。 + + +协定类是一种 {@code public final} 类,它包含对 +URI、列名称、MIME +类型以及其他与提供程序有关的元数据的常量定义。SAF +会为您提供这些协定类,因此您无需自行编写: +</p> + +<ul> + <li>{@link android.provider.DocumentsContract.Document}</li> + <li>{@link android.provider.DocumentsContract.Root}</li> +</ul> + +<p>例如,当系统在您的文档提供程序中查询文档或根目录时,您可能会在游标中返回以下列: +</p> + +<pre>private static final String[] DEFAULT_ROOT_PROJECTION = + new String[]{Root.COLUMN_ROOT_ID, Root.COLUMN_MIME_TYPES, + Root.COLUMN_FLAGS, Root.COLUMN_ICON, Root.COLUMN_TITLE, + Root.COLUMN_SUMMARY, Root.COLUMN_DOCUMENT_ID, + Root.COLUMN_AVAILABLE_BYTES,}; +private static final String[] DEFAULT_DOCUMENT_PROJECTION = new + String[]{Document.COLUMN_DOCUMENT_ID, Document.COLUMN_MIME_TYPE, + Document.COLUMN_DISPLAY_NAME, Document.COLUMN_LAST_MODIFIED, + Document.COLUMN_FLAGS, Document.COLUMN_SIZE,}; +</pre> + +<h3 id="subclass">为 DocumentsProvider 创建子类</h3> + +<p>编写自定义文档提供程序的下一步是为抽象类 +{@link android.provider.DocumentsProvider} 创建子类。您至少需要实现以下方法: +</p> + +<ul> +<li>{@link android.provider.DocumentsProvider#queryRoots queryRoots()}</li> + +<li>{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()}</li> + +<li>{@link android.provider.DocumentsProvider#queryDocument queryDocument()}</li> + +<li>{@link android.provider.DocumentsProvider#openDocument openDocument()}</li> +</ul> + +<p>这些只是您需要严格实现的方法,但您可能还想实现许多其他方法。 +详情请参阅 +{@link android.provider.DocumentsProvider}。</p> + +<h4 id="queryRoots">实现 queryRoots</h4> + +<p>您实现的 +{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 必须使用在 +{@link android.provider.DocumentsContract.Root} 中定义的列返回一个指向文档提供程序所有根目录的 {@link android.database.Cursor}。</p> + +<p>在以下代码段中,{@code projection} +参数表示调用方想要返回的特定字段。代码段会创建一个新游标,并为其添加一行—一个根目录,如 +Downloads 或 Images +等顶层目录。大多数提供程序只有一个根目录。有时您可能有多个根目录,例如,当您具有多个用户帐户时。 +在这种情况下,只需再为游标添加一行。 +</p> + +<pre> +@Override +public Cursor queryRoots(String[] projection) throws FileNotFoundException { + + // Create a cursor with either the requested fields, or the default + // projection if "projection" is null. + final MatrixCursor result = + new MatrixCursor(resolveRootProjection(projection)); + + // If user is not logged in, return an empty root cursor. This removes our + // provider from the list entirely. + if (!isUserLoggedIn()) { + return result; + } + + // It's possible to have multiple roots (e.g. for multiple accounts in the + // same app) -- just add multiple cursor rows. + // Construct one row for a root called "MyCloud". + final MatrixCursor.RowBuilder row = result.newRow(); + row.add(Root.COLUMN_ROOT_ID, ROOT); + row.add(Root.COLUMN_SUMMARY, getContext().getString(R.string.root_summary)); + + // FLAG_SUPPORTS_CREATE means at least one directory under the root supports + // creating documents. FLAG_SUPPORTS_RECENTS means your application's most + // recently used documents will show up in the "Recents" category. + // FLAG_SUPPORTS_SEARCH allows users to search all documents the application + // shares. + row.add(Root.COLUMN_FLAGS, Root.FLAG_SUPPORTS_CREATE | + Root.FLAG_SUPPORTS_RECENTS | + Root.FLAG_SUPPORTS_SEARCH); + + // COLUMN_TITLE is the root title (e.g. Gallery, Drive). + row.add(Root.COLUMN_TITLE, getContext().getString(R.string.title)); + + // This document id cannot change once it's shared. + row.add(Root.COLUMN_DOCUMENT_ID, getDocIdForFile(mBaseDir)); + + // The child MIME types are used to filter the roots and only present to the + // user roots that contain the desired type somewhere in their file hierarchy. + row.add(Root.COLUMN_MIME_TYPES, getChildMimeTypes(mBaseDir)); + row.add(Root.COLUMN_AVAILABLE_BYTES, mBaseDir.getFreeSpace()); + row.add(Root.COLUMN_ICON, R.drawable.ic_launcher); + + return result; +}</pre> + +<h4 id="queryChildDocuments">实现 queryChildDocuments</h4> + +<p>您实现的 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} 必须使用在 +{@link android.provider.DocumentsContract.Document} +中定义的列返回一个指向指定目录中所有文件的 +{@link android.database.Cursor}。</p> + +<p>当您在选取器 UI +中选择应用时,系统会调用此方法。它会获取根目录下某个目录内的子文档。可以在文件层次结构的任何级别调用此方法,并非只能从根目录调用。 +以下代码段可创建一个包含所请求列的新游标,然后向游标添加父目录中每个直接子目录的相关信息。子目录可以是图像、另一个目录乃至任何文件: + + +</p> + +<pre>@Override +public Cursor queryChildDocuments(String parentDocumentId, String[] projection, + String sortOrder) throws FileNotFoundException { + + final MatrixCursor result = new + MatrixCursor(resolveDocumentProjection(projection)); + final File parent = getFileForDocId(parentDocumentId); + for (File file : parent.listFiles()) { + // Adds the file's display name, MIME type, size, and so on. + includeFile(result, null, file); + } + return result; +} +</pre> + +<h4 id="queryDocument">实现 queryDocument</h4> + +<p>您实现的 +{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +必须使用在 {@link android.provider.DocumentsContract.Document} 中定义的列返回一个指向指定文件的 +{@link android.database.Cursor}。 +</p> + +<p>除了特定文件的信息外,{@link android.provider.DocumentsProvider#queryDocument queryDocument()} +方法返回的信息与 +{@link android.provider.DocumentsProvider#queryChildDocuments queryChildDocuments()} +中传递的信息相同:</p> + + +<pre>@Override +public Cursor queryDocument(String documentId, String[] projection) throws + FileNotFoundException { + + // Create a cursor with the requested projection, or the default projection. + final MatrixCursor result = new + MatrixCursor(resolveDocumentProjection(projection)); + includeFile(result, documentId, null); + return result; +} +</pre> + +<h4 id="openDocument">实现 queryDocument</h4> + +<p>您必须实现 {@link android.provider.DocumentsProvider#openDocument +openDocument()} 以返回表示指定文件的 +{@link android.os.ParcelFileDescriptor}。其他应用可以使用返回的 {@link android.os.ParcelFileDescriptor} +来流式传输数据。用户选择了文件,并且客户端应用通过调用 +{@link android.content.ContentResolver#openFileDescriptor openFileDescriptor()} +来请求对文件的访问权限,系统便会调用此方法。例如: +</p> + +<pre>@Override +public ParcelFileDescriptor openDocument(final String documentId, + final String mode, + CancellationSignal signal) throws + FileNotFoundException { + Log.v(TAG, "openDocument, mode: " + mode); + // It's OK to do network operations in this method to download the document, + // as long as you periodically check the CancellationSignal. If you have an + // extremely large file to transfer from the network, a better solution may + // be pipes or sockets (see ParcelFileDescriptor for helper methods). + + final File file = getFileForDocId(documentId); + + final boolean isWrite = (mode.indexOf('w') != -1); + if(isWrite) { + // Attach a close listener if the document is opened in write mode. + try { + Handler handler = new Handler(getContext().getMainLooper()); + return ParcelFileDescriptor.open(file, accessMode, handler, + new ParcelFileDescriptor.OnCloseListener() { + @Override + public void onClose(IOException e) { + + // Update the file with the cloud server. The client is done + // writing. + Log.i(TAG, "A file with id " + + documentId + " has been closed! + Time to " + + "update the server."); + } + + }); + } catch (IOException e) { + throw new FileNotFoundException("Failed to open document with id " + + documentId + " and mode " + mode); + } + } else { + return ParcelFileDescriptor.open(file, accessMode); + } +} +</pre> + +<h3 id="security">安全性</h3> + +<p>假设您的文档提供程序是受密码保护的云存储服务,并且您想在开始共享用户的文件之前确保其已登录。如果用户未登录,您的应用应该执行哪些操作呢? + +解决方案是在您实现的 +{@link android.provider.DocumentsProvider#queryRoots +queryRoots()} 中返回零个根目录。也就是空的根目录游标:</p> + +<pre> +public Cursor queryRoots(String[] projection) throws FileNotFoundException { +... + // If user is not logged in, return an empty root cursor. This removes our + // provider from the list entirely. + if (!isUserLoggedIn()) { + return result; +} +</pre> + +<p>另一个步骤是调用 +{@code getContentResolver().notifyChange()}。还记得 {@link android.provider.DocumentsContract} 吗?我们将使用它来创建此 +URI。以下代码段会在每次用户的登录状态发生变化时指示系统查询文档提供程序的根目录。 +如果用户未登录,则调用 +{@link android.provider.DocumentsProvider#queryRoots queryRoots()} +会返回一个空游标,如上文所示。这可以确保只有在用户登录提供程序后其中的文档才可用。 +</p> + +<pre>private void onLoginButtonClick() { + loginOrLogout(); + getContentResolver().notifyChange(DocumentsContract + .buildRootsUri(AUTHORITY), null); +} +</pre>
\ No newline at end of file diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd new file mode 100644 index 000000000000..e8e583a7307f --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/accessing-resources.jd @@ -0,0 +1,337 @@ +page.title=访问资源 +parent.title=应用资源 +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>内容快览</h2> + <ul> + <li>可以使用 +{@code R.drawable.myimage} 等来自 {@code R.java} 的整型数在代码中引用资源</li> + <li>可以使用 {@code +@drawable/myimage} 等特殊 XML 语法在资源中引用资源</li> + <li>您还可以通过 +{@link android.content.res.Resources} 中的方法访问您的应用资源</li> + </ul> + + <h2>关键类</h2> + <ol> + <li>{@link android.content.res.Resources}</li> + </ol> + + <h2>本文内容</h2> + <ol> + <li><a href="#ResourcesFromCode">在代码中访问资源</a></li> + <li><a href="#ResourcesFromXml">在 XML 中访问资源</a> + <ol> + <li><a href="#ReferencesToThemeAttributes">引用样式属性</a></li> + </ol> + </li> + <li><a href="#PlatformResources">访问平台资源</a></li> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="providing-resources.html">提供资源</a></li> + <li><a href="available-resources.html">资源类型</a></li> + </ol> +</div> +</div> + + + + +<p>您在应用中提供资源后(<a href="providing-resources.html">提供资源</a>中对此做了阐述),可通过引用其资源 ID 来应用该资源。所有资源 ID 都在您项目的 {@code R} 类中定义,后者由 {@code aapt} 工具自动生成。 + +</p> + +<p>编译应用时,{@code aapt} 会生成 {@code R} 类,其中包含您的 {@code +res/} 目录中所有资源的资源 ID。 +每个资源类型都有对应的 {@code R} +子类(例如,{@code R.drawable} +对应于所有 Drawable 资源),而该类型的每个资源都有对应的静态整型数(例如,{@code R.drawable.icon})。这个整型数就是可用来检索资源的资源 +ID。</p> + +<p>尽管资源 ID 是在 {@code R} 类中指定的,但您应该永远都不需要在其中查找资源 +ID。资源 ID 始终由以下部分组成:</p> +<ul> + <li><em>资源类型</em>:每个资源都被分到一个“类型”组中,例如 {@code +string}、{@code drawable} 和 {@code layout}。如需了解有关不同类型的详细信息,请参阅<a href="available-resources.html">资源类型</a>。 + </li> + <li><em>资源名称</em>,它是不包括扩展名的文件名;或是 XML +{@code android:name} +属性中的值,如果资源是简单值的话(例如字符串)。</li> +</ul> + +<p>访问资源的方法有两种:</p> +<ul> + <li><strong>在代码中</strong>:使用来自 {@code R} +类的某个子类的静态整型数,例如: + <pre class="classic no-pretty-print">R.string.hello</pre> + <p>{@code string} 是资源类型,{@code hello} 是资源名称。当您提供此格式的资源 ID 时,有许多 +Android API 可以访问您的资源。请参阅<a href="#ResourcesFromCode">在代码中访问资源</a>。 +</p> + </li> + <li><strong>在 XML 中</strong>:使用同样与您 +{@code R} 类中定义的资源 ID 对应的特殊 XML 语法,例如: + <pre class="classic no-pretty-print">@string/hello</pre> + <p>{@code string} 是资源类型,{@code hello} 是资源名称。您可以在 XML +资源中任何应该存在您在资源中所提供值的地方使用此语法。请参阅<a href="#ResourcesFromXml">在 XML 中访问资源</a>。</p> + </li> +</ul> + + + +<h2 id="ResourcesFromCode">在代码中访问资源 </h2> + +<p>您可以通过以方法参数的形式传递资源 ID,在代码中使用资源。例如,您可以设置一个 +{@link android.widget.ImageView},以利用 {@link android.widget.ImageView#setImageResource(int) setImageResource()} 使用 {@code res/drawable/myimage.png} +资源:</p> +<pre> +ImageView imageView = (ImageView) findViewById(R.id.myimageview); +imageView.setImageResource(<strong>R.drawable.myimage</strong>); +</pre> + +<p>您还可以利用 {@link +android.content.res.Resources} 中的方法检索个别资源,您可以通过 {@link android.content.Context#getResources()} +获得资源实例。</p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> +<h2>访问原始文件</h2> + +<p>尽管并不常见,但您的确有可能需要访问原始文件和目录。如果确有需要,则将您的文件保存在 +{@code res/} 中不起作用,因为从 {@code res/} +读取资源的唯一方法是使用资源 ID。您可以改为将资源保存在 +{@code assets/} 目录中。</p> +<p>保存在 {@code assets/} 目录中的文件“绝对没有”<em></em>资源 +ID,因此您无法通过 {@code R} 类或在 XML 中引用它们。您可以改为采用类似普通文件系统的方式查询 +{@code assets/} 目录中的文件,并利用 +{@link android.content.res.AssetManager} 读取原始数据。</p> +<p>不过,如需的只是读取原始数据(例如视频文件或音频文件)的能力,则可将文件保存在 {@code res/raw/} +目录中,并利用 {@link +android.content.res.Resources#openRawResource(int) openRawResource()} 读取字节流。</p> + +</div> +</div> + + +<h3>语法</h3> + +<p>以下是在代码中引用资源的语法:</p> + +<pre class="classic no-pretty-print"> +[<em><package_name></em>.]R.<em><resource_type></em>.<em><resource_name></em> +</pre> + +<ul> + <li><em>{@code <package_name>}</em> +是资源所在包的名称(如果引用的资源来自您自己的资源包,则不需要)。</li> + <li><em>{@code <resource_type>}</em> 是资源类型的 {@code R} 子类。</li> + <li><em>{@code <resource_name>}</em> +是不带扩展名的资源文件名,或 XML 元素中的 {@code android:name} +属性值(如果资源是简单值)。</li> +</ul> +<p>如需了解有关各资源类型及其引用方法的详细信息,请参阅<a href="available-resources.html">资源类型</a>。 +</p> + + +<h3>用例</h3> + +<p>有许多方法接受资源 ID 参数,您可以利用 {@link android.content.res.Resources} +中的方法检索资源。您可以通过 {@link android.content.Context#getResources +Context.getResources()} 获得 {@link +android.content.res.Resources} 的实例。</p> + + +<p>以下是一些在代码中访问资源的示例:</p> + +<pre> +// Load a background for the current screen from a drawable resource +{@link android.app.Activity#getWindow()}.{@link +android.view.Window#setBackgroundDrawableResource(int) +setBackgroundDrawableResource}(<strong>R.drawable.my_background_image</strong>) ; + +// Set the Activity title by getting a string from the Resources object, because +// this method requires a CharSequence rather than a resource ID +{@link android.app.Activity#getWindow()}.{@link android.view.Window#setTitle(CharSequence) +setTitle}(getResources().{@link android.content.res.Resources#getText(int) +getText}(<strong>R.string.main_title</strong>)); + +// Load a custom layout for the current screen +{@link android.app.Activity#setContentView(int) +setContentView}(<strong>R.layout.main_screen</strong>); + +// Set a slide in animation by getting an Animation from the Resources object +mFlipper.{@link android.widget.ViewAnimator#setInAnimation(Animation) +setInAnimation}(AnimationUtils.loadAnimation(this, + <strong>R.anim.hyperspace_in</strong>)); + +// Set the text on a TextView object using a resource ID +TextView msgTextView = (TextView) findViewById(<strong>R.id.msg</strong>); +msgTextView.{@link android.widget.TextView#setText(int) +setText}(<strong>R.string.hello_message</strong>); +</pre> + + +<p class="caution"><strong>注意:</strong>切勿手动修改 {@code +R.java} 文件 — 它是在编译您的项目时由 {@code aapt} +工具生成的。您下次编译时所有更改都会被替代。</p> + + + +<h2 id="ResourcesFromXml">在 XML 中访问资源</h2> + +<p>您可以利用对现有资源的引用为某些 XML +属性和元素定义值。创建布局文件时,为给您的小工具提供字符串和图像,您经常要这样做。 +</p> + +<p>例如,如果您为布局添加一个 +{@link android.widget.Button},应该为按钮文本使用<a href="string-resource.html">字符串资源</a>:</p> + +<pre> +<Button + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:text="<strong>@string/submit</strong>" /> +</pre> + + +<h3>语法</h3> + +<p>以下是在 XML 资源中引用资源的语法:</p> + +<pre class="classic no-pretty-print"> +@[<em><package_name></em>:]<em><resource_type></em>/<em><resource_name></em> +</pre> + +<ul> + <li>{@code <package_name>} +是资源所在包的名称(如果引用的资源来自同一包,则不需要)</li> + <li>{@code <resource_type>} 是资源类型的 +{@code R} 子类</li> + <li>{@code <resource_name>} +是不带扩展名的资源文件名,或 XML 元素中的 {@code android:name} +属性值(如果资源是简单值)。</li> +</ul> + +<p>如需了解有关各资源类型及其引用方法的详细信息,请参阅<a href="available-resources.html">资源类型</a>。 +</p> + + +<h3>用例</h3> + +<p>在某些情况下,您必须使用资源作为 XML +中的值(例如,对小工具应用可绘制图像),但您也可以在 XML 中任何接受简单值的地方使用资源。例如,如果您具有以下资源文件,其中包括一个<a href="more-resources.html#Color">颜色资源</a>和一个<a href="string-resource.html">字符串资源</a>: +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="opaque_red">#f00</color> + <string name="hello">Hello!</string> +</resources> +</pre> + +<p>您可以在以下布局文件中使用这些资源来设置文本颜色和文本字符串: +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<EditText xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:textColor="<strong>@color/opaque_red</strong>" + android:text="<strong>@string/hello</strong>" /> +</pre> + +<p>在此情况下,您无需在资源引用中指定包名称,因为资源来自您自己的资源包。 +要引用系统资源,您需要加入包名称。 +例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<EditText xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:textColor="<strong>@android:color/secondary_text_dark</strong>" + android:text="@string/hello" /> +</pre> + +<p class="note"><strong>注:</strong>您应该始终使用字符串 +资源,以便将您的应用本地化为其他语言。如需了解有关创建备用资源(例如本地化字符串)的信息,请参阅<a href="providing-resources.html#AlternativeResources">提供备用资源</a>。 + + +如需查看将您的应用本地化为其他语言的完整指南,请参阅<a href="localization.html">本地化</a>。 +</p> + +<p>您甚至可以在 XML 中使用资源创建别名。例如,您可以创建一个 Drawable 资源,将其作为另一个 Drawable 资源的别名: +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/other_drawable" /> +</pre> + +<p>这听起来多余,但对使用备用资源可能很有帮助。阅读更多关于<a href="providing-resources.html#AliasResources">创建别名资源</a>的内容。 +</p> + + + +<h3 id="ReferencesToThemeAttributes">引用样式属性</h3> + +<p>您可以通过样式属性资源在当前应用的风格主题中引用某个属性的值。 +通过引用样式属性,您可以不采用为 UI +元素提供硬编码值这种方式,而是通过为 UI +元素设置样式,使其匹配当前风格主题提供的标准变型来定制这些元素的外观。引用样式属性的实质作用是,“在当前风格主题中使用此属性定义的样式”。 +</p> + +<p>要引用样式属性,名称语法几乎与普通资源格式完全相同,只不过将 at 符号 ({@code @}) 改为问号 ({@code ?}),资源类型部分为可选项。 + +例如:</p> + +<pre class="classic"> +?[<em><package_name></em>:][<em><resource_type></em>/]<em><resource_name></em> +</pre> + +<p>例如,您可以通过以下代码引用一个属性,将文本颜色设置为与系统风格主题的“主要”文本颜色匹配: +</p> + +<pre> +<EditText id="text" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:textColor="<strong>?android:textColorSecondary</strong>" + android:text="@string/hello_world" /> +</pre> + +<p>在以上代码中,{@code android:textColor} +属性表示当前风格主题中某个样式属性的名称。Android 现在会使用应用于 {@code android:textColorSecondary} +样式属性的值作为 {@code android:textColor} 在这个小工具中的值。由于系统资源工具知道此环境中肯定存在某个属性资源,因此您无需显式声明类型(类型应为 +<code>?android:attr/textColorSecondary</code>)— 您可以将 +{@code attr} +类型排除在外。</p> + + + + +<h2 id="PlatformResources">访问平台资源</h2> + +<p>Android 包含许多标准资源,例如样式、风格主题和布局。要访问这些资源,请通过 +<code>android</code> +包名称限定您的资源引用。例如,您可以将 Android 提供的布局资源用于 +{@link android.widget.ListAdapter} 中的列表项:</p> + +<pre> +{@link android.app.ListActivity#setListAdapter(ListAdapter) +setListAdapter}(new {@link +android.widget.ArrayAdapter}<String>(this, <strong>android.R.layout.simple_list_item_1</strong>, myarray)); +</pre> + +<p>在上例中,{@link android.R.layout#simple_list_item_1} +是平台为 {@link android.widget.ListView} 中的项目定义的布局资源。您可以使用它,而不必自行创建列表项布局。 +如需了解详细信息,请参阅<a href="{@docRoot}guide/topics/ui/layout/listview.html">列表视图</a>开发指南。 +</p> + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd new file mode 100644 index 000000000000..21dbe6ff37a9 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/overview.jd @@ -0,0 +1,103 @@ +page.title=资源概览 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>主题</h2> + <ol> + <li><a href="providing-resources.html">提供资源</a></li> + <li><a href="accessing-resources.html">访问资源</a></li> + <li><a href="runtime-changes.html">处理运行时变更</a></li> + <li><a href="localization.html">本地化</a></li> + </ol> + + <h2>参考文档</h2> + <ol> + <li><a href="available-resources.html">资源类型</a></li> + </ol> +</div> +</div> + + +<p>您应该始终外部化资源,例如图像和应用代码中的字符串,这样有利于您单独维护这些资源。 +此外,通过外部化资源,您还可以提供支持特定设备配置(例如,不同的语言或屏幕尺寸)的备用资源,随着采用 +Android + 技术且配置各异的设备越来越多,这些资源的重要性也日益增加。 +为了提供与不同配置的兼容性,您必须使用各种按类型和配置对资源进行分组的子目录,对项目 +{@code res/} +目录中的资源加以组织。 +</p> + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram1.png" height="167" alt="" /> +<p class="img-caption"> +<strong>图 1. </strong>两种不同的设备,均使用默认布局(应用不提供备用布局)。 +</p> +</div> + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram2.png" height="167" alt="" /> +<p class="img-caption"> +<strong>图 2. </strong>两种不同的设备,分别使用针对不同屏幕尺寸提供的不同布局。 +</p> +</div> + +<p>对于任意类型的资源,您均可以为应用指定默认资源和多个备用资源: +<em></em><em></em></p> +<ul> + <li>默认资源系指无论设备配置如何,或者在没有备用资源与当前配置相匹配时,均应使用的资源。 + +</li> + <li>备用资源系指设计用于特定配置的资源。 +要指明某组资源适用于特定配置,请将相应的配置限定符追加到目录名称。 +</li> +</ul> + +<p>例如,尽管默认 UI +布局保存在 +{@code res/layout/} 目录中,但是您可以指定在屏幕处于横向时要使用的不同布局,方法是将其保存在 {@code res/layout-land/} +目录中。Android +可以通过将设备的当前配置与资源目录名称进行匹配,自动应用合适的资源。</p> + +<p>图 1 +说明了在没有备用资源可用时,系统如何为两种不同的设备应用相同布局。图 2 +显示的是同一应用针对大屏幕添加了备用布局资源。</p> + +<p>以下文档提供了有关如何组织应用资源、如何指定备用资源以及如何在应用中访问这些资源等的完整指南: +</p> + +<dl> + <dt><strong><a href="providing-resources.html">提供资源</a></strong></dt> + <dd>您可在应用中提供何种资源、可将这些资源保存在何处以及如何为特定设备配置创建备用资源。 +</dd> + <dt><strong><a href="accessing-resources.html">访问资源</a></strong></dt> + <dd>如何通过从应用代码或其他 +XML 资源中引用来使用所提供的资源</dd> + <dt><strong><a href="runtime-changes.html">处理运行时变更</a></strong></dt> + <dd>如何管理在 Activity 运行时发生的配置变更。</dd> + <dt><strong><a href="localization.html">本地化</a></strong></dt> + <dd>从点到面地指导您使用备用资源本地化应用。尽管这只是备用资源的一种具体运用,但是这对于赢得更多用户非常重要。 + +</dd> + <dt><strong><a href="available-resources.html">资源类型</a></strong></dt> + <dd>有关您可提供的各种资源类型的参考文档,其中描述了这些资源的 +XML 元素、属性和语法。例如,此参考文档向您展示了如何为应用菜单、可绘制对象、动画等创建资源。 +</dd> +</dl> + +<!-- +<h2>Raw Assets</h2> + +<p>An alternative to saving files in {@code res/} is to save files in the {@code +assets/} directory. This should only be necessary if you need direct access to original files and +directories by name. Files saved in the {@code assets/} directory will not be given a resource +ID, so you can't reference them through the {@code R} class or from XML resources. Instead, you can +query data in the {@code assets/} directory like an ordinary file system, search through the +directory and +read raw data using {@link android.content.res.AssetManager}. For example, this can be more useful +when dealing with textures for a game. However, if you only need to read raw data from a file +(such as a video or audio file), then you should save files into the {@code res/raw/} directory and +then read a stream of bytes using {@link android.content.res.Resources#openRawResource(int)}. This +is uncommon, but if you need direct access to original files in {@code assets/}, refer to the {@link +android.content.res.AssetManager} documentation.</p> +--> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd new file mode 100644 index 000000000000..ea46d86152a4 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/providing-resources.jd @@ -0,0 +1,1094 @@ +page.title=提供资源 +parent.title=应用资源 +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>内容快览</h2> + <ul> + <li>不同类型的资源属于 {@code res/} 的不同子目录</li> + <li>备用资源提供特定于配置的资源文件</li> + <li>始终包含默认资源,使您的应用不依赖于特定的设备配置 +</li> + </ul> + <h2>本文内容</h2> + <ol> + <li><a href="#ResourceTypes">分组资源类型</a></li> + <li><a href="#AlternativeResources">提供备用资源</a> + <ol> + <li><a href="#QualifierRules">限定符命名规则</a></li> + <li><a href="#AliasResources">创建别名资源</a></li> + </ol> + </li> + <li><a href="#Compatibility">利用资源提供最佳设备兼容性</a></li> + <li><a href="#BestMatch">Android 如何找到最匹配资源</a></li> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="accessing-resources.html">访问资源</a></li> + <li><a href="available-resources.html">资源类型</a></li> + <li><a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a> +</li> + </ol> +</div> +</div> + +<p>您应该始终外部化应用资源,例如图像和代码中的字符串,这样有利于您单独维护这些资源。 +此外,您还应该为特定设备配置提供备用资源,方法是将它们分组到专门命名的资源目录中。 +在运行时,Android +会根据当前配置使用适当的资源。例如,您可能需要根据屏幕尺寸提供不同的 +UI +布局,或者根据语言设置提供不同的字符串。</p> + +<p>外部化应用资源后,即可使用在项目 {@code R} 类中生成的资源 +ID 访问这些资源。有关如何在应用中使用资源,我们将在<a href="accessing-resources.html">访问资源</a>中讨论。 + +本文档介绍如何对 Android +项目中的资源进行分组,以及如何为特定的设备配置提供备用资源。</p> + + +<h2 id="ResourceTypes">分组资源类型</h2> + +<p>您应将各种资源放入项目 +{@code res/} 目录的特定子目录下。例如,以下是一个简单项目的文件层次结构:</p> + +<pre class="classic no-pretty-print"> +MyProject/ + src/ <span style="color:black"> + MyActivity.java </span> + res/ + drawable/ <span style="color:black"> + graphic.png </span> + layout/ <span style="color:black"> + main.xml + info.xml</span> + mipmap/ <span style="color:black"> + icon.png </span> + values/ <span style="color:black"> + strings.xml </span> +</pre> + +<p>正如您在此示例中所看到的那样,{@code res/} 目录包含所有资源(在子目录下):一个图像资源、两个布局资源、启动器图标的 +{@code mipmap/} +目录以及一个字符串资源文件。资源目录名称非常重要,将在表 1 +中进行介绍。</p> + +<p class="note"><strong>注:</strong>如需了解有关使用 mipmap +文件夹的详细信息,请参阅<a href="{@docRoot}tools/projects/index.html#mipmap">管理项目概览</a>。</p> + +<p class="table-caption" id="table1"><strong>表 1. </strong>项目 {@code res/} +目录内支持的资源目录</p> + +<table> + <tr> + <th scope="col">目录</th> + <th scope="col">资源类型</th> + </tr> + + <tr> + <td><code>animator/</code></td> + <td>用于定义<a href="{@docRoot}guide/topics/graphics/prop-animation.html">属性动画</a>的 XML +文件。</td> + </tr> + + <tr> + <td><code>anim/</code></td> + <td>定义<a href="{@docRoot}guide/topics/graphics/view-animation.html#tween-animation">渐变动画</a>的 XML +文件。(属性动画也可以保存在此目录中,但是为了区分这两种类型,属性动画首选 +{@code animator/} +目录。)</td> + </tr> + + <tr> + <td><code>color/</code></td> + <td>用于定义颜色状态列表的 XML 文件。请参阅<a href="color-list-resource.html">颜色状态列表资源</a> +</td> + </tr> + + <tr> + <td><code>drawable/</code></td> + + <td><p>位图文件({@code .png}、{@code .9.png}、{@code .jpg}、{@code .gif})或编译为以下 Drawable 资源子类型的 +XML 文件:</p> + <ul> + <li>位图文件</li> + <li>九宫格(可调整大小的位图)</li> + <li>状态列表</li> + <li>形状</li> + <li>动画 Drawable</li> + <li>其他 Drawable</li> + </ul> + <p>请参阅 <a href="drawable-resource.html">Drawable 资源</a>。</p> + </td> + </tr> + + <tr> + <td><code>mipmap/</code></td> + <td>适用于不同启动器图标密度的 Drawable 文件。如需了解有关使用 {@code mipmap/} 文件夹管理启动器图标的详细信息,请参阅<a href="{@docRoot}tools/project/index.html#mipmap">管理项目概览</a>。 + +</td> + </tr> + + <tr> + <td><code>layout/</code></td> + <td>用于定义用户界面布局的 XML 文件。 + 请参阅<a href="layout-resource.html">布局资源</a>。</td> + </tr> + + <tr> + <td><code>menu/</code></td> + <td>用于定义应用菜单(如选项菜单、上下文菜单或子菜单)的 XML +文件。请参阅<a href="menu-resource.html">菜单资源</a>。</td> + </tr> + + <tr> + <td><code>raw/</code></td> + <td><p>要以原始形式保存的任意文件。要使用原始 +{@link java.io.InputStream} 打开这些资源,请使用资源 ID(即 {@code R.raw.<em>filename</em>})调用 {@link android.content.res.Resources#openRawResource(int) +Resources.openRawResource()}。</p> + <p>但是,如需访问原始文件名和文件层次结构,则可以考虑将某些资源保存在 +{@code +assets/} 目录下(而不是 {@code res/raw/})。{@code assets/} 中的文件没有资源 ID,因此您只能使用 {@link android.content.res.AssetManager} 读取这些文件。 +</p></td> + </tr> + + <tr> + <td><code>values/</code></td> + <td><p>包含字符串、整型数和颜色等简单值的 XML 文件。</p> + <p>其他 {@code res/} 子目录中的 XML +资源文件是根据 +XML +文件名定义单个资源,而目录中的 {@code values/} 文件可描述多个资源。对于此目录中的文件,{@code <resources>} 元素的每个子元素均定义一个资源。例如,{@code <string>} 元素创建 +{@code R.string} 资源,{@code <color>} 元素创建 {@code R.color} +资源。</p> + <p>由于每个资源均用其自己的 XML +元素定义,因此您可以根据自己的需要命名文件,并将不同的资源类型放在一个文件中。但是,为了清晰起见,您可能需要将独特的资源类型放在不同的文件中。 +例如,对于可在此目录中创建的资源,下面给出了相应的文件名约定: +</p> + <ul> + <li>arrays.xml,用于资源数组(<a href="more-resources.html#TypedArray">类型化数组</a>)。</li> + <li>colors.xml:<a href="more-resources.html#Color">颜色值</a>。</li> + <li>dimens.xml:<a href="more-resources.html#Dimension">尺寸值</a>。</li> + <li>strings.xml:<a href="string-resource.html">字符串值</a>。 +</li> + <li>styles.xml:<a href="style-resource.html">样式</a>。</li> + </ul> + <p>请参阅<a href="string-resource.html">字符串资源</a>、<a href="style-resource.html">样式资源</a>和<a href="more-resources.html">更多资源类型</a>。 + +</p> + </td> + </tr> + + <tr> + <td><code>xml/</code></td> + <td>可以在运行时通过调用 {@link +android.content.res.Resources#getXml(int) Resources.getXML()} 读取的任意 XML 文件。各种 XML +配置文件(如<a href="{@docRoot}guide/topics/search/searchable-config.html">可搜索配置</a>)都必须保存在此处。 +<!-- or preferences configuration. --></td> + </tr> +</table> + +<p class="caution"><strong>注意:</strong>切勿将资源文件直接保存在 +{@code res/} 目录内,这会导致出现编译错误。</p> + +<p>如需了解有关某些资源类型的详细信息,请参阅<a href="available-resources.html">资源类型</a>文档。</p> + +<p>保存在表 1 +中定义的子目录下的资源是“默认”资源。即,这些资源定义应用的默认设计和内容。但是,采用 +Android +技术的不同设备类型可能需要不同类型的资源。例如,如果设备的屏幕尺寸大于标准屏幕,则应提供不同的布局资源,以充分利用额外的屏幕空间。 +或者,如果设备的语言设置不同,则应提供不同的字符串资源,以转换用户界面中的文本。 + +要为不同的设备配置提供这些不同资源,除了默认资源以外,您还需要提供备用资源。 + +</p> + + +<h2 id="AlternativeResources">提供备用资源</h2> + + +<div class="figure" style="width:429px"> +<img src="{@docRoot}images/resources/resource_devices_diagram2.png" height="167" alt="" /> +<p class="img-caption"> +<strong>图 1. </strong>两种不同的设备,均使用不同的布局资源。</p> +</div> + +<p>几乎每个应用都应提供备用资源以支持特定的设备配置。 +例如,对于不同的屏幕密度和语言,您应分别包括备用 Drawable 资源和备用字符串资源。 +在运行时,Android +会检测当前设备配置并为应用加载合适的资源。 +</p> + +<p>为一组资源指定特定于配置的备用资源:</p> +<ol> + <li>在 {@code res/} 中创建一个以 {@code +<em><resources_name></em>-<em><config_qualifier></em>} 形式命名的新目录。 + <ul> + <li><em>{@code <resources_name>}</em> 是相应默认资源的目录名称(如表 1 +中所定义)。</li> + <li><em>{@code <qualifier>}</em> 是指定要使用这些资源的各个配置的名称(如表 2 +中所定义)。</li> + </ul> + <p>您可以追加多个 <em>{@code <qualifier>}</em>。以短划线将其分隔。 +</p> + <p class="caution"><strong>注意:</strong>追加多个限定符时,必须按照表 +2 中列出的相同顺序放置它们。如果限定符的顺序错误,则该资源将被忽略。 +</p> + </li> + <li>将相应的备用资源保存在此新目录下。这些资源文件的名称必须与默认资源文件完全一样。 +</li> +</ol> + +<p>例如,以下是一些默认资源和备用资源:</p> + +<pre class="classic no-pretty-print"> +res/ + drawable/ <span style="color:black"> + icon.png + background.png </span> + drawable-hdpi/ <span style="color:black"> + icon.png + background.png </span> +</pre> + +<p>{@code hdpi} +限定符表示该目录中的资源适用于屏幕密度较高的设备。其中每个 Drawable 目录中的图像已针对特定的屏幕密度调整大小,但是文件名完全相同。 + +这样一来,用于引用 {@code icon.png} 或 {@code +background.png} +图像的资源 ID 始终相同,但是 Android +会通过将设备配置信息与资源目录名称中的限定符进行比较,选择最符合当前设备的各个资源版本。</p> + +<p>Android +支持若干配置限定符,您可以通过使用短划线分隔每个限定符,向一个目录名称添加多个限定符。表 2 +按优先顺序列出了有效的配置限定符;如果对资源目录使用多个限定符,则必须按照表中列出的顺序将它们添加到目录名称。 + +</p> + + +<p class="table-caption" id="table2"><strong>表 2. </strong>配置限定符名称。 +</p> +<table> + <tr> + <th>配置</th> + <th>限定符值</th> + <th>描述</th> + </tr> + <tr id="MccQualifier"> + <td>MCC 和 MNC</td> + <td>示例:<br/> + <code>mcc310</code><br/> + <code><nobr>mcc310-mnc004</nobr></code><br/> + <code>mcc208-mnc00</code><br/> + 等等 + </td> + <td> + <p>移动国家代码 (MCC),(可选)后跟设备 SIM +卡中的移动网络代码 (MNC)。例如,<code>mcc310</code> 是指美国的任一运营商,<code>mcc310-mnc004</code> 是指美国的 Verizon +公司,<code>mcc208-mnc00</code> 是指法国的 Orange +公司。</p> + <p>如果设备使用无线电连接(GSM 手机),则 MCC 和 MNC 值来自 SIM +卡。</p> + <p>也可以单独使用 +MCC(例如,将国家/地区特定的合法资源包括在应用中)。如果只需根据语言指定,则改用“语言和区域”<em></em>限定符(稍后进行介绍)。 +如果决定使用 MCC 和 +MNC 限定符,请谨慎执行此操作并测试限定符是否按预期工作。</p> + <p>另请参阅配置字段 +{@link +android.content.res.Configuration#mcc} 和 {@link +android.content.res.Configuration#mnc},这两个字段分别表示当前的移动国家代码和移动网络代码。</p> + </td> + </tr> + <tr id="LocaleQualifier"> + <td>语言和区域</td> + <td>示例:<br/> + <code>en</code><br/> + <code>fr</code><br/> + <code>en-rUS</code><br/> + <code>fr-rFR</code><br/> + <code>fr-rCA</code><br/> + 等等 + </td> + <td><p>语言通过由两个字母组成的 <a href="http://www.loc.gov/standards/iso639-2/php/code_list.php">ISO +639-1</a> 语言代码定义,(可选)后跟两个字母组成的 +<a href="http://www.iso.org/iso/en/prods-services/iso3166ma/02iso-3166-code-lists/list-en1.html">ISO +3166-1-alpha-2</a> 区域码(前带小写字母“{@code r}”)。 + </p><p> + 这些代码不区分大小写;{@code r} +前缀用于区分区域码。<em></em> + 不能单独指定区域。</p> + <p>如果用户更改系统设置中的语言,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + <p>有关针对其他语言本地化应用的完整指南,请参阅<a href="localization.html">本地化</a>。 +</p> + <p>另请参阅 {@link android.content.res.Configuration#locale} 配置字段,该字段表示当前的区域设置。 +</p> + </td> + </tr> + <tr id="LayoutDirectionQualifier"> + <td>布局方向</td> + <td><code>ldrtl</code><br/> + <code>ldltr</code><br/> + </td> + <td><p>应用的布局方向。{@code ldrtl} 是指“布局方向从右到左”。{@code ldltr} +是指“布局方向从左到右”,这是默认的隐式值。 + </p> + <p>它适用于布局、图片或值等任何资源。 + </p> + <p>例如,若要针对阿拉伯语提供某种特定布局,并针对任何其他“从右到左”语言(如波斯语或希伯来语)提供某种通用布局,则可编码如下: + + </p> +<pre class="classic no-pretty-print"> +res/ + layout/ <span style="color:black"> + main.xml </span>(Default layout) + layout-ar/ <span style="color:black"> + main.xml </span>(Specific layout for Arabic) + layout-ldrtl/ <span style="color:black"> + main.xml </span>(Any "right-to-left" language, except + for Arabic, because the "ar" language qualifier + has a higher precedence.) +</pre> + <p class="note"><strong>注:</strong>要为应用启用从右到左的布局功能,必须将 <a href="{@docRoot}guide/topics/manifest/application-element.html#supportsrtl">{@code +supportsRtl}</a> 设置为 +{@code "true"},并将 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> 设置为 17 或更高。</p> + <p><em>此项为API 级别 17 中新增配置。</em></p> + </td> + </tr> + <tr id="SmallestScreenWidthQualifier"> + <td>smallestWidth</td> + <td><code>sw<N>dp</code><br/><br/> + 示例:<br/> + <code>sw320dp</code><br/> + <code>sw600dp</code><br/> + <code>sw720dp</code><br/> + 等等 + </td> + <td> + <p>屏幕的基本尺寸,由可用屏幕区域的最小尺寸指定。 +具体来说,设备的 smallestWidth +是屏幕可用高度和宽度的最小尺寸(您也可以将其视为屏幕的“最小可能宽度”)。无论屏幕的当前方向如何,您均可使用此限定符确保应用 +UI 的可用宽度至少为 +{@code <N>}dp。</p> + <p>例如,如果布局要求屏幕区域的最小尺寸始终至少为 +600dp,则可使用此限定符创建布局资源 {@code +res/layout-sw600dp/}。仅当可用屏幕的最小尺寸至少为 +600dp 时,系统才会使用这些资源,而不考虑 +600dp 所代表的边是用户所认为的高度还是宽度。smallestWidth 是设备的固定屏幕尺寸特性;<strong>设备的 smallestWidth +不会随屏幕方向的变化而改变</strong>。</p> + <p>设备的 smallestWidth 将屏幕装饰元素和系统 UI +考虑在内。例如,如果设备的屏幕上有一些永久性 UI 元素占据沿 +smallestWidth +轴的空间,则系统会声明 smallestWidth +小于实际屏幕尺寸,因为这些屏幕像素不适用于您的 +UI。因此,使用的值应该是布局所需要<em></em>的实际最小尺寸(通常,无论屏幕的当前方向如何,此值都是布局支持的“最小宽度”)。</p> + <p>以下是一些可用于普通屏幕尺寸的值:</p> + <ul> + <li>320,适用于屏幕配置如下的设备: + <ul> + <li>240x320 ldpi(QVGA 手机)</li> + <li>320x480 mdpi(手机)</li> + <li>480x800 hdpi(高密度手机)</li> + </ul> + </li> + <li>480,适用于 480x800 mdpi 之类的屏幕(平板电脑/手机)。</li> + <li>600,适用于 600x1024 mdpi 之类的屏幕(7 英寸平板电脑)。</li> + <li>720,适用于 720x1280 mdpi 之类的屏幕(10 英寸平板电脑)。</li> + </ul> + <p>应用为多个资源目录提供不同的 +smallestWidth 限定符值时,系统会使用最接近(但未超出)设备 +smallestWidth 的值。 </p> + <p><em>此项为 API 级别 13 中新增配置。</em></p> + <p>另请参阅 <a href="{@docRoot}guide/topics/manifest/supports-screens-element.html#requiresSmallest">{@code +android:requiresSmallestWidthDp}</a> 属性和 {@link +android.content.res.Configuration#smallestScreenWidthDp} 配置字段,前者声明与应用兼容的最小 +smallestWidth;后者存放设备的 +smallestWidth 值。</p> + <p>如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>开发者指南。 + +</p> + </td> + </tr> + <tr id="ScreenWidthQualifier"> + <td>可用宽度</td> + <td><code>w<N>dp</code><br/><br/> + 示例:<br/> + <code>w720dp</code><br/> + <code>w1024dp</code><br/> + 等等 + </td> + <td> + <p>指定资源应该使用的最小可用屏幕宽度,以 {@code dp} +为单位,由 <code><N></code> 值定义。在横向和纵向之间切换时,为了匹配当前实际宽度,此配置值也会随之发生变化。 + +</p> + <p>应用为多个资源目录提供不同的此配置值时,系统会使用最接近(但未超出)设备当前屏幕宽度的值。 + +此处的值考虑到了屏幕装饰元素,因此如果设备显示屏的左边缘或右边缘上有一些永久性 UI +元素,考虑到这些 UI +元素,它会使用小于实际屏幕尺寸的宽度值,这样会减少应用的可用空间。 + +</p> + <p><em>此项为 API 级别 13 中新增配置。</em></p> + <p>另请参阅 +{@link android.content.res.Configuration#screenWidthDp} 配置字段,该字段存放当前屏幕宽度。</p> + <p>如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>开发者指南。 + +</p> + </td> + </tr> + <tr id="ScreenHeightQualifier"> + <td>可用高度</td> + <td><code>h<N>dp</code><br/><br/> + 示例:<br/> + <code>h720dp</code><br/> + <code>h1024dp</code><br/> + 等等 + </td> + <td> + <p>指定资源应该使用的最小可用屏幕高度,以“dp”为单位,由 <code><N></code> 值定义。 +在横向和纵向之间切换时,为了匹配当前实际高度,此配置值也会随之发生变化。 + +</p> + <p>应用为多个资源目录提供不同的此配置值时,系统会使用最接近(但未超出)设备当前屏幕高度的值。 + +此处的值考虑到了屏幕装饰元素,因此如果设备显示屏的上边缘或下边缘有一些永久性 UI +元素,考虑到这些 UI +元素,同时为减少应用的可用空间,它会使用小于实际屏幕尺寸的高度值。 + +非固定的屏幕装饰元素(例如,全屏时可隐藏的手机状态栏)并不<em></em>在考虑范围内,标题栏或操作栏等窗口装饰也不在考虑范围内,因此应用必须准备好处理稍小于其所指定值的空间。 + + + + + <p><em>此项为 API 级别 13 中新增配置。</em></p> + <p>另请参阅 +{@link android.content.res.Configuration#screenHeightDp} 配置字段,该字段存放当前屏幕宽度。</p> + <p>如需了解有关设计不同屏幕和使用此限定符的详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>开发者指南。 + +</p> + </td> + </tr> + <tr id="ScreenSizeQualifier"> + <td>屏幕尺寸</td> + <td> + <code>small</code><br/> + <code>normal</code><br/> + <code>large</code><br/> + <code>xlarge</code> + </td> + <td> + <ul class="nolist"> + <li>{@code small}:尺寸类似于低密度 +QVGA 屏幕的屏幕。小屏幕的最小布局尺寸约为 +320x426 dp 单位。例如,QVGA 低密度屏幕和 VGA +高密度屏幕。</li> + <li>{@code normal}:尺寸类似于中等密度 +HVGA 屏幕的屏幕。标准屏幕的最小布局尺寸约为 +320x470 dp 单位。例如,WQVGA +低密度屏幕、HVGA 中等密度屏幕、WVGA +高密度屏幕。</li> + <li>{@code large}:尺寸类似于中等密度 +VGA 屏幕的屏幕。 + 大屏幕的最小布局尺寸约为 480x640 dp 单位。 + 例如,VGA 和 WVGA 中等密度屏幕。</li> + <li>{@code xlarge}:明显大于传统中等密度 +HVGA 屏幕的屏幕。超大屏幕的最小布局尺寸约为 +720x960 dp 单位。在大多数情况下,屏幕超大的设备体积过大,不能放进口袋,最常见的是平板式设备。 + +<em>此项为 API 级别 9 中新增配置。</em></li> + </ul> + <p class="note"><strong>注:</strong>使用尺寸限定符并不表示资源仅<em></em>适用于该尺寸的屏幕。 +如果没有为备用资源提供最符合当前设备配置的限定符,则系统可能使用其中<a href="#BestMatch">最匹配</a>的资源。 + +</p> + <p class="caution"><strong>注意:</strong>如果所有资源均使用大于<em></em>当前屏幕的尺寸限定符,则系统<strong>不</strong>会使用这些资源,并且应用在运行时将会崩溃(例如,如果所有布局资源均用 {@code +xlarge} 限定符标记,但设备是标准尺寸的屏幕)。 + +</p> + <p><em>此项为 API 级别 4 中新增配置。</em></p> + + <p>如需了解详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>。 +</p> + <p>另请参阅 {@link android.content.res.Configuration#screenLayout} 配置字段,该字段表示屏幕是小尺寸、标准尺寸还是大尺寸。 + +</p> + </td> + </tr> + <tr id="ScreenAspectQualifier"> + <td>屏幕纵横比</td> + <td> + <code>long</code><br/> + <code>notlong</code> + </td> + <td> + <ul class="nolist"> + <li>{@code long}:宽屏,如 WQVGA、WVGA、FWVGA</li> + <li>{@code notlong}:非宽屏,如 QVGA、HVGA 和 VGA</li> + </ul> + <p><em>此项为 API 级别 4 中新增配置。</em></p> + <p>它完全基于屏幕的纵横比(宽屏较宽),而与屏幕方向无关。 +</p> + <p>另请参阅 {@link android.content.res.Configuration#screenLayout} 配置字段,该字段指示屏幕是否为宽屏。 +</p> + </td> + </tr> + <tr id="OrientationQualifier"> + <td>屏幕方向</td> + <td> + <code>port</code><br/> + <code>land</code> <!-- <br/> + <code>square</code> --> + </td> + <td> + <ul class="nolist"> + <li>{@code port}:设备处于纵向(垂直)</li> + <li>{@code land}:设备处于横向(水平)</li> + <!-- Square mode is currently not used. --> + </ul> + <p>如果用户旋转屏幕,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + <p>另请参阅 {@link android.content.res.Configuration#orientation} 配置字段,该字段指示当前的设备方向。 +</p> + </td> + </tr> + <tr id="UiModeQualifier"> + <td>UI 模式</td> + <td> + <code>car</code><br/> + <code>desk</code><br/> + <code>television</code><br/> + <code>appliance</code> +<code>watch</code> + </td> + <td> + <ul class="nolist"> + <li>{@code car}:设备正在车载手机座上显示</li> + <li>{@code desk}:设备正在桌面手机座上显示</li> + <li>{@code television}:设备正在电视上显示,为用户提供“十英尺”体验,其 +UI 位于远离用户的大屏幕上,主要面向方向键或其他非指针式交互 + +</li> + <li>{@code appliance}:设备用作不带显示屏的装置 +</li> + <li>{@code watch}:设备配有显示屏,戴在手腕上</li> + </ul> + <p><em>此项为 API 级别 8 中新增配置,API 13 中新增电视配置,API 20 中新增手表配置。</em></p> + <p>如需了解应用在设备插入手机座或从中移除时的响应方式,请阅读<a href="{@docRoot}training/monitoring-device-state/docking-monitoring.html">确定并监控插接状态和类型</a>。 + +</p> + <p>如果用户将设备放入手机座中,它有可能在应用生命周期中发生改变。 +可以使用 {@link +android.app.UiModeManager} 启用或禁用其中某些模式。如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + </td> + </tr> + <tr id="NightQualifier"> + <td>夜间模式</td> + <td> + <code>night</code><br/> + <code>notnight</code> + </td> + <td> + <ul class="nolist"> + <li>{@code night}:夜间</li> + <li>{@code notnight}:白天</li> + </ul> + <p><em>此项为 API 级别 8 中新增配置。</em></p> + <p>如果夜间模式停留在自动模式(默认),它有可能在应用生命周期中发生改变。在这种情况下,该模式会根据当天的时间进行调整。 +可以使用 +{@link android.app.UiModeManager} 启用或禁用此模式。如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + </td> + </tr> + <tr id="DensityQualifier"> + <td>屏幕像素密度 (dpi)</td> + <td> + <code>ldpi</code><br/> + <code>mdpi</code><br/> + <code>hdpi</code><br/> + <code>xhdpi</code><br/> + <code>xxhdpi</code><br/> + <code>xxxhdpi</code><br/> + <code>nodpi</code><br/> + <code>tvdpi</code> + </td> + <td> + <ul class="nolist"> + <li>{@code ldpi}:低密度屏幕;约为 120dpi。</li> + <li>{@code mdpi}:中等密度(传统 HVGA)屏幕;约为 +160dpi。</li> + <li>{@code hdpi}:高密度屏幕;约为 240dpi。</li> + <li>{@code xhdpi}:超高密度屏幕;约为 320dpi。<em>API +级别 8 中新增配置</em></li> + <li>{@code xxhdpi}:超超高密度屏幕;约为 480dpi。<em>API +级别 16 中新增配置</em></li> + <li>{@code xxxhdpi}:超超超高密度屏幕使用(仅限启动器图标,请参阅“支持多个屏幕”中的<a href="{@docRoot}guide/practices/screens_support.html#xxxhdpi-note">注释</a>);约为 +640dpi。<em></em> +<em>API +级别 18 中新增配置</em></li> + <li>{@code nodpi}:它可用于您不希望缩放以匹配设备密度的位图资源。 +</li> + <li>{@code tvdpi}:密度介于 mdpi 和 hdpi 之间的屏幕;约为 213dpi。它并不是“主要”密度组, +主要用于电视,而大多数应用都不需要它。对于大多数应用而言,提供 mdpi 和 +hdpi +资源便已足够,系统将根据需要对其进行缩放。API 级别 13 中引入了此限定符。</li> + </ul> + <p>六个主要密度之间的缩放比为 3:4:6:8:12:16(忽略 +tvdpi 密度)。因此,9x9 (ldpi) 位图相当于 12x12 (mdpi)、18x18 (hdpi)、24x24 (xhdpi) 位图,依此类推。 +</p> + <p>如果您认为图像资源在电视或其他某些设备上呈现的效果不够好,而想尝试使用 tvdpi 资源,则缩放比例为 +1.33*mdpi。例如,mdpi +屏幕的 100px x 100px 图像应该相当于 tvdpi 的133px x 133px。</p> + <p class="note"><strong>注:</strong>使用密度限定符并不表示资源仅适用于该密度的屏幕。 +<em></em>如果没有为备用资源提供最符合当前设备配置的限定符,则系统可能使用其中<a href="#BestMatch">最匹配</a>的资源。 + +</p> + <p>如需了解有关如何处理不同屏幕密度以及 Android 如何缩放位图以适应当前密度的详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>。 + +</p> + </td> + </tr> + <tr id="TouchscreenQualifier"> + <td>触摸屏类型</td> + <td> + <code>notouch</code><br/> + <code>finger</code> + </td> + <td> + <ul class="nolist"> + <li>{@code notouch}:设备没有触摸屏。</li> + <li>{@code finger}:设备有一个专供用户通过手指直接与其交互的触摸屏。 +</li> + </ul> + <p>另请参阅 {@link android.content.res.Configuration#touchscreen} 配置字段,该字段指示设备上的触摸屏类型。 +</p> + </td> + </tr> + <tr id="KeyboardAvailQualifier"> + <td>键盘可用性</td> + <td> + <code>keysexposed</code><br/> + <code>keyshidden</code><br/> + <code>keyssoft</code> + </td> + <td> + <ul class="nolist"> + <li>{@code keysexposed}:设备具有可用的键盘。如果设备启用了软键盘(不无可能),那么即使硬键盘没有展示给用户,哪怕设备没有硬键盘,也可以使用此限定符。<em></em> + +如果没有提供或已经禁用软键盘,则只有在显示硬键盘时才会使用此限定符。 + +</li> + <li>{@code keyshidden}:设备具有可用的硬键盘,但它处于隐藏状态,且设备没有启用软键盘。<em></em><em></em> +</li> + <li>{@code keyssoft}:设备已经启用软键盘(无论是否可见)。 +</li> + </ul> + <p>如果提供了 <code>keysexposed</code> 资源,但未提供 <code>keyssoft</code> +资源,那么只要系统已经启用软键盘,就会使用 +<code>keysexposed</code> 资源,而不考虑键盘是否可见。</p> + <p>如果用户打开硬键盘,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + <p>另请参阅配置字段 +{@link +android.content.res.Configuration#hardKeyboardHidden} 和 {@link +android.content.res.Configuration#keyboardHidden},这两个字段分别指示硬键盘的可见性和任何一种键盘(包括软键盘)的可见性。</p> + </td> + </tr> + <tr id="ImeQualifier"> + <td>主要文本输入法</td> + <td> + <code>nokeys</code><br/> + <code>qwerty</code><br/> + <code>12key</code> + </td> + <td> + <ul class="nolist"> + <li>{@code nokeys}:设备没有用于文本输入的硬按键。</li> + <li>{@code qwerty}:设备具有标准硬键盘(无论是否对用户可见)。 + +</li> + <li>{@code 12key}:设备具有 12 键硬键盘(无论是否对用户可见)。 +</li> + </ul> + <p>另请参阅 {@link android.content.res.Configuration#keyboard} 配置字段,该字段指示可用的主要文本输入法。 +</p> + </td> + </tr> + <tr id="NavAvailQualifier"> + <td>导航键可用性</td> + <td> + <code>navexposed</code><br/> + <code>navhidden</code> + </td> + <td> + <ul class="nolist"> + <li>{@code navexposed}:导航键可供用户使用。</li> + <li>{@code navhidden}:导航键不可用(例如,位于密封盖子后面)。 +</li> + </ul> + <p>如果用户显示导航键,它有可能在应用生命周期中发生改变。 +如需了解这会在运行期间给应用带来哪些影响,请参阅<a href="runtime-changes.html">处理运行时变更</a>。 +</p> + <p>另请参阅 +{@link android.content.res.Configuration#navigationHidden} 配置字段,该字段指示导航键是否处于隐藏状态。</p> + </td> + </tr> + <tr id="NavigationQualifier"> + <td>主要非触摸导航方法</td> + <td> + <code>nonav</code><br/> + <code>dpad</code><br/> + <code>trackball</code><br/> + <code>wheel</code> + </td> + <td> + <ul class="nolist"> + <li>{@code nonav}:除了使用触摸屏以外,设备没有其他导航设施。 +</li> + <li>{@code dpad}:设备具有用于导航的方向键。</li> + <li>{@code trackball}:设备具有用于导航的轨迹球。</li> + <li>{@code wheel}:设备具有用于导航的方向盘(不常见)。</li> + </ul> + <p>另请参阅 {@link android.content.res.Configuration#navigation} +配置字段,该字段指示可用的导航方法类型。</p> + </td> + </tr> +<!-- DEPRECATED + <tr> + <td>Screen dimensions</td> + <td>Examples:<br/> + <code>320x240</code><br/> + <code>640x480</code><br/> + etc. + </td> + <td> + <p>The larger dimension must be specified first. <strong>This configuration is deprecated +and should not be used</strong>. Instead use "screen size," "wider/taller screens," and "screen +orientation" described above.</p> + </td> + </tr> +--> + <tr id="VersionQualifier"> + <td>平台版本(API 级别)</td> + <td>示例:<br/> + <code>v3</code><br/> + <code>v4</code><br/> + <code>v7</code><br/> + 等等</td> + <td> + <p>设备支持的 API 级别。例如,<code>v1</code> 对应于 API 级别 +1(带有 Android 1.0 或更高版本系统的设备),<code>v4</code> 对应于 API 级别 4(带有 Android +1.6 或更高版本系统的设备)。如需了解有关这些值的详细信息,请参阅 +<a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#ApiLevels">Android API 级别</a>文档。</p> + </td> + </tr> +</table> + + +<p class="note"><strong>注:</strong>有些配置限定符是从 Android 1.0 +才开始添加,因此并非所有版本的 Android 系统都支持所有限定符。使用新限定符会隐式添加平台版本限定符,因此较旧版本系统的设备必然会忽略它。 +例如,使用 +<code>w600dp</code> 限定符会自动包括 <code>v13</code> 限定符,因为可用宽度限定符是 API 级别 13 +中的新增配置。为了避免出现任何问题,请始终包含一组默认资源(一组“不带限定符”的资源)。<em></em> +如需了解详细信息,请参阅<a href="#Compatibility">利用资源提供最佳设备兼容性</a>部分。 + +</p> + + + +<h3 id="QualifierRules">限定符命名规则</h3> + +<p>以下是一些关于使用配置限定符名称的规则:</p> + +<ul> + <li>您可以为单组资源指定多个限定符,并使用短划线分隔。例如,<code>drawable-en-rUS-land</code> +适用于横排美国英语设备。 +</li> + <li>这些限定符必须遵循<a href="#table2">表 2</a> 中列出的顺序。例如: + + <ul> + <li>错误:<code>drawable-hdpi-port/</code></li> + <li>正确:<code>drawable-port-hdpi/</code></li> + </ul> + </li> + <li>不能嵌套备用资源目录。例如,您不能拥有 +<code>res/drawable/drawable-en/</code>。</li> + <li>值不区分大小写。在处理之前,资源编译器会将目录名称转换为小写,以避免不区分大小写的文件系统出现问题。 + +名称中使用的任何大写字母只是为了便于认读。</li> + <li>对于每种限定符类型,仅支持一个值。例如,若要对西班牙语和法语使用相同的 Drawable 文件,则您肯定不能拥有名为 +<code>drawable-rES-rFR/</code> +的目录,<em></em>而是需要两个包含相应文件的资源目录,如 +<code>drawable-rES/</code> +和 <code>drawable-rFR/</code>。然而,实际上您无需将相同的文件都复制到这两个位置。相反,您可以创建指向资源的别名。 +请参阅下面的<a href="#AliasResources">创建别名资源</a>。 +</li> +</ul> + +<p>将备用资源保存到以这些限定符命名的目录中之后,Android +会根据当前设备配置在应用中自动应用这些资源。 +每次请求资源时,Android +都会检查备用资源目录是否包含所请求的资源文件,然后<a href="#BestMatch">找到最匹配资源</a>(下文进行介绍)。 +如果没有与特定设备配置匹配的备用资源,则 +Android +会使用相应的默认资源(一组用于不含配置限定符的特定资源类型的资源)。 +</p> + + + +<h3 id="AliasResources">创建别名资源</h3> + +<p>如果您想将某一资源用于多种设备配置(但是不想作为默认资源提供),则无需将同一资源放入多个备用资源目录中。 + +相反,您可以(在某些情况下)创建备用资源,充当保存在默认资源目录下的资源的别名。 + +</p> + +<p class="note"><strong>注:</strong>并非所有资源都会提供相应机制让您创建指向其他资源的别名。 +特别是,{@code xml/} 目录中的动画资源、菜单资源、原始资源以及其他未指定资源均不提供此功能。 +</p> + +<p>例如,假设您有一个应用图标 {@code icon.png},并且需要不同区域设置的独特版本。 +但是,加拿大英语和加拿大法语这两种区域设置需要使用同一版本。 +您可能会认为需要将相同的图像复制到加拿大英语和加拿大法语对应的资源目录中,但事实并非如此。 + +相反,您可以将用于二者的图像另存为 {@code icon_ca.png}(除 +{@code icon.png} +以外的任何名称),并将其放入默认 {@code res/drawable/} 目录中。然后,在 {@code +res/drawable-en-rCA/} +和 {@code res/drawable-fr-rCA/} 中创建 {@code icon.xml} 文件,使用 {@code <bitmap>} 元素引用 {@code icon_ca.png}资源。这样,您只需存储 PNG +文件的一个版本和两个指向该版本的小型 XML 文件。(XML 文件示例如下。)</p> + + +<h4>Drawable</h4> + +<p>要创建指向现有 Drawable 的别名,请使用 +{@code <bitmap>} 元素。例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<bitmap xmlns:android="http://schemas.android.com/apk/res/android" + android:src="@drawable/icon_ca" /> +</pre> + +<p>如果将此文件另存为 +{@code icon.xml}(例如,在备用资源目录中,另存为 {@code res/drawable-en-rCA/}),则会编译到可作为 +{@code R.drawable.icon} 引用的资源中,但实际上它是 {@code +R.drawable.icon_ca} 资源(保存在 {@code res/drawable/} 中)的别名。</p> + + +<h4>布局</h4> + +<p>要创建指向现有布局的别名,请使用包装在 {@code <merge>} 中的 +{@code <include>}元素。例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<merge> + <include layout="@layout/main_ltr"/> +</merge> +</pre> + +<p>如果将此文件另存为 {@code main.xml},则会编译到可作为 +{@code R.layout.main} 引用的资源中,但实际上它是 {@code R.layout.main_ltr} +资源的别名。</p> + + +<h4>字符串和其他简单值</h4> + +<p>要创建指向现有字符串的别名,只需将所需字符串的资源 +ID 用作新字符串的值即可。例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="hello">Hello</string> + <string name="hi">@string/hello</string> +</resources> +</pre> + +<p>{@code R.string.hi} 资源现在是 {@code R.string.hello} 的别名。</p> + +<p> <a href="{@docRoot}guide/topics/resources/more-resources.html">其他简单值</a>的原理相同。 +例如,颜色:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<resources> + <color name="yellow">#f00</color> + <color name="highlight">@color/red</color> +</resources> +</pre> + + + + +<h2 id="Compatibility">利用资源提供最佳设备兼容性</h2> + +<p>要使应用支持多种设备配置,则务必为应用使用的每种资源类型提供默认资源,这一点非常重要。 +</p> + +<p>例如,如果应用支持多种语言,请始终包含不带<em></em><a href="#LocaleQualifier">语言和区域限定符</a>的 {@code +values/} 目录(用于保存字符串)。相反,如果您将所有字符串放入带有语言和区域限定符的目录中,则在语言设置不支持您的字符串的设备上运行应用时,应用将会崩溃。 + +但是,只要提供默认 {@code values/} 资源,应用就会正常运行(即使用户不理解该语言,这也总比崩溃要好)。 + +</p> + +<p>同样,如果您根据屏幕方向提供不同的布局资源,则应选择一个方向作为默认方向。 +例如,不要在 {@code +layout-land/} 和 {@code layout-port/} 中分别提供横向和纵向的布局资源,而是保留其中之一作为默认设置,例如:{@code layout/} +用于横向,{@code layout-port/} 用于纵向。</p> + +<p>提供默认资源至关重要,这不仅仅因为应用可能在超出预期的配置上运行,也因为新版 +Android +有时会添加旧版本不支持的配置限定符。若要使用新的资源限定符,又希望维持对旧版 +Android 的代码兼容性,则当旧版 +Android +运行应用时,如果不提供默认资源,应用将会崩溃,这是因为它无法使用以新限定符命名的资源。例如,如果将 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code +minSdkVersion}</a> 设置为 4,并使用<a href="#NightQualifier">夜间模式</a>({@code night} 或 {@code notnight},API +级别 8 中新增配置)限定所有 Drawable 资源,则 API 级别 4 设备无法访问 Drawable 资源,而且会崩溃。在这种情况下,您可能希望 {@code notnight} 成为默认资源,为此,您应排除该限定符,使 Drawable 资源位于 {@code drawable/} 或 +{@code drawable-night/} +中。</p> + +<p>因此,为了提供最佳设备兼容性,请始终为应用正确运行所必需的资源提供默认资源。 +然后,使用配置限定符为特定的设备配置创建备用资源。 +</p> + +<p>这条规则有一个例外:如果应用的 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> 为 4 或更高,则在提供带<a href="#DensityQualifier">屏幕密度</a>限定符的备用 Drawable 资源时,不<em></em>需要默认 Drawable 资源。 + +即使没有默认 Drawable 资源,Android +也可以从备用屏幕密度中找到最佳匹配项并根据需要缩放位图。 +但是,为了在所有类型的设备上提供最佳体验,您应该为所有三种类型的密度提供备用 Drawable。 +</p> + + + +<h2 id="BestMatch">Android 如何找到最匹配资源</h2> + +<p>当您请求要为其提供备用资源的资源时,Android +会根据当前的设备配置选择要在运行时使用的备用资源。为演示 +Android 如何选择备用资源,假设以下 Drawable 目录分别包含相同图像的不同版本: +</p> + +<pre class="classic no-pretty-print"> +drawable/ +drawable-en/ +drawable-fr-rCA/ +drawable-en-port/ +drawable-en-notouch-12key/ +drawable-port-ldpi/ +drawable-port-notouch-12key/ +</pre> + +<p>同时,假设设备配置如下:</p> + +<p style="margin-left:1em;"> +区域设置 = <code>en-GB</code> <br/> +屏幕方向 = <code>port</code> <br/> +屏幕像素密度 = <code>hdpi</code> <br/> +触摸屏类型 = <code>notouch</code> <br/> +主要文本输入法 = <code>12key</code> +</p> + +<p>通过将设备配置与可用的备用资源进行比较,Android 从 +{@code drawable-en-port} 中选择 Drawable。</p> + +<p>系统使用以下逻辑决定要使用的资源: +</p> + + +<div class="figure" style="width:371px"> +<img src="{@docRoot}images/resources/res-selection-flowchart.png" alt="" height="471" /> +<p class="img-caption"><strong>图 2. </strong>Android +如何找到最匹配资源的流程图。</p> +</div> + + +<ol> + <li>淘汰与设备配置冲突的资源文件。 + <p><code>drawable-fr-rCA/</code> 目录与 +<code>en-GB</code> 区域设置冲突,因而被淘汰。</p> +<pre class="classic no-pretty-print"> +drawable/ +drawable-en/ +<strike>drawable-fr-rCA/</strike> +drawable-en-port/ +drawable-en-notouch-12key/ +drawable-port-ldpi/ +drawable-port-notouch-12key/ +</pre> +<p class="note"><strong>例外:</strong>屏幕像素密度是唯一一个未因冲突而被淘汰的限定符。 +尽管设备的屏幕密度为 +hdpi,但是 +<code>drawable-port-ldpi/</code> 未被淘汰,因为此时每个屏幕密度均视为匹配。如需了解详细信息,请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>文档。 +</p></li> + + <li>选择列表(<a href="#table2">表 2</a>)中(下一个)优先级最高的限定符。(先从 MCC +开始,然后下移。) </li> + <li>是否有资源目录包括此限定符? </li> + <ul> + <li>若无,请返回到第 2 步,看看下一个限定符。(在该示例中,除非达到语言限定符,否则答案始终为“否”。) +</li> + <li>若有,请继续执行第 4 步。</li> + </ul> + </li> + + <li>淘汰不含此限定符的资源目录。在该示例中,系统会淘汰所有不含语言限定符的目录。 +</li> +<pre class="classic no-pretty-print"> +<strike>drawable/</strike> +drawable-en/ +drawable-en-port/ +drawable-en-notouch-12key/ +<strike>drawable-port-ldpi/</strike> +<strike>drawable-port-notouch-12key/</strike> +</pre> +<p class="note"><strong>例外:</strong>如果涉及的限定符是屏幕像素密度,则 +Android +会选择最接近设备屏幕密度的选项。通常,Android +倾向于缩小大型原始图像,而不是放大小型原始图像。请参阅<a href="{@docRoot}guide/practices/screens_support.html">支持多个屏幕</a>。 +</p> + </li> + + <li>返回并重复第 2 步、第 3 步和第 4 步,直到只剩下一个目录为止。在此示例中,屏幕方向是下一个判断是否匹配的限定符。因此,未指定屏幕方向的资源被淘汰: + + +<pre class="classic no-pretty-print"> +<strike>drawable-en/</strike> +drawable-en-port/ +<strike>drawable-en-notouch-12key/</strike> +</pre> +<p>剩下的目录是 {@code drawable-en-port}。</p> + </li> +</ol> + +<p>尽管对所请求的每个资源均执行此程序,但是系统仍会对某些方面做进一步优化。 +例如,系统一旦知道设备配置,即会淘汰可能永远无法匹配的备用资源。 +比如说,如果配置语言是英语(“en”),则系统绝不会将语言限定符设置为非英语的任何资源目录包含在选中的资源池中(不过,仍会将不带语言限定符的资源目录包含在该池中)。<em></em> + + +</p> + +<p>根据屏幕尺寸限定符选择资源时,如果没有更好的匹配资源,则系统将使用专为小于当前屏幕的屏幕而设计的资源(例如,如有必要,大尺寸屏幕将使用标准尺寸的屏幕资源)。 + +但是,如果唯一可用的资源大于当前屏幕,则系统<strong>不会</strong>使用这些资源,并且如果没有其他资源与设备配置匹配,应用将会崩溃(例如,如果所有布局资源均用 {@code xlarge} 限定符标记,但设备是标准尺寸的屏幕)。<em></em> + + + +</p> + +<p class="note"><strong>注:</strong>限定符的优先顺序(<a href="#table2">表 +2</a> 中)比与设备完全匹配的限定符数量更加重要。<em></em>例如,在上面的第 +4 步中,列表剩下的最后选项包括三个与设备完全匹配的限定符(方向、触摸屏类型和输入法),而 +<code>drawable-en</code> +只有一个匹配参数(语言)。但是,语言的优先顺序高于其他两个限定符,因此 +<code>drawable-port-notouch-12key</code> 被淘汰。</p> + +<p>如需了解有关如何在应用中使用资源的更多信息,请转至<a href="accessing-resources.html">访问资源</a>。</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd b/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd new file mode 100644 index 000000000000..0df99982de9a --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/resources/runtime-changes.jd @@ -0,0 +1,281 @@ +page.title=处理运行时变更 +page.tags=Activity,生命周期 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + + <h2>本文内容</h2> + <ol> + <li><a href="#RetainingAnObject">在配置变更期间保留对象</a></li> + <li><a href="#HandlingTheChange">自行处理配置变更</a> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="providing-resources.html">提供资源</a></li> + <li><a href="accessing-resources.html">访问资源</a></li> + <li><a href="http://android-developers.blogspot.com/2009/02/faster-screen-orientation-change.html">加快屏幕方向变更</a> +</li> + </ol> +</div> +</div> + +<p>有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 +发生这种变化时,Android +会重启正在运行的 +{@link android.app.Activity}(先后调用 {@link android.app.Activity#onDestroy()} 和 {@link +android.app.Activity#onCreate(Bundle) onCreate()})。重启行为旨在通过利用与新设备配置匹配的备用资源自动重新加载您的应用,来帮助它适应新配置。 + +</p> + +<p>要妥善处理重启行为,Activity 必须通过常规的<a href="{@docRoot}guide/components/activities.html#Lifecycle">Activity 生命周期</a>恢复其以前的状态,在 Activity 生命周期中,Android +会在销毁 Activity 之前调用 +{@link android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()},以便您保存有关应用状态的数据。 + +然后,您可以在 +{@link android.app.Activity#onCreate(Bundle) onCreate()} 或 {@link +android.app.Activity#onRestoreInstanceState(Bundle) onRestoreInstanceState()} 期间恢复 Activity 状态。</p> + +<p>要测试应用能否在保持应用状态完好的情况下自行重启,您应该在应用中执行各种任务时调用配置变更(例如,更改屏幕方向)。 + +您的应用应该能够在不丢失用户数据或状态的情况下随时重启,以便处理如下事件:配置发生变化,或者用户收到来电并在应用进程被销毁很久之后返回到应用。 + + +要了解如何恢复 Activity 状态,请阅读<a href="{@docRoot}guide/components/activities.html#Lifecycle">Activity 生命周期</a>。</p> + +<p>但是,您可能会遇到这种情况:重启应用并恢复大量数据不仅成本高昂,而且给用户留下糟糕的使用体验。 +在这种情况下,您有两个其他选择: +</p> + +<ol type="a"> + <li><a href="#RetainingAnObject">在配置变更期间保留对象</a> + <p>允许 Activity 在配置变更时重启,但是要将有状态对象传递给 Activity 的新实例。 +</p> + + </li> + <li><a href="#HandlingTheChange">自行处理配置变更</a> + <p>阻止系统在某些配置变更期间重启 Activity,但要在配置确实发生变化时接收回调,这样,您就能够根据需要手动更新 Activity。 + +</p> + </li> +</ol> + + +<h2 id="RetainingAnObject">在配置变更期间保留对象</h2> + +<p>如果重启 Activity 需要恢复大量数据、重新建立网络连接或执行其他密集操作,那么因配置变更而引起的完全重启可能会给用户留下应用运行缓慢的体验。 + +此外,依靠系统通过 {@link +android.app.Activity#onSaveInstanceState(Bundle) onSaveInstanceState()} 回调为您保存的 +{@link android.os.Bundle},可能无法完全恢复 Activity 状态,因为它 +并非设计用于携带大型对象(例如位图),而且其中的数据必须先序列化,再进行反序列化, +这可能会消耗大量内存并使得配置变更速度缓慢。在这种情况下,如果 Activity 因配置变更而重启,则可通过保留 +{@link +android.app.Fragment} 来减轻重新初始化 Activity 的负担。此片段可能包含对您要保留的有状态对象的引用。 +</p> + +<p>当 +Android 系统因配置变更而关闭 Activity 时,不会销毁您已标记为要保留的 Activity 的片段。您可以将此类片段添加到 Activity 以保留有状态的对象。 +</p> + +<p>要在运行时配置变更期间将有状态的对象保留在片段中,请执行以下操作:</p> + +<ol> + <li>扩展 {@link android.app.Fragment} +类并声明对有状态对象的引用。</li> + <li>在创建片段后调用 {@link android.app.Fragment#setRetainInstance(boolean)}。 + </li> + <li>将片段添加到 Activity。</li> + <li>重启 Activity 后,使用 {@link android.app.FragmentManager} +检索片段。</li> +</ol> + +<p>例如,按如下所示定义片段:</p> + +<pre> +public class RetainedFragment extends Fragment { + + // data object we want to retain + private MyDataObject data; + + // this method is only called once for this fragment + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + // retain this fragment + setRetainInstance(true); + } + + public void setData(MyDataObject data) { + this.data = data; + } + + public MyDataObject getData() { + return data; + } +} +</pre> + +<p class="caution"><strong>注意:</strong>尽管您可以存储任何对象,但是切勿传递与 +{@link android.app.Activity} 绑定的对象,例如,{@link +android.graphics.drawable.Drawable}、{@link android.widget.Adapter}、{@link android.view.View} +或其他任何与 {@link android.content.Context} 关联的对象。否则,它将泄漏原始 Activity 实例的所有视图和资源。 +(泄漏资源意味着应用将继续持有这些资源,但是无法对其进行垃圾回收,因此可能会丢失大量内存。) + +</p> + +<p>然后,使用 {@link android.app.FragmentManager} 将片段添加到 Activity。在运行时配置变更期间再次启动 Activity 时,您可以获得片段中的数据对象。 + +例如,按如下所示定义 Activity:</p> + +<pre> +public class MyActivity extends Activity { + + private RetainedFragment dataFragment; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main); + + // find the retained fragment on activity restarts + FragmentManager fm = getFragmentManager(); + dataFragment = (DataFragment) fm.findFragmentByTag(“data”); + + // create the fragment and data the first time + if (dataFragment == null) { + // add the fragment + dataFragment = new DataFragment(); + fm.beginTransaction().add(dataFragment, “data”).commit(); + // load the data from the web + dataFragment.setData(loadMyData()); + } + + // the data is available in dataFragment.getData() + ... + } + + @Override + public void onDestroy() { + super.onDestroy(); + // store the data in the fragment + dataFragment.setData(collectMyLoadedData()); + } +} +</pre> + +<p>在此示例中,{@link android.app.Activity#onCreate(Bundle) onCreate()} +添加了一个片段或恢复了对它的引用。此外,{@link android.app.Activity#onCreate(Bundle) onCreate()} +还将有状态的对象存储在片段实例内部。{@link android.app.Activity#onDestroy() onDestroy()} +对所保留的片段实例内的有状态对象进行更新。 +</p> + + + + + +<h2 id="HandlingTheChange">自行处理配置变更</h2> + +<p>如果应用在特定配置变更期间无需更新资源,并且因性能限制您需要尽量避免重启,则可声明 Activity 将自行处理配置变更,这样可以阻止系统重启 Activity。<em></em> + + +</p> + +<p class="note"><strong>注:</strong>自行处理配置变更可能导致备用资源的使用更为困难,因为系统不会为您自动应用这些资源。 + +只能在您必须避免Activity因配置变更而重启这一万般无奈的情况下,才考虑采用自行处理配置变更这种方法,而且对于大多数应用并不建议使用此方法。 +</p> + +<p>要声明由 Activity 处理配置变更,请在清单文件中编辑相应的 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code <activity>}</a> +元素,以包含 <a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a> +属性以及代表要处理的配置的值。<a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a>属性的文档中列出了该属性的可能值(最常用的值包括 {@code "orientation"} +和 +{@code "keyboardHidden"},分别用于避免因屏幕方向和可用键盘改变而导致重启)。您可以在该属性中声明多个配置值,方法是用管道 +{@code |} 字符分隔这些配置值。</p> + +<p>例如,以下清单文件代码声明的 Activity 可同时处理屏幕方向变更和键盘可用性变更: +</p> + +<pre> +<activity android:name=".MyActivity" + android:configChanges="orientation|keyboardHidden" + android:label="@string/app_name"> +</pre> + +<p>现在,当其中一个配置发生变化时,{@code MyActivity} 不会重启。相反,{@code MyActivity} +会收到对 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 的调用。向此方法传递 +{@link android.content.res.Configuration} +对象指定新设备配置。您可以通过读取 +{@link android.content.res.Configuration} +中的字段,确定新配置,然后通过更新界面中使用的资源进行适当的更改。调用此方法时,Activity 的 +{@link android.content.res.Resources} +对象会相应地进行更新,以根据新配置返回资源,这样,您就能够在系统不重启 Activity 的情况下轻松重置 +UI 的元素。</p> + +<p class="caution"><strong>注意:</strong>从 +Android +3.2(API 级别 13)开始,当设备在纵向和横向之间切换时,<strong>“屏幕尺寸”也会发生变化</strong>。因此,在开发针对 +API 级别 13 或更高版本系统的应用时,若要避免由于设备方向改变而导致运行时重启(正如 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#min">{@code minSdkVersion}</a> 和 <a href="{@docRoot}guide/topics/manifest/uses-sdk-element.html#target">{@code targetSdkVersion}</a> +属性中所声明),则除了 {@code +"orientation"} 值以外,您还必须添加 {@code "screenSize"} 值。即,您必须声明 {@code +android:configChanges="orientation|screenSize"}。但是,如果您的应用是面向 API 级别 +12 或更低版本的系统,则 Activity 始终会自行处理此配置变更(即便是在 +Android 3.2 或更高版本的设备上运行,此配置变更也不会重启 Activity)。</p> + +<p>例如,以下 {@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} 实现 +检查当前设备方向:</p> + +<pre> +@Override +public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + // Checks the orientation of the screen + if (newConfig.orientation == Configuration.ORIENTATION_LANDSCAPE) { + Toast.makeText(this, "landscape", Toast.LENGTH_SHORT).show(); + } else if (newConfig.orientation == Configuration.ORIENTATION_PORTRAIT){ + Toast.makeText(this, "portrait", Toast.LENGTH_SHORT).show(); + } +} +</pre> + +<p>{@link android.content.res.Configuration} +对象代表所有当前配置,而不仅仅是已经变更的配置。大多数时候,您并不在意配置具体发生了哪些变更,而且您可以轻松地重新分配所有资源,为您正在处理的配置提供备用资源。 + +例如,由于 {@link +android.content.res.Resources} 对象现已更新,因此您可以通过 +{@link android.widget.ImageView#setImageResource(int) +setImageResource()} +重置任何 {@link android.widget.ImageView},并且使用适合于新配置的资源(如<a href="providing-resources.html#AlternateResources">提供资源</a>中所述)。</p> + +<p>请注意,{@link +android.content.res.Configuration} 字段中的值是与 +{@link android.content.res.Configuration} 类中的特定常量匹配的整型数。有关要对每个字段使用哪些常量的文档,请参阅 +{@link +android.content.res.Configuration} 参考文档中的相应字段。</p> + +<p class="note"><strong>请谨记:</strong>在声明由 Activity 处理配置变更时,您有责任重置要为其提供备用资源的所有元素。 +如果您声明由 Activity 处理方向变更,而且有些图像应该在横向和纵向之间切换,则必须在 +{@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()} +期间将每个资源重新分配给每个元素。</p> + +<p>如果无需基于这些配置变更更新应用,则可不用实现 +{@link +android.app.Activity#onConfigurationChanged(Configuration) onConfigurationChanged()}。<em></em>在这种情况下,仍将使用在配置变更之前用到的所有资源,只是您无需重启 Activity。 + +但是,应用应该始终能够在保持之前状态完好的情况下关闭和重启,因此您不得试图通过此方法来逃避在正常 Activity 生命周期期间保持您的应用状态。 + +这不仅仅是因为还存在其他一些无法禁止重启应用的配置变更,还因为有些事件必须由您处理,例如用户离开应用,而在用户返回应用之前该应用已被销毁。 + + +</p> + +<p>如需了解有关您可以在 Activity 中处理哪些配置变更的详细信息,请参阅 <a href="{@docRoot}guide/topics/manifest/activity-element.html#config">{@code +android:configChanges}</a> 文档和 {@link android.content.res.Configuration} +类。</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd new file mode 100644 index 000000000000..0f1a543d1f66 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/controls.jd @@ -0,0 +1,90 @@ +page.title=输入控件 +parent.title=用户界面 +parent.link=index.html +@jd:body + +<div class="figure" style="margin:0"> + <img src="{@docRoot}images/ui/ui-controls.png" alt="" style="margin:0" /> +</div> + +<p>输入控件是您的应用用户界面中的交互式组件。Android 提供了多种可在 UI 中使用的控件,如按钮、文本字段、定位栏、复选框、缩放按钮、切换按钮等。 + +</p> + +<p>向 UI 中添加输入控件与向 <a href="{@docRoot}guide/topics/ui/declaring-layout.html">XML 布局</a>中添加 XML 元素一样简单。例如,以下是一个包含文本字段和按钮的布局: +</p> + +<pre style="clear:right"> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="horizontal"> + <EditText android:id="@+id/edit_message" + android:layout_weight="1" + android:layout_width="0dp" + android:layout_height="wrap_content" + android:hint="@string/edit_message" /> + <Button android:id="@+id/button_send" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/button_send" + android:onClick="sendMessage" /> +</LinearLayout> +</pre> + +<p>每个输入控件都支持一组特定的输入事件,以便您处理用户输入文本或触摸按钮等事件。 +</p> + + +<h2 id="CommonControls">通用控件</h2> +<p>以下列出了您可以在应用中使用的一些通用控件。点击链接可了解有关各控件用法的详情。 +</p> + +<p class="note"><strong>注:</strong>除了此处列出的控件外,Android 还提供了几种其他控件。 +浏览 {@link android.widget} 软件包可发现更多控件。如果您的应用需要特定类型的输入控件,则可以构建您自己的<a href="{@docRoot}guide/topics/ui/custom-components.html">自定义组件</a>。 +</p> + +<table> + <tr> + <th scope="col">控件类型</th> + <th scope="col">描述</th> + <th scope="col">相关类</th> + </tr> + <tr> + <td><a href="controls/button.html">按钮</a></td> + <td>可由用户按压或点击来执行操作的按钮。</td> + <td>{@link android.widget.Button Button} </td> + </tr> + <tr> + <td><a href="controls/text.html">文本字段</a></td> + <td>一种可编辑的文本字段。您可以使用 <code>AutoCompleteTextView</code> 小工具创建提供自动完成建议的文本输入小工具</td> + <td>{@link android.widget.EditText EditText}、{@link android.widget.AutoCompleteTextView}</td> + </tr> + <tr> + <td><a href="controls/checkbox.html">复选框</a></td> + <td>可由用户切换的启用/禁用开关。您应该在向用户呈现一组不互斥的可选选项时使用复选框。</td> + <td>{@link android.widget.CheckBox CheckBox} </td> + </tr> + <tr> + <td><a href="controls/radiobutton.html">单选按钮</a></td> + <td>与复选框类似,不同的是只能选择组中的一个选项。</td> + <td>{@link android.widget.RadioGroup RadioGroup} + <br>{@link android.widget.RadioButton RadioButton} </td> + </tr> + <tr> + <td><a href="controls/togglebutton.html" style="white-space:nowrap">切换按钮</a></td> + <td>一种具有指示灯的开/关按钮。</td> + <td>{@link android.widget.ToggleButton ToggleButton} </td> + </tr> + <tr> + <td><a href="controls/spinner.html">微调框</a></td> + <td>一种允许用户从值集中选择一个值的下拉列表。</td> + <td>{@link android.widget.Spinner Spinner} </td> + </tr> + <tr> + <td><a href="controls/pickers.html">选取器</a></td> + <td>一种供用户通过使用向上/向下按钮或轻扫手势选择值集中单个值的对话框。使用 <code>DatePicker</code> 小工具输入日期(月、日、年)值,或使用 <code>TimePicker</code> 小工具输入时间(小时、分钟、上午/下午)值,系统将根据用户的区域设置自动设置所输入内容的格式。</td> + <td>{@link android.widget.DatePicker}、{@link android.widget.TimePicker}</td> + </tr> +</table> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd new file mode 100644 index 000000000000..38e534eb091a --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/declaring-layout.jd @@ -0,0 +1,492 @@ +page.title=布局 +page.tags=view,viewgroup +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文内容</h2> +<ol> + <li><a href="#write">编写 XML</a></li> + <li><a href="#load">加载 XML 资源</a></li> + <li><a href="#attributes">属性</a> + <ol> + <li><a href="#id">ID</a></li> + <li><a href="#layout-params">布局参数</a></li> + </ol> + </li> + <li><a href="#Position">布局位置</a></li> + <li><a href="#SizePaddingMargins">尺寸、内边距和外边距</a></li> + <li><a href="#CommonLayouts">常见布局</a></li> + <li><a href="#AdapterViews">使用适配器构建布局</a> + <ol> + <li><a href="#FillingTheLayout">使用数据填充适配器视图</a></li> + <li><a href="#HandlingUserSelections">处理点击事件</a></li> + </ol> + </li> +</ol> + + <h2>关键类</h2> + <ol> + <li>{@link android.view.View}</li> + <li>{@link android.view.ViewGroup}</li> + <li>{@link android.view.ViewGroup.LayoutParams}</li> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="{@docRoot}training/basics/firstapp/building-ui.html">构建简单的用户界面</a> +</li> </div> +</div> + +<p>布局定义用户界面的视觉结构,如<a href="{@docRoot}guide/components/activities.html">Activity</a>或<a href="{@docRoot}guide/topics/appwidgets/index.html">应用小工具</a>的 +UI。您可以通过两种方式声明布局:</p> +<ul> +<li><strong>在 XML 中声明 UI 元素</strong>。Android 提供了对应于 View 类及其子类的简明 XML +词汇,如用于小工具和布局的词汇;</li> +<li><strong>运行时实例化布局元素</strong>。您的应用可以通过编程创建 +View 对象和 ViewGroup 对象(并操纵其属性)。 </li> +</ul> + +<p>Android 框架让您可以灵活地使用以下一种或两种方法来声明和管理应用的 UI。例如,您可以在 XML 中声明应用的默认布局,包括将出现在布局中的屏幕元素及其属性。然后,您可以在应用中添加可在运行时修改屏幕对象(包括那些已在 XML 中声明的对象)状态的代码。 </p> + +<div class="sidebox-wrapper"> +<div class="sidebox"> + <ul> + <li><a href="{@docRoot}tools/sdk/eclipse-adt.html">ADT + Plugin for Eclipse</a> 提供了 XML 的布局预览— + 在 XML 文件处于打开状态时选择 <strong>Layout</strong> 选项卡</li> + <li>您还应尝试使用<a href="{@docRoot}tools/debugging/debugging-ui.html#hierarchyViewer">层次结构查看器</a>工具来调试布局—当您在模拟器或设备上进行调试时,它会显示布局属性值、绘制具有内边距/外边距指示符的线框以及完整渲染视图。 + + + +</li> + <li>您可以利用 <a href="{@docRoot}tools/debugging/debugging-ui.html#layoutopt">layoutopt</a> 工具快速分析布局和层次结构中是否存在低效环节或其他问题。 +</li> +</div> +</div> + +<p>在 XML 中声明 UI 的优点在于,您可以更好地将应用的外观与控制应用行为的代码隔离。您的 UI 描述位于应用代码外部,这意味着您在修改或调整描述时无需修改您的源代码并重新编译。例如,您可以创建适用于不同屏幕方向、不同设备屏幕尺寸和不同语言的 XML 布局。此外,在 XML 中声明布局还能更轻松地显示 UI 的结构,从而简化问题调试过程。因此,本文将侧重于示范如何在 XML 中声明布局。如果您对在运行时实例化 View +对象感兴趣,请参阅 {@link android.view.ViewGroup} 类和 +{@link android.view.View} 类的参考资料。</p> + +<p>一般而言,用于声明 UI 元素的 XML 词汇严格遵循类和方法的结构和命名方式,其中元素名称对应于类名称,属性名称对应于方法。实际上,这种对应关系往往非常直接,让您可以猜到对应于类方法的 XML 属性,或对应于给定 XML 元素的类。但请注意,并非所有词汇都完全相同。在某些情况下,在命名上略有差异。例如,EditText 元素具有的 +<code>text</code> 属性对应的类方法是 <code>EditText.setText()</code>。 + </p> + +<p class="note"><strong>提示</strong>:如需了解有关不同布局类型的更多信息,请参阅<a href="{@docRoot}guide/topics/ui/layout-objects.html">常见布局对象</a>。 + +<a href="{@docRoot}resources/tutorials/views/index.html">Hello 视图</a>教程指南中也提供了一系列有关构建各种布局的教程。</p> + +<h2 id="write">编写 XML</h2> + +<p>您可以利用 Android 的 XML 词汇,按照在 HTML 中创建包含一系列嵌套元素的网页的相同方式快速设计 UI 布局及其包含的屏幕元素。 </p> + +<p>每个布局文件都必须只包含一个根元素,并且该元素必须是视图对象或 ViewGroup 对象。定义根元素之后,即可再以子元素的形式添加其他布局对象或小工具,从而逐步构建定义布局的视图层次结构。例如,以下这个 XML 布局使用垂直 {@link android.widget.LinearLayout} +来储存一个 {@link android.widget.TextView} 和一个 {@link android.widget.Button}:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" > + <TextView android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello, I am a TextView" /> + <Button android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="Hello, I am a Button" /> +</LinearLayout> +</pre> + +<p>在 XML 中声明布局后,请在您的 Android 项目 <code>res/layout/</code> 目录中以 <code>.xml</code> 扩展名保存文件,以便其能够正确编译。 + </p> + +<p><a href="{@docRoot}guide/topics/resources/layout-resource.html">布局资源</a>文档中提供了有关布局 XML 文件语法的更多信息。</p> + +<h2 id="load">加载 XML 资源</h2> + +<p>当您编译应用时,每个 XML 布局文件都会编译到一个 +{@link android.view.View} 资源中。您应该在 +{@link android.app.Activity#onCreate(android.os.Bundle) Activity.onCreate()} +回调实现中从您的应用代码加载布局资源。请通过调用 <code>{@link android.app.Activity#setContentView(int) setContentView()}</code>,以 +<code>R.layout.<em>layout_file_name</em></code> +形式向其传递对布局资源的引用来执行此操作。例如,如果您的 +XML 布局保存为 +<code>main_layout.xml</code>,则需要像下面这样为您的 Activity 加载该布局:</p> +<pre> +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.main_layout); +} +</pre> + +<p>启动您的 Activity 时,Android +框架会调用 Activity 中的 +<code>onCreate()</code> +回调方法(请参阅<a href="{@docRoot}guide/components/activities.html#Lifecycle">Activity</a>文档中有关生命周期的阐述)。</p> + + +<h2 id="attributes">属性</h2> + +<p>每个视图对象和 ViewGroup 对象都支持各自的各类 XML +属性。某些属性是视图对象的专用属性(例如,TextView 支持 <code>textSize</code> +属性),但这些属性也会被任何可以扩展此类的视图对象继承。某些属性通用于所有 View +对象,因为它们继承自根 View 类(如 <code>id</code> +属性)。此外,其他属性被视为“布局参数”,即描述 View +对象特定布局方向的属性,如该对象的父 ViewGroup +对象所定义的属性。</p> + +<h3 id="id">ID</h3> + +<p>任何视图对象都可能具有关联的整型 ID,此 ID 用于在结构树中对 View +对象进行唯一标识。编译应用后,此 ID 将作为整型数引用,但在布局 XML +文件中,通常会在 <code>id</code> 属性中为该 ID 赋予字符串值。这是所有 +View 对象共用的 XML 属性(由 {@link android.view.View} +类定义),您会经常用到它。XML 标记内部的 ID +语法是:</p> +<pre>android:id="@+id/my_button"</pre> + +<p>字符串开头处的 @ 符号指示 XML 解析程序应该解析并展开 +ID 字符串的其余部分,并将其标识为 ID 资源。加号 (+) +表示这是一个新的资源名称,必须创建该名称并将其添加到我们的资源(在 <code>R.java</code> 文件中)内。Android 框架还提供了许多其他 ID +资源。引用 Android 资源 ID 时,不需要加号,但必须添加 +<code>android</code> 软件包命名空间,如下所示:</p> +<pre>android:id="@android:id/empty"</pre> +<p>添加 <code>android</code> 软件包命名空间之后,现在,我们将从 <code>android.R</code> +资源类而非本地资源类引用 ID。</p> + +<p>要想创建视图并从应用中引用它们,常见的模式是:</p> +<ol> + <li>在布局文件中定义一个视图/小工具,并为其分配一个唯一的 ID: +<pre> +<Button android:id="@+id/my_button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="@string/my_button_text"/> +</pre> + </li> + <li>然后创建一个 view +对象实例,并从布局中捕获它(通常使用 <code>{@link android.app.Activity#onCreate(Bundle) onCreate()}</code> 方法): +<pre> +Button myButton = (Button) findViewById(R.id.my_button); +</pre> + </li> +</ol> +<p>创建 {@link android.widget.RelativeLayout} +时,为 view 对象定义 ID 非常重要。在相对布局中,同级视图可以定义其相对于其他同级视图的布局,同级视图通过唯一的 ID +进行引用。</p> +<p>ID +不需要在整个结构树中具有唯一性,但在您要搜索的结构树部分应具有唯一性(要搜索的部分往往是整个结构树,因此最好尽可能具有全局唯一性)。 +</p> + + +<h3 id="layout-params">布局参数</h3> + +<p>名为 <code>layout_<em>something</em></code> 的 XML +布局属性可为视图定义与其所在的 ViewGroup 相适的布局参数。</p> + +<p>每个 ViewGroup 类都会实现一个扩展 {@link +android.view.ViewGroup.LayoutParams} 的嵌套类。此子类包含的属性类型会根据需要为视图组的每个子视图定义尺寸和位置。 + +正如您在图 1 +中所见,父视图组为每个子视图(包括子视图组)定义布局参数。</p> + +<img src="{@docRoot}images/layoutparams.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>以可视化方式表示的视图层次结构,其中包含与每个视图关联的布局参数。 +</p> + +<p>请注意,每个 LayoutParams +子类都有自己的值设置语法。每个子元素都必须定义适合其父元素的 +LayoutParams,但父元素也可为其子元素定义不同的 LayoutParams。 </p> + +<p>所有视图组都包括宽度和高度(<code>layout_width</code> 和 +<code>layout_height</code>),并且每个视图都必须定义它们。许多 +LayoutParams 还包括可选的外边距和边框。 <p> + +<p>您可以指定具有确切尺寸的宽度和高度,但您多半不想经常这样做。 +在更多的情况下,您会使用以下常量之一来设置宽度或高度: + </p> + +<ul> + <li><var>wrap_content</var> 指示您的视图将其大小调整为内容所需的尺寸 +</li> + <li><var>match_parent</var> (在 API 级别 8 之前名为 <var>fill_parent</var> )指示您的视图尽可能采用其父视图组所允许的最大尺寸 +</li> +</ul> + +<p>一般而言,建议不要使用绝对单位(如像素)来指定布局宽度和高度, +而是使用相对测量单位,如密度无关像素单位 +(<var>dp</var>)、 <var>wrap_content</var> 或 +<var>match_parent</var>,这种方法更好,因为它有助于确保您的应用在各类尺寸的设备屏幕上正确显示。<a href="{@docRoot}guide/topics/resources/available-resources.html#dimension">可用资源</a>文档中定义了可接受的测量单位类型。 + + + +</p> + + +<h2 id="Position">布局位置</h2> + <p> + 视图的几何形状就是矩形的几何形状。视图具有一个位置(以一对<em>水平向左</em>和<em>垂直向上</em>坐标表示)和两个尺寸(以宽度和高度表示)。 + +位置和尺寸的单位是像素。 + + </p> + + <p> + 可以通过调用方法 + {@link android.view.View#getLeft()} 和方法 {@link android.view.View#getTop()} 来检索视图的位置。前者会返回表示视图的矩形的水平向左(或称 X 轴) + 坐标。后者会返回表示视图的矩形的垂直向上(或称 Y 轴)坐标。 +这些方法都会返回视图相对于其父项的位置。 +例如,如果 <code>getLeft()</code> 返回 20,则意味着视图位于其直接父项左边缘向右 20 个像素处。 + + + </p> + + <p> + 此外,系统还提供了几种便捷方法来避免不必要的计算,即 {@link android.view.View#getRight()} 和 {@link android.view.View#getBottom()}。 + + 这些方法会返回表示视图的矩形的右边缘和下边缘的坐标。 +例如,调用 {@link android.view.View#getRight()} + 类似于进行以下计算:<code>getLeft() + getWidth()</code>。 + </p> + + +<h2 id="SizePaddingMargins">尺寸、内边距和外边距</h2> + <p> + 视图的尺寸通过宽度和高度表示。视图实际上具有两对宽度和高度值。 + + </p> + + <p> + 第一对称为<em>测量宽度</em>和<em>测量高度</em>。 +这些尺寸定义视图想要在其父项内具有的大小。 + + 这些测量尺寸可以通过调用 {@link android.view.View#getMeasuredWidth()} + 和 {@link android.view.View#getMeasuredHeight()} 来获得。 + </p> + + <p> + 第二对简称为<em>宽度</em>和<em>高度</em>,有时称为<em>绘制宽度</em>和<em>绘制高度</em>。 +这些尺寸定义视图在绘制时和布局后在屏幕上的实际尺寸。 + +这些值可以(但不必)与测量宽度和测量高度不同。 +宽度和高度可以通过调用 + {@link android.view.View#getWidth()} 和 {@link android.view.View#getHeight()} 来获得。 + </p> + + <p> + 要想测量其尺寸,视图需要将其内边距考虑在内。内边距以视图左侧、顶部、右侧和底部各部分的像素数表示。 + + 内边距可用于以特定数量的 + 像素弥补视图的内容。例如,左侧内边距为 2,会将视图的内容从左边缘向右推 + 2 个像素。可以使用 + {@link android.view.View#setPadding(int, int, int, int)} 方法设置内边距,并通过调用 + {@link android.view.View#getPaddingLeft()}、{@link android.view.View#getPaddingTop()}、{@link android.view.View#getPaddingRight()} 和 {@link android.view.View#getPaddingBottom()} 进行查询。 + + </p> + + <p> + 尽管视图可以定义内边距,但它并不支持外边距。 +不过,视图组可以提供此类支持。如需了解更多信息,请参阅 + {@link android.view.ViewGroup} 和 + {@link android.view.ViewGroup.MarginLayoutParams}。 + </p> + + <p>如需了解有关尺寸的详细信息,请参阅 + <a href="{@docRoot}guide/topics/resources/more-resources.html#Dimension">尺寸值</a>。 + </p> + + + + + + +<style type="text/css"> +div.layout { + float:left; + width:200px; + margin:0 0 20px 20px; +} +div.layout.first { + margin-left:0; + clear:left; +} +</style> + + + + +<h2 id="CommonLayouts">常见布局</h2> + +<p>{@link android.view.ViewGroup} +类的每个子类都提供了一种独特的方式来显示您在其中嵌套的视图。以下是 +Android 平台中内置的一些较为常见的布局类型。</p> + +<p class="note"><strong>注:</strong>尽管您可以通过将一个或多个布局嵌套在另一个布局内来实现您的 +UI +设计,但应该使您的布局层次结构尽可能简略。布局的嵌套布局越少,绘制速度越快(扁平的视图层次结构优于深层的视图层次结构)。 +</p> + +<!-- +<h2 id="framelayout">FrameLayout</h2> +<p>{@link android.widget.FrameLayout FrameLayout} is the simplest type of layout +object. It's basically a blank space on your screen that you can +later fill with a single object — for example, a picture that you'll swap in and out. +All child elements of the FrameLayout are pinned to the top left corner of the screen; you cannot +specify a different location for a child view. Subsequent child views will simply be drawn over +previous ones, +partially or totally obscuring them (unless the newer object is transparent). +</p> +--> + + +<div class="layout first"> + <h4><a href="layout/linear.html">线性布局</a></h4> + <a href="layout/linear.html"><img src="{@docRoot}images/ui/linearlayout-small.png" alt="" /></a> + <p>一种使用单个水平行或垂直行来组织子项的布局。它会在窗口长度超出屏幕长度时创建一个滚动条。 +</p> +</div> + +<div class="layout"> + <h4><a href="layout/relative.html">相对布局</a></h4> + <a href="layout/relative.html"><img src="{@docRoot}images/ui/relativelayout-small.png" alt="" /></a> + <p>让您能够指定子对象彼此之间的相对位置(子对象 A +在子对象 B 左侧)或子对象与父对象的相对位置(与父对象顶部对齐)。</p> +</div> + +<div class="layout"> + <h4><a href="{@docRoot}guide/webapps/webview.html">Web 视图</a></h4> + <a href="{@docRoot}guide/webapps/webview.html"><img src="{@docRoot}images/ui/webview-small.png" alt="" /></a> + <p>显示网页。</p> +</div> + + + + +<h2 id="AdapterViews" style="clear:left">使用适配器构建布局</h2> + +<p>如果布局的内容是属于动态或未预先确定的内容,您可以使用这样一种布局:在运行时通过子类 +{@link android.widget.AdapterView} 用视图填充布局。{@link android.widget.AdapterView} +类的子类使用 {@link android.widget.Adapter} +将数据与其布局绑定。{@link android.widget.Adapter} +充当数据源与 {@link android.widget.AdapterView} +布局之间的中间人—{@link android.widget.Adapter}(从数组或数据库查询等来源)检索数据,并将每个条目转换为可以添加到 {@link android.widget.AdapterView} +布局中的视图。</p> + +<p>适配器支持的常见布局包括:</p> + +<div class="layout first"> + <h4><a href="layout/listview.html">列表视图</a></h4> + <a href="layout/listview.html"><img src="{@docRoot}images/ui/listview-small.png" alt="" /></a> + <p>显示滚动的单列列表。</p> +</div> + +<div class="layout"> + <h4><a href="layout/gridview.html">网格视图</a></h4> + <a href="layout/gridview.html"><img src="{@docRoot}images/ui/gridview-small.png" alt="" /></a> + <p>显示滚动的行列网格。</p> +</div> + + + +<h3 id="FillingTheLayout" style="clear:left">使用数据填充适配器视图</h3> + +<p>您可以通过将 {@link android.widget.AdapterView} 实例与 {@link android.widget.Adapter} 绑定来填充 {@link android.widget.AdapterView}(如 {@link android.widget.ListView} 或 +{@link android.widget.GridView}),此操作会从外部来源检索数据,并创建表示每个数据条目的 +{@link +android.view.View}。</p> + +<p>Android 提供了几个 {@link android.widget.Adapter} 子类,用于检索不同种类的数据和构建 +{@link android.widget.AdapterView} 的视图。两种最常见的适配器是: +</p> + +<dl> + <dt>{@link android.widget.ArrayAdapter}</dt> + <dd>请在数据源为数组时使用此适配器。默认情况下,{@link +android.widget.ArrayAdapter} 会通过在每个项目上调用 {@link +java.lang.Object#toString()} 并将内容放入 {@link +android.widget.TextView} 来为每个数组项创建视图。 + <p>例如,如果您具有想要在 {@link +android.widget.ListView} 中显示的字符串数组,请使用构造函数初始化一个新的 +{@link android.widget.ArrayAdapter},为每个字符串和字符串数组指定布局:</p> +<pre> +ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, + android.R.layout.simple_list_item_1, myStringArray); +</pre> +<p>此构造函数的参数是:</p> +<ul> + <li>您的应用 {@link android.content.Context}</li> + <li>包含数组中每个字符串的 {@link android.widget.TextView} 的布局</li> + <li>字符串数组</li> +</ul> +<p>然后,只需在您的 {@link android.widget.ListView} 上调用 +{@link android.widget.ListView#setAdapter setAdapter()}:</p> +<pre> +ListView listView = (ListView) findViewById(R.id.listview); +listView.setAdapter(adapter); +</pre> + + <p>要想自定义每个项的外观,您可以重写数组中各个对象的 {@link +java.lang.Object#toString()} 方法。或者,要想为 +{@link android.widget.TextView} 之外的每个项创建视图(例如,如果您想为每个数组项创建一个 +{@link android.widget.ImageView}),请扩展 {@link +android.widget.ArrayAdapter} 类并重写 {@link android.widget.ArrayAdapter#getView +getView()} 以返回您想要为每个项获取的视图类型。</p> + +</dd> + + <dt>{@link android.widget.SimpleCursorAdapter}</dt> + <dd>请在数据来自 {@link android.database.Cursor} 时使用此适配器。使用 +{@link android.widget.SimpleCursorAdapter} 时,您必须指定要为 {@link android.database.Cursor} +中的每个行使用的布局,以及应该在哪些布局视图中插入 {@link android.database.Cursor} +中的哪些列。例如,如果您想创建人员姓名和电话号码列表,则可以执行一个返回 +{@link +android.database.Cursor}(包含对应每个人的行,以及对应姓名和号码的列)的查询。 +然后,您可以创建一个字符串数组,指定您想要在每个结果的布局中包含 {@link +android.database.Cursor} +中的哪些列,并创建一个整型数组,指定应该将每个列放入的对应视图:</p> +<pre> +String[] fromColumns = {ContactsContract.Data.DISPLAY_NAME, + ContactsContract.CommonDataKinds.Phone.NUMBER}; +int[] toViews = {R.id.display_name, R.id.phone_number}; +</pre> +<p>当您实例化 {@link android.widget.SimpleCursorAdapter} +时,请传递要用于每个结果的布局、包含结果的 {@link android.database.Cursor} 以及以下两个数组:</p> +<pre> +SimpleCursorAdapter adapter = new SimpleCursorAdapter(this, + R.layout.person_name_and_number, cursor, fromColumns, toViews, 0); +ListView listView = getListView(); +listView.setAdapter(adapter); +</pre> +<p>然后,{@link android.widget.SimpleCursorAdapter} 会使用提供的布局,将每个 +{@code +fromColumns} 项插入对应的 {@code toViews} 视图,为 {@link android.database.Cursor} 中的每个行创建一个视图。</p>.</dd> +</dl> + + +<p>如果您在应用的生命周期中更改了适配器读取的底层数据,则应调用 +{@link android.widget.ArrayAdapter#notifyDataSetChanged()}。此操作会通知附加的视图,数据发生了变化,它应该自行刷新。 +</p> + + + +<h3 id="HandlingUserSelections">处理点击事件</h3> + +<p>您可以通过实现 {@link android.widget.AdapterView.OnItemClickListener} +界面来响应 {@link android.widget.AdapterView} 中每一项上的点击事件。例如:</p> + +<pre> +// Create a message handling object as an anonymous class. +private OnItemClickListener mMessageClickedHandler = new OnItemClickListener() { + public void onItemClick(AdapterView parent, View v, int position, long id) { + // Do something in response to the click + } +}; + +listView.setOnItemClickListener(mMessageClickedHandler); +</pre> + + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd new file mode 100644 index 000000000000..84922b40e445 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/dialogs.jd @@ -0,0 +1,798 @@ +page.title=对话框 +page.tags=提醒对话框,对话框片段 + +@jd:body + + + +<div id="qv-wrapper"> + <div id="qv"> + <h2>本文内容</h2> +<ol> + <li><a href="#DialogFragment">创建对话框片段</a></li> + <li><a href="#AlertDialog">构建提醒对话框</a> + <ol> + <li><a href="#AddingButtons">添加按钮</a></li> + <li><a href="#AddingAList">添加列表</a></li> + <li><a href="#CustomLayout">创建自定义布局</a></li> + </ol> + </li> + <li><a href="#PassingEvents">将事件传递回对话框的宿主</a></li> + <li><a href="#ShowingADialog">显示对话框</a></li> + <li><a href="#FullscreenDialog">全屏显示对话框或将其显示为嵌入式片段</a> + <ol> + <li><a href="#ActivityAsDialog">将 Activity 显示为大屏幕上的对话框</a></li> + </ol> + </li> + <li><a href="#DismissingADialog">清除对话框</a></li> +</ol> + + <h2>关键类</h2> + <ol> + <li>{@link android.app.DialogFragment}</li> + <li>{@link android.app.AlertDialog}</li> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="{@docRoot}design/building-blocks/dialogs.html">对话框设计指南</a></li> + <li><a href="{@docRoot}guide/topics/ui/controls/pickers.html">选取器</a>(日期/时间对话框)</li> + </ol> + </div> +</div> + +<p>对话框是提示用户作出决定或输入额外信息的小窗口。 +对话框不会填充屏幕,通常用于需要用户采取行动才能继续执行的模式事件。 +</p> + +<div class="note design"> +<p><strong>对话框设计</strong></p> + <p>如需了解有关如何设计对话框的信息(包括语言建议),请阅读<a href="{@docRoot}design/building-blocks/dialogs.html">对话框</a>设计指南。 +</p> +</div> + +<img src="{@docRoot}images/ui/dialogs.png" /> + +<p>{@link android.app.Dialog} +类是对话框的基类,但您应该避免直接实例化 +{@link android.app.Dialog},而是使用下列子类之一:</p> +<dl> + <dt>{@link android.app.AlertDialog}</dt> + <dd>此对话框可显示标题、最多三个按钮、可选择项列表或自定义布局。 +</dd> + <dt>{@link android.app.DatePickerDialog} 或 {@link android.app.TimePickerDialog}</dt> + <dd>此对话框带有允许用户选择日期或时间的预定义 UI。</dd> +</dl> + +<div class="sidebox"> +<h2>避免使用 ProgressDialog</h2> +<p>Android 包括另一种名为 +{@link android.app.ProgressDialog} 的对话框类,可显示具有进度条的对话框。不过,如需指示加载进度或不确定的进度,则应改为遵循<a href="{@docRoot}design/building-blocks/progress.html">进度和 Activity</a>的设计指南,并在您的布局中使用 +{@link android.widget.ProgressBar}。 + +</p> +</div> + +<p>这些类定义您的对话框的样式和结构,但您应该将 +{@link android.support.v4.app.DialogFragment} +用作对话框的容器。{@link android.support.v4.app.DialogFragment} +类提供您创建对话框和管理其外观所需的所有控件,而不是调用 {@link android.app.Dialog} +对象上的方法。</p> + +<p>使用 {@link android.support.v4.app.DialogFragment} +管理对话框可确保它能正确处理生命周期事件,如用户按“返回”<em></em>按钮或旋转屏幕时。 +此外,{@link +android.support.v4.app.DialogFragment} 类还允许您将对话框的 UI 作为嵌入式组件在较大 UI 中重复使用,就像传统 {@link +android.support.v4.app.Fragment} +一样(例如,当您想让对话框 UI +在大屏幕和小屏幕上具有不同外观时)。</p> + +<p>本指南的后文将描述如何将 {@link +android.support.v4.app.DialogFragment} 与 {@link android.app.AlertDialog} +对象结合使用。如果您想创建一个日期或时间选取器,应改为阅读<a href="{@docRoot}guide/topics/ui/controls/pickers.html">选取器</a>指南。 +</p> + +<p class="note"><strong>注:</strong>由于 +{@link android.app.DialogFragment} 类最初是通过 +Android 3.0(API 11 级)添加的,因此本文描述的是如何使用<a href="{@docRoot}tools/support-library/index.html">支持库</a>附带的 {@link +android.support.v4.app.DialogFragment} 类。通过将该库添加到您的应用,您可以在运行 +Android 1.6 或更高版本的设备上使用 {@link android.support.v4.app.DialogFragment} 以及各种其他 +API。如果您的应用支持的最低版本是 +API 11 级或更高版本,则可使用 {@link +android.app.DialogFragment} 的框架版本,但请注意,本文中的链接适用于支持库 +API。使用支持库时,请确保您导入的是 +<code>android.support.v4.app.DialogFragment</code> +类,而“绝对不”<em></em>是 <code>android.app.DialogFragment</code>。</p> + + +<h2 id="DialogFragment">创建对话框片段</h2> + +<p>您可以完成各种对话框设计—包括自定义布局以及<a href="{@docRoot}design/building-blocks/dialogs.html">对话框</a>设计指南中描述的布局—通过扩展 +{@link android.support.v4.app.DialogFragment} +并在 +{@link android.support.v4.app.DialogFragment#onCreateDialog +onCreateDialog()} +回调方法中创建 {@link android.app.AlertDialog}。</p> + +<p>例如,以下是一个在 +{@link android.support.v4.app.DialogFragment} 内管理的基础 {@link android.app.AlertDialog}:</p> + +<pre> +public class FireMissilesDialogFragment extends DialogFragment { + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Use the Builder class for convenient dialog construction + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.dialog_fire_missiles) + .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // FIRE ZE MISSILES! + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); + // Create the AlertDialog object and return it + return builder.create(); + } +} +</pre> + +<div class="figure" style="width:290px;margin:0 0 0 20px"> +<img src="{@docRoot}images/ui/dialog_buttons.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>一个包含消息和两个操作按钮的对话框。 +</p> +</div> + +<p>现在,当您创建此类的实例并调用该对象上的 {@link +android.support.v4.app.DialogFragment#show show()} 时,对话框将如图 +1 所示。</p> + +<p>下文将详细描述如何使用 {@link android.app.AlertDialog.Builder} +API 创建对话框。</p> + +<p>根据对话框的复杂程度,您可以在 +{@link android.support.v4.app.DialogFragment} +中实现各种其他回调方法,包括所有基础 <a href="{@docRoot}guide/components/fragments.html#Lifecycle">片段生命周期方法</a>。 + + + + + +<h2 id="AlertDialog">构建提醒对话框</h2> + + +<p>您可以通过 {@link android.app.AlertDialog} +类构建各种对话框设计,并且该类通常是您需要的唯一对话框类。如图 2 +所示,提醒对话框有三个区域:</p> + +<div class="figure" style="width:311px;margin-top:0"> +<img src="{@docRoot}images/ui/dialogs_regions.png" alt="" style="margin-bottom:0" /> +<p class="img-caption"><strong>图 2. </strong>对话框的布局。</p> +</div> + +<ol> +<li><b>标题</b> + <p>这是可选项,只应在内容区域被详细消息、列表或自定义布局占据时使用。 +如需陈述的是一条简单消息或问题(如图 1 中的对话框),则不需要标题。 +</li> +<li><b>内容区域</b> + <p>它可以显示消息、列表或其他自定义布局。</p></li> +<li><b>操作按钮</b> + <p>对话框中的操作按钮不应超过三个。</p></li> +</ol> + +<p>{@link android.app.AlertDialog.Builder} +类提供的 API 允许您创建具有这几种内容(包括自定义布局)的 +{@link android.app.AlertDialog}。</p> + +<p>要想构建 {@link android.app.AlertDialog},请执行以下操作:</p> + +<pre> +<b>// 1. Instantiate an {@link android.app.AlertDialog.Builder} with its constructor</b> +AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + +<b>// 2. Chain together various setter methods to set the dialog characteristics</b> +builder.setMessage(R.string.dialog_message) + .setTitle(R.string.dialog_title); + +<b>// 3. Get the {@link android.app.AlertDialog} from {@link android.app.AlertDialog.Builder#create()}</b> +AlertDialog dialog = builder.create(); +</pre> + +<p>以下主题介绍如何使用 +{@link android.app.AlertDialog.Builder} 类定义各种对话框属性。</p> + + + + +<h3 id="AddingButtons">添加按钮</h3> + +<p>要想添加如图 2 +所示的操作按钮,请调用 {@link android.app.AlertDialog.Builder#setPositiveButton setPositiveButton()} 和 +{@link android.app.AlertDialog.Builder#setNegativeButton setNegativeButton()} 方法:</p> + +<pre style="clear:right"> +AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); +// Add the buttons +builder.setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User clicked OK button + } + }); +builder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // User cancelled the dialog + } + }); +// Set other dialog properties +... + +// Create the AlertDialog +AlertDialog dialog = builder.create(); +</pre> + +<p><code>set...Button()</code> +方法需要一个按钮标题(由<a href="{@docRoot}guide/topics/resources/string-resource.html">字符串资源</a>提供)和一个 +{@link android.content.DialogInterface.OnClickListener},后者用于定义用户按下该按钮时执行的操作。 +</p> + +<p>您可以添加三种不同的操作按钮:</p> +<dl> + <dt>肯定</dt> + <dd>您应该使用此按钮来接受并继续执行操作(“确定”操作)。</dd> + <dt>否定</dt> + <dd>您应该使用此按钮来取消操作。</dd> + <dt>中性</dt> + <dd>您应该在用户可能不想继续执行操作,但也不一定想要取消操作时使用此按钮。 +它出现在肯定按钮和否定按钮之间。 +例如,实际操作可能是“稍后提醒我”。</dd> +</dl> + +<p>对于每种按钮类型,您只能为 {@link +android.app.AlertDialog} 添加一个该类型的按钮。也就是说,您不能添加多个“肯定”按钮。</p> + + + +<div class="figure" style="width:290px;margin:0 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_list.png" alt="" /> +<p class="img-caption"><strong>图 3. </strong>一个包含标题和列表的对话框。 +</p> +</div> + +<h3 id="AddingAList">添加列表</h3> + +<p>可通过 {@link android.app.AlertDialog} API 提供三种列表:</p> +<ul> +<li>传统单选列表</li> +<li>永久性单选列表(单选按钮)</li> +<li>永久性多选列表(复选框)</li> +</ul> + +<p>要想创建如图 3 所示的单选列表,请使用 +{@link android.app.AlertDialog.Builder#setItems setItems()} 方法:</p> + +<pre style="clear:right"> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setTitle(R.string.pick_color) + .setItems(R.array.colors_array, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int which) { + // The 'which' argument contains the index position + // of the selected item + } + }); + return builder.create(); +} +</pre> + +<p>由于列表出现在对话框的内容区域,因此对话框无法同时显示消息和列表,您应该通过 +{@link android.app.AlertDialog.Builder#setTitle setTitle()} +为对话框设置标题。要想指定列表项,请调用 +{@link +android.app.AlertDialog.Builder#setItems setItems()} 来传递一个数组。或者,您也可以使用 +{@link +android.app.AlertDialog.Builder#setAdapter setAdapter()} 指定一个列表。这样一来,您就可以使用 {@link android.widget.ListAdapter} +以动态数据(如来自数据库的数据)支持列表。</p> + +<p>如果您选择通过 {@link android.widget.ListAdapter} +支持列表,请务必使用 +{@link android.support.v4.content.Loader},以便内容以异步方式加载。<a href="{@docRoot}guide/topics/ui/declaring-layout.html#AdapterViews">使用适配器构建布局</a>和<a href="{@docRoot}guide/components/loaders.html">加载程序</a>指南中对此做了进一步描述。 + + +</p> + +<p class="note"><strong>注:</strong>默认情况下,触摸列表项会清除对话框,除非您使用的是下列其中一种永久性选择列表。 +</p> + +<div class="figure" style="width:290px;margin:-30px 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_checkboxes.png" /> +<p class="img-caption"><strong>图 4. </strong> +多选项列表。</p> +</div> + + +<h4 id="Checkboxes">添加永久性多选列表或单选列表</h4> + +<p>要想添加多选项(复选框)或单选项(单选按钮)列表,请分别使用 +{@link android.app.AlertDialog.Builder#setMultiChoiceItems(Cursor,String,String, +DialogInterface.OnMultiChoiceClickListener) setMultiChoiceItems()} 或 +{@link android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()} +方法。</p> + +<p>例如,以下示例展示了如何创建如图 4 +所示的多选列表,将选定项保存在一个 +{@link java.util.ArrayList} 中:</p> + +<pre style="clear:right"> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + mSelectedItems = new ArrayList(); // Where we track the selected items + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Set the dialog title + builder.setTitle(R.string.pick_toppings) + // Specify the list array, the items to be selected by default (null for none), + // and the listener through which to receive callbacks when items are selected + .setMultiChoiceItems(R.array.toppings, null, + new DialogInterface.OnMultiChoiceClickListener() { + @Override + public void onClick(DialogInterface dialog, int which, + boolean isChecked) { + if (isChecked) { + // If the user checked the item, add it to the selected items + mSelectedItems.add(which); + } else if (mSelectedItems.contains(which)) { + // Else, if the item is already in the array, remove it + mSelectedItems.remove(Integer.valueOf(which)); + } + } + }) + // Set the action buttons + .setPositiveButton(R.string.ok, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + // User clicked OK, so save the mSelectedItems results somewhere + // or return them to the component that opened the dialog + ... + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + ... + } + }); + + return builder.create(); +} +</pre> + +<p>尽管传统列表和具有单选按钮的列表都能提供“单选”操作,但如果您想持久保存用户的选择,则应使用 +{@link +android.app.AlertDialog.Builder#setSingleChoiceItems(int,int,DialogInterface.OnClickListener) +setSingleChoiceItems()}。也就是说,如果稍后再次打开对话框时系统应指示用户的当前选择,那么您就需要创建一个具有单选按钮的列表。 + +</p> + + + + + +<h3 id="CustomLayout">创建自定义布局</h3> + +<div class="figure" style="width:290px;margin:-30px 0 0 40px"> +<img src="{@docRoot}images/ui/dialog_custom.png" alt="" /> +<p class="img-caption"><strong>图 5. </strong>自定义对话框布局。</p> +</div> + +<p>如果您想让对话框具有自定义布局,请创建一个布局,然后通过调用 +{@link +android.app.AlertDialog.Builder} 对象上的 {@link +android.app.AlertDialog.Builder#setView setView()} 将其添加到 {@link android.app.AlertDialog}。</p> + +<p>默认情况下,自定义布局会填充对话框窗口,但您仍然可以使用 +{@link android.app.AlertDialog.Builder} 方法来添加按钮和标题。</p> + +<p>例如,以下是图 5 中对话框的布局文件:</p> + +<p style="clear:right" class="code-caption">res/layout/dialog_signin.xml</p> +<pre> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:orientation="vertical" + android:layout_width="wrap_content" + android:layout_height="wrap_content"> + <ImageView + android:src="@drawable/header_logo" + android:layout_width="match_parent" + android:layout_height="64dp" + android:scaleType="center" + android:background="#FFFFBB33" + android:contentDescription="@string/app_name" /> + <EditText + android:id="@+id/username" + android:inputType="textEmailAddress" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="16dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="4dp" + android:hint="@string/username" /> + <EditText + android:id="@+id/password" + android:inputType="textPassword" + android:layout_width="match_parent" + android:layout_height="wrap_content" + android:layout_marginTop="4dp" + android:layout_marginLeft="4dp" + android:layout_marginRight="4dp" + android:layout_marginBottom="16dp" + android:fontFamily="sans-serif" + android:hint="@string/password"/> +</LinearLayout> +</pre> + +<p class="note"><strong>提示</strong>:默认情况下,当您将 {@link android.widget.EditText} +元素设置为使用 {@code "textPassword"} +输入类型时,字体系列将设置为固定宽度。因此,您应该将其字体系列更改为 +{@code "sans-serif"},以便两个文本字段都使用匹配的字体样式。</p> + +<p>要扩展 {@link android.support.v4.app.DialogFragment} +中的布局,请通过 {@link android.app.Activity#getLayoutInflater()} +获取一个 {@link android.view.LayoutInflater} 并调用 +{@link android.view.LayoutInflater#inflate inflate()},其中第一个参数是布局资源 +ID,第二个参数是布局的父视图。然后,您可以调用 +{@link android.app.AlertDialog#setView setView()} +将布局放入对话框。</p> + +<pre> +@Override +public Dialog onCreateDialog(Bundle savedInstanceState) { + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + // Get the layout inflater + LayoutInflater inflater = getActivity().getLayoutInflater(); + + // Inflate and set the layout for the dialog + // Pass null as the parent view because its going in the dialog layout + builder.setView(inflater.inflate(R.layout.dialog_signin, null)) + // Add action buttons + .setPositiveButton(R.string.signin, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + // sign in the user ... + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + LoginDialogFragment.this.getDialog().cancel(); + } + }); + return builder.create(); +} +</pre> + +<div class="note"> +<p><strong>提示</strong>:如果您想要自定义对话框,可以改用对话框的形式显示 {@link android.app.Activity},而不是使用 {@link android.app.Dialog} +API。 +只需创建一个 Activity,并在 +<a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a> 清单文件元素中将其主题设置为 +{@link android.R.style#Theme_Holo_Dialog Theme.Holo.Dialog}:</p> + +<pre> +<activity android:theme="@android:style/Theme.Holo.Dialog" > +</pre> +<p>就这么简单。Activity 现在会显示在一个对话框窗口中,而非全屏显示。</p> +</div> + + + +<h2 id="PassingEvents">将事件传回给对话框的宿主</h2> + +<p>当用户触摸对话框的某个操作按钮或从列表中选择某一项时,您的 +{@link android.support.v4.app.DialogFragment} +可能会自行执行必要的操作,但通常您想将事件传递给打开该对话框的 Activity 或片段。 +为此,请定义一个界面,为每种点击事件定义一种方法。然后在从该对话框接收操作事件的宿主组件中实现该界面。 + +</p> + +<p>例如,以下 {@link android.support.v4.app.DialogFragment} +定义了一个界面,通过该界面将事件传回给宿主 Activity:</p> + +<pre> +public class NoticeDialogFragment extends DialogFragment { + + /* The activity that creates an instance of this dialog fragment must + * implement this interface in order to receive event callbacks. + * Each method passes the DialogFragment in case the host needs to query it. */ + public interface NoticeDialogListener { + public void onDialogPositiveClick(DialogFragment dialog); + public void onDialogNegativeClick(DialogFragment dialog); + } + + // Use this instance of the interface to deliver action events + NoticeDialogListener mListener; + + // Override the Fragment.onAttach() method to instantiate the NoticeDialogListener + @Override + public void onAttach(Activity activity) { + super.onAttach(activity); + // Verify that the host activity implements the callback interface + try { + // Instantiate the NoticeDialogListener so we can send events to the host + mListener = (NoticeDialogListener) activity; + } catch (ClassCastException e) { + // The activity doesn't implement the interface, throw exception + throw new ClassCastException(activity.toString() + + " must implement NoticeDialogListener"); + } + } + ... +} +</pre> + +<p>对话框的宿主 Activity 会通过对话框片段的构造函数创建一个对话框实例,并通过实现的 +{@code NoticeDialogListener} +界面接收对话框的事件:</p> + +<pre> +public class MainActivity extends FragmentActivity + implements NoticeDialogFragment.NoticeDialogListener{ + ... + + public void showNoticeDialog() { + // Create an instance of the dialog fragment and show it + DialogFragment dialog = new NoticeDialogFragment(); + dialog.show(getSupportFragmentManager(), "NoticeDialogFragment"); + } + + // The dialog fragment receives a reference to this Activity through the + // Fragment.onAttach() callback, which it uses to call the following methods + // defined by the NoticeDialogFragment.NoticeDialogListener interface + @Override + public void onDialogPositiveClick(DialogFragment dialog) { + // User touched the dialog's positive button + ... + } + + @Override + public void onDialogNegativeClick(DialogFragment dialog) { + // User touched the dialog's negative button + ... + } +} +</pre> + +<p>由于宿主 Activity 会实现 +{@code NoticeDialogListener}—由以上显示的 +{@link android.support.v4.app.Fragment#onAttach onAttach()} +回调方法强制执行 — 因此对话框片段可以使用界面回调方法向 Activity 传递点击事件:</p> + +<pre> +public class NoticeDialogFragment extends DialogFragment { + ... + + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // Build the dialog and set up the button click handlers + AlertDialog.Builder builder = new AlertDialog.Builder(getActivity()); + builder.setMessage(R.string.dialog_fire_missiles) + .setPositiveButton(R.string.fire, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Send the positive button event back to the host activity + mListener.onDialogPositiveClick(NoticeDialogFragment.this); + } + }) + .setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + // Send the negative button event back to the host activity + mListener.onDialogNegativeClick(NoticeDialogFragment.this); + } + }); + return builder.create(); + } +} +</pre> + + + +<h2 id="ShowingADialog">显示对话框</h2> + +<p>如果您想显示对话框,请创建一个 {@link +android.support.v4.app.DialogFragment} 实例并调用 {@link android.support.v4.app.DialogFragment#show +show()},以传递对话框片段的 {@link android.support.v4.app.FragmentManager} +和标记名称。</p> + +<p>您可以通过从 {@link android.support.v4.app.FragmentActivity} +调用 {@link android.support.v4.app.FragmentActivity#getSupportFragmentManager()} 或从 +{@link +android.support.v4.app.Fragment} 调用 {@link +android.support.v4.app.Fragment#getFragmentManager()} 来获取 {@link android.support.v4.app.FragmentManager}。例如:</p> + +<pre> +public void confirmFireMissiles() { + DialogFragment newFragment = new FireMissilesDialogFragment(); + newFragment.show(getSupportFragmentManager(), "missiles"); +} +</pre> + +<p>第二个参数 {@code "missiles"} +是系统用于保存片段状态并在必要时进行恢复的唯一标记名称。该标记还允许您通过调用 +{@link android.support.v4.app.FragmentManager#findFragmentByTag +findFragmentByTag()} 获取片段的句柄。</p> + + + + +<h2 id="FullscreenDialog">全屏显示对话框或将其显示为嵌入式片段</h2> + +<p>您可能采用以下 UI +设计:您想让一部分 UI +在某些情况下显示为对话框,但在其他情况下全屏显示或显示为嵌入式片段(也许取决于设备使用大屏幕还是小屏幕)。{@link android.support.v4.app.DialogFragment} +类便具有这种灵活性,因为它仍然可以充当嵌入式 {@link +android.support.v4.app.Fragment}。</p> + +<p>但在这种情况下,您不能使用 {@link android.app.AlertDialog.Builder AlertDialog.Builder} +或其他 {@link android.app.Dialog} 对象来构建对话框。如果您想让 {@link android.support.v4.app.DialogFragment} +具有嵌入能力,则必须在布局中定义对话框的 +UI,然后在 +{@link android.support.v4.app.DialogFragment#onCreateView +onCreateView()} 回调中加载布局。</p> + +<p>以下示例 {@link android.support.v4.app.DialogFragment} 可以显示为对话框或嵌入式片段(使用名为 +<code>purchase_items.xml</code> 的布局):</p> + +<pre> +public class CustomDialogFragment extends DialogFragment { + /** The system calls this to get the DialogFragment's layout, regardless + of whether it's being displayed as a dialog or an embedded fragment. */ + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, + Bundle savedInstanceState) { + // Inflate the layout to use as dialog or embedded fragment + return inflater.inflate(R.layout.purchase_items, container, false); + } + + /** The system calls this only when creating the layout in a dialog. */ + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + // The only reason you might override this method when using onCreateView() is + // to modify any dialog characteristics. For example, the dialog includes a + // title by default, but your custom layout might not need it. So here you can + // remove the dialog title, but you must call the superclass to get the Dialog. + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } +} +</pre> + +<p>以下代码可根据屏幕尺寸决定将片段显示为对话框还是全屏 +UI:</p> + +<pre> +public void showDialog() { + FragmentManager fragmentManager = getSupportFragmentManager(); + CustomDialogFragment newFragment = new CustomDialogFragment(); + + if (mIsLargeLayout) { + // The device is using a large layout, so show the fragment as a dialog + newFragment.show(fragmentManager, "dialog"); + } else { + // The device is smaller, so show the fragment fullscreen + FragmentTransaction transaction = fragmentManager.beginTransaction(); + // For a little polish, specify a transition animation + transaction.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN); + // To make it fullscreen, use the 'content' root view as the container + // for the fragment, which is always the root view for the activity + transaction.add(android.R.id.content, newFragment) + .addToBackStack(null).commit(); + } +} +</pre> + +<p>如需了解有关执行片段事务的详细信息,请参阅<a href="{@docRoot}guide/components/fragments.html">片段</a>指南。 +</p> + +<p>在本示例中,<code>mIsLargeLayout</code> +布尔值指定当前设备是否应该使用应用的大布局设计(进而将此片段显示为对话框,而不是全屏显示)。 +设置这种布尔值的最佳方法是声明一个<a href="{@docRoot}guide/topics/resources/more-resources.html#Bool">布尔资源值</a>,其中包含适用于不同屏幕尺寸的<a href="{@docRoot}guide/topics/resources/providing-resources.html#AlternativeResources">备用资源</a>值。 + +例如,以下两个版本的布尔资源适用于不同的屏幕尺寸: +</p> + +<p class="code-caption">res/values/bools.xml</p> +<pre> +<!-- Default boolean values --> +<resources> + <bool name="large_layout">false</bool> +</resources> +</pre> + +<p class="code-caption">res/values-large/bools.xml</p> +<pre> +<!-- Large screen boolean values --> +<resources> + <bool name="large_layout">true</bool> +</resources> +</pre> + +<p>然后,您可以在 Activity 的 +{@link android.app.Activity#onCreate onCreate()} 方法执行期间初始化 {@code mIsLargeLayout} 值:</p> + +<pre> +boolean mIsLargeLayout; + +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.activity_main); + + mIsLargeLayout = getResources().getBoolean(R.bool.large_layout); +} +</pre> + + + +<h3 id="ActivityAsDialog">在大屏幕上将 Activity 显示为对话框</h3> + +<p>相对于在小屏幕上将对话框显示为全屏 +UI,您可以通过在大屏幕上将 {@link android.app.Activity} +显示为对话框来达到相同的效果。您选择的方法取决于应用设计,但当应用已经针对小屏幕进行设计,而您想要通过将短生存期 Activity 显示为对话框来改善平板电脑体验时,将 Activity 显示为对话框往往很有帮助。 + + +</p> + +<p>要想仅在大屏幕上将 Activity 显示为对话框,请将 +{@link android.R.style#Theme_Holo_DialogWhenLarge Theme.Holo.DialogWhenLarge} +主题应用于 <a href="{@docRoot}guide/topics/manifest/activity-element.html">{@code +<activity>}</a> 清单文件元素:</p> + +<pre> +<activity android:theme="@android:style/Theme.Holo.DialogWhenLarge" > +</pre> + +<p>如需了解有关通过主题设置 Activity 样式的详细信息,请参阅<a href="{@docRoot}guide/topics/ui/themes.html">样式和主题</a>指南。</p> + + + +<h2 id="DismissingADialog">清除对话框</h2> + +<p>当用户触摸使用 +{@link android.app.AlertDialog.Builder} 创建的任何操作按钮时,系统会为您清除对话框。</p> + +<p>系统还会在用户触摸某个对话框列表项时清除对话框,但列表使用单选按钮或复选框时除外。 +否则,您可以通过在 {@link +android.support.v4.app.DialogFragment} 上调用 +{@link android.support.v4.app.DialogFragment#dismiss()} 来手动清除对话框。</p> + +<p>如需在对话框消失时执行特定操作,则可以在您的 +{@link +android.support.v4.app.DialogFragment} 中实现 {@link +android.support.v4.app.DialogFragment#onDismiss onDismiss()} 方法。</p> + +<p>您还可以<em>取消</em>对话框。这是一个特殊事件,它表示用户显式离开对话框,而不完成任务。 +如果用户按“返回”<em></em>按钮,触摸对话框区域外部的屏幕,或者您在 {@link +android.app.Dialog} 上显式调用 {@link android.app.Dialog#cancel()}(例如,为了响应对话框中的“取消”按钮),就会发生这种情况。 + +</p> + +<p>如上例所示,您可以通过在您的 {@link +android.support.v4.app.DialogFragment} 中实现 +{@link android.support.v4.app.DialogFragment#onCancel onCancel()} 来响应取消事件。</p> + +<p class="note"><strong>注:</strong>系统会在每个调用 {@link android.support.v4.app.DialogFragment#onCancel onCancel()} +回调的事件发生时立即调用 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()}。不过,如果您调用 {@link android.app.Dialog#dismiss Dialog.dismiss()} 或 +{@link +android.support.v4.app.DialogFragment#dismiss DialogFragment.dismiss()},系统会调用 +{@link android.support.v4.app.DialogFragment#onDismiss onDismiss()},“而绝不会”调用<em></em> +{@link android.support.v4.app.DialogFragment#onCancel onCancel()}。因此,当用户在对话框中按“肯定”<em></em>按钮,从视图中移除对话框时,您通常应该调用 +{@link android.support.v4.app.DialogFragment#dismiss dismiss()}。 +</p> + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd new file mode 100644 index 000000000000..b77f3bf497da --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/menus.jd @@ -0,0 +1,1031 @@ +page.title=菜单 +parent.title=用户界面 +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文内容</h2> +<ol> + <li><a href="#xml">使用 XML 定义菜单</a></li> + <li><a href="#options-menu">创建选项菜单</a> + <ol> + <li><a href="#RespondingOptionsMenu">处理点击事件</a></li> + <li><a href="#ChangingTheMenu">在运行时更改菜单项</a></li> + </ol> + </li> + <li><a href="#context-menu">创建上下文菜单</a> + <ol> + <li><a href="#FloatingContextMenu">创建浮动上下文菜单</a></li> + <li><a href="#CAB">使用上下文操作模式</a></li> + </ol> + </li> + <li><a href="#PopupMenu">创建弹出菜单</a> + <ol> + <li><a href="#PopupEvents">处理点击事件</a></li> + </ol> + </li> + <li><a href="#groups">创建菜单组</a> + <ol> + <li><a href="#checkable">使用可选中的菜单项</a></li> + </ol> + </li> + <li><a href="#intents">添加基于 Intent 的菜单项</a> + <ol> + <li><a href="#AllowingToAdd">允许将 Activity 添加到其他菜单中</a></li> + </ol> + </li> +</ol> + + <h2>关键类</h2> + <ol> + <li>{@link android.view.Menu}</li> + <li>{@link android.view.MenuItem}</li> + <li>{@link android.view.ContextMenu}</li> + <li>{@link android.view.ActionMode}</li> + </ol> + + <h2>另请参阅</h2> + <ol> + <li><a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a></li> + <li><a href="{@docRoot}guide/topics/resources/menu-resource.html">菜单资源</a></li> + <li><a href="http://android-developers.blogspot.com/2012/01/say-goodbye-to-menu-button.html">从此告别菜单按钮</a> +</li> + </ol> +</div> +</div> + +<p>菜单是许多应用中常见的用户界面组件。要提供熟悉而一致的用户体验,您应使用 {@link android.view.Menu} API +呈现 Activity 中的用户操作和其他选项。 +</p> + +<p>从 +Android 3.0(API 级别 11)开始,采用 Android 技术的设备不必再提供一个专用“菜单”按钮。<em></em>随着这种改变,Android +应用需摆脱对包含 +6 个项目的传统菜单面板的依赖,取而代之的是要提供一个操作栏来呈现常见的用户操作。</p> + +<p>尽管某些菜单项的设计和用户体验已发生改变,但定义一系列操作和选项所使用的语义仍是以 +{@link android.view.Menu} API 为基础。本指南将介绍所有 +Android 版本系统中三种基本菜单或操作呈现效果的创建方法: +</p> + +<dl> + <dt><strong>选项菜单和操作栏</strong></dt> + <dd><a href="#options-menu">选项菜单</a>是某个 Activity 的主菜单项, +供您放置对应用产生全局影响的操作,如“搜索”、“撰写电子邮件”和“设置”。 + + <p>如果您的应用是针对 +Android 2.3 或更低版本的系统而开发,则用户可以通过按“菜单”按钮显示选项菜单面板。<em></em></p> + <p>在 +Android 3.0 及更高版本的系统中,<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>以屏幕操作项和溢出选项的组合形式呈现选项菜单中的各项。从 Android 3.0 开始,“菜单”按钮已弃用(某些设备没有该按钮),因此您应改为使用操作栏,来提供对操作和其他选项的访问。<em></em> + + +</p> + <p>请参阅<a href="#options-menu">创建选项菜单</a>部分。</p> + </dd> + + <dt><strong>上下文菜单和上下文操作模式</strong></dt> + + <dd>上下文菜单是用户长按某一元素时出现的<a href="#FloatingContextMenu">浮动菜单</a>。 +它提供的操作将影响所选内容或上下文框架。 + + <p>开发针对 Android 3.0 及更高版本系统的应用时,您应改为使用<a href="#CAB">上下文操作模式</a>,以便对所选内容启用操作。此模式在屏幕顶部栏显示影响所选内容的操作项目,并允许用户选择多项。 + +</p> + <p>请参阅<a href="#context-menu">创建上下文菜单</a>部分。</p> +</dd> + + <dt><strong>弹出菜单</strong></dt> + <dd>弹出菜单将以垂直列表形式显示一系列项目,这些项目将锚定到调用该菜单的视图中。 +它特别适用于提供与特定内容相关的大量操作,或者为命令的另一部分提供选项。 +弹出菜单中的操作<strong>不会</strong>直接影响对应的内容,而上下文操作则会影响。 + +相反,弹出菜单适用于与您 Activity 中的内容区域相关的扩展操作。 + + <p>请参阅<a href="#PopupMenu">创建弹出菜单</a>部分。</p> +</dd> +</dl> + + + +<h2 id="xml">使用 XML 定义菜单</h2> + +<p>对于所有菜单类型,Android +提供了标准的 XML 格式来定义菜单项。您应在 +XML <a href="{@docRoot}guide/topics/resources/menu-resource.html">菜单资源</a>中定义菜单及其所有项,而不是在 Activity 的代码中构建菜单。定义后,您可以在 Activity 或片段中扩充菜单资源(将其作为 +{@link android.view.Menu} +对象加载)。</p> + +<p>使用菜单资源是一种很好的做法,原因如下:</p> +<ul> + <li>更易于使用 XML 可视化菜单结构</li> + <li>将菜单内容与应用的行为代码分离</li> + <li>允许您利用<a href="{@docRoot}guide/topics/resources/index.html">应用资源</a>框架,为不同的平台版本、屏幕尺寸和其他配置创建备用菜单配置 +</li> +</ul> + +<p>要定义菜单,请在项目 <code>res/menu/</code> +目录内创建一个 XML 文件,并使用以下元素构建菜单:</p> +<dl> + <dt><code><menu></code></dt> + <dd>定义 {@link android.view.Menu},即菜单项的容器。<code><menu></code> +元素必须是该文件的根节点,并且能够包含一个或多个 +<code><item></code> 和 <code><group></code> 元素。</dd> + + <dt><code><item></code></dt> + <dd>创建 {@link android.view.MenuItem},此元素表示菜单中的一项,可能包含嵌套的 <code><menu></code> +元素,以便创建子菜单。</dd> + + <dt><code><group></code></dt> + <dd>{@code <item>} 元素的不可见容器(可选)。它支持您对菜单项进行分类,使其共享活动状态和可见性等属性。 +如需了解详细信息,请参阅<a href="#groups">创建菜单组</a>部分。 +</dd> +</dl> + + +<p>以下是名为 <code>game_menu.xml</code> 的菜单示例:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/new_game" + android:icon="@drawable/ic_new_game" + android:title="@string/new_game" + android:showAsAction="ifRoom"/> + <item android:id="@+id/help" + android:icon="@drawable/ic_help" + android:title="@string/help" /> +</menu> +</pre> + +<p><code><item></code> +元素支持多个属性,您可使用这些属性定义项目的外观和行为。上述菜单中的项目包括以下属性:</p> + +<dl> + <dt>{@code android:id}</dt> + <dd>项目特有的资源 +ID,让应用能够在用户选择项目时识别该项目。</dd> + <dt>{@code android:icon}</dt> + <dd>引用一个要用作项目图标的 Drawable 类。</dd> + <dt>{@code android:title}</dt> + <dd>引用一个要用作项目标题的字符串。</dd> + <dt>{@code android:showAsAction}</dt> + <dd>指定此项应作为操作项目显示在<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>中的时间和方式。</dd> +</dl> + +<p>这些是您应使用的最重要属性,但还有许多其他属性。有关所有受支持属性的信息,请参阅<a href="{@docRoot}guide/topics/resources/menu-resource.html">菜单资源</a>文档。 +</p> + +<p>您可以通过以 {@code <item>} +子元素的形式添加 {@code <menu>} 元素,向任何菜单(子菜单除外)中的某个菜单项添加子菜单。当应用具有大量可按主题进行组织的功能时,类似于 PC 应用程序菜单栏中的菜单项(“文件”、“编辑”、“视图”等),子菜单非常有用。 + +例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/file" + android:title="@string/file" > + <!-- "file" submenu --> + <menu> + <item android:id="@+id/create_new" + android:title="@string/create_new" /> + <item android:id="@+id/open" + android:title="@string/open" /> + </menu> + </item> +</menu> +</pre> + +<p>要在 Activity 中使用菜单,您需要使用 {@link android.view.MenuInflater#inflate(int,Menu) +MenuInflater.inflate()} 扩充菜单资源(将 XML +资源转换为可编程对象)。在下文中,您将了解如何扩充每种类型的菜单。 +</p> + + + +<h2 id="options-menu">创建选项菜单</h2> + +<div class="figure" style="width:200px;margin:0"> + <img src="{@docRoot}images/options_menu.png" height="333" alt="" /> + <p class="img-caption"><strong>图 1. </strong>Android 2.3 系统上浏览器 +中的选项菜单。</p> +</div> + +<p>在选项菜单中,您应当包括与当前 Activity 上下文相关的操作和其他选项,如“搜索”、“撰写电子邮件”和“设置”。 +</p> + +<p>选项菜单中的项目在屏幕上的显示位置取决于您开发的应用所适用的 Android 版本: +</p> + +<ul> + <li>如果您开发的应用是用于 +<strong>Android +2.3.x(API 级别 10)或更低版本</strong>的系统,则当用户按“菜单”按钮时,选项菜单的内容会出现在屏幕底部,如图 1 所示。<em></em>打开时,第一个可见部分是图标菜单,其中包含多达 6 个菜单项。 + +如果菜单包括 6 个以上项目,则 +Android +会将第六项和其余项目放入溢出菜单。用户可以通过选择“更多”打开该菜单。<em></em></li> + + <li>如果您开发的应用是用于 <strong>Android +3.0(API 级别 11)及更高版本</strong>的系统,则选项菜单中的项目将出现在<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>中。默认情况下,系统会将所有项目均放入操作溢出菜单中。用户可以使用操作栏右侧的操作溢出菜单图标(或者,通过按设备“菜单”按钮(如有))显示操作溢出菜单。 + +<em></em>要支持快速访问重要操作,您可以将 +{@code android:showAsAction="ifRoom"} +添加到对应的 +{@code <item>} 元素,从而将几个项目提升到操作栏中(请参阅图 +2)。 <p>如需了解有关操作项目和其他操作栏行为的详细信息,请参阅<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>指南。 </p> +<p class="note"><strong>注:</strong>即使您的应用不是针对 +Android 3.0 或更高版本的系统而开发,但仍可构建自己的操作栏布局,以获得类似的效果。<em></em>有关如何使用操作栏支持旧版 +Android +的示例,请参阅<a href="{@docRoot}resources/samples/ActionBarCompat/index.html">操作栏兼容性</a>示例。</p> +</li> +</ul> + +<img src="{@docRoot}images/ui/actionbar.png" alt="" /> +<p class="img-caption"><strong>图 2. </strong>摘自 +<a href="{@docRoot}resources/samples/HoneycombGallery/index.html">Honeycomb Gallery</a> 应用的操作栏,其中显示了导航选项卡和相机操作项目(以及操作溢出菜单按钮)。</p> + +<p>您可以通过 {@link android.app.Activity} +子类或 {@link android.app.Fragment} 子类为选项菜单声明项目。如果您的 Activity 和片段均为选项菜单声明项目,则这些项目将合并到 +UI 中。系统将首先显示 Activity 的项目,随后按每个片段添加到 Activity 中的顺序显示该片段的项目。 + +如有必要,您可以使用 {@code android:orderInCategory} +属性,对需要移动的每个 {@code <item>} 中的菜单项重新排序。</p> + +<p>要为 Activity 指定选项菜单,请重写 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}(Fragment 会提供自己的 +{@link android.app.Fragment#onCreateOptionsMenu onCreateOptionsMenu()} 回调)。通过此方法,您可以将菜单资源(<a href="#xml">使用 +XML</a> 定义)扩充到回调中提供的 {@link +android.view.Menu} 中。例如:</p> + +<pre> +@Override +public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = {@link android.app.Activity#getMenuInflater()}; + inflater.inflate(R.menu.game_menu, menu); + return true; +} +</pre> + +<p>此外,您还可以使用 {@link android.view.Menu#add(int,int,int,int) +add()} 添加菜单项,并使用 +{@link android.view.Menu#findItem findItem()} 检索项目,以便使用 {@link android.view.MenuItem} API 修改其属性。</p> + +<p>如果您开发的应用是用于 Android 2.3.x 及更低版本的系统,则当用户首次打开选项菜单时,系统会调用 {@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} +来创建该菜单。如果您开发的应用是用于 +Android 3.0 及更高版本的系统,则系统将在启动 Activity 时调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()},以便向操作栏显示项目。</p> + + + +<h3 id="RespondingOptionsMenu">处理点击事件</h3> + +<p>用户从选项菜单中选择项目(包括操作栏中的操作项目)时,系统将调用 Activity 的 +{@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} 方法。此方法将传递所选的 {@link android.view.MenuItem}。您可以通过调用 +{@link android.view.MenuItem#getItemId()} 方法来识别项目,该方法将返回菜单项的唯一 ID(由菜单资源中的 {@code android:id} 属性定义,或通过提供给 {@link android.view.Menu#add(int,int,int,int) add()} +方法的整型数定义)。 +您可以将此 +ID 与已知的菜单项匹配,以执行适当的操作。例如:</p> + +<pre> +@Override +public boolean onOptionsItemSelected(MenuItem item) { + // Handle item selection + switch (item.getItemId()) { + case R.id.new_game: + newGame(); + return true; + case R.id.help: + showHelp(); + return true; + default: + return super.onOptionsItemSelected(item); + } +} +</pre> + +<p>成功处理菜单项后,系统将返回 {@code true}。如果未处理菜单项,则应调用 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} +的超类实现(默认实现将返回 false)。 +</p> + +<p>如果 Activity 包括片段,则系统将依次为 Activity 和每个片段(按照每个片段的添加顺序)调用 {@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} +,直到有一返回结果为 +{@code true} 或所有片段均调用完毕为止。</p> + +<p class="note"><strong>提示:</strong>Android 3.0 新增了一项功能,支持您在 XML 中使用 {@code android:onClick} +属性为菜单项定义点击行为。该属性的值必须是 Activity 使用菜单定义的方法的名称。 +该方法必须是公用的,且接受单个 +{@link android.view.MenuItem} +参数;当系统调用此方法时,它会传递所选的菜单项。如需了解详细信息和示例,请参阅<a href="{@docRoot}guide/topics/resources/menu-resource.html">菜单资源</a>文档。</p> + +<p class="note"><strong>提示:</strong>如果应用包含多个 Activity,且其中某些 Activity 提供相同的选项菜单,则可考虑创建一个仅实现 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 和 {@link android.app.Activity#onOptionsItemSelected(MenuItem) +onOptionsItemSelected()} +方法的 Activity。然后,为每个应共享相同选项菜单的 Activity 扩展此类。 +通过这种方式,您可以管理一个用于处理菜单操作的代码集,且每个子级类均会继承菜单行为。若要将菜单项添加到一个子级 Activity,请重写该 Activity 中的 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()}。 + +调用 {@code super.onCreateOptionsMenu(menu)},以便创建原始菜单项,然后使用 +{@link +android.view.Menu#add(int,int,int,int) menu.add()} 添加新菜单项。此外,您还可以替代各个菜单项的超类行为。 +</p> + + +<h3 id="ChangingTheMenu">在运行时更改菜单项</h3> + +<p>系统调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) +onCreateOptionsMenu()} 后,将保留您填充的 {@link android.view.Menu} 实例。除非菜单由于某些原因而失效,否则不会再次调用 +{@link android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()}。但是,您仅应使用 +{@link +android.app.Activity#onCreateOptionsMenu(Menu) onCreateOptionsMenu()} 来创建初始菜单状态,而不应使用它在 Activity 生命周期中执行任何更改。</p> + +<p>如需根据在 Activity 生命周期中发生的事件修改选项菜单,则可通过 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()} +方法执行此操作。此方法向您传递 +{@link android.view.Menu} +对象(因为该对象目前存在),以便您能够对其进行修改,如添加、删除或禁用项目。(此外,片段还提供 {@link +android.app.Fragment#onPrepareOptionsMenu onPrepareOptionsMenu()} 回调。)</p> + +<p>在 +Android 2.3.x 及更低版本中,每当用户打开选项菜单时(按“菜单”按钮),系统均会调用 {@link +android.app.Activity#onPrepareOptionsMenu(Menu) +onPrepareOptionsMenu()}。<em></em></p> + +<p>在 +Android 3.0 及更高版本中,当菜单项显示在操作栏中时,选项菜单被视为始终处于打开状态。发生事件时,如果您要执行菜单更新,则必须调用 +{@link android.app.Activity#invalidateOptionsMenu invalidateOptionsMenu()} 来请求系统调用 +{@link android.app.Activity#onPrepareOptionsMenu(Menu) onPrepareOptionsMenu()}。</p> + +<p class="note"><strong>注:</strong>切勿根据目前处于焦点的 +{@link android.view.View} +更改选项菜单中的项目。处于触摸模式(用户未使用轨迹球或方向键)时,视图无法形成焦点,因此切勿根据焦点修改选项菜单中的项目。 + +若要为 {@link +android.view.View} 提供上下文相关的菜单项,请使用<a href="#context-menu">上下文菜单</a>。</p> + + + + +<h2 id="context-menu">创建上下文菜单</h2> + +<div class="figure" style="width:420px;margin-top:-1em"> + <img src="{@docRoot}images/ui/menu-context.png" alt="" /> + <p class="img-caption"><strong>图 3. </strong>浮动上下文菜单(左)和上下文操作栏(右)的屏幕截图。 +</p> +</div> + +<p>上下文菜单提供了许多操作,这些操作影响 UI 中的特定项目或上下文框架。您可以为任何视图提供上下文菜单,但这些菜单通常用于 +{@link +android.widget.ListView}、{@link android.widget.GridView} +或用户可直接操作每个项目的其他视图集合中的项目。</p> + +<p>提供上下文操作的方法有两种:</p> +<ul> + <li>使用<a href="#FloatingContextMenu">浮动上下文菜单</a>。用户长按(按住)一个声明支持上下文菜单的视图时,菜单显示为菜单项的浮动列表(类似于对话框)。 + +用户一次可对一个项目执行上下文操作。 +</li> + + <li>使用<a href="#CAB">上下文操作模式</a>。此模式是 +{@link android.view.ActionMode} +的系统实现,它将在屏幕顶部显示上下文操作栏,其中包括影响所选项的操作项目。<em></em>当此模式处于活动状态时,用户可以同时对多项执行操作(如果应用允许)。 +</li> +</ul> + +<p class="note"><strong>注:</strong>上下文操作模式可用于 +Android 3.0(API +级别 11)及更高版本的系统,是显示上下文操作(如果可用)的首选方法。如果应用支持低于 +3.0 版本的系统,则应在这些设备上回退到浮动上下文菜单。</p> + + +<h3 id="FloatingContextMenu">创建浮动上下文菜单</h3> + +<p>要提供浮动上下文菜单,请执行以下操作:</p> +<ol> + <li>通过调用 +{@link android.app.Activity#registerForContextMenu(View) registerForContextMenu()},注册应与上下文菜单关联的 +{@link android.view.View} 并将其传递给 {@link android.view.View}。 + <p>如果 Activity 使用 {@link android.widget.ListView} 或 {@link android.widget.GridView} +且您希望每个项目均提供相同的上下文菜单,请通过将 +{@link android.widget.ListView} 或 {@link android.widget.GridView} 传递给 {@link +android.app.Activity#registerForContextMenu(View) registerForContextMenu()},为上下文菜单注册所有项目。</p> +</li> + + <li>在 {@link android.app.Activity} 或 {@link android.app.Fragment} 中实现 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +方法。 + <p>当注册后的视图收到长按事件时,系统将调用您的 {@link +android.view.View.OnCreateContextMenuListener#onCreateContextMenu onCreateContextMenu()} +方法。在此方法中,您通常可通过扩充菜单资源来定义菜单项。例如: +</p> +<pre> +@Override +public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); +} +</pre> + +<p>{@link android.view.MenuInflater} 允许您通过<a href="{@docRoot}guide/topics/resources/menu-resource.html">菜单资源</a>扩充上下文菜单。回调方法参数包括用户所选的 +{@link android.view.View},以及一个提供有关所选项的附加信息的 +{@link android.view.ContextMenu.ContextMenuInfo} +对象。如果 Activity 有多个视图,每个视图均提供不同的上下文菜单,则可使用这些参数确定要扩充的上下文菜单。 + +</p> +</li> + +<li>实现 {@link android.app.Activity#onContextItemSelected(MenuItem) +onContextItemSelected()}。 + <p>用户选择菜单项时,系统将调用此方法,以便您能够执行适当的操作。 +例如:</p> + +<pre> +@Override +public boolean onContextItemSelected(MenuItem item) { + AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo(); + switch (item.getItemId()) { + case R.id.edit: + editNote(info.id); + return true; + case R.id.delete: + deleteNote(info.id); + return true; + default: + return super.onContextItemSelected(item); + } +} +</pre> + +<p>{@link android.view.MenuItem#getItemId()} +方法将查询所选菜单项的 ID,您应使用 {@code +android:id} 属性将此 ID 分配给 XML 中的每个菜单项,如<a href="#xml">使用 +XML 定义菜单</a>部分所示。</p> + +<p>成功处理菜单项后,系统将返回 {@code true}。如果未处理菜单项,则应将菜单项传递给超类实现。 +如果 Activity 包括片段,则 Activity 将先收到此回调。 +通过在未处理的情况下调用超类,系统将事件逐一传递给每个片段中相应的回调方法(按照每个片段的添加顺序),直到返回 +{@code true} 或 {@code false} +为止。({@link android.app.Activity} +和 {@code android.app.Fragment} 的默认实现返回 {@code +false},因此您始终应在未处理的情况下调用超类。)</p> +</li> +</ol> + + +<h3 id="CAB">使用上下文操作模式</h3> + +<p>上下文操作模式是 +{@link android.view.ActionMode} 的一种系统实现,它将用户交互的重点转到执行上下文操作上。用户通过选择项目启用此模式时,屏幕顶部将出现一个“上下文操作栏”,显示用户可对当前所选项执行的操作。 + +<em></em>启用此模式后,用户可以选择多个项目(若您允许)、取消选择项目以及继续在 Activity 内导航(在您允许的最大范围内)。 + +当用户取消选择所有项目、按“返回”按钮或选择操作栏左侧的“完成”操作时,该操作模式将会禁用,且上下文操作栏将会消失。 + +<em></em></p> + +<p class="note"><strong>注:</strong>上下文操作栏不一定与<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>相关联。 +尽管表面上看来上下文操作栏取代了操作栏的位置,但事实上二者独立运行。 + +</p> + +<p>如果您的应用是针对 +Android 3.0(API 级别 11)或更高版本的系统而开发,则通常应使用上下文操作模式(而不是<a href="#FloatingContextMenu">浮动上下文菜单</a>)显示上下文操作。</p> + +<p>对于提供上下文操作的视图,当出现以下两个事件(或之一)时,您通常应调用上下文操作模式: +</p> +<ul> + <li>用户长按视图。</li> + <li>用户选中复选框或视图内的类似 UI 组件。</li> +</ul> + +<p>应用如何调用上下文操作模式以及如何定义每个操作的行为,具体取决于您的设计。 +设计基本上分为两种:</p> +<ul> + <li>针对单个任意视图的上下文操作。</li> + <li>针对 {@link +android.widget.ListView} 或 {@link android.widget.GridView} +中项目组的批处理上下文操作(允许用户选择多个项目并针对所有项目执行操作)。</li> +</ul> + +<p>下文介绍每种场景所需的设置。</p> + + +<h4 id="CABforViews">为单个视图启用上下文操作模式</h4> + +<p>如果希望仅当用户选择特定视图时才调用上下文操作模式,则应: +</p> +<ol> + <li>实现 {@link android.view.ActionMode.Callback} 接口。在其回调方法中,您既可以为上下文操作栏指定操作,又可以响应操作项目的点击事件,还可以处理操作模式的其他生命周期事件。 + +</li> + <li>当需要显示操作栏时(例如,用户长按视图),请调用 +{@link android.app.Activity#startActionMode startActionMode()}。</li> +</ol> + +<p>例如:</p> + +<ol> + <li>实现 {@link android.view.ActionMode.Callback ActionMode.Callback} 接口: +<pre> +private ActionMode.Callback mActionModeCallback = new ActionMode.Callback() { + + // Called when the action mode is created; startActionMode() was called + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate a menu resource providing context menu items + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context_menu, menu); + return true; + } + + // Called each time the action mode is shown. Always called after onCreateActionMode, but + // may be called multiple times if the mode is invalidated. + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + return false; // Return false if nothing is done + } + + // Called when the user selects a contextual menu item + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + switch (item.getItemId()) { + case R.id.menu_share: + shareCurrentItem(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + // Called when the user exits the action mode + @Override + public void onDestroyActionMode(ActionMode mode) { + mActionMode = null; + } +}; +</pre> + +<p>请注意,这些事件回调与<a href="#options-menu">选项菜单</a>的回调几乎完全相同,只是其中每个回调还会传递与事件相关联的 {@link +android.view.ActionMode} 对象。您可以使用 +{@link +android.view.ActionMode} API 对 CAB 进行各种更改,例如:使用 {@link android.view.ActionMode#setTitle setTitle()} +和 {@link +android.view.ActionMode#setSubtitle setSubtitle()}(这对指示要选择多少个项目非常有用)修改标题和副标题。</p> + +<p>另请注意,操作模式被销毁时,上述示例会将 {@code mActionMode} +变量设置为 null。在下一步中,您将了解如何初始化该变量,以及保存 Activity 或片段中的成员变量有何作用。 +</p> +</li> + + <li>调用 {@link android.app.Activity#startActionMode startActionMode()} +以便适时启用上下文操作模式,例如:响应对 {@link +android.view.View} 的长按操作:</p> + +<pre> +someView.setOnLongClickListener(new View.OnLongClickListener() { + // Called when the user long-clicks on someView + public boolean onLongClick(View view) { + if (mActionMode != null) { + return false; + } + + // Start the CAB using the ActionMode.Callback defined above + mActionMode = getActivity().startActionMode(mActionModeCallback); + view.setSelected(true); + return true; + } +}); +</pre> + +<p>当您调用 {@link android.app.Activity#startActionMode startActionMode()} 时,系统将返回已创建的 +{@link android.view.ActionMode}。通过将其保存在成员变量中,您可以更改上下文操作栏来响应其他事件。 +在上述示例中, +{@link android.view.ActionMode} +用于在启动操作模式之前检查成员是否为空,以确保当 {@link android.view.ActionMode} +实例已激活时不再重建该实例。</p> +</li> +</ol> + + + +<h4 id="CABforListView">在 ListView 或 GridView 中启用批处理上下文操作</h4> + +<p>如果您在 {@link android.widget.ListView} 或 {@link +android.widget.GridView} 中有一组项目(或 {@link android.widget.AbsListView} +的其他扩展),且需要允许用户执行批处理操作,则应:</p> + +<ul> + <li>实现 +{@link android.widget.AbsListView.MultiChoiceModeListener} 接口,并使用 {@link android.widget.AbsListView#setMultiChoiceModeListener +setMultiChoiceModeListener()} 为视图组设置该接口。在侦听器的回调方法中,您既可以为上下文操作栏指定操作,也可以响应操作项目的点击事件,还可以处理从 +{@link android.view.ActionMode.Callback} +接口继承的其他回调。</li> + + <li>使用 {@link +android.widget.AbsListView#CHOICE_MODE_MULTIPLE_MODAL} 参数调用 {@link android.widget.AbsListView#setChoiceMode setChoiceMode()}。</li> +</ul> + +<p>例如:</p> + +<pre> +ListView listView = getListView(); +listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL); +listView.setMultiChoiceModeListener(new MultiChoiceModeListener() { + + @Override + public void onItemCheckedStateChanged(ActionMode mode, int position, + long id, boolean checked) { + // Here you can do something when items are selected/de-selected, + // such as update the title in the CAB + } + + @Override + public boolean onActionItemClicked(ActionMode mode, MenuItem item) { + // Respond to clicks on the actions in the CAB + switch (item.getItemId()) { + case R.id.menu_delete: + deleteSelectedItems(); + mode.finish(); // Action picked, so close the CAB + return true; + default: + return false; + } + } + + @Override + public boolean onCreateActionMode(ActionMode mode, Menu menu) { + // Inflate the menu for the CAB + MenuInflater inflater = mode.getMenuInflater(); + inflater.inflate(R.menu.context, menu); + return true; + } + + @Override + public void onDestroyActionMode(ActionMode mode) { + // Here you can make any necessary updates to the activity when + // the CAB is removed. By default, selected items are deselected/unchecked. + } + + @Override + public boolean onPrepareActionMode(ActionMode mode, Menu menu) { + // Here you can perform updates to the CAB due to + // an {@link android.view.ActionMode#invalidate} request + return false; + } +}); +</pre> + +<p>就这么简单。现在,当用户通过长按选择项目时,系统即会调用 {@link +android.widget.AbsListView.MultiChoiceModeListener#onCreateActionMode onCreateActionMode()} +方法,并显示包含指定操作的上下文操作栏。当上下文操作栏可见时,用户可以选择其他项目。 +</p> + +<p>在某些情况下,如果上下文操作提供常用的操作项目,则您可能需要添加一个复选框或类似的 +UI +元素来支持用户选择项目,这是因为他们可能没有发现长按行为。用户选中该复选框时,您可以通过使用 +{@link android.widget.AbsListView#setItemChecked setItemChecked()} +将相应的列表项设置为选中状态,以此调用上下文操作模式。</p> + + + + +<h2 id="PopupMenu">创建弹出菜单</h2> + +<div class="figure" style="width:220px"> +<img src="{@docRoot}images/ui/popupmenu.png" alt="" /> +<p><strong>图 4. </strong>Gmail 应用中的弹出菜单,锚定到右上角的溢出按钮。 +</p> +</div> + +<p>{@link android.widget.PopupMenu} +是锚定到 {@link android.view.View} 的模态菜单。如果空间足够,它将显示在定位视图下方,否则显示在其上方。它适用于:</p> +<ul> + <li>为与特定内容确切相关的操作提供溢出样式菜单(例如,Gmail +的电子邮件标头,如图 4 所示)。<em></em> + <p class="note"><strong>注:</strong>这与上下文菜单不同,后者通常用于影响所选内容的操作。 +<em></em>对于影响所选内容的操作,请使用<a href="#CAB">上下文操作模式</a>或<a href="#FloatingContextMenu">浮动上下文菜单</a>。 +</p></li> + <li>提供命令语句的另一部分(例如,标记为“添加”且使用不同的“添加”选项生成弹出菜单的按钮)。 +</li> + <li>提供类似于 +{@link android.widget.Spinner} 且不保留永久选择的下拉菜单。</li> +</ul> + + +<p class="note"><strong>注:</strong>{@link android.widget.PopupMenu} 在 API +级别 11 及更高版本中可用。</p> + +<p>如果<a href="#xml">使用 XML 定义菜单</a>,则显示弹出菜单的方法如下:</p> +<ol> + <li>实例化 +{@link android.widget.PopupMenu} 及其构造函数,该函数将提取当前应用的 {@link android.content.Context} 以及菜单应锚定到的 +{@link android.view.View}。</li> + <li>使用 {@link android.view.MenuInflater} 将菜单资源扩充到 {@link +android.widget.PopupMenu#getMenu() PopupMenu.getMenu()} 返回的 {@link +android.view.Menu} 对象中。在 API 级别 14 及更高版本中,您可以改为使用 +{@link android.widget.PopupMenu#inflate PopupMenu.inflate()}。</li> + <li>调用 {@link android.widget.PopupMenu#show() PopupMenu.show()}。</li> +</ol> + +<p>例如,以下是一个使用 +{@link android.R.attr#onClick android:onClick} 属性显示弹出菜单的按钮:</p> + +<pre> +<ImageButton + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:src="@drawable/ic_overflow_holo_dark" + android:contentDescription="@string/descr_overflow_button" + android:onClick="showPopup" /> +</pre> + +<p>稍后,Activity 可按照如下方式显示弹出菜单:</p> + +<pre> +public void showPopup(View v) { + PopupMenu popup = new PopupMenu(this, v); + MenuInflater inflater = popup.getMenuInflater(); + inflater.inflate(R.menu.actions, popup.getMenu()); + popup.show(); +} +</pre> + +<p>在 API 级别 14 及更高版本中,您可以将两行合在一起,使用 {@link +android.widget.PopupMenu#inflate PopupMenu.inflate()} 扩充菜单。</p> + +<p>当用户选择项目或触摸菜单以外的区域时,系统即会清除此菜单。 +您可使用 {@link +android.widget.PopupMenu.OnDismissListener} 侦听清除事件。</p> + +<h3 id="PopupEvents">处理点击事件</h3> + +<p>要在用户选择菜单项时执行操作,您必须实现 +{@link +android.widget.PopupMenu.OnMenuItemClickListener} 接口,并通过调用 {@link android.widget.PopupMenu#setOnMenuItemClickListener +setOnMenuItemclickListener()} 将其注册到 {@link +android.widget.PopupMenu}。用户选择项目时,系统会在接口中调用 {@link +android.widget.PopupMenu.OnMenuItemClickListener#onMenuItemClick onMenuItemClick()} +回调。</p> + +<p>例如:</p> + +<pre> +public void showMenu(View v) { + PopupMenu popup = new PopupMenu(this, v); + + // This activity implements OnMenuItemClickListener + popup.setOnMenuItemClickListener(this); + popup.inflate(R.menu.actions); + popup.show(); +} + +@Override +public boolean onMenuItemClick(MenuItem item) { + switch (item.getItemId()) { + case R.id.archive: + archive(item); + return true; + case R.id.delete: + delete(item); + return true; + default: + return false; + } +} +</pre> + + +<h2 id="groups">创建菜单组</h2> + +<p>菜单组是指一系列具有某些共同特征的菜单项。通过菜单组,您可以: +</p> +<ul> + <li>使用 {@link android.view.Menu#setGroupVisible(int,boolean) +setGroupVisible()} 显示或隐藏所有项目</li> + <li>使用 {@link android.view.Menu#setGroupEnabled(int,boolean) +setGroupEnabled()} 启用或禁用所有项目</li> + <li>使用 {@link +android.view.Menu#setGroupCheckable(int,boolean,boolean) setGroupCheckable()} 指定所有项目是否可选中</li> +</ul> + +<p>通过将 {@code <item>} 元素嵌套在菜单资源中的 {@code <group>} +元素内,或者通过使用 {@link +android.view.Menu#add(int,int,int,int) add()} 方法指定组 ID,您可以创建组。</p> + +<p>以下是包含组的菜单资源示例:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <item android:id="@+id/menu_save" + android:icon="@drawable/menu_save" + android:title="@string/menu_save" /> + <!-- menu group --> + <group android:id="@+id/group_delete"> + <item android:id="@+id/menu_archive" + android:title="@string/menu_archive" /> + <item android:id="@+id/menu_delete" + android:title="@string/menu_delete" /> + </group> +</menu> +</pre> + +<p>组中的项目出现在与第一项相同的级别,即:菜单中的所有三项均为同级。 +但是,您可以通过引用组 ID +并使用上面列出的方法,修改组中两项的特征。此外,系统也绝不会分离已分组的项目。 +例如,如果为每个项目声明 +{@code +android:showAsAction="ifRoom"},则它们会同时显示在操作栏或操作溢出菜单中。</p> + + +<h3 id="checkable">使用可选中的菜单项</h3> + +<div class="figure" style="width:200px"> + <img src="{@docRoot}images/radio_buttons.png" height="333" alt="" /> + <p class="img-caption"><strong>图 5. </strong>含可选中项目的子菜单的屏幕截图。 +</p> +</div> + +<p>作为启用/禁用选项的接口,菜单非常实用,既可针对独立选项使用复选框,也可针对互斥选项组使用单选按钮。 + +图 5 显示了一个子菜单,其中的项目可使用单选按钮选中。 +</p> + +<p class="note"><strong>注:</strong>“图标菜单”(在选项菜单中)的菜单项无法显示复选框或单选按钮。 +如果您选择使“图标菜单”中的项目可选中,则必须在选中状态每次发生变化时交换图标和/或文本,手动指出该状态。 + +</p> + +<p>您可以使用 {@code <item>} 元素中的 {@code +android:checkable} 属性为各个菜单项定义可选中的行为,或者使用 {@code <group>} 元素中的 +{@code android:checkableBehavior} 属性为整个组定义可选中的行为。例如,此菜单组中的所有项目均可使用单选按钮选中: +</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<menu xmlns:android="http://schemas.android.com/apk/res/android"> + <group android:checkableBehavior="single"> + <item android:id="@+id/red" + android:title="@string/red" /> + <item android:id="@+id/blue" + android:title="@string/blue" /> + </group> +</menu> +</pre> + +<p>{@code android:checkableBehavior} 属性接受以下任一选项: +<dl> + <dt>{@code single}</dt> + <dd>组中只有一个项目可以选中(单选按钮)</dd> + <dt>{@code all}</dt> + <dd>所有项目均可选中(复选框)</dd> + <dt>{@code none}</dt> + <dd>所有项目均无法选中</dd> +</dl> + +<p>您可以使用 {@code <item>} +元素中的 {@code android:checked} 属性将默认的选中状态应用于项目,并可使用 {@link +android.view.MenuItem#setChecked(boolean) setChecked()} 方法在代码中更改此默认状态。</p> + +<p>选择可选中项目后,系统将调用所选项目的相应回调方法(例如,{@link android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()})。 +此时,您必须设置复选框的状态,因为复选框或单选按钮不会自动更改其状态。 + +您可以使用 {@link android.view.MenuItem#isChecked()} +查询项目的当前状态(正如用户选择该项目之前一样),然后使用 +{@link android.view.MenuItem#setChecked(boolean) setChecked()} 设置选中状态。例如:</p> + +<pre> +@Override +public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.vibrate: + case R.id.dont_vibrate: + if (item.isChecked()) item.setChecked(false); + else item.setChecked(true); + return true; + default: + return super.onOptionsItemSelected(item); + } +} +</pre> + +<p>如果未通过这种方式设置选中状态,则项目的可见状态(复选框或单选按钮)不会因为用户选择它而发生变化。 + +如果已设置该状态,则 Activity 会保留项目的选中状态。这样一来,当用户稍后打开菜单时,您设置的选中状态将会可见。 + +</p> + +<p class="note"><strong>注:</strong>可选中菜单项的使用往往因会话而异,且在应用销毁后不予保存。 + +如果您想为用户保存某些应用设置,则应使用<a href="{@docRoot}guide/topics/data/data-storage.html#pref">共享首选项</a>存储数据。 +</p> + + + +<h2 id="intents">添加基于 Intent 的菜单项</h2> + +<p>有时,您希望菜单项通过使用 +{@link android.content.Intent} 启动 Activity(无论该 Activity 是位于您的应用还是其他应用中)。如果您知道自己要使用的 Intent,且具有启动 Intent 的特定菜单项,则可在相应的 +on-item-selected 回调方法(例如,{@link +android.app.Activity#onOptionsItemSelected(MenuItem) onOptionsItemSelected()} 回调)期间使用 +{@link android.app.Activity#startActivity(Intent) startActivity()} +执行 Intent。</p> + +<p>但是,如果不确定用户的设备是否包含可处理 Intent 的应用,则添加调用 Intent 的菜单项可能会导致该菜单项无法正常工作,这是因为 Intent 可能无法解析为 Activity。 + + +为了解决这一问题,当 +Android 在设备上找到可处理 Intent 的 Activity 时,则允许您向菜单动态添加菜单项。</p> + +<p>要根据接受 Intent 的可用 Activity 添加菜单项,请执行以下操作:</p> +<ol> + <li>使用类别 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} 和/或 +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} 以及任何其他要求定义 Intent。</li> + <li>调用 {@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +Menu.addIntentOptions()}。Android +随后即会搜索能够执行 Intent 的所有应用,并将其添加到菜单中。</li> +</ol> + +<p>如果未安装可处理 Intent 的应用,则不会添加任何菜单项。 +</p> + +<p class="note"><strong>注:</strong> +{@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} +用于处理屏幕上当前所选的元素。因此,只有在 {@link +android.app.Activity#onCreateContextMenu(ContextMenu,View,ContextMenuInfo) +onCreateContextMenu()} 中创建菜单时,才能使用它。</p> + +<p>例如:</p> + +<pre> +@Override +public boolean onCreateOptionsMenu(Menu menu){ + super.onCreateOptionsMenu(menu); + + // Create an Intent that describes the requirements to fulfill, to be included + // in our menu. The offering app must include a category value of Intent.CATEGORY_ALTERNATIVE. + Intent intent = new Intent(null, dataUri); + intent.addCategory(Intent.CATEGORY_ALTERNATIVE); + + // Search and populate the menu with acceptable offering applications. + menu.addIntentOptions( + R.id.intent_group, // Menu group to which new items will be added + 0, // Unique item ID (none) + 0, // Order for the items (none) + this.getComponentName(), // The current activity name + null, // Specific items to place first (none) + intent, // Intent created above that describes our requirements + 0, // Additional flags to control items (none) + null); // Array of MenuItems that correlate to specific items (none) + + return true; +}</pre> + +<p>如果发现 Activity 提供的 Intent 过滤器与定义的 Intent 匹配,则会添加菜单项,并使用 Intent 过滤器 +<code>android:label</code> +中的值作为菜单项标题,使用应用图标作为菜单项图标。{@link android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} +方法将返回已添加的菜单项数量。</p> + +<p class="note"><strong>注:</strong>调用 +{@link +android.view.Menu#addIntentOptions(int,int,int,ComponentName,Intent[],Intent,int,MenuItem[]) +addIntentOptions()} 方法时,它将使用第一个参数中指定的菜单组替代所有菜单项。</p> + + +<h3 id="AllowingToAdd">允许将 Activity 添加到其他菜单中</h3> + +<p>此外,您还可以为其他应用提供您的 Activity 服务,以便您的应用能够包含在其他应用的菜单中(与上述角色相反)。 +</p> + +<p>要包含在其他应用菜单中,您需要按常规方式定义 Intent 过滤器,但请确保为 Intent 过滤器类别添加 +{@link android.content.Intent#CATEGORY_ALTERNATIVE} +和/或 {@link android.content.Intent#CATEGORY_SELECTED_ALTERNATIVE} +值。例如:</p> +<pre> +<intent-filter label="@string/resize_image"> + ... + <category android:name="android.intent.category.ALTERNATIVE" /> + <category android:name="android.intent.category.SELECTED_ALTERNATIVE" /> + ... +</intent-filter> +</pre> + +<p>请仔细阅读<a href="/guide/components/intents-filters.html"> Intent 和 Intent 过滤器</a>文档中更多有关编写 Intent 过滤器的内容。 +</p> + +<p>有关使用此方法的应用示例,请参阅<a href="{@docRoot}resources/samples/NotePad/src/com/example/android/notepad/NoteEditor.html">记事本</a>示例代码。 + +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd new file mode 100644 index 000000000000..c0bd74cdd3ac --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/notifiers/notifications.jd @@ -0,0 +1,979 @@ +page.title=通知 +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> +<h2>本文内容</h2> +<ol> + <li><a href="#Design">设计注意事项</a></li> + <li><a href="#CreateNotification">创建通知</a> + <ol> + <li><a href="#Required">必需的通知内容</a></li> + <li><a href="#Optional">可选通知内容和设置</a></li> + <li><a href="#Actions">通知操作</a></li> + <li><a href="#Priority">通知优先级</a></li> + <li><a href="#SimpleNotification">创建简单通知</a></li> + <li><a href="#ApplyStyle">将扩展布局应用于通知</a></li> + <li><a href="#Compatibility">处理兼容性</a></li> + </ol> + </li> + <li><a href="#Managing">管理通知</a> + <ol> + <li><a href="#Updating">更新通知</a></li> + <li><a href="#Removing">删除通知</a></li> + </ol> + </li> + <li><a href="#NotificationResponse">启动 Activity 时保留导航</a> + <ol> + <li><a href="#DirectEntry">设置常规 Activity PendingIntent</a></li> + <li><a href="#ExtendedNotification">设置特殊 Activity PendingIntent</a></li> + </ol> + </li> + <li><a href="#Progress">在通知中显示进度</a> + <ol> + <li><a href="#FixedProgress">显示持续时间固定的进度指示器</a></li> + <li><a href="#ActivityIndicator">显示持续 Activity 指示器</a></li> + </ol> + </li> + <li><a href="#metadata">通知元数据</a></li> + <li><a href="#Heads-up">浮动通知</a></li> + <li><a href="#lockscreenNotification">锁定屏幕通知</a></li> + <ol> + <li><a href="#visibility">设置可见性</a></li> + <li><a href="#controllingMedia">在锁定屏幕上控制媒体播放</a></li> + </ol> + <li><a href="#CustomNotification">自定义通知布局</a></li> +</ol> + + <h2>关键类</h2> + <ol> + <li>{@link android.app.NotificationManager}</li> + <li>{@link android.support.v4.app.NotificationCompat}</li> + </ol> + <h2>视频</h2> + <ol> + <li> + <a href="http://www.youtube.com/watch?v=Yc8YrVc47TI&feature=player_detailpage#t=1672s"> + 4.1 中的通知</a> + </li> + </ol> +<h2>另请参阅</h2> +<ol> + <li> + <a href="{@docRoot}design/patterns/notifications.html">Android 设计:通知</a> + </li> +</ol> +</div> +</div> +<p> + 通知是您可以在应用的常规 +UI +外部向用户显示的消息。当您告知系统发出通知时,它将先以图标的形式显示在<strong>通知区域</strong>中。用户可以打开<strong>抽屉式通知栏</strong>查看通知的详细信息。 +通知区域和抽屉式通知栏均是由系统控制的区域,用户可以随时查看。 + +</p> +<img id="figure1" src="{@docRoot}images/ui/notifications/notification_area.png" height="" alt="" /> +<p class="img-caption"> + <strong>图 1. </strong>通知区域中的通知。 +</p> +<img id="figure2" src="{@docRoot}images/ui/notifications/notification_drawer.png" width="280px" alt="" /> +<p class="img-caption"> + <strong>图 2. </strong>抽屉式通知栏中的通知。 +</p> + +<p class="note"><strong>注:</strong>除非特别注明,否则本指南均引用版本 +4 <a href="{@docRoot}tools/support-library/index.html">支持库</a>中的 {@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +类。Android +3.0(API 级别 11)中已添加类 +{@link android.app.Notification.Builder Notification.Builder}。</p> + +<h2 id="Design">设计注意事项</h2> + +<p>作为 Android 用户界面的一个重要组成部分,通知具有自己的设计指导方针。Android +5.0(API 级别 21)中引入的材料设计变更尤为重要,您应查阅<a href="{@docRoot}training/material/index.html">材料设计</a>培训资料了解详细信息。 + +要了解如何设计通知及其交互,请阅读<a href="{@docRoot}design/patterns/notifications.html">通知</a>设计指南。 +</p> + +<h2 id="CreateNotification">创建通知</h2> + +<p>您可以在 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +对象中为通知指定 UI 信息和操作。要创建通知,请调用 +{@link android.support.v4.app.NotificationCompat.Builder#build NotificationCompat.Builder.build()},它将返回包含您的具体规范的 +{@link android.app.Notification} 对象。要发出通知,请通过调用 +{@link android.app.NotificationManager#notify NotificationManager.notify()} 将 {@link android.app.Notification} +对象传递给系统。</p> + +<h3 id="Required">必需的通知内容</h3> +<p> + {@link android.app.Notification} 对象必须包含以下内容:<em></em> +</p> +<ul> + <li> + 小图标,由 +{@link android.support.v4.app.NotificationCompat.Builder#setSmallIcon setSmallIcon()} 设置 + </li> + <li> + 标题,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} 设置 + </li> + <li> + 详细文本,由 +{@link android.support.v4.app.NotificationCompat.Builder#setContentText setContentText()} 设置 + </li> +</ul> +<h3 id="Optional">可选通知内容和设置</h3> +<p> + 所有其他通知设置和内容都是可选的。如需了解有关它们的更多详情,请参阅 +{@link android.support.v4.app.NotificationCompat.Builder} 参考文档。 +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Actions">通知操作</h3> +<p> + 尽管通知操作都是可选的,但是您至少应向通知添加一个操作。 + 操作允许用户直接从通知转到应用中的 +{@link android.app.Activity},他们可在其中查看一个或多个事件或执行进一步的操作。 + +</p> +<p> + 一个通知可以提供多个操作。您应该始终定义一个当用户点击通知时会触发的操作;通常,此操作会在应用中打开 +{@link android.app.Activity}。 +您也可以向通知添加按钮来执行其他操作,例如,暂停闹铃或立即答复短信;此功能自 +Android +4.1 起可用。如果使用其他操作按钮,则您还必须使这些按钮的功能在应用的 +{@link android.app.Activity} +中可用;请参阅<a href="#Compatibility">处理兼容性</a>部分,以了解更多详情。 +</p> +<p> + 在 {@link android.app.Notification} 内部,操作本身由 +{@link android.app.PendingIntent} 定义,后者包含在应用中启动 +{@link android.app.Activity} +的 {@link android.content.Intent}。要将 +{@link android.app.PendingIntent} 与手势相关联,请调用 +{@link android.support.v4.app.NotificationCompat.Builder} 的适当方法。例如,如果您要在用户点击抽屉式通知栏中的通知文本时启动 +{@link android.app.Activity},则可通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent setContentIntent()} +来添加 {@link android.app.PendingIntent}。 +</p> +<p> + 在用户点击通知时启动 {@link android.app.Activity} +是最常见的操作场景。此外,您还可以在用户清除通知时启动 +{@link android.app.Activity}。在 Android 4.1 及更高版本中,您可以通过操作按钮启动 +{@link android.app.Activity}。如需了解更多信息,请阅读参考指南的 +{@link android.support.v4.app.NotificationCompat.Builder} 部分。 +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Priority">通知优先级</h3> +<p> + 您可以根据需要设置通知的优先级。优先级充当一个提示,提醒设备 +UI 应该如何显示通知。 + 要设置通知的优先级,请调用 {@link + android.support.v4.app.NotificationCompat.Builder#setPriority(int) + NotificationCompat.Builder.setPriority()} 并传入一个 {@link + android.support.v4.app.NotificationCompat} 优先级常量。有五个优先级别,范围从 {@link + android.support.v4.app.NotificationCompat#PRIORITY_MIN} (-2) 到 {@link + android.support.v4.app.NotificationCompat#PRIORITY_MAX} (2);如果未设置,则优先级默认为 {@link + android.support.v4.app.NotificationCompat#PRIORITY_DEFAULT} (0)。 + + +</p> +<p> 有关设置适当优先级别的信息,请参阅<a href="{@docRoot}design/patterns/notifications.html">通知</a>设计指南中的“正确设置和管理通知优先级”。 + + +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="SimpleNotification">创建简单通知</h3> +<p> + 以下代码段说明了一个指定某项 Activity 在用户点击通知时打开的简单通知。 +请注意,该代码将创建 +{@link android.support.v4.app.TaskStackBuilder} 对象并使用它来为操作创建 +{@link android.app.PendingIntent}。<a href="#NotificationResponse">启动 Activity 时保留导航</a>部分对此模式做了更详尽的阐述: + + +</p> +<pre> +NotificationCompat.Builder mBuilder = + new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification_icon) + .setContentTitle("My notification") + .setContentText("Hello World!"); +// Creates an explicit intent for an Activity in your app +Intent resultIntent = new Intent(this, ResultActivity.class); + +// The stack builder object will contain an artificial back stack for the +// started Activity. +// This ensures that navigating backward from the Activity leads out of +// your application to the Home screen. +TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); +// Adds the back stack for the Intent (but not the Intent itself) +stackBuilder.addParentStack(ResultActivity.class); +// Adds the Intent that starts the Activity to the top of the stack +stackBuilder.addNextIntent(resultIntent); +PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent( + 0, + PendingIntent.FLAG_UPDATE_CURRENT + ); +mBuilder.setContentIntent(resultPendingIntent); +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// mId allows you to update the notification later on. +mNotificationManager.notify(mId, mBuilder.build()); +</pre> +<p>就这么简单。您的用户现已收到通知。</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ApplyStyle">将扩展布局应用于通知</h3> +<p> + 要使通知出现在展开视图中,请先创建一个带有所需普通视图选项的 +{@link android.support.v4.app.NotificationCompat.Builder} +对象。接下来,调用以扩展布局对象作为其参数的 {@link android.support.v4.app.NotificationCompat.Builder#setStyle + Builder.setStyle()}。 +</p> +<p> + 请记住,扩展通知在 Android 4.1 之前的平台上不可用。要了解如何处理针对 +Android 4.1 及更早版本平台的通知,请阅读<a href="#Compatibility">处理兼容性</a>部分。 + +</p> +<p> + 例如,以下代码段演示了如何更改在前面的代码段中创建的通知,以便使用扩展布局: + +</p> +<pre> +NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this) + .setSmallIcon(R.drawable.notification_icon) + .setContentTitle("Event tracker") + .setContentText("Events received") +NotificationCompat.InboxStyle inboxStyle = + new NotificationCompat.InboxStyle(); +String[] events = new String[6]; +// Sets a title for the Inbox in expanded layout +inboxStyle.setBigContentTitle("Event tracker details:"); +... +// Moves events into the expanded layout +for (int i=0; i < events.length; i++) { + + inboxStyle.addLine(events[i]); +} +// Moves the expanded layout object into the notification object. +mBuilder.setStyle(inBoxStyle); +... +// Issue the notification here. +</pre> + +<h3 id="Compatibility">处理兼容性</h3> + +<p> + 并非所有通知功能都可用于某特定版本,即便用于设置这些功能的方法位于支持库类 +{@link android.support.v4.app.NotificationCompat.Builder NotificationCompat.Builder} +中也是如此。 + 例如,依赖于扩展通知的操作按钮仅会显示在 Android +4.1 及更高版本的系统中,这是因为扩展通知本身仅在 +Android 4.1 及更高版本的系统中可用。 +</p> +<p> + 为了确保最佳兼容性,请使用 +{@link android.support.v4.app.NotificationCompat NotificationCompat} 及其子类(特别是 +{@link android.support.v4.app.NotificationCompat.Builder + NotificationCompat.Builder})创建通知。此外,在实现通知时,请遵循以下流程: +</p> +<ol> + <li> + 为所有用户提供通知的全部功能,无论他们使用何种版本的 Android 系统。 +为此,请验证是否可从应用的 +{@link android.app.Activity} 中获得所有功能。要执行此操作,您可能需要添加新的 +{@link android.app.Activity}。 + <p> + 例如,若要使用 +{@link android.support.v4.app.NotificationCompat.Builder#addAction addAction()} 提供停止和启动媒体播放的控件,请先在应用的 +{@link android.app.Activity} +中实现此控件。 + </p> + </li> + <li> + 确保所有用户均可通过点击通知启动 {@link android.app.Activity} 来获得该Activity中的功能。 +为此,请为 +{@link android.app.Activity} +创建 {@link android.app.PendingIntent}。调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 以将 {@link android.app.PendingIntent} 添加到通知。 + </li> + <li> + 现在,将要使用的扩展通知功能添加到通知。请记住,您添加的任何功能还必须在用户点击通知时启动的 +{@link android.app.Activity} +中可用。 + </li> +</ol> + + +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="Managing">管理通知</h2> +<p> + 当您需要为同一类型的事件多次发出同一通知时,应避免创建全新的通知, +而是应考虑通过更改之前通知的某些值和/或为其添加某些值来更新通知。 + +</p> +<p> + 例如,Gmail 通过增加未读消息计数并将每封电子邮件的摘要添加到通知,通知用户收到了新的电子邮件。 +这称为“堆叠”通知;<a href="{@docRoot}design/patterns/notifications.html">通知</a>设计指南对此进行了更详尽的描述。 + + +</p> +<p class="note"> + <strong>注:</strong>此 +Gmail 功能需要“收件箱”扩展布局,该布局是自 Android 4.1 版本起可用的扩展通知功能的一部分。 +</p> +<p> + 下文介绍如何更新和删除通知。 +</p> +<h3 id="Updating">更新通知</h3> +<p> + 要将通知设置为能够更新,请通过调用 +{@link android.app.NotificationManager#notify(int, android.app.Notification) NotificationManager.notify()} 发出带有通知 ID 的通知。 + 要在发出之后更新此通知,请更新或创建 +{@link android.support.v4.app.NotificationCompat.Builder} +对象,从该对象构建 {@link android.app.Notification} 对象,并发出与之前所用 ID 相同的 +{@link android.app.Notification}。如果之前的通知仍然可见,则系统会根据 +{@link android.app.Notification} +对象的内容更新该通知。相反,如果之前的通知已被清除,系统则会创建一个新通知。 + +</p> +<p> + 以下代码段演示了经过更新以反映所发生事件数量的通知。 +它将通知堆叠并显示摘要: +</p> +<pre> +mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// Sets an ID for the notification, so it can be updated +int notifyID = 1; +mNotifyBuilder = new NotificationCompat.Builder(this) + .setContentTitle("New Message") + .setContentText("You've received new messages.") + .setSmallIcon(R.drawable.ic_notify_status) +numMessages = 0; +// Start of a loop that processes data and then notifies the user +... + mNotifyBuilder.setContentText(currentText) + .setNumber(++numMessages); + // Because the ID remains unchanged, the existing notification is + // updated. + mNotificationManager.notify( + notifyID, + mNotifyBuilder.build()); +... +</pre> + +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="Removing">删除通知</h3> +<p> + 除非发生以下情况之一,否则通知仍然可见: +</p> +<ul> + <li> + 用户单独或通过使用“全部清除”清除了该通知(如果通知可以清除)。 + + </li> + <li> + 用户点击通知,且您在创建通知时调用了 +{@link android.support.v4.app.NotificationCompat.Builder#setAutoCancel setAutoCancel()}。 + + </li> + <li> + 您针对特定的通知 ID 调用了 +{@link android.app.NotificationManager#cancel(int) cancel()}。此方法还会删除当前通知。 + </li> + <li> + 您调用了 +{@link android.app.NotificationManager#cancelAll() cancelAll()} 方法,该方法将删除之前发出的所有通知。 + </li> +</ul> +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="NotificationResponse">启动 Activity 时保留导航</h2> +<p> + 从通知中启动 +{@link android.app.Activity} 时,您必须保留用户的预期导航体验。 <i> </i> 点击“返回”应该使用户将应用的正常工作流回退到主屏幕,而点击“最新动态”则应将 + <i> </i> {@link android.app.Activity} 显示为单独的任务。 +要保留导航体验,您应该在全新任务中启动 +{@link android.app.Activity}。如何设置 +{@link android.app.PendingIntent} 以获得全新任务取决于正在启动的 +{@link android.app.Activity} 的性质。一般有两种情况: +</p> +<dl> + <dt> + 常规 Activity + </dt> + <dd> + 您要启动的 +{@link android.app.Activity} 是应用的正常工作流的一部分。在这种情况下,请设置 {@link android.app.PendingIntent} +以启动全新任务并为 +{@link android.app.PendingIntent}提供返回栈,这将重现应用的正常“返回”行为。 <i> </i> + <p> + Gmail 应用中的通知演示了这一点。点击一封电子邮件消息的通知时,您将看到消息具体内容。 +触摸<b>返回</b>将使您从 +Gmail 回退到主屏幕,就好像您是从主屏幕(而不是通知)进入 +Gmail 一样。 + </p> + <p> + 无论您触摸通知时处于哪个应用,都会发生这种情况。 +例如,如果您在 +Gmail 中撰写消息时点击了一封电子邮件的通知,则会立即转到该电子邮件。 <i> </i> + 触摸“返回”会依次转到收件箱和主屏幕,而不是转到您在撰写的邮件。 + + </p> + </dd> + <dt> + 特殊 Activity + </dt> + <dd> + 仅当从通知中启动时,用户才会看到此 {@link android.app.Activity}。 + 从某种意义上说,{@link android.app.Activity} +是通过提供很难显示在通知本身中的信息来扩展通知。对于这种情况,请将 +{@link android.app.PendingIntent} 设置为在全新任务中启动。但是,由于启动的 +{@link android.app.Activity} +不是应用 Activity 流程的一部分,因此无需创建返回栈。点击“返回”仍会将用户带到主屏幕。<i></i> + + </dd> +</dl> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="DirectEntry">设置常规 Activity PendingIntent</h3> +<p> + 要设置可启动直接进入 {@link android.app.Activity} 的 +{@link android.app.PendingIntent},请执行以下步骤: +</p> +<ol> + <li> + 在清单文件中定义应用的 {@link android.app.Activity} 层次结构。 + <ol style="list-style-type: lower-alpha;"> + <li> + 添加对 Android 4.0.3 及更低版本的支持。为此,请通过添加 +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html"><meta-data></a></code> +元素作为 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code>的子项来指定正在启动的 +{@link android.app.Activity} 的父项。 + <p> + 对于此元素,请设置 +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a>="android.support.PARENT_ACTIVITY"</code>。 + 设置 +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#val">android:value</a>="<parent_activity_name>"</code>,其中,<code><parent_activity_name></code> +是父 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +元素的 +<code><a href="{@docRoot}guide/topics/manifest/meta-data-element.html#nm">android:name</a></code> +值。请参阅下面的 XML 示例。 + </p> + </li> + <li> + 同样添加对 Android 4.1 及更高版本的支持。为此,请将 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#parent">android:parentActivityName</a></code> +属性添加到正在启动的 +{@link android.app.Activity} 的 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> 元素中。 + </li> + </ol> + <p> + 最终的 XML 应如下所示: + </p> +<pre> +<activity + android:name=".MainActivity" + android:label="@string/app_name" > + <intent-filter> + <action android:name="android.intent.action.MAIN" /> + <category android:name="android.intent.category.LAUNCHER" /> + </intent-filter> +</activity> +<activity + android:name=".ResultActivity" + android:parentActivityName=".MainActivity"> + <meta-data + android:name="android.support.PARENT_ACTIVITY" + android:value=".MainActivity"/> +</activity> +</pre> + </li> + <li> + 根据可启动 +{@link android.app.Activity} 的 {@link android.content.Intent} 创建返回栈: + <ol style="list-style-type: lower-alpha;"> + <li> + 创建 {@link android.content.Intent} 以启动 {@link android.app.Activity}。 + </li> + <li> + 通过调用 {@link android.app.TaskStackBuilder#create + TaskStackBuilder.create()} 创建堆栈生成器。 + </li> + <li> + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} 将返回栈添加到堆栈生成器。 + 对于在清单文件中所定义层次结构内的每个 +{@link android.app.Activity},返回栈均包含可启动 {@link android.app.Activity} 的 +{@link android.content.Intent} 对象。此方法还会添加一些可在全新任务中启动堆栈的标志。 + + <p class="note"> + <strong>注:</strong>尽管 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} +的参数是对已启动 +{@link android.app.Activity} 的引用,但是方法调用不会添加可启动 {@link android.app.Activity} 的 +{@link android.content.Intent},而是留待下一步进行处理。 + </p> + </li> + <li> + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()},添加可从通知中启动 {@link android.app.Activity} +的 {@link android.content.Intent}。 + 将在第一步中创建的 {@link android.content.Intent} +作为 +{@link android.support.v4.app.TaskStackBuilder#addNextIntent addNextIntent()} 的参数传递。 + </li> + <li> + 如需,请通过调用 {@link android.support.v4.app.TaskStackBuilder#editIntentAt + TaskStackBuilder.editIntentAt()} 向堆栈中的 {@link android.content.Intent} +对象添加参数。有时,需要确保目标 {@link android.app.Activity} 在用户使用“返回”导航回它时会显示有意义的数据。 + + <i> </i> + </li> + <li> + 通过调用 +{@link android.support.v4.app.TaskStackBuilder#getPendingIntent getPendingIntent()} 获得此返回栈的 {@link android.app.PendingIntent}。 + 然后,您可以使用此 {@link android.app.PendingIntent} 作为 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 的参数。 + </li> + </ol> + </li> +</ol> +<p> + 以下代码段演示了该流程: +</p> +<pre> +... +Intent resultIntent = new Intent(this, ResultActivity.class); +TaskStackBuilder stackBuilder = TaskStackBuilder.create(this); +// Adds the back stack +stackBuilder.addParentStack(ResultActivity.class); +// Adds the Intent to the top of the stack +stackBuilder.addNextIntent(resultIntent); +// Gets a PendingIntent containing the entire back stack +PendingIntent resultPendingIntent = + stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); +... +NotificationCompat.Builder builder = new NotificationCompat.Builder(this); +builder.setContentIntent(resultPendingIntent); +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +mNotificationManager.notify(id, builder.build()); +</pre> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ExtendedNotification">设置特殊 Activity PendingIntent</h3> +<p> + 下文介绍如何设置特殊 Activity +{@link android.app.PendingIntent}。 +</p> +<p> + 特殊 +{@link android.app.Activity} 无需返回栈,因此您不必在清单文件中定义其 +{@link android.app.Activity} 层次结构,也不必调用 +{@link android.support.v4.app.TaskStackBuilder#addParentStack addParentStack()} +来构建返回栈。取而代之的是,您可使用清单文件设置 +{@link android.app.Activity} 任务选项,并通过调用 {@link android.app.PendingIntent#getActivity getActivity()} +创建 {@link android.app.PendingIntent}: +</p> +<ol> + <li> + 在清单文件中,将以下属性添加到 {@link android.app.Activity} 的 +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html"><activity></a></code> +元素 + <dl> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#nm">android:name</a>="<i>activityclass</i>"</code> + </dt> + <dd> + Activity 的完全限定类名。 + </dd> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#aff">android:taskAffinity</a>=""</code> + </dt> + <dd> + 与您在代码中设置的 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} +标志相结合,这可确保此 {@link android.app.Activity} +不会进入应用的默认任务。任何具有应用默认关联的现有任务均不受影响。 + + </dd> + <dt> +<code><a href="{@docRoot}guide/topics/manifest/activity-element.html#exclude">android:excludeFromRecents</a>="true"</code> + </dt> + <dd> + <i> </i>将新任务从“最新动态”中排除,这样用户就不会在无意中导航回它。 + + </dd> + </dl> + <p> + 以下代码段显示了该元素: + </p> +<pre> +<activity + android:name=".ResultActivity" +... + android:launchMode="singleTask" + android:taskAffinity="" + android:excludeFromRecents="true"> +</activity> +... +</pre> + </li> + <li> + 构建并发出通知: + <ol style="list-style-type: lower-alpha;"> + <li> + 创建可启动 {@link android.app.Activity}的 +{@link android.content.Intent}。 + </li> + <li> + 通过使用 +{@link android.content.Intent#FLAG_ACTIVITY_NEW_TASK FLAG_ACTIVITY_NEW_TASK} +和 +{@link android.content.Intent#FLAG_ACTIVITY_CLEAR_TASK FLAG_ACTIVITY_CLEAR_TASK} 标志调用 +{@link android.content.Intent#setFlags setFlags()},将 {@link android.app.Activity} 设置为在新的空任务中启动。 + </li> + <li> + 为 {@link android.content.Intent} 设置所需的任何其他选项。 + </li> + <li> + 通过调用 +{@link android.app.PendingIntent#getActivity getActivity()} 从 {@link android.content.Intent} 中创建 {@link android.app.PendingIntent}。 + 然后,您可以使用此 {@link android.app.PendingIntent} 作为 +{@link android.support.v4.app.NotificationCompat.Builder#setContentIntent + setContentIntent()} 的参数。 + </li> + </ol> + <p> + 以下代码段演示了该流程: + </p> +<pre> +// Instantiate a Builder object. +NotificationCompat.Builder builder = new NotificationCompat.Builder(this); +// Creates an Intent for the Activity +Intent notifyIntent = + new Intent(this, ResultActivity.class); +// Sets the Activity to start in a new, empty task +notifyIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK + | Intent.FLAG_ACTIVITY_CLEAR_TASK); +// Creates the PendingIntent +PendingIntent notifyPendingIntent = + PendingIntent.getActivity( + this, + 0, + notifyIntent, + PendingIntent.FLAG_UPDATE_CURRENT +); + +// Puts the PendingIntent into the notification builder +builder.setContentIntent(notifyPendingIntent); +// Notifications are issued by sending them to the +// NotificationManager system service. +NotificationManager mNotificationManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +// Builds an anonymous Notification object from the builder, and +// passes it to the NotificationManager +mNotificationManager.notify(id, builder.build()); +</pre> + </li> +</ol> +<!-- ------------------------------------------------------------------------------------------ --> +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="Progress">在通知中显示进度</h2> +<p> + 通知可能包括动画形式的进度指示器,向用户显示正在进行的操作状态。 +如果您可以估计操作所需的时间以及任意时刻的完成进度,则使用“限定”形式的指示器(进度栏)。 + +如果无法估计操作的时长,则使用“非限定”形式的指示器(Activity 指示器)。 + +</p> +<p> + 平台的 +{@link android.widget.ProgressBar} 类实现中显示有进度指示器。 +</p> +<p> + 要在 Android 4.0 及更高版本的平台上使用进度指示器,需调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()}。对于早期版本,您必须创建包括 {@link android.widget.ProgressBar} +视图的自定义通知布局。 + +</p> +<p> + 下文介绍如何使用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} 在通知中显示进度。 +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="FixedProgress">显示持续时间固定的进度指示器</h3> +<p> + 要显示限定形式的进度栏,请通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(max, progress, false)} 将进度栏添加到通知,然后发出通知。随着操作继续进行,递增 +<code>progress</code> 并更新通知。操作结束时, +<code>progress</code> 应该等于 <code>max</code>。调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress()} +的常见方法是将 <code>max</code> 设置为 100,然后将 <code>progress</code> +作为操作的“完成百分比”值递增。 +</p> +<p> + 您可以在操作完成后仍保留显示进度栏,也可以将其删除。无论哪种情况,都请记住更新通知文本以显示操作已完成。 + + 要删除进度栏,请调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress + setProgress(0, 0, false)}。例如: +</p> +<pre> +... +mNotifyManager = + (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); +mBuilder = new NotificationCompat.Builder(this); +mBuilder.setContentTitle("Picture Download") + .setContentText("Download in progress") + .setSmallIcon(R.drawable.ic_notification); +// Start a lengthy operation in a background thread +new Thread( + new Runnable() { + @Override + public void run() { + int incr; + // Do the "lengthy" operation 20 times + for (incr = 0; incr <= 100; incr+=5) { + // Sets the progress indicator to a max value, the + // current completion percentage, and "determinate" + // state + mBuilder.setProgress(100, incr, false); + // Displays the progress bar for the first time. + mNotifyManager.notify(0, mBuilder.build()); + // Sleeps the thread, simulating an operation + // that takes time + try { + // Sleep for 5 seconds + Thread.sleep(5*1000); + } catch (InterruptedException e) { + Log.d(TAG, "sleep failure"); + } + } + // When the loop is finished, updates the notification + mBuilder.setContentText("Download complete") + // Removes the progress bar + .setProgress(0,0,false); + mNotifyManager.notify(ID, mBuilder.build()); + } + } +// Starts the thread by calling the run() method in its Runnable +).start(); +</pre> + +<!-- ------------------------------------------------------------------------------------------ --> +<h3 id="ActivityIndicator">显示持续 Activity 指示器</h3> +<p> + 要显示非限定形式的 Activity 指示器,请使用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, true)} +将其添加到通知(忽略前两个参数),然后发出通知。这样一来,指示器的样式就与进度栏相同,只是其动画还在继续。 + +</p> +<p> + 在操作开始之际发出通知。除非您修改通知,否则动画将一直运行。 +操作完成后,调用 +{@link android.support.v4.app.NotificationCompat.Builder#setProgress setProgress(0, 0, false)},然后更新通知以删除 Activity 指示器。 + + 请务必这样做;否则,即使操作完成,动画仍将运行。同时,请记得更改通知文本,以表明操作已完成。 + +</p> +<p> + 要了解 Activity 指示器的工作方式,请参阅上述代码段。找到以下几行: +</p> +<pre> +// Sets the progress indicator to a max value, the current completion +// percentage, and "determinate" state +mBuilder.setProgress(100, incr, false); +// Issues the notification +mNotifyManager.notify(0, mBuilder.build()); +</pre> +<p> + 将找到的这几行替换为以下几行: +</p> +<pre> + // Sets an activity indicator for an operation of indeterminate length +mBuilder.setProgress(0, 0, true); +// Issues the notification +mNotifyManager.notify(0, mBuilder.build()); +</pre> + +<h2 id="metadata">通知元数据</h2> + +<p>通知可根据您使用以下 +{@link android.support.v4.app.NotificationCompat.Builder} 方法分配的元数据进行排序:</p> + +<ul> + <li>当设备处于“优先”模式时,{@link android.support.v4.app.NotificationCompat.Builder#setCategory(java.lang.String) setCategory()} +会告知系统如何处理应用通知(例如,通知代表传入呼叫、即时消息还是闹铃)。 +</li> + <li>如果优先级字段设置为 +{@code PRIORITY_MAX} 或 {@code PRIORITY_HIGH} 的通知还有声音或振动,则 {@link android.support.v4.app.NotificationCompat.Builder#setPriority(int) setPriority()} +会将其显示在小型浮动窗口中。</li> + <li>{@link android.support.v4.app.NotificationCompat.Builder#addPerson(java.lang.String) addPerson()} +允许您向通知添加人员名单。您的应用可以使用此名单指示系统将指定人员发出的通知归成一组,或者将这些人员发出的通知视为更重要的通知。 + +</li> +</ul> + +<div class="figure" style="width:230px"> + <img src="{@docRoot}images/ui/notifications/heads-up.png" alt="" width="" height="" id="figure3" /> + <p class="img-caption"> + <strong>图 3. </strong>显示浮动通知的全屏 Activity + </p> +</div> + +<h2 id="Heads-up">浮动通知</h2> + +<p>对于 Android 5.0(API 级别 21),当设备处于活动状态时(即,设备未锁定且其屏幕已打开),通知可以显示在小型浮动窗口中(也称为“浮动通知”)。<em></em> + +这些通知看上去类似于精简版的通知,只是浮动通知还显示操作按钮。 + +用户可以在不离开当前应用的情况下处理或清除浮动通知。 +</p> + +<p>可能触发浮动通知的条件示例包括:</p> + +<ul> + <li>用户的 Activity 处于全屏模式中(应用使用 +{@link android.app.Notification#fullScreenIntent}),或者</li> + <li>通知具有较高的优先级并使用铃声或振动 +</li> +</ul> + +<h2 id="lockscreenNotification">锁定屏幕通知</h2> + +<p>随着 +Android 5.0(API 级别 21)的发布,通知现在还可显示在锁定屏幕上。您的应用可以使用此功能提供媒体播放控件以及其他常用操作。 +用户可以通过“设置”选择是否将通知显示在锁定屏幕上,并且您可以指定您应用中的通知在锁定屏幕上是否可见。 +</p> + +<h3 id="visibility">设置可见性</h3> + +<p>您的应用可以控制在安全锁定屏幕上显示的通知中可见的详细级别。 +调用 {@link android.support.v4.app.NotificationCompat.Builder#setVisibility(int) setVisibility()} +并指定以下值之一:</p> + +<ul> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC} +显示通知的完整内容。</li> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_SECRET} +不会在锁定屏幕上显示此通知的任何部分。</li> + <li>{@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} +显示通知图标和内容标题等基本信息,但是隐藏通知的完整内容。</li> +</ul> + +<p>设置 {@link android.support.v4.app.NotificationCompat#VISIBILITY_PRIVATE} +后,您还可以提供其中隐藏了某些详细信息的替换版本通知内容。例如,短信 +应用可能会显示一条通知,指出“您有 +3 条新短信”,但是隐藏了短信内容和发件人。<em></em>要提供此替换版本的通知,请先使用 +{@link android.support.v4.app.NotificationCompat.Builder} 创建替换通知。创建专用通知对象时,请通过 +{@link android.support.v4.app.NotificationCompat.Builder#setPublicVersion(android.app.Notification) setPublicVersion()} +方法为其附加替换通知。 +</p> + +<h3 id="controllingMedia">在锁定屏幕上控制媒体播放</h3> + +<p>在 Android 5.0(API 级别 21)中,锁定屏幕不再基于 +{@link android.media.RemoteControlClient}(现已弃用)显示媒体控件。取而代之的是,将 +{@link android.app.Notification.MediaStyle} 模板与 +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +方法结合使用,后者可将操作转换为可点击的图标。</p> + +<p class="note"><strong>注:</strong>该模板和 +{@link android.app.Notification.Builder#addAction(android.app.Notification.Action) addAction()} +方法未包含在支持库中,因此这些功能只能在 Android 5.0 及更高版本的系统上运行。</p> + +<p>要在 Android 5.0 系统的锁定屏幕上显示媒体播放控件,请将可见性设置为 +{@link android.support.v4.app.NotificationCompat#VISIBILITY_PUBLIC},如上文所述。然后,添加操作并设置 +{@link android.app.Notification.MediaStyle} +模板,如以下示例代码中所述:</p> + +<pre> +Notification notification = new Notification.Builder(context) + // Show controls on lock screen even when user hides sensitive content. + .setVisibility(Notification.VISIBILITY_PUBLIC) + .setSmallIcon(R.drawable.ic_stat_player) + // Add media control buttons that invoke intents in your media service + .addAction(R.drawable.ic_prev, "Previous", prevPendingIntent) // #0 + .addAction(R.drawable.ic_pause, "Pause", pausePendingIntent) // #1 + .addAction(R.drawable.ic_next, "Next", nextPendingIntent) // #2 + // Apply the media style template + .setStyle(new Notification.MediaStyle() + .setShowActionsInCompactView(1 /* #1: pause button */) + .setMediaSession(mMediaSession.getSessionToken()) + .setContentTitle("Wonderful music") + .setContentText("My Awesome Band") + .setLargeIcon(albumArtBitmap) + .build(); +</pre> + +<p class="note"><strong>注:</strong>弃用 {@link android.media.RemoteControlClient} +会对控制媒体产生进一步的影响。如需了解有关用于管理媒体会话和控制播放的新 API 的详细信息,请参阅<a href="{@docRoot}about/versions/android-5.0.html#MediaPlaybackControl">媒体播放控件</a>。 + +</p> + + +<!-- ------------------------------------------------------------------------------------------ --> +<h2 id="CustomNotification">自定义通知布局</h2> +<p> + 您可以利用通知框架定义自定义通知布局,由该布局定义通知在 +{@link android.widget.RemoteViews} 对象中的外观。 + 自定义布局通知类似于常规通知,但是它们是基于 XML 布局文件中所定义的 +{@link android.widget.RemoteViews}。 +</p> +<p> + 自定义通知布局的可用高度取决于通知视图。普通视图布局限制为 +64 dp,扩展视图布局限制为 256 dp。 +</p> +<p> + 要定义自定义通知布局,请首先实例化 +{@link android.widget.RemoteViews} 对象来扩充 XML 布局文件。然后,调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()},而不是调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContentTitle setContentTitle()} +等方法。要在自定义通知中设置内容详细信息,请使用 +{@link android.widget.RemoteViews} +中的方法设置视图子项的值: +</p> +<ol> + <li> + 在单独的文件中为通知创建 XML 布局。您可以根据需要使用任何文件名,但必须使用扩展名 +<code>.xml</code>。 + </li> + <li> + 在您的应用中,使用 {@link android.widget.RemoteViews} +方法定义通知的图标和文本。通过调用 +{@link android.support.v4.app.NotificationCompat.Builder#setContent setContent()} 将此 {@link android.widget.RemoteViews} 对象放入 +{@link android.support.v4.app.NotificationCompat.Builder} 中。避免在 +{@link android.widget.RemoteViews} 对象上设置背景 +{@link android.graphics.drawable.Drawable},因为文本颜色可能使文本变得难以阅读。 + </li> +</ol> +<p> + 此外,{@link android.widget.RemoteViews} 类中还有一些方法可供您轻松将 +{@link android.widget.Chronometer} 或 {@link android.widget.ProgressBar} +添加到通知布局。如需了解有关为通知创建自定义布局的详细信息,请参阅 +{@link android.widget.RemoteViews} 参考文档。 +</p> +<p class="caution"> + <strong>注意:</strong>使用自定义通知布局时,要特别注意确保自定义布局适用于不同的设备方向和分辨率。 +尽管这条建议适用于所有“视图”布局,但对通知尤为重要,因为抽屉式通知栏中的空间非常有限。 + +不要让自定义布局过于复杂,同时确保在各种配置中对其进行测试。 + +</p> +<!-- ------------------------------------------------------------------------------------------ --> +<h4>对自定义通知文本使用样式资源</h4> +<p> + 始终对自定义通知的文本使用样式资源。通知的背景颜色可能因设备和系统版本的不同而异,使用样式资源有助于您充分考虑到这一点。 + +从 +Android 2.3 开始,系统定义了标准通知布局文本的样式。若要在面向 Android +2.3 或更高版本系统的多个应用中使用相同样式,则应确保文本在显示背景上可见。 +</p> diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd new file mode 100644 index 000000000000..5097c76e4027 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/overview.jd @@ -0,0 +1,71 @@ +page.title=UI 概览 +@jd:body + + +<p>Android 应用中的所有用户界面元素都是使用 {@link android.view.View} 和 +{@link android.view.ViewGroup} 对象构建而成。{@link android.view.View} +对象用于在屏幕上绘制可供用户交互的内容。{@link android.view.ViewGroup} +对象用于储存其他 {@link android.view.View}(和 +{@link android.view.ViewGroup})对象,以便定义界面的局部。</p> + +<p>Android 提供了一系列 +{@link android.view.View} 和 {@link +android.view.ViewGroup} 子类,可为您提供常用输入控件(如按钮和文本字段)和各种布局模式(如线性布局或相对布局)。</p> + + +<h2 id="Layout">用户界面布局</h2> + +<p>如图 1 所示,每个应用组件的用户界面都是使用 {@link +android.view.View} 和 {@link android.view.ViewGroup} 对象的层次结构定义的。每个视图组都是一个用于组织子视图的不可见容器,而子视图可以是输入控件或其他可绘制某一 +UI +部分的小工具。此层次结构树可繁可简,随需而定(但是简单的结构可提供最佳性能)。 + +</p> + +<img src="{@docRoot}images/viewgroup.png" alt="" /> +<p class="img-caption"><strong>图 1. </strong>视图层次结构的图示,它定义了一个 UI +布局。</p> + +<p>要声明布局,您可以实例化代码中的 {@link android.view.View} 对象并开始构建树,但是定义布局最简单且最有效的方法是使用 XML +文件。如同 HTML 一样,XML +也为布局提供了一种用户可读结构。</p> + +<p>视图的 XML 元素名称与其代表的 Android 类相对应。因此, +<code><TextView></code> 元素用于在 UI 中创建一个 {@link android.widget.TextView} 小工具,而 +<code><LinearLayout></code> 元素用于创建一个 {@link android.widget.LinearLayout} +视图组。 </p> + +<p>例如,包含文本视图和按钮的简单垂直布局如下所示:</p> +<pre> +<?xml version="1.0" encoding="utf-8"?> +<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + android:layout_width="fill_parent" + android:layout_height="fill_parent" + android:orientation="vertical" > + <TextView android:id="@+id/text" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="I am a TextView" /> + <Button android:id="@+id/button" + android:layout_width="wrap_content" + android:layout_height="wrap_content" + android:text="I am a Button" /> +</LinearLayout> +</pre> + +<p>在应用中加载布局资源时,Android +会将布局的每个节点初始化为运行时对象,供您定义其他行为、查询对象状态或修改布局。 +</p> + +<p>有关创建 UI 布局的完整指南,请参阅 <a href="declaring-layout.html">XML +布局</a>。 + + +<h2 id="UIComponents">用户界面组件</h2> + +<p>您无需使用 {@link android.view.View} 和 {@link +android.view.ViewGroup} 对象构建所有 UI。Android 提供了几个带有标准 UI 布局的应用组件,您只需定义内容。 +这些 UI +组件均拥有一组唯一的 API,具体描述可参阅相应的文档,如<a href="{@docRoot}guide/topics/ui/actionbar.html">操作栏</a>、<a href="{@docRoot}guide/topics/ui/dialogs.html">对话框</a>和<a href="{@docRoot}guide/topics/ui/notifiers/notifications.html">状态通知</a>。</p> + + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd new file mode 100644 index 000000000000..f9be97be8c2e --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/settings.jd @@ -0,0 +1,1202 @@ +page.title=设置 +page.tags=首选项,首选项 Activity,首选项片段 + +@jd:body + + +<div id="qv-wrapper"> +<div id="qv"> + +<h2>本文内容</h2> +<ol> + <li><a href="#Overview">概览</a> + <ol> + <li><a href="#SettingTypes">首选项</a></li> + </ol> + </li> + <li><a href="#DefiningPrefs">使用 XML 定义首选项</a> + <ol> + <li><a href="#Groups">创建设置组</a></li> + <li><a href="#Intents">使用 Intent </a></li> + </ol> + </li> + <li><a href="#Activity">创建首选项 Activity</a></li> + <li><a href="#Fragment">使用首选项片段</a></li> + <li><a href="#Defaults">设置默认值</a></li> + <li><a href="#PreferenceHeaders">使用首选项标头</a> + <ol> + <li><a href="#CreateHeaders">创建标头文件</a></li> + <li><a href="#DisplayHeaders">显示标头</a></li> + <li><a href="#BackCompatHeaders">使用首选项标头支持旧版本</a></li> + </ol> + </li> + <li><a href="#ReadingPrefs">读取首选项</a> + <ol> + <li><a href="#Listening">侦听首选项变更</a></li> + </ol> + </li> + <li><a href="#NetworkUsage">管理网络使用情况</a></li> + <li><a href="#Custom">构建自定义首选项</a> + <ol> + <li><a href="#CustomSelected">指定用户界面</a></li> + <li><a href="#CustomSave">保存设置的值</a></li> + <li><a href="#CustomInitialize">初始化当前值</a></li> + <li><a href="#CustomDefault">提供默认值</a></li> + <li><a href="#CustomSaveState">保存和恢复首选项的状态</a></li> + </ol> + </li> +</ol> + +<h2>关键类</h2> +<ol> + <li>{@link android.preference.Preference}</li> + <li>{@link android.preference.PreferenceActivity}</li> + <li>{@link android.preference.PreferenceFragment}</li> +</ol> + + +<h2>另请参阅</h2> +<ol> + <li><a href="{@docRoot}design/patterns/settings.html">设置设计指南</a></li> +</ol> +</div> +</div> + + + + +<p>应用通常包括允许用户修改应用特性和行为的设置。例如,有些应用允许用户指定是否启用通知,或指定应用与云端同步数据的频率。 + +</p> + +<p>若要为应用提供设置,您应该使用 +Android 的 {@link android.preference.Preference} API +构建一个与其他 Android 应用中的用户体验一致的界面(包括系统设置)。本文旨在介绍如何使用 +{@link android.preference.Preference} API 构建应用设置。</p> + +<div class="note design"> +<p><strong>设置设计</strong></p> + <p>有关如何设计设置的信息,请阅读<a href="{@docRoot}design/patterns/settings.html">设置</a>设计指南。</p> +</div> + + +<img src="{@docRoot}images/ui/settings/settings.png" alt="" width="435" /> +<p class="img-caption"><strong>图 1. </strong>来自 Android 信息应用的设置的屏幕截图。 +选择由 {@link android.preference.Preference} +定义的项目将打开一个用于更改设置的界面。</p> + + + + +<h2 id="Overview">概览</h2> + +<p>设置是使用您在 XML 文件中声明的 +{@link android.preference.Preference} +类的各种子类构建而成,而不是使用 {@link android.view.View} 对象构建用户界面。</p> + +<p>{@link android.preference.Preference} +对象是单个设置的构建基块。每个 {@link android.preference.Preference} 均作为项目显示在列表中,并提供适当的 +UI 供用户修改设置。例如,{@link +android.preference.CheckBoxPreference} 可创建一个列表项用于显示复选框,{@link +android.preference.ListPreference} 可创建一个项目用于打开包含选择列表的对话框。</p> + +<p>您添加的每个 {@link android.preference.Preference} 都有一个相应的键值对,可供系统用来将设置保存在应用设置的默认 +{@link android.content.SharedPreferences} +文件中。当用户更改设置时,系统会为您更新 +{@link android.content.SharedPreferences} 文件中的相应值。您只应在需要读取值以根据用户设置确定应用的行为时,才与关联的 +{@link android.content.SharedPreferences} +文件直接交互。</p> + +<p>为每个设置保存在 {@link android.content.SharedPreferences} +中的值可能是以下数据类型之一:</p> + +<ul> + <li>布尔型</li> + <li>浮点型</li> + <li>整型</li> + <li>长整型</li> + <li>字符串</li> + <li>字符串 {@link java.util.Set}</li> +</ul> + +<p>由于应用的设置 UI 是使用 {@link android.preference.Preference} +对象(而非 +{@link android.view.View} 对象)构建而成,因此您需要使用专门的 {@link android.app.Activity} 或 +{@link android.app.Fragment} 子类显示列表设置:</p> + +<ul> + <li>如果应用支持早于 3.0(API 级别 10 及更低级别)的 Android 版本,则您必须将 Activity 构建为 +{@link android.preference.PreferenceActivity} 类的扩展。</li> + <li>对于 Android 3.0 及更高版本,您应改用传统 +{@link android.app.Activity},以托管可显示应用设置的 {@link android.preference.PreferenceFragment}。但是,如果您拥有多组设置,则还可以使用 +{@link android.preference.PreferenceActivity} +为大屏幕创建双窗格布局。</li> +</ul> + +<p><a href="#Activity">创建首选项 Activity</a>和<a href="#Fragment">使用首选项片段</a> +部分将讨论如何设置 {@link android.preference.PreferenceActivity} 以及 {@link +android.preference.PreferenceFragment} 实例。</p> + + +<h3 id="SettingTypes">首选项</h3> + +<p>所有应用设置均由 {@link +android.preference.Preference} 类的特定子类表示。每个子类均包括一组核心属性,允许您指定设置标题和默认值等内容。 +此外,每个子类还提供自己的专用属性和用户界面。 +例如,图 1 显示的是“信息” +应用的设置屏幕截图。设置屏幕中的每个列表项均由不同的 {@link +android.preference.Preference} 对象提供支持。</p> + +<p>一些最常用的首选项如下:</p> + +<dl> + <dt>{@link android.preference.CheckBoxPreference}</dt> + <dd>显示一个包含已启用或已禁用设置复选框的项目。保存的值是布尔型(如果选中则为 +<code>true</code>)。</dd> + + <dt>{@link android.preference.ListPreference}</dt> + <dd>打开一个包含单选按钮列表的对话框。保存的值可以是任一受支持的值类型(如上所列)。 +</dd> + + <dt>{@link android.preference.EditTextPreference}</dt> + <dd>打开一个包含 {@link android.widget.EditText} 小工具的对话框。保存的值是 {@link +java.lang.String}。</dd> +</dl> + +<p>有关所有其他子类及其对应属性的列表,请参阅 {@link android.preference.Preference} +类。</p> + +<p>当然,内置类不能满足所有需求,您的应用可能需要更专业化的内容。 +例如,该平台目前不提供用于选取数字或日期的 {@link +android.preference.Preference} 类。因此,您可能需要定义自己的 +{@link android.preference.Preference} 子类。如需有关执行此操作的帮助,请参阅<a href="#Custom">构建自定义首选项</a>部分。</p> + + + +<h2 id="DefiningPrefs">使用 XML 定义首选项</h2> + +<p>虽然您可以在运行时实例化新的 {@link android.preference.Preference} +对象,不过您还是应该使用 {@link android.preference.Preference} +对象的层次结构在 XML 中定义设置列表。使用 XML +文件定义设置的集合是首选方法,因为该文件提供了一个便于更新的易读结构。此外,应用的设置通常是预先确定的,不过您仍可在运行时修改此集合。 +</p> + +<p>每个 {@link android.preference.Preference} 子类均可以使用与类名(如 {@code <CheckBoxPreference>})匹配的 XML 元素来声明。 +</p> + +<p>您必须将 XML 文件保存在 {@code res/xml/} 目录中。尽管您可以随意命名该文件,但它通常命名为 +{@code preferences.xml}。您通常只需一个文件,因为层次结构中的分支(可打开各自的设置列表)是使用 +{@link android.preference.PreferenceScreen} +的嵌套实例声明的。</p> + +<p class="note"><strong>注:</strong>若要为设置创建多窗格布局,则需要为每个片段提供单独的 +XML 文件。</p> + +<p>XML 文件的根节点必须是一个 {@link android.preference.PreferenceScreen +<PreferenceScreen>} 元素。您可以在此元素内添加每个 {@link +android.preference.Preference}。在 +{@link android.preference.PreferenceScreen <PreferenceScreen>} +元素内添加的每个子项均将作为单独的项目显示在设置列表中。</p> + +<p>例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <CheckBoxPreference + android:key="pref_sync" + android:title="@string/pref_sync" + android:summary="@string/pref_sync_summ" + android:defaultValue="true" /> + <ListPreference + android:dependency="pref_sync" + android:key="pref_syncConnectionType" + android:title="@string/pref_syncConnectionType" + android:dialogTitle="@string/pref_syncConnectionType" + android:entries="@array/pref_syncConnectionTypes_entries" + android:entryValues="@array/pref_syncConnectionTypes_values" + android:defaultValue="@string/pref_syncConnectionTypes_default" /> +</PreferenceScreen> +</pre> + +<p>在此示例中,有一个 {@link android.preference.CheckBoxPreference} 和 {@link +android.preference.ListPreference}。这两项均包括以下三个属性:</p> + +<dl> + <dt>{@code android:key}</dt> + <dd>对于要保留数据值的首选项,必须拥有此属性。它指定系统在将此设置的值保存在 +{@link +android.content.SharedPreferences} 中时所用的唯一键(字符串)。 + <p>不需要此属性的仅有情形是:首选项是 +{@link android.preference.PreferenceCategory} 或{@link android.preference.PreferenceScreen},或者首选项指定要调用的 +{@link android.content.Intent}(使用 <a href="#Intents">{@code <intent>}</a> 元素)或要显示的 {@link android.app.Fragment}(使用 <a href="{@docRoot}reference/android/preference/Preference.html#attr_android:fragment">{@code +android:fragment}</a> 属性)。<em></em></p> + </dd> + <dt>{@code android:title}</dt> + <dd>此属性为设置提供用户可见的名称。</dd> + <dt>{@code android:defaultValue}</dt> + <dd>此属性指定系统应该在 {@link +android.content.SharedPreferences} 文件中设置的初始值。您应该为所有设置提供默认值。 +</dd> +</dl> + +<p>有关所有其他受支持属性的信息,请参阅 {@link +android.preference.Preference}(和相应子类)文档。</p> + + +<div class="figure" style="width:300px"> + <img src="{@docRoot}images/ui/settings/settings-titles.png" alt="" /> + <p class="img-caption"><strong>图 2. </strong>带标题的设置类别。 + <br/><b>1.</b>类别由 {@link +android.preference.PreferenceCategory <PreferenceCategory>} 元素指定。 <br/><b>2.</b>标题由 +{@code android:title} 属性指定。</p> +</div> + + +<p>当设置列表超过 10 项时,您可能需要添加标题来定义设置组或在单独的屏幕中显示这些组。 + +下文将介绍这些选项。</p> + + +<h3 id="Groups">创建设置组</h3> + +<p>如果您提供的列表包含 10 +项或更多设置,则用户可能难以浏览、理解和处理这些设置。若要弥补这一点,您可以将部分或全部设置分成若干组,从而有效地将一个长列表转化为多个短列表。 + +可以通过下列两种方法之一提供一组相关设置:</p> + +<ul> + <li><a href="#Titles">使用标题</a></li> + <li><a href="#Subscreens">使用子屏幕</a></li> +</ul> + +<p>您可以使用其中一种或两种分组方法来组织应用的设置。决定要使用的技巧以及如何拆分设置时,应遵循 Android 设计的<a href="{@docRoot}design/patterns/settings.html">设置</a>指南中的准则。 + +</p> + + +<h4 id="Titles">使用标题</h4> + +<p>若要以分隔线分隔两组设置并为其提供标题(如图 2 所示),请将每组 +{@link android.preference.Preference} 对象放入 {@link +android.preference.PreferenceCategory} 内。</p> + +<p>例如:</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <PreferenceCategory + android:title="@string/pref_sms_storage_title" + android:key="pref_key_storage_settings"> + <CheckBoxPreference + android:key="pref_key_auto_delete" + android:summary="@string/pref_summary_auto_delete" + android:title="@string/pref_title_auto_delete" + android:defaultValue="false"... /> + <Preference + android:key="pref_key_sms_delete_limit" + android:dependency="pref_key_auto_delete" + android:summary="@string/pref_summary_delete_limit" + android:title="@string/pref_title_sms_delete"... /> + <Preference + android:key="pref_key_mms_delete_limit" + android:dependency="pref_key_auto_delete" + android:summary="@string/pref_summary_delete_limit" + android:title="@string/pref_title_mms_delete" ... /> + </PreferenceCategory> + ... +</PreferenceScreen> +</pre> + + +<h4 id="Subscreens">使用子屏幕</h4> + +<p>若要将设置组放入子屏幕(如图 3 所示),请将 +{@link android.preference.Preference} 对象组放入 {@link +android.preference.PreferenceScreen} 内。</p> + +<img src="{@docRoot}images/ui/settings/settings-subscreen.png" alt="" /> +<p class="img-caption"><strong>图 3. </strong>设置子屏幕。{@code +<PreferenceScreen>} 元素创建的项目选中后,即会打开一个单独的列表来显示嵌套设置。 +</p> + +<p>例如:</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <!-- opens a subscreen of settings --> + <PreferenceScreen + android:key="button_voicemail_category_key" + android:title="@string/voicemail" + android:persistent="false"> + <ListPreference + android:key="button_voicemail_provider_key" + android:title="@string/voicemail_provider" ... /> + <!-- opens another nested subscreen --> + <PreferenceScreen + android:key="button_voicemail_setting_key" + android:title="@string/voicemail_settings" + android:persistent="false"> + ... + </PreferenceScreen> + <RingtonePreference + android:key="button_voicemail_ringtone_key" + android:title="@string/voicemail_ringtone_title" + android:ringtoneType="notification" ... /> + ... + </PreferenceScreen> + ... +</PreferenceScreen> +</pre> + + +<h3 id="Intents">使用 Intent </h3> + +<p>在某些情况下,您可能需要首选项来打开不同的 Activity(而不是 Web +浏览器等设置屏幕)或查看网页。要在用户选择首选项时调用 {@link +android.content.Intent},请将{@code <intent>} +元素添加为相应 {@code <Preference>} 元素的子元素。</p> + +<p>例如,您可以按如下方法使用首选项打开网页:</p> + +<pre> +<Preference android:title="@string/prefs_web_page" > + <intent android:action="android.intent.action.VIEW" + android:data="http://www.example.com" /> +</Preference> +</pre> + +<p>您可以使用以下属性创建隐式和显式 Intent:</p> + +<dl> + <dt>{@code android:action}</dt> + <dd>要分配的操作(按照 {@link android.content.Intent#setAction setAction()} +方法)。</dd> + <dt>{@code android:data}</dt> + <dd>要分配的数据(按照 {@link android.content.Intent#setData setData()} 方法)。</dd> + <dt>{@code android:mimeType}</dt> + <dd>要分配的 MIME 类型(按照 {@link android.content.Intent#setType setType()} +方法)。</dd> + <dt>{@code android:targetClass}</dt> + <dd>组件名称的类部分(按照 {@link android.content.Intent#setComponent +setComponent()} 方法)。</dd> + <dt>{@code android:targetPackage}</dt> + <dd>组件名称的软件包部分(按照 {@link +android.content.Intent#setComponent setComponent()} 方法)。</dd> +</dl> + + + +<h2 id="Activity">创建首选项 Activity</h2> + +<p>要在 Activity 中显示您的设置,请扩展 {@link +android.preference.PreferenceActivity} 类。这是传统 {@link +android.app.Activity} 类的扩展,该类根据 {@link +android.preference.Preference} 对象的层次结构显示设置列表。当用户进行更改时,{@link android.preference.PreferenceActivity} +会自动保留与每个 {@link +android.preference.Preference} 相关的设置。</p> + +<p class="note"><strong>注:</strong>如果您是开发针对 Android 3.0 及 +更高版本系统的应用,则应改为使用 {@link android.preference.PreferenceFragment}。转到下文有关<a href="#Fragment">使用首选项片段</a>的部分。 +</p> + +<p>请记住最重要的一点,就是不要在 {@link +android.preference.PreferenceActivity#onCreate onCreate()} 回调期间加载视图的布局。相反,请调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} 以将在 +XML 文件中声明的首选项添加到 Activity。例如,一个能够正常工作的 +{@link android.preference.PreferenceActivity} 至少需要如下代码:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preferences); + } +} +</pre> + +<p>实际上,对于某些应用而言此,代码就已足够,因为用户修改某首选项后,系统会立即将所做的更改保存到默认 {@link android.content.SharedPreferences} 文件中,如需检查用户的设置,可以使用您的其他应用组件读取该文件。 + +不过,许多应用需要的代码要稍微多一点,以侦听首选项发生的变化。有关侦听 +{@link android.content.SharedPreferences} +文件变化的信息,请参阅<a href="#ReadingPrefs">读取首选项</a>部分。 +</p> + + + + +<h2 id="Fragment">使用首选项片段</h2> + +<p>如果您是开发针对 Android 3.0(API 级别 11)及更高版本系统的应用,则应使用 {@link +android.preference.PreferenceFragment} 显示 {@link android.preference.Preference} +对象的列表。您可以将 {@link android.preference.PreferenceFragment} 添加到任何 Activity,而不必使用 +{@link android.preference.PreferenceActivity}。</p> + +<p>与仅使用上述 Activity 相比,无论您在构建何种 Activity,<a href="{@docRoot}guide/components/fragments.html">片段</a>都可为应用提供一个更加灵活的体系结构。 + +因此,我们建议您尽可能使用 {@link +android.preference.PreferenceFragment} 控制设置的显示,而不是使用 {@link +android.preference.PreferenceActivity}。</p> + +<p>{@link android.preference.PreferenceFragment} 的实现就像定义 +{@link android.preference.PreferenceFragment#onCreate onCreate()} 方法以使用 +{@link android.preference.PreferenceFragment#addPreferencesFromResource +addPreferencesFromResource()} 加载首选项文件一样简单。例如:</p> + +<pre> +public static class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Load the preferences from an XML resource + addPreferencesFromResource(R.xml.preferences); + } + ... +} +</pre> + +<p>然后,正如您对其他任何 +{@link android.app.Fragment} 的处理一样,您可以将此片段添加到 {@link android.app.Activity}。例如:</p> + +<pre> +public class SettingsActivity extends Activity { + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // Display the fragment as the main content. + getFragmentManager().beginTransaction() + .replace(android.R.id.content, new SettingsFragment()) + .commit(); + } +} +</pre> + +<p class="note"><strong>注:</strong>{@link android.preference.PreferenceFragment} 没有自己的 +{@link android.content.Context} 对象。如需 {@link android.content.Context} +对象,您可以调用 {@link android.app.Fragment#getActivity()}。但请注意,只应在该片段附加到 Activity 时才调用 +{@link android.app.Fragment#getActivity()}。如果该片段尚未附加或在其生命周期结束期间已分离,则 +{@link +android.app.Fragment#getActivity()} 将返回空 null。</p> + + +<h2 id="Defaults">设置默认值</h2> + +<p>您创建的首选项可能会为应用定义一些重要行为,因此在用户首次打开应用时,您有必要使用每个 {@link android.preference.Preference} +的默认值初始化相关的 +{@link android.content.SharedPreferences} +文件。</p> + +<p>首先,您必须使用 {@code android:defaultValue} 属性为 XML 文件中的每个 {@link +android.preference.Preference} +对象指定默认值。该值可以是适合相应 +{@link android.preference.Preference} 对象的任意数据类型。例如: +</p> + +<pre> +<!-- default value is a boolean --> +<CheckBoxPreference + android:defaultValue="true" + ... /> + +<!-- default value is a string --> +<ListPreference + android:defaultValue="@string/pref_syncConnectionTypes_default" + ... /> +</pre> + +<p>然后,通过应用的主 Activity(以及用户首次进入应用所藉由的任何其他 Activity)中的 {@link android.app.Activity#onCreate onCreate()} +方法调用 {@link android.preference.PreferenceManager#setDefaultValues +setDefaultValues()}: +</p> + +<pre> +PreferenceManager.setDefaultValues(this, R.xml.advanced_preferences, false); +</pre> + +<p>在 +{@link android.app.Activity#onCreate onCreate()} +期间调用此方法可确保使用默认设置正确初始化应用,而应用可能需要读取这些设置以确定某些行为(例如,是否在蜂窝网络中下载数据)。 +</p> + +<p>此方法采用三个参数:</p> +<ul> + <li>应用 {@link android.content.Context}。</li> + <li>要为其设置默认值的首选项 XML 文件的资源 ID。</li> + <li>一个布尔值,用于指示是否应该多次设置默认值。 +<p>如果该值为 <code>false</code>,则仅当过去从未调用此方法时(或者默认值共享首选项文件中的 +{@link android.preference.PreferenceManager#KEY_HAS_SET_DEFAULT_VALUES}为 +false 时),系统才会设置默认值。</p></li> +</ul> + +<p>只要将第三个参数设置为 +<code>false</code>,您便可在每次启动 Activity 时安全地调用此方法,而不必通过重置为默认值来替代用户已保存的首选项。 +但是,如果将它设置为 +<code>true</code>,则需要使用默认值替代之前的所有值。</p> + + + +<h2 id="PreferenceHeaders">使用首选项标头</h2> + +<p>在极少数情况下,您可能需要设计设置,使第一个屏幕仅显示<a href="#Subscreens">子屏幕</a>的列表(例如在系统“设置”应用中,如图 4 和图 5 所示)。 + +在开发针对 Android 3.0 及更高版本系统的此类设计时,您应该使用 +Android 3.0 中的新“标头”功能,而非使用嵌套的 +{@link android.preference.PreferenceScreen} 元素构建子屏幕。</p> + +<p>要使用标头构建设置,您需要:</p> +<ol> + <li>将每组设置分成单独的 {@link +android.preference.PreferenceFragment} 实例。即,每组设置均需要一个单独的 XML +文件。</li> + <li>创建 XML +标头文件,其中列出每个设置组并声明哪个片段包含对应的设置列表。</li> + <li>扩展 {@link android.preference.PreferenceActivity} 类以托管设置。</li> + <li>实现 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} +回调以指定标头文件。</li> +</ol> + +<p>使用此设计的一大好处是,在大屏幕上运行时,{@link android.preference.PreferenceActivity} +会自动提供双窗格布局(如图 4 所示)。</p> + +<p>即使您的应用支持早于 3.0 的 Android 版本,您仍可将应用设计为使用 +{@link android.preference.PreferenceFragment} +在较新版本的设备上呈现双窗格布局,同时仍支持较旧版本设备上传统的多屏幕层次结构(请参阅<a href="#BackCompatHeaders">使用首选项标头支持旧版本</a>部分)。 + +</p> + +<img src="{@docRoot}images/ui/settings/settings-headers-tablet.png" alt="" /> +<p class="img-caption"><strong>图 4. </strong>带标头的双窗格布局。 <br/><b>1.</b>标头用 +XML 标头文件定义。 <br/><b>2.</b>每组设置均由 +{@link android.preference.PreferenceFragment}(通过标头文件中的 {@code <header>} +元素指定)定义。</p> + +<img src="{@docRoot}images/ui/settings/settings-headers-handset.png" alt="" /> +<p class="img-caption"><strong>图 5. </strong>带设置标头的手机设备。选择项目后,相关的 +{@link android.preference.PreferenceFragment} +将替换标头。</p> + + +<h3 id="CreateHeaders" style="clear:left">创建标头文件</h3> + +<p>标头列表中的每组设置均由根 {@code <preference-headers>} 元素内的单个 {@code <header>} +元素指定。例如:</p> + +<pre> +<?xml version="1.0" encoding="utf-8"?> +<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> + <header + android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentOne" + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" /> + <header + android:fragment="com.example.prefs.SettingsActivity$SettingsFragmentTwo" + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" > + <!-- key/value pairs can be included as arguments for the fragment. --> + <extra android:name="someKey" android:value="someHeaderValue" /> + </header> +</preference-headers> +</pre> + +<p>每个标头均可使用 {@code android:fragment} 属性声明在用户选择该标头时应打开的 {@link +android.preference.PreferenceFragment} 实例。</p> + +<p>{@code <extras>} 元素允许您使用 {@link +android.os.Bundle} 将键值对传递给片段。该片段可以通过调用 {@link +android.app.Fragment#getArguments()} 检索参数。您向该片段传递参数的原因可能有很多,不过一个重要原因是,要对每个组重复使用 +{@link +android.preference.PreferenceFragment} +的相同子类,而且要使用参数来指定该片段应加载哪些首选项 XML 文件。</p> + +<p>例如,当每个标头均使用 +{@code "settings"} 键定义 {@code <extra>} 参数时,则可以对多个设置组重复使用以下片段:</p> + +<pre> +public static class SettingsFragment extends PreferenceFragment { + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String settings = getArguments().getString("settings"); + if ("notifications".equals(settings)) { + addPreferencesFromResource(R.xml.settings_wifi); + } else if ("sync".equals(settings)) { + addPreferencesFromResource(R.xml.settings_sync); + } + } +} +</pre> + + + +<h3 id="DisplayHeaders">显示标头</h3> + +<p>要显示首选项标头,您必须实现 {@link +android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()} 回调方法并调用 +{@link android.preference.PreferenceActivity#loadHeadersFromResource +loadHeadersFromResource()}。例如:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity { + @Override + public void onBuildHeaders(List<Header> target) { + loadHeadersFromResource(R.xml.preference_headers, target); + } +} +</pre> + +<p>当用户从标头列表中选择一个项目时,系统会打开相关的 {@link +android.preference.PreferenceFragment}。</p> + +<p class="note"><strong>注:</strong>使用首选项标头时,{@link +android.preference.PreferenceActivity} 的子类无需实现 {@link +android.preference.PreferenceActivity#onCreate onCreate()} +方法,因为 Activity 唯一所需执行的任务就是加载标头。</p> + + +<h3 id="BackCompatHeaders">使用首选项标头支持旧版本</h3> + +<p>如果您的应用支持早于 3.0 的 Android +版本,则在 Android 3.0 及更高版本系统上运行时,您仍可使用标头提供双窗格数据。为此,您只需另外创建 +一个使用基本 {@link android.preference.Preference +<Preference>} 元素的首选项 XML 文件即可,这些基本元素的行为方式与标头项目类似(供较旧版本的 Android + 系统使用)。</p> + +<p>但是,每个 {@link +android.preference.Preference <Preference>} 元素均会向 {@link android.preference.PreferenceActivity} 发送一个 {@link android.content.Intent},指定要加载哪个首选项 XML 文件,而不是打开新的 +{@link android.preference.PreferenceScreen}。 +</p> + +<p>例如,下面就是一个用于 Android 3.0 +及更高版本系统的首选项标头 XML 文件 ({@code res/xml/preference_headers.xml}):</p> + +<pre> +<preference-headers xmlns:android="http://schemas.android.com/apk/res/android"> + <header + android:fragment="com.example.prefs.SettingsFragmentOne" + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" /> + <header + android:fragment="com.example.prefs.SettingsFragmentTwo" + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" /> +</preference-headers> +</pre> + +<p>下面是为早于 +Android 3.0 版本的系统提供相同标头的首选项文件 ({@code res/xml/preference_headers_legacy.xml}):</p> + +<pre> +<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"> + <Preference + android:title="@string/prefs_category_one" + android:summary="@string/prefs_summ_category_one" > + <intent + android:targetPackage="com.example.prefs" + android:targetClass="com.example.prefs.SettingsActivity" + android:action="com.example.prefs.PREFS_ONE" /> + </Preference> + <Preference + android:title="@string/prefs_category_two" + android:summary="@string/prefs_summ_category_two" > + <intent + android:targetPackage="com.example.prefs" + android:targetClass="com.example.prefs.SettingsActivity" + android:action="com.example.prefs.PREFS_TWO" /> + </Preference> +</PreferenceScreen> +</pre> + +<p>由于是从 Android 3.0 开始方添加对 {@code <preference-headers>} 的支持,因此只有在 Androd 3.0 或更高版本中运行时,系统才会在您的 {@link +android.preference.PreferenceActivity} +中调用 {@link android.preference.PreferenceActivity#onBuildHeaders onBuildHeaders()}。要加载“旧版”标头文件 +({@code preference_headers_legacy.xml}),您必须检查 Android +版本,如果版本低于 Android 3.0 ({@link +android.os.Build.VERSION_CODES#HONEYCOMB}),请调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +来加载旧版标头文件。例如:</p> + +<pre> +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ... + + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // Load the legacy preferences headers + addPreferencesFromResource(R.xml.preference_headers_legacy); + } +} + +// Called only on Honeycomb and later +@Override +public void onBuildHeaders(List<Header> target) { + loadHeadersFromResource(R.xml.preference_headers, target); +} +</pre> + +<p>最后要做的就是处理传入 Activity 的 +{@link android.content.Intent},以确定要加载的首选项文件。因此,请检索 Intent 的操作,并将其与在首选项 XML 的 {@code <intent>} +标记中使用的已知操作字符串进行比较。</p> + +<pre> +final static String ACTION_PREFS_ONE = "com.example.prefs.PREFS_ONE"; +... + +@Override +public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + String action = getIntent().getAction(); + if (action != null && action.equals(ACTION_PREFS_ONE)) { + addPreferencesFromResource(R.xml.preferences); + } + ... + + else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.HONEYCOMB) { + // Load the legacy preferences headers + addPreferencesFromResource(R.xml.preference_headers_legacy); + } +} +</pre> + +<p>值得注意的是,连续调用 {@link +android.preference.PreferenceActivity#addPreferencesFromResource addPreferencesFromResource()} +会将所有首选项堆叠在一个列表中,因此请将条件与 +else-if 语句链接在一起,确保它只调用一次。</p> + + + + + +<h2 id="ReadingPrefs">读取首选项</h2> + +<p>默认情况下,应用的所有首选项均保存到一个可通过调用静态方法 +{@link +android.preference.PreferenceManager#getDefaultSharedPreferences +PreferenceManager.getDefaultSharedPreferences()} 从应用内的任何位置访问的文件中。这将返回 {@link +android.content.SharedPreferences} 对象,其中包含与 +{@link +android.preference.PreferenceActivity} 中所用 {@link android.preference.Preference} 对象相关的所有键值对。</p> + +<p>例如,从应用中的任何其他 Activity 读取某个首选项值的方法如下: +</p> + +<pre> +SharedPreferences sharedPref = PreferenceManager.getDefaultSharedPreferences(this); +String syncConnPref = sharedPref.getString(SettingsActivity.KEY_PREF_SYNC_CONN, ""); +</pre> + + + +<h3 id="Listening">侦听首选项变更</h3> + +<p>出于某些原因,您可能希望在用户更改任一首选项时立即收到通知。 +要在任一首选项发生更改时收到回调,请实现 +{@link android.content.SharedPreferences.OnSharedPreferenceChangeListener +SharedPreference.OnSharedPreferenceChangeListener} 接口,并通过调用 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} 为 +{@link android.content.SharedPreferences} 对象注册侦听器。</p> + +<p>该接口只有 {@link +android.content.SharedPreferences.OnSharedPreferenceChangeListener#onSharedPreferenceChanged +onSharedPreferenceChanged()} +一种回调方法,而且您可能会发现在 Activity 过程中实现该接口最为简单。例如:</p> + +<pre> +public class SettingsActivity extends PreferenceActivity + implements OnSharedPreferenceChangeListener { + public static final String KEY_PREF_SYNC_CONN = "pref_syncConnectionType"; + ... + + public void onSharedPreferenceChanged(SharedPreferences sharedPreferences, + String key) { + if (key.equals(KEY_PREF_SYNC_CONN)) { + Preference connectionPref = findPreference(key); + // Set summary to be the user-description for the selected value + connectionPref.setSummary(sharedPreferences.getString(key, "")); + } + } +} +</pre> + +<p>在此示例中,该方法检查更改的设置是否是针对已知的首选项键。它调用 +{@link android.preference.PreferenceActivity#findPreference findPreference()} 来获取已更改的 +{@link android.preference.Preference} +对象,以便能够将项目摘要修改为对用户选择的说明。即,如果设置为 {@link +android.preference.ListPreference} 或其他多选设置时,则当设置更改为显示当前状态(例如,图 +5 所示的“Sleep”设置)时,您应调用 {@link +android.preference.Preference#setSummary setSummary()}。</p> + +<p class="note"><strong>注:</strong>正如 Android 设计有关<a href="{@docRoot}design/patterns/settings.html">设置</a>的文档中所述,我们建议您在用户每次更改首选项时更新 +{@link android.preference.ListPreference} +的摘要,以描述当前设置。</p> + +<p>若要妥善管理 Activity 生命周期,我们建议您在 +{@link +android.app.Activity#onResume} 和 {@link android.app.Activity#onPause} 回调期间分别注册和注销 {@link android.content.SharedPreferences.OnSharedPreferenceChangeListener}。</p> + +<pre> +@Override +protected void onResume() { + super.onResume(); + getPreferenceScreen().getSharedPreferences() + .registerOnSharedPreferenceChangeListener(this); +} + +@Override +protected void onPause() { + super.onPause(); + getPreferenceScreen().getSharedPreferences() + .unregisterOnSharedPreferenceChangeListener(this); +} +</pre> + +<p class="caution"><strong>注意:</strong>目前,首选项管理器不会在您调用 {@link +android.content.SharedPreferences#registerOnSharedPreferenceChangeListener +registerOnSharedPreferenceChangeListener()} +时存储对侦听器的强引用。但是,您必须存储对侦听器的强引用,否则它将很容易被当作垃圾回收。 +我们建议您将对侦听器的引用保存在只要您需要侦听器就会存在的对象的实例数据中。 + +</p> + +<p>例如,在以下代码中,调用方未保留对侦听器的引用。 +因此,侦听器将容易被当作垃圾回收,并在将来某个不确定的时间失败: +</p> + +<pre> +prefs.registerOnSharedPreferenceChangeListener( + // Bad! The listener is subject to garbage collection! + new SharedPreferences.OnSharedPreferenceChangeListener() { + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + // listener implementation + } +}); +</pre> + +<p>有鉴于此,请将对侦听器的引用存储在只要需要侦听器就会存在的对象的实例数据字段中: +</p> + +<pre> +SharedPreferences.OnSharedPreferenceChangeListener listener = + new SharedPreferences.OnSharedPreferenceChangeListener() { + public void onSharedPreferenceChanged(SharedPreferences prefs, String key) { + // listener implementation + } +}; +prefs.registerOnSharedPreferenceChangeListener(listener); +</pre> + +<h2 id="NetworkUsage">管理网络使用情况</h2> + + +<p>从 Android 4.0 +开始,通过系统的“设置”应用,用户可以了解自己的应用在前台和后台使用的网络数据量。然后,用户可以据此禁止具体的应用使用后台数据。 +为了避免用户禁止您的应用从后台访问数据,您应该有效地使用数据连接,并允许用户通过应用设置优化应用的数据使用。 + +<p> + +<p>例如,您可以允许用户控制应用同步数据的频率,控制应用是否仅在有 +Wi-Fi 时才执行上传/下载操作,以及控制应用能否在漫游时使用数据,等等。为用户提供这些控件后,即使数据使用量接近他们在系统“设置”中设置的限制,他们也不大可能禁止您的应用访问数据,因为他们可以精确地控制应用使用的数据量。 + + +</p> + +<p>在 {@link android.preference.PreferenceActivity} +中添加必要的首选项来控制应用的数据使用习惯后,您应立即在清单文件中为 {@link +android.content.Intent#ACTION_MANAGE_NETWORK_USAGE} 添加 Intent 过滤器。例如:</p> + +<pre> +<activity android:name="SettingsActivity" ... > + <intent-filter> + <action android:name="android.intent.action.MANAGE_NETWORK_USAGE" /> + <category android:name="android.intent.category.DEFAULT" /> + </intent-filter> +</activity> +</pre> + +<p>此 Intent +过滤器指示系统此 Activity 控制应用的数据使用情况。因此,当用户从系统的“设置”应用检查应用所使用的数据量时,可以使用“查看应用设置”按钮启动 +{@link android.preference.PreferenceActivity},这样,用户就能够优化应用使用的数据量。 + +<em></em></p> + + + + + + + +<h2 id="Custom">构建自定义首选项</h2> + +<p>Android 框架包括各种 +{@link android.preference.Preference} +子类,您可以使用它们为各种不同类型的设置构建 UI。不过,您可能会发现自己需要的设置没有内置解决方案,例如,数字选取器或日期选取器。 +在这种情况下,您将需要通过扩展 +{@link android.preference.Preference} 类或其他子类之一来创建自定义首选项。</p> + +<p>扩展 {@link android.preference.Preference} +类时,您需要执行以下几项重要操作:</p> + +<ul> + <li>指定在用户选择设置时显示的用户界面。</li> + <li>适时保存设置的值。</li> + <li>使用显示的当前(默认)值初始化 +{@link android.preference.Preference}。</li> + <li>在系统请求时提供默认值。</li> + <li>如果 {@link android.preference.Preference} +提供自己的 UI(例如对话框),请保存并恢复状态以处理生命周期变更(例如,用户旋转屏幕)。</li> +</ul> + +<p>下文介绍如何完成所有这些任务。</p> + + + +<h3 id="CustomSelected">指定用户界面</h3> + + <p>如果您要直接扩展 {@link android.preference.Preference} 类,则需要实现 +{@link android.preference.Preference#onClick()} +来定义在用户选择该项时发生的操作。不过,大多数自定义设置都会扩展 {@link android.preference.DialogPreference} +以显示对话框,从而简化这一过程。扩展 {@link +android.preference.DialogPreference} 时,必须在类构造函数中调用 {@link +android.preference.DialogPreference#setDialogLayoutResource setDialogLayoutResourcs()} +来指定对话框的布局。</p> + + <p>例如,自定义 {@link +android.preference.DialogPreference} +可以使用下面的构造函数来声明布局并为默认的肯定和否定对话框按钮指定文本:</p> + +<pre> +public class NumberPickerPreference extends DialogPreference { + public NumberPickerPreference(Context context, AttributeSet attrs) { + super(context, attrs); + + setDialogLayoutResource(R.layout.numberpicker_dialog); + setPositiveButtonText(android.R.string.ok); + setNegativeButtonText(android.R.string.cancel); + + setDialogIcon(null); + } + ... +} +</pre> + + + +<h3 id="CustomSave">保存设置的值</h3> + +<p>如果设置的值为整型数或是用于保存布尔值的 +{@link android.preference.Preference#persistBoolean persistBoolean()},则可通过调用 {@link +android.preference.Preference} 类的一个 {@code persist*()} 方法(如 {@link +android.preference.Preference#persistInt persistInt()})随时保存该值。</p> + +<p class="note"><strong>注:</strong>每个 {@link android.preference.Preference} +均只能保存一种数据类型,因此您必须使用适合自定义 +{@link android.preference.Preference} 所用数据类型的 {@code persist*()} 方法。</p> + +<p>至于何时选择保留设置,则可能取决于要扩展的 {@link +android.preference.Preference} 类。如果扩展 +{@link +android.preference.DialogPreference},则只能在对话框因肯定结果(用户选择“确定”按钮)而关闭时保留该值。</p> + +<p>当 {@link android.preference.DialogPreference} 关闭时,系统会调用 {@link +android.preference.DialogPreference#onDialogClosed onDialogClosed()} 方法。该方法包括一个布尔参数,用于指定用户结果是否为“肯定”;如果值为 +<code>true</code>,则表示用户选择的是肯定按钮且您应该保存新值。 +例如: +</p> + +<pre> +@Override +protected void onDialogClosed(boolean positiveResult) { + // When the user selects "OK", persist the new value + if (positiveResult) { + persistInt(mNewValue); + } +} +</pre> + +<p>在此示例中,<code>mNewValue</code> +是一个类成员,可存放设置的当前值。调用 {@link android.preference.Preference#persistInt persistInt()} 会将该值保存到 +{@link android.content.SharedPreferences} 文件(自动使用在此 +{@link android.preference.Preference} 的 XML 文件中指定的键)。</p> + + +<h3 id="CustomInitialize">初始化当前值</h3> + +<p>系统将 {@link android.preference.Preference} 添加到屏幕时,会调用 +{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} +来通知您设置是否具有保留值。如果没有保留值,则此调用将为您提供默认值。 +</p> + +<p>{@link android.preference.Preference#onSetInitialValue onSetInitialValue()} +方法传递一个布尔值 (<code>restorePersistedValue</code>),以指示是否已为该设置保留值。 +如果值为 <code>true</code>,则应通过调用 +{@link +android.preference.Preference} 类的一个 {@code getPersisted*()} 方法(如整型值对应的 {@link +android.preference.Preference#getPersistedInt getPersistedInt()})来检索保留值。通常,您会需要检索保留值,以便能够正确更新 UI 来反映之前保存的值。 + +</p> + +<p>如果 <code>restorePersistedValue</code> 为 +<code>false</code>,则应使用在第二个参数中传递的默认值。</p> + +<pre> +@Override +protected void onSetInitialValue(boolean restorePersistedValue, Object defaultValue) { + if (restorePersistedValue) { + // Restore existing state + mCurrentValue = this.getPersistedInt(DEFAULT_VALUE); + } else { + // Set default state from the XML attribute + mCurrentValue = (Integer) defaultValue; + persistInt(mCurrentValue); + } +} +</pre> + +<p>每种 {@code getPersisted*()} +方法均采用一个参数,用于指定在实际上没有保留值或该键不存在时所要使用的默认值。在上述示例中,当 +{@link +android.preference.Preference#getPersistedInt getPersistedInt()} 不能返回保留值时,局部常量用于指定默认值。</p> + +<p class="caution"><strong>注意:</strong>您<strong>不能</strong>使用 +<code>defaultValue</code> 作为 {@code getPersisted*()} 方法中的默认值,因为当 +<code>restorePersistedValue</code> 为 <code>true</code> 时,其值始终为 null。</p> + + +<h3 id="CustomDefault">提供默认值</h3> + +<p>如果 {@link android.preference.Preference} +类的实例指定一个默认值(使用 {@code android:defaultValue} +属性),则在实例化对象以检索该值时,系统会调用 {@link android.preference.Preference#onGetDefaultValue +onGetDefaultValue()}。您必须实现此方法,系统才能将默认值保存在 {@link +android.content.SharedPreferences} 中。 +例如:</p> + +<pre> +@Override +protected Object onGetDefaultValue(TypedArray a, int index) { + return a.getInteger(index, DEFAULT_VALUE); +} +</pre> + +<p>方法参数可提供您所需的一切:属性的数组和 +{@code android:defaultValue}(必须检索的值)的索引位置。之所以必须实现此方法以从该属性中提取默认值,是因为您必须为此属性指定在未定义属性值时所要使用的局部默认值。 + +</p> + + + +<h3 id="CustomSaveState">保存和恢复首选项的状态</h3> + +<p>正如布局中的 {@link android.view.View} +一样,在重启 Activity 或片段时(例如,用户旋转屏幕),{@link android.preference.Preference} +子类也负责保存并恢复其状态。要正确保存并恢复 +{@link android.preference.Preference} 类的状态,您必须实现生命周期回调方法 +{@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()}。</p> + +<p>{@link android.preference.Preference} 的状态由实现 +{@link android.os.Parcelable} 接口的对象定义。Android 框架为您提供此类对象,作为定义状态对象({@link +android.preference.Preference.BaseSavedState} +类)的起点。</p> + +<p>要定义 {@link android.preference.Preference} 类保存其状态的方式,您应该扩展 +{@link android.preference.Preference.BaseSavedState} 类。您只需重写几种方法并定义 +{@link android.preference.Preference.BaseSavedState#CREATOR} +对象。</p> + +<p>对于大多数应用,如果 {@link android.preference.Preference} +子类保存除整型数以外的其他数据类型,则可复制下列实现并直接更改处理 +{@code value} 的行。</p> + +<pre> +private static class SavedState extends BaseSavedState { + // Member that holds the setting's value + // Change this data type to match the type saved by your Preference + int value; + + public SavedState(Parcelable superState) { + super(superState); + } + + public SavedState(Parcel source) { + super(source); + // Get the current preference's value + value = source.readInt(); // Change this to read the appropriate data type + } + + @Override + public void writeToParcel(Parcel dest, int flags) { + super.writeToParcel(dest, flags); + // Write the preference's value + dest.writeInt(value); // Change this to write the appropriate data type + } + + // Standard creator object using an instance of this class + public static final Parcelable.Creator<SavedState> CREATOR = + new Parcelable.Creator<SavedState>() { + + public SavedState createFromParcel(Parcel in) { + return new SavedState(in); + } + + public SavedState[] newArray(int size) { + return new SavedState[size]; + } + }; +} +</pre> + +<p>如果将上述 {@link android.preference.Preference.BaseSavedState} +实现添加到您的应用(通常,作为 {@link android.preference.Preference} +子类的子类),则需要为 +{@link android.preference.Preference} 子类实现 {@link android.preference.Preference#onSaveInstanceState +onSaveInstanceState()} 和 {@link +android.preference.Preference#onRestoreInstanceState onRestoreInstanceState()} 方法。</p> + +<p>例如:</p> + +<pre> +@Override +protected Parcelable onSaveInstanceState() { + final Parcelable superState = super.onSaveInstanceState(); + // Check whether this Preference is persistent (continually saved) + if (isPersistent()) { + // No need to save instance state since it's persistent, + // use superclass state + return superState; + } + + // Create instance of custom BaseSavedState + final SavedState myState = new SavedState(superState); + // Set the state's value with the class member that holds current + // setting value + myState.value = mNewValue; + return myState; +} + +@Override +protected void onRestoreInstanceState(Parcelable state) { + // Check whether we saved the state in onSaveInstanceState + if (state == null || !state.getClass().equals(SavedState.class)) { + // Didn't save the state, so call superclass + super.onRestoreInstanceState(state); + return; + } + + // Cast state to custom BaseSavedState and pass to superclass + SavedState myState = (SavedState) state; + super.onRestoreInstanceState(myState.getSuperState()); + + // Set this Preference's widget to reflect the restored state + mNumberPicker.setValue(myState.value); +} +</pre> + diff --git a/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd b/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd new file mode 100644 index 000000000000..f9e976302d34 --- /dev/null +++ b/docs/html-intl/intl/zh-cn/guide/topics/ui/ui-events.jd @@ -0,0 +1,291 @@ +page.title=输入事件 +parent.title=用户界面 +parent.link=index.html +@jd:body + +<div id="qv-wrapper"> +<div id="qv"> + <h2>本文内容</h2> + <ol> + <li><a href="#EventListeners">事件侦听器</a></li> + <li><a href="#EventHandlers">事件处理程序</a></li> + <li><a href="#TouchMode">触摸模式</a></li> + <li><a href="#HandlingFocus">处理焦点</a></li> + </ol> + +</div> +</div> + +<p>在 Android 系统中,从用户与应用的交互中截获事件的方法不止一种。如考虑截获用户界面内的事件,则可从用户与之交互的特定视图对象中捕获事件。 + +为此,View 类提供了多种方法。</p> + +<p>在您将用于构建布局的各种 View 类中,您可能会注意到几种看起来适用于 +UI 事件的公共回调方法。当该对象上发生相应的操作时,Android +框架会调用这些方法。例如,在触摸一个视图对象(例如“按钮”)时,对该对象调用 +<code>onTouchEvent()</code> 方法。不过,为了截获此事件,您必须扩展 View 类并重写该方法。 +然而,为了处理此类事件而扩展每个视图对象并不现实。 +正因如此,View 类还包含一系列嵌套接口以及您可以更加轻松定义的回调。 +这些接口称为<a href="#EventListeners">事件侦听器</a>,是您捕获用户与 +UI 之间交互的票证。</p> + +<p>尽管您通常会使用事件侦听器来侦听用户交互,但有时您确实需要扩展 View 类以构建自定义组件。 +也许,您想扩展 +{@link android.widget.Button} +类来丰富某些内容的样式。在这种情况下,您将能够使用该类的<a href="#EventHandlers">事件处理程序</a>为类定义默认事件行为。 +</p> + + +<h2 id="EventListeners">事件侦听器</h2> + +<p>事件侦听器是 {@link android.view.View} +类中包含一个回调方法的接口。当用户与 UI 项目之间的交互触发已注册此视图的侦听器时,Android +框架将调用这些方法。</p> + +<p>各事件侦听器接口包含的回调方法如下:</p> + +<dl> + <dt><code>onClick()</code></dt> + <dd>在 {@link android.view.View.OnClickListener} 中。 +当用户触摸项目(处于触摸模式下)时,或者使用导航键或轨迹球聚焦于项目,然后按适用的“Enter”键或按下轨迹球时,将调用此方法。 + +</dd> + <dt><code>onLongClick()</code></dt> + <dd>在 {@link android.view.View.OnLongClickListener} 中。 +当用户触摸并按住项目(处于触摸模式下)者,或者使用导航键或轨迹球聚焦于项目,然后按住适用的“Enter”键或按住轨迹球(持续一秒钟)时,将调用此方法。 + +</dd> + <dt><code>onFocusChange()</code></dt> + <dd>在 {@link android.view.View.OnFocusChangeListener} 中。 +当用户使用导航键或轨迹球导航到或远离项目时,将调用此方法。</dd> + <dt><code>onKey()</code></dt> + <dd>在 {@link android.view.View.OnKeyListener} 中。 +当用户聚焦于项目并按下或释放设备上的硬按键时,将调用此方法。</dd> + <dt><code>onTouch()</code></dt> + <dd>在 {@link android.view.View.OnTouchListener} 中。 +当用户执行可视为触摸事件的操作时,其中包括按下、释放或屏幕上的任何移动手势(在项目边界内),将调用此方法。 +</dd> + <dt><code>onCreateContextMenu()</code></dt> + <dd>在 {@link android.view.View.OnCreateContextMenuListener} 中。 +当(因持续“长按”而)生成上下文菜单时,将调用此方法。请参阅<a href="{@docRoot}guide/topics/ui/menus.html#context-menu">菜单</a>开发者指南中有关上下文菜单的阐述。 + +</dd> +</dl> + +<p>这些方法是其相应接口的唯一成员。要定义其中一个方法并处理事件,请在 Activity 中实现嵌套接口或将其定义为匿名类。然后,将实现的实例传递给相应的 +<code>View.set...Listener()</code> +方法。 +(例如,调用 +<code>{@link android.view.View#setOnClickListener(View.OnClickListener) setOnClickListener()}</code> +并向其传递 {@link android.view.View.OnClickListener OnClickListener} 实现。)</p> + +<p>以下示例显示了如何为按钮注册点击侦听器。 </p> + +<pre> +// Create an anonymous implementation of OnClickListener +private OnClickListener mCorkyListener = new OnClickListener() { + public void onClick(View v) { + // do something when the button is clicked + } +}; + +protected void onCreate(Bundle savedValues) { + ... + // Capture our button from layout + Button button = (Button)findViewById(R.id.corky); + // Register the onClick listener with the implementation above + button.setOnClickListener(mCorkyListener); + ... +} +</pre> + +<p>您可能还会发现,将 +OnClickListener 作为 Activity 的一部分来实现更为方便。这样可以避免加载额外的类和分配对象。例如:</p> +<pre> +public class ExampleActivity extends Activity implements OnClickListener { + protected void onCreate(Bundle savedValues) { + ... + Button button = (Button)findViewById(R.id.corky); + button.setOnClickListener(this); + } + + // Implement the OnClickListener callback + public void onClick(View v) { + // do something when the button is clicked + } + ... +} +</pre> + +<p>请注意,上述示例中的 +<code>onClick()</code> 回调没有返回值,但是其他某些事件侦听器方法必须返回布尔值。具体原因取决于事件。 +对于这几个事件侦听器,必须返回布尔值的原因如下:</p> +<ul> + <li><code>{@link android.view.View.OnLongClickListener#onLongClick(View) onLongClick()}</code>: +此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。 +也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何点击侦听器,则返回“false”。 + +<em></em><em></em></li> + <li><code>{@link android.view.View.OnKeyListener#onKey(View,int,KeyEvent) onKey()}</code>: +此方法返回一个布尔值,表示您是否已处理完事件,以及是否应该将它继续传下去。 + 也就是说,返回“true”表示您已经处理事件且事件应就此停止;如果您尚未处理事件和/或事件应该继续传递给其他任何按键侦听器,则返回“false”。 + +<em></em><em></em></li> + <li><code>{@link android.view.View.OnTouchListener#onTouch(View,MotionEvent) onTouch()}</code>: +此方法返回一个布尔值,表示侦听器是否处理完此事件。重要的是,此事件可以拥有多个分先后顺序的操作。 +因此,如果在收到关闭操作事件时返回“false”,则表示您并未处理完此事件,而且对其后续操作也不感兴趣。 + +<em></em>因此,您无需执行事件内的任何其他操作,如手势或最终操作事件。 +</li> +</ul> + +<p>请记住,硬按键事件总是传递给目前处于焦点的视图对象。它们从视图 +层次结构的顶部开始分派,然后向下,直至到达合适的目的地。如果您的视图对象(或视图对象的子项)目前具有焦点,那么您可以看到事件经由 +<code>{@link android.view.View#dispatchKeyEvent(KeyEvent) +dispatchKeyEvent()}</code> 方法的分派过程。除了通过视图捕获按键事件,您还可以使用 +<code>{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}</code> +和 <code>{@link android.app.Activity#onKeyUp(int,KeyEvent) onKeyUp()}</code> 接收 Activity 内部的所有事件。</p> + +<p>此外,考虑应用的文本输入时,请记住:许多设备只有软件输入法。 +此类方法无需基于按键;某些可能使用语音输入、手写等。尽管输入法提供了类似键盘的界面,但它通常<strong>不会</strong>触发 +<code>{@link android.app.Activity#onKeyDown(int,KeyEvent) onKeyDown()}</code> +系列的事件。除非您要将应用限制为带有硬键盘的设备,否则,在设计 UI 时切勿要求必须通过特定按键进行控制。 + +特别是,当用户按下返回键时,不要依赖这些方法验证输入;请改用 +{@link android.view.inputmethod.EditorInfo#IME_ACTION_DONE} +等操作让输入法知晓您的应用预计会作何反应,这样,可以通过一种有意义的方式更改其 UI。不要推断软件输入法应如何工作,只要相信它能够为应用提供已设置格式的文本即可。 +</p> + +<p class="note"><strong>注:</strong>Android 会先调用事件处理程序,然后从类定义调用合适的默认处理程序。 +因此,从这些事件侦听器返回“true”会停止将事件传播到其他事件侦听器,还会阻止回调视图对象中的默认事件处理程序。<em></em> + +因此,在返回“true”时请确保您要终止事件。<em></em></p> + + +<h2 id="EventHandlers">事件处理程序</h2> + +<p>如果您从视图构建自定义组件,则将能够定义几种用作默认事件处理程序的回调方法。在有关<a href="{@docRoot}guide/topics/ui/custom-components.html">自定义组件</a>的文档中,您将了解某些用于事件处理的常见回调,其中包括: + + + +</p> +<ul> + <li><code>{@link android.view.View#onKeyDown}</code>:在发生新的按键事件时调用</li> + <li><code>{@link android.view.View#onKeyUp}</code>:在发生按键弹起事件时调用</li> + <li><code>{@link android.view.View#onTrackballEvent}</code>:在发生轨迹球运动事件时调用</li> + <li><code>{@link android.view.View#onTouchEvent}</code>:在发生触摸屏运动事件时调用</li> + <li><code>{@link android.view.View#onFocusChanged}</code>:在视图获得或失去焦点时调用</li> +</ul> +<p>还有一些其他方法值得您注意,尽管它们并非 View 类的一部分,但可能会直接影响所能采取的事件处理方式。 +因此,在管理布局内更复杂的事件时,请考虑使用以下其他方法: +</p> +<ul> + <li><code>{@link android.app.Activity#dispatchTouchEvent(MotionEvent) + Activity.dispatchTouchEvent(MotionEvent)}</code>:此方法允许 {@link + android.app.Activity} 在分派给窗口之前截获所有触摸事件。</li> + <li><code>{@link android.view.ViewGroup#onInterceptTouchEvent(MotionEvent) + ViewGroup.onInterceptTouchEvent(MotionEvent)}</code>:此方法允许 {@link + android.view.ViewGroup} 监视分派给子视图的事件。</li> + <li><code>{@link android.view.ViewParent#requestDisallowInterceptTouchEvent(boolean) + ViewParent.requestDisallowInterceptTouchEvent(boolean)}</code>: +对父视图调用此方法表明不应使用 <code>{@link + android.view.ViewGroup#onInterceptTouchEvent(MotionEvent)}</code> 截获触摸事件。</li> +</ul> + +<h2 id="TouchMode">触摸模式</h2> +<p> +当用户使用方向键或轨迹球导航用户界面时,必须聚焦到可操作项目上(如按钮),以便用户看到将接受输入的对象。 + +但是,如果设备具有触摸功能且用户开始通过触摸界面与之交互,则不再需要突出显示项目或聚焦到特定视图对象上。 + +因此,有一种交互模式称为“触摸模式”。 + +</p> +<p> +对于支持触摸功能的设备,当用户触摸屏幕时,设备会立即进入触摸模式。 +自此以后,只有 +{@link android.view.View#isFocusableInTouchMode} +为“true”的视图才可聚焦,如文本编辑小工具。其他可触摸的视图(如按钮)在用户触摸时不会获得焦点;按下时它们只是触发点击侦听器。 + +</p> +<p> +无论何时,只要用户点击方向键或滚动轨迹球,设备就会退出触摸模式并找到一个视图使其获得焦点。 +现在,用户可在不触摸屏幕的情况下继续与用户界面交互。 + +</p> +<p> +整个系统(所有窗口和 Activity)都将保持触摸模式状态。要查询当前状态,您可以调用 +{@link android.view.View#isInTouchMode} +来检查设备目前是否处于触摸模式。 +</p> + + +<h2 id="HandlingFocus">处理焦点</h2> + +<p>该框架将处理例行焦点移动来响应用户输入。其中包括在视图被删除或隐藏时或在新视图变得可用时更改焦点。 + +视图对象表示愿意通过 +<code>{@link android.view.View#isFocusable()}</code> 方法获得焦点。要设置视图能否获得焦点,请调用 +<code>{@link android.view.View#setFocusable(boolean) setFocusable()}</code>。在触摸模式中,您可以使用 +<code>{@link android.view.View#isFocusableInTouchMode()}</code> 查询视图是否允许聚焦,而且可以使用 +<code>{@link android.view.View#setFocusableInTouchMode(boolean) setFocusableInTouchMode()}</code> 对此进行更改。 +</p> + +<p>焦点移动所使用的算法会查找指定方向上距离最近的元素。 +在极少数情况下,默认算法可能与开发者的期望行为不一致。 +在这些情况下,您可以在布局文件中显式重写以下 +XML 属性: +<var>nextFocusDown</var>、 <var>nextFocusLeft</var>、 <var>nextFocusRight</var>和 +<var>nextFocusUp</var>。将其中一个属性添加到失去焦点的视图。 +<em></em>将属性的值定义为应该聚焦的视图的 +ID。<em></em>例如:</p> +<pre> +<LinearLayout + android:orientation="vertical" + ... > + <Button android:id="@+id/top" + android:nextFocusUp="@+id/bottom" + ... /> + <Button android:id="@+id/bottom" + android:nextFocusDown="@+id/top" + ... /> +</LinearLayout> +</pre> + +<p>一般来说,在此垂直布局中,从第一个按钮向上导航或从第二个按钮向下导航,焦点都不会移到任何其他位置。 +现在,顶部按钮已将底部按钮定义为 + <var>nextFocusUp</var> (反之亦然),因而导航焦点将自上而下和自下而上循环往复。 +</p> + +<p>若要将一个视图声明为在 UI 中可聚焦(传统上并非如此),请在布局声明中将 <code>android:focusable</code> XML 属性添加到该视图。将值设置为 + + <var>true</var>。此外,您还可以使用 +<code>android:focusableInTouchMode</code> 将 Vew 声明为在触摸模式下可聚焦。</p> +<p>要请求要获得焦点的特定视图,请调用 <code>{@link android.view.View#requestFocus()}</code>。</p> +<p>要侦听焦点事件(视图获得或失去焦点时会收到通知),请使用 +<code>{@link android.view.View.OnFocusChangeListener#onFocusChange(View,boolean) onFocusChange()}</code>, +如上文的<a href="#EventListeners">事件侦听器</a>部分中所述。</p> + + + +<!-- +<h2 is="EventCycle">Event Cycle</h2> + <p>The basic cycle of a View is as follows:</p> + <ol> + <li>An event comes in and is dispatched to the appropriate View. The View + handles the event and notifies any listeners.</li> + <li>If, in the course of processing the event, the View's bounds may need + to be changed, the View will call {@link android.view.View#requestLayout()}.</li> + <li>Similarly, if in the course of processing the event the View's appearance + may need to be changed, the View will call {@link android.view.View#invalidate()}.</li> + <li>If either {@link android.view.View#requestLayout()} or {@link android.view.View#invalidate()} were called, + the framework will take care of measuring, laying out, and drawing the tree + as appropriate.</li> + </ol> + + <p class="note"><strong>Note:</strong> The entire View tree is single threaded. You must always be on + the UI thread when calling any method on any View. + If you are doing work on other threads and want to update the state of a View + from that thread, you should use a {@link android.os.Handler}. + </p> +--> |