网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
]
…
看下面代码, 其他页面可以通过 Intent 打开该 Page 时默认展示具体哪个 AbilitySlice:
public class MyAbility extends Ability {
@Override
public void onStart(Intent intent) {
super.onStart(intent);
// 设置主 AbilitySlice
setMainRoute(MainSlice.class.getName());
// 注册默认的页面, 其他页面可以通过 Intent 打开该 Page 时默认展示具体哪个 AbilitySlice
addActionRoute(“action.pay”, PaySlice.class.getName());
addActionRoute(“action.scan”, ScanSlice.class.getName());
}
}
状态机如下所示:
onStart()
系统 首次创建 Page实例的时机。 该回调在生命周期中仅触发一次, Page在该逻辑后会进入 INACTIVE
状态,该方法必须重写,且需要在该方法里设置 AbilitySlice,模板代码如下:
@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 生命周期的一些区别:
-
Android中使用
onStart()
表示进入前台, 第一次也会调, 鸿蒙用onForeground()
表示,但是第一次进入时不会调用 -
无。
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.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 均会出栈,并终止其生命周期。
AbilitySlice 是作为 Page 的内部单元,以 Action 的形式对外暴露。
Page 间的导航使用 startAbility()
/ startAbilityForResult()
方法导航, 获得返回结果的回调为 onAbilityResult()
。在 Ability 中的 setResult()
可以设置返回结果。
跨设备迁移,支持将 Page 在同一用户的不同设备间的迁移,就是将一个页面从 A 设备转移到 B 设备,这是万物互联的一个体现。其步骤大概三步走:
-
设备A上的Page请求迁移
-
HarmonyOS 处理迁移任务,并回调设备A上Page的保存数据方法,用于保存迁移必须的数据
-
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 设备,流程如下:
-
设备 A 上的 Page 请求迁移
-
系统回调设备 A 上 Page 以及 AbilitySlice 栈里面所有 AbilitySlice 实例的
IAbilityContinuation.onStartContinuation
方法,已确认当前是否可以立即迁移 -
如果可以立即迁移,则系统回调设备 A 上 Page 及其 AbilitySlice 实例的
IAbilityContinuation.onSaveData
-
如果数据保存成功,则系统在 B上启动一个 Page,并恢复其 AbilitySlice 栈,然后回调
IAbilityContinuation.onRestore
方法,传递此前保存的数据,然后 B 设备的 Page 从onStart()
生命周期开始进行 -
调用 A 设备 Page 以及其所有 AbilitySlice 的
IAbilityContinuation.onCompleteContinuation
方法 -
如果迁移发生异常, 系统回调 A 的 Page及其所有 AbilitySlice 栈中所有 AbilitySlice 实例的
IAbilityContinuation.onFialedContinuation
方法,并不是所有异常都会回调此FA方法,仅局限于该接口枚举的异常。
这里有一个问题,A设备如何找到B设备,这就涉及到获取分布式设备类了,需要通过监听迁移按钮的点击事件,然后获取分布式设备列表,选择设备后进行传递,具体代码可以看:获取分布式设备
2.6.3 请求回迁
如果前面调用 continueAbilityReversibly
请求迁移完成后, 源设备可以调用 reverseContinueAbility
发起回迁:
try {
reverseContinueAbility();
} catch (IllegalStateException e) {
…
}
这里就不具体展示具体流程了,和迁移流程大同小异。
====================================================================================
基于 Service 的 Ability ,主要提供的能力是后台运行任务(比如音乐播放、文件下载),不能提供页面UI服务。
Service 是单例的,一个设备上,一个Service 只存在一个实例, 一个 Service 可以绑定多个 Ability, 只有在绑定的 Ability 全部退出后, 这个Service 才能退出。
理论上,它和 Android 的 Service 组件作用一样, 这样看来, Ability 更像是一个 context。
它有两种启动方法
-
启动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
…
}
]
…
}
…
}
通过 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);
如果 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();
}
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: 音乐播放器
=================================================================================
Data Ability 有助于应用管理自身和其他应用存储数据的访问,并提供与其他应用共享数据的方法。 Data既可以用于同设备下不同应用的数据共享,也支持跨设备不同应用数据共享
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条数据
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;
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!
t(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;
[外链图片转存中…(img-HkXoVuva-1715703609503)]
[外链图片转存中…(img-ayqMBgJj-1715703609504)]
网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。
一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!