程序员的资源宝库

网站首页 > gitee 正文

HarmonyOS网络管理

sanyeah 2024-04-25 18:46:54 gitee 8 ℃ 0 评论

网络管理模块主要提供以下功能:

  • HTTP数据请求:通过HTTP发起一个数据请求。
  • WebSocket连接:使用WebSocket建立服务器与客户端的双向连接。
  • Socket连接:通过Socket进行数据传输。

使用网络管理模块的相关功能时,需要请求相应的权限。

权限名说明
ohos.permission.GET_NETWORK_INFO 获取网络连接信息。
ohos.permission.SET_NETWORK_INFO 修改网络连接状态。
ohos.permission.INTERNET 允许程序打开网络套接字,进行网络连接。

一、HTTP数据请求

日常生活中我们使用应用程序看新闻、视频、发送消息等,都需要连接到互联网,从服务端获取数据。例如,使用哔哩哔哩可以从B站的服务器中获取视频,从展示给用户。

要实现这样一种能实时从服务端获取数据的场景,就依赖于HTTP数据请求。

1.1.什么是HTTP

HTTP即超文本传输协议(Hyper Text Transfer Protocol,HTTP)是一个简单的请求-响应协议。它指定了客户端可能发送给服务器什么样的消息以及得到什么样的响应。

HTTP的工作原理正如上图所示,客户端向服务端发出一条HTTP数据请求,服务端接收请求后向客户端返回一些数据,然后客户端再对这些数据进行解析和处理。

1.2.如何发起HTTP请求

1.2.1.内置的http模块发送http请求

HTTP数据请求功能主要由http模块提供,包括发起请求、中断请求、订阅/取消订阅HTTP Response Header 事件等。在进行网络请求前,您需要在module.json5文件中申明网络访问权限。

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

应用访问网络需要申请ohos.permission.INTERNET权限,因为HarmonyOS提供了一种访问控制机制即应用权限,用来保证这些数据或功能不会被不当或恶意使用。可以按照以下步骤完成HTTP数据请求:

⑴.导入http模块。

import http from '@ohos.net.http';

⑵.创建httpRequest对象。

使用createHttp()创建一个httpRequest对象,里面包括常用的一些网络请求方法,比如request、destroy、on('headerReceive')等。

let httpRequest = http.createHttp();

注意:每一个httpRequest对象对应一个http请求任务,不可复用。换言之就是每一次都需创建一个创建httpRequest对象

⑶.订阅请求头(可选)。

用于订阅http响应头,此接口会比request请求先返回,可以根据业务需要订阅此消息。httpRequest提供了 on() 和 off() 方法用来监听 Response Header 事件的回调,

httpRequest.on("headersReceive", (headers) => {
  // todo
})

httpRequest.off("headersReceive", (headers) => {
  // todo
})

⑷.发起http请求。

http模块支持常用的POST和GET等方法,封装在RequestMethod中。调用request方法发起网络请求,需要传入两个参数。第一个是请求的url地址,第二个是可选参数,类型为HttpRequestOptions,用于定义可选参数的类型和取值范围,包含请求方式、连接超时时间、请求头字段等。

使用Get请求,参数内容需要拼接到URL中进行发送,如下示例中在url后面拼接了两个自定义参数,分别命名为param1和param2,值分别为value1和value2:
let url= "https://EXAMPLE_URL?param1=v1&param2=v2";
let promise = httpRequest.request(
  // 请求url地址
  url,
  {
    // 请求方式
    method: http.RequestMethod.GET,
    // 可选,默认为60s
    connectTimeout: 60000,
    // 可选,默认为60s
    readTimeout: 60000,
    // 开发者根据自身业务需要添加header字段
    header: {
      'Content-Type': 'application/json'
    }
  })

POST请求参数需要添加到extraData里面,如下示例中在extraData里面定义添加了两个自定义参数param1和param2,值分别为value1和value2:

let url = "https://EXAMPLE_URL";
let promise = httpRequest.request(
  // 请求url地址
  url,
  {
    // 请求方式
    method: http.RequestMethod.POST,
    // 请求的额外数据。
    extraData: {
      "param1": "value1",
      "param2": "value2",
    },
    // 可选,默认为60s
    connectTimeout: 60000,
    // 可选,默认为60s
    readTimeout: 60000,
    // 开发者根据自身业务需要添加header字段
    header: {
      'Content-Type': 'application/json'
    }
  });

⑸.处理响应结果。

data为网络请求返回的结果,err为请求异常时返回的结果。data的类型为HttpResponse。

promise.then((data) => { 
  if (data.responseCode === http.ResponseCode.OK) {
    console.info('Result:' + data.result);
    console.info('code:' + data.responseCode);
  }
}).catch((err) => {
  console.info('error:' + JSON.stringify(err));
});

其中,data.responseCode为http请求返回的状态码,如果状态码为http.ResponseCode.OK(即200),则表示请求成功,更多状态码可以在ResponseCode中查看。

data.result为服务器返回的业务数据,开发者可以根据自身业务场景解析此数据。

HttpResponse是request方法回调函数的返回值类型。说明如下:

参数名

类型

必填

说明

result

string | Object | ArrayBuffer8+

Http请求根据响应头中Content-type类型返回对应的响应格式内容:

- application/json:返回JSON格式的字符串,如需Http响应具体内容,需开发者自行解析

- application/octet-stream:ArrayBuffer

- 其他:string

responseCode

ResponseCode | number

回调函数执行成功时,此字段为ResponseCode。若执行失败,错误码将会从AsyncCallback中的err字段返回。错误码如下:

- 200:通用错误

- 202:参数错误

- 300:I/O错误

header

Object

发起http请求返回来的响应头。当前返回的是JSON格式字符串,如需具体字段内容,需开发者自行解析。常见字段及解析方式如下:

- Content-Type:header['Content-Type'];

- Status-Line:header['Status-Line'];

- Date:header.Date/header['Date'];

- Server:header.Server/header['Server'];

cookies8+

Array<string>

服务器返回的 cookies。

1.2.2.发送http请求案例

实现一个读取百度首页html的例子,样例结果如下图所示:

 

点击按钮后,会请求百度首页,请求成功后将返回内容显示出来代码如下:

import http from '@ohos.net.http';

@Entry
@Component
struct HttpExample {
  @State html: string = "";
  @State header: string = "";

  build() {
    Column({space:20}){
      Button("获取网页信息")
        .onClick(()=>{
          this.httpRequest();
        })

      Text(this.header).margin({bottom:10})

      Text(this.html)

    }.width("100%").height("100%").padding(20)
  }

  private httpRequest(){
    //创建对象
    let httpRequest = http.createHttp()

    //发送请求
    httpRequest.request(
      "https://www.baidu.com",
      {
        method: http.RequestMethod.GET,
        header:{
          "Content-Type": 'text/plain' // 系统默认Content-Type为application/json,读取网页需要设置为文本text/plain或者text/html
        },
        readTimeout: 15000, //读取超时时间
        connectTimeout: 15000 //连接超时时间
      }
    )
      .then(resp=>{
        //判断状态码是否为200
        if(http.ResponseCode.OK == resp.responseCode){
          //解析响应头和响应体
          this.header = JSON.stringify(resp.header)
          this.html = JSON.stringify(resp.result)
        }
      })
      .catch(error=>{
        console.log("error: "+JSON.stringify(error))
      })
  }
}

二、HarmonyOS如何使用异步并发能力进行开发

在编程过程中,经常会遇到需要处理异步操作的情况,例如网络请求、文件读写等。传统的异步回调方式存在回调地狱和代码可读性差的问题,这导致代码难以维护和扩展。为了解决这些问题,鸿蒙推出了一种强大的异步编程解决方案——鸿蒙 Promise。

2.1.Promise 简介

Promise 是一种解决异步编程的模式,它提供了一种更加优雅的方式来处理异步操作。Promise 可以表示一个异步操作的最终完成或失败,并可以将异步操作的结果传递给相关处理函数。鸿蒙 Promise 的特点如下:

  • 异步操作的结果可以通过 resolve() 和 reject() 方法传递给相关处理函数;
  • 可以通过链式调用的方式处理多个异步操作,提高代码可读性;
  • 支持对多个 Promise 实例进行并行或串行的处理;
  • 可以通过 catch() 方法捕获异常。

2.2.Promise语法说明

Promise是一种用于处理异步操作的对象,它代表了一个异步操作的最终完成或失败,以及其结果值。new Promise是用于创建一个新的Promise对象的语法。下面是new Promise的基本语法:

let myPromise = new Promise(function(resolve, reject) {
  // 异步操作
  // 如果操作成功,调用 resolve(value),并传递操作结果
  // 如果操作失败,调用 reject(error),并传递错误信息
});

new Promise接受一个带有resolvereject两个参数的函数作为参数。在这个函数中,你可以执行异步操作,当操作成功时调用resolve,并传递操作结果,当操作失败时调用reject,并传递错误信息。

下面是一个简单的例子,演示了如何使用new Promise来封装一个异步操作:

let myAsyncFunction = function() {
  return new Promise(function(resolve, reject) {
    setTimeout(function() {
      let randomNumber = Math.random();
      if (randomNumber > 0.5) {
        resolve(randomNumber);
      } else {
        reject("Random number is less than 0.5");
      }
    }, 1000);
  });
};

myAsyncFunction()
  .then(function(result) {
    console.log("Operation succeeded with result: " + result);
  })
  .catch(function(error) {
    console.log("Operation failed with error: " + error);
  });

在这个例子中,myAsyncFunction是一个返回Promise对象的函数。在Promise中,我们使用setTimeout模拟了一个异步操作,1秒后随机生成一个数字,如果数字大于0.5,则调用resolve,否则调用reject。然后我们使用.then.catch来处理操作成功和失败的情况。

2.3.案例说明

基于ArkTS的声明式开发范式实现的样例,主要介绍了数据请求使用。如下:

相关概念

  • List:列表包含一系列相同宽度的列表项。

  • Tabs:通过页签进行内容视图切换的容器组件。

  • TabContent组件:仅在Tabs中使用,对应一个切换页签的内容视图。

  • 数据请求:提供HTTP数据请求能力。

相关权限

添加网络权限:ohos.permission.INTERNET。

2.3.1.服务端搭建流程

1)搭建nodejs环境:服务端是基于nodejs实现的,需要安装nodejs,如果本地已有nodejs环境可以跳过此步骤。检查本地是否安装nodejs:打开命令行工具(如Windows系统的cmd和Mac电脑的Terminal,这里以Windows为例),输入node -v,如果可以看到版本信息,说明已经安装nodejs:

2)运行服务端代码:在HttpServerOfNews目录下打开命令行工具,输入npm install 安装服务端依赖包,安装成功后输入npm start点击回车。看到“服务器启动成功!”则表示服务端已经在正常运行。

2.3.2.案例代码

1)创建项目在ets下创建common,封装请求服务

创建constants子包,新建文件CommonConstan.ets,里面封装场景的信息内容如下:

import { NewsTypeBean } from '../../viewmodel/NewsViewModel';

export class CommonConstant {

  /**
   * 服务器地址
   */
  static base_url = "http://192.168.0.194:9588"

  /**
   * 铺满的高度
   */
  static readonly FULL_HEIGHT: string = '100%';

  /**
   * TabBars 常量
   */
  static readonly NewsListConstant_ITEM_BORDER_RADIUS: number = 16;
  static readonly TabBars_UN_SELECT_TEXT_FONT_SIZE: number = 18;
  static readonly TabBars_SELECT_TEXT_FONT_SIZE: number = 24;
  static readonly TabBars_UN_SELECT_TEXT_FONT_WEIGHT: number = 400;
  static readonly TabBars_SELECT_TEXT_FONT_WEIGHT: number = 700;
  static readonly TabBars_BAR_HEIGHT: string = '7.2%';
  static readonly TabBars_HORIZONTAL_PADDING: string  = '2.2%';
  static readonly TabBars_BAR_WIDTH: string = '100%';


  static readonly TabBars_DEFAULT_NEWS_TYPES: Array<NewsTypeBean> = [
    { id: 0, name: '全部' },
    { id: 1, name: '国内' },
    { id: 2, name: '国际' },
    { id: 3, name: '娱乐' },
    { id: 4, name: '军事' },
    { id: 5, name: '体育' },
    { id: 6, name: '科技' },
    { id: 7, name: '财经' }
  ];


  /**
   * 页面展示新闻大小
   */
  static readonly PAGE_SIZE: number = 4;

  /**
   * 刷新和加载高度
   */
  static readonly CUSTOM_LAYOUT_HEIGHT: number = 70;

  /**
   * 弹窗持续时间
   */
  static readonly ANIMATION_DURATION: number = 2000;

  /**
   * NewsTitle 常量
   */
  static readonly NewsTitle_TEXT_FONT_SIZE: number = 20;
  static readonly NewsTitle_TEXT_FONT_WEIGHT: number = 500;
  static readonly NewsTitle_TEXT_MARGIN_LEFT: string = '2.4%';
  static readonly NewsTitle_TEXT_WIDTH: string = '78.6%';
  static readonly NewsTitle_IMAGE_MARGIN_LEFT: string = '3.5%';
  static readonly NewsTitle_IMAGE_WIDTH: string = '11.9%';

  /**
   * NewsContent 常量
   */
  static readonly NewsContent_WIDTH: string = '93%';
  static readonly NewsContent_HEIGHT: string = '16.8%';
  static readonly NewsContent_MARGIN_LEFT: string = '3.5%';
  static readonly NewsContent_MARGIN_TOP: string = '3.4%';
  static readonly NewsContent_MAX_LINES: number = 2;
  static readonly NewsContent_FONT_SIZE: number = 15;

  /**
   * NewsSource 常量
   */
  static readonly NewsSource_MAX_LINES: number = 1;
  static readonly NewsSource_FONT_SIZE: number = 12;
  static readonly NewsSource_MARGIN_LEFT: string = '3.5%';
  static readonly NewsSource_MARGIN_TOP: string = '3.4%';
  static readonly NewsSource_HEIGHT: string = '7.2%';
  static readonly NewsSource_WIDTH: string = '93%';

  /**
   * NewsGrid 常量
   */
  static readonly NewsGrid_MARGIN_LEFT: string = '3.5%';
  static readonly NewsGrid_MARGIN_RIGHT: string = '3.5%';
  static readonly NewsGrid_MARGIN_TOP: string = '5.1%';
  static readonly NewsGrid_WIDTH: string = '93%';
  static readonly NewsGrid_HEIGHT: string = '31.5%';
  static readonly NewsGrid_ASPECT_RATIO: number = 4;
  static readonly NewsGrid_COLUMNS_GAP: number = 5;
  static readonly NewsGrid_ROWS_TEMPLATE: string = '1fr';
  static readonly NewsGrid_IMAGE_BORDER_RADIUS: number = 8;

  /**
   * RefreshLayout 常量
   */
  static readonly RefreshLayout_MARGIN_LEFT: string = '40%';
  static readonly RefreshLayout_TEXT_MARGIN_BOTTOM: number = 1;
  static readonly RefreshLayout_TEXT_MARGIN_LEFT: number = 7;
  static readonly RefreshLayout_TEXT_FONT_SIZE: number = 17;
  static readonly RefreshLayout_IMAGE_WIDTH: number = 18;
  static readonly RefreshLayout_IMAGE_HEIGHT: number = 18;

  /**
   * NoMoreLayout 常量
   */
  static readonly NoMoreLayoutConstant_NORMAL_PADDING: number = 8;
  static readonly NoMoreLayoutConstant_TITLE_FONT: string = '16fp';

  /**
   * RefreshConstant 常量
   */
  static readonly RefreshConstant_DELAY_PULL_DOWN_REFRESH: number = 50;
  static readonly RefreshConstant_CLOSE_PULL_DOWN_REFRESH_TIME: number = 150;
  static readonly RefreshConstant_DELAY_SHRINK_ANIMATION_TIME: number = 500;

  /**
   * 网格列模板
   */
  static readonly GRID_COLUMN_TEMPLATES: string = '1fr ';

  /**
   * 偏移单位
   */
  static readonly LIST_OFFSET_UNIT: string = 'px';

}

在utils包下创建HttpUtil.ets,封装http请求操作:

import http from '@ohos.net.http';
import { CommonConstant } from '../constants/CommonConstan';
export function request(url: string, method: http.RequestMethod, requestData?: any) {

  let httpRequest = http.createHttp();
  let httpResponse = httpRequest.request(CommonConstant.base_url + url, {
    method: method,
    header: {
      'Content-Type': 'application/json'
    },
    extraData: JSON.stringify(requestData),
    connectTimeout: 60000,
    readTimeout: 60000

  })
  return  httpResponse.then((resp) => {
    if (resp.responseCode === http.ResponseCode.OK) {
      console.info('Result:' + resp.result)
      return JSON.parse(resp.result.toString()) //将json字符串转换成对象
    }
  }).catch((err) => {
    console.info('error:' + JSON.stringify(err));
  });

}

/**
 * Get请求
 * @param url
 */
export function httpRequestGet(url: string){
  return request(url,http.RequestMethod.GET)
}

export  function  httpRequestPost(url: string,requestData?: any){
  return request(url,http.RequestMethod.POST,requestData)
}

export  function  httpRequestPut(url: string,requestData?: any){
  return request(url,http.RequestMethod.PUT,requestData)
}

export  function  httpRequestDelete(url: string,requestData?: any){
  return request(url,http.RequestMethod.DELETE,requestData)
}

2)在components包下创建组件

创建TabBarComponent.ets实现tabs功能:

import { CommonConstant } from '../common/constants/CommonConstan';
import { NewsViewList } from '../viewmodel/NewsModel';
import { NewsTypeBean} from '../viewmodel/NewsViewModel';
import { NewsListComponent } from './NewsListComponent';

@Component
export struct TabBarComponent {
  //获取所有的tabs信息
  @State newsTypeData:NewsTypeBean[] = []
  private controller: TabsController = new TabsController();  // 创建一个名为controller的TabsController实例,并设置为私有变量

  //定义tabs的索引
  @State currentIndex: number = 0;//选项卡下标,默认为第一个

  //获取数据
  changeCategory(){
    //通过接口方法获取数据
    NewsViewList.getNewsTypeList().then((result:NewsTypeBean[])=>{
      this.newsTypeData = result
    })
  }

  /**
   * aboutToAppear函数在创建自定义组件的新实例后,在执行其build()函数之前执行
   */
  aboutToAppear(){
    this.changeCategory();
  }

  @Builder TabBuilder(selectId: number, name:string) {
    Column() {
      Text(name)
        .height(CommonConstant.FULL_HEIGHT)
        .padding({ left: CommonConstant.TabBars_HORIZONTAL_PADDING, right: CommonConstant.TabBars_HORIZONTAL_PADDING })
        .fontSize(this.currentIndex === selectId ? CommonConstant.TabBars_SELECT_TEXT_FONT_SIZE : CommonConstant.TabBars_UN_SELECT_TEXT_FONT_SIZE)
        .fontWeight(this.currentIndex === selectId ? CommonConstant.TabBars_SELECT_TEXT_FONT_WEIGHT : CommonConstant.TabBars_UN_SELECT_TEXT_FONT_WEIGHT)
        .fontColor($r('app.color.fontColor_text3'))
    }.onClick(() => {  // 点击事件监听
      //修改状态
      this.currentIndex = selectId  // 点击后更新index的值为0
      /**
       * 用来调用TabsController实例的changeIndex方法,目的是更新选项卡的索引。
       * 在这段代码中,this.controller是一个TabsController实例,通过调用changeIndex方法并传入this.index作为参数,
       * 可以实现在用户点击选项卡时更新选项卡的索引位置,从而实现选项卡内容的切换。
       */
      this.controller.changeIndex(this.currentIndex)  // 调用控制器的changeIndex方法,更新选项卡的索引
    })
  }


  build() {
    Tabs({
      barPosition: BarPosition.Start, // TabBar排列在下方
      controller: this.controller,   // 绑定控制器
      index:this.currentIndex //打开的页面索引
    }){
      ForEach(this.newsTypeData,(item:NewsTypeBean,index)=>{
        TabContent(){
          Column() {
            //信息列表
            NewsListComponent()
          }
          .width('100%')  // 设置宽度为100%
          .height('100%')  // 设置高度为100%
          .backgroundColor("#ccaabb")  // 设置背景颜色
        }
        .tabBar(this.TabBuilder(item.id,item.name))    // 使用自定义TabBar,传入信息
      })
    }
    .vertical(false)
    .barHeight(CommonConstant.TabBars_BAR_HEIGHT)   // 设置标签栏高度为60
    .barWidth(CommonConstant.TabBars_BAR_WIDTH)
    .barMode(BarMode.Scrollable)         // TabBar均分布局模式,具体描述见BarMode枚举说明。默认值:BarMode.Fixed\
    .onChange((idx: number) => {  // 页面切换回调
      this.currentIndex = idx;
    })

  }
}

创建NewsItemComponent.ets,实现单个新闻卡片

import { CommonConstant } from '../common/constants/CommonConstan';
import { NewsData, NewsFile } from '../viewmodel/NewsViewModel';
@Component
export struct NewsItemComponent {
  newsData: NewsData

  build() {
    Column() {
      Row() {
        Image($r('app.media.news'))
          .width(CommonConstant.NewsTitle_IMAGE_WIDTH)
          .height($r('app.float.news_title_image_height'))
          .objectFit(ImageFit.Fill)
        Text(this.newsData.title)
          .fontSize(CommonConstant.NewsTitle_TEXT_FONT_SIZE)
          .fontColor($r('app.color.fontColor_text'))
          .width(CommonConstant.NewsTitle_TEXT_WIDTH)
          .maxLines(1)
          .margin({ left: CommonConstant.NewsTitle_TEXT_MARGIN_LEFT })
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .fontWeight(CommonConstant.NewsTitle_TEXT_FONT_WEIGHT)
      }
      .alignItems(VerticalAlign.Center)
      .height($r('app.float.news_title_row_height'))
      .margin({
        top: $r('app.float.news_title_row_margin_top'),
        left: CommonConstant.NewsTitle_IMAGE_MARGIN_LEFT
      })

      Text(this.newsData.content)
        .fontSize(CommonConstant.NewsContent_FONT_SIZE)
        .fontColor($r('app.color.fontColor_text'))
        .height(CommonConstant.NewsContent_HEIGHT)
        .width(CommonConstant.NewsContent_WIDTH)
        .maxLines(CommonConstant.NewsContent_MAX_LINES)
        .margin({ left: CommonConstant.NewsContent_MARGIN_LEFT, top: CommonConstant.NewsContent_MARGIN_TOP })
        .textOverflow({ overflow: TextOverflow.Ellipsis })

      Grid() {
        ForEach(this.newsData.imagesUrl, (itemImg: NewsFile) => {
          GridItem() {
            Image(CommonConstant.base_url + itemImg.url)
              .objectFit(ImageFit.Cover)
              .borderRadius(CommonConstant.NewsGrid_IMAGE_BORDER_RADIUS)
          }
        }, (itemImg: NewsFile, index?: number) => JSON.stringify(itemImg) + index)
      }
      .columnsTemplate(CommonConstant.GRID_COLUMN_TEMPLATES.repeat(this.newsData.imagesUrl.length))
      .columnsGap(CommonConstant.NewsGrid_COLUMNS_GAP)
      .rowsTemplate(CommonConstant.NewsGrid_ROWS_TEMPLATE)
      .width(CommonConstant.NewsGrid_WIDTH)
      .height(CommonConstant.NewsGrid_HEIGHT)
      .margin({ left: CommonConstant.NewsGrid_MARGIN_LEFT, top: CommonConstant.NewsGrid_MARGIN_TOP,
        right: CommonConstant.NewsGrid_MARGIN_RIGHT })

      Text(this.newsData.source)
        .fontSize(CommonConstant.NewsSource_FONT_SIZE)
        .fontColor($r('app.color.fontColor_text2'))
        .height(CommonConstant.NewsSource_HEIGHT)
        .width(CommonConstant.NewsSource_WIDTH)
        .maxLines(CommonConstant.NewsSource_MAX_LINES)
        .margin({ left: CommonConstant.NewsSource_MARGIN_LEFT, top: CommonConstant.NewsSource_MARGIN_TOP })
        .textOverflow({ overflow: TextOverflow.None })
    }
    .alignItems(HorizontalAlign.Start).justifyContent(FlexAlign.Center)
  }
}

创建NewsListComponent.ets,循环渲染展示多条新闻信息

import { CommonConstant} from '../common/constants/CommonConstan';
import { NewsViewList } from '../viewmodel/NewsModel';
import { NewsData} from '../viewmodel/NewsViewModel';
import { NewsItemComponent } from './NewsItemComponent';


@Component
export struct NewsListComponent {

  @State newsData:NewsData[] = []

  currentPage:number = 1 //翻页的页码,默认从第一页开始

  isLoading:boolean = false //决定是否需要在此加载数据的状态
  isMore:boolean = true //是的还有数据,false表示就没有数据了,不要在查询了

  getNewsInformation(){
    //这里this.currentPage是页面
    NewsViewList.getNewsList(this.currentPage).then((result:NewsData[])=>{
      //分页查询,需要合并数据,每次查询的结果都报错到数组中,否则之前的信息就没有了
      this.newsData = this.newsData.concat(result)

      //修改为false,表示需要进行下一次查询
      this.isLoading = false

      //判断是否还有数据,如果没有数据就修改isMore的值为false
      if(!result || result.length === 0){
        this.isMore = false
      }
    })
  }

  aboutToAppear(){
    //通过请求获取新闻数据
    this.getNewsInformation();
  }

  build() {
    Column(){
      List(){
        ForEach(this.newsData,(item:NewsData)=>{
          ListItem(){
            //调用新闻信息展示组件
            NewsItemComponent({newsData:item})
          }
          .height($r('app.float.news_list_height')) //高度
          .backgroundColor($r('app.color.white')) //背景颜色
          .margin({ top: $r('app.float.news_list_margin_top') }) //内上边距
          .borderRadius(CommonConstant.NewsListConstant_ITEM_BORDER_RADIUS) //圆角半径
        })
      }.onReachEnd(()=>{//信息触底后所做的事情
        console.log("触底了");
        if(!this.isLoading && this.isMore){//isLoading是否需要继续下一次查询, isMore还有数据表示在往下查询
          //修改
          console.log("查询数据----");
          this.isLoading = true
          //TODO 在次加载数据
          this.currentPage++
          this.getNewsInformation()
        }
      })
    }
  }
}

3)创建子包viewmodel

在里面创建NewsModel.ets,通过前面的封装的请求操作,实现异步查询tabs和新闻列表,代码如下:

import { CommonConstant } from '../common/constants/CommonConstan';
import { httpRequestGet } from '../common/utils/HttpUtil';
import { NewsData, NewsTypeBean } from './NewsViewModel';

export class NewsViewList{
  /**
   * 从接口获取tabs信息
   * @returns
   */
  static getNewsTypeList():Promise<NewsTypeBean[]>{
    return new Promise((resolve:Function,reject)=>{

      //发送请求
      httpRequestGet("/news/getNewsType").then((resp) => {
        resolve(resp["data"]) //之前在resp中已经将json字符串转换成对象,所以这里是取data的key所对应的值,进行封装
      }).catch((err) => {
        console.info('error:' + err);
        reject("error"+JSON.stringify(err))
      });
    })
  }
  /**
   * 固定写死tabs信息
   * @returns tabs数组
   */
  static getDefaultTypeList(): NewsTypeBean[] {
    return CommonConstant.TabBars_DEFAULT_NEWS_TYPES;
  }

  /**
   * 获取新闻列表
   * @param currentPage 当前页面
   * @param pageSize 页面大小
   * @returns 返回的异步封装的新闻类的数组
   */
  static getNewsList(currentPage: number, pageSize: number=CommonConstant.PAGE_SIZE):Promise<NewsData[]>{
    return new Promise((resolve:Function,reject)=>{
      httpRequestGet(`/news/getNewsList?currentPage=${currentPage}&pageSize=${pageSize}`).then((resp) => {
        resolve(resp["data"]) //之前在resp中已经将json字符串转换成对象,所以这里是取data的key所对应的值,进行封装
      }).catch((err) => {
        reject("error"+JSON.stringify(err))
      });
    })
  }
}

创建NewsViewModel.ets,里面封装实体类,代码如下,

/**
 * 封装tabs信息实体类
 */
export class NewsTypeBean{
  id:number = 0;
  name:string = "";
}


/**
 * 图片实体类
 */
export class NewsFile{
  //图片ID
  id:number = 0
  //图片的URL
  url:string = ""
  //类型
  type:number = 0
  //新闻图像列表项id
  newsId = 0
}

/**
 * 新闻实体类
 */
export class NewsData{
  //标题
  title:string = ""
  //描述信息
  content:string = ""
  //图片
  imagesUrl: Array<NewsFile> = [new NewsFile()]
  //新闻列表来源
  source:string = ""
}

4)文本、颜色封装

在string.json5中添加内容如下:

{
  "string": [
    {
      "name": "module_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_desc",
      "value": "description"
    },
    {
      "name": "EntryAbility_label",
      "value": "newsData"
    },
    {
      "name": "pull_up_load_text",
      "value": "Loading in..."
    },
    {
      "name": "pull_down_refresh_text",
      "value": "The drop-down refresh"
    },
    {
      "name": "release_refresh_text",
      "value": "Loosen the refresh"
    },
    {
      "name": "refreshing_text",
      "value": "Is refreshing"
    },
    {
      "name": "refresh_success_text",
      "value": "Refresh the success"
    },
    {
      "name": "refresh_fail_text",
      "value": "Refresh the failure"
    },
    {
      "name": "http_error_message",
      "value": "Network request failed, please try later!"
    },
    {
      "name": "page_none_msg",
      "value": "Network loading failure"
    },
    {
      "name": "prompt_message",
      "value": "No more data"
    },
    {
      "name": "dependency_reason",
      "value": "Used to initiate network data requests."
    }
  ]
}

在color.json创建颜色如下:

{
  "color": [
    {
      "name": "start_window_background",
      "value": "#FFFFFF"
    },
    {
      "name": "white",
      "value": "#FFFFFF"
    },
    {
      "name": "color_index",
      "value": "#1E67DC"
    },
    {
      "name": "fontColor_text",
      "value": "#000000"
    },
    {
      "name": "fontColor_text1",
      "value": "#8A8A8A"
    },
    {
      "name": "fontColor_text2",
      "value": "#FF989898"
    },
    {
      "name": "fontColor_text3",
      "value": "#182431"
    },
    {
      "name": "listColor",
      "value": "#F1F3F5"
    },
    {
      "name": "dividerColor",
      "value": "#E2E2E2"
    }
  ]
}

三、axios

3.1.什么是axios?

axios是一个基于Promise的HTTP库,可以用在浏览器和Node.js中,并且axios已经将许多特性都封装好了,可以自动转换JSON数据、能够拦截请求和响应等,是现在Vue项目开发首选的一个库。

3.2.Axios网络请求库的使用

Axios(@ohos/axios)第三方库是基于axios库进行适配,使其可以运行在OpenHarmony中的一个发送网络请求库,并且本库沿用axios库现有用法和特性,使其更加适合于鸿蒙项目的开发。

在鸿蒙中安装第三方库,需要先现在包管理工具ohpm,参考官网地址:https://developer.harmonyos.com/cn/docs/documentation/doc-guides-V3/ide-command-line-ohpm-0000001490235312-V3,安装步骤如下:

1)下载ohpm工具包,点击链接获取。

2)解压文件,进入“ohpm/bin”目录,打开命令行工具,执行如下指令初始化ohpm(初始化ohpm前,需先完成node.js环境变量配置)

Windows环境下执行:
init.bat

执行命令

  • Linux/macOS环境下执行:
./init.sh

3)将ohpm配置到环境变量中。

Windows环境变量设置方法:

在此电脑 > 属性 > 高级系统设置 > 高级 > 环境变量中,将ohpm命令行工具的bin目录配置到系统或者用户的PATH变量中。

macOS环境变量设置方法,打开终端工具,执行以下命令。

export OHPM_HOME=/home/xx/Downloads/ohpm  #本处路径请替换为ohpm的安装路径
export PATH=${OHPM_HOME}/bin:${PATH}

4)安装完成之后,执行如下命令:

ohpm -v

终端输出为版本号(如:1.2.0),则表示安装成功。

3.3.Axios网络请求库的安装

首先,需要安装Axios(@ohos/axios)库,可以通过ohpm的方式来安装,安装指令如下:

ohpm install @ohos/axios

我们可以在项目工程中,打开命令行终端执行以上的安装指令。如下图所示。

安装成功之后在oh-package.json5配置文件中找到Axios的配置信息和对应的版本号。如下图所示。

在oh_modules目录下也会安装这个第三方的库。如下图所示。

现在,已经成功将Axios(@ohos/axios)第三方库安装到项目中。

3.4.语法说明

参考官网:https://repo.harmonyos.com/#/cn/application/atomService/@ohos%2Faxios,通过向 axios 传递相关配置来创建请求语法如下:

axios(config)
axios({
  method: "get",
  url: 'http://www.xxx.com/info'
}).then(res => {
   console.info('result:' + JSON.stringify(res.data));
}).catch(error => {
   console.error(error);
})
axios(url[, config])
axios('http://www.xxx.com/info').then(res => {
   console.info('result:' + JSON.stringify(res.data));
}).catch(error => {
   console.error(error);
})

3.5.请求方法的 别名方式 来创建请求

Axios为所有支持的请求方法提供了别名,便于使用,如下:

  • axios.request(config)
  • axios.get(url[, config])
  • axios.delete(url[, config])
  • axios.post(url[, data[, config]])
  • axios.put(url[, data[, config]])

代码示例:

axios.get("http://www.xxx.com/info", { params: { key: "value" } })
  .then(function (response) {
    console.info("result:" + JSON.stringify(response.data));
  })
  .catch(function (error) {
    console.error(error);
  });

创建请求时可以指定配置选项。只有 url 是必需的。如果没有指定 method,请求将默认使用 get 方法。参数说明如下:

// `url` 是用于请求的服务器 URL
url: '/user',

// `method` 是创建请求时使用的方法 支持post/get/put/delete方法,不区分大小写,默认为get方法
method: 'get', // default

// `baseURL` 将自动加在 `url` 前面,除非 `url` 是一个绝对 URL。
// 它可以通过设置一个 `baseURL` 便于为 axios 实例的方法传递相对 URL
baseURL: 'https://some-domain.com/api/',
// `headers` 是即将被发送的自定义请求头

headers: {'Content-Type': 'application/json'},

// `params` 是即将与请求一起发送的 URL 参数
// 必须是一个无格式对象(plain object),其他对象如 URLSearchParams ,必须使用 paramsSerializer 进行序列化
params: {
   ID: 12345
}, 

// `paramsSerializer` 是一个负责 `params` 序列化的函数
paramsSerializer: function(params) {
   return params
},

// `data` 是作为请求主体被发送的数据
// 只适用于这些请求方法 'PUT', 'POST', 和 'PATCH'
// 在没有设置 `transformRequest` 时,必须是以下类型之一,其他类型使用 transformRequest 转换处理
// - string, plain object, ArrayBuffer
data: {
   firstName: 'Fred'
},

// `transformRequest` 允许在向服务器发送前,修改请求数据
// 只能用在 'PUT', 'POST' 和 'PATCH' 这几个请求方法
// 后面数组中的函数必须返回一个字符串,或 ArrayBuffer,或 Stream
transformRequest: [function (data, headers) {
   // 对 data 进行任意转换处理
   return data;
}],

// `transformResponse` 在传递给 then/catch 前,允许修改响应数据
transformResponse: [function (data) {
   // 对 data 进行任意转换处理
   return data;
}],

// `timeout` 指定请求超时的毫秒数(0 表示无超时时间)
// 如果请求超过 `timeout` 的时间,请求将被中断
timeout: 1000,

// `onUploadProgress` 允许为上传处理进度事件
onUploadProgress: function (progressEvent) {
   // 对原生进度事件的处理
},

// `onDownloadProgress` 允许为下载处理进度事件
onDownloadProgress: function (progressEvent) {
   // 对原生进度事件的处理
},

3.6.常见请求

1)GET 请求方法示例

// 方式1,通过向 axios 传递相关配置来创建请求
axios({
  method: "get",
  url: 'http://www.xxx.com/info'
}).then(res => {
   console.info('result:' + JSON.stringify(res.data));
}).catch(error => {
   console.error(error);
})

// 方式2,通过别名来创建请求
axios.get("http://www.xxx.com/info", { params: { key: "value" } })
  .then(function (response) {
    console.info("result:" + JSON.stringify(response.data));
  })
  .catch(function (error) {
    console.error(error);
  });

2)POST 请求方法示例

axios({
   url: 'http://www.xxx.com/post',
   method: 'post',
   data: {titleId: 2, typeId: 1},
}).then(res=>{
   console.info('result: ' + JSON.stringify(res.data));
}).catch((error)=>{
   console.info('error: ' + JSON.stringify(error));
})

3.7.创建axios实例

在项目开发中,我们发送的请求有很多的配置都是相同的,这时我们可以创建axios实例,调用实例的请求方法,用自定义配置新建一个axios实例。代码如下:

const instance = axios.create({
   //地址的公共前缀
   baseURL: http://localhost:8899,
   //设置超时时间
   timeout: 5000,
   //自定义header
   headers: {'X-Custom-Header': 'custom'}
})

这样所有的请求都使用这个实例来发起,可以实现请求的定制化。我们可以使用如下的请求:

instance.request(config)
instance.get(url[, config])
instance.delete(url[, config])
instance.head(url[, config])
instance.options(url[, config])
instance.post(url[, data[, config]])
instance.put(url[, data[, config]])
instance.patch(url[, data[, config]])

3.8.配置全局的axios默认值

也可以通过axios的default属性来配置请求的默认值。代码如下:

instance.defaults.baseURL = 'http://localhost:8899'
instance.defaults.headers.post['Content-Type'] = 'application/json'

在以上代码中配置了全局的请求前缀和请求头的类型。

3.9.配置的优先级

对以上各种配置进行合并时,会按照一定的优先级进行排序。优先级如下:

  • 请求的config参数配置 > defaults属性配置 > axios默认配置

如下所示:

const instance = axios.create()
instance.defaults.timeout = 2500
instance.get(this.api,{
  timeout: 5000
})

使用axios创建实例此时的超时时间为默认值0,使用defaults属性设置后超时时间为2.5秒,在get方法中我们指定超时时间为5秒。后面的优先级要高于前面的。

3.10.使用案例

要请求网络数据,首先需要申请权限,你需要在module.json5文件中申请网络访问权限。如下图所示。

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

如下图:

接下来,我们将发送一个简单的Get请求。请求接口如下:

import axios from "@ohos/axios"

/**
 * 定义接口,作为数据接收的对象使用
 */
interface CodeLife{
  hotValue:string,
  index:number,
  link:string,
  title:string
}

@Entry
@Component
struct AxiosPage {
  @State ListData:CodeLife[] = []

  //封装请求获取数据
  getCodeLife(){
    //方式一
    /*axios.get("https://api.codelife.cc/api/top/list?lang=cn&id=KqndgxeLl9",).then((resp)=>{
        //resp.data获取的响应体中的内容,resp.data.data获取的响应体中data的值 resp.headers获取的响应头
        this.ListData = resp.data.data

      }).catch((error)=>{
        console.log("error:"+error);
    })*/
    //方式二
    axios({
      method:"get",
      url:"https://api.codelife.cc/api/top/list?lang=cn&id=KqndgxeLl9"
    }).then((resp)=>{
      //resp.data获取的响应体中的内容,resp.data.data获取的响应体中data的值 resp.headers获取的响应头
      this.ListData = resp.data.data

    }).catch((error)=>{
      console.log("error:"+error);
    })
  }

  build() {
    Column(){
      Row(){
        Button("获取新浪头条")
          .margin(20)
          .onClick(() => {
            //发送请求获取数据
            this.getCodeLife()
          })
      }
      Divider()

      Column(){
        List({space:20}){
          ForEach(this.ListData,(item:CodeLife,index:number)=>{
            ListItem(){
              Row(){
                Text(`${item.index}.${item.title}`).fontColor(Color.White)
                Text(item.hotValue).fontWeight(FontWeight.Bold).fontColor(Color.White)
              }
              .width('100%')
              .backgroundColor('#EE6363')
              .padding(10)
              .borderRadius(8)
              .justifyContent(FlexAlign.SpaceBetween)
              .alignItems(VerticalAlign.Center)
            }
          })
        }.width("100%").height("100%").padding({left:10,right:10})
      }
    }.width("100%").height("100%")
  }
}

获取新浪最新头条,如下:

3.11.章节二中案例通过axios实现

将之前的案例发送请求由内置的http,替换为axios模块实现,只需要修改HttpUtil.ets代码如下:

import http from '@ohos.net.http';
import axios from "@ohos/axios"
import data from '@ohos.telephony.data';
import { CommonConstant } from '../constants/CommonConstan';

/**
 * 方式一:通过内置的http发送请求
 */
/*export function request(url: string, method: http.RequestMethod, requestData?: any) {

  let httpRequest = http.createHttp();
  let httpResponse = httpRequest.request(CommonConstant.base_url + url, {
    method: method,
    header: {
      'Content-Type': 'application/json'
    },
    extraData: JSON.stringify(requestData),
    connectTimeout: 60000,
    readTimeout: 60000

  })
  return  httpResponse.then((resp) => {
    if (resp.responseCode === http.ResponseCode.OK) {
      console.info('Result:' + resp.result)
      return JSON.parse(resp.result.toString()) //将json字符串转换成对象
    }
  }).catch((err) => {
    console.info('error:' + JSON.stringify(err));
  });
}*/

/**
 * 使用axios封装请求
 * @param url URL地址
 * @param method 请求类型
 * @param requestData post请求数据
 * @returns
 */
export function request(url: string, method: http.RequestMethod, requestData?: any){
  return axios({
    method:method,
    baseURL: CommonConstant.base_url,//服务器地址 http://192.168.0.104
    url: url,//URL地址
    headers:{
      'Content-Type': 'application/json'
    },
    data:JSON.stringify(requestData)
  }).then((resp)=>{
    if(resp.status === http.ResponseCode.OK){
      console.info('Result:' + resp.data.data)
      return resp.data
    }else {
      console.info('error:' + JSON.stringify(resp.data));
    }
  }).catch((err)=>{
    console.info('error:' + JSON.stringify(err));
  })
}

/**
 * Get请求
 * @param url
 */
export function httpRequestGet(url: string){
  return request(url,http.RequestMethod.GET)
}

export function httpRequestPost(url: string,requestData?: any){
  return request(url,http.RequestMethod.POST,requestData)
}

export function httpRequestPut(url: string,requestData?: any){
  return request(url,http.RequestMethod.PUT,requestData)
}

export function httpRequestDelete(url: string,requestData?: any){
  return request(url,http.RequestMethod.DELETE,requestData)
}

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表