鸿蒙最全鸿蒙Ability学习,2024年最新HarmonyOS鸿蒙高级面试题

img
img

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以戳这里获取

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

@Override

public void onStart(Intent intent) {

super.onStart(intent);

super.setMainRoute(FooSlice.class.getName());

}

  • onActive()

Page 在进入了 INACTIVE 状态后来到前台,就会调用该方法。 在此进入 ACTIVE 状态, 该状态是应用于用户交互的状态。

Page一直保持在该状态, 直至 Page 失去焦点,发生时,Page 会回到 INACTIVE 状态,系统调用 onInactive() 方法,此后如果回到 ACTIVE 状态,就会再次调用 onActive()

  • onInactive()

当 Page 失去焦点时,就会调用此方法。

  • onBackground()

当 Page 不再可见时,会调用此方法,此后 Page 进入 BACKGROUND 状态,该方法可以用来释放无用资源

  • onForeground()

Page 在处于 BACKGROUND 的状态时若仍然驻留在内存中,当重新回到前台时, 会首先调用 onForeground() 通知开发者,

然后进入 INACTIVE 状态

  • onStop()

系统将要销毁 Page 时,将会触发此回调函数,这个时候需要释放系统资源。销毁Page的原因:

①:用户使用系统能力关闭Page,比如任务管理器关闭

②:触发 Page 的 terminateAbility() 方法

③:配置变更导致系统暂时销毁Page并重建 (竖屏转横屏?)

④:系统处于资源管理的目的, 回收处于 BACKGROUND 状态的Page

和 Android Activity/Fragment 生命周期的一些区别:

  1. Android中使用 onStart() 表示进入前台, 第一次也会调, 鸿蒙用 onForeground() 表示,但是第一次进入时不会调用

  2. 无。

2.3 AbilitySlice 生命周期


AbilitySlice 的生命周期和 Page 是一样的,具有同状态和同名的回调。 当 Page 生命周期发生变化时,它的 AbilitySlice 也会发生相同的生命周期的变化, 但是如果只是在同个 Page 内导航 AbilitySlice,Page状态不变,AbilitySlice 状态会变,相当于独立了。

此外,必须重写 AbilitySlice 的 onStart() 方法,并通过 setUIContent() 设置页面:

@Override

protected void onStart(Intent intent) {

super.onStart(intent);

setUIContent(ResourceTable.Layout_main_layout);

}

在同一个 Page 中,从 A AbilitySlice 导航到 B AbilitySlice 所经历的方法回调是:

A.onInactive() -> B.onStart() -> B.onActive() -> A.onBackground()

2.4 同一 Page 内导航


2.4.1 presenter()

当发起导航的 AbilitySlice 和导航目标的 AbilitySlice 处于同一个 Page 时, 可以通过 presenter() 实现导航,代码如下,导航到 Target AbilitySlice:

private void initUi() {

Text text = (Text) findComponentById(ResourceTable.Id_jump_button);

text.setClickedListener(listener -> present(new TargetSlice(), new Intent()));

}

2.4.2 presentForResult()

如果希望从导航目标的 AbilitySlice 返回时,带上结果,则需要使用 presenterForResult(),带上 requestCode ,并重写 onResult() 回调:

private void initUi() {

Text text = (Text) findComponentById(ResourceTable.Id_jump_button);

text.setClickedListener(listener -> presentForResult(new TargetSlice(), new Intent(), 0));

}

@Override

protected void onResult(int requestCode, Intent resultIntent) {

// 这里返回页面结果

super.onResult(requestCode, resultIntent);

}

2.4.3 AbilitySlice 实例栈

每个 Page 都会维护一个 AbilitySlice 实例的栈,每个进入前台的 AbilitySlice 实例都会入栈。

当调用 presenter() 时,指定的 AbilitySlice 实例已经入栈了,则栈中位于此实例之上的 AbilitySlice 均会出栈,并终止其生命周期。

2.5 不同 Page 间导航


AbilitySlice 是作为 Page 的内部单元,以 Action 的形式对外暴露

Page 间的导航使用 startAbility() / startAbilityForResult() 方法导航, 获得返回结果的回调为 onAbilityResult()。在 Ability 中的 setResult() 可以设置返回结果。

2.6 跨设备迁移


跨设备迁移,支持将 Page 在同一用户的不同设备间的迁移,就是将一个页面从 A 设备转移到 B 设备,这是万物互联的一个体现。其步骤大概三步走:

  1. 设备A上的Page请求迁移

  2. HarmonyOS 处理迁移任务,并回调设备A上Page的保存数据方法,用于保存迁移必须的数据

  3. HarmonyOS 在设备B上启动同一个Page,并回调其恢复数据方法

2.6.1 实现 IAbilityContinuation 接口

一个应用可能包含多个Page,仅需要在支持迁移的Page中通过以下方法实现IAbilityContinuation接口。同时,此Page所包含的所有AbilitySlice也需要实现此接口。

  • onStartContinuation

Page 请求迁移后,系统首先回调这个方法,开发者可以在此回调中决策当前是否可以执行迁移。比如弹一个弹窗让用户确认是否迁移

  • onSaveData

如果 onStartContinuation 返回true,那么就会调用这个方法,开发者在此方法中保存数据,这些数据后续将传到另一台设备上

  • onRestoreData

源侧设备上Page完成保存数据后,系统在目标侧设备上回调这个方法,开发者在此回调中接收用于恢复 Page 状态的数据。

这个方法调用实际在 onStart 之前,会触发目标设备上 Page 的重新启动生命周期

  • onCompleteContinuation

如果数据传递成功,源设备会回调这个方法,告知迁移结束,源设备可以在这里结束Page

  • onFailedContinuation

迁移过程中发生异常,会在源设备回调FA的此方法。

  • onRemoteTerminated (可以不必实现)

如果开发者使用 continueAbilityReversibly() 而不是 continueAbility(),则此后可以在源设备上使用 reverseContinueAbility 进行回迁。这种场景下,相当于同一个 Page 的两个实例运行在两台设备上。迁移完成后,如果目标设备上的 Page 生命周期销毁,那么源设备会调用此方法

2.6.2 请求迁移

实现了 IAbilityContinuation 的接口后,可以在生命周期内, 调用 continueAbility() / continueAbilityReversibly 请求迁移,后者可以回迁:

try {

continueAbility();

} catch (IllegalStateException e) {

}

从 A 设备迁移到 B 设备,流程如下:

  1. 设备 A 上的 Page 请求迁移

  2. 系统回调设备 A 上 Page 以及 AbilitySlice 栈里面所有 AbilitySlice 实例的 IAbilityContinuation.onStartContinuation 方法,已确认当前是否可以立即迁移

  3. 如果可以立即迁移,则系统回调设备 A 上 Page 及其 AbilitySlice 实例的 IAbilityContinuation.onSaveData

  4. 如果数据保存成功,则系统在 B上启动一个 Page,并恢复其 AbilitySlice 栈,然后回调 IAbilityContinuation.onRestore 方法,传递此前保存的数据,然后 B 设备的 Page 从 onStart() 生命周期开始进行

  5. 调用 A 设备 Page 以及其所有 AbilitySlice 的 IAbilityContinuation.onCompleteContinuation 方法

  6. 如果迁移发生异常, 系统回调 A 的 Page及其所有 AbilitySlice 栈中所有 AbilitySlice 实例的 IAbilityContinuation.onFialedContinuation 方法,并不是所有异常都会回调此FA方法,仅局限于该接口枚举的异常。

这里有一个问题,A设备如何找到B设备,这就涉及到获取分布式设备类了,需要通过监听迁移按钮的点击事件,然后获取分布式设备列表,选择设备后进行传递,具体代码可以看:获取分布式设备

2.6.3 请求回迁

如果前面调用 continueAbilityReversibly 请求迁移完成后, 源设备可以调用 reverseContinueAbility 发起回迁:

try {

reverseContinueAbility();

} catch (IllegalStateException e) {

}

这里就不具体展示具体流程了,和迁移流程大同小异。

3. Service Ability

====================================================================================

基于 Service 的 Ability ,主要提供的能力是后台运行任务(比如音乐播放、文件下载),不能提供页面UI服务。

Service 是单例的,一个设备上,一个Service 只存在一个实例, 一个 Service 可以绑定多个 Ability, 只有在绑定的 Ability 全部退出后, 这个Service 才能退出。

理论上,它和 Android 的 Service 组件作用一样, 这样看来, Ability 更像是一个 context。

3.1 Service Ability 的生命周期


在这里插入图片描述

它有两种启动方法

  • 启动Service, 其他 Ability 通过调用 startAbility() 时创建,然后保持运行,其他 Ability 通过调用 stopAbility() 来停止 Service,停止后,它将会被销毁

  • 绑定Service,其他 Ability 通过调用 connectAbility 来绑定 Service, 通过 disconnectAbility() 来解绑。多个 Ability 可以连接一个 Service,当一个 Service 不在被任何 Ability 绑定时,其将会被销毁

Service Ability 生命周期的方法:

  • onStart

创建 Ability 的时候调用,整个生命周期只调用一次,传入的 Intent 应该是空的

  • onCommand

在Service创建完成之后调用,该方法在客户端每次启动该Service时都会调用,开发者可以在该方法中做一些调用统计、初始化类的操作

  • onConnect

在 Ability 绑定 Service Ability 的时候回调,会让 Service 创建并返回一个 IRemoteObject 对象,可以通过这个对象生成一个 IPC 通道,便于 Service 和 Ability 通信,所以可以看出来,这个 Service 和 通信的Ability 可以不在一个进程中。

其次,多个 Ability 可以绑定同一个 Service, 系统会缓存这个 IPC 通道,只有在第一个客户端绑定的时候,会调用 onConnect 方法,之后别的 Ability 再次绑定时,会将这个 IPC 通道发送过去,而无需再次调用 onConnect 方法

  • onDisConnected

在 Ability 和 Service 解除绑定的时候调用

  • onStop

Service 销毁的时候调用,可以在这里释放资源

public class ServiceAbility extends Ability {

@Override

public void onStart(Intent intent) {

super.onStart(intent);

}

@Override

public void onCommand(Intent intent, boolean restart, int startId) {

super.onCommand(intent, restart, startId);

}

@Override

public IRemoteObject onConnect(Intent intent) {

return super.onConnect(intent);

}

@Override

public void onDisconnect(Intent intent) {

super.onDisconnect(intent);

}

@Override

public void onStop() {

super.onStop();

}

}

同时需要在 config.json 中注册:

{

“module”: {

“abilities”: [

{

“name”: “.ServiceAbility”,

“type”: “service”,

“visible”: true

}

]

}

}

3.2 启动 Service


通过 startAbility() 来启动一个 Service Ability,可以通过传入 Intent 来启动,可以支持本地或者远程的 Service

可以通过Intent传入三个信息:

  • DeviceId

设备ID,如果是本地设备,可以为空,如果是远程设备,可以通过 ohos.distributedschedule.interwork.DeviceManager 获取设备列表

  • BundleName

表示包名

  • AbilityName

表示待启动的Ability名称

启动本地设备 Service 的代码如下:

Intent intent = new Intent();

Operation operation = new Intent.OperationBuilder()

.withDeviceId(“”)

.withBundleName(“com.xxx”)

.withAbilityName(“com.xxx.ServiceAbility”)

.withFlag(Intent.FLAG_ABILITYSLCE_MULTI_DEVICE) // 启动远端设备的Service时需要带上,表示分布式调度系统多设备启动

.build();

intent.setOperation(operation);

startAbility(intent);

3.3 连接 Service


如果 Service 需要与 Page 或者其他应用的 Service Ability 交互,就必须要创建用于连接的 Connection, 这样别的 Ability 就可以通过 connectAbility() 方法和它进行连接。

在使用 connectAbility 时,需要传入目标 Service 的 Intent 和 IAblityConnection 的示例,它有两个方法,分别是连接成功的回调 和 异常死亡的回调,如下所示:

// 创建连接Service回调实例

private IAbilityConnection connection = new IAbilityConnection() {

// 连接到Service的回调

@Override

public void onAbilityConnectDone(ElementName elementName, IRemoteObject iRemoteObject, int resultCode) {

// Client侧需要定义与Service侧相同的IRemoteObject实现类。开发者获取服务端传过来IRemoteObject对象,并从中解析出服务端传过来的信息。

}

// Service异常死亡的回调

@Override

public void onAbilityDisconnectDone(ElementName elementName, int resultCode) {

}

};

连接 Service的代码,需要带上 Connection:

// 连接Service

Intent intent = new Intent();

Operation operation = new Intent.OperationBuilder()

.withDeviceId(“deviceId”)

.withBundleName(“com.domainname.hiworld.himusic”)

.withAbilityName(“com.domainname.hiworld.himusic.ServiceAbility”)

.build();

intent.setOperation(operation);

connectAbility(intent, connection);

同时需要在 Service 需要在 onConnect() 时,返回 IRemoteObject,从而传递这个 IPC通道。鸿蒙提供了默认实现,可以通过继承 LocalRemoteObject 来创建这个通道,然后回传,如下所示:

private class MyRemoteObject extends LocalRemoteObject {

MyRemoteObject(){

}

}

// 把IRemoteObject返回给客户端

@Override

protected IRemoteObject onConnect(Intent intent) {

return new MyRemoteObject();

}

3.4 前台 Service


Service 一般是在后台运行,所以优先级比较低,容易被回收。

但是在一些场景下(比如文件下载),用户希望能够一直保持运行,这个时候就需要使用前台Service。 前台Service会始终保持正在运行的图标在状态栏显示,即和通知绑定

使用 前台Service,只需以下步骤:

  • Serivce 内部调用 keepBackgroundRunning() 绑定 Service 和 通知

  • 在 Service 的配置文件中声明 ohos.permission.KEEP_BACKGROUND_RUNNING 权限

  • 在配置文件中添加对应的 backgroundModes 参数

  • 在 Serivce 的 onStop() 方法中调用 cancelBackgroundRunning()

示例如下:

// 创建通知,其中121为notificationId

NotificationRequest request = new NotificationRequest(121);

NotificationRequest.NotificationNormalContent content = new NotificationRequest.NotificationNormalContent();

content.setTitle(“title”).setText(“text”);

NotificationRequest.NotificationContent notificationContent = new NotificationRequest.NotificationContent(content);

request.setContent(notificationContent);

// 绑定通知,1005为创建通知时传入的notificationId

keepBackgroundRunning(1005, request);

同时修改 config.json 里面的配置:

{

“name”: “.ServiceAbility”,

“type”: “service”,

“visible”: true,

“backgroundModes”: [“dataTransfer”, “location”]

}

backgroundModes 表示后台的服务类型,该标签仅适用于 Service Ability,取值如下:

在这里插入图片描述

这个是前台的音乐播放器Demo: 音乐播放器

4. Data Ability

=================================================================================

Data Ability 有助于应用管理自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。 Data既可以用于同设备下不同应用的数据共享,也支持跨设备不同应用数据共享

4.1 URI概述


Data 提供的 api 都是 URI 来标识一个具体的数据,HarmonyOS的URI是基于URI通用标准,格式如下:

在这里插入图片描述

  • scheme: 固定为 “dataability”

  • authority: 设备id,如果为跨设备场景,则为目标设备的id,如果为本地设备场景,则不需要填写

  • path:资源路径信息,代表特定资源的位置信息

  • query:查询参数

  • fragment:可以用于指示要访问的子资源

例如:

跨设备场景:dataability://device_id/com.domainname.dataability.persondata/person/10

本地设备:dataability:///com.domainname.dataability.persondata/person/10

查询 persondata 路径下 person 参数的第10条数据

4.1 创建 Data


Data 提供自定义数据的增删改查等功能,并对外提供这些接口

4.1.1 确定数据存储方式

确定数据的存储方式, Data 支持一下两种数据形式:

  • 文本数据:文本、图片、音乐等

  • 结构化数据:如数据库、数据Bean等。

4.1.2 实现 DataAbility

Data Ability 用于接收其他应用发送的请求,所以它提供访问接口。

通过在工程目录下,点击 Empty Data Ability 并且输入 Data 的名称,既可创建一个默认实现的 DataAbility

在这里插入图片描述

Data 提供了两组接口,分别是:

  • 文件存储

  • 数据库存储

4.1.3 文件存储

通过 FileDescriptor openFile(Uri uri, String mode) 来操作文件, uri 为调用方传入的请求目标路径, mode为开发者对文件的操作选项,可选方式包括 “r”(只读), “w”(只写),“rw”(读写) 等

ohos.rpc.MessageParcel 提供了一个静态方法,用于获取 MessageParcel 实例。 开发者可以通过获取到的 MessageParcel 实例,使用 dupFileDescriptor() 复制带操作文件流的文件描述符,并将其返回,供远端应用访问文件。

代码如下所示:

@Override

public FileDescriptor openFile(Uri uri, String mode) throws FileNotFoundException {

// 创建messageParcel

MessageParcel messageParcel = MessageParcel.obtain();

File file = new File(uri.getDecodedPathList().get(0)); //get(0)是获取URI完整字段中查询参数字段。

if (mode == null || !“rw”.equals(mode)) {

file.setReadOnly();

}

FileInputStream fileIs = new FileInputStream(file);

FileDescriptor fd = null;

try {

fd = fileIs.getFD();

} catch (IOException e) {

HiLog.info(LABEL_LOG, “failed to getFD”);

}

// 绑定文件描述符,使其具备文件操作流

return messageParcel.dupFileDescriptor(fd);

}

4.1.4 数据库存储

使用数据库,需要先连接到数据库。

系统会在应用启动的时候调用 onStart() 方法来创建 Data 实例,所以这个方法里面,开发者需要创建数据库连接,并获取连接对象,以便后续操作。

下面是一段连接数据库的示例代码:

private static final String DATABASE_NAME = “UserDataAbility.db”;

private static final String DATABASE_NAME_ALIAS = “UserDataAbility”;

private OrmContext ormContext = null;

@Override

public void onStart(Intent intent) {

super.onStart(intent);

DatabaseHelper manager = new DatabaseHelper(this);

ormContext = manager.getOrmContext(DATABASE_NAME_ALIAS, DATABASE_NAME, BookStore.class);

}

提供下面的 api 来增删改查:

在这里插入图片描述

这里需要注意的点是, 数据库是支持 ORM 的, ValueBucket 就像 Bundle 一样,可以传递参数,例如下面这个在数据库中插入一条数据:

public int insert(Uri uri, ValuesBucket value) {

// 参数校验

if (ormContext == null) {

HiLog.error(LABEL_LOG, “failed to insert, ormContext is null”);

return -1;

}

// 构造插入数据

User user = new User();

user.setUserId(value.getInteger(“userId”));

user.setFirstName(value.getString(“firstName”));

user.setLastName(value.getString(“lastName”));

user.setAge(value.getInteger(“age”));

user.setBalance(value.getDouble(“balance”));

// 插入数据库

boolean isSuccessful = ormContext.insert(user);

if (!isSuccessful) {

HiLog.error(LABEL_LOG, “failed to insert”);

return -1;

}

isSuccessful = ormContext.flush();

if (!isSuccessful) {

HiLog.error(LABEL_LOG, “failed to insert flush”);

return -1;

}

DataAbilityHelper.creator(this, uri).notifyChange(uri);

int id = Math.toIntExact(user.getRowId());

return id;

}

具体 orm 能力可以看这里: 对象关系映射数据库开发指导

4.1.5 注册 UserDataAbility

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

ge(value.getInteger(“age”));

user.setBalance(value.getDouble(“balance”));

// 插入数据库

boolean isSuccessful = ormContext.insert(user);

if (!isSuccessful) {

HiLog.error(LABEL_LOG, “failed to insert”);

return -1;

}

isSuccessful = ormContext.flush();

if (!isSuccessful) {

HiLog.error(LABEL_LOG, “failed to insert flush”);

return -1;

}

DataAbilityHelper.creator(this, uri).notifyChange(uri);

int id = Math.toIntExact(user.getRowId());

return id;

}

具体 orm 能力可以看这里: 对象关系映射数据库开发指导

4.1.5 注册 UserDataAbility

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!


[外链图片转存中…(img-UprsAyAX-1715594198345)]
[外链图片转存中…(img-ari0kWSa-1715594198346)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

需要这份系统化的资料的朋友,可以戳这里获取

  • 22
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值