零基础快速上手HarmonyOS ArkTS开发3---应用程序框架

接着上一次零基础快速上手HarmonyOS ArkTS开发2---ArkTS开发实践继续往下学习,如上次文末所示,这次来学习新的章节:

其实这章的知识点也比较简单,类似于Android的Intent之间Activity的跳转相关的知识,而在鸿蒙中叫UIAbility:

什么是UIAbility?

它是一种包含用户界面的应用组件,主要是和用户进行交互,而Android中的Activity也是用户界面的组件,异曲同工,下面来看一下应用程序存在的以下几种交互形式,它们都是由UIAbility来进行构建的:

形式一:点击桌面图标进入应用

如图:

其中打开的图库应用就是基于UIAbility实现的一个实例。

形式二: 一个应用拉起另一个应用

比如图库应用中可以将图片分享到备忘录:

其中备忘录应用也是使用UIAbility实现的一个应用实例。

形式三:最近任务列表切回应用

比如从最近任务列表中切回图库应用:

其中任务列表中的应用任务也是基于UIAbility实现的一个应用实例。

单UIAbility应用和多UIAbility应用:

每一个UIAbility实例都对应于一个最近任务列表中的任务,它作为系统调度的单元,提供窗口用于绘制界面,一个应用可以有单个UIAbility,也可以有多个,建议将一个独立的功能模块放到同一个UIAbility当中,比如:

 

浏览器应用:可以用单个UIAbility结合多页面的形式让用户进行搜索和浏览内容,而像聊天应用它里面会增加一个“外卖功能”的场景,所以聊天应用和外卖功能作为2个UIAbility存在,而当在外卖详情有新的聊天消息时,就可以通过最近任务列表切回到聊天窗口继续进行聊天对话。

而一个UIAbility可以对应多个页面,例如在新闻应用在浏览内容时可以进行多页面的跳转。

UIAbility内的页面创建:

在创建一个默认工程里,就会有一个UIAbility:

并且在pages中会有一个入口页面Index:

而新建一个页面的话可以这样:

页面间的跳转和数据传递:

现在已经创建了2个页面了,接下来就可以来进行跳转了。

Index页面跳转到Second页面:

而具体的实现首先需要导入router路由模块:

接下来再调用router.pushUrl来指定需要跳转的Second页面路径既可:

Index页面带数据跳转到Second页面:

通常我们在跳转页面时,是需要携带参数的,在跳转的时候定义params参数既可:

而在接收页面,用如下方式获取发送过来的参数:

其效果为:

Second页面返回到Index页面:

当我们在Second进行了操作之后,想返回到Index页面,调用router.back()方法既可实现返回上一页面的功能:

效果为:

UIAbility的生命周期:

先来看一下官方给的生命周期状态图:

掌握好生命周期对于写出正确的程序很重要,下面则具体的过一下,其实官网对这块描述也非常详细了。

Create状态:

当UIAbility实例创建完成时会触发,系统会调用onCreate()回调,这里可以对应用进行一个初始化的操作【方法名都跟Acitivty.onCreate()一模一样~~】:

WindowStageCreate状态:

UIAbility实例创建完成之后,在进入Foreground之前,系统会创建一个WindowStage。每一个UIAbility实例都对应持有一个WindowStage实例。

WindowStage为本地窗口管理器,用于管理窗口相关的内容,例如与界面相关的获焦/失焦、可见/不可见。

可以在onWindowStageCreate回调中,设置UI页面加载、设置WindowStage的事件订阅。

在onWindowStageCreate(windowStage)中通过loadContent接口设置应用要加载的页面。

Foreground和Background状态:

Foreground和Background状态,分别在UIAbility切换至前台或者切换至后台时触发。

分别对应于onForeground回调和onBackground回调。

类似于Android中的onResume()和onStop(),这里举一个使用场景:

例如用户打开地图应用查看当前地理位置的时候,假设地图应用已获得用户的定位权限授权。在UI页面显示之前,可以在onForeground回调中打开定位功能,从而获取到当前的位置信息。

当地图应用切换到后台状态,可以在onBackground回调中停止定位功能,以节省系统的资源消耗。 

WindowStageDestroy状态:

在UIAbility实例销毁之前,则会先进入onWindowStageDestroy回调,我们可以在该回调中释放UI页面资源。

Destroy状态:

在UIAbility销毁时触发。可以在onDestroy回调中进行系统资源的释放、数据的保存等操作。

UIAbility的启动模式

在Android中的Activity会有不同的启动模式,很重要但是应用不当也很容易产生BUG,同样的,对标的UIAbility也是一样,关于这块的知识先大致有个了解,具体在实战开发中再去慢慢体会。

singleton(单实例模式)

简介:

singleton启动模式为单实例模式,也是默认情况下的启动模式。

每次调用startAbility()方法时,如果应用进程中该类型的UIAbility实例已经存在,则复用系统中的UIAbility实例。系统中只存在唯一一个该UIAbility实例,即在最近任务列表中只存在一个该类型的UIAbility实例。

场景举例:

对于浏览器或者新闻等应用,用户在打开该应用,并浏览访问相关内容后,回到桌面,再次打开该应用,显示的仍然是用户当前访问的界面。

效果:

看一下官方给的一个实例的效果:

注意:

应用的UIAbility实例已创建,该UIAbility配置为单实例模式,再次调用startAbility()方法启动该UIAbility实例。由于启动的还是原来的UIAbility实例,并未重新创建一个新的UIAbility实例,此时只会进入该UIAbility的onNewWant()回调,不会进入其onCreate()和onWindowStageCreate()生命周期回调。

看到“onNewWant”这个名字,有木有很熟悉,Android的“onNewIntent”~~

使用:

在module.json5文件中的“launchType”字段配置为“singleton”即可。

multiton(多实例模式)

简介:

multiton启动模式为多实例模式,每次调用startAbility()方法时,都会在应用进程中创建一个新的该类型UIAbility实例。即在最近任务列表中可以看到有多个该类型的UIAbility实例。这种情况下可以将UIAbility配置为multiton(多实例模式)。

这个跟Android的“standard”启动模式差不多。

场景举例:

对于应用的分屏操作,用户希望使用两个不同应用(例如备忘录应用和图库应用)之间进行分屏,也希望能使用同一个应用(例如备忘录应用自身)进行分屏。

效果:

看一下官方给的一个实例的效果:

也就是每次打开时,都生成了一个新的实例。

 

使用:

specified(指定实例模式)

 简介:

specified启动模式为指定实例模式,针对一些特殊场景使用(例如文档应用中每次新建文档希望都能新建一个文档实例,重复打开一个已保存的文档希望打开的都是同一个文档实例)。

效果:

看一下官方给的一个实例的效果:

可以看到,新建文档每次都是打开的一个新实例,而查看已保存的文档永远只有一个实例。

使用:

这里的使用就稍微麻烦一些,依照官网的说明,这里简单过一下。

例如有两个UIAbility:EntryAbility和SpecifiedAbility,SpecifiedAbility配置为指定实例模式启动,需要从EntryAbility的页面中启动SpecifiedAbility。

1、先将要启动的SpecifiedAbility指定为specified启动模式:

2、接下来则来启动该SpecifiedAbility,具体调用如官网示例所示:

// 在启动指定实例模式的UIAbility时,给每一个UIAbility实例配置一个独立的Key标识
// 例如在文档使用场景中,可以用文档路径作为Key标识
import common from '@ohos.app.ability.common';
import Want from '@ohos.app.ability.Want';
import { BusinessError } from '@ohos.base';

function getInstance() {
  return 'key';
}

let context:common.UIAbilityContext = ...; // context为调用方UIAbility的UIAbilityContext
let want: Want = {
  deviceId: '', // deviceId为空表示本设备
  bundleName: 'com.example.myapplication',
  abilityName: 'SpecifiedAbility',
  moduleName: 'specified', // moduleName非必选
  parameters: { // 自定义信息
    instanceKey: getInstance(),
  },
}

context.startAbility(want).then(() => {
  console.info('Succeeded in starting ability.');
}).catch((err: BusinessError) => {
  console.error(`Failed to start ability. Code is ${err.code}, message is ${err.message}`);
})

跟Android一样,其Activity的跳转也是用Context,而这里的context的实例是这么初始化的:

其中startAbility时需要传一个want参数,里面就是指定跳转的一些信息:

其中这个want参数中,有一个自定义的信息叫instanceKey:

那这个instanceKey是啥含义呢?从这参数名称就大概能猜到:实例主键,官方有这么一句与此参数相关的说明:“业务逻辑会根据这个参数返回一个字符串Key,用于标识当前UIAbility实例。如果返回的Key已经对应一个已启动的UIAbility实例,系统会将该UIAbility实例拉回前台并获焦,而不会创建新的实例。如果返回的Key没有对应已启动的UIAbility实例,则系统会创建新的UIAbility实例并启动。”,也就是说,如果启动的这个SpecifiedAbility,由于我们指定了它是一个指定实例模式,所以当发现启动的实例的key已经对应一个已启动的UIAbility实例,则直接会复用,此时就不会再重新创建一个新的UIAbility实例了,而如果没有发现则就会创建新的UIAbility实例。

3、接下来在SpecifiedAbility还得进行参数的处理:

import AbilityStage from '@ohos.app.ability.AbilityStage';
import Want from '@ohos.app.ability.Want';

export default class MyAbilityStage extends AbilityStage {
  onAcceptWant(want: Want): string {
    // 在被调用方的AbilityStage中,针对启动模式为specified的UIAbility返回一个UIAbility实例对应的一个Key值
    // 当前示例指的是module1 Module的SpecifiedAbility
    if (want.abilityName === 'SpecifiedAbility') {
      // 返回的字符串Key标识为自定义拼接的字符串内容
      if (want.parameters) {
        return `SpecifiedAbilityInstance_${want.parameters.instanceKey}`;
      }
    }

    return '';
  }
}

在SpecifiedAbility启动之前,会先进入对应的AbilityStage的onAcceptWant()生命周期回调中,以获取该UIAbility实例的Key值。

此时系统会自动匹配,如果存在与该UIAbility实例匹配的Key,则会启动与之绑定的UIAbility实例,并进入该UIAbility实例的onNewWant()回调函数;否则会创建一个新的UIAbility实例,并进入该UIAbility实例的onCreate()回调函数和onWindowStageCreate()回调函数

Codelab实践:

接下来借用官方的效果来实战一下。

效果:

也就是页面之间的一个跳转,并且数据传递,比较简单。

实现:

1、新建工程:

此时保证能够运行:

2、创建IndexPage.ets:

先将默认的“Index.ets”改个名:

此时则需要注意:改名后,修改工程entryability目录下EntryAbility.ets文件中windowStage.loadContent方法第一个参数为pages/IndexPage,修改后,启动应用会自动加载IndexPage页。

3、创建SecondPage.ets:

接下来再来创建第二个页面,这里通过向导来创建(选中工程entry > src > main > ets > pages目录,点击鼠标右键 > New > Page),如下:

用向导创建有一个好处,就是DevEco Studio会自动在工程目录entry > src > main > resources > base > profile > main_pages.json文件中添加pages/SecondPage。

如果是通过copy的方式来创建,则需要手动的在这个文件中去添加一下,稍麻烦一点。

3、页面跳转:

1、IndexPage中准备布局:

这块比较简单,就不从0开始了,从官方开源的源码中copy:

@Entry
@Component
struct Index {
  @State message: string = CommonConstants.INDEX_MESSAGE

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(CommonConstants.FONT_SIZE)
          .fontWeight(FontWeight.Bold)
        Blank()
        Button($r('app.string.next'))
          .fontSize(CommonConstants.BUTTON_FONT_SIZE)
          .width(CommonConstants.BUTTON_WIDTH)
          .height(CommonConstants.BUTTON_HEIGHT)
          .backgroundColor($r('app.color.button_bg'))
      }
      .width(CommonConstants.FULL_WIDTH)
      .height(CommonConstants.LAYOUT_HEIGHT)
    }
    .height(CommonConstants.FULL_HEIGHT)
    .backgroundColor($r('app.color.page_bg'))
  }
}

此时会涉及到常量和色值:

CommonConstants.ets:

/**
 * Common constants for all features.
 */
export default class CommonConstants {
  /**
   * The index page message.
   */
  static readonly INDEX_MESSAGE: string = 'Index Page';

  /**
   * The src msg.
   */
  static readonly SECOND_SRC_MSG: string = 'Index页面传来的数据';

  /**
   * The second page url.
   */
  static readonly SECOND_URL: string = 'pages/SecondPage';

  /**
   * The second page message.
   */
  static readonly SECOND_MESSAGE: string = 'Second Page';

  /**
   * The src param.
   */
  static readonly SECOND_SRC_PARAM: string = 'src';

  /**
   * Full the width.
   */
  static readonly FULL_WIDTH: string = '100%';

  /**
   * Full the height.
   */
  static readonly FULL_HEIGHT: string = '100%';

  /**
   * The hello world font size.
   */
  static readonly FONT_SIZE: string = '38fp';

  /**
   * The param font size.
   */
  static readonly PARAMS_FONT_SIZE: string = '20fp';

  /**
   * The button font size.
   */
  static readonly BUTTON_FONT_SIZE: string = '16fp';

  /**
   * The button width.
   */
  static readonly BUTTON_WIDTH: string = '296vp';

  /**
   * The button height.
   */
  static readonly BUTTON_HEIGHT: string = '40vp';

  /**
   * The layout height.
   */
  static readonly LAYOUT_HEIGHT: string = '140vp';

  /**
   * The param opacity.
   */
  static readonly PARAMS_OPACITY: number = 0.6;
}

Logger.ets:

import hilog from '@ohos.hilog';

/**
 * Common log for all features.
 */
export class Logger {
  private domain: number;
  private prefix: string;
  private format: string = `%{public}s, %{public}s`;

  constructor(prefix: string) {
    this.prefix = prefix;
    this.domain = 0xFF00;
  }

  debug(...args: string[]) {
    hilog.debug(this.domain, this.prefix, this.format, args);
  }

  info(...args: string[]) {
    hilog.info(this.domain, this.prefix, this.format, args);
  }

  warn(...args: string[]) {
    hilog.warn(this.domain, this.prefix, this.format, args);
  }

  error(...args: string[]) {
    hilog.error(this.domain, this.prefix, this.format, args);
  }

  fatal(...args: string[]) {
    hilog.fatal(this.domain, this.prefix, this.format, args);
  }

  isLoggable(level: number) {
    hilog.isLoggable(this.domain, this.prefix, level);
  }
}

export default new Logger('[PageRouter]');

这块Log的输出可以参考:@ohos.hilog (HiLog日志打印)

color.json:

{
  "color": [
    {
      "name": "white",
      "value": "#FFFFFF"
    },
    {
      "name": "button_bg",
      "value": "#007DFF"
    },
    {
      "name": "page_bg",
      "value": "#F1F3F5"
    }
  ]
}

string.json:

 

{
  "string": [
    {
      "name": "module_desc",
      "value": "module description"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "pageRouter"
    },
    {
      "name": "next",
      "value": "Next"
    },
    {
      "name": "back",
      "value": "Back"
    }
  ]
}

此时运行发现报错了:

这是因为我把start_window_background这种色值给删掉了,这里改一下,直接改用白色既可:

再运行。

2、跳转到SecondPage:

此时点击按钮时,让其跳到第二个页面上来,所以增加点击事件:

此时需要导入路由模块:

其中url定义在常量文件中:

 

此时再运行看一下:

3、SecondPage准备布局:

接下来准备一下第二个页面的内容:

其中会接收第一个页面传过来的参数,另外从这个页面还可以回到第一个页面,具体布局如下:

import router from '@ohos.router'
import CommonConstants from '../common/constants/CommonConstants'

@Entry
@Component
struct SecondPage {
  @State message: string = 'Second Page'

  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(CommonConstants.FONT_SIZE)
          .fontWeight(FontWeight.Bold)
        Text('这个是要获取上一个页面参数的')
          .fontSize(CommonConstants.PARAMS_FONT_SIZE)
          .opacity(CommonConstants.PARAMS_OPACITY)
        Blank()
        Button($r('app.string.back'))
          .fontSize(CommonConstants.BUTTON_FONT_SIZE)
          .width(CommonConstants.BUTTON_WIDTH)
          .height(CommonConstants.BUTTON_HEIGHT)
          .backgroundColor($r('app.color.button_bg'))
      }
      .width(CommonConstants.FULL_WIDTH)
      .height(CommonConstants.LAYOUT_HEIGHT)
    }
    .height(CommonConstants.FULL_HEIGHT)
    .backgroundColor($r('app.color.page_bg'))
  }
}

此时看一下页面效果:

 

4、传递数据:

接下来我们在跳转时携带一个参数,来看一下如何在页面之间进行数据的传递:

首先在第一个页面跳转时增加一个参数:

然后在第二个页面来接收第一个页面传递过来的参数,这块知识上面已经学习过了,下面来看代码:

运行一下:

4、页面返回:

最后,第二个页面如果想返回到上一个页面,如下处理:

同时回来时也可以携带参数:

这块逻辑差不多,就不演示了,下面来看一下返回上一个页面的效果:

总结:

这节其实比较简单,也是构建一个项目必备的基础,值得提一嘴的是,有木有发现官网的源码中,页面上对于常量全抽取到了一个外部统一的文件当中了:

页面中没一个把色值、文本写死的,维护性大大提高,这种好习惯值得我们去学习。

这节可能觉得太简单了,学起来感觉秒秒钟就搞定了,但下节起就开始复杂了,贴上官方的效果动图提前感受一下:

涉及到的知识点就会比较多了:

加油~~

 

 

posted on 2024-03-02 00:24  cexo  阅读(3)  评论(0编辑  收藏  举报

导航