重拾Activity(三)Activity知识

这篇文章是从安卓开发者文档找到的Activity资料进行整理的(篇幅较长,用于个人回顾)

目录

这篇文章是从安卓开发者文档找到的Activity资料进行整理的(篇幅较长,用于个人回顾)

Activity

创建 Activity

实现用户界面

在清单文件中声明 Activity

启动 Activity

 

 

返回结果

启动 Activity 以获得结果

结束 Activity

管理 Activity 生命周期

实现生命周期回调

活动状态和从内存中弹出

保存 Activity 状态

处理配置变更

协调 Activity

理解任务和后台堆栈

管理任务

定义启动模式

处理亲和力

清理后栈

开始一项任务

流程和应用程序生命周期

多窗口支持

概览

多窗口生命周期

针对多窗口模式配置应用

布局属性

在多窗口模式中运行应用

多窗口模式中被禁用的功能

多窗口变更通知和查询

进入画中画模式

在多窗口模式中启动新活动

支持拖放

测试应用的多窗口支持

配置测试设备

如果应用并非使用N Preview SDK构建

如果支持多窗口模式

如果已禁用多窗口支持

应用程序快捷方式概述

快捷方式类型

快捷方式限制

创建快捷方式

创建静态快捷方式

自定义属性值

配置内部元素

创建动态快捷方式

创建固定快捷方式

创建自定义快捷方式活动

测试快捷方式

管理快捷方式

快捷方式行为

快捷方式可见性

快捷方式显示顺序

管理多种意图和活动

分配多个意图

从另一个开始一项活动

设置意图标志

更新快捷方式

处理系统区域设置更改

跟踪快捷方式用法

禁用快捷方式

限速

备份还原

快捷方式的最佳做法

应用小部件概述

小部件类型

信息小部件

收集小部件

控制小部件

混合小部件

小部件限制

手势

分子

设计指南

小部件内容

小部件调整大小

布局考虑因素

小部件配置

清单

构建应用程序小部件

基础

在清单中声明应用程序小部件

添加AppWidgetProviderInfo元数据

创建应用程序小组件布局

将边距添加到App Widgets

使用AppWidgetProvider类

接收App Widget广播Intents

固定App小部件

创建应用程序小组件配置活动

从配置活动更新App Widget

设置预览图像

将App小部件与集合一起使用

样品申请

使用集合实现app小部件

坚持数据

保持收集数据新鲜

构建应用程序小部件主机

绑定应用程序小部件

在Android 4.0及更低版本上绑定应用小部件

在Android 4.1及更高版本上绑定应用小部件

主持人的责任

您针对哪个版本?


Activity

Activity 是一个应用组件,用户可与其提供的屏幕进行交互,以执行拨打电话、拍摄照片、发送电子邮件或查看地图等操作。 每个 Activity 都会获得一个用于绘制其用户界面的窗口。窗口通常会充满屏幕,但也可小于屏幕并浮动在其他窗口之上。

一个应用通常由多个彼此松散联系的 Activity 组成。 一般会指定应用中的某个 Activity 为“主”Activity,即首次启动应用时呈现给用户的那个 Activity。 而且每个 Activity 均可启动另一个 Activity,以便执行不同的操作。 每次新 Activity 启动时,前一 Activity 便会停止,但系统会在堆栈(“返回栈”)中保留该 Activity。 当新 Activity 启动时,系统会将其推送到返回栈上,并取得用户焦点。 返回栈遵循基本的“后进先出”堆栈机制,因此,当用户完成当前 Activity 并按“返回”按钮时,系统会从堆栈中将其弹出(并销毁),然后恢复前一 Activity。 (任务和返回栈文档中对返回栈有更详细的阐述。)

当一个 Activity 因某个新 Activity 启动而停止时,系统会通过该 Activity 的生命周期回调方法通知其这一状态变化。Activity 因状态变化—系统是创建 Activity、停止 Activity、恢复 Activity 还是销毁 Activity— 而收到的回调方法可能有若干种,每一种回调都会为您提供执行与该状态变化相应的特定操作的机会。 例如,停止时,您的 Activity 应释放任何大型对象,例如网络或数据库连接。 当 Activity 恢复时,您可以重新获取所需资源,并恢复执行中断的操作。 这些状态转变都是 Activity 生命周期的一部分。

本文的其余部分阐述有关如何创建和使用 Activity 的基础知识(包括对 Activity 生命周期工作方式的全面阐述),以便您正确管理各种 Activity 状态之间的转变。

创建 Activity

要创建 Activity,您必须创建 Activity 的子类(或使用其现有子类)。您需要在子类中实现 Activity 在其生命周期的各种状态之间转变时(例如创建 Activity、停止 Activity、恢复 Activity 或销毁 Activity 时)系统调用的回调方法。 两个最重要的回调方法是:

onCreate()

您必须实现此方法。系统会在创建您的 Activity 时调用此方法。您应该在实现内初始化 Activity 的必需组件。 最重要的是,您必须在此方法内调用 setContentView(),以定义 Activity 用户界面的布局。

onPause()

系统将此方法作为用户离开 Activity 的第一个信号(但并不总是意味着 Activity 会被销毁)进行调用。 您通常应该在此方法内确认在当前用户会话结束后仍然有效的任何更改(因为用户可能不会返回)。

您还应使用几种其他生命周期回调方法,以便提供流畅的 Activity 间用户体验,以及处理导致您的 Activity 停止甚至被销毁的意外中断。 后文的管理 Activity 生命周期部分对所有生命周期回调方法进行了阐述。

实现用户界面

Activity 的用户界面是由层级式视图 — 衍生自 View 类的对象 — 提供的。每个视图都控制 Activity 窗口内的特定矩形空间,可对用户交互作出响应。 例如,视图可以是在用户触摸时启动某项操作的按钮。

您可以利用 Android 提供的许多现成视图设计和组织您的布局。“小部件”是提供按钮、文本字段、复选框或仅仅是一幅图像等屏幕视觉(交互式)元素的视图。 “布局”是衍生自 ViewGroup 的视图,为其子视图提供唯一布局模型,例如线性布局、网格布局或相对布局。 您还可以为 View 类和 ViewGroup 类创建子类(或使用其现有子类)来自行创建小部件和布局,然后将它们应用于您的 Activity 布局。

利用视图定义布局的最常见方法是借助保存在您的应用资源内的 XML 布局文件。这样一来,您就可以将用户界面的设计与定义 Activity 行为的源代码分开维护。 您可以通过 setContentView() 将布局设置为 Activity 的 UI,从而传递布局的资源 ID。不过,您也可以在 Activity 代码中创建新 View,并通过将新 View 插入 ViewGroup 来创建视图层次,然后通过将根 ViewGroup 传递到 setContentView() 来使用该布局。

在清单文件中声明 Activity

您必须在清单文件中声明您的 Activity,这样系统才能访问它。 要声明您的 Activity,请打开您的清单文件,并将  元素添加为  元素的子项。例如:


  
      
      ...
  
  ...

 

您还可以在此元素中加入几个其他特性,以定义 Activity 标签、Activity 图标或风格主题等用于设置 Activity UI 风格的属性。 android:name 属性是唯一必需的属性—它指定 Activity 的类名。应用一旦发布,即不应更改此类名,否则,可能会破坏诸如应用快捷方式等一些功能(请阅读博客文章 Things That Cannot Change [不能更改的内容])。

请参阅  元素参考文档,了解有关在清单文件中声明 Activity 的详细信息。

使用 Intent 过滤器

 元素还可指定各种 Intent 过滤器—使用  元素—以声明其他应用组件激活它的方法。

当您使用 Android SDK 工具创建新应用时,系统自动为您创建的存根 Activity 包含一个 Intent 过滤器,其中声明了该 Activity 响应“主”操作且应置于“launcher”类别内。 Intent 过滤器的内容如下所示:


    
        
        
    

 

 元素指定这是应用的“主”入口点。 元素指定此 Activity 应列入系统的应用启动器内(以便用户启动该 Activity)。

如果您打算让应用成为独立应用,不允许其他应用激活其 Activity,则您不需要任何其他 Intent 过滤器。 正如前例所示,只应有一个 Activity 具有“主”操作和“launcher”类别。 您不想提供给其他应用的 Activity 不应有任何 Intent 过滤器,您可以利用显式 Intent 自行启动它们(下文对此做了阐述)。

不过,如果您想让 Activity 对衍生自其他应用(以及您的自有应用)的隐式 Intent 作出响应,则必须为 Activity 定义其他 Intent 过滤器。 对于您想要作出响应的每一个 Intent 类型,您都必须加入相应的 ,其中包括一个 元素,还可选择性地包括一个  元素和/或一个  元素。这些元素指定您的 Activity 可以响应的 Intent 类型。

如需了解有关您的 Activity 如何响应 Intent 的详细信息,请参阅 Intent 和 Intent 过滤器文档。

启动 Activity

您可以通过调用 startActivity(),并将其传递给描述您想启动的 Activity 的 Intent 来启动另一个 Activity。Intent 对象会指定您想启动的具体 Activity 或描述您想执行的操作类型(系统会为您选择合适的 Activity,甚至是来自其他应用的 Activity)。 Intent 对象还可能携带少量供所启动 Activity 使用的数据。

在您的自有应用内工作时,您经常只需要启动某个已知 Activity。 您可以通过使用类名创建一个显式定义您想启动的 Activity 的 Intent 对象来实现此目的。 例如,可以通过以下代码让一个 Activity 启动另一个名为 SignInActivity 的 Activity:

Intent intent = new Intent(this, SignInActivity.class);
startActivity(intent);

 

不过,您的应用可能还需要利用您的 Activity 数据执行某项操作,例如发送电子邮件、短信或状态更新。 在这种情况下,您的应用自身可能不具有执行此类操作所需的 Activity,因此您可以改为利用设备上其他应用提供的 Activity 为您执行这些操作。 这便是 Intent 对象的真正价值所在 — 您可以创建一个 Intent 对象,对您想执行的操作进行描述,系统会从其他应用启动相应的 Activity。 如果有多个 Activity 可以处理 Intent,则用户可以选择要使用哪一个。 例如,如果您想允许用户发送电子邮件,可以创建以下 Intent:

Intent intent = new Intent(Intent.ACTION_SEND);
intent.putExtra(Intent.EXTRA_EMAIL, recipientArray);
startActivity(intent);

 

添加到 Intent 中的 EXTRA_EMAIL extra 是一个字符串数组,其中包含应将电子邮件发送到的电子邮件地址。 当电子邮件应用响应此 Intent 时,它会读取 extra 中提供的字符串数组,并将它们放入电子邮件撰写窗体的“收件人”字段。 在这种情况下,电子邮件应用的 Activity 启动,并且当用户完成操作时,您的 Activity 会恢复执行。

如果在非activity去启动一个activity,你需要额外添加如下flags:

//在安卓系统中,在非Acitivity中启动Activity,使用context.startAcitivityt()需要给Intent意图添加此标志:
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

具体Intent的使用参见  重拾Activity(二)Intent和Intent过滤器

 

 

返回结果

如果您想将结果返回给调用您 Activity 的 Activity,只需调用 setResult() 指定结果代码和结果 Intent。当您的操作完成且用户应返回原始 Activity 时,调用 finish() 关闭(和销毁)您的 Activity。例如:

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

您必须始终为结果指定结果代码。通常,结果代码是 RESULT_OK 或 RESULT_CANCELED。之后您可以根据需要为 Intent 提供额外的数据。

请注意:默认情况下,结果设置为 RESULT_CANCELED。因此,如果用户在完成操作或设置结果前按了返回按钮,原始 Activity 会收到“已取消”的结果。

如果您只需返回指示若干结果选项之一的整数,则可以将结果代码设置为大于 0 的任何值。如果您使用结果代码传递整数,且无需包括 Intent,则可以调用 setResult() 并仅传递结果代码。例如:

setResult(RESULT_COLOR_RED);
finish();

在这种情况下,只有几个可能的结果,因此结果代码是一个本地定义的整数(大于 0)。当您向自己应用中的 Activity 返回结果时,这将非常有效,因为接收结果的 Activity 可引用公共常量来确定结果代码的值。

请注意:无需检查您的 Activity 通过 startActivity() 还是 startActivityForResult() 启动。如果启动您 Activity 的 Intent 可能需要结果,只需调用 setResult()。如果原始 Activity 已经调用 startActivityForResult(),则系统将向其传递您提供给 setResult() 的结果;否则,会忽略结果。

启动 Activity 以获得结果

有时,您可能需要从启动的 Activity 获得结果。在这种情况下,请通过调用 startActivityForResult()(而非 startActivity())来启动 Activity。 要想在随后收到后续 Activity 的结果,请实现 onActivityResult() 回调方法。 当后续 Activity 完成时,它会使用 Intent 向您的 onActivityResult() 方法返回结果。

例如,您可能希望用户选取其中一位联系人,以便您的 Activity 对该联系人中的信息执行某项操作。 您可以通过以下代码创建此类 Intent 并处理结果:

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

 

上例显示的是,您在处理 Activity 结果时应该在 onActivityResult() 方法中使用的基本逻辑。 第一个条件检查请求是否成功(如果成功,则resultCode 将为 RESULT_OK)以及此结果响应的请求是否已知 — 在此情况下,requestCode与随 startActivityForResult() 发送的第二个参数匹配。 代码通过查询 Intent 中返回的数据(data 参数)从该处开始处理 Activity 结果。

实际情况是,ContentResolver 对一个内容提供程序执行查询,后者返回一个 Cursor,让查询的数据能够被读取。

结束 Activity

您可以通过调用 Activity 的 finish() 方法来结束该 Activity。您还可以通过调用 finishActivity() 结束您之前启动的另一个 Activity。

:在大多数情况下,您不应使用这些方法显式结束 Activity。 正如下文有关 Activity 生命周期的部分所述,Android 系统会为您管理 Activity 的生命周期,因此您无需结束自己的 Activity。 调用这些方法可能对预期的用户体验产生不良影响,因此只应在您确实不想让用户返回此 Activity 实例时使用。

管理 Activity 生命周期

通过实现回调方法管理 Activity 的生命周期对开发强大而又灵活的应用至关重要。 Activity 的生命周期会直接受到 Activity 与其他 Activity、其任务及返回栈的关联性的影响。

Activity 基本上以三种状态存在:

继续(OnResume())

此 Activity 位于屏幕前台并具有用户焦点。(有时也将此状态称作“运行中”。)

暂停(onPause())

另一个 Activity 位于屏幕前台并具有用户焦点,但此 Activity 仍可见。也就是说,另一个 Activity 显示在此 Activity 上方,并且该 Activity 部分透明或未覆盖整个屏幕。 暂停的 Activity 处于完全活动状态(Activity 对象保留在内存中,它保留了所有状态和成员信息,并与窗口管理器保持连接),但在内存极度不足的情况下,可能会被系统终止。

停止(onStop())

该 Activity 被另一个 Activity 完全遮盖(该 Activity 目前位于“后台”)。 已停止的 Activity 同样仍处于活动状态(Activity 对象保留在内存中,它保留了所有状态和成员信息,但与窗口管理器连接)。 不过,它对用户不再可见,在他处需要内存时可能会被系统终止。

如果 Activity 处于暂停或停止状态,系统可通过要求其结束(调用其 finish() 方法)或直接终止其进程,将其从内存中删除。(将其结束或终止后)再次打开 Activity 时,必须重建。

实现生命周期回调

当一个 Activity 转入和转出上述不同状态时,系统会通过各种回调方法向其发出通知。 所有回调方法都是挂钩,您可以在 Activity 状态发生变化时替代这些挂钩来执行相应操作。 以下框架 Activity 包括每一个基本生命周期方法:

public class ExampleActivity extends Activity {
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // The activity is being created.
    }
    @Override
    protected void onStart(){
        super.onStart();
        // The activity is about to become visible.
    }
    @Override
    protected void onResume(){
        super.onResume();
        // The activity has become visible (it is now "resumed").
    }
    @Override
    protected void onPause(){
        super.onPause();
        // Another activity is taking focus (this activity is about to be "paused").
    }
    @Override
    protected void onStop(){
        super.onStop();
        // The activity is no longer visible (it is now "stopped")
    }
    @Override
    protected void onDestroy(){
        super.onDestroy();
        // The activity is about to be destroyed.
    }
}

 

:正如以上示例所示,您在实现这些生命周期方法时必须始终先调用超类实现,然后再执行任何操作。

这些方法共同定义 Activity 的整个生命周期。您可以通过实现这些方法监控 Activity 生命周期中的三个嵌套循环:

  • Activity 的整个生命周期发生在 onCreate() 调用与 onDestroy() 调用之间。您的 Activity 应在 onCreate() 中执行“全局”状态设置(例如定义布局),并释放 onDestroy() 中的所有其余资源。例如,如果您的 Activity 有一个在后台运行的线程,用于从网络上下载数据,它可能会在 onCreate() 中创建该线程,然后在 onDestroy() 中停止该线程。
  • Activity 的可见生命周期发生在 onStart() 调用与 onStop() 调用之间。在这段时间,用户可以在屏幕上看到 Activity 并与其交互。 例如,当一个新 Activity 启动,并且此 Activity 不再可见时,系统会调用 onStop()。您可以在调用这两个方法之间保留向用户显示 Activity 所需的资源。 例如,您可以在 onStart() 中注册一个 BroadcastReceiver 以监控影响 UI 的变化,并在用户无法再看到您显示的内容时在 onStop() 中将其取消注册。在 Activity 的整个生命周期,当 Activity 在对用户可见和隐藏两种状态中交替变化时,系统可能会多次调用 onStart() 和 onStop()

  • Activity 的前台生命周期发生在 onResume() 调用与 onPause() 调用之间。在这段时间,Activity 位于屏幕上的所有其他 Activity 之前,并具有用户输入焦点。 Activity 可频繁转入和转出前台 — 例如,当设备转入休眠状态或出现对话框时,系统会调用 onPause()。 由于此状态可能经常发生转变,因此这两个方法中应采用适度轻量级的代码,以避免因转变速度慢而让用户等待。

图 1 说明了这些循环以及 Activity 在状态转变期间可能经过的路径。矩形表示回调方法,当 Activity 在不同状态之间转变时,您可以实现这些方法来执行操作。

重拾Activity(三)Activity知识_第1张图片

图 1. Activity 生命周期。

表 1 列出了相同的生命周期回调方法,其中对每一种回调方法做了更详细的描述,并说明了每一种方法在 Activity 整个生命周期内的位置,包括在回调方法完成后系统能否终止 Activity。

表 1. Activity 生命周期回调方法汇总表。 

方法 说明 是否能事后终止? 后接
onCreate() 首次创建 Activity 时调用。 您应该在此方法中执行所有正常的静态设置 — 创建视图、将数据绑定到列表等等。 系统向此方法传递一个 Bundle 对象,其中包含 Activity 的上一状态,不过前提是捕获了该状态(请参阅后文的保存 Activity 状态)。

始终后接 onStart()

onStart()
onRestart() 在 Activity 已停止并即将再次启动前调用。

始终后接 onStart()

onStart()
onStart() 在 Activity 即将对用户可见之前调用。

如果 Activity 转入前台,则后接 onResume(),如果 Activity 转入隐藏状态,则后接 onStop()

onResume() 

onStop()
onResume() 在 Activity 即将开始与用户进行交互之前调用。 此时,Activity 处于 Activity 堆栈的顶层,并具有用户输入焦点。

始终后接 onPause()

onPause()
onPause() 当系统即将开始继续另一个 Activity 时调用。 此方法通常用于确认对持久性数据的未保存更改、停止动画以及其他可能消耗 CPU 的内容,诸如此类。 它应该非常迅速地执行所需操作,因为它返回后,下一个 Activity 才能继续执行。

如果 Activity 返回前台,则后接 onResume(),如果 Activity 转入对用户不可见状态,则后接 onStop()

onResume() 

onStop()
onStop() 在 Activity 对用户不再可见时调用。如果 Activity 被销毁,或另一个 Activity(一个现有 Activity 或新 Activity)继续执行并将其覆盖,就可能发生这种情况。

如果 Activity 恢复与用户的交互,则后接 onRestart(),如果 Activity 被销毁,则后接 onDestroy()

onRestart()

onDestroy()
onDestroy() 在 Activity 被销毁前调用。这是 Activity 将收到的最后调用。 当 Activity 结束(有人对 Activity 调用了 finish()),或系统为节省空间而暂时销毁该 Activity 实例时,可能会调用它。 您可以通过 isFinishing() 方法区分这两种情形。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

名为“是否能事后终止?”的列表示系统是否能在不执行另一行 Activity 代码的情况下,在方法返回后随时终止承载 Activity 的进程。 有三个方法带有“是”标记:(onPause()onStop() 和 onDestroy())。由于 onPause() 是这三个方法中的第一个,因此 Activity 创建后,onPause() 必定成为最后调用的方法,然后才能终止进程 — 如果系统在紧急情况下必须恢复内存,则可能不会调用 onStop() 和 onDestroy()。因此,您应该使用 onPause() 向存储设备写入至关重要的持久性数据(例如用户编辑)。不过,您应该对 onPause() 调用期间必须保留的信息有所选择,因为该方法中的任何阻止过程都会妨碍向下一个 Activity 的转变并拖慢用户体验。

是否能在事后终止?列中标记为“否”的方法可从系统调用它们的一刻起防止承载 Activity 的进程被终止。 因此,在从 onPause() 返回的时间到 onResume() 被调用的时间,系统可以终止 Activity。在 onPause() 被再次调用并返回前,将无法再次终止 Activity。

:根据表 1 中的定义属于技术上无法“终止”的 Activity 仍可能被系统终止 — 但这种情况只有在无任何其他资源的极端情况下才会发生。进程和线程处理文档对可能会终止 Activity 的情况做了更详尽的阐述。

活动状态和从内存中弹出

系统在需要释放RAM时会终止进程; 系统杀死给定进程的可能性取决于当时进程的状态。反过来,进程状态取决于进程中运行的活动的状态。表1显示了进程状态,活动状态和系统终止进程的可能性之间的相关性。

被杀的可能性(Likelihood of being killed) 流程状态(Process state) 活动状态(Activity state)
最小(Least)

前景(有或将要获得焦点)

(Foreground (having or about to get focus))

创建已(Created)
开始(
Started)
恢复(
Resumed)
更多(More)

背景(失去焦点)

(Background (lost focus))

已暂停(Paused)
最(Most)

背景(不可见)

(Background (not visible))

停止(Stopped)

(Empty)

摧毁(Destroyed)

表1.流程生命周期与活动状态之间的关系

系统永远不会直接杀死活动以释放内存。相反,它会杀死活动运行的过程,不仅会破坏活动,还会破坏流程中运行的所有其他活动。要了解在系统启动的进程死亡发生时如何保留和恢复活动的UI状态,请参阅保存和恢复活动状态。

用户还可以使用“设置”下的“应用程序管理器”终止相应的应用程序。

有关一般进程的更多信息,请参阅 进程和线程。有关流程生命周期如何与其中活动状态相关联的更多信息,请参阅该页面的“ 流程生命周期”部分。

保存 Activity 状态

管理 Activity 生命周期的引言部分简要提及,当 Activity 暂停或停止时,Activity 的状态会得到保留。 确实如此,因为当 Activity 暂停或停止时,Activity 对象仍保留在内存中 — 有关其成员和当前状态的所有信息仍处于活动状态。 因此,用户在 Activity 内所做的任何更改都会得到保留,这样一来,当 Activity 返回前台(当它“继续”)时,这些更改仍然存在。

不过,当系统为了恢复内存而销毁某项 Activity 时,Activity 对象也会被销毁,因此系统在继续 Activity 时根本无法让其状态保持完好,而是必须在用户返回 Activity 时重建 Activity 对象。但用户并不知道系统销毁 Activity 后又对其进行了重建,因此他们很可能认为 Activity 状态毫无变化。 在这种情况下,您可以实现另一个回调方法对有关 Activity 状态的信息进行保存,以确保有关 Activity 状态的重要信息得到保留:onSaveInstanceState()

系统会先调用 onSaveInstanceState(),然后再使 Activity 变得易于销毁。系统会向该方法传递一个 Bundle,您可以在其中使用 putString() 和 putInt() 等方法以名称-值对形式保存有关 Activity 状态的信息。然后,如果系统终止您的应用进程,并且用户返回您的 Activity,则系统会重建该 Activity,并将 Bundle 同时传递给 onCreate() 和 onRestoreInstanceState()。您可以使用上述任一方法从 Bundle 提取您保存的状态并恢复该 Activity 状态。如果没有状态信息需要恢复,则传递给您的 Bundle 是空值(如果是首次创建该 Activity,就会出现这种情况)。

重拾Activity(三)Activity知识_第2张图片

图 2. 在两种情况下,Activity 重获用户焦点时可保持状态完好:系统在销毁 Activity 后重建 Activity,Activity 必须恢复之前保存的状态;系统停止 Activity 后继续执行 Activity,并且 Activity 状态保持完好。

:无法保证系统会在销毁您的 Activity 前调用 onSaveInstanceState(),因为存在不需要保存状态的情况(例如用户使用“返回”按钮离开您的 Activity 时,因为用户的行为是在显式关闭 Activity)。 如果系统调用 onSaveInstanceState(),它会在调用 onStop() 之前,并且可能会在调用 onPause() 之前进行调用。

不过,即使您什么都不做,也不实现 onSaveInstanceState()Activity 类的 onSaveInstanceState() 默认实现也会恢复部分 Activity 状态。具体地讲,默认实现会为布局中的每个 View 调用相应的 onSaveInstanceState() 方法,让每个视图都能提供有关自身的应保存信息。Android 框架中几乎每个小部件都会根据需要实现此方法,以便在重建 Activity 时自动保存和恢复对 UI 所做的任何可见更改。例如,EditText 小部件保存用户输入的任何文本,CheckBox 小部件保存复选框的选中或未选中状态。您只需为想要保存其状态的每个小部件提供一个唯一的 ID(通过 android:id 属性)。如果小部件没有 ID,则系统无法保存其状态。

您还可以通过将 android:saveEnabled 属性设置为 "false" 或通过调用 setSaveEnabled() 方法显式阻止布局内的视图保存其状态。您通常不应将该属性停用,但如果您想以不同方式恢复 Activity UI 的状态,就可能需要这样做。

尽管 onSaveInstanceState() 的默认实现会保存有关您的Activity UI 的有用信息,您可能仍需替换它以保存更多信息。例如,您可能需要保存在 Activity 生命周期内发生了变化的成员值(它们可能与 UI 中恢复的值有关联,但默认情况下系统不会恢复储存这些 UI 值的成员)。

由于 onSaveInstanceState() 的默认实现有助于保存 UI 的状态,因此如果您为了保存更多状态信息而替换该方法,应始终先调用 onSaveInstanceState() 的超类实现,然后再执行任何操作。 同样,如果您替换 onRestoreInstanceState() 方法,也应调用它的超类实现,以便默认实现能够恢复视图状态。

:由于无法保证系统会调用 onSaveInstanceState(),因此您只应利用它来记录 Activity 的瞬态(UI 的状态)— 切勿使用它来存储持久性数据,而应使用 onPause() 在用户离开 Activity 后存储持久性数据(例如应保存到数据库的数据)。

您只需旋转设备,让屏幕方向发生变化,就能有效地测试您的应用的状态恢复能力。 当屏幕方向变化时,系统会销毁并重建 Activity,以便应用可供新屏幕配置使用的备用资源。 单凭这一理由,您的 Activity 在重建时能否完全恢复其状态就显得非常重要,因为用户在使用应用时经常需要旋转屏幕。

处理配置变更

有些设备配置可能会在运行时发生变化(例如屏幕方向、键盘可用性及语言)。 发生此类变化时,Android 会重建运行中的 Activity(系统调用 onDestroy(),然后立即调用 onCreate())。此行为旨在通过利用您提供的备用资源(例如适用于不同屏幕方向和屏幕尺寸的不同布局)自动重新加载您的应用来帮助它适应新配置。

如果您对 Activity 进行了适当设计,让它能够按以上所述处理屏幕方向变化带来的重启并恢复 Activity 状态,那么在遭遇 Activity 生命周期中的其他意外事件时,您的应用将具有更强的适应性。

正如上文所述,处理此类重启的最佳方法是利用onSaveInstanceState() 和 onRestoreInstanceState()(或 onCreate())保存并恢复 Activity 的状态。

1.发生配置更改

有许多事件可以触发配置更改。也许最突出的例子是纵向和横向之间的变化。可能导致配置更改的其他情况包括更改语言或输入设备。

发生配置更改时,将销毁并重新创建活动。原来的活动实例将有 onPause(), onStop()和 onDestroy()回调触发。活动的一个新实例将被创建并拥有 , 以及回调触发。 onCreate()onStart()onResume()

使用ViewModel,onSaveInstanceState()方法和/或持久本地存储的组合来保持活动在配置更改中的UI状态。决定如何组合这些选项取决于UI数据的复杂性,应用程序的使用情况以及检索检索速度与内存使用情况的关系。有关保存活动UI状态的更多信息,请参阅保存UI状态。

2.处理多窗口案例

当应用程序进入多窗口模式(Android 7.0(API级别24)及更高级别)时,系统会通知当前正在运行的配置更改活动,从而完成上述生命周期转换。如果已经处于多窗口模式的应用程序调整大小,也会发生此行为。您的活动可以自行处理配置更改,也可以允许系统销毁活动并使用新维度重新创建活动。

有关多窗口生命周期的更多信息,请参阅“ 多窗口支持” 页面的“ 多窗口生命周期”部分 。

在多窗口模式中,尽管有两个应用程序对用户可见,但只有用户与之交互的应用程序位于前台并具有焦点。该活动处于“已恢复”状态,而另一个窗口中的应用程序处于“暂停”状态。

当用户从应用程序A切换到应用程序B时,系统会调用 onPause()应用程序A和onResume()应用程序B.每次用户在应用程序之间切换时,它都会在这两种方法之间切换。

有关多窗口的更多详细信息,请参阅 多窗口支持。

活动或对话框出现在前台

如果前景中出现新活动或对话框,焦点并部分覆盖正在进行的活动,则覆盖的活动将失去焦点并进入暂停状态。然后,系统调用 onPause()它。

当被覆盖的活动返回前景并重新获得焦点时,它会调用onResume()

如果前景中出现新活动或对话框,关注焦点并完全覆盖正在进行的活动,则覆盖的活动将失去焦点并进入“已停止”状态。然后系统快速连续调用onPause()和 onStop()

当覆盖活动的同一实例回来到前台时,系统调用onRestart(), onStart()以及 onResume()对活动。如果它是覆盖活动的新实例,则系统不会调用onRestart(),只调用 onStart()和 onResume()

注意:当用户点击“概览”或“主页”按钮时,系统的行为就像当前活动已完全覆盖一样。

3.用户点击后退按钮 

如果活动是在前台,而用户点击后退按钮,该活动通过转换 onPause(), onStop()和 onDestroy() 回调。除了被销毁之外,活动也从后台堆栈中移除。

重要的是要注意,默认情况下, onSaveInstanceState() 在这种情况下不会触发回调。此行为基于用户点击“ 返回”按钮而不期望返回到活动的同一实例的假设。但是,您可以覆盖该 onBackPressed()方法以实现某些自定义行为,例如“确认退出”对话框。

如果重写该onBackPressed() 方法,我们仍强烈建议您super.onBackPressed()从重写方法调用 。否则,后退按钮行为可能会刺激用户。

4.系统会杀死应用程序进程

如果应用程序在后台并且系统需要为前台应用程序释放额外的内存,则系统可以终止后台应用程序以释放更多内存。要了解有关系统如何决定要销毁哪些进程的更多信息,请阅读“ 活动状态”和“从内存和 进程和应用程序生命周期中弹出”。

要了解在系统终止应用程序进程时如何保存活动UI状态,请参阅 保存和恢复活动状态。

协调 Activity

当一个 Activity 启动另一个 Activity 时,它们都会体验到生命周期转变。第一个 Activity 暂停并停止(但如果它在后台仍然可见,则不会停止)时,同时系统会创建另一个 Activity。 如果这些 Activity 共用保存到磁盘或其他地方的数据,必须了解的是,在创建第二个 Activity 前,第一个 Activity 不会完全停止。更确切地说,启动第二个 Activity 的过程与停止第一个 Activity 的过程存在重叠。

生命周期回调的顺序经过明确定义,当两个 Activity 位于同一进程,并且由一个 Activity 启动另一个 Activity 时,其定义尤其明确。 以下是当 Activity A 启动 Activity B 时一系列操作的发生顺序:

  1. Activity A 的 onPause() 方法执行。
  2. Activity B 的 onCreate()onStart() 和 onResume() 方法依次执行。(Activity B 现在具有用户焦点。)
  3. 然后,如果 Activity A 在屏幕上不再可见,则其 onStop() 方法执行。

您可以利用这种可预测的生命周期回调顺序管理从一个 Activity 到另一个 Activity 的信息转变。 例如,如果您必须在第一个 Activity 停止时向数据库写入数据,以便下一个 Activity 能够读取该数据,则应在 onPause() 而不是 onStop() 执行期间向数据库写入数据。

理解任务和后台堆栈

任务是用户在执行特定作业时与之交互的活动的集合。活动按堆栈排列 - 后堆栈 - 按打开每个活动的顺序排列。例如,电子邮件应用可能有一个活动来显示新消息列表。当用户选择消息时,将打开一个新活动以查看该消息。此新活动将添加到后台堆栈中。如果用户按下“ 返回”按钮,则该新活动将完成并从堆栈中弹出。以下视频概述了后端堆栈的工作原理。

当应用程序在多窗口环境中同时运行时 ,在Android 7.0(API级别24)及更高版本中受支持,系统会为每个窗口单独管理任务; 每个窗口可能有多个任务。对于 在Chromebook上运行的Android应用程序也是如此:系统基于每个窗口管理任务或任务组。

设备主屏幕是大多数任务的起始位置。当用户触摸应用程序启动器中的图标(或主屏幕上的快捷方式)时,该应用程序的任务将进入前台。如果应用程序不存在任务(最近未使用该应用程序),则会创建一个新任务,该应用程序的“主”活动将作为堆栈中的根活动打开。

当前活动开始另一个活动时,新活动将被推到堆栈顶部并获得焦点。之前的活动仍在堆栈中,但已停止。当活动停止时,系统将保留其用户界面的当前状态。当用户按下“ 返回” 按钮时,当前活动将从堆栈顶部弹出(活动被销毁),之前的活动将恢复(其UI的先前状态将恢复)。堆栈中的活动永远不会重新排列,只能在当前活动启动时从堆栈中推送并弹出到堆栈中,并在用户使用Back退出时弹出按钮。因此,后堆栈作为“后进先出”对象结构操作。图1显示了这种行为,时间轴显示了活动之间的进度以及每个时间点的当前后栈。

重拾Activity(三)Activity知识_第3张图片

图1.表示任务中的每个新活动如何将项添加到后台堆栈。当用户按下“ 返回”按钮时,将破坏当前活动并恢复先前的活动。

如果用户继续按Back,则弹出堆栈中的每个活动以显示前一个活动,直到用户返回主屏幕(或任务开始时运行的任何活动)。从堆栈中删除所有活动后,该任务不再存在。

重拾Activity(三)Activity知识_第4张图片

图2.两个任务:任务B在前台接收用户交互,而任务A在后台,等待恢复。

任务是一个内聚单元,当用户开始新任务或通过主页按钮进入主屏幕时,可以移动到“背景” 。在后台,任务中的所有活动都会停止,但任务的后台堆栈仍然完好无损 - 任务在发生另一项任务时就失去了焦点,如图2所示。然后任务可以返回到“前景“所以用户可以从他们中断的地方继续前进。例如,假设当前任务(任务A)在其堆栈中有三个活动 - 在当前活动下有两个活动。用户按下Home 按钮,然后从应用启动器启动一个新的应用程序。出现主屏幕时,任务A进入后台。当新应用程序启动时,系统会使用自己的一系列活动为该应用程序(任务B)启动任务。在与该应用程序交互之后,用户再次返回Home并选择最初启动任务A的应用程序。现在,任务A进入前台 - 其堆栈中的所有三个活动都是完整的,并且堆栈顶部的活动将恢复。此时,用户还可以通过返回主页并选择启动该任务的应用程序图标(或从“ 最近”屏幕中选择应用程序的任务)切换回任务B. 这是Android上的多任务处理的一个示例。

注意:可以在后台同时保存多个任务。但是,如果用户同时运行许多后台任务,系统可能会开始销毁后台活动以恢复内存,从而导致活动状态丢失。

重拾Activity(三)Activity知识_第5张图片

图3.单个活动多次实例化。

由于后备堆栈中的活动永远不会重新排列,如果您的应用程序允许用户从多个活动启动特定活动,则会创建该活动的新实例并将其推送到堆栈(而不是带来任何先前的活动实例)到顶部)。因此,应用程序中的一个活动可能会被多次实例化(甚至来自不同的任务),如图3所示。因此,如果用户使用“ 后退”按钮向后导航,则活动的每个实例都按顺序显示被打开(每个都有自己的UI状态)。但是,如果您不希望多次实例化活动,则可以修改此行为。有关如何执行此操作将在后面的“ 管理任务”一节中讨论。

总结活动和任务的默认行为:

  • 当活动A启动活动B时,活动A停止,但系统保留其状态(例如滚动位置和输入到表单中的文本)。如果用户在活动B中按下“ 返回”按钮,则活动A将恢复其状态。
  • 当用户通过按Home键离开任务时,当前活动将停止,其任务将进入后台。系统保留任务中每个活动的状态。如果用户稍后通过选择开始任务的启动器图标来恢复任务,则任务将到达前台并在堆栈顶部恢复活动。
  • 如果用户按下“ 返回”按钮,则会从堆栈中弹出当前活动并将其销毁。堆栈中的先前活动已恢复。当活动被销毁时,系统 不会保留活动的状态。
  • 活动可以多次实例化,甚至可以从其他任务实例化。

导航设计

有关Android导航如何在Android上运行的详细信息,请参阅Android Design的导航指南。

管理任务

Android管理任务和后台堆栈的方式,如上所述 - 通过将所有活动连续启动到同一任务和“后进先出”堆栈 - 适用于大多数应用程序,您不必担心关于您的活动如何与任务相关联或它们如何存在于后台堆栈中。但是,您可能决定要中断正常行为。也许您希望应用程序中的活动在启动时开始新任务(而不是放在当前任务中); 或者,当你开始一个活动时,你想要提出它的现有实例(而不是在后面的堆栈顶部创建一个新的实例); 或者,您希望在用户离开任务时清除除了根活动之外的所有活动的后备堆栈。

您可以使用 清单元素中的属性以及传递给的intent中的标记 来执行这些操作 startActivity()

在这方面,您可以使用的主要属性是: 

  • taskAffinity
  • launchMode
  • allowTaskReparenting
  • clearTaskOnLaunch
  • alwaysRetainTaskState
  • finishOnTaskLaunch

您可以使用的主要意图标志是:

  • FLAG_ACTIVITY_NEW_TASK
  • FLAG_ACTIVITY_CLEAR_TOP
  • FLAG_ACTIVITY_SINGLE_TOP

在以下部分中,您将了解如何使用这些清单属性和意图标志来定义活动与任务的关联方式以及它们在后端堆栈中的行为方式。

另外,单独讨论的是如何在“ 最近”屏幕中表示和管理任务和活动的注意事项。有关 详细信息,请参阅最近屏幕。通常,您应该允许系统在“ 最近”屏幕中定义您的任务和活动的表示方式,而不需要修改此行为。

警告:大多数应用程序不应该中断活动和任务的默认行为。如果您确定您的活动需要修改默认行为,请谨慎使用并确保在启动期间以及使用“ 返回”按钮从其他活动和任务导航回活动时测试活动的可用性。请务必测试可能与用户预期行为冲突的导航行为。

定义启动模式

启动模式允许您定义活动的新实例与当前任务的关联方式。您可以通过两种方式定义不同的启动模式:

  • 使用清单文件

    在清单文件中声明活动时,可以指定活动在启动时应如何与任务关联。

  • 使用Intent标志

    当您调用时startActivity(),您可以在其中包含一个标志Intent,声明新活动应如何(或是否)与当前任务相关联。

因此,如果活动A启动活动B,活动B可以在其清单中定义它应该如何与当前任务相关联(如果有的话),活动A也可以请求活动B应该如何与当前任务相关联。如果两个活动都定义了活动B应该如何与任务相关联,则活动A的请求(如意图中所定义)将遵循活动B的请求(如其清单中所定义)。

注意:清单文件可用的某些启动模式不可用作intent的标志,同样,可以在清单中定义一些可用作intent的标志的启动模式。

使用清单文件

在清单文件中声明活动时,可以使用 元素的launchMode属性指定活动应如何与任务关联。

launchMode属性指定有关如何将活动启动到任务的指令。您可以为launchMode 属性分配四种不同的启动模式 :

"standard" (默认模式)

默认。系统在启动它的任务中创建活动的新实例,并将意图路由到该实例。活动可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例。

"singleTop"

如果活动的实例已存在于当前任务的顶部,则系统通过调用其onNewIntent()方法将意图路由到该实例,而不是创建活动的新实例。活动可以多次实例化,每个实例可以属于不同的任务,一个任务可以有多个实例(但只有当后端堆栈顶部的活动不是活动的现有实例时)。

例如,假设任务的后台堆栈由根活动A组成,其中活动B,C和D位于顶部(堆栈为ABCD; D位于顶部)。意图到达类型D的活动。如果D具有默认"standard"启动模式,则启动该类的新实例并且堆栈变为ABCDD。但是,如果D的启动模式是"singleTop",D的现有实例接收意图onNewIntent(),因为它位于堆栈的顶部 - 堆栈仍然是ABCD。但是,如果意图到达类型B的活动,则将新的B实例添加到堆栈中,即使其启动模式为"singleTop"

注意:创建活动的新实例时,用户可以按“ 返回”按钮返回上一个活动。但是,当活动的现有实例处理新意图时,用户无法在新意图到达之前按“ 返回”按钮返回活动状态onNewIntent()

"singleTask"

系统创建新任务并在新任务的根目录下实例化活动。但是,如果活动的实例已存在于单独的任务中,则系统会通过调用其onNewIntent()方法将意图路由到现有实例 ,而不是创建新实例。一次只能存在一个活动实例。

注意:虽然活动在新任务中启动,但“ 后退”按钮仍会将用户返回到上一个活动。

"singleInstance"

相同"singleTask",区别在于:系统不启动任何其他活动纳入控股实例的任务。活动始终是其任务的唯一成员; 任何由此开始的活动都在一个单独的任务中打开。

作为另一个示例,Android浏览器应用程序声明Web浏览器活动应始终在其自己的任务中打开 - 通过singleTask元素中指定启动模式。这意味着,如果您的应用发出打开Android浏览器的意图,则其活动不会与您的应用放在同一任务中。相反,要么为浏览器启动新任务,要么如果浏览器已经在后台运行任务,则该任务将被提前处理新意图。

无论活动是在新任务中启动还是在与启动它的活动相同的任务中启动,“ 返回”按钮始终会将用户带到上一个活动。但是,如果启动指定singleTask启动模式的活动,则如果后台任务中存在该活动的实例,则将整个任务带到前台。此时,后端堆栈现在包括在堆栈顶部提出的任务中的所有活动。图4说明了这种情况。

重拾Activity(三)Activity知识_第6张图片

图4.表示如何将启动模式“singleTask”的活动添加到后台堆栈。如果活动已经是具有自己的后台堆栈的后台任务的一部分,那么整个后台堆栈也会在当前任务的基础上出现。

有关在清单文件中使用启动模式的更多信息,请参阅  元素文档,其中launchMode详细讨论了属性和接受的值。

注意:您为具有该launchMode属性的活动指定的行为可以被包含在启动您的活动的intent中的标记覆盖,如下一节中所述。

使用Intent标志

启动活动时,您可以通过在传递到的intent中包含标志来修改活动与其任务的默认关联startActivity()。您可以用来修改默认行为的标志是:

FLAG_ACTIVITY_NEW_TASK

在新任务中启动活动。如果任务已在为您正在启动的活动运行,则该任务将恢复到前台,并恢复其上一个状态,并且活动将接收新的意图onNewIntent()

这会产生与"singleTask" launchMode上一节中讨论的值相同的行为。

FLAG_ACTIVITY_SINGLE_TOP

如果正在启动的活动是当前活动(在后台堆栈的顶部),则现有实例将接收调用onNewIntent(),而不是创建活动的新实例。

这会产生与"singleTop" launchMode上一节中讨论的值相同的行为。

FLAG_ACTIVITY_CLEAR_TOP

如果正在启动的活动已在当前任务中运行,则不会启动该活动的新实例,而是销毁其上的所有其他活动,并将此意图传递给活动的恢复实例(现在开启)顶部),通过onNewIntent())。

launchMode 生成此行为的属性没有值。

FLAG_ACTIVITY_CLEAR_TOP最常用的是 FLAG_ACTIVITY_NEW_TASK。当一起使用时,这些标志是一种在另一个任务中定位现有活动并将其置于可以响应意图的位置的方法。

注意:如果指定活动的启动模式是 "standard",它也会从堆栈中删除,并在其位置启动一个新实例来处理传入的意图。这是因为在启动模式下,总是为新意图创建一个新实例"standard"

处理亲和力

亲和力表示活动更喜欢哪个任务属于。默认情况下,同一应用程序中的所有活动都具有彼此的关联。因此,默认情况下,同一个应用中的所有活动都希望处于同一任务中。但是,您可以修改活动的默认关联。在不同应用中定义的活动可以共享亲和力,或者可以为同一应用中定义的活动分配不同的任务亲和力。

您可以使用 元素的taskAffinity属性修改任何给定活动的亲缘关系

taskAffinity 属性采用字符串值,该值必须与元素中声明的默认包名称唯一,因为系统使用该名称来标识应用程序的默认任务关联。 

亲和力在两种情况下起作用:

  • 当启动活动的意图包含 FLAG_ACTIVITY_NEW_TASK 标志时。

    默认情况下,新活动将启动到调用的活动的任务中startActivity()。它被调到与调用者相同的后栈。但是,如果传递给的意图 startActivity() 包含FLAG_ACTIVITY_NEW_TASK 标志,则系统会查找另一个任务以容纳新活动。通常,这是一项新任务。但是,它不一定是。如果已存在与新活动具有相同亲缘关系的现有任务,则会将活动启动到该任务中。如果没有,它开始一项新任务。

    如果此标志导致活动开始新任务并且用户按下主页按钮以离开它,则必须有某种方式让用户导航回任务。某些实体(例如通知管理器)总是在外部任务中启动活动,从不作为自己的一部分,因此它们总是放入FLAG_ACTIVITY_NEW_TASK它们传递给的意图中 startActivity()。如果您有可以由可能使用此标志的外部实体调用的活动,请注意用户有一种独立的方式来返回已启动的任务,例如使用启动器图标(任务的根活动)有一个CATEGORY_LAUNCHERintent过滤器;请参阅下面的Starting a task部分。

  • 当活动的属性设置为。 allowTaskReparenting"true"

    在这种情况下,当该任务到达前台时,活动可以从它开始的任务移动到它具有亲和力的任务。

    例如,假设在所选城市中报告天气状况的活动被定义为旅行应用程序的一部分。它与同一应用程序中的其他活动(默认的应用程序关联)具有相同的亲和力,并允许使用此属性重新生成父项。当您的某个活动启动天气报告者活动时,它最初属于与您的活动相同的任务。但是,当旅行应用程序的任务到达前台时,天气报告者活动将重新分配给该任务并显示在其中。

提示:如果APK文件从用户的角度包含多个“app”,您可能希望使用该taskAffinity 属性为与每个“app”关联的活动分配不同的亲和力。

清理后栈

如果用户长时间离开任务,系统将清除除根活动之外的所有活动的任务。当用户再次返回任务时,仅恢复根活动。系统以这种方式运行,因为在很长一段时间之后,用户可能已经放弃了之前正在做的事情,并且正在返回任务以开始新事物。

您可以使用一些活动属性来修改此行为:

alwaysRetainTaskState

如果将此属性设置为"true"任务的根活动,则不会发生刚才描述的默认行为。即使经过很长一段时间,任务仍会保留堆栈中的所有活动。

clearTaskOnLaunch

如果将此属性设置为"true"任务的根活动,则只要用户离开任务并返回到该任务,就会将堆栈清除为根活动。换句话说,它与之相反 。即使在离开任务片刻之后,用户也始终返回初始状态的任务。 alwaysRetainTaskState

finishOnTaskLaunch

此属性类似clearTaskOnLaunch,但它在单个活动上运行,而不是整个任务。它还可以导致任何活动消失,包括根活动。当它设置为时"true",活动仍然是当前会话的任务的一部分。如果用户离开然后返回任务,则它不再存在。

开始一项任务

您可以通过为任务提供具有"android.intent.action.MAIN"指定操作和 "android.intent.category.LAUNCHER" 指定类别的intent过滤器,将活动设置为任务的入口点 。例如:


    
        
        
    
    ...

这种意图过滤器会导致活动的图标和标签显示在应用程序启动器中,从而为用户提供启动活动并返回其在启动后随时创建的任务的方法。

第二种能力很重要:用户必须能够离开任务,然后使用此活动启动器返回该任务。为此,标志着活动一如既往启动任务,这两种启动模式"singleTask"和 "singleInstance",应使用只有当活动有一个 ACTION_MAIN 和CATEGORY_LAUNCHER过滤器。例如,想象一下,如果缺少过滤器会发生什么:一个intent启动一个"singleTask"活动,启动一个新任务,并且用户花一些时间在该任务中工作。然后用户按下主页 按钮。该任务现在发送到后台并且不可见。现在用户无法返回任务,因为它未在应用启动器中显示。

对于您不希望用户能够返回活动的情况,请将 元素 设置 finishOnTaskLaunch 为"true"(请参阅清除后台堆栈)。

有关如何在“ 概述”屏幕中表示和管理任务和活动的更多信息,请 参阅“最近用户”屏幕。

流程和应用程序生命周期

在大多数情况下,每个Android应用程序都在自己的Linux进程中运行。当需要运行某些代码时,将为应用程序创建此过程,并且该过程将一直运行,直到不再需要它为止,并且 系统需要回收其内存以供其他应用程序使用。

Android的一个不寻常的基本特征是应用程序进程的生命周期不是由应用程序本身直接控制的。相反,它由系统通过系统知道正在运行的应用程序部分的组合,这些事物对用户的重要性以及系统中可用的总体内存量来确定。

重要的是,应用程序开发人员了解不同应用程序组件(特别是ActivityServiceBroadcastReceiver)影响应用程序的进程的生存期。 不正确使用这些组件会导致系统在执行重要工作时终止应用程序的进程。

进程生命周期错误的一个常见示例是 BroadcastReceiver,当一个线程在其BroadcastReceiver.onReceive() 方法中接收到一个Intent时启动一个线程,然后从该函数返回。一旦返回,系统会认为BroadcastReceiver不再处于活动状态,因此不再需要其托管进程(除非其他应用程序组件处于活动状态)。因此,系统可能随时终止进程以回收内存,并且这样做会终止在进程中运行的生成线程。此问题的解决方案通常是JobService从BroadcastReceiver 安排a ,因此系统知道在该过程中仍有活动的工作。

为了确定在内存不足时应该杀死哪些进程,Android会根据其中运行的组件以及这些组件的状态将每个进程置于“重要性层次结构”中。这些流程类型(按重要性顺序):

系统中只会有一些这样的进程,如果内存太低甚至这些进程都不能继续运行,这些只会作为最后的手段被杀死。通常,此时,设备已达到内存分页状态,因此需要此操作以保持用户界面响应。

  1. 一个前台进程是需要什么样的用户目前正在做一个。各种应用程序组件可以使其包含进程以不同方式被视为前景。如果满足以下任何条件,则认为进程处于前台:
    • 它正在Activity 用户正在与之交互的屏幕顶部运行(其 onResume()方法已被调用)。
    • 它有一个BroadcastReceiver当前正在运行的(它的BroadcastReceiver.onReceive()方法正在执行)。
    • 它有一个Service当前在其回调之一执行代码(Service.onCreate(), Service.onStart(),或Service.onDestroy())。
  2. 一个可见的过程正在进行用户当前意识到的工作,因此杀死它会对用户体验产生明显的负面影响。在以下条件下,可以看到一个过程:
    • 它运行的Activity 是屏幕上的用户可见但不在前台(其 onPause()方法已被调用)。例如,如果前景活动显示为允许在其后面看到前一个活动的对话框,则可能会发生这种情况。
    • 它有一个Service作为前台服务运行的Service.startForeground()(通过要求系统将服务视为用户知道的或者对他们基本可见的东西)。
    • 它正在托管系统用于用户知道的特定功能的服务,例如动态壁纸,输入法服务等。

    在系统中运行的这些进程的数量比前台进程更少,但仍然是相对受控的。这些进程被认为是非常重要的,除非需要这样做以保持所有前台进程运行,否则不会被杀死。

  3. 一个服务的过程是一个拿着Service 已经开始与 startService()方法。虽然用户无法直接看到这些进程,但它们通常是用户关心的事情(例如后台网络数据上传或下载),因此系统将始终保持此类进程运行,除非没有足够的内存来保留所有进程前景和可见过程。

    已经运行了很长时间(例如30分钟或更长时间)的服务可能会降级,以允许其进程下降到下面描述的缓存LRU列表。这有助于避免出现内存泄漏或其他问题的长时间运行服务占用大量RAM而导致系统无法有效使用缓存进程的情况。

  4. 缓存过程是一个当前没有必要,因此系统是免费时别处需要存储器根据需要将其杀死。在正常运行的系统中,这些是内存管理中涉及的唯一过程:运行良好的系统将始终具有多个缓存进程(用于在应用程序之间进行更有效的切换),并根据需要定期终止最旧的进程。只有在非常关键(且不可取)的情况下,系统才会到达所有缓存进程被杀死的点,并且必须开始终止服务进程。

    这些进程通常包含一个或多个Activity当前对用户不可见的实例(该 onStop()方法已被调用并返回)。如果他们正确地实现了他们的活动生命周期(参见Activity更多细节),当系统杀死这样的过程时,它不会影响用户返回该应用程序时的体验:它可以在相关活动重新创建时恢复以前保存的状态。新进程。

    这些进程保存在伪LRU列表中,列表中的最后一个进程是第一个被回收内存的进程。在此列表上排序的确切策略是平台的实现细节,但通常它会尝试在其他类型的进程之前保留更多有用的进程(一个托管用户的主应用程序,他们看到的最后一个活动等)。还可以应用其他用于终止进程的策略:对允许的进程数量的硬限制,对进程可以持续缓存的时间量的限制等。

在决定如何对流程进行分类时,系统将根据流程中当前活动的所有组件中找到的最重要级别做出决策。见ActivityService和 BroadcastReceiver文档,了解每个组件有助于过程的整体生命周期的更多细节。每个类的文档都更详细地描述了它们如何影响其应用程序的整个生命周期。

还可以基于进程对其具有的其他依赖性来增加进程的优先级。例如,如果进程A已Service使用Context.BIND_AUTO_CREATE 标志绑定到a 或正在使用 ContentProvider进程B,那么进程B的分类将始终至少与进程A一样重要。

多窗口支持

Android N添加了对同时显示多个应用窗口的支持。在手持设备上,两个应用可以在“分屏”模式中左右并排或上下并显显示。 在电视设备上,应用可以使用“画中画”模式,在用户与另一个应用交互的同时继续播放视频。

如果您使用N预览SDK构建应用,则可以配置应用处理多窗口显示的方法。例如,您可以指定活动的最小允许尺寸。您还可以禁用应用的多窗口显示,确保系统以及全屏模式显示应用。

 

概览

 

用户体验取决于设备:

  • 运行Android N的手持设备具有分屏模式。在此模式中,系统以左右并排或上下并排的方式分屏显示两个应用。用户可以拖动两个应用之间的分界线,放大其中一个应用,同时缩小另一个。
  • 在运行Android N的Nexus Player上,应用能以画中画模式显示,即在用户浏览网页或与其他应用交互的同时继续显示内容。
  • 较大设备的制造商可选择启用自由形状模式,在该模式中,用户可以自由调整各活动的尺寸。若制造商启用此功能,设备将同时具有自由形状模式和分屏模式。

重拾Activity(三)Activity知识_第7张图片

图1.两个应用在分屏模式中左右并排显示。

用户可以通过以下方式切换到多窗口模式:

  • 若用户打开概述屏幕并长按活动标题,则可以拖动该活动至屏幕突出显示的区域,使活动进入多窗口模式。
  • 若用户长按概述按钮,设备上的当前活动将进入多窗口模式,同时将打开概述屏幕,用户可在该屏幕中选择要共享屏幕的另一个活动。

用户可以在两个活动共享屏幕的同时在这两个活动之间拖放数据(在此之前,用户只能在一个活动内部拖放数据)。

 

多窗口生命周期

 

多窗口模式不会更改活动生命周期。

在多窗口模式中,在指定时间只有最近与用户交互过的活动为活动状态。该活动将被视为顶级活动。 所有其他活动虽然可见,但均处于暂停状态。但是,这些已暂停但可见的活动在系统中享有比不可见活动更高的优先级。如果用户与其中一个暂停的活动交互,该活动将恢复,而之前的顶级活动将暂停。

注:在多窗口模式中,用户仍可以看到处于暂停状态的应用。应用在暂停状态下可能仍需要继续其操作。例如,处于暂停模式但可见的视频播放应用应继续显示视频。因此,我们建议播放视频的活动不要暂停其onPause()处理程序中的视频。 暂停应onStop()中的视频,并恢复onStart()中的视频播放。

如处理运行时变更中所述,用户使用多窗口模式显示应用时,系统将通知活动发生配置变更。这也会发生在当用户调整应用大小,或将应用恢复到全屏模式时。该变更与系统通知应用设备从纵向模式切换到横向模式时的活动生命周期影响基本相同,但设备不仅仅是交换尺寸,而是会变更尺寸。如处理运行时变更中所述,您的活动可以自行处理配置变更,或允许系统销毁Activity,并以新的尺寸重新创建该活动。

如果用户调整窗口大小,并在任意维度放大窗口尺寸,系统将调整活动以匹配用户操作,同时根据需要发布运行时变更。如果应用在新公开区域的绘制滞后,系统将使用windowBackground属性或默认windowBackgroundFallback样式属性指定的颜色暂时填充该区域。

 

针对多窗口模式配置应用

 

如果你的应用面向Android N,您可以对应用的活动是否支持多窗口显示以及显示方式进行配置。根据活动的属性设置适用于其任务栈中的所有活动。例如,如果根活动已android:resizeableActivity设定为真,则任务栈中的所有活动都将可以调整大小。

注:如果您使用低于Android N版本的SDK构建多向应用,则用户在多窗口模式中使用应用时,系统将强制调整应用大小。系统将显示对话框,提醒用户应用可能会发生异常。系统不会调整定向应用的大小;如果用户尝试在多窗口模式下打开定向应用,应用将全屏显示。

 

机器人:resizeableActivity

 

清单在的或 节点中设置该属性,启用或禁用多窗口显示:

 

android :resizeableActivity = [ “true” | “false” ]  

 

如果该属性设置为true,Activity将能以分屏和自由形状模式启动。如果此属性设置为false,Activity将不支持多窗口模式。如果该值为false,且用户尝试在多窗口模式下启动,该活动将全屏显示。

如果你的应用面向Android N,但未对该属性指定值,则该属性的值默认设为真。

 

机器人:supportsPictureInPicture

 

在清单文件的节点中设置该属性,指明活动是否支持画中画显示。如果android:resizeableActivity为false,将忽略该属性。

 

android :supportsPictureInPicture = [ “true” | “false” ]  

布局属性

 

对于Android N,清单元素支持以下几种属性,这些属性影响活动在多窗口模式中的行为:

android:defaultWidth

以自由形状模式启动时Activity的默认宽度。

android:defaultHeight

以自由形状模式启动时Activity的默认高度。

android:gravity

以自由形状模式启动时活动的初始位置。请参阅Gravity参考资料,了解合适的值设置。

android:minimalHeightandroid:minimalWidth

分屏和自由形状模式中,活动尺寸低于指定的最小值

例如,以下节点显示了如何指定活动在自由形状模式中显示时活动的默认大小,位置和最小尺寸:

 

 
     

在多窗口模式中运行应用

 

Android N添加了新功能,以支持可在多窗口模式中运行的应用。

 

多窗口模式中被禁用的功能

 

在设备处于多窗口模式中时,某些功能会被禁用或忽略,因为这些功能对与其他活动或应用共享设备屏幕的活动而言没有意义。此类功能包括:

  • 某些系统UI自定义选项将被禁用;例如,在非全屏模式中,应用无法隐藏状态栏。
  • 将系统-忽略对android:screenOrientation属性所作的更改。

 

多窗口变更通知和查询

 

Activity类中各添加了以下新方法,以支持多窗口显示。有关各方法的详细信息,请参阅N预览SDK参考。

Activity.isInMultiWindowMode()

调用该方法以确认活动是否处于多窗口模式。

Activity.isInPictureInPictureMode()

调用该方法以确认Activity是否处于画中画模式。

注:画中画模式是多窗口模式的特例。如果myActivity.isInPictureInPictureMode()返回true,则myActivity.isInMultiWindowMode()也返回true。

Activity.onMultiWindowModeChanged()

活动进入或退出多窗口模式时系统将调用此方法。在活动进入多窗口模式时,系统向该方法传递真值,在退出多窗口模式时,则传递错值。

Activity.onPictureInPictureModeChanged()

活动进入或退出画中画模式时系统将调用此方法。在活动进入画中画模式时,系统向该方法传递真值,在退出画中画模式时,则传递错值。

方法每个还有Fragment版本,例如Fragment.isInMultiWindowMode()

 

进入画中画模式

 

如需在画中画模式中启动Activity,请调用新方法Activity.enterPictureInPictureMode()。如果设备不支持画中画模式,则此方法无效。如需了解详细信息,请参阅画中画文档。

 

在多窗口模式中启动新活动

 

在启动新活动时,用户可以提示系统如果可能,应将新活动显示在当前活动旁边。要执行此操作,可使用标志Intent.FLAG_ACTIVITY_LAUNCH_TO_ADJACENT。传递此标志将请求以下行为:

  • 如果设备处于分屏模式,系统会尝试在启动系统的活动旁创建新活动,这样两个活动将共享屏幕。系统并不一定能实现此操作,但如果可以,系统将使两个活动处于相邻的位置。
  • 如果设备不处于分屏模式,则该标志无效。

如果设备处于自由形状模式,则在启动新活动时,用户可通过调用ActivityOptions.setLaunchBounds()指定新活动的尺寸和屏幕位置。如果设备不处于多窗口模式,则该方法无效。

注:如果您在任务栈中启动活动,该活动将替换屏幕上的活动,并继承其所有的多窗口属性。如果要在多窗口模式中以单独的窗口启动新活动,则必须在新的任务栈中启动此活动。

 

支持拖放

 

用户可以在两个活动共享屏幕的同时在这两个活动之间拖放数据(在此之前,用户只能在一个活动内部拖放数据)。因此,如果您的应用目前不支持拖放功能,您可以在其中添加此功能。

N预览SDK扩展了android.view软件包,以支持跨应用拖放。有关以下类和方法的详细信息,请参阅N预览SDK参考。

android.view.DropPermissions

令牌对象,负责指定对接收拖放数据的应用授予的权限。

View.startDragAndDrop()

View.startDrag()的新别名。要启用跨活动拖放,请传递新标志View.DRAG_FLAG_GLOBAL。如需对接收拖放数据的活动授予URI权限,可根据情况传递新标志View.DRAG_FLAG_GLOBAL_URI_READView.DRAG_FLAG_GLOBAL_URI_WRITE

View.cancelDragAndDrop()

取消当前正在进行的拖动操作。只能由发起拖动操作的应用调用。

View.updateDragShadow()

替换当前正在进行的拖动操作的拖动阴影。只能由发起拖动操作的应用调用。

Activity.requestDropPermissions()

请求使用DragEvent中包含的ClipData传递的内容URI的权限。

 

测试应用的多窗口支持

 

无论您是否针对Android N更新应用,都应验证应用在多窗口模式下的行为,以防用户尝试在运行Android N的设备上以多窗口模式启动应用。

 

配置测试设备

 

如果在设备上安装Android N,则将自动支持分屏模式。

 

如果应用并非使用N Preview SDK构建

 

如果你的应用不是使用N预览SDK构建的,则用户尝试在多窗口模式中使用应用时,系统将强制调整应用大小,除非应用进行了定向声明。

如果您的应用没有进行定向声明,则应在运行Android N的设备上启动应用,并尝试将应用切换到分屏模式。验证并确保在强制调整应用大小时用户体验可接受。

如果应用进行了定向声明,则应尝试将应用切换到多窗口模式。验证并确保执行此操作后,应用仍保持全屏模式。

 

如果支持多窗口模式

 

如果你的应用是使用N预览SDK构建的,并且禁用多窗口支持,则分别在分屏和自由形状模式下验证以下行为。

  • 在全屏模式下启动应用,然后通过长按概述按钮切换到多窗口模式。验证并确保应用正常切换。
  • 你可以按一下概述按钮,再长按应用的标题栏,并将其拖动到屏幕上任一突出显示的区域,从而在多窗口模式中启动应用。
  • 拖动分界线,在分屏模式中调整应用的大小。验证并确保应用正常调整大小且未崩溃,并且必要的UI元素仍可见。
  • 如果您指定了应用的最小尺寸,请尝试将应用尺寸调整到低于最小值。验证并确保无法将应用尺寸调整到低于指定最小值。
  • 完成所有测试后,验证并确保应用性能可以接受。例如,验证并确保调整应用大小后更新UI没有长时间的滞后。

 

测试检查单

 

要在多窗口模式中验证应用性能,请执行以下操作。除非另有说明,否则请分别在分屏和多窗口模式中执行以下操作。

  • 进入和退出多窗口模式。
  • 从您的应用切换到另一个应用,验证并确保应用在非活动但可见的状态下正常运行。例如,如果您的应用在播放视频,则验证并确保在用户与另一个应用交互时视频仍在继续播放。
  • 在分屏模式中,尝试移动分界线,放大或缩小应用。分别在左右和上下并排显示模式中尝试这些操作。验证并确保应用不会崩溃,主要功能可见,且调整操作不需要过长时间。
  • 快速连续执行几次调整操作。验证并确保应用不会崩溃或出现内存泄漏。有关检查应用内存使用率的信息,请参阅查看内存使用率。
  • 在多个不同窗口配置中正常使用应用,验证并确保应用正常运行。验证并确保文本可读,且UI元素大小正常,无影响交互。

 

如果已禁用多窗口支持

 

如果您通过设置Application的android:resizableActivity="false"禁用了多窗口支持,则应在运行Android N的设备上启动应用,并尝试将应用切换到自由形状和分屏模式。验证并确保执行此操作后,应用仍保持全屏模式。

应用程序快捷方式概述

作为开发人员,您可以定义快捷方式以在应用中执行特定操作。这些快捷方式可以显示在支持的启动器中,帮助用户快速启动应用内的常见或推荐任务。

这套指南教您如何 创建和 管理应用程序快捷方式。此外,您将学习一些可以提高快捷方式效果的最佳实践。

 

快捷方式类型

重拾Activity(三)Activity知识_第8张图片

 

图1.使用应用程序快捷方式,您可以显示关键操作并立即将用户深入到您的应用程序中

每个快捷方式引用一个或多个 意图,当用户选择快捷方式时,每个意图都会在应用中启动特定操作。您为应用创建的快捷方式类型取决于应用的关键用例。您可以表示为快捷方式的操作示例包括:

  • 在电子邮件应用中撰写新电子邮件。
  • 将用户导航到地图应用中的特定位置。
  • 在通信应用中向朋友发送消息。
  • 在媒体应用中播放电视节目的下一集。
  • 在游戏应用中加载最后一个保存点。

注意:只有处理Intent.ACTION_MAIN 操作和 Intent.CATEGORY_LAUNCHER 类别的主要活动 - 活动 才能有快捷方式。如果应用程序有多个主要活动,则需要为每个活动定义一组快捷方式。

您可以为您的应用发布以下类型的快捷方式:

  • 静态快捷方式在打包到APK或应用程序包中的资源文件中定义 。
  • 只有在运行时,您的应用才能发布,更新和删除动态快捷方式
  • 如果用户授予权限,则可以在运行时将固定快捷方式添加到受支持的启动器。

    注意:用户还可以通过将应用程序的静态和动态快捷方式复制到启动器上来创建固定快捷方式。

 

快捷方式限制

 

虽然您可以一次为应用程序发布最多五个快捷方式(静态和动态快捷方式组合),但大多数启动器只能显示四个。

但是,用户可以创建的应用程序固定快捷方式的数量没有限制。即使您的应用无法删除固定的快捷方式,它仍然可以 禁用它们。

注意:虽然其他应用无法访问快捷方式中的元数据,但启动器本身可以访问此数据。因此,这些元数据应隐藏敏感的用户信息。

要开始为您的应用创建快捷方式,请参阅以下页面:

  • 创建快捷方式
  • 管理快捷方式
  • 快捷方式的最佳做法

有关可以在快捷方式上执行的操作的更多详细信息,请参阅 ShortcutManager API参考。

 

创建快捷方式

快捷方式可帮助用户快速访问部分应用,从而为用户提供特定类型的内容。

如何使用快捷方式传递内容取决于您的用例以及快捷方式的上下文是应用驱动还是用户驱动。虽然静态快捷方式的上下文不会更改,并且动态快捷方式的上下文会不断更改,但两种情况下的上下文都是由您的应用程序驱动的。如果用户选择他们希望您的应用向他们传递内容的方式,例如使用固定的快捷方式,则上下文由用户定义。以下方案演示了每种快捷方式类型的一些用例:

  • 静态快捷方式 最适合在用户与应用程序交互的整个生命周期内使用一致结构链接到内容的应用程序。由于大多数启动程序 一次 只能显示四个快捷方式,因此静态快捷方式对于常见活动非常有用。例如,如果用户想要以特定方式查看他们的日历或电子邮件,则使用静态快捷方式可确保他们执行例行任务的体验是一致的。
  • 动态快捷方式 用于对上下文敏感的应用中的操作。例如,如果您构建的游戏允许用户在启动时从当前级别开始,则需要经常更新快捷方式。使用动态快捷方式允许每次用户清除级别时更新快捷方式。
  • 固定快捷方式 用于特定的用户驱动操作。例如,用户可能希望将特定网站固定到启动器。这是有益的,因为它允许用户执行自定义操作,例如一步导航到网站,比使用浏览器的默认实例更快。

 

创建静态快捷方式

 

静态快捷方式提供了应用程序中通用操作的链接,这些操作应在应用程序当前版本的生命周期内保持一致。静态快捷方式的良好候选者包括查看已发送消息,设置警报以及显示当天用户的锻炼活动。

要创建静态快捷方式,请完成以下步骤:

  1. 在应用程序的清单文件(AndroidManifest.xml)中,查找其意图过滤器设置为操作和类别的活动。android.intent.action.MAIN android.intent.category.LAUNCHER

  2. 向此活动 添加一个元素,该元素引用定义应用程序快捷方式的资源文件:

    
      
        
          
            
            
          
          
           
        
      
    
  3. 创建一个新的资源文件:res/xml/shortcuts.xml

  4. 在此新资源文件中,添加一个根元素,其中包含元素列表。每个 元素都包含有关静态快捷方式的信息,包括其图标,描述标签以及它在应用程序中启动的意图:

    
      
        
        
        
      
      
    

 

自定义属性值

 

以下列表包含静态快捷方式中不同属性的说明。您必须为android:shortcutId和 提供值android:shortcutShortLabel。所有其他值都是可选的。

android:shortcutId

字符串文字,表示对象对其执行操作时的快捷方式。 ShortcutManager

注意:您不能将此属性的值设置为资源字符串,例如@string/foo

android:shortcutShortLabel

描述快捷方式目的的简明短语。如果可能,将快捷方式的“简短描述”的长度限制为10个字符。

有关更多信息,请参阅。 setShortLabel()

注意:此属性的值必须是资源字符串,例如 @string/shortcut_short_label

android:shortcutLongLabel

描述快捷方式目的的扩展短语。如果有足够的空间,启动器会显示此值而不是android:shortcutShortLabel。如果可能,将快捷方式的“长描述”的长度限制为25个字符。

有关更多信息,请参阅 setLongLabel()

注意:此属性的值必须是资源字符串,例如 @string/shortcut_long_label

android:shortcutDisabledMessage

当用户尝试启动已禁用的快捷方式时,支持的启动程序中显示的消息。该消息应向用户解释为什么现在禁用该快捷方式。如果android:enabled是,则此属性的值无效true

注意:此属性的值必须是资源字符串,例如 @string/shortcut_disabled_message

android:enabled

确定用户是否可以从支持的启动器与快捷方式进行交互。默认值android:enabledtrue。如果将其设置为false,则还应设置一个android:shortcutDisabledMessage解释禁用快捷方式的原因。如果您认为不需要提供此类消息,则最简单的方法是从XML文件中完全删除该快捷方式。

android:icon

启动器在显示用户快捷方式时使用 的位图 或 自适应图标。此值可以是图像的路径,也可以是包含图像的资源文件。尽可能使用自适应图标以提高性能和一致性。

注意:快捷方式图标不能包含 色调。

 

配置内部元素

 

列出应用程序的静态快捷方式的XML文件支持每个元素中的以下元素 。您必须intent为您定义的每个静态快捷方式 包含内部元素。

intent

用户选择快捷方式时系统启动的操作。此意图必须为android:action属性提供值。

注意:intent元素不能包含字符串资源。

您可以为单个快捷方式提供多个意图。有关详细信息,请参阅 管理多个意图和活动, 使用意图和TaskStackBuilder类引用。

categories

为应用程序快捷方式执行的操作类型提供分组,例如创建新的聊天消息。

有关支持的快捷方式类别的列表,请参阅 ShortcutInfo类引用。

 

创建动态快捷方式

 

动态快捷方式提供指向应用内特定的上下文相关操作的链接。这些操作可能会在您的应用使用之间发生变化,甚至在您的应用运行时也会发生变化。动态快捷方式的良好候选者包括呼叫特定人员,导航到特定位置,以及从用户的最后保存点加载游戏。

 ShortcutManagerAPI允许你完成动态快捷键下面的操作:

  • 发布:使用 setDynamicShortcuts()重新定义动态快捷键的完整列表,或者使用 addDynamicShortcuts() 以增加动态快捷键的现有列表。
  • 更新:使用该 updateShortcuts()方法。
  • 删除:使用删除一组动态快捷方式 removeDynamicShortcuts(),或删除所有动态快捷方式removeAllDynamicShortcuts()

有关在快捷方式上执行操作的更多信息,请阅读 管理快捷方式和 ShortcutManager参考。

以下代码段中显示了创建动态快捷方式并将其与您的应用相关联的示例:

注:在的实例ShortcutManager必须使用获得类 Context.getSystemService(Class)的说法ShortcutManager.classContext.getSystemService(String)与争论 Context.SHORTCUT_SERVICE

ShortcutManager shortcutManager = getSystemService(ShortcutManager.class);

ShortcutInfo shortcut = new ShortcutInfo.Builder(context, "id1")
    .setShortLabel("Website")
    .setLongLabel("Open the website")
    .setIcon(Icon.createWithResource(context, R.drawable.icon_website))
    .setIntent(new Intent(Intent.ACTION_VIEW,
                   Uri.parse("https://www.mysite.example.com/")))
    .build();

shortcutManager.setDynamicShortcuts(Arrays.asList(shortcut));

 

创建固定快捷方式

重拾Activity(三)Activity知识_第9张图片

 

图1.应用程序快捷方式和固定快捷方式的外观

在Android 8.0(API级别26)及更高版本上,您可以创建固定快捷方式。与静态和动态快捷方式不同,固定快捷方式在支持的启动器中显示为单独的图标。图1显示了这两种类型的快捷方式之间的区别。

注意:当您尝试将快捷方式固定到支持的启动器上时,用户会收到一个确认对话框,询问他们是否可以固定快捷方式。如果用户不允许固定快捷方式,则启动器会取消该请求。

要使用您的应用固定支持的启动器的快捷方式,请完成以下一系列步骤:

  1. 使用 isRequestPinShortcutSupported()来验证设备的默认启动支持,程序快捷方式的牵制。
  2. ShortcutInfo根据快捷方式是否已存在,以两种方式之一创建对象:

    1. 如果快捷方式已存在,请创建 ShortcutInfo仅包含现有快捷方式ID的对象。系统自动查找并固定与快捷方式相关的所有其他信息。
    2. 如果要固定新快捷方式,请创建一个ShortcutInfo对象,该对象包含新快捷方式 的ID,意图和短标签。

    注意:由于系统会自动对固定快捷方式执行 备份和还原,因此这些快捷方式的ID应包含稳定的常量字符串或服务器端标识符,而不是本地生成的标识符,这些标识符可能在其他设备上没有意义。

  3. 尝试通过调用将快捷方式固定到设备的启动器 requestPinShortcut()。在此过程中,您可以传入一个PendingIntent对象,该对象仅在快捷方式成功固定时通知您的应用。

    注意:如果用户不允许将快捷方式固定到启动器,则您的应用程序不会收到回调。

    固定快捷方式后,您的应用可以使用该 updateShortcuts()方法更新其内容 。有关更多信息,请阅读 更新快捷方式。

以下代码段演示了如何创建固定的快捷方式:

注:在的实例ShortcutManager必须使用获得类 Context.getSystemService(Class)的说法ShortcutManager.classContext.getSystemService(String)与争论 Context.SHORTCUT_SERVICE

ShortcutManager shortcutManager =
        context.getSystemService(ShortcutManager.class);

if (shortcutManager.isRequestPinShortcutSupported()) {
    // Assumes there's already a shortcut with the ID "my-shortcut".
    // The shortcut must be enabled.
    ShortcutInfo pinShortcutInfo =
            new ShortcutInfo.Builder(context, "my-shortcut").build();

    // Create the PendingIntent object only if your app needs to be notified
    // that the user allowed the shortcut to be pinned. Note that, if the
    // pinning operation fails, your app isn't notified. We assume here that the
    // app has implemented a method called createShortcutResultIntent() that
    // returns a broadcast intent.
    Intent pinnedShortcutCallbackIntent =
            shortcutManager.createShortcutResultIntent(pinShortcutInfo);

    // Configure the intent so that your app's broadcast receiver gets
    // the callback successfully.For details, see PendingIntent.getBroadcast().
    PendingIntent successCallback = PendingIntent.getBroadcast(context, /* request code */ 0,
            pinnedShortcutCallbackIntent, /* flags */ 0);

    shortcutManager.requestPinShortcut(pinShortcutInfo,
            successCallback.getIntentSender());
}

注意:另请参阅支持库API, isRequestPinShortcutSupported()以及 requestPinShortcut()适用于Android 7.1(API级别25)及更低版本的API。支持库回退到已弃用的 EXTRA_SHORTCUT_INTENT额外值以尝试固定过程。

 

创建自定义快捷方式活动

重拾Activity(三)Activity知识_第10张图片

 

图2.自定义应用程序快捷方式对话框活动的示例

您还可以创建专门的活动,帮助用户创建快捷方式,完成自定义选项和确认按钮。图2显示了Gmail应用中此类活动的示例。

在应用程序的清单文件中,添加到活动的元素。当用户尝试创建快捷方式时,此声明将设置以下行为: ACTION_CREATE_SHORTCUT 

  1. 系统启动您应用的专业活动。
  2. 用户设置快捷方式的选项。
  3. 用户选择确认按钮。
  4. 您的应用使用该方法创建快捷方式。此方法返回一个,您的应用程序将使用返回到先前执行的活动。createShortcutResultIntent() Intent setResult()
  5. 您的应用程序调用 用于创建自定义快捷方式的活动。 finish()

同样,您的应用可以提示用户在安装后或第一次启动应用时将固定快捷方式添加到主屏幕。此方法很有效,因为它可以帮助您的用户在其普通工作流程中创建快捷方式。

 

测试快捷方式

 

要测试应用的快捷方式,请在具有支持快捷方式的启动器的设备上安装您的应用。然后,执行以下操作:

  • 长按您应用的启动器图标,即可查看您为应用定义的快捷方式。
  • 点击并拖动快捷方式将其固定到设备的启动器。

 

 

管理快捷方式

创建快捷方式后,您可能需要在应用的生命周期内管理它们。例如,您可能希望通过确定用户使用快捷方式完成特定操作的频率来优化您的应用。在另一种情况下,您可能决定禁用固定快捷方式,以防止您的应用执行过时或丢失的操作。本指南介绍了管理快捷方式的这些以及其他几种常用方法。

 

快捷方式行为

 

以下部分包含有关快捷方式行为的一般信息,包括可见性,显示顺序和排名。

 

快捷方式可见性

 

重要安全说明:所有快捷方式信息都存储在 凭据加密存储中,因此您的应用在解锁设备之前无法访问用户的快捷方式。

当用户执行特定手势时,静态快捷方式和动态快捷方式将显示在受支持的启动器中。在当前支持的启动器上,手势是长按应用程序的启动器图标,但实际手势可能与其他启动器应用程序不同。

 LauncherApps类提供了启动应用程序的API来访问快捷。

由于固定的快捷方式出现在启动器本身中,因此它们始终可见。仅在以下情况下才会从启动器中删除固定的快捷方式:

  • 用户将其删除。
  • 与快捷方式关联的应用程序已卸载。
  • 用户通过转到设置>应用和通知,选择应用,然后按 存储>清除存储来清除应用的数据 。

 

快捷方式显示顺序

 

当启动器显示应用程序的快捷方式时,它们应按以下顺序显示:

  1. 静态快捷方式: isDeclaredInManifest()方法返回的快捷方式 true
  2. 动态快捷方式: ShortcutInfo.isDynamic()方法返回的快捷方式 true

内的每个快捷类型(静态和动态),快捷方式以递增的顺序排序 等级根据 ShortcutInfo.getRank()

等级是非负的,连续的整数。当你调用,您可以更新现有的快捷方式的行列 updateShortcuts(List)addDynamicShortcuts(List) setDynamicShortcuts(List)

注意:等级是自动调整的,因此它们对于每种类型的快捷方式(静态或动态)都是唯一的。例如,如果有三个具有等级0,1和2的动态快捷方式,则添加另一个等级为1的动态快捷方式表示将此快捷方式放在第二个位置的请求。作为响应,第三和第四个快捷方式移动到快捷方式列表的底部,其排名分别变为2和3。

 

管理多种意图和活动

 

如果您希望应用在用户激活快捷方式时执行多项操作,则可以将其配置为触发后续活动。您可以通过分配多个意图,从另一个启动一个活动或设置意图标志来完成此操作,具体取决于快捷方式的类型。

 

分配多个意图

 

使用时创建快捷方式,您可以使用 而不是。通过调用,您可以在用户选择快捷方式时在应用程序中启动多个活动,将除列表中最后一个活动之外的所有活动放在 后堆栈上。如果用户随后决定按设备的后退按钮,他们将在您的应用中看到另一项活动,而不是返回设备的启动器。 ShortcutInfo.Builder setIntents() setIntent()setIntents()

注意:当用户选择快捷方式然后按后退键时,您的应用程序将启动与资源文件中列出的快捷方式倒数第二个意图相对应的活动。重复按下后退按钮后,此行为模式继续,直到用户清除快捷方式创建的后堆栈。当用户下一次按下后退按钮时,系统会将它们导航回启动器。

 

从另一个开始一项活动

 

静态快捷方式不能具有自定义意图标志。静态快捷方式的第一个意图将始终具有和设置。这意味着,当应用程序已在运行时,启动静态快捷方式时,应用程序中的所有现有活动都将被销毁。如果不希望出现这种情况,您可以使用trampoline活动或启动其他活动的不可见活动,然后调用: Intent.FLAG_ACTIVITY_NEW_TASKIntent.FLAG_ACTIVITY_CLEAR_TASK Activity.onCreate(Bundle) Activity.finish()

  1. AndroidManifest.xml文件中,trampoline活动应包括属性赋值android:taskAffinity=""
  2. 在快捷方式资源文件中,静态快捷方式中的意图应引用trampoline活动。

有关蹦床活动的更多信息,请阅读 从另一个活动 开始一个活动。

 

设置意图标志

 

可以使用任何Intent标志集发布动态快捷方式 。您最好 Intent.FLAG_ACTIVITY_CLEAR_TASK与其他标志一起指定 。否则,如果您在应用程序运行时尝试启动其他任务,则可能不会显示目标活动。

要了解有关任务和意图标记的更多信息,请阅读 任务和返回堆栈指南。

 

更新快捷方式

 

每个应用程序的启动器图标最多可包含 getMaxShortcutCountPerActivity()多个静态和动态快捷方式。但是,应用程序可以创建的固定快捷方式的数量没有限制。

固定动态快捷方式时,即使发布者将其作为动态快捷方式删除,固定的快捷方式仍然可见并且可以启动。这允许应用程序具有多个 getMaxShortcutCountPerActivity()快捷方式。

举个例子,假设 getMaxShortcutCountPerActivity()是四个:

  1. 聊天应用程序发布四个动态快捷方式,表示最近的四个对话(c1,c2,c3,c4)。
  2. 用户将所有四个快捷键固定。
  3. 之后,用户又启动了三个额外的对话(c5,c6和c7),因此发布者应用程序会重新发布其动态快捷方式。新的动态快捷键列表是:c4,c5,c6,c7。

    该应用程序必须删除c1,c2和c3,因为它无法显示四个以上的动态快捷方式。但是,c1,c2和c3仍然是用户可以访问和启动的固定快捷方式。

    用户现在可以访问总共七个链接到发布者应用中的活动的快捷方式。这是因为总数包括最大快捷方式数和三个固定快捷方式。

  4. 该应用程序可用于 updateShortcuts(List)更新任何现有的七个快捷方式。例如,您可以在聊天对等方的图标发生更改时更新这组快捷方式。
  5.  addDynamicShortcuts(List)和 setDynamicShortcuts(List)方法还可以用来更新与相同ID的现有的快捷方式。但是,它们不能用于更新非动态固定快捷方式,因为这两种方法会尝试将给定的快捷方式列表转换为动态快捷方式。

要详细了解我们的应用程序快捷方式指南,包括更新快捷方式,请阅读 最佳做法。

 

处理系统区域设置更改

 

应用程序应在接收 Intent.ACTION_LOCALE_CHANGED广播时更新动态和固定快捷方式 ,表示系统区域设置已更改。

 

跟踪快捷方式用法

 

要确定应出现静态和动态快捷方式的情况,启动器会检查快捷方式的激活历史记录。当发生以下一事件时reportShortcutUsed(),您可以通过调用方法并将快捷方式的ID传递 给用户来跟踪用户何时在应用内完成特定操作 :

  • 用户选择具有给定ID的快捷方式。
  • 在应用程序内,用户手动完成与同一快捷方式对应的操作。

 

禁用快捷方式

 

由于您的应用及其用户可以将快捷方式固定到设备的启动器,因此这些固定的快捷方式可能会引导用户执行应用中已过期或不再存在的操作。要管理这种情况,您可以禁用不希望用户通过调用选择的 disableShortcuts()快捷方式,这会从静态和动态快捷方式列表中删除指定的快捷方式,并禁用这些快捷方式的任何固定副本。您还可以使用此方法的 重载版本,该方法接受 CharSequence自定义错误消息。当用户尝试启动任何禁用的快捷方式时,将显示该错误消息。

注意:如果在更新应用程序时删除了某些应用程序的静态快捷方式,系统会自动禁用这些快捷方式。

 

限速

 

当使用 setDynamicShortcuts(), addDynamicShortcuts()或 updateShortcuts()方法,记住,你可能只能调用这些方法的时间在一个特定的号码后台程序,与当前在前台没有任何活动或服务的应用程序。可以调用这些方法的特定次数限制称为速率限制。此功能用于防止 ShortcutManager 过度消耗设备资源。

当速率限制处于活动状态时, isRateLimitingActive()返回true。但是,在某些事件期间会重置速率限制,因此即使是后台应用程序也可以调用ShortcutManager 方法,直到再次达到速率限制。这些事件包括以下内容:

  • 一个应用程序来到前台。
  • 系统区域设置更改。
  • 用户对通知执行内联回复操作。

如果在开发或测试期间遇到速率限制,可以从 设备的设置中选择 开发人员选项>重置ShortcutManager速率限制,或者您可以在以下位置输入以下命令: adb

$ adb shell cmd shortcut reset-throttling [ --user your-user-id ]

 

备份还原

 

通过 android:allowBackup="true"在应用程序的清单文件中包含属性分配,您可以允许用户在更改设备时对应用程序执行备份和还原操作。如果您允许备份和还原,请记住以下有关应用程序快捷方式的要点:

  • 静态快捷方式会自动重新发布,但只有在用户在新设备上重新安装应用程序后才能重新发布。
  • 不会备份动态快捷方式,因此您必须在应用中包含逻辑,以便在用户在新设备上打开您的应用时重新发布它们。
  • 固定快捷方式会自动恢复到设备的启动器,但系统不会备份与固定快捷方式关联的图标。因此,您应该在应用中保存固定快捷方式的图像,以便在新设备上轻松恢复它们。

以下代码段显示了如何最好地恢复应用的动态快捷方式,以及如何检查应用的固定快捷方式是否已保留:

public class MainActivity extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        ShortcutManager shortcutManager =
                getSystemService(ShortcutManager.class);

        if (shortcutManager.getDynamicShortcuts().size() == 0) {
            // Application restored. Need to re-publish dynamic shortcuts.
            if (shortcutManager.getPinnedShortcuts().size() > 0) {
                // Pinned shortcuts have been restored. Use
                // updateShortcuts() to make sure they contain
                // up-to-date information.
            }
        }
    }
    // ...
}

快捷方式的最佳做法

在设计和创建应用程序的快捷方式时,请遵循以下准则:

遵循设计准则

要使应用程序的快捷方式与系统应用程序使用的快捷方式在视觉上保持一致,请遵循 应用程序快捷方式设计指南。

仅发布四个不同的快捷方式

虽然API目前支持在任何给定时间为您的应用程序组合最多五个静态和动态快捷方式,但我们建议您仅发布四个不同的快捷方式,以改善其在启动器中的视觉外观。

限制快捷方式描述长度

菜单中的空间有限,在启动器中显示应用程序的快捷方式。如果可能,将快捷方式的“简短描述”的长度限制为10个字符,并将“长描述”的长度限制为25个字符。

有关静态快捷方式标签的详细信息,请参阅 自定义属性值。对于动态和固定的快捷方式,阅读的参考文档和。setLongLabel() setShortLabel()

维护快捷方式和操作使用历史记录

对于您创建的每个快捷方式,请考虑用户可以在应用程序中直接完成相同任务的不同方式。请记住reportShortcutUsed()在每种情况下调用 ,以便启动器保持用户执行表示快捷方式的操作的频率的准确历史记录。

仅在保留其含义时更新快捷方式

更改动态和固定快捷方式时, updateShortcuts()仅在更改保留其含义的快捷方式的信息时才调用 。否则,您应该使用以下方法之一,具体取决于您要重新创建的快捷方式的类型:

  • 动态快捷方式:或 。 addDynamicShortcuts() setDynamicShortcuts()
  • 固定快捷方式:。 requestPinShortcut()

例如,如果您创建了导航到超市的快捷方式,则在超市名称发生变化但位置保持不变的情况下更新快捷方式是合适的。但是,如果用户开始在不同的超市位置购物,则最好创建新的快捷方式。

启动应用时,请检查动态快捷方式

当用户将数据还原到新设备时,不会保留动态快捷方式。因此,我们建议您检查 getDynamicShortcuts()每次启动应用程序时返回的对象数, 并根据需要重新发布动态快捷方式,如“ 备份和还原”中的代码段所示 。

应用小部件概述

小部件是主屏幕定制的重要方面。您可以将它们想象为应用程序最重要的数据和功能的“一目了然”视图,可以从用户的主屏幕访问它们。用户可以在其主屏幕面板上移动小部件,并且如果支持,则调整它们的大小以根据他们的偏好定制小部件中的信息量。

此页面介绍了您可能要创建的不同类型的小部件以及要遵循的一些设计原则。要开始构建应用小部件,请阅读构建应用小部件。

 

小部件类型

 

在开始规划小部件时,请考虑您尝试构建的小部件类型。窗口小部件通常属于以下类别之一:

 

信息小部件

 

重拾Activity(三)Activity知识_第11张图片

信息小部件通常显示一些对用户很重要的关键信息元素,并跟踪信息随时间的变化情况。信息小部件的好例子是天气小部件,时钟小部件或体育比分跟踪器。触摸信息窗口小部件通常会启动关联的应用程序并打开窗口小部件信息的详细视图。

 

 

收集小部件

 

顾名思义,集合小部件专门用于显示相同类型的众多元素,例如来自图库应用程序的图片集合,来自新闻应用程序的文章集合或来自通信应用程序的电子邮件/消息集合。集合小部件通常关注两个用例:浏览集合,并将集合的元素打开到其详细视图以供使用。集合小部件可以垂直滚动。

重拾Activity(三)Activity知识_第12张图片

ListView小部件

重拾Activity(三)Activity知识_第13张图片

GridView小部件

 

控制小部件

 

重拾Activity(三)Activity知识_第14张图片

控件小部件的主要目的是显示用户可以直接从主屏幕触发的常用功能,而无需先打开应用程序。将它们视为应用程序的远程控制。控件小部件的典型示例是音乐应用小部件,其允许用户从实际音乐应用之外播放,暂停或跳过音乐曲目。

根据控件小部件的功能是否生成数据集(例如,在搜索小部件的情况下),与控件小部件的交互可以或可以不进行到关联的细节视图。

 

 

混合小部件

 

重拾Activity(三)Activity知识_第15张图片

虽然所有小部件倾向于倾向于上述三种类型中的一种,但实际上许多小部件是组合不同类型的元素的混合小部件。

出于窗口小部件规划的目的,将窗口小部件围绕其中一个基本类型居中,并根据需要添加其他类型的元素。

音乐播放器小部件主要是控件小部件,但也使用户了解当前正在播放的音轨。它基本上将控件小部件与信息小部件类型的元素组合在一起。

 

 

小部件限制

 

虽然小部件可以被理解为“迷你应用程序”,但在开始设计小部件之前,有一些重要的理解限制:

 

手势

 

由于小部件位于主屏幕上,因此它们必须与在那里建立的导航共存。与全屏应用程序相比,这限制了窗口小部件中可用的手势支持。虽然应用程序例如可以支持允许用户横向在屏幕之间导航的视图寻呼机,但是已经在主屏幕上进行该手势以便在主屏幕之间导航。

可用于小部件的唯一手势是:

  • 触摸
  • 垂直滑动

重拾Activity(三)Activity知识_第16张图片

 

分子

 

鉴于上述交互限制,依赖于受限制手势的一些UI构建块不可用于窗口小部件。有关支持的构建块的完整列表以及有关布局限制的更多信息,请参阅App Widgets API Guide中的“Creating App Widget Layouts”部分。

 

设计指南

小部件内容

 

窗口小部件是一种很好的机制,可以通过“宣传”可在您的应用中使用的新内容和有趣内容来吸引用户加入您的应用。

就像报纸头版上的戏弄者一样,小工具应该整合并集中应用程序的信息,然后在应用程序中提供更丰富细节的连接; 换句话说:小工具是信息“零食”,而应用程序是“用餐”。作为底线,始终确保您的应用程序显示有关信息项的详细信息,而不是窗口小部件已显示的信息。

 

 

除了纯粹的信息内容,您还应该考虑通过提供应用程序常用区域的导航链接来完善您的小部件产品。这使用户可以更快地完成任务,并将应用程序的功能范围扩展到主屏幕。

适用于小部件表面导航链接的良好候选者是:

  • 生成功能:这些功能允许用户为应用创建新内容,例如创建新文档或新消息。
  • 在顶层打开应用程序:点击信息元素通常会将用户导航到较低级别的详细信息屏幕。提供对应用程序顶层的访问可提供更多导航灵活性,并可替换用户可用于从主屏幕导航到应用程序的专用应用程序快捷方式。使用应用程序图标作为可用性还可以为您的窗口小部件提供清晰的标识,以防您显示的数据不明确。

 

小部件调整大小

重拾Activity(三)Activity知识_第17张图片

 

长按和后续发布将可调整大小的小部件设置为调整大小模式。用户可以使用拖动手柄或小部件角来设置所需的大小。

调整大小允许用户在主面板放置网格的约束内调整窗口小部件的高度和/或宽度。您可以决定您的窗口小部件是否可以自由调整大小,或者是否受限于水平或垂直大小更改。如果您的特定小部件本身是固定大小的,则不必支持调整大小。

允许用户调整窗口小部件的大小具有重要的好处:

  • 他们可以微调他们想要在每个小部件上看到多少信息。
  • 它们可以更好地影响其主页面板上的小部件和快捷方式的布局。

规划窗口小部件的调整大小策略取决于您正在创建的窗口小部件的类型。列表或基于网格的集合小部件通常很简单,因为调整小部件的大小只会扩展或收缩垂直滚动区域。无论窗口小部件的大小如何,用户仍可以将所有信息元素滚动到视图中。另一方面,信息小部件需要更多动手规划,因为它们不可滚动且所有内容必须适合给定大小。您必须动态调整窗口小部件的内容和布局,使其达到用户通过调整大小操作定义的大小。

在这个简单的示例中,用户可以分4个步骤水平调整天气小部件的大小,并在小部件增长时在当前位置显示有关天气的更丰富信息。

对于每个小部件大小,确定应该显示的应用程序信息量。对于较小的尺寸,请专注于必要,然后在窗口小部件水平和垂直增长时添加更多上下文信息。

 

 

布局考虑因素

 

根据您拥有和开发的特定设备的放置网格的尺寸来布置窗口小部件很有诱惑力。在布局窗口小部件时,这可能是一个有用的初始近似值,但请记住以下几点:

  • 电池的数量,大小和间距因设备而异,因此,您的控件非常灵活,可以容纳比预期更多或更少的空间,这一点非常重要。
  • 实际上,当用户调整窗口小部件的大小时,系统将以dp大小范围响应,您的窗口小部件可以在其中重绘自身。规划小部件调整大小策略跨“大小存储桶”而不是可变网格维度将为您提供最可靠的结果。

 

小部件配置

 

有时,小部件需要先设置才能变得有用。例如,可以考虑一个电子邮件小部件,您需要在显示收件箱之前提供帐户。或者是静态照片小部件,其中用户必须分配要从库中显示的图片。

Android小部件在小部件被放到主面板后立即显示其配置选项。保持窗口小部件配置轻,不要提供超过2-3个配置元素。使用对话框样式而不是全屏活动来呈现配置选项并保留用户的位置上下文,即使这样做需要使用多个对话框。

设置完成后,通常没有太多理由重新访问设置。因此,Android小部件不显示“设置”或“配置”按钮。

 

重拾Activity(三)Activity知识_第18张图片

将Play小部件添加到主面板后,小部件会要求用户指定小部件应显示的媒体类型。

 

 

清单

 

  • 专注于您的小部件上的一小部分可浏览信息。展开应用中的信息。
  • 为您的目的选择正确的小部件类型。
  • 对于可调整大小的小部件,请规划小部件的内容应如何适应不同的大小。
  • 通过确保布局能够拉伸和收缩,使您的窗口小部件方向和设备独立。

 

构建应用程序小部件

App Widgets是微型应用程序视图,可以嵌入到其他应用程序(例如主屏幕)中并接收定期更新。这些视图在用户界面中称为窗口小部件,您可以使用应用程序窗口小部件提供程序发布一个窗口小部件。能够容纳其他App Widgets的应用程序组件称为App Widget主机。下面的屏幕截图显示了音乐应用小工具。

本文档描述了如何使用App Widget提供程序发布App Widget。有关创建自己AppWidgetHost 的主机应用程序小部件的讨论,请参阅 应用程序小组件主机。

注意: 有关如何设计应用小部件的信息,请阅读应用小部件概述。

 

基础

 

要创建App Widget,您需要以下内容:

AppWidgetProviderInfo 宾语

描述App Widget的元数据,例如App Widget的布局,更新频率和AppWidgetProvider类。这应该用XML定义。

AppWidgetProvider 类实现

定义允许您基于广播事件以编程方式与App Widget进行交互的基本方法。通过它,您将在App Widget更新,启用,禁用和删除时收到广播。

查看布局

定义以XML格式定义的App Widget的初始布局。

此外,您可以实现App Widget配置活动。这是一个可选项 Activity,可在用户添加App Widget时启动,并允许他们在创建时修改App Widget设置。

以下部分描述了如何设置每个组件。

 

在清单中声明应用程序小部件

 

首先,AppWidgetProvider在应用程序的AndroidManifest.xml文件中声明该类 。例如:


    
        
    
    

元素需要该android:name 属性,该属性指定AppWidgetProviderApp Widget使用的 属性。

所述元件必须包括一个  与元件android:name属性。该属性指定AppWidgetProvider接受ACTION_APPWIDGET_UPDATE广播。这是您必须明确声明的唯一广播。根据需要AppWidgetManager 自动将所有其他App Widget广播发送到AppWidgetProvider。

元素指定 AppWidgetProviderInfo资源并需要以下属性:

  • android:name - 指定元数据名称。使用 android.appwidget.provider 标识数据的AppWidgetProviderInfo 描述符。
  • android:resource- 指定AppWidgetProviderInfo 资源位置。

 

添加AppWidgetProviderInfo元数据

 

所述AppWidgetProviderInfo一个应用程序的widget,的基本品质定义如它的最小布局尺寸,其初始布局资源,多久更新应用窗口小部件,和(任选地)一个配置Activity推出在创建时间。使用单个元素在XML资源中定义AppWidgetProviderInfo对象, 并将其保存在项目的 res/xml/ 文件夹中。

例如:


以下是属性的摘要:

  • minWidthminHeight 属性的值指定App Widget 默认使用的最小空间量 。默认主屏幕根据具有已定义高度和宽度的单元格网格在其窗口中定位App Widgets。如果App Widget的最小宽度或高度的值与单元格的尺寸不匹配,则App Widget尺寸将向上舍 入到最接近的单元格大小。

    有关调整App小部件大小的更多信息,请参阅 App小部件设计指南。

    注意:要使您的应用小部件在设备之间移植,您的应用小部件的最小大小不应超过4 x 4小区。

  • minResizeWidthminResizeHeight属性指定应用Widget的绝对最小尺寸。这些值应指定App Widget难以辨认或无法使用的大小。使用这些属性允许用户将窗口小部件的大小调整为可能小于minWidthminHeight属性定义的默认窗口小部件大小的大小 。在Android 3.1中引入。

    有关调整App小部件大小的更多信息,请参阅 App小部件设计指南。

  • updatePeriodMillis属性定义了App Widget框架应该AppWidgetProvider通过调用 onUpdate() 回调方法来请求更新的频率。实际更新不能保证准确地按时发生,我们建议尽可能不经常更新 - 也许每小时不超过一次以节省电池。您可能还允许用户调整配置中的频率 - 有些人可能希望股票代码每15分钟更新一次,或者一天只能更新四次。

    注意:如果设备在需要更新时(如定义的那样updatePeriodMillis)处于睡眠状态,则设备将被唤醒以执行更新。如果每小时更新不超过一次,这可能不会导致电池寿命出现严重问题。但是,如果您需要更频繁地更新和/或在设备处于睡眠状态时无需更新,则可以根据不会唤醒设备的警报执行更新。为此,请使用以下命令设置AppWidgetProvider接收的Intent警报 AlarmManager。将警报类型设置为ELAPSED_REALTIME或 RTC,仅在设备唤醒时发出警报。然后设置updatePeriodMillis为零("0")。

  • initialLayout属性指向定义App Widget布局的布局资源。
  • configure属性定义在Activity用户添加App Widget时启动,以便他们配置App Widget属性。这是可选的(请参阅下面的“ 创建应用程序小组件配置活动”)。
  • previewImage属性指定应用小部件配置后的外观预览,用户在选择应用小部件时看到的内容。如果未提供,则用户会看到应用程序的启动器图标。该字段对应 于文件中元素的 android:previewImage属性。有关使用的更多讨论,请参阅设置预览图像。在Android 3.0中推出。AndroidManifest.xmlpreviewImage
  • autoAdvanceViewId属性指定应由窗口小部件主机自动提升的应用窗口小部件子视图的视图ID。在Android 3.0中推出。
  • resizeMode属性指定可以调整窗口小部件的规则。您可以使用此属性使主屏幕小部件可以水平,垂直或在两个轴上进行调整。用户触摸按住窗口小部件以显示其调整大小手柄,然后拖动水平和/或垂直手柄以更改布局网格上的大小。resizeMode属性的值 包括“horizo​​ntal”,“vertical”和“none”。要将窗口小部件声明为水平和垂直可调整大小,请提供值“horizo​​ntal | vertical”。在Android 3.1中引入。
  • minResizeHeight属性指定可以调整窗口小部件的最小高度(以dps为单位)。如果该字段大于minHeight或未启用垂直调整大小,则此字段无效(请参阅参考资料resizeMode)。在Android 4.0中推出。
  •  minResizeWidth 属性指定可以调整窗口小部件的最小宽度(以dps为单位)。如果该字段大于minWidth或未启用水平调整大小,则此字段无效(请参阅参考资料resizeMode)。在Android 4.0中推出。
  • widgetCategory属性声明您的App Widget是否可以显示在主屏幕(home_screen),锁定屏幕(keyguard)或两者上。只有低于5.0的Android版本才支持锁屏小部件。对于Android 5.0及更高版本,仅home_screen有效。

有关元素AppWidgetProviderInfo接受的属性的更多信息,请参阅该类

 

创建应用程序小组件布局

 

您必须以XML格式定义App Widget的初始布局,并将其保存在项目的 res/layout/目录中。您可以使用下面列出的View对象设计App Widget,但在开始设计App Widget之前,请阅读并理解 App Widget设计指南。

如果您熟悉Layouts,则创建App Widget布局很简单。但是,您必须知道App Widget布局是基于的RemoteViews,它不支持所有类型的布局或视图窗口小部件。

RemoteViews对象(以及App Widget)可以支持以下布局类:

  • FrameLayout
  • LinearLayout
  • RelativeLayout
  • GridLayout

以下小部件类:

  • AnalogClock
  • Button
  • Chronometer
  • ImageButton
  • ImageView
  • ProgressBar
  • TextView
  • ViewFlipper
  • ListView
  • GridView
  • StackView
  • AdapterViewFlipper

不支持这些类的后代。

RemoteViews还支持ViewStub,这是一个不可见的,零大小的View,您可以使用它在运行时懒惰地夸大布局资源。

 

将边距添加到App Widgets

 

窗口小部件通常不应扩展到屏幕边缘,并且不应在视觉上与其他窗口小部件齐平,因此您应在窗口小部件框架的所有边上添加边距。

从Android 4.0开始,应用程序小部件会在窗口小部件框架和应用程序窗口小部件的边界框之间自动填充,以便更好地与用户主屏幕上的其他窗口小部件和图标对齐。要利用此强烈建议的行为,请将应用程序的targetSdkVersion设置为14或更高。

编写单个布局很容易,该布局具有应用于早期版本平台的自定义边距,并且没有针对Android 4.0及更高版本的额外边距:

  1. 将您的应用程序设置targetSdkVersion为14或更高。
  2. 创建一个如下所示的布局,引用其边距的维度资源:
    
    
      
    
    
  3. 创建两个维度资源,一个res/values/用于提供Android 4.0之前的自定义边距,另一个res/values-v14/用于为Android 4.0小部件提供额外的填充:

    res / values / dimens.xml

    8dp

     

    res / values-v14 / dimens.xml

    0dp

     

另一个选择是默认情况下简单地在九个补丁后台资产中构建额外的边距,并提供不同的九个补丁,没有API级别14或更高版本的边距。

 

使用AppWidgetProvider类

 

AppWidgetProvider类广播接收器作为一个方便的类来处理应用的Widget广播延伸。AppWidgetProvider仅接收与App Widget相关的事件广播,例如更新,删除,启用和禁用App Widget时。当这些广播事件发生时,AppWidgetProvider会收到以下方法调用:

onUpdate()

这被调用以按updatePeriodMillis AppWidgetProviderInfo中的属性定义的间隔更新App Widget (请参阅上面的添加AppWidgetProviderInfo元数据)。当用户添加App Widget时也会调用此方法,因此它应该执行基本设置,例如为视图定义事件处理程序并Service在必要时启动临时操作 。但是,如果已声明配置活动,在用户添加App Widget时不会调用此方法,但会为后续更新调用此方法。配置Activity负责在配置完成时执行第一次更新。(请参阅下面的“ 创建应用程序小组件配置活动”。)

onAppWidgetOptionsChanged()

首次放置窗口小部件时以及窗口小部件调整大小时调用此方法。您可以使用此回调根据窗口小部件的大小范围显示或隐藏内容。您可以通过调用获取大小范围getAppWidgetOptions(),返回 Bundle包含以下内容的大小:

  • OPTION_APPWIDGET_MIN_WIDTH- 包含窗口小部件实例的当前宽度的下限(以dp为单位)。
  • OPTION_APPWIDGET_MIN_HEIGHT- 包含窗口小部件实例的当前高度(以dp为单位)的下限。
  • OPTION_APPWIDGET_MAX_WIDTH- 包含窗口小部件实例的当前宽度的上限(以dp为单位)。
  • OPTION_APPWIDGET_MAX_HEIGHT- 包含窗口小部件实例的当前高度(以dp为单位)的上限。

此回调是在API Level 16(Android 4.1)中引入的。如果您实现此回调,请确保您的应用不依赖于它,因为它不会在旧设备上调用。

onDeleted(Context, int[])

每次从App Widget主机删除App Widget时都会调用此方法。

onEnabled(Context)

当第一次创建App Widget的实例时调用此方法。例如,如果用户添加了App Widget的两个实例,则仅在第一次调用时。如果您需要打开一个新数据库或执行其他只需要为所有App Widget实例发生一次的设置,那么这是一个很好的地方。

onDisabled(Context)

当从App Widget主机删除App Widget的最后一个实例时调用此方法。这是您应该清理完成的任何工作的地方onEnabled(Context),例如删除临时数据库。

onReceive(Context, Intent)

在每个广播和每个上述回调方法之前调用此方法。您通常不需要实现此方法,因为默认的AppWidgetProvider实现过滤所有App Widget广播并根据需要调用上述方法。

您必须使用AndroidManifest中的元素将AppWidgetProvider类实现声明为广播接收器(请参阅 上面的Manifest中的声明App Widget)。

最重要的AppWidgetProvider回调是 onUpdate() 因为在将每个App Widget添加到主机时调用它(除非您使用配置Activity)。如果您的App Widget接受任何用户交互事件,那么您需要在此回调中注册事件处理程序。如果您的App Widget不创建临时文件或数据库,或执行其他需要清理的工作,则 onUpdate() 可能是您需要定义的唯一回调方法。例如,如果您想要一个App Widget,其按钮在单击时启动Activity,您可以使用AppWidgetProvider的以下实现:

public class ExampleAppWidgetProvider extends AppWidgetProvider {

    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        final int N = appWidgetIds.length;

        // Perform this loop procedure for each App Widget that belongs to this provider
        for (int i=0; i

此AppWidgetProvider仅定义 onUpdate() 用于定义PendingIntent启动Activity并将其附加到App Widget按钮的方法setOnClickPendingIntent(int, PendingIntent)。请注意,它包含一个遍历每个条目的循环 appWidgetIds,这是一个ID数组,用于标识此提供程序创建的每个App Widget。这样,如果用户创建了多个App Widget实例,那么它们都会同时更新。但是,只有一个updatePeriodMillis将为App Widget的所有实例管理计划。例如,如果更新计划定义为每两个小时,并且在第一个实例之后一小时添加App Widget的第二个实例,那么它们将在第一个和第二个更新定义的时间段内更新期间将被忽略(它们将每两小时更新一次,而不是每小时更新一次)。

注意:因为AppWidgetProvider是扩展BroadcastReceiver,所以在回调方法返回后,不保证您的进程继续运行(BroadcastReceiver有关广播生命周期的信息,请参阅参考资料)。如果您的App Widget设置过程可能需要几秒钟(可能在执行Web请求时),并且您需要继续进程,请考虑Service在 onUpdate() 方法中启动a 。在服务中,您可以对App Widget执行自己的更新,而无需担心由于应用程序无响应(ANR)错误导致AppWidgetProvider关闭。有关运行a的App Widget的示例,请参阅 Wiktionary示例的AppWidgetProviderService

另请参阅 ExampleAppWidgetProvider.java示例类。

 

接收App Widget广播Intents

 

AppWidgetProvider只是一个方便的类。如果您希望直接接收App Widget广播,您可以实现自己的BroadcastReceiver或覆盖 onReceive(Context, Intent)回调。您需要关注的意图如下:

  • ACTION_APPWIDGET_UPDATE
  • ACTION_APPWIDGET_DELETED
  • ACTION_APPWIDGET_ENABLED
  • ACTION_APPWIDGET_DISABLED
  • ACTION_APPWIDGET_OPTIONS_CHANGED

 

固定App小部件

 

在运行Android 8.0(API级别26)及更高版本的设备上,允许您创建固定快捷方式的启动器还允许您将应用小部件固定到启动器上。与固定快捷方式类似,这些固定小部件可让用户访问您应用中的特定任务。

在您的应用程序中,您可以通过完成以下一系列步骤,为系统创建一个请求,将窗口小部件固定到支持的启动器上:

  1. 在应用的清单文件中创建小部件,如以下代码段所示:
    
    ...
      
        ...
        
            
                
            
            
        
      
    
  2. 调用 requestPinAppWidget()方法,如以下代码段所示:
    AppWidgetManager appWidgetManager =
            context.getSystemService(AppWidgetManager.class);
    ComponentName myProvider =
            new ComponentName(context, MyAppWidgetProvider.class);
    
    if (appWidgetManager.isRequestPinAppWidgetSupported()) {
        // Create the PendingIntent object only if your app needs to be notified
        // that the user allowed the widget to be pinned. Note that, if the pinning
        // operation fails, your app isn't notified.
        Intent pinnedWidgetCallbackIntent = new Intent( ... );
    
        // Configure the intent so that your app's broadcast receiver gets
        // the callback successfully. This callback receives the ID of the
        // newly-pinned widget (EXTRA_APPWIDGET_ID).
        PendingIntent successCallback = PendingIntent.getBroadcast(context, 0,
                pinnedWidgetCallbackIntent, PendingIntent.FLAG_UPDATE_CURRENT);
    
        appWidgetManager.requestPinAppWidget(myProvider, null, successCallback);
    }

     

注意:如果您的应用程序不需要通知系统是否成功将小部件固定到支持的启动器上,您可以将null第三个参数 传入requestPinAppWidget()

 

创建应用程序小组件配置活动

 

如果您希望用户在添加新的应用程序窗口小部件时配置设置,则可以创建应用程序窗口小部件配置活动。这Activity 将由App Widget主机自动启动,并允许用户在创建时配置App Widget的可用设置,例如App Widget颜色,大小,更新周期或其他功能设置。

配置Activity应该在Android清单文件中声明为普通活动。但是,它将由App Widget主机启动并执行ACTION_APPWIDGET_CONFIGURE操作,因此Activity需要接受此Intent。例如:


    
        
    

此外,必须在AppWidgetProviderInfo XML文件中使用该android:configure属性声明Activity (请参阅上面的添加AppWidgetProviderInfo元数据)。例如,配置Activity可以这样声明:


请注意,Activity是使用完全限定的命名空间声明的,因为它将从包范围外部引用。

这就是开始使用配置活动所需的全部内容。现在您只需要实际的活动。但是,在实现Activity时,需要记住两件重要的事情:

  • App Widget主机调用配置Activity,配置Activity应始终返回结果。结果应该包括由启动Activity的Intent传递的App Widget ID(保存在Intent extras中 EXTRA_APPWIDGET_ID)。
  • 创建App Widget时不会调用该 onUpdate() 方法(启动配置Activity时系统不会发送ACTION_APPWIDGET_UPDATE广播)。首次创建App Widget时,配置Activity负责从AppWidgetManager请求更新。但是, 将调用后续更新 - 它仅在第一次被跳过。onUpdate()

有关如何从配置返回结果并更新App Widget的示例,请参阅以下部分中的代码片段。

 

从配置活动更新App Widget

 

当App Widget使用配置Activity时,Activity负责在配置完成时更新App Widget。您可以直接从中请求更新AppWidgetManager

以下是正确更新App Widget并关闭配置Activity的过程的摘要:

  1. 首先,从启动Activity的Intent中获取App Widget ID:
    Intent intent = getIntent();
    Bundle extras = intent.getExtras();
    if (extras != null) {
        appWidgetId = extras.getInt(
                AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

     

  2. 执行App Widget配置。
  3. 配置完成后,通过调用getInstance(Context)以下命令获取AppWidgetManager的实例 :
    AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context);

     

  4. RemoteViews通过调用updateAppWidget(int, RemoteViews)以下内容更新App Widget的布局 :
    RemoteViews views = new RemoteViews(context.getPackageName(),
    R.layout.example_appwidget);
    appWidgetManager.updateAppWidget(appWidgetId, views);
  5. 最后,创建返回Intent,使用Activity结果设置它,然后完成Activity:
    Intent resultValue = new Intent();
    resultValue.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
    setResult(RESULT_OK, resultValue);
    finish();

     

提示:首次打开配置活动时,将活动结果与EXTRA_APPWIDGET_ID一起设置为RESULT_CANCELED,如上面的步骤5所示。这样,如果用户在到达结束之前退出活动,则会通知App Widget主机配置已取消,并且不会添加App Widget。

有关示例,请参阅ApiDemos中的 ExampleAppWidgetConfigure.java示例类。

 

设置预览图像

 

Android 3.0引入了该previewImage字段,该字段指定应用程序小部件的外观预览。此预览将从窗口小部件选择器显示给用户。如果未提供此字段,则应用小部件的图标将用于预览。

这是您在XML中指定此设置的方式:


为了帮助您为应用程序窗口小部件创建预览图像(在previewImage字段中指定),Android模拟器包含一个名为“ 窗口小部件预览”的应用程序。要创建预览图像,请启动此应用程序,为您的应用程序选择应用程序窗口小部件,并将其设置为您希望显示预览图像的方式,然后保存并将其放在应用程序的可绘制资源中。

 

将App小部件与集合一起使用

 

Android 3.0引入了带有集合的app小部件。这些类型的App Widgets使用它RemoteViewsService来显示由远程数据支持的集合,例如来自内容提供者。RemoteViewsService 应用程序窗口小部件使用以下视图类型之一显示其提供的数据,我们将其称为“集合视图”:

ListView

一个视图,显示垂直滚动列表中的项目。有关示例,请参阅Gmail应用小部件。

GridView

在二维滚动网格中显示项目的视图。有关示例,请参阅“书签”应用小部件。

StackView

堆叠的卡片视图(类似于rolodex),用户可以分别向上/向下轻弹前卡以查看上一张/下一张卡片。示例包括YouTube和图书应用小部件。

AdapterViewFlipper

支持适配器的简单 ViewAnimator,可在两个或多个视图之间进行动画处理。一次只能展示一个孩子。

如上所述,这些集合视图显示由远程数据支持的集合。这意味着他们使用an Adapter将用户界面绑定到他们的数据。一个Adapter结合从一组数据转换成单独的个别项目View的对象。由于这些集合视图由适配器支持,因此Android框架必须包含额外的体系结构以支持它们在app小部件中的使用。在app小部件的上下文中,它被a Adapter替换RemoteViewsFactory,它只是一个围绕Adapter 界面的瘦包装器。当请求集合中的特定项时,RemoteViewsFactory创建并将集合的项作为RemoteViews 对象返回。要在应用小部件中包含集合视图,您必须实现RemoteViewsServiceRemoteViewsFactory

RemoteViewsService是一种允许远程适配器请求RemoteViews对象的服务。RemoteViewsFactory为集合视图之间的适配器的接口(如ListViewGridView该视图的基础数据,等等)和。从 StackWidget示例中,以下是用于实现此服务和接口的样板代码的示例:

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {

//... include adapter-like methods here. See the StackView Widget sample.

}

 

样品申请

 

本节中的代码摘录来自 StackWidget示例:

重拾Activity(三)Activity知识_第19张图片

此示例包含10个视图的堆栈,这些视图"0!"通过"9!"示例应用程序小部件显示值 具有以下主要行为:

  • 用户可以垂直投影app小部件中的顶视图以显示下一个或上一个视图。这是一个内置的StackView行为。
  • 在没有任何用户交互的情况下,app小部件会自动按顺序前进其视图,如幻灯片放映。这是由于设置android:autoAdvanceViewId="@id/stack_view"了在 res/xml/stackwidgetinfo.xml文件中。此设置适用于视图ID,在本例中是视图ID,是堆栈视图的视图ID。
  • 如果用户触摸顶视图,则app小部件显示Toast消息“Touched view n ”,其中 n是触摸视图的索引(位置)。有关如何实现此操作的更多讨论,请参阅 向各个项添加行为。

 

使用集合实现app小部件

 

要使用集合实现app小部件,请遵循用于实现任何app小部件的相同基本步骤。以下部分描述了使用集合实现app小部件所需执行的其他步骤。

 

带有集合的app小部件的清单

 

除了在清单中声明应用程序窗口小部件中列出的要求之外,为了使包含集合的应用程序窗口小部件能够绑定到您的要求RemoteViewsService,您必须使用该权限在清单文件中声明该服务BIND_REMOTEVIEWS。这可以防止其他应用程序自由访问应用程序窗口小部件的数据。例如,在创建RemoteViewsService用于填充集合视图的App Widget时,清单条目可能如下所示:

该行android:name="MyWidgetService" 指的是您的子类RemoteViewsService

 

带有集合的app小部件的布局

 

为您的应用程序窗口小部件的布局XML文件中的主要要求是,它包括收集意见之一:ListView, GridViewStackView,或 AdapterViewFlipper。这里是widget_layout.xml为 StackWidget样本:




    
    

请注意,空视图必须是集合视图的兄弟,其中空视图表示空状态。

除了整个应用程序窗口小部件的布局文件之外,还必须创建另一个布局文件,该文件定义集合中每个项目的布局(例如,书籍集合中每本书的布局)。该 StackWidget样品只有一个布局文件widget_item.xml,因为所有的项目使用相同的布局。

 

具有集合的app小部件的AppWidgetProvider类

 

与常规应用程序小部件一样,AppWidgetProvider子类中的大部分代码通常都会进入onUpdate()。在onUpdate()使用集合创建应用小部件时,您的实现的主要区别在于您必须调用setRemoteAdapter()。这告诉集合视图获取其数据的位置。然后RemoteViewsService可以返回您的实现RemoteViewsFactory,并且窗口小部件可以提供适当的数据。调用此方法时,必须传递指向实现的intent RemoteViewsService和指定要更新的app小部件的app小部件ID。

例如,以下是StackWidget示例如何实现onUpdate()回调方法以将其设置RemoteViewsService为应用程序窗口小部件集合的远程适配器:

public void onUpdate(Context context, AppWidgetManager appWidgetManager,
int[] appWidgetIds) {
    // update each of the app widgets with the remote adapter
    for (int i = 0; i < appWidgetIds.length; ++i) {

        // Set up the intent that starts the StackViewService, which will
        // provide the views for this collection.
        Intent intent = new Intent(context, StackWidgetService.class);
        // Add the app widget ID to the intent extras.
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
        intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
        // Instantiate the RemoteViews object for the app widget layout.
        RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
        // Set up the RemoteViews object to use a RemoteViews adapter.
        // This adapter connects
        // to a RemoteViewsService  through the specified intent.
        // This is how you populate the data.
        rv.setRemoteAdapter(R.id.stack_view, intent);

        // The empty view is displayed when the collection has no items.
        // It should be in the same layout used to instantiate the RemoteViews
        // object above.
        rv.setEmptyView(R.id.stack_view, R.id.empty_view);

        //
        // Do additional processing specific to this app widget...
        //

        appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
    }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
}

 

RemoteViewsService类

坚持数据

 

如上所述,您的RemoteViewsService子类提供了RemoteViewsFactory用于填充远程集合视图的用法。

具体来说,您需要执行以下步骤:

  1. 子类RemoteViewsServiceRemoteViewsService是远程适配器可以请求的服务RemoteViews
  2. 在您的RemoteViewsService子类中,包含一个实现该RemoteViewsFactory 接口的类。RemoteViewsFactory为远程集合视图之间的适配器的接口(如ListViewGridView该视图的基础数据,等等)和。您的实现负责为RemoteViews数据集中的每个项目创建一个对象。这个界面是一个薄的包装Adapter

您不能依赖服务的单个实例或其包含的任何数据来保持。因此,您不应在您的任何数据中存储任何数据RemoteViewsService(除非它是静态的)。如果您希望应用程序窗口小部件的数据保持不变,最好的方法是使用ContentProvider其数据在流程生命周期之后持续存在的数据。

RemoteViewsService 实现的主要内容RemoteViewsFactory如下所述。

 

RemoteViewsFactory接口

 

实现该RemoteViewsFactory 接口的自定义类为应用程序窗口小部件提供其集合中项目的数据。为此,它将您的应用小部件项XML布局文件与数据源相结合。这个数据源可以是从数据库到简单数组的任何数据。在 StackWidget示例中,数据源是一个数组WidgetItemsRemoteViewsFactory作为适配器的 功能,用于将数据粘合到远程集合视图。

您需要为RemoteViewsFactory 子类实现的两个最重要的方法 是 onCreate()和 getViewAt() 。

onCreate()第一次创建工厂时系统调用。您可以在此处为数据源设置任何连接和/或游标。例如, StackWidget示例用于onCreate() 初始化WidgetItem对象数组。当您的应用程序窗口小部件处于活动状态时,系统将使用其在数组中的索引位置访问这些对象,并显示它们包含的文本。

以下是 StackWidget示例RemoteViewsFactory 实现的摘录, 其中显示了该onCreate()方法的部分内容 :

class StackRemoteViewsFactory implements
RemoteViewsService.RemoteViewsFactory {
    private static final int count = 10;
    private List widgetItems = new ArrayList();
    private Context context;
    private int appWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        this.context = context;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    public void onCreate() {
        // In onCreate() you setup any connections / cursors to your data source. Heavy lifting,
        // for example downloading or creating content etc, should be deferred to onDataSetChanged()
        // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
        for (int i = 0; i < count; i++) {
            widgetItems.add(new WidgetItem(i + "!"));
        }
        ...
    }
...

        ...     } ...

RemoteViewsFactory方法getViewAt() 返回与数据集中RemoteViews指定position的数据相对应的对象。以下是StackWidget示例 RemoteViewsFactory实现的摘录 :

public RemoteViews getViewAt(int position) {

    // Construct a remote views item based on the app widget item XML file,
    // and set the text based on the position.
    RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
    rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

    ...
    // Return the remote views object.
    return rv;
}

 

向单个项目添加行为

 

以上部分介绍如何将数据绑定到应用程序窗口小部件集合。但是,如果要将动态行为添加到集合视图中的各个项目,该怎么办?

如使用AppWidgetProvider类中所述,通常用于setOnClickPendingIntent()设置对象的单击行为 - 例如使按钮启动Activity。但是,单个集合项中的子视图不允许使用此方法(为了澄清,您可以使用setOnClickPendingIntent()Gmail应用程序窗口小部件中的设置全局按钮来启动应用程序,但不能在单个列表项上设置)。相反,要将点击行为添加到集合中的各个项目,请使用setOnClickFillInIntent()。这需要为您的集合视图设置待定的意图模板,然后通过您的设置为集合中的每个项目设置填充意图RemoteViewsFactory

本节使用 StackWidget示例来描述如何向单个项添加行为。在StackWidget示例中,如果用户触摸顶视图,则app小部件显示 Toast消息“Touched view n ”,其中n是触摸视图的索引(位置)。这是它的工作原理:

  • StackWidgetProvider(一个AppWidgetProvider子类)创建待意图已调用自定义操作TOAST_ACTION
  • 当用户触摸视图时,意图被触发并广播 TOAST_ACTION
  • 这个广播由所截取StackWidgetProvider的 onReceive()方法,以及应用程序插件播放 Toast所触摸的视图的信息。收集项目的数据由RemoteViewsFactory,通过RemoteViewsService。提供。

注:该 StackWidget样品使用广播,但通常一个应用程序窗口小部件只会在这样一个情况下推出的活动。

 

设置待定的意图模板

 

StackWidgetProviderAppWidgetProvider子)建立了一个悬而未决的意图。集合中的个人项目无法设置自己的待处理意图。相反,集合作为一个整体设置一个待定的意图模板,并且各个项目设置一个填充意图,以逐项创建唯一的行为。

该类还接收用户触摸视图时发送的广播。它在其onReceive()方法中处理此事件。如果意图的操作是 TOAST_ACTION,则app小部件显示Toast 当前视图的消息。

public class StackWidgetProvider extends AppWidgetProvider {
    public static final String TOAST_ACTION = "com.example.android.stackwidget.TOAST_ACTION";
    public static final String EXTRA_ITEM = "com.example.android.stackwidget.EXTRA_ITEM";

    ...

    // Called when the BroadcastReceiver receives an Intent broadcast.
    // Checks to see whether the intent's action is TOAST_ACTION. If it is, the app widget
    // displays a Toast message for the current item.
    @Override
    public void onReceive(Context context, Intent intent) {
        AppWidgetManager mgr = AppWidgetManager.getInstance(context);
        if (intent.getAction().equals(TOAST_ACTION)) {
            int appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
            int viewIndex = intent.getIntExtra(EXTRA_ITEM, 0);
            Toast.makeText(context, "Touched view " + viewIndex, Toast.LENGTH_SHORT).show();
        }
        super.onReceive(context, intent);
    }

    @Override
    public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
        // update each of the app widgets with the remote adapter
        for (int i = 0; i < appWidgetIds.length; ++i) {

            // Sets up the intent that points to the StackViewService that will
            // provide the views for this collection.
            Intent intent = new Intent(context, StackWidgetService.class);
            intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            // When intents are compared, the extras are ignored, so we need to embed the extras
            // into the data so that the extras will not be ignored.
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
            rv.setRemoteAdapter(appWidgetIds[i], R.id.stack_view, intent);

            // The empty view is displayed when the collection has no items. It should be a sibling
            // of the collection view.
            rv.setEmptyView(R.id.stack_view, R.id.empty_view);

            // This section makes it possible for items to have individualized behavior.
            // It does this by setting up a pending intent template. Individuals items of a collection
            // cannot set up their own pending intents. Instead, the collection as a whole sets
            // up a pending intent template, and the individual items set a fillInIntent
            // to create unique behavior on an item-by-item basis.
            Intent toastIntent = new Intent(context, StackWidgetProvider.class);
            // Set the action for the intent.
            // When the user touches a particular view, it will have the effect of
            // broadcasting TOAST_ACTION.
            toastIntent.setAction(StackWidgetProvider.TOAST_ACTION);
            toastIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetIds[i]);
            intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
            PendingIntent toastPendingIntent = PendingIntent.getBroadcast(context, 0, toastIntent,
                PendingIntent.FLAG_UPDATE_CURRENT);
            rv.setPendingIntentTemplate(R.id.stack_view, toastPendingIntent);

            appWidgetManager.updateAppWidget(appWidgetIds[i], rv);
        }
    super.onUpdate(context, appWidgetManager, appWidgetIds);
    }
}

设置填充意图

 

RemoteViewsFactory必须为集合中的每个项目设置填充意图。这使得可以区分给定项目的单独点击动作。然后将填充意图与PendingIntent模板组合以确定单击项目时将执行的最终意图。

public class StackWidgetService extends RemoteViewsService {
    @Override
    public RemoteViewsFactory onGetViewFactory(Intent intent) {
        return new StackRemoteViewsFactory(this.getApplicationContext(), intent);
    }
}

class StackRemoteViewsFactory implements RemoteViewsService.RemoteViewsFactory {
    private static final int count = 10;
    private List widgetItems = new ArrayList();
    private Context context;
    private int appWidgetId;

    public StackRemoteViewsFactory(Context context, Intent intent) {
        this.context = context;
        appWidgetId = intent.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID,
                AppWidgetManager.INVALID_APPWIDGET_ID);
    }

    // Initialize the data set.
        public void onCreate() {
            // In onCreate() you set up any connections / cursors to your data source. Heavy lifting,
            // for example downloading or creating content etc, should be deferred to onDataSetChanged()
            // or getViewAt(). Taking more than 20 seconds in this call will result in an ANR.
            for (int i = 0; i < count; i++) {
                widgetItems.add(new WidgetItem(i + "!"));
            }
           ...
        }
        ...

        // Given the position (index) of a WidgetItem in the array, use the item's text value in
        // combination with the app widget item XML file to construct a RemoteViews object.
        public RemoteViews getViewAt(int position) {
            // position will always range from 0 to getCount() - 1.

            // Construct a RemoteViews item based on the app widget item XML file, and set the
            // text based on the position.
            RemoteViews rv = new RemoteViews(context.getPackageName(), R.layout.widget_item);
            rv.setTextViewText(R.id.widget_item, widgetItems.get(position).text);

            // Next, set a fill-intent, which will be used to fill in the pending intent template
            // that is set on the collection view in StackWidgetProvider.
            Bundle extras = new Bundle();
            extras.putInt(StackWidgetProvider.EXTRA_ITEM, position);
            Intent fillInIntent = new Intent();
            fillInIntent.putExtras(extras);
            // Make it possible to distinguish the individual on-click
            // action of a given item
            rv.setOnClickFillInIntent(R.id.widget_item, fillInIntent);

            ...

            // Return the RemoteViews object.
            return rv;
        }
    ...
    }

 

保持收集数据新鲜

 

下图说明了在发生更新时使用集合的应用程序窗口小部件中发生的流程。它显示了应用程序窗口小部件代码如何与之交互RemoteViewsFactory,以及如何触发更新:

重拾Activity(三)Activity知识_第20张图片

使用集合的app小部件的一个功能是为用户提供最新内容的能力。例如,考虑Android 3.0 Gmail应用小部件,该小部件为用户提供其收件箱的快照。为了实现这一点,您需要能够触发您的RemoteViewsFactory和集合视图来获取和显示新数据。你通过AppWidgetManager电话实现了这一目标notifyAppWidgetViewDataChanged()。此调用会导致回调您RemoteViewsFactoryonDataSetChanged()方法,这使您有机会获取任何新数据。请注意,您可以在onDataSetChanged()回调中同步执行处理密集型操作 。您可以保证在从中获取元数据或视图数据之前完成此调用RemoteViewsFactory。此外,您还可以在其中执行处理密集型操作getViewAt() 方法。如果该呼叫需要很长的时间,装载视图(由指定 RemoteViewsFactory的 getLoadingView()方法)将被显示在集合视图,直到它返回相应的位置。

 

 

构建应用程序小部件主机

大多数Android设备上提供的Android主屏幕允许用户嵌入应用小部件以便快速访问内容。如果您正在构建Home替换或类似的应用程序,您还可以允许用户通过实现一个来嵌入应用程序小部件 AppWidgetHost。这不是大多数应用程序需要做的事情,但如果您要创建自己的主机,那么理解主机隐含同意的合同义务非常重要。

本文档重点介绍实现自定义所涉及的职责 AppWidgetHost。有关如何实现的示例 AppWidgetHost,请参阅Android主屏幕 启动器的源代码 。

以下是实现自定义所涉及的关键类和概念的概述 AppWidgetHost

  • App Widget Host - AppWidgetHost为想要在其UI中嵌入app小部件的应用程序(如主屏幕)提供AppWidget服务的交互。一个AppWidgetHost必须有一个ID,它是主机本身的包中是唯一的。该ID在主机的所有使用中保持不变。ID通常是您在应用程序中分配的硬编码值。
  • 应用程序小组件ID - 每个应用程序小组件实例在绑定时都会分配一个唯一的ID(请参阅绑定应用程序小组件bindAppWidgetIdIfAllowed()中的更多详细信息)。唯一ID由主机使用获得。此ID在窗口小部件的生命周期内是持久的,也就是说,直到从主机中删除它为止。任何特定于主机的状态(例如窗口小部件的大小和位置)都应由托管包保留,并与应用程序窗口小部件ID关联。 allocateAppWidgetId()
  • 应用小部件主机视图 - AppWidgetHostView可以被认为是小部件在需要显示时被包装的框架。AppWidgetHostView每次小部件被主机夸大时,都会为每个小部件分配一个应用小部件。
  • 选项包 - AppWidgetHost使用选项包将信息传递给AppWidgetProvider关于窗口小部件的显示方式(例如,大小范围,以及窗口小部件是在锁定屏幕还是主屏幕上)。此信息允许 AppWidgetProvider根据窗口的显示方式和位置定制窗口小部件的内容和外观。您可以使用 updateAppWidgetOptions() 和 updateAppWidgetSize() 修改应用小部件的捆绑包。这两种方法都会触发回调 AppWidgetProvider

     

 

绑定应用程序小部件

 

当用户将app小部件添加到主机时,会发生称为绑定的过程 。绑定是指将特定应用程序窗口小部件ID与特定主机和特定主机相关联 AppWidgetProvider。根据您的应用运行的Android版本,有不同的实现方法。

 

在Android 4.0及更低版本上绑定应用小部件

 

在运行Android 4.0及更低版本的设备上,用户通过允许用户选择窗口小部件的系统活动添加应用程序窗口小部件。这隐式地执行权限检查 - 即,通过添加应用小部件,用户隐式授予应用将应用小部件添加到主机的权限。下面是一个示例,说明了这种方法,取自原始的 Launcher。在此片段中,事件处理程序startActivityForResult() 使用请求代码调用 以REQUEST_PICK_APPWIDGET响应用户操作:

private static final int REQUEST_CREATE_APPWIDGET = 5;
private static final int REQUEST_PICK_APPWIDGET = 9;
...
public void onClick(DialogInterface dialog, int which) {
    switch (which) {
    ...
        case AddAdapter.ITEM_APPWIDGET: {
            ...
            int appWidgetId =
                    Launcher.this.appWidgetHost.allocateAppWidgetId();
            Intent pickIntent =
                    new Intent(AppWidgetManager.ACTION_APPWIDGET_PICK);
            pickIntent.putExtra
                    (AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
            ...
            startActivityForResult(pickIntent, REQUEST_PICK_APPWIDGET);
            break;
    }
    ...
}

系统活动完成后,会将用户选择的应用小部件结果返回给您的活动。在以下示例中,活动通过调用addAppWidget()添加应用小部件来响应:

public final class Launcher extends Activity
        implements View.OnClickListener, OnLongClickListener {
    ...
    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        waitingForResult = false;

        if (resultCode == RESULT_OK && addItemCellInfo != null) {
            switch (requestCode) {
                ...
                case REQUEST_PICK_APPWIDGET:
                    addAppWidget(data);
                    break;
                case REQUEST_CREATE_APPWIDGET:
                    completeAddAppWidget(data, addItemCellInfo, !desktopLocked);
                    break;
                }
        }
        ...
    }
}

该方法addAppWidget()检查应用小部件是否需要在添加之前进行配置:

void addAppWidget(Intent data) {
    int appWidgetId = data.getIntExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, -1);

    String customWidget = data.getStringExtra(EXTRA_CUSTOM_WIDGET);
    AppWidgetProviderInfo appWidget =
            appWidgetManager.getAppWidgetInfo(appWidgetId);

    if (appWidget.configure != null) {
        // Launch over to configure widget, if needed.
        Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_CONFIGURE);
        intent.setComponent(appWidget.configure);
        intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
        startActivityForResult(intent, REQUEST_CREATE_APPWIDGET);
    } else {
        // Otherwise, finish adding the widget.
    }
}

有关配置的更多讨论,请参阅创建应用程序小组件配置活动。

应用程序小部件准备就绪后,下一步是将实际工作添加到工作区。在 原发射使用称为方法completeAddAppWidget() 来做到这一点。

 

在Android 4.1及更高版本上绑定应用小部件

 

Android 4.1添加了API,以实现更简化的绑定过程。这些API还使主机可以提供用于绑定的自定义UI。要使用此改进的流程,您的应用必须BIND_APPWIDGET在其清单中声明 权限:

但这只是第一步。在运行时,用户必须明确授予您的应用程序权限,以允许它将应用程序小部件添加到主机。要测试您的应用是否有权添加小部件,请使用该 bindAppWidgetIdIfAllowed() 方法。如果bindAppWidgetIdIfAllowed() 返回false,您的应用必须显示一个对话框,提示用户授予权限(“允许”或“始终允许”,以涵盖所有未来的应用小部件添加)。此代码段提供了如何显示对话框的示例:

Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_BIND);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_PROVIDER, info.componentName);
// This is the options bundle discussed above
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_OPTIONS, options);
startActivityForResult(intent, REQUEST_BIND_APPWIDGET);

主机还必须检查用户是否添加了需要配置的应用程序小部件。有关此主题的更多讨论,请参阅创建应用程序小组件 配置活动。

 

主持人的责任

 

窗口小部件开发人员可以使用 AppWidgetProviderInfo元数据为窗口小部件指定许多配置设置。下面更详细讨论的这些配置选项可以由主机从AppWidgetProviderInfo 与小部件提供者相关联的对象中检索。

无论您定位的Android版本如何,所有主机都有以下责任:

  • 添加窗口小部件时,必须按上述方法分配窗口小部件ID。您还必须确保在从主机中删除窗口小部件时,您调用deleteAppWidgetId() 以取消分配窗口小部件ID。
  • 添加窗口小部件时,请务必启动其配置活动(如果存在),如 从配置活动更新应用程序窗口 小部件中所述。对于许多应用程序小部件,这是必要的步骤才能正确显示它们。
  • 每个应用小部件指定dps中的最小宽度和高度,如AppWidgetProviderInfo元数据中所定义(使用android:minWidth和 android:minHeight)。确保窗口小部件的布局至少包含这么多dps。例如,许多主机将网格中的图标和小部件对齐。在这种情况下,默认情况下,主机应使用满足minWidthminHeight约束的最小单元格数添加应用程序窗口小部件。

除了上面列出的要求之外,特定平台版本还引入了在主机上承担新职责的功能。

 

您针对哪个版本?

 

您在实施主机时使用的方法应取决于您要定位的Android版本。本节中描述的许多功能都是在3.0或更高版本中引入的。例如:

  • Android 3.0(API Level 11)引入了小部件的自动前进行为。
  • Android 3.1(API Level 12)引入了调整窗口小部件大小的功能。
  • Android 4.0(API Level 15)引入了填充策略的更改,该策略将主机的职责放在管理填充上。
  • Android 4.1(API Level 16)添加了一个API,允许窗口小部件提供程序获取有关托管其窗口小部件实例的环境的更详细信息。
  • Android 4.2(API Level 17)引入了选项包和 bindAppWidgetIdIfAllowed() 方法。它还引入了锁屏小部件。

如果您要定位早期设备,请参阅原始 启动器作为示例。

以下部分提供了有关在主机上放置新职责的功能的其他详细信息。

 

Android 3.0

 

Android 3.0(API Level 11)引入了小部件指定的功能autoAdvanceViewId()。此视图ID应指向an的实例Advanceable,例如StackView 或AdapterViewFlipper。这表明主机应该以主机advance()认为合适的间隔调用此视图(考虑到推进小部件是否有意义 - 例如,主机可能不希望推进小部件,如果它在另一个小部件上)页面,或者如果屏幕关闭)。

 

Android 3.1

 

Android 3.1(API Level 12)引入了调整窗口小部件大小的功能。窗口小部件可以使用 元数据中的android:resizeMode属性指定它可调整大小 AppWidgetProviderInfo,并指示它是否支持水平和/或垂直大小调整。在Android 4.0(API Level 14)中引入,小部件也可以指定 android:minResizeWidth 和/或android:minResizeHeight

主机负责使小部件可以按小部件的指定水平和/或垂直调整大小。指定可调整大小的窗口小部件可以任意调整大小,但不应调整大小小于android:minResizeWidth 和指定的值android:minResizeHeight。有关示例实现,请 在。AppWidgetResizeFrameLauncher2

 

Android 4.0

 

Android 4.0(API Level 15)引入了填充策略的更改,该策略将主机的职责放在管理填充上。从4.0开始,app小部件不再包含自己的填充。相反,系统根据当前屏幕的特征为每个小部件添加填充。这导致网格中小部件的更均匀,一致的呈现。为了帮助托管app小部件的应用程序,平台提供了该方法 getDefaultPaddingForWidget()。应用程序可以调用此方法来获取系统定义的填充,并在计算要分配给窗口小部件的单元格数时对其进行说明。

 

Android 4.1

 

Android 4.1(API Level 16)添加了一个API,允许窗口小部件提供程序获取有关托管其窗口小部件实例的环境的更详细信息。具体而言,主机向窗口小部件提供者提示有关窗口小部件显示的大小。主办方有责任提供此尺寸信息。

主持人通过提供此信息 updateAppWidgetSize()。大小指定为dps的最小和最大宽度/高度。指定范围(而不是固定大小)的原因是因为窗口小部件的宽度和高度可能会随方向而变化。您不希望主机在轮换时更新其所有小部件,因为这可能会导致严重的系统速度下降。这些值应该在放置窗口小部件时,每次调整窗口小部件时以及启动器在给定引导中第一次膨胀窗口小部件时更新(因为值不会在引导期间保持不变)。

 

Android 4.2

 

Android 4.2(API级别17)增加了在绑定时指定选项包的功能。这是指定应用小部件选项(包括大小)的理想方式,因为它AppWidgetProvider可以在第一次更新时立即访问选项数据。这可以通过使用该方法来实现bindAppWidgetIdIfAllowed()。有关此主题的更多讨论,请参阅绑定应用程序小部件。

Android 4.2还引入了锁屏小部件。在锁定屏幕上托管窗口小部件时,主机必须在应用程序窗口小部件选项包中指定此信息(AppWidgetProvider可以使用此信息来适当地设置窗口小部件的样式)。要将窗口小部件指定为锁屏窗口小部件,请使用updateAppWidgetOptions() 并包含OPTION_APPWIDGET_HOST_CATEGORY 带有值的字段WIDGET_CATEGORY_KEYGUARD。此选项默认为WIDGET_CATEGORY_HOME_SCREEN,因此不明确要求为主屏幕主机设置此选项 。

确保您的主机仅添加适合您的应用的应用小部件 - 例如,如果您的主机是主屏幕,请确保元数据中的android:widgetCategory 属性 AppWidgetProviderInfo包含该标志WIDGET_CATEGORY_HOME_SCREEN。同样,对于锁屏,请确保该字段包含该标志 WIDGET_CATEGORY_KEYGUARD。有关此主题的更多讨论,请参阅 在锁屏上启用App小部件。

 

你可能感兴趣的:(android,Activity)