深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
从 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;
}
// 构造插入数据
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
和 Service 类似,开发者必须在配置文件中注册 Data
需要关注几个属性:
- type
类型设置为 data
- uri
对外提供的访问路径,全局唯一
- permission
访问该 data ability 时需要申请的权限
如下所示:
{
“name”: “.UserDataAbility”,
“type”: “data”,
“visible”: true,
“uri”: “dataability://com.example.myapplication5.DataAbilityTest”,
“permissions”: [
“com.example.myapplication5.DataAbility.DATA”
]
}
开发者通过 DataAbilityHelper
来访问当前应用或者其他应用提供的共享数据。
4.2.1 声明访问权限
如果需要访问的 Data 声明了权限,那么访问此 Data 需要也在配置文件中声明权限
4.2.2 创建 DataAbilityHelper
通过传入一个 context 来创建 DataAbilityHelper
对象
DataAbilityHelper helper = DataAbilityHelper.creator(this);
4.2.3 使用 DataAbilityHelper
- 访问文件
通过 DataAbilityHelper 的 FileDescriptor openFile(Uri uri, String mode)
来操作文件,需要传入两个参数,其中uri用来确定目标资源路径,mode用来指定打开文件的方式,这个方法返回一个目标文件的描述符,可以把它封装成文件流,就可以对其进行自定义处理:
// 读取文件描述符
FileDescriptor fd = helper.openFile(uri, “r”);
// 使用文件描述符封装成的文件流,进行文件操作
FileInputStream fis = new FileInputStream(fd);
- 访问数据库
上面的图片又数据库操作的api,可以用来对数据库增删改查,下面是一段查询操作代码
DataAbilityHelper helper = DataAbilityHelper.creator(this);
// 构造查询条件
DataAbilityPredicates predicates = new DataAbilityPredicates();
predicates.between(“userId”, 101, 103);
// 进行查询
ResultSet resultSet = helper.query(uri, columns, predicates);
// 处理结果
resultSet.goToFirstRow();
do {
// 在此处理ResultSet中的记录;
} while(resultSet.goToNextRow());
===========================================================================
Intent 是对象之间传递信息的载体。
当 Intent 用于发起请求时,根据指定元素的不同,分为两种类型:
-
如果同时指定了 BundleName 与 AbilityName, 则根据 AbilityName 来直接启动应用
-
如果未同时指定 BundleName 和 AbilityName, 则根据 Operation 中的其他属性来启动应用
Intent intent = new Intent();
// 通过Intent中的OperationBuilder类构造operation对象,指定设备标识(空串表示当前设备)、应用包名、Ability名称
Operation operation = new Intent.OperationBuilder()
.withDeviceId(“”)
.withBundleName(“com.demoapp”)
.withAbilityName(“com.demoapp.FooAbility”)
.build();
// 把operation设置到intent中
intent.setOperation(operation);
startAbility(intent);
和 Android 的 Intent 一样,可以通过设置 Action,来使用其他应用提供的能力,而不用关心具体是哪一个应用。
例如打开一个 天气查询的功能,请求只需要设置一个 ACTION_QUERY_WEATHER
就行:
private void queryWeather() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction(Intent.ACTION_QUERY_WEATHER)
.build();
intent.setOperation(operation);
startAbilityForResult(intent, REQ_CODE_QUERY_WEATHER);
}
@Override
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
switch (requestCode) {
case REQ_CODE_QUERY_WEATHER:
// Do something with result.
…
return;
default:
…
}
}
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
开一个 天气查询的功能,请求只需要设置一个 ACTION_QUERY_WEATHER
就行:
private void queryWeather() {
Intent intent = new Intent();
Operation operation = new Intent.OperationBuilder()
.withAction(Intent.ACTION_QUERY_WEATHER)
.build();
intent.setOperation(operation);
startAbilityForResult(intent, REQ_CODE_QUERY_WEATHER);
}
@Override
protected void onAbilityResult(int requestCode, int resultCode, Intent resultData) {
switch (requestCode) {
case REQ_CODE_QUERY_WEATHER:
// Do something with result.
…
return;
default:
…
}
}
深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
[外链图片转存中…(img-djD67Pwg-1715167219735)]
[外链图片转存中…(img-YxV5v9aP-1715167219736)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新