精讲鸿蒙多线程下载音视频
多线程下载音视频
官方文档:鸿蒙多线程:
- Actor并发模型每一个线程都是一个独立Actor,每个Actor有自己独立的内存,Actor之间通过消息传递机制触发对方Actor的行为,不同Actor之间不能直接访问对方的内存空间
- Actor并发模型对比内存共享并发模型的优势在于不同线程间内存隔离,不会产生不同线程竞争同一内存资源的问题。开发者不需要考虑对内存上锁导致的一系列功能、性能问题,提升了开发效率。由于Actor并发模型线程之间不共享内存,需要通过线程间通信机制传输并发任务和任务结果

简单示例:
import { taskpool } from '@kit.ArkTS';
// 跨线程并发任务
@Concurrent
async function produce(): Promise<number> {
// 添加生产相关逻辑
console.info("producing...");
return Math.random();
}
class Consumer {
public consume(value: Object) {
// 添加消费相关逻辑
console.info("consuming value: " + value);
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
Button() {
Text("start")
}.onClick(() => {
let produceTask: taskpool.Task = new taskpool.Task(produce);
let consumer: Consumer = new Consumer();
for (let index: number = 0; index < 10; index++) {
// 执行生产异步并发任务
taskpool.execute(produceTask).then((res: Object) => {
consumer.consume(res);
}).catch((e: Error) => {
console.error(e.message);
})
}
})
.width('20%')
.height('20%')
}
.width('100%')
}
.height('100%')
}
}
任务池(TaskPool)简介
官方文档:[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/taskpool-introduction-V5](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/taskpool-introduction-V5)TaskPool运作机制示意图

TaskPool的适用场景主要分为如下三类:
- 需要设置优先级的任务。
- 需要频繁取消的任务。
- 大量或者调度点较分散的任务。
因为朋友圈场景存在不同好友同时上传视频图片,在频繁滑动时将多次触发下载任务,所以下面将以使用朋友圈加载网络数据并且进行解析和数据处理的场景为例,来演示如何使用TaskPool进行大量或调度点较分散的任务开发和处理。
TaskPool支持开发者在宿主线程封装任务抛给任务队列,系统选择合适的工作线程,进行任务的分发及执行,再将结果返回给宿主线程。接口直观易用,支持任务的执行、取消,以及指定优先级的能力,同时通过系统统一线程管理,结合动态调度及负载均衡算法,可以节约系统资源
TaskPool注意事项:
+ 实现任务的函数需要使用[@Concurrent装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/taskpool-introduction-V5#concurrent装饰器)标注,且仅支持在.ets文件中使用 + 从API version 11开始,跨并发实例传递带方法的实例对象时,该类必须使用装饰器[@Sendable装饰器](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-sendable-V5#sendable装饰器)标注,且仅支持在.ets文件中使用 + 任务函数在TaskPool工作线程的执行耗时不能超过3分钟(不包含Promise和async/await异步调用的耗时,例如网络下载、文件读写等I/O任务的耗时),否则会被强制退出。 + 实现任务的函数入参需满足序列化支持的类型,详情请参见[线程间通信对象](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/interthread-communication-overview-V5)。 + ArrayBuffer参数在TaskPool中默认转移,需要设置转移列表的话可通过接口[setTransferList()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-taskpool-V5#settransferlist10)设置。 + 由于不同线程中上下文对象是不同的,因此TaskPool工作线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。 + 序列化传输的数据量大小限制为16MB。 + [Priority](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-taskpool-V5#priority)的IDLE优先级是用来标记需要在后台运行的耗时任务(例如数据同步、备份),它的优先级别是最低的。这种优先级标记的任务只会在所有线程都空闲的情况下触发执行,并且只会占用一个线程来执行。 + Promise不支持跨线程传递,如果TaskPool返回pending或rejected状态的Promise,会返回失败;对于fulfilled状态的Promise,TaskPool会解析返回的结果,如果结果可以跨线程传递,则返回成功。 + 不支持在TaskPool工作线程中使用**[AppStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-appstorage-V5)**。| @Concurrent并发装饰器 | 说明 |
|---|---|
| 装饰器参数 | 无。 |
| 使用场景 | 仅支持在Stage模型的工程中使用。仅支持在.ets文件中使用。 |
| 装饰的函数类型 | 允许标注async函数或普通函数。禁止标注generator、箭头函数、类方法。不支持类成员函数或者匿名函数。 |
| 装饰的函数内的变量类型 | 允许使用local变量、入参和通过import引入的变量。禁止使用闭包变量。 |
| 装饰的函数内的返回值类型 | 支持的类型请查线程间通信对象。 |
由于@Concurrent标记的函数不能访问闭包,因此@Concurrent标记的函数内部不能调用当前文件的其他函数,例如:
装饰器使用示例
**并发函数一般使用**import { taskpool } from '@kit.ArkTS';
@Concurrent
function add(num1: number, num2: number): number {
return num1 + num2;
}
async function ConcurrentFunc(): Promise<void> {
try {
let task: taskpool.Task = new taskpool.Task(add, 1, 2);
console.info("taskpool res is: " + await taskpool.execute(task));
} catch (e) {
console.error("taskpool execute error is: " + e);
}
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World'
build() {
Row() {
Column() {
Text(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
ConcurrentFunc();
})
}
.width('100%')
}
.height('100%')
}
}
并发函数返回Promise
import { taskpool } from '@kit.ArkTS';
@Concurrent
function testPromise(args1: number, args2: number): Promise<number> {
return new Promise<number>((testFuncA, testFuncB)=>{
testFuncA(args1 + args2);
});
}
@Concurrent
async function testPromise1(args1: number, args2: number): Promise<number> {
return new Promise<number>((testFuncA, testFuncB)=>{
testFuncA(args1 + args2);
});
}
@Concurrent
async function testPromise2(args1: number, args2: number): Promise<number> {
return await new Promise<number>((testFuncA, testFuncB)=>{
testFuncA(args1 + args2);
});
}
@Concurrent
function testPromise3() {
return Promise.resolve(1);
}
@Concurrent
async function testPromise4(): Promise<number> {
return 1;
}
@Concurrent
async function testPromise5(): Promise<string> {
return await new Promise((resolve) => {
setTimeout(()=>{
resolve("Promise setTimeout after resolve");
}, 1000)
});
}
async function testConcurrentFunc() {
let task1: taskpool.Task = new taskpool.Task(testPromise, 1, 2);
let task2: taskpool.Task = new taskpool.Task(testPromise1, 1, 2);
let task3: taskpool.Task = new taskpool.Task(testPromise2, 1, 2);
let task4: taskpool.Task = new taskpool.Task(testPromise3);
let task5: taskpool.Task = new taskpool.Task(testPromise4);
let task6: taskpool.Task = new taskpool.Task(testPromise5);
taskpool.execute(task1).then((d:object)=>{
console.info("task1 res is: " + d);
}).catch((e:object)=>{
console.info("task1 catch e: " + e);
})
taskpool.execute(task2).then((d:object)=>{
console.info("task2 res is: " + d);
}).catch((e:object)=>{
console.info("task2 catch e: " + e);
})
taskpool.execute(task3).then((d:object)=>{
console.info("task3 res is: " + d);
}).catch((e:object)=>{
console.info("task3 catch e: " + e);
})
taskpool.execute(task4).then((d:object)=>{
console.info("task4 res is: " + d);
}).catch((e:object)=>{
console.info("task4 catch e: " + e);
})
taskpool.execute(task5).then((d:object)=>{
console.info("task5 res is: " + d);
}).catch((e:object)=>{
console.info("task5 catch e: " + e);
})
taskpool.execute(task6).then((d:object)=>{
console.info("task6 res is: " + d);
}).catch((e:object)=>{
console.info("task6 catch e: " + e);
})
}
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
Row() {
Column() {
Button(this.message)
.fontSize(50)
.fontWeight(FontWeight.Bold)
.onClick(() => {
testConcurrentFunc();
})
}
.width('100%')
}
.height('100%')
}
}
Worker简介
官方文档:[https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5)Worker运作机制示意图

worker并发示例图:

对于Worker,有以下适用场景:
- 运行时间超过3分钟的任务,需要使用Worker。
- 有关联的一系列同步任务,例如数据库增、删、改、查等,要保证同一个句柄,需要使用Worker。
以视频解压的场景为例,点击右上角下载按钮,该示例会执行网络下载并监听,下载完成后自动执行解压操作。当视频过大时,可能会出现解压时长超过3分钟耗时的情况,因此我们选用该场景来说明如何使用Worker。
创建Worker的线程称为宿主线程(不一定是主线程,工作线程也支持创建Worker子线程),Worker自身的线程称为Worker子线程(或Actor线程、工作线程)。每个Worker子线程与宿主线程拥有独立的实例,包含基础设施、对象、代码段等,因此每个Worker启动存在一定的内存开销,需要限制Worker的子线程数量。Worker子线程和宿主线程之间的通信是基于消息传递的,Worker通过序列化机制与宿主线程之间相互通信,完成命令及数据交互。
Worker注意事项
+ 创建Worker时,**有手动和自动两种创建方式**,手动创建Worker线程目录及文件时,还需同步进行相关配置,详情请参考[创建Worker的注意事项](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5#创建worker的注意事项)。 + 使用Worker能力时,构造函数中传入的Worker线程文件的路径在不同版本有不同的规则,详情请参见[文件路径注意事项](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5#文件路径注意事项)。 + Worker创建后**需要手动管理生命周期**,且最多同时运行的Worker子线程数量为64个,详情请参见[生命周期注意事项](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/worker-introduction-V5#生命周期注意事项)。 + 由于不同线程中上下文对象是不同的,因此Worker线程只能使用线程安全的库,例如UI相关的非线程安全库不能使用。 + 序列化传输的数据量大小限制为16MB。 + 使用Worker模块时,需要在宿主线程中**注册onerror**接口,否则当Worker线程出现异常时会发生**jscrash**问题。 + **不支持跨HAP**使用Worker线程文件。 + 引用HAR/HSP前,需要先配置对HAR/HSP的依赖,详见[引用共享包](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-har-import-V5)。 + 不支持在Worker工作线程中使用[AppStorage](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/arkts-appstorage-V5)。创建Worker的注意事项
Worker线程文件需要放在"{moduleName}/src/main/ets/"目录层级之下,否则不会被打包到应用中。有手动和自动两种创建Worker线程目录及文件的方式。- 手动创建:开发者手动创建相关目录及文件,此时需要配置build-profile.json5的相关字段信息,Worker线程文件才能确保被打包到应用中。Stage模型:
"buildOption": {
"sourceOption": {
"workers": [
"./src/main/ets/workers/worker.ets"
]
}
}
- 自动创建:DevEco Studio支持一键生成Worker,在对应的{moduleName}目录下任意位置,点击鼠标右键 > New > Worker,即可自动生成Worker的模板文件及配置信息,无需再手动在build-profile.json5中进行相关配置。
文件路径注意事项
当使用Worker模块具体功能时,均需先构造Worker实例对象,其构造函数与API版本相关,且构造函数需要传入Worker线程文件的路径(scriptURL)。// 导入模块
import { worker } from '@kit.ArkTS';
// API 9及之后版本使用:
const worker1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/worker.ets');
// API 8及之前版本使用:
const worker2: worker.Worker = new worker.Worker('entry/ets/workers/worker.ets');
Stage模型下的文件路径规则
构造函数中的scriptURL要求如下:
- scriptURL的组成包含 {moduleName}/ets 和相对路径 relativePath。
- relativePath是Worker线程文件相对于"{moduleName}/src/main/ets/"目录的相对路径。
1) 加载Ability中Worker线程文件场景
加载Ability中的worker线程文件,加载路径规则:{moduleName}/ets/{relativePath}。
import { worker } from '@kit.ArkTS';
// worker线程文件所在路径:"entry/src/main/ets/workers/worker.ets"
const workerStage1: worker.ThreadWorker = new worker.ThreadWorker('entry/ets/workers/worker.ets');
// worker线程文件所在路径:"phone/src/main/ets/ThreadFile/workers/worker.ets"
const workerStage2: worker.ThreadWorker = new worker.ThreadWorker('phone/ets/ThreadFile/workers/worker.ets');
2) 加载HSP中Worker线程文件场景
3) 加载HAR中Worker线程文件场景
参考官方文档
生命周期注意事项
+ Worker的创建和销毁耗费性能,建议开发者合理管理已创建的Worker并重复使用。Worker空闲时也会一直运行,因此当不需要Worker时,可以调用[terminate()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-worker-V5#terminate9)接口或[close()](https://developer.huawei.com/consumer/cn/doc/harmonyos-references-V5/js-apis-worker-V5#close9)方法主动销毁Worker。若Worker处于已销毁或正在销毁等非运行状态时,调用其功能接口,会抛出相应的错误。 + Worker的数量由内存管理策略决定,设定的内存阈值为1.5GB和设备物理内存的60%中的较小者。在内存允许的情况下,系统最多可以同时运行64个Worker。如果尝试创建的Worker数量超出这一上限,系统将抛出错误:“Worker initialization failure, the number of workers exceeds the maximum.”。实际运行的Worker数量会根据当前内存使用情况动态调整。一旦所有Worker和主线程的累积内存占用超过了设定的阈值,系统将触发内存溢出(OOM)错误,导致应用程序崩溃。Worker基本用法示例
自动New > Worker或手动创建 Worker// Index.ets
import { ErrorEvent, MessageEvents, worker } from '@kit.ArkTS'
// Index.ets
@Entry
@Component
struct Index {
@State message: string = 'Hello World';
build() {
RelativeContainer() {
Text(this.message)
.id('HelloWorld')
.fontSize(50)
.fontWeight(FontWeight.Bold)
.alignRules({
center: { anchor: '__container__', align: VerticalAlign.Center },
middle: { anchor: '__container__', align: HorizontalAlign.Center }
})
.onClick(() => {
// 创建Worker对象
let workerInstance = new worker.ThreadWorker('entry/ets/workers/worker.ets');
// 注册onmessage回调,当宿主线程接收到来自其创建的Worker通过workerPort.postMessage接口发送的消息时被调用,在宿主线程执行
workerInstance.onmessage = (e: MessageEvents) => {
let data: string = e.data;
console.info("workerInstance onmessage is: ", data);
}
// 注册onerror回调,当Worker在执行过程中发生异常时被调用,在宿主线程执行
workerInstance.onerror = (err: ErrorEvent) => {
console.info("workerInstance onerror message is: " + err.message);
}
// 注册onmessageerror回调,当Worker对象接收到一条无法被序列化的消息时被调用,在宿主线程执行
workerInstance.onmessageerror = () => {
console.info('workerInstance onmessageerror');
}
// 注册onexit回调,当Worker销毁时被调用,在宿主线程执行
workerInstance.onexit = (e: number) => {
// 当Worker正常退出时code为0,异常退出时code为1
console.info("workerInstance onexit code is: ", e);
}
// 向Worker线程发送消息
workerInstance.postMessage('1');
})
}
.height('100%')
.width('100%')
}
}
在Worker文件中注册回调函数。
// worker.ets
import { ErrorEvent, MessageEvents, ThreadWorkerGlobalScope, worker } from '@kit.ArkTS';
const workerPort: ThreadWorkerGlobalScope = worker.workerPort;
// 注册onmessage回调,当Worker线程收到来自其宿主线程通过postMessage接口发送的消息时被调用,在Worker线程执行
workerPort.onmessage = (e: MessageEvents) => {
let data: string = e.data;
console.info('workerPort onmessage is: ', data);
// 向主线程发送消息
workerPort.postMessage('2');
}
// 注册onmessageerror回调,当Worker对象接收到一条无法被序列化的消息时被调用,在Worker线程执行
workerPort.onmessageerror = () => {
console.info('workerPort onmessageerror');
}
// 注册onerror回调,当Worker在执行过程中发生异常被调用,在Worker线程执行
workerPort.onerror = (err: ErrorEvent) => {
console.info('workerPort onerror err is: ', err.message);
}
跨har包加载Worker
1. 创建har详情参考[开发静态共享包](https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/har-package-V5)。 2. 在har中创建Worker线程文件相关内容。// worker.ets
workerPort.onmessage = (e: MessageEvents) => {
console.info("worker thread receive message: ", e.data);
workerPort.postMessage('worker thread post message to main thread');
}
其他的参考官方文档
多级Worker生命周期管理
由于支持创建多级Worker(即通过父Worker创建子Worker的机制形成层级线程关系),且Worker线程生命周期由用户自行管理,因此需要注意多级Worker生命周期的正确管理。若用户销毁父Worker时未能结束其子Worker的运行,会产生不可预期的结果。建议用户确保子Worker的生命周期始终在父Worker生命周期范围内,并在销毁父Worker前先销毁所有子Worker。示例及注意事项参考官方文档
音视频示例:
```typescript import taskpool from '@ohos.taskpool'; import { ErrorEvent, MessageEvents, worker } from '@kit.ArkTS'; import { common } from '@kit.AbilityKit'; import { promptAction } from '@kit.ArkUI'; import { photoAccessHelper } from '@kit.MediaLibraryKit'; import { fileIo } from '@kit.CoreFileKit'; import { http } from '@kit.NetworkKit'; import { emitter } from '@kit.BasicServicesKit';let VideoUrl: string = ‘https://v6.huanqiucdn.cn/4394989evodtranscq1500012236/bace602f1397757901699951011/v.f100830.mp4’
// @Sendable
class student {
name: string
constructor(name: string) {
this.name = name;
}
}
@Entry
@Component
struct TaskPoolPage {
@State message: string = ‘Hello World’;
@State p1: boolean = false
@State p2: boolean = false
@State ps1: DataModel = { receiveSize: 0, totalSize: 0 }
@State ps2: DataModel = { receiveSize: 0, totalSize: 0 }
aboutToAppear(): void {
// const workerThread: worker.ThreadWorker = new worker.ThreadWorker(‘…/…/workers/Worker’)
// let oa = new student(‘小黑zi’)
// let task = new taskpool.Task(log, oa)
// let taskGroup: taskpool.TaskGroup = new taskpool.TaskGroup()
// taskGroup.addTask(task)
// taskpool.execute(taskGroup)
// task.onReceiveData((data: string) => {
// console.log(data)
// })
// console.log(‘执行多线程后’ + oa.name)
emitter.on(‘1’, (eventData: emitter.EventData) => {
let data = eventData.data as object
this.ps1.receiveSize = data[‘rec’]
this.ps1.totalSize = data[‘to’]
if (this.ps1.receiveSize === this.ps1.totalSize) {
this.p1 = true
}
BtoMB(this.ps1.receiveSize, this.ps1.totalSize)
.then((value) => {
this.ps1.receiveSize = Number(value[0])
this.ps1.totalSize = Number(value[1])
})
.catch((e: Error) => [
console.log(‘错误:’, e)
])
})
emitter.on(‘2’, (eventData: emitter.EventData) => {
let data = eventData.data as object
this.ps2.receiveSize = data[‘rec’]
this.ps2.totalSize = data[‘to’]
if (this.ps2.receiveSize === this.ps2.totalSize) {
this.p2 = true
}
BtoMB(this.ps2.receiveSize, this.ps2.totalSize)
.then((value) => {
this.ps2.receiveSize = Number(value[0])
this.ps2.totalSize = Number(value[1])
})
.catch((e: Error) => [
console.log('错误:', e)
])
})
}
build() {
Column() {
Text(‘线程一下载’).fontSize(20)
if (!this.p1) {
Stack({ alignContent: Alignment.Center }) {
// 从左往右,1号环形进度条,默认前景色为蓝色渐变,默认strokeWidth进度条宽度为2.0vp
Progress({ value: this.ps1.receiveSize * 100, total: this.ps1.totalSize * 100, type: ProgressType.Ring })
.width(100)
.height(100)
Row({ space: 2 }) {
Text(this.ps1.receiveSize + '').fontSize(15)
Text('/')
Text(this.ps1.totalSize + '').fontSize(15)
Text('MB')
}
}
}
Text('线程二下载').fontSize(20)
if (!this.p2) {
Stack({ alignContent: Alignment.Center }) {
// 从左往右,1号环形进度条,默认前景色为蓝色渐变,默认strokeWidth进度条宽度为2.0vp
Progress({ value: this.ps2.receiveSize * 100, total: this.ps2.totalSize * 100, type: ProgressType.Ring })
.width(100)
.height(100)
Row({ space: 2 }) {
Text(this.ps2.receiveSize + '').fontSize(15)
Text('/')
Text(this.ps2.totalSize + '').fontSize(15)
Text('MB')
}
}
}
SaveButton({ buttonType: ButtonType.Circle, text: SaveDescription.DOWNLOAD })
.onClick((event: ClickEvent, result: SaveButtonOnClickResult) => {
if (result === SaveButtonOnClickResult.SUCCESS) {
const context: common.UIAbilityContext = getContext(this) as common.UIAbilityContext
// todo 添加任务
let task1 = new taskpool.Task(savePhotoToGallery, context, VideoUrl, '1')
let task2 = new taskpool.Task(savePhotoToGallery, context, VideoUrl, '2')
taskpool.execute(task1)
taskpool.execute(task2)
// const workerThread1: worker.ThreadWorker = new worker.ThreadWorker('../../workers/Worker')
// const workerThread2: worker.ThreadWorker = new worker.ThreadWorker('../../workers/Worker')
//
// workerThread1.postMessage({ 'type': 1, value: '「主线程数据包」' })
} else {
promptAction.showToast({ message: '设置权限失败!' })
}
})
}
.height('100%')
.width('100%')
}
}
@Concurrent
function log(stu: student) {
stu.name = ‘小黑’
taskpool.Task.sendData(‘你好’)
console.log(‘执行多线程’ + stu.name)
}
@Concurrent
export async function savePhotoToGallery(context: common.UIAbilityContext, videoSrc: string, emitnum: string) {
let helper = photoAccessHelper.getPhotoAccessHelper(context);
// onClick触发后5秒内通过createAsset接口创建图片文件,5秒后createAsset权限收回。
let uri = await helper.createAsset(photoAccessHelper.PhotoType.VIDEO, ‘mp4’);
// 使用uri打开文件,可以持续写入内容,写入过程不受时间限制
let file = await fileIo.open(uri, fileIo.OpenMode.READ_WRITE | fileIo.OpenMode.CREATE);
let httpRequest = http.createHttp();
// 用于订阅HTTP响应头事件
httpRequest.on(‘headersReceive’, (header: Object) => {
console.info('header: ’ + JSON.stringify(header));
});
// 用于订阅HTTP流式响应数据接收事件
let res = new ArrayBuffer(0);
httpRequest.on(‘dataReceive’, (data: ArrayBuffer) => {
const newRes = new ArrayBuffer(res.byteLength + data.byteLength);
const resView = new Uint8Array(newRes);
resView.set(new Uint8Array(res));
resView.set(new Uint8Array(data), res.byteLength);
res = newRes;
console.info('视频流长度length: ' + res.byteLength);
});
// 用于订阅HTTP流式响应数据接收完毕事件
httpRequest.on(‘dataEnd’, () => {
// 写到媒体库文件中
fileIo.writeSync(file.fd, res);
fileIo.closeSync(file.fd);
promptAction.showToast({ message: 已保存${emitnum}视频! });
console.info(‘No more data in response, data receive end’);
});
// 用于订阅HTTP流式响应数据接收进度事件
httpRequest.on(‘dataReceiveProgress’, (data: http.DataReceiveProgressInfo) => {
let eventData: emitter.EventData = {
data: {
rec: data.receiveSize,
to: data.totalSize
}
};
emitter.emit(emitnum, eventData)
console.log(“dataReceiveProgress receiveSize:” + data.receiveSize + “, totalSize:” + data.totalSize);
});
// 发起一个流式请求
httpRequest.requestInStream(videoSrc,
(num) => {
}
)
}
class DataModel {
receiveSize: number = 0;
totalSize: number = 0;
}
function BtoMB(num1: number, num2: number): Promise<string []> {
return new Promise((resolve, reject) => {
let t1 = num1 / 1024 / 1024
let t2 = num2 / 1024 / 1024
if (t1 && t2) {
resolve([t1.toFixed(2), t2.toFixed(2)])
} else {
reject(‘失败了’)
}
})
}
效果:

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上鸿蒙开发知识点,真正体系化!
由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新
一个人在知识的海洋里独自摸索学习,就像在黑暗中独行,而一群人共同学习鸿蒙,如同并肩前行的探索者,彼此启发、相互促进,思维的火花不断碰撞,知识传递的速度与深度都远超单人,能在学习的道路上走得更快、更远 。

更多推荐



所有评论(0)