鸿蒙 Ability 讲解(页面生命周期、后台服务,马上要面试了

@Override

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

}

@Override

public IRemoteObject onConnect(Intent intent) {

return null;

}

@Override

public void onDisconnect(Intent intent) {

}

}

生命周期:onStart()onCommand()onConnect()onDisconnect()onStop()

单个讲解

  • onStart() 该方法在创建 Service 的时候调用,用于 Service 的初始化,在 Service 的整个生命周期只会调用一次。

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

  • onConnect() 在 Ability 和 Service 连接时调用,该方法返回 IRemoteObject 对象,用户可以在该回调函数中生成对应 Service 的 IPC 通信通道,以便 Ability 与 Service 交互。Ability 可以多次连接同一个 Service,系统会缓存该 Service 的 IPC 通信对象,只有第一个客户端连接 Service 时,系统才会调用 Service 的 onConnect 方法来生成 IRemoteObject 对象,而后系统会将同一个RemoteObject 对象传递至其他连接同一个 Service 的所有客户端,而无需再次调用onConnect 方法。

  • onDisconnect() 在 Ability 与绑定的 Service 断开连接时调用。

  • onStop() 在 Service 销毁时调用。Service 应通过实现此方法来清理任何资源,如关闭线程、注册的侦听器等。

② 启动Service Ability

Ability 为开发者提供了startAbility() 方法来启动另外一个 Ability。因为 Service 也是 Ability的一种,开发者同样可以通过将 Intent 传递给该方法来启动 Service。不仅支持启动本地Service,还支持启动远程 Service。

开发者可以通过构造包含 DeviceId、BundleName 与 AbilityName 的 Operation 对象来设置目标 Service 信息。这三个参数的含义如下:

  • DeviceId:表示设备 ID。如果是本地设备,则可以直接留空;如果是远程设备,可以通过ohos.distributedschedule.interwork.DeviceManager 提供的 getDeviceList 获取设备列表。

  • BundleName:表示包名称。

  • AbilityName:表示待启动的 Ability 名称。

下面用代码来实践一下,比如我现在要在MainAbilitySlice的onStart方法中启动ServiceAbility。就可以这么写

/**

  • 启动本地服务

*/

private void startupLocalService() {

Intent intent = new Intent();

//构建操作方式

Operation operation = new Intent.OperationBuilder()

// 设备id

.withDeviceId(“”)

// 应用的包名

.withBundleName(“com.llw.helloworld”)

// 跳转目标的路径名 通常是包名+类名

.withAbilityName(“com.llw.helloworld.ServiceAbility”)

.build();

//设置操作

intent.setOperation(operation);

startAbility(intent);

}

然后在onStart中调用即可。

在这里插入图片描述

那么怎么证明ServiceAbility是启动了呢?很简单,我们只要在ServiceAbility的onStart方法中打印一个日志就可以了。进入到ServiceAbility,你会发现创建的时候就给你写好了日志。

在这里插入图片描述

那么现在启动远程模拟器,然后运行HelloWorld。进入到主页

在这里插入图片描述

那么这个时候Service已经启动了,通过日志来看看,点击编译器下面的HiLog栏目,然后输入Demo,就能找到这个日志了。

在这里插入图片描述

那么现在我们就启动了这个本地的Service,那么如何启动远程的Service呢?

private void startupRemotelyService() {

Intent intent = new Intent();

Operation operation = new Intent.OperationBuilder()

.withDeviceId(“deviceId”)

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

.withAbilityName(“com.huawei.hiworld.himusic.entry.ServiceAbility”)

// 设置支持分布式调度系统多设备启动的标识

.withFlags(Intent.FLAG_ABILITYSLICE_MULTI_DEVICE)

.build();

intent.setOperation(operation);

startAbility(intent);

}

远程启动Service可以这么写,但是有一点你要确认,那就是你启动的这个服务是否允许其他应用程序发现?否则你就算知道这个服务的包名和类名也是白搭。还记得刚才在创建Service Ability的时候的Visible吗?勾选就是允许,默认是没有勾选的。那么我又想去勾选了咋办?难道我现在重新创建一个再勾选上?感觉这样是可以的,但是太蠢了。不够优雅。既然你也不知道怎么搞,我也不知道怎么搞,那么就实验一下,比如我再创建一个ServiceAbility。这里设置名为ServiceAbility2,然后勾选一下Visible,然后我们到config.json配置文件中去看之前的没有勾选的Service有啥不同。

在这里插入图片描述

现在你是不是就有种恍然大明白的感觉了。只要通过加一个visible的属性,设置为true,就可以了,如果没有这个属性,就是默认为false。OK,那么这就解决了这个启动Service的问题。

通过 startAbility() 方法来启动 Service。

  • 如果 Service 尚未运行,则系统会先调用 onStart()来初始化 Service,再回调 Service 的 onCommand()方法来启动Service。刚才我们并没有看到有打印onCommand,是因为它里面没有方法。那么现在我在这个onCommand方法里面也加一个日志,然后重新运行一下

@Override

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

HiLog.error(LABEL_LOG, “ServiceAbility::onCommand”);

}

在这里插入图片描述

  • 如果 Service 正在运行,则系统会直接回调 Service 的 onCommand()方法来启动 Service。这个场景需要先返回到设备主页面,然后再打开这个应用,首先返回主页面,点击右边的圆形按钮

在这里插入图片描述

设备主页,这时候Service在后台运行,然后再点一下圆形按钮,进入到应用页面。

在这里插入图片描述

这里是应用页面,目前只有一个新增的应用,其他两个是系统应用,这里是一个列表,你可以通过鼠标按住左键上下进行拖动。然后点击这个HelloWorld。

在这里插入图片描述

回到应用的主页面。这个时候你看日志

在这里插入图片描述

系统直接回调 Service 的 onCommand()方法来启动 Service。这样实际操作一下是不是印象更深刻呢?为了使这个操作更加易懂,我决定安装一个电脑录屏软件,然后再把录得视频转GIF,再贴到文章里,这样看起来就更加的易懂了。刚才说了启动,那么下面说停止。

③ 停止Service Ability

  • 停止 Service

Service 一旦创建就会一直保持在后台运行,除非必须回收内存资源,否则系统不会停止或销毁 Service。开发者可以在 Service 中通过 terminateAbility()停止本 Service 或在其他 Ability调用 stopAbility()来停止 Service。

停止 Service 同样支持停止本地设备 Service 和停止远程设备 Service,使用方法与启动Service 一样。一旦调用停止 Service 的方法,系统便会尽快销毁 Service。

有两种停止Service的方法,在Page Ability中停止,和在本Service中停止,先试一下第一种。

下面我们在MainAbilitySlice中增加一个停止服务的方法。

/**

  • 停止本地服务 在Page Ability中停止Service

*/

private void stopLocalService() {

Intent intent = new Intent();

//构建操作方式

Operation operation = new Intent.OperationBuilder()

// 设备id

.withDeviceId(“”)

// 应用的包名

.withBundleName(“com.llw.helloworld”)

// 跳转目标的路径名 通常是包名+类名

.withAbilityName(“com.llw.helloworld.ServiceAbility”)

.build();

//设置操作

intent.setOperation(operation);

//停止服务

stopAbility(intent);

}

然后再点击按钮的时候调用。

在这里插入图片描述

然后先运行一下进入到主页面,然后点击Next按钮,看下面的日志。

在这里插入图片描述

可以看到当我们从其他的Page Ability中停止Service时,会先回调onBackground。因为这个时候服务是在前台运行的,系统会把服务放到后台,然后再通过stop来停止这个服务。

下面再看看在本Service中停止这个服务。可以通过一个延时服务来操作,下面来看看代码怎么写的。

/**

  • 创建一个线程池

*/

final static ScheduledExecutorService service = Executors.newScheduledThreadPool(4);

private void stopService() {

// 延时任务

service.schedule(threadFactory.newThread(new Runnable() {

@Override

public void run() {

//停止服务当前服务

terminateAbility();

}

//延时三秒执行

}), 3, TimeUnit.SECONDS);

}

/**

  • 线程工厂

*/

private ThreadFactory threadFactory = new ThreadFactory() {

@Override

public Thread newThread(final Runnable r) {

return new Thread() {

@Override

public void run() {

r.run();

}

};

}

};

为什么要这么写呢?因为DS里面推荐使用ScheduledExecutorService ,不然我就直接用Timer或者Thread就可以了。创建了一个线程池,然后创建一个线程工厂,在进行延时操作的时候,传入了三个参数,一个是线程工厂,里面有一个Runnable(),第二个参数代表数量,第三个参数是单位,上面的代码就是3秒。

下面直接运行到模拟器,然后等待三秒就会自动调用terminateAbility();停止Service。你会发现和通过其他的Page Ability停止服务的执行流程是一样的。在这里插入图片描述

③ 连接Service Ability

如果 Service 需要与 Page Ability 或其他应用的 Service Ability 进行交互,则应创建用于连接的 Connection。Service 支持其他 Ability 通过 connectAbility()方法与其进行连接。

在使用 connectAbility()处理回调时,需要传入目标 Service 的 Intent 与 IAbilityConnection的实例。IAbilityConnection 提供了两个方法供开发者实现:onAbilityConnectDone() 用来处理连接的回调,onAbilityDisconnectDone() 用来处理断开连接的回调。

在MainAbilitySlice中添加如下代码:

/**

  • 连接服务

*/

private void connectService(){

// 连接 Service

Intent intent = new Intent();

//构建操作方式

Operation operation = new Intent.OperationBuilder()

// 设备id

.withDeviceId(“”)

// 应用的包名

.withBundleName(“com.llw.helloworld”)

// 跳转目标的路径名 通常是包名+类名

.withAbilityName(“com.llw.helloworld.ServiceAbility”)

.build();

//设置操作

intent.setOperation(operation);

//连接到服务

connectAbility(intent,connection);

}

// 创建连接回调实例

private IAbilityConnection connection = new IAbilityConnection() {

// 连接到 Service 的回调

@Override

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

// 在这里开发者可以拿到服务端传过来 IRemoteObject 对象,从中解析出服务端传过来的信息

}

// 断开与连接的回调

@Override

public void onAbilityDisconnectDone(ElementName elementName, int i) {

}

};

然后在点击的时候调用

在这里插入图片描述

别Service的onConnect方法中加入日志打印

在这里插入图片描述

下面运行一下:

在这里插入图片描述

连接成功。

④ 断开Service Ability

断开服务其实就比较的简单了,调用**disconnectAbility()**方法即可,而且不用传intent,但是要传IAbilityConnection进入,所以可以可以这样来测试,在连接到Service之后马上断开连接。

//断开服务

disconnectAbility(connection);

在这里插入图片描述

然后运行起来,进入应用页面,然后点击Next。

在这里插入图片描述

OK,到这一步,相信你已经会基本操作了。而Service的生命周期根据调用方法的不同,其生命周期有以下两种路径:

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

  • 连接 Service 该 Service 在其他 Ability 调用 connectAbility()时创建,客户端可通过调用disconnectAbility()断开连接。多个客户端可以绑定到相同 Service,而且当所有绑定全部取消后,系统即会销毁该 Service。

看一下官网的图片

在这里插入图片描述

⑤ 前台Service

刚才我们说的都是后台的Service,那么怎么到前台来呢?最通用的前台服务就是音乐播放了,用手机的时候它会在通知栏创建,然后播放音乐,那么在鸿蒙中需要怎么使用前台服务呢?使用前台 Service 并不复杂,开发者只需在 Service 创建的方法里,调用keepBackgroundRunning()将 Service 与通知绑定。调用 keepBackgroundRunning()方法前需要在配置文件中声明 ohos.permission.KEEP_BACKGROUND_RUNNING 权限,该权限是 normal 级别,同时还需要在配置文件中添加对应的 backgroundModes 参数。在onStop()方法中调用 cancelBackgroundRunning()方法可停止前台 Service。

说这么多没啥用,下面来实际操作一下:

在connectService方法中注释断开服务

在这里插入图片描述

然后进入到ServiceAbility中,新一个启动前台服务的方法。

/**

  • 启动前台服务

*/

private void startupForegroundService(){

//创建通知请求 设置通知id为9527

NotificationRequest request = new NotificationRequest(1005);

//创建普通通知

NotificationRequest.NotificationNormalContent content =

new NotificationRequest.NotificationNormalContent();

//设置通知的标题和内容

content.setTitle(“Title”).setText(“Text”);

//创建通知内容

NotificationRequest.NotificationContent notificationContent = new

NotificationRequest.NotificationContent(content);

//设置通知

request.setContent(notificationContent);

keepBackgroundRunning(1005,request);

HiLog.error(LABEL_LOG, “ServiceAbility::startupForegroundService”);

}

然后在onStart中调用。

在这里插入图片描述

别忘了在config.json中给相关的代码配置:

在这里插入图片描述

然后直接运行到主页面,之后会先启动Service,然后将Service变成前台服务。运行之后如下:

在这里插入图片描述

说实话目前也就只是日志打印出来了,但是我也不知道当前这个服务是不是在前台。

然后在onCommand中取消前台服务:

@Override

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

HiLog.error(LABEL_LOG, “ServiceAbility::onCommand”);

cancelBackgroundRunning();

HiLog.error(LABEL_LOG, “ServiceAbility::cancelBackgroundRunning”);

}

再运行一次。

在这里插入图片描述

四、Data Ability讲解


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

数据的存放形式多样,可以是数据库,也可以是磁盘上的文件。Data 对外提供对数据的增、删、改、查,以及打开文件等接口,这些接口的具体实现由开发者提供。说起来和Android的ContentProvider有些像。

① URI 介绍

Data 的提供方和使用方都通过 URI(Uniform Resource Identifier)来标识一个具体的数据,例如数据库中的某个表或磁盘上的某个文件。HarmonyOS 的 URI 仍基于 URI 通用标准,格式如下:

  • scheme:协议方案名,固定为“dataability”,代表 Data Ability 所使用的协议类型。

  • authority:设备 ID,如果为跨设备场景,则为目的设备的 IP 地址;如果为本地设备场景,则不需要填写。

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

  • query:查询参数。

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

URI 示例:

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

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

② 访问 Data和声明使用权限

开发者可以通过 DataAbilityHelper 类来访问当前应用或其他应用提供的共享数据。

DataAbilityHelper 作为客户端,与提供方的 Data 进行通信。Data 接收到请求后,执行相应的处理,并返回结果。DataAbilityHelper 提供了一系列与 Data Ability 对应的方法。

如果待访问的 Data 声明了访问需要权限,则访问此 Data 需要在配置文件中声明需要此权限。比如

在这里插入图片描述

reqPermissions 表示应用运行时向系统申请的权限。

说了这么多还是来创建一个Data Ability吧,鼠标右键包名 → New → Ability → Empty Data Ability

在这里插入图片描述

这个的Visible和Service的Visible是同样的意思,勾选上就是运行其他应用程序访问数据。

在这里插入图片描述

然后打开config.json,看创建DataAbility时,自动生成了那些代码。

在这里插入图片描述

可以看到type为“data”,另外还自带一个提供给外部数据的权限,已经访问这个DataAbility的uri。

然后看一下DataAbility的代码:

package com.llw.helloworld;

import ohos.aafwk.ability.Ability;

import ohos.aafwk.content.Intent;

import ohos.data.resultset.ResultSet;

import ohos.data.rdb.ValuesBucket;

import ohos.data.dataability.DataAbilityPredicates;

import ohos.hiviewdfx.HiLog;

import ohos.hiviewdfx.HiLogLabel;

import ohos.utils.net.Uri;

import ohos.utils.PacMap;

import java.io.FileDescriptor;

public class DataAbility extends Ability {

private static final HiLogLabel LABEL_LOG = new HiLogLabel(3, 0xD001100, “Demo”);

@Override

public void onStart(Intent intent) {

super.onStart(intent);

HiLog.info(LABEL_LOG, “ProviderAbility onStart”);

}

@Override

public ResultSet query(Uri uri, String[] columns, DataAbilityPredicates predicates) {

return null;

}

@Override

public int insert(Uri uri, ValuesBucket value) {

HiLog.info(LABEL_LOG, “ProviderAbility insert”);

return 999;

}

@Override

public int delete(Uri uri, DataAbilityPredicates predicates) {

return 0;

}

@Override

public int update(Uri uri, ValuesBucket value, DataAbilityPredicates predicates) {

return 0;

}

@Override

public FileDescriptor openFile(Uri uri, String mode) {

return null;

}

@Override

public String[] getFileTypes(Uri uri, String mimeTypeFilter) {

return new String[0];

}

@Override

public PacMap call(String method, String arg, PacMap extras) {

return null;

}

@Override

public String getType(Uri uri) {

return null;

}

}

在创建的时候就生成了一些代码,基本的增删改查、打开文件、获取URI类型、获取文件类型、还有一个回调。再加上一个onStart方法,总共是9个,乍一看比较多。下面先来介绍 DataAbilityHelper 具体的使用步骤。

创建 DataAbilityHelper

DataAbilityHelper 为开发者提供了 creator()方法来创建 DataAbilityHelper 实例。该方法为静态方法,有多个重载。最常见的方法是通过传入一个 context 对象来创建DataAbilityHelper 对象。

在这里插入图片描述

DataAbilityHelper 为开发者提供了一系列的接口来访问不同类型的数据(文件、数据库等)。

  • 访问文件

DataAbilityHelper 为开发者提供了 FileDescriptor openFile(Uri uri, String mode)方法来操作文件。此方法需要传入两个参数,其中 uri 用来确定目标资源路径,mode 用来指定打开文件的方式,可选方式包含“r”(读), “w”(写), “rw”(读写),“wt”(覆盖写),“wa”(追加写),“rwt”(覆盖写且可读)。该方法返回一个目标文件的 FD(文件描述符),把文件描述符封装成流,开发者就可以对文件流进行自定义处理。比如:

// 读取文件描述符

try {

//通过文件描述符 读取指定uri的文件 ,“r”(读), “w”(写), “rw”(读写),“wt”(覆盖写),“wa”(追加写),“rwt”(覆盖写且可读)

FileDescriptor fileDescriptor = helper.openFile(Uri.parse(“dataability://com.llw.helloworld.DataAbility”),“r”);

//获取文件输入流

FileInputStream fileInputStream = new FileInputStream(fileDescriptor);

} catch (DataAbilityRemoteException e) {

e.printStackTrace();

} catch (FileNotFoundException e) {

e.printStackTrace();

}

  • 访问数据库

DataAbilityHelper 为开发者提供了增、删、改、查以及批量处理等方法来操作数据库。

下面代码来说明一下:

  • query 查询方法,其中 uri 为目标资源路径,columns 为想要查询的字段。开发者的查询条件可以通过 DataAbilityPredicates 来构建。查询用户表中 id 在 1-10 之间的用户的年龄,并把结果打印出来,代码示例如下:

/**

  • 查询

*/

private void queryData(DataAbilityHelper helper) {

//构建uri

Uri uri = Uri.parse(“dataability://com.llw.helloworld.DataAbility”);

//构建查询字段

String[] column = {“age”};

// 构造查询条件

DataAbilityPredicates predicates = new DataAbilityPredicates();

//查询用户id在1~10之间的数据

predicates.between(“userId”,1,10);

//进行查询

try {

//用一个结果集来接收查询返回的数据

ResultSet resultSet = helper.query(uri,column,predicates);

//从第一行开始

resultSet.goToFirstRow();

//处理每一行的数据

do {

// 在此处理 ResultSet 中的记录

HiLog.info(LABEL_LOG, resultSet.toString());

}while (resultSet.goToNextRow());

} catch (DataAbilityRemoteException e) {

e.printStackTrace();

}

}

  • insert 插入方法,其中 uri 为目标资源路径,ValuesBucket 为要新增的对象。插入一条用户信息的代码示例如下:

/**

  • 插入 单条数据

*/

private void insertData(DataAbilityHelper helper) {

//构建uri

Uri uri = Uri.parse(“dataability://com.llw.helloworld.DataAbility”);

// 构造插入数据

ValuesBucket valuesBucket = new ValuesBucket();

valuesBucket.putString(“name”,“KaCo”);

valuesBucket.putInteger(“age”,24);

try {

helper.insert(uri,valuesBucket);

} catch (DataAbilityRemoteException e) {

e.printStackTrace();

}

}

  • batchInsert 批量插入方法,和 insert()类似。批量插入用户信息的代码示例如下:

/**

  • 插入 多条数据

  • @param helper 数据帮助类

*/

private void batchInsertData(DataAbilityHelper helper) {

//构建uri

Uri uri = Uri.parse(“dataability://com.llw.helloworld.DataAbility”);

// 构造插入数据

ValuesBucket[] valuesBuckets = new ValuesBucket[3];

//构建第一条数据

valuesBuckets[0] = new ValuesBucket();

valuesBuckets[0].putString(“name”,“Jim”);

valuesBuckets[0].putInteger(“age”,18);

//构建第二条数据

valuesBuckets[1] = new ValuesBucket();

valuesBuckets[1].putString(“name”,“Tom”);

valuesBuckets[1].putInteger(“age”,20);

//构建第三条数据

valuesBuckets[2] = new ValuesBucket();

valuesBuckets[2].putString(“name”,“Kerry”);

valuesBuckets[2].putInteger(“age”,24);

try {

//批量插入数据

helper.batchInsert(uri,valuesBuckets);

} catch (DataAbilityRemoteException e) {

e.printStackTrace();

}

}

  • delete 删除方法,其中删除条件可以通过 DataAbilityPredicates 来构建。删除用户表中 id 在 1-10 之间的用户,代码示例如下:

/**

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
img
img
img

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

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

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
img

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

uesBuckets[2] = new ValuesBucket();

valuesBuckets[2].putString(“name”,“Kerry”);

valuesBuckets[2].putInteger(“age”,24);

try {

//批量插入数据

helper.batchInsert(uri,valuesBuckets);

} catch (DataAbilityRemoteException e) {

e.printStackTrace();

}

}

  • delete 删除方法,其中删除条件可以通过 DataAbilityPredicates 来构建。删除用户表中 id 在 1-10 之间的用户,代码示例如下:

/**

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数HarmonyOS鸿蒙开发工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年HarmonyOS鸿蒙开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
[外链图片转存中…(img-bFIo1c7d-1712805179445)]
[外链图片转存中…(img-YDSeg8Zg-1712805179446)]
[外链图片转存中…(img-qT6OPbwi-1712805179446)]

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

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

如果你觉得这些内容对你有帮助,可以添加VX:vip204888 (备注鸿蒙获取)
[外链图片转存中…(img-k9Qldkz4-1712805179446)]

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值