当前位置: 首页 > article >正文

鸿蒙开发-网络数据访问、应用本地数据保存

HTTP概述

HTTP,全称Hyper Text Transfer Protocol 超文本传输协议。

HTTP请求为短连接。客户端发起请求,服务器返回响应。本次连接即结束。

添加网络权限

在访问网络之前,需要在module.json5中给APP添加网络权限

"module": {
	"requestPermissions": [
        {
        	"name": "ohos.permission.INTERNET"
        }
    ]
}

HTTP请求开发步骤

导入http模块

import { http } from '@kit.NetworkKit'

创建http请求

let httpRequest = http.createHttp()

订阅响应事件

httpRequest.on('headersReceive', (header) => {});

发起请求

let promise = httpRequest.request(
  url,
  {
    method: http.RequestMethod.POST,
    extraData: {
      "param1": "value1",
      "param2": "value2"
    },
    connectTimeout: 60000,
    readTimeout: 60000,
    expectDataType: http.HttpDataType.STRING,
    usingCache: true,
    priority: 1,
    usingProtocol: http.HttpProtocol.HTTP1_1,
    usingProxy: false,
    header: {
      'Content-Type': 'application/json'
    }
  })

请求参数解读如下表

字段类型
method请求方式
extraData请求的额外数据
connectTimeout连接超时时间
readTimeout读取超时时间
expectDataType指定返回数据的类型
usingCache是否使用缓存,默认true
priority优先级,1~1000,默认1
usingProtocol是否使用协议
usingProxy是否使用http代理
headerhttp请求头

请求方式支持列表

字段类型
GET请求指定的页面信息,并返回响应主体
POST请求会向指定资源提交数据,请求服务器进行处理
PUT请求会向指定资源位置上传其最新内容
CONNECTHTTP/1.1协议预留,能够将连接改为管道方式的代理服务器
HEAD类似GET请求,返回响应头信息,但不会返回响应主体
DELETE请求服务器删除所请求URI
TRACE请求服务器回显其收到的请求信息
OPTIONS请求用于客户端查看服务器的性能

处理响应

let promise = httpRequest.request()

promise.then((data) => {
	if (data.responseCode === http.ResponseCode.OK) {}
})

data中包含以下字段

data包含的字段类型
responseCode响应结果状态码
result响应数据
resultType返回值类型
header响应头
cookies服务器返回的cookies

常见responseCode状态码

名称说明
OK200请求成功
CREATED201已创建新的资源
NOT_FOUND404找不到资源
VERSION505服务器请求的HTTP协议版本

取消订阅

httpRequest.off("headersReceive")

销毁http对象(可选)

httpRequest.destroy()

HTTP流式请求开发步骤

HTTP流式请求通常用于下载文件

前几步与前文HTTP请求相同

// 导入https模块
import { http } from '@kit.NetworkKit’;
// 创建请求
let httpRequest = http.createHttp()
// 订阅响应事件
httpRequest.on('headersReceive', (header) => {
// 返回请求头
});

订阅更多响应事件

let res = ""
httpRequest.on("dataReceive", (data: ArrayBuffer) => {
  // 累加数据
  res += data
})
httpRequest.on("dataEnd", () => {
  // 接收完毕
})

使用requestInStream发起请求

与前文不同的是,这里使用requestInStream发起请求

let promise = httpRequest.requestInStream(
 url,
   {
  method: http.RequestMethod.POST,
     extraData: {
       "param1": "value1",
       "param2": "value2"
     },
     connectTimeout: 60000,
     readTimeout: 60000,
  expectDataType: http.HttpDataType.STRING,
     usingCache: true,
     priority: 1,
  usingProtocol: http.HttpProtocol.HTTP1_1,
     usingProxy: false,
  header: {
       'Content-Type': 'application/json'
     }
 });

请求字段如下

字段类型
method请求方式
extraData请求的额外数据
connectTimeout连接超时时间
readTimeout读取超时时间
expectDataType指定返回数据的类型
usingCache是否使用缓存,默认true
priority优先级,1~1000,默认1
usingProtocol是否使用协议
usingProxy是否使用http代理
headerhttp请求头

处理相应

let promise = httpRequest.requestInStream()

promise.then((responseCode) => {
  if (responseCode === http.ResponseCode.OK) {}
})

取消订阅

httpRequest.off("headersReceive")
httpRequest.off("dataReceive")
httpRequest.off("dataReceiveProgress")
httpRequest.off("dataEnd")

销毁http对象(可选)

httpRequest.destroy()

案例:新闻加载

本章的知识有配套的工程源码,请参阅附件

本Demo将从网络api获取一个新闻列表,收到的数据为json格式,并将其显示到一个列表界面上面

我们将分这几个步骤来实现这个Demo

  1. 行布局
  2. ForEach实现列表
  3. 配置Http请求
  4. 发起请求处理响应
  5. 界面触发更新

在这里插入图片描述

行布局

我们简单做个行布局,横向排列一个文本和图片。

ListItem() {
  Row() {
    Text(item.title)
    Image(item.imgsrc)
  }
}

效果如下

在这里插入图片描述

ForEach实现列表

我们声明一个自定义类Item用于表达每行数据,包含title和imgsrc属性。声明一个@State属性newsList,类型为Item数组。在ForEach渲染的时候把newsList传入,每次循环的时候取得item实例,把其中的属性赋值给UI组件。

class Item {
  title?: string
  imgsrc?: string
}
…
@State newsList: Item[] = new Array(10)ForEach(this.newsList, (item: Item) => {
  ListItem() {}
}, (item: Item) => item.title)

配置Http请求

参考上文,配置Http请求。请求的url可以使用https://v2.alapi.cn/api/new/toutiao?token=qlVquQZPYSeaCi6u。

let httpRequest = http.createHttp()
httpRequest.on('headersReceive', (header) => {
  console.info('header: ' + JSON.stringify(header))
})
let url = "https://v2.alapi.cn/api/new/toutiao?token=qlVquQZPYSeaCi6u"
let promise = httpRequest.request(
  url, // 请求url地址
  {
   	// 请求方式
   	method: http.RequestMethod.GET,
   	// 开发者根据自身业务需要添加header字段
   	header: {
             'Content-Type': 'application/json'
   	}
  }
)

发起请求处理响应

用上文创建的promise任务发送请求,promise.then。在then回调里面拿到返回数据之后,赋值给newsList属性。

@State newsList: Item[] = new Array(10)
…
promise.then((data) => {
  if (data.responseCode === http.ResponseCode.OK) {
    console.info('Result:' + data.result)
    console.info('code:' + data.responseCode)

    this.newsList = JSON.parse(data.result as string)["data"]
  }
}).catch((err: BusinessError) => {
  console.info('error:' + JSON.stringify(err))
})

界面触发更新

newsList更新之后,由于被@State,会自定触发UI更新。在之前定义的ForEach循环中,再次使用newsList渲染,界面将显示相应信息。

@State newsList: Item[] = new Array(10)this.newsList = JSON.parse(data.result as string)["data"]List() {
  ForEach(this.newsList, (item: Item) => {
    ListItem() {
      Row() {
        Text(item.title)
        Image(item.imgsrc)
      }
    }
  }, (item: Item) => item.title)
}

案例:文件下载

本例将通过一个文件下载的Demo来学习Http流式请求。文件我们选择了一个常用磁盘工具DiskGenius,从其官网获得的下载链接,一共30m左右,大小和网络条件都比较合适。

分为以下步骤

  1. 简单界面布局
  2. 点击下载按钮设置保存文件
  3. 配置Http请求
  4. 开始下载
  5. 下载完成

简单界面布局

界面上我们需要这几个元素:下载链接输入框,进度条,下载按钮,下载完成的文字提示。同时,有几个@State修饰的属性。

@State progress用于更新Progress组件的进度显示。

@State downloadUrl用于设置下载链接。代码中可以填写好链接,通常不需要在界面上手动修改。

@State downloadFinished用于表示下载是否完成的状态。可以用于“下载完成”的文字显示与否。

@State progress: number = 0
@State downloadUrl: string = “…”
@State downloadFinished: boolean = falseColumn() {
  TextInput({ text: this.downloadUrl })
  Progress({ value: this.progress })
  Button("下载")
    .onClick(() => {})
  Text("下载完成")
    .visibility(this.downloadFinished ? Visibility.Visible : Visibility.Hidden)
}

大致效果图如下

在这里插入图片描述

点击下载按钮,设置保存文件

接下来,我们要配置点击按钮的动作。

当我们点击下载按钮的时候,会调起系统的文件管理器界面,我们会在界面上声明创建的文件名以及选择它所在的目录。

在这里插入图片描述

这里需要使用系统自带的DocumentViewPicker,调用实例的save方法,在异步回调then里面会获取到新文件的完整路径(包含文件名)。

在回调里面我们用获取到的文件路径来创建一个文件实例,保存在变量file里。

使用fs.openSync方法,打开文件,设定读写模式,并获取file实例。获取到file实例之后我们把它传到我们自己封装的download方法里。

Button("下载")
  .onClick(() => {
   const documentViewPicker = new picker.DocumentViewPicker(); // 创建文件选择器实例
   documentViewPicker.save().then((documentSaveResult: Array<string>) => {
           let file = fs.openSync(documentSaveResult[0], fs.OpenMode.READ_WRITE);
           this.download(file)
    })
  })

配置Http请求

在download方法中,我们拿到了file实例,接下来我们看看怎么实现这个方法。

根据上文,我们同样先创建一个HttpRequest实例,然后注册几个事件监听:dataReceive,dataReceiveProgress和dataEnd。

在dataReceive中,我们会不停获取到文件的部分数据,每次获得数据的时候就用file实例写入磁盘。

在dataReceiveProgress中,我们会获取到文件下载进度。有两个数据,一个是已下载的大小,一个是总大小。我们可以利用这个数据,更新界面上进度条的显示。

在dataEnd中,我们可以关闭文件操作,并把@State修饰的progress属性置为0,让界面上的进度条回到初始显示状态。

download(file: fs.File): void {
  let httpRequest = http.createHttp()
  httpRequest.on('dataReceive', (data: ArrayBuffer) => {
    fs.writeSync(file.fd, data)
  })
  httpRequest.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => {
    this.progress = info.receiveSize / info.totalSize * 100
  })
  httpRequest.on('dataEnd', () => {
    fs.closeSync(file)
    this.progress = 0
  })}

开始下载

创建完请求,注册完监听之后,我们就要触发请求了。不同于上文,我们这里使用httpRequest.requestInStream()方法来配置请求地址。获取到异步任务之后,调用promise.then()发起请求。在文件接收完之后,会触发此处的then回调。

download(file: fs.File): void {
  let httpRequest = http.createHttp()let promise = httpRequest.requestInStream(
      	// 请求url地址
   		this.downloadUrl,
      	{
           	// 请求方式
     		method: http.RequestMethod.GET,
     		header: {
              'Content-Type': 'application/json'
           	}
       	}
  )
  promise.then((data) => {
    console.info('Result:' + data)
  })
}

在这里插入图片描述

下载完成

当下载完成时,会显示完成的提示。

案例:图片下载

本例将通过一个图片下载的Demo来学习Http流式请求。我们选择了百度首页的百度logo图片地址。

分为以下步骤

  1. 简单界面布局
  2. 点击下载按钮调用下载方法
  3. 配置Http请求
  4. 开始下载
  5. 下载完成

简单界面布局

界面上我们需要这几个元素:下载链接输入框,进度条,下载按钮,下载完成的文字提示。同时,有几个@State修饰的属性。

@State progress用于更新Progress组件的进度显示。

@State downloadUrl用于设置下载链接。代码中可以填写好链接,通常不需要在界面上手动修改。

@State downloadFinished用于表示下载是否完成的状态。可以用于“下载完成”的文字显示与否。

@State pixelMap用于设置图片组件的数据源。

@State progress: number = 0
@State downloadUrl: string = “…”
@State downloadFinished: boolean = false
@State pixelMap?: PixelMap = undefined
imageBuffer: ArrayBuffer = new ArrayBuffer(0)Column() {
  TextInput({ text: this.downloadUrl })
  Progress({ value: this.progress })
  Image(this.pixelMap)
  Button("下载")
    .onClick(() => {})
  Text("下载完成")
    .visibility(this.downloadFinished ? Visibility.Visible : Visibility.Hidden)
}

大致效果图如下

在这里插入图片描述

点击下载按钮,调用下载方法

Button("下载")
  .onClick(() => {
       this.download()
  })

配置Http请求

在download方法中,我们拿到了file实例,接下来我们看看怎么实现这个方法。

根据上文,我们同样先创建一个HttpRequest实例,然后注册几个事件监听:dataReceive,dataReceiveProgress和dataEnd。

在dataReceive中,我们会不停获取到文件的部分数据,每次获得数据的时候就拼接到imageBuffer尾部。

在dataReceiveProgress中,我们会获取到文件下载进度。有两个数据,一个是已下载的大小,一个是总大小。我们可以利用这个数据,更新界面上进度条的显示。

在dataEnd中,我们可以调用createImageSource,createPixelMap方法,把收到的二进制数据转换为pixel map。

并把@State修饰的progress属性置为0,让界面上的进度条回到初始显示状态。

download(file: fs.File): void {
  let httpRequest = http.createHttp()
  httpRequest.on('dataReceive', (data: ArrayBuffer) => {
    this.imageBuffer = concatenateArrayBuffers(this.imageBuffer, data)
  })
  httpRequest.on('dataReceiveProgress', (info: http.DataReceiveProgressInfo) => {
    this.progress = info.receiveSize / info.totalSize * 100
  })
  httpRequest.on('dataEnd', () => {
    let imageSource: image.ImageSource = image.createImageSource(this.imageBuffer);
    imageSource.createPixelMap().then((pixelMap: PixelMap) => {
        this.pixelMap = pixelMap
    })
    this.progress = 0
  })}

开始下载

创建完请求,注册完监听之后,我们就要触发请求了。不同于上文,我们这里使用httpRequest.requestInStream()方法来配置请求地址。获取到异步任务之后,调用promise.then()发起请求。在文件接收完之后,会触发此处的then回调。

download(file: fs.File): void {
  let httpRequest = http.createHttp()let promise = httpRequest.requestInStream(
      	// 请求url地址
   		this.downloadUrl,
      	{
           	// 请求方式
     		method: http.RequestMethod.GET,
     		header: {
              'Content-Type': 'application/json'
           	}
       	}
  )
  promise.then((data) => {
    console.info('Result:' + data)
  })
}

下载完成

当下载完成时,会显示完成的提示。

应用本地数据保存

什么是用户首选项

用户首选项为应用提供Key-Value键值型的数据存储能力,支持应用持久化轻量级数据,并对其进行增删除改查等。该存储对象中的数据会被缓存在内存中,因此它可以获得更快的存取速度,下面详细介绍下用户首选项的开发过程。

用户首选项运作机制

用户首选项的特点是:

1、以Key-Value形式存储数据

​ Key是不重复的关键字,Value是数据值。

2、非关系型数据库

​ 区别于关系型数据库,它不保证遵循ACID(Atomicity, Consistency, Isolation and Durability)特性,数据之间无关系。

进程中每个文件对应一个Preferences实例,应用获取到实例后,可以从中读取数据,或者将数据存入实例中。通过调用flush方法可以将实例中的数据回写到文件里。

与关系数据库的区别

分类关系型数据库用户首选项
数据库类型关系型非关系型
使用场景提供复杂场景下的本地数据库管理机制对Key-Value结构的数据进行存取和持久化操作
存储方式SQLite数据库文件
约束与限制1.连接池最大4个 2.同一时间只支持一个写操作1.建议数据不超一万条 2.Key为string型

接口介绍

常用接口有:保存数据(put)、获取数据(get)、是否包含指定的key(has)、数据持久化(flush)、删除数据(delete)等,后面依次详细介绍接口使用。

接口使用前提

1、需要导入preferences模块到开发环境中,同时定义一个常量KEY_APP_FONT_SIZE。相关代码实现如下:

// entryAbility.ets
import { preferences } from '@kit.ArkData';
const KEY_APP_FONT_SIZE = 'appFontSize';  // 用户首选项Key字段

2、需要在entryAbility的onCreate方法获取用户首选项实例,同时定义一个Preferences实例配置选项options,其中name表示Preferences实例的名称,以便后续能进行保存、读取、删除等操作,获取实例需要上下文context和文件名字PREFERENCES_NAME,相关代码实现如下:

// entryAbility.ets  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    Logger.info(TAG, 'onCreate');
    // 设置字体默认大小
    try {
      let options: preferences.Options = { name: 'myStore' };
      let promise = preferences.getPreferences(this.context, options);
      ...
    } catch (err) {
      console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
    }
  }

保存数据(put)

1、在entryAbility的onCreate方法,先用has方法判断当前key是否有存在,如果没有就通过put方法把用户数据保存起来,该方法通过key-value键值对方式保存,常量KEY_APP_FONT_SIZE作为key,用户数据fontSize作为value,再通过flush方法把数据保存到文件,相关代码实现如下:

// entryAbility.ets  
  onCreate(want: Want, launchParam: AbilityConstant.LaunchParam) {
    ...
    // 设置字体默认大小
    try {
      let options: preferences.Options = { name: 'myStore' };
      let promise = preferences.getPreferences(this.context, options);
      promise.then((object: preferences.Preferences) => {
        object.has(KEY_APP_FONT_SIZE).then(async (isExist: boolean) => {
          Logger.info(TAG, 'preferences has changeFontSize is ' + isExist);
          if (!isExist) {
            await object?.put(KEY_APP_FONT_SIZE, CommonConstants.SET_SIZE_NORMAL);
            await object?.flush();
          }
        }).catch((err: Error) => {
          Logger.error(TAG, 'Has the value failed with err: ' + err);
        });
        console.info("Succeeded in getting preferences.");
      }).catch((err: base.BusinessError) => {
        console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
      })
    } catch (err) {
      console.error("Failed to get preferences. code =" + err.code + ", message =" + err.message);
    }
  }

2、在SetFontSizePage页面,当手指移动Slider滑动条时,通过onChange方法回调获取当前进度值,再通过flush方法把数据保存到文件,相关代码实现如下:

// SetFontSizePage.ets
  build() {
    Row() {
      Slider({
       ...
      }).onChange(async (value: number) => {
          // 保存当前进度值
          if (this.changeFontSize === 0) {
            this.fontSizeText = SetViewModel.getTextByFontSize(value);
            return;
          }
          this.changeFontSize = (value === CommonConstants.SET_SLIDER_MAX ? CommonConstants.SET_SIZE_HUGE : value);
          this.fontSizeText = SetViewModel.getTextByFontSize(this.changeFontSize);
          let myPreferences: preferences.Preferences | null = null;
          preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {
            myPreferences = object;
            myPreferences.put(KEY_APP_FONT_SIZE, this.changeFontSize).then(() => {
              myPreferences?.flush();
            });
          })
      })
    }
  }

获取数据(get)

在HomePage的onPageShow方法,调用preferences.get方法获取用户数据,该方法通过key-value键值对方式读取,常量KEY_APP_FONT_SIZE作为key,默认数据defValue作为value,把得到的结果赋值给变量fontSize,相关代码实现如下:

// HomePage.ets
  onPageShow() {
    preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {
      object.get(KEY_APP_FONT_SIZE, 0).then((data) => {
        this.changeFontSize = Number(data);
      });
    })
  }

是否包含指定的key(has)

通过has方法判断用户首选项中是否包含指定的key,保证指定的key不会被重复保存,相关代码实现如下:

// 判断保存的key是否存在
...
preferences.has(KEY_APP_FONT_SIZE).then(async (isExist: boolean) => {
  Logger.info(TAG, 'preferences has changeFontSize is ' + isExist);
}).catch((err: Error) => {
  Logger.error(TAG, 'Has the value failed with err: ' + err);
});

数据持久化(flush)

通过flush方法把应用数据保存到文件中,使得应用数据保存期限变长,相关代码实现如下:

// SetFontSizePage.ets 
...
let myPreferences: preferences.Preferences | null = null;
preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {
  myPreferences = object;
  myPreferences.put(KEY_APP_FONT_SIZE, this.changeFontSize).then(() => {
    myPreferences?.flush();
  });
})

删除数据(delete)

删除用户首选项数据需要获取Preferences实例,用delete方法删除指定的key所对应的值,常量KEY_APP_FONT_SIZE作为key,通过Promise异步回调是否删除成功,相关代码实现如下:

...
let myPreferences: preferences.Preferences | null = null;
preferences.getPreferences(getContext(this), 'myStore').then((object: preferences.Preferences) => {
  myPreferences = object;
  myPreferences.delete(KEY_APP_FONT_SIZE).then(() => {
    console.info("Succeeded in deleting the key 'appFontSize'.");
  }).catch((err: BusinessError) => {
    console.error("Failed to delete the key 'appFontSize'. code =" + err.code +", message =" + err.message);
  });
})

http://www.kler.cn/a/398164.html

相关文章:

  • 基于大语言模型意图识别和实体提取功能;具体ZK数值例子:加密货币交易验证;
  • IDC 报告:百度智能云 VectorDB 优势数量 TOP 1
  • Android笔记(三十七):封装一个RecyclerView Item曝光工具——用于埋点上报
  • c++原型模式(Prototype Pattern)
  • hive alter table add columns 是否使用 cascade 的方案
  • 丹摩征文活动 |【前端开发】HTML+CSS+JavaScript前端三剑客的基础知识体系了解
  • Unity类银河战士恶魔城学习总结(P129 Craft UI 合成面板UI)
  • dockers+Jenkins+git+自动化框架
  • Java基础——高级技术
  • LeetCode 热题100(八)【二叉树】(3)
  • 深入剖析:Spring MVC与Struts的较量
  • 探秘 Nacos 服务注册与发现:微服务领域的创新驱动
  • golang使用etcd版本问题
  • 告别系统限制,一键关闭Windows Defender
  • 计算机视觉 1-8章 (硕士)
  • Electron 沙盒模式与预加载脚本:保障桌面应用安全的关键机制
  • 网络工程实验三:DHCP的配置
  • [UnLua]动态创建SceneCapture2d相机,并且添加渲染目标纹理
  • GA/T1400视图库平台EasyCVR视频融合平台HLS视频协议是什么?
  • DLL注入
  • 鸿蒙next版开发:音频并发策略扩展(ArkTS)
  • GoogleCloud服务器的SSH连接配置
  • [含文档+PPT+源码等]精品基于springboot实现的原生Andriod手机使用管理软件
  • VMware Tools工具安装脚本(CentOS Ubuntu)
  • 【微信小程序】用户房屋管理
  • 软硬互联——革新机器人非标产线智能制造