第3 章 窗 口 Ability是应用所具备能力的抽象,也是应用程序的重要组成部分,是Harmony应用程 序的一大核心。一个应用可以包含多个Ability,而Ability 又可以分为FA(Feature Ability)和PA(ParticleAbility)两种类型,FA 仅支持PageAbility,它对用户是可见的,承 载了一个业务可视化界面,即用户可通过FA 与应用程序进行交互,PA 支持DataAbility (第9章)和ServiceAbility(第10章),它在后台运行,对用户是不可见的,PA 无法提供与 用户交互的能力。本章主要对PageAbility进行讨论。 通过阅读本章,读者可以掌握: 如何使用PageAbility。 如何在PageAbility之间进行交互。 如何进行跨设备迁移PageAbility。 如何使用AbilitySlice。 PageAbility与AbilitySlice的生命周期。 3.1 PageAbility概述 PageAbility是FA 唯一支持的模板,它本质上是一个窗口,用于提供与用户交互的能 力,类似于Android中的Activity。另外,HarmonyOS提供了AbilitySlice,AbilitySlice是 指应用的单个页面控制逻辑的总和,相当于页面内的子窗口,类似于导航窗口,其功能与 PageAbility相同,在切换时可以在同一个PageAbility内完成,也可以跳转至不同的Page Ability。PageAbility之间的切换相当于Web网页之间的切换,而AbilitySlice之间的切换 相当于在一个Web页面下不同导航窗口之间的切换。 图3-1 PageAbility与AbilitySlice 的关系 PageAbility可以使用一个或多个AbilitySlice,也可以不使用。在创建HarmonyOS工程 时,包含了一个默认的AbilitySlice(MainAbilitySlice. java)。当在一个PageAbility中使用多个AbilitySlice 时,这些AbilitySlice所提供的功能之间应该具有高度 的相关性,换言之,页面提供的功能之间有高度相关性 时,应该在一个PageAbility下使用两个AbilitySlice, 而不必使用两个Page Ability,以减少冗余。Page Ability和AbilitySlice的关系如图3-1所示。 为了提高开发者的开发效率,使用DevEcoStudio 创建HarmonyOS工程时,IDE 提供了一些Ability模 28 HarmonyOS 应用程序开发与实战(Java 版) 板供开发者使用,如图3-2所示,读者可以使用这些Ability模板快速生成一个HarmonyOS 工程的框架,相当于一个简单的HelloWorld工程。 图3-2 Ability模板 3.2 PageAbility的基本用法 为了提高开发效率,DevEcoStudio也为开发人员提供了自动创建PageAbility的功 能,在创建过程中会自动创建Page类型的Ability类,同时创建一个AbilitySlice类以及布 局文件,并自动向config.json文件中添加PageAbility的配置信息,这些都是开发工具自动 完成的。为了使读者能够更好地理解和使用PageAbility,本节将介绍如何手动创建Page Ability、布局文件,以及如何装载布局文件,并在config.json中配置一个PageAbility。 3.2.1 手动创建Page Ability 类 PageAbility是一个普通的Java类,其必须继承Ability类,该类属于ohos.aafwk.ability 包,下面给出MyFirstAbility类的创建代码: package com.example.createpageability; import ohos.aafwk.ability.Ability; public class MyFirstAbility extends Ability { } 3.2.2 在config.json 文件中注册Page Ability 在HarmonyOSApp中,所有Ability在使用前必须在config.json文件中的abilities配 第3 章 窗口 29 置项配置。abilities是一个对象数组,每一个对象表示一个Ability,包括PageAbility、Data Ability和ServiceAbility。MyFirstAbility的配置代码如下: { "skills": [ { "actions": [ "com.example.myfirstability" ] } ], "orientation": "unspecified", "formsEnabled": false, "name": "com.example.createpageability.MyFirstAbility", "icon": "$media:icon", "description": "$string:myfirstability_description", "label": "$string:myfirstability_label", "type": "page", "launchType": "standard" } 下面对abilities配置信息中的常用属性进行介绍: (1)skills表示该Ability能够接收Intent的特征,是一个对象数组。 skills中对象又有3种属性: actions:表示能够接收的Intent的action值,可以包含一个或者多个action; entities:表示能够接收的Intent的Ability的类别,可以包含一个或者多个entity; uris:表示能够接收的Intent的uri,可以包含一个或者多个uri。 (2)orientation表示该Ability的显示模式,该标签仅适用于page类型的Ability。 其只有4种取值: unspecified:由系统自动判断显示方向; landscape:横屏模式; portrait:竖屏模式; followRecent:跟随栈中最近的应用。 (3)formsEnabled表示是否支持卡片功能,仅适用于page类型的Ability。 (4)name表示该Ability的名称,取值可采用反向域名方式表示,由包名加类名组成, 如“com.example.createpageability.MyFirstAbility”,也可以使用“.”开头的类名方式表示, 如“.MyFirstAbility”,一个应用中的每个Ability的name属性应该是唯一的。 (5)icon表示Ability图标资源文件的索引,用于配置该Ability的图标,这里使用 “$media:icon”来表示对resources/base/media下的icon.png文件的引用。 (6)description表示对Ability的描述,这里使用“$string:myfirstability_description” 来表示对resources/base/element下的string.json文件中定义的字符串的引用,以后所有 的字符串都将在这个文件中定义,使用“$string:”来引用。 (7)label表示Ability对用户显示的名称。 30 HarmonyOS 应用程序开发与实战(Java 版) (8)type表示该Ability的类型。 其一共有四种取值: page:表示基于Page模板开发的FA,用于提供与用户交互的能力; service:表示基于Service模板开发的PA,用于提供后台运行任务的能力; data:表示基于Data模板开发的PA,用于对外部提供统一的数据访问抽象; CA:表示支持其他应用以窗口方式调起该Ability。 (9)launchType表示Ability的启动模式。 它支持“standard”“singleMission”和“singleton”3种模式: standard:表示该Ability可以有多实例,“standard”模式适用于大多数应用场景; singleMission:表示该Ability在每个任务栈中只能有一个实例; singleton:表示该Ability在所有任务栈中仅可以有一个实例。例如,具有全局唯一性 的呼叫来电界面即采用“singleton”模式。该标签仅适用于手机、平板、智慧屏、车机、智能 穿戴。 (10)reqPermissions:表示此Ability需要申请的权限。 3.2.3 创建布局文件 HarmonyOSApp所有的布局文件将放在entry/src/main/resources/base/layout目录 下面,首先,创建一个LayoutResourceFile并命名为my_first_layout.xml,然后输入以下 代码: 关于布局和组件,之后的章节会详细进行介绍,读者在此模仿例子使用即可,这里用到 第3 章 窗口 31 的是方向布局DirectionalLayout,也是创建布局文件时,IDE会创建的默认布局。这里用到 了两个组件:Text和Button。 3.2.4 静态装载布局文件 创建完布局文件后,需要将布局文件加载到PageAbility上,才能显示布局中的组件。 通常需要在PageAbility启动时装载布局文件,也就是需要在PageAbility的生命周期方 法中的onStart()方法里完成。关于生命周期方法,后面小节会详细介绍,读者只需要知道 onStart()方法在PageAbility启动时调用即可,通常会在这个方法里做一些初始化的工作, 例如,加载布局文件,初始化组件,为组件添加事件监听器等。 现在需要调用父类Ability的onStart()方法,并使用super.setUIContent()方法加载之 前创建的布局文件my_first_layout.xml,注意在写Java代码时,用到了其他类或其他类的 静态方法时,需要import特定的类,这一点在之后的代码中不做体现,代码如下: import ohos.aafwk.ability.Ability; import ohos.aafwk.content.Intent; public class MyFirstAbility extends Ability { //调用父类onStart()方法 public void onStart(Intent intent) { super.onStart(intent); super.setUIContent(ResourceTable.Layout_my_first_layout); } } 在HarmonyOSApp中,系统会将所有静态资源与一个int类型的值进行绑定,并将这 些值以常量的形式定义在static类型的类ResourceTable中,以便通过这个静态类调用这 些值,通过该值引用相关资源。这些值是自动生成的,以资源文件的名称加上资源类型(作 为前缀)作为变量名。例如,布局文件生成的ID需要加上前缀Layout,本例的布局文件是 my_first_layout.xml,因此在ResourceTable类中会自动生成ID:Layout_my_first_layout。 根据这个生成规则,还要求资源文件的命名必须符合Java标识符的命名规则,否则无法在 ResourceTable自动生成ID。 3.2.5 显示Page Ability 目前为止,一个小型但完整的PageAbility已经创建完成,最后一步就是显示这个创建 好的PageAbility。如果想让MyFirstAbility作为应用的主Ability(即程序运行后的显示 的第一个页面)显示,可以修改MyFirstAbility的配置信息的skills部分,将其修改为如下 形式: "skills": [ { "entities": [ "entity.system.home" 32 HarmonyOS 应用程序开发与实战(Java 版) ], "actions": [ "action.system.home" ] } ] 需要注意的是,一个应用只能有一个主Ability,但在config.json 文件中还有其他 Ability的actions也设为“action.system.home”,而HarmonyOS只会显示在config.json文 件中遇到的第一个主Ability。因此如果需要将您的Ability设为主Ability,就需要将您的 Ability配置信息作为abilities中的第一个元素,或者删除其他的action属性值为“action. system.home”的配置项。将MyFirstAbility作为主Ability显示时,打开应用会看到如 图3-3(左)所示的页面,而如果从其他页面显示MyFirstAbility,如从图3-3(右)显示 MyFirstAbility,显示效果和图3-3(左)相同。 图3-3 显示MyFirstAbility 3.2.6 销毁Page Ability 在PageAbility使用完后,需要关闭(或销毁)PageAbility,调用如下代码即可销毁 PageAbility: terminateAbility(); 该方法属于Ability类,如果在AbilitySlice(后面章节介绍)中需要调用该方法,需要获 得包含该AbilitySlice的Ability对象。 第3 章 窗口 33 3.3 PageAbility之间的交互 本小节将介绍两个不同的PageAbility之间如何进行交互,例如,在两个不同Page Ability之间传递数据、通过显式和隐式的方式在一个PageAbility中显示另一个Page Ability。 3.3.1 Intent 的基本概念 Intent是对象之间传递信息的载体。例如,当一个Ability需要启动另一个Ability时, 或者一个AbilitySlice需要导航到另一个AbilitySlice时,可以通过Intent指定启动的目标 同时携带相关数据。Intent的构成元素包括Operation与Parameters。 Operation又包含以下7项属性: (1)Action:表示动作,可以使用系统内置的Action,也可以使用用户在config.json文 件中自定义的Action。 (2)Entity:表示类别,可以使用系统内置的Action,也可以使用用户在config.json文 件中自定义的Action。 (3)URI:表示URI描述。如果在Intent中指定了URI,则Intent将匹配指定的URI 信息。 (4)Flags:表示处理Intent的方式。如Intent.FLAG_ABILITY_CONTINUATION 标记在本地的一个Ability是否可以迁移到远端设备继续运行。 (5)BundleName:表示包描述。 (6)AbilityName:表示待启动的Ability 名称。如果在Intent 中同时指定了 BundleName和AbilityName,则Intent可以直接匹配到指定的Ability。 (7)DeviceId:表示运行指定Ability的设备ID。 Parameters是一种支持自定义的数据结构,开发者可以通过Parameters传递某些请求 所需的额外信息。 3.3.2 显式使用Intent 所谓显式使用就是同时指定Operation属性的BundleName和AbilityName两项子属 性,即根据Ability的全称启动Ability,隐式使用就是指未同时指定Operation 属性的 BundleName和AbilityName,根据Operation的其他属性启动Ability,通常指定Ability指 定的action属性启动PageAbility。 【例3.1】 首先需要创建一个名为ability_main.xml的布局文件,同时添加两个Button 组件并绑定到MainAbility,主Ability用于演示显式使用Intent和隐式使用Intent的区别。 布局文件代码如下: 然后创建一个名为ExplicitAbility的PageAbility,同时绑定一个布局文件,并在配置 文件中进行注册,由于代码简单,因此不在此赘述。显式地显示PageAbility的步骤如下: (1)创建Intent对象; (2)使用Intent.OperationBuilder类构造包含BundleName与AbilityName的Operation 对象; (3)通过Intent的setOperation()方法指定Intent的Operation对象; (4)调用startAbility()方法显示PageAbility。 按照以上步骤编写显示ExplicitAbility的代码如下: Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() //指定设备标识,空串表示当前设备 .withDeviceId("") //指定包名 .withBundleName("com.example.explicitintent") //指定Page Activity 的name 属性值 .withAbilityName("com.example.explicitintent.ExplicitIntentAbility") .build(); intent.setOperation(operation); startAbility(intent); 其中,withDeviceID用于指定设备ID,在config.json文件中的deviceConfig中进行配 置,空串表示当前设备。withBundleName指定的是HarmonyOSApp的包名,在config.json文 件中bundlename属性指定。withAbilityName指定的是PageAbility的全名(包名+类 名)。最后需要调用build()方法返回一个Operation对象。执行程序,单击“Explicit”按钮 将会显示ExplicitAbility,效果如图3-4所示。 第3 章 窗口 35 图3-4 显式使用Intent 3.3.3 隐式使用Intent 首先创建一个名为Implicit1Ability的PageAbility类,同时绑定一个布局文件,然后 在config.json文件中添加如下配置信息: { "skills": [ { "actions": [ "action.implicit" ] } ], "orientation": "unspecified", "name": "com.example.intentuse.Implicit1Ability", "icon": "$media:icon", "description": "$string:implicit1", "label": "$string:implicit1", "type": "page", "launchType": "standard" } 由于在隐式使用Intent的方式中需要用到action属性,因此需要配置action为“action. implicit”,并通过如下代码显示Implicit1Ability: 36 HarmonyOS 应用程序开发与实战(Java 版) Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() .withAction("action. implicit ") .build(); intent.setOperation(operation); startAbility(intent); 隐式使用Intent时,不需要指定BundleName与AbilityName,只需要指定Action即 可。注意,由于在实际开发过程中,有可能遇到多个PageAbility指定同一个action,因此, 使用隐式显示PageAbility时,HarmonyOS会弹出一个列表,列表中是所有绑定了同一个 action的PageAbility,供用户选择到底使用哪一个PageAbility。这也是和显式地显示 PageAbility的一个重要区别,由于显式方法指定确定的BundleName与AbilityName,因 此会显示指定PageAbility。下面通过一个例子介绍多个PageAbility指定同一个action 时会出现的情况。 再创建一个名为Implicit2Ability的PageAbility,同时绑定布局文件,在config.json文 件中的配置信息如下所示: { "skills": [ { "actions": [ "action.implicit" ] } ], "orientation": "unspecified", "name": "com.example.intentuse.Implicit2Ability", "icon": "$media:icon", "description": "$string:implicit2", "label": "$string:implicit2", "type": "page", "launchType": "standard" } 显然,这两个PageAbility指定了同一个action,使用如下代码显示PageAbility: Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() .withAction("action. implicit ") .build(); intent.setOperation(operation); startAbility(intent); 执行程序,单击“Implicit”按钮执行以上代码,系统将会弹出一个列表供用户选择进入 哪个PageAbility,如图3-5所示。 第3 章 窗口 37 图3-5 隐式使用Intent 3.3.4 Page Ability 之间的通信 一般来说,PageAbility之间是需要进行通信的,而通信方式可以分为两种,一种是 PageAbility 向下一个PageAbility 传递数据;另一种是PageAbility 向上一个Page Ability返回数据。 第一种通信方式是单向的,因此只用通过Intent对象的setParam()函数携带相关数据 并调用startAbility()方法传递到下一个PageAbility即可,代码如下: Intent intent = new Intent(); intent.setParam("data","mydata"); startAbility(intent); 执行这段代码,就能将数据传到下一个PageAbility,在下一个PageAbility中,通过调 用Intent.getStringParam()方法就能获得传递过来的数据,代码如下: Intent myintent = getIntent(); String data = myintent.getStringParam("data"); 第二种通信方式是双向的,从PageAbility1传递数据并跳转到PageAbility2时,需要 PageAbility2返回数据给PageAbility1。此时,需要使用startAbilityForResult()方法。 当使用该方法跳转到PageAbility2 时,由PageAbility2 返回到PageAbility1 后,Page Ability1会自动调用onAbilityResult()方法,因此,需要提前在PageAbility1中重写该方 法用于接收从PageAbility2传递过来的数据,该方法的原型: protected void onAbilityResult (int requestCode, int resultCode, Intent resultData); 38 HarmonyOS 应用程序开发与实战(Java 版) onAbilityResult()方法的参数含义如下。 (1)requestCode:请求码,通过在启动Ability时设置,即startAbilityResult()方法的 第2个参数。 (2)resultCode:响应码,在PageAbility2中调用setResult()方法时设置。 (3)resultData:由PageAbility2返回的Intent对象形式的数据。 这里重点介绍一下requestCode和resultCode。因为在PageAbility中可能会通过 startAbilityForResult()方法显示多个PageAbility,所以onAbilityResult()方法可能是多 个PageAbility共享的,这就要求在onAbilityResult()方法中区别是哪一个PageAbility 返回的结果。可以通过requestCode或resultCode单独区分不同的PageAbility,也可以使 用requestCode和resultCode共同区分不同的PageAbility。如果在PageAbilityl中通过 startAbilityForResult()方法显示PageAbility2,那么requestCode应该在PageAbility1中 指定,而resultCode应该在PageAbility2中指定。 【例3.2】 演示两个Page Ability 之间是如何通信的。在Page Ability1 调用 startAbilityForResult()方法显示Page Ability2,并传递一个字符串类型数据,Page Ability2接收这个数据,并显示在页面上,然后关闭PageAbility2并返回一些数据给Page Ability1,PageAbility1在onAbilityResult()方法中接收来自PageAbility2的数据。 首先创建一个名为pageability1_layout.xml的布局文件,布局中包括一个Text,用于显 示PageAbility2返回的数据,一个Button,用于显示PageAbility2,代码如下: 然后创建一个名为pageability2_layout.xml的布局文件,布局中包括一个Text,用于显 示PageAbility1传递来的数据,一个Button,用于关闭PageAbility2,代码如下: 第3 章 窗口 39 接下来创建一个名为PageAbility1的PageAbility类,主要代码如下: public class PageAbility1 extends Ability { Button button; Text text; public void onStart(Intent intent){ super.onStart(intent); //加载布局文件 super.setUIContent(ResourceTable.Layout_pageability1_layout); //通过组件ID 获取组件 button = (Button)findComponentById(ResourceTable.Id_button_page1); text = (Text)findComponentById(ResourceTable.Id_text_page1); //绑定按钮监听事件 button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { Intent intent = new Intent(); //传递一个字符串 intent.setParam("pageability1_data", "这是Page Ability1 传递的数据"); Operation operation = new Intent.OperationBuilder() .withBundleName("com.example.interactionofpage") .withAbilityName("com.example.interactionofpage .PageAbility2").build(); intent.setOperation(operation); //显示PageAbility2,设置请求码为100 startAbilityForResult(intent,100); 40 HarmonyOS 应用程序开发与实战(Java 版) } }); } //重写onAbilityResult 方法,当PageAbility2 关闭时自动调用 protected void onAbilityResult(int requestCode, int resultCode, Intent resultData){ switch (requestCode){ case 100: switch(resultCode){ case 101: //接收返回的数据 String data = resultData.getStringParam ("pageability2_data"); //将接收到的数据显示在页面中Text 组件上 if(data!=null)text.setText(data); break; } break; } } } 这段代码实现了使用startAbilityForResult()方法从PageAbility1显示PageAbility2, 并传递一个字符串类型数据,重写了onAbilityResult(),对requestCode和resultCode进行 了判断,如果满足条件,则接收返回的数据,现在需要创建一个名为PageAbility2的Page Ability类,主要代码如下: public class PageAbility2 extends Ability { Button button; Text text; public void onStart(Intent intent){ super.onStart(intent); //加载布局文件 super.setUIContent(ResourceTable.Layout_pageability2_layout); //获取PageAbility1 传递的数据 Intent myintent = getIntent(); String data = myintent.getStringParam("pageability1_data"); //通过组件ID 获取组件 button = (Button)findComponentById(ResourceTable.Id_button_page2); text = (Text)findComponentById(ResourceTable.Id_text_page2); //将传递来的数据显示在页面的Text 组件上 if(data != null)text.setText(data); //绑定按钮监听,返回PageAbility1,并返回一个字符串类型数据 button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { Intent resultintent = new Intent(); //设置返回到PageAbility1 的数据 resultintent.setParam("pageability2_data", "这是PageAbility2 返回的数据"); 第3 章 窗口 41 //设置响应码以及返回的Intent 对象 setResult(101, resultintent); //关闭PageAbility2 terminateAbility(); } }); } } 现在,两个PageAbility已经准备就绪,最后在config.json文件中注册这两个Page Ability即可使用这两个PageAbility。配置信息如下: { "orientation": "unspecified", "name": "com.example.interactionofpage.PageAbility1", "icon": "$media:icon", "description": "$string:pageability1", "label": "$string:pageability1", "type": "page", "launchType": "standard" }, { "orientation": "unspecified", "name": "com.example.interactionofpage.PageAbility2", "icon": "$media:icon", "description": "$string:pageability2", "label": "$string:pageability2", "type": "page", "launchType": "standard" } 运行程序,效果如图3-6所示。 图3-6 PageAbility交互效果图 42 HarmonyOS 应用程序开发与实战(Java 版) 3.4 PageAbility的启动类型 在之前的例子中,所有Ability的配置信息launchType的值都为standard,这也是 launchType属性的默认值。launchType还有两个值为singleMission和singleton,本节将 对这3种属性值的作用进行介绍。 standard表示此Ability可以创建多个实例,且在任何情况下,无论PageAbility被显 示多少次,每次被显示都会创建一个新的PageAbility实例。 singleMission表示此Ability在每个任务栈中只能有一个实例。如果要显示的Page Ability在栈顶,那么再次显示这个PageAbility时,不会再创建新的PageAbility实例,而 是直接使用这个PageAbility实例。如果PageAbility上面有其他的PageAbility,那么首 先弹出这些PageAbility,然后再复用这个PageAbility。 singleton表示该Ability在所有任务栈中只能有一个实例,例如,具有全局唯一性的呼 叫来电界面即采用“singleton”模式。 其中涉及的栈是HarmonyOS管理PageAbility的模式。由于HarmonyOSApp只能 显示一个PageAbility,HarmonyOSApp会使用栈来管理App中的所有PageAbility,只 有在栈顶的PageAbility才能显示,如果要让非栈顶的PageAbility显示,那么就需要将要 显示的PageAbility之前的所有PageAbility销毁,使得要显示的PageAbility位于栈顶, 要销毁PageAbility,则需要调用terminateAbility()方法。 【例3.3】 由于singleMission模式和singleton模式都只允许Ability只有一个实例, 本例仅演示standard和singleMission启动类型的区别。 首先创建一个名为LaunchTypeAbility1的PageAbility类,主要代码如下: public class LaunchTypeAbility1 extends Ability { private static int count = 0; //计数器 public void onStart(Intent intent){ super.onStart(intent); super.setUIContent(ResourceTable.Layout_launchtype1); //每创建一个LaunchTypeAbility1 对象,计数器加一 count++; Button button = (Button)findComponentById(ResourceTable.Id_button1); Text count1 = (Text)findComponentById(ResourceTable.Id_count1); if(count1 != null){ //将计数器的值显示在页面中 count1.setText(String.valueOf(count)); } //设置按钮单击事件监听器 button.setClickedListener(new Component.ClickedListener() { @Override public void onClick(Component component) { Intent intent = new Intent(); Operation operation = new Intent.OperationBuilder() .withBundleName("com.example.launchtype").withAbilityName 第3 章 窗口 43 ("com.example.launchtype.LaunchTypeAbility1").build(); intent.setOperation(operation); startAbility(intent); } }); } } 其次在config.json文件中添加这个类的配置信息,注意,先将这个类的launchType属 性都设置为standard,在LaunchTypeAbility1重复显示LaunchTypeAbility1,这样可以很 好地观察到在standard和singleMission这两种启动模式下,计数器的变化情况。 LaunchTypeAbility1这个类中定义了一个变量count作为计数器,每次创建一个 LaunchTypeAbility1实例对象,计数器都会加一,在standard 启动模式下,显示三次 LaunchTypeAbility1,系统则会创建三个LaunchTypeAbility1实例对象,因此计数器的值 变为3,页面效果如图3-7所示。 现在将LaunchTypeAbility1 的启动模式改为singleMission,然后再显示三次 LaunchTypeAbility1。由于在singleton模式下,显示LaunchTypeAbility1并不会创建新的 LaunchTypeAbility1实例对象,可以观察到计数器的值都为1,页面效果如图3-8所示。 图3-7 standard启动模式的页面效果图 图3-8 singleMission启动模式的页面效果 3.5 PageAbility的跨设备迁移 HarmonyOS的一大技术特性是分布式任务调度,所谓分布式任务调度,就是构建统一 的分布式服务管理(发现、同步、注册、调用)机制,支持对跨设备的应用进行远程启动、远程 44 HarmonyOS 应用程序开发与实战(Java 版) 调用、远程连接以及迁移等操作,能够根据不同设备的能力、位置、业务运行状态、资源使用 情况,以及用户的习惯和意图,选择合适的设备运行分布式任务。而PageAbility的跨设备 迁移则依赖了分布式设备调度中的业务迁移能力,是分布式任务调度的一个具体实现。跨 设备迁移支持将Page在同一用户的不同设备间迁移,以便支持用户无缝切换的诉求。实现 这个操作的前提是参加跨设备迁移的设备在同一个网段内,或者登录了同一个HUAWEI 账号,接下来将介绍跨设备前需要准备的工作。 3.5.1 跨设备迁移前的准备工作 在进行跨设备迁移之前(后面几章所讲的跨设备调用DataAbility、ServiceAbility也 同样要进行这些准备), 需要对HarmonyOS 设备做一些准备工作: (1)打开HarmonyOS 设备的蓝牙,并把设备名称修改为可识别的名称,如图3-9所示; (2)将HarmonyOS 设备连入Wi-Fi,并且所有参与跨设备迁移的HarmonyOS 设备在 同一个网段; (3)所有参与跨迁移的HarmonyOS 设备登录同一个HUAWEI 账号; (4)选择“设置”→“超级终端”查看附近设备如图3-10 所示。 图3- 9 设置设备名称图3-10 附近设备 3.5.2 获取设备列表 跨设备迁移需要知道目标设备的ID,因此需要提前获取所有可用的设备ID 。 yOS 提供了一个用于获取所有设备信息的方法,即Dggt() HarmoneviceManaer.etDeviceLis 方法,该方法返回一个List列表,类型是DeviceInfo。DeviceInfo类型描述了设备的名称和 第3 章 窗口 45 设备ID等相关信息,实现代码如下: List deviceInfoList = DeviceManager.getDeviceList(DeviceInfo.FLAG_GET_ONLINE_DEVICE) 其中,getDeviceList()方法的参数表示获取哪种状态下的设备的信息,一共有3种取值: (1)DeviceInfo.FLAG_GET_ONLINE_DEVICE:所有在线设备; (2)DeviceInfo.FLAG_GET_OFFLINE_DEVICE:所有离线设备; (3)DeviceInfo.FLAG_GET_ALL_DEVICE:所有设备。 一般会使用第一个值,因为只有设备在线,才能进行迁移PageAbility操作。 【例3.4】 实现一个获取在线设备列表的PageAbility,单击一个设备会返回该设备的 ID。首先创建一个名为device_ids.xml的布局文件,代码如下: 布局中包含了一个ListContainer组件,用于显示所有设备信息,再创建一个名为 device_id_item.xml的布局文件,作为ListContainer组件的Item 布局,代码如下: 该布局文件中放置的两个TextField组件,分别显示设备名称和设备ID。接下来是 PageAbility的实现。创建一个名为DeviceIDAbility的PageAbility类,代码如下: public class DeviceIDAbility extends Ability { //存放获取到的在线设备信息 private List deviceInfoList; //展示设备信息的容器 private ListContainer listContainer; //获取所有在线设备信息 private static List getAllOnlineDeviceInfo(){ List deviceInfoList = DeviceManager.getDeviceList (DeviceInfo.FLAG_GET_ONLINE_DEVICE); if(deviceInfoList == null || deviceInfoList.size() == 0) { return new ArrayList<>(); } else{ return deviceInfoList; } } public void onStart(Intent intent) {