鸿蒙网络数据传输案例实战

发布于:2025-05-29 ⋅ 阅读:(20) ⋅ 点赞:(0)

一、案例效果截图

二、案例运用到的知识点

  1. 核心知识点
  • 网络连接管理:connection模块
  • HTTP数据请求:http模块
  • RPC数据请求:rcp模块
  • 文件管理能力:fileIo模块、fileUri模块
  1. 其他知识点
  • ArkTS 语言基础
  • V2版状态管理:@ComponentV2/@Local
  • 渲染控制:if/ForEach
  • 自定义组件和组件生命周期
  • 自定义构建函数@Builder
  • @Extend:定义扩展样式
  • Navigation:导航组件
  • 内置组件:Stack/TabBar/Progress/Image/Column/Row/Text/TextInput/Button/Line/Blank/SymbolGlyph
  • 常量与资源分类的访问
  • MVVM模式

三、代码结构

├──entry/src/main/ets        // 代码区
│  ├──common
│  │  └──network
│  │     ├──ConnectionUtils.ets
│  │     ├──HttpUtils.ets
│  │     └──RCPUtils.ets
│  ├──entryability            
│  │  └──EntryAbility.ets    // 程序入口类
│  ├──pages
│  │  ├──LoginPage.ets       // 登录页面
│  │  └──MainPage.ets        // 主页面
│  ├──view
│  │  ├──Home.ets            // 首页组件
│  │  └──Setting.ets         // 我的组件
│  └──viewmodel
│     ├──ResponseData
│     │  ├──ListInfo.ets     // 列表数据模型
│     │  ├──ResponseData.ets // 响应结果模型
│     │  └──Value.ets        // 响应数据模型
│     ├──ItemData.ets        // 页面数据模型
│     └──MainViewModel.ets   // 绑定数据到组件并提供接口
└──entry/src/main/resources  
   ├──base
   │  ├──element             // 尺寸、颜色、文字等资源文件存放位置
   │  ├──media               // 媒体资源存放位置
   │  └──profile             // 页面配置文件存放位置
   └──rawfile                // 本地html代码存放位置 

四、公共文件与资源

本案例涉及到的资源文件如下:

  • entry/src/main/resources/base/element/string.json
  • entry/src/main/resources/base/element/float.json
  • entry/src/main/resources/base/element/color.json

其他资源请到源码中获取。

五、网络状态监听的实现

我们希望在首页登录时,不仅对账号和密码进行非空校验,还需要对网络状态进行检测。在没有网络的情况下,拒绝登录操作,并提示用户网络未连接。

  1. 网络连接工具类

首先,我们可以在ets目录下创建一个common文件夹,用于存放一些公共能力。

随后在common文件夹下创建network文件夹,并创建ConnectionUtils.ets文件(网络连接工具类),我们将在该文件实现网络状态监听的功能。

// entry/src/main/ets/common/network/ConnectionUtils.ets
import { connection } from '@kit.NetworkKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { promptAction } from '@kit.ArkUI'
import { hilog } from '@kit.PerformanceAnalysisKit'

const TAG: string = 'ConnectionUtils'

/**
 * ConnectionUtils 提供网络监听功能。
 */
export class ConnectionUtils {
  netConnection = connection.createNetConnection()

  /**
   * 检查网络是否连接的方法。
   */
  async isNetworkConnected(): Promise<boolean> {
    let result: boolean = false
    await connection.getDefaultNet().then(async (data: connection.NetHandle) => {
      if (data.netId === 0) {
        hilog.info(0x0000, TAG, '网络错误')
        return
      }
      await connection.getNetCapabilities(data).then(
        (data: connection.NetCapabilities) => {
          let bearerTypes: Set<number> = new Set(data.bearerTypes)
          let bearerTypesNum = Array.from(bearerTypes.values())
          for (let item of bearerTypesNum) {
            if (item === 0) {
              result = true
              hilog.info(0x0000, TAG, '蜂窝网络')
            } else if (item === 1) {
              result = true
              hilog.info(0x0000, TAG, 'WiFi')
            } else if (item === 3) {
              result = true
              hilog.info(0x0000, TAG, '以太网')
            } else {
              return
            }
          }
        })
    })
    return result
  }

  /**
   * 打开网络注册的方法。
   */
  openRegister() {
    this.netConnection.register((error: BusinessError) => {
      hilog.info(0x0000, TAG, JSON.stringify(error))
    })
  }

  /**
   * 监听网络状态变化的方法。
   */
  registerNetworkAvailableStatus() {
    this.netConnection.on('netAvailable', () => {
      promptAction.showToast({
        message: $r('app.string.Network_Available'),
        duration: 2000
      })
    })

    this.netConnection.on('netUnavailable', () => {
      promptAction.showToast({
        message: $r('app.string.Network_Unavailable'),
        duration: 2000
      })
    })

    this.netConnection.on('netLost', () => {
      promptAction.showToast({
        message: $r('app.string.Network_Lost'),
        duration: 2000
      })
    })
  }

  /**
   * 关闭网络注册的方法。
   */
  closeRegister() {
    this.netConnection.unregister((error: BusinessError) => {
      hilog.info(0x0000, TAG, JSON.stringify(error))
    })
  }
}

export default new ConnectionUtils()

关键代码说明:

  • 实现是否有网络的校验,需要使用@kit.NetworkKit的connection模块,首先我们定义一个名为isNetworkConnected()的方法用于判断是否有网络连接,所以该函数应该返回一个布尔值。而具体的方法实现,需要使用connection模块提供的connection.getDefaultNet()方法,该方法可以获取到当前连接网络的信息,同时也可以判断是否有网络连接。connection模块还提供了connection.getNetCapabilities()方法,可以获取当前网络的具体信息,包括网络类型、网络具体能力等。
  • 监听网络状态的registerNetworkAvailableStatus()方法中,我们主要通过模块提供的on('netAvailable')、on('netUnavailable')和on('netLost')三个方法,分别监听网络可用、网络不可用、网络丢失三种状态,并在监听到网络状态改变时,弹窗提醒用户。
  1. 添加访问权限

在实现网络连接工具类的封装前,首先需要申请权限,在entry/src/main目录下的module.json5中,添加网络访问权限和网络信息查看权限。

{
  "module": {
    // ...
    "requestPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "$string:Internet",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.GET_NETWORK_INFO",
        "reason": "$string:network_info",
        "usedScene": {
          "abilities": [
            "EntryAbility"
          ],
          "when": "inuse"
        }
      }
    ]
  }
}
  1. 登录页面实现监听逻辑
// entry/src/main/ets/pages/LoginPage.ets
import { promptAction } from '@kit.ArkUI'
import ConnectionUtils from '../common/network/ConnectionUtils'

@Extend(TextInput)
function inputStyle() {
  .placeholderColor($r('sys.color.font_primary'))
  .height('45vp')
  .fontSize('18fp')
  .backgroundColor('#F1F3F5')
  .width('328vp')
  .margin({ top: 12 })
}

@Extend(Line)
function lineStyle() {
  .width('328vp')
  .height('1vp')
  .backgroundColor($r('sys.color.font_fourth'))
}

@Extend(Text)
function blueTextStyle() {
  .fontColor($r('sys.color.brand'))
  .fontSize('14fp')
  .fontWeight(FontWeight.Medium)
}

@Entry
@Component
struct LoginPage {
  @State account: string = ''
  @State password: string = ''
  @State isShowProgress: boolean = false
  private timeOutId: number = -1
  pathStack: NavPathStack = new NavPathStack()

  @Builder
  imageButton(src: Resource) {
    Button({ type: ButtonType.Circle, stateEffect: true }) {
      Image(src)
    }
    .height('48vp')
    .width('48vp')
    .backgroundColor('#F1F3F5')
  }

  login(result: boolean): void {
    if (this.account === '' || this.password === '') {
      promptAction.showToast({
        message: $r('app.string.input_empty_tips')
      })
    } else {
      this.isShowProgress = true
      if (this.timeOutId === -1) {
        this.timeOutId = setTimeout(async () => {
          this.isShowProgress = false
          this.timeOutId = -1

          if (result) {
            this.pathStack.pushPathByName('MainPage', null)
          } else {
            promptAction.showToast({
              message: $r('app.string.Network_Lost')
            })
          }
        }, 2000)
      }
    }
  }

  aboutToDisappear() {
    clearTimeout(this.timeOutId)
    this.timeOutId = -1
  }

  build() {
    Navigation(this.pathStack) {
      Column() {
        Image($r('app.media.logo'))
          .width('78vp')
          .height('78vp')
          .margin({
            top: '150vp',
            bottom: '8vp'
          })
        Text($r('app.string.login_page'))
          .fontSize('24fp')
          .fontWeight(FontWeight.Medium)
          .fontColor($r('sys.color.font_primary'))
        Text($r('app.string.login_more'))
          .fontSize('16fp')
          .fontColor($r('sys.color.font_secondary'))
          .margin({
            bottom: '30vp',
            top: '8vp'
          })

        TextInput({ placeholder: $r('app.string.account') })
          .maxLength(11)
          .type(InputType.Number)
          .inputStyle()
          .onChange((value: string) => {
            this.account = value
          })
        Line()
          .lineStyle()

        TextInput({ placeholder: $r('app.string.password') })
          .maxLength(8)
          .type(InputType.Password)
          .inputStyle()
          .onChange((value: string) => {
            this.password = value
          })
        Line()
          .lineStyle()

        Row() {
          Text($r('app.string.message_login'))
            .blueTextStyle()
          Text($r('app.string.forgot_password'))
            .blueTextStyle()
        }
        .justifyContent(FlexAlign.SpaceBetween)
        .width('328vp')
        .margin({ top: '8vp' })

        Button($r('app.string.login'), { type: ButtonType.Capsule })
          .width('328vp')
          .height('40vp')
          .fontSize('16fp')
          .fontWeight(FontWeight.Medium)
          .backgroundColor($r('sys.color.brand'))
          .margin({
            top: '48vp',
            bottom: '12vp'
          })
          .onClick(async () => {
            await ConnectionUtils.isNetworkConnected().then((value) => {
              this.login(value)
            })
          })
        Text($r('app.string.register_account'))
          .fontColor($r('sys.color.brand'))
          .fontSize('16fp')
          .fontWeight(FontWeight.Medium)

        if (this.isShowProgress) {
          LoadingProgress()
            .color($r('sys.color.font_primary'))
            .width('30vp')
            .height('30vp')
            .margin({ top: '20vp' })
        }

        Blank()
        Text($r('app.string.other_login_method'))
          .fontColor('#838D97')
          .fontSize('12fp')
          .fontWeight(FontWeight.Medium)
          .margin({
            top: '50vp',
            bottom: '12vp'
          })
        Row({ space: 44 }) {
          this.imageButton($r('app.media.login_method1'))
          this.imageButton($r('app.media.login_method2'))
          this.imageButton($r('app.media.login_method3'))
        }
        .margin({ bottom: '16vp' })
      }
      .height('100%')
      .width('100%')
      .padding({
        left: '12vp',
        right: '12vp',
        bottom: '24vp'
      })
    }
    .backgroundColor('#F1F3F5')
    .width('100%')
    .height('100%')
    .hideTitleBar(true)
    .hideToolBar(true)
  }
}

关键代码说明:

  • ConnectionUtils.isNetworkConnected(),检查网络连接情况。
  • login(result: boolean): void {...},登录按钮点击的回调方法。
  • if (result) { this.pathStack.pushPathByName('MainPage', null) },当网络连接正常(result值为true)时,跳转到MainPage页面。

六、使用HTTP请求网络数据

完成了网络状态的校验后,接下来需要进行网络请求了。我们可以通过@kit.NetworkKit中的http模块,完成网络请求的实现。

  1. 页面路由

本案例通过Navigation组件实现登录页到主要页的跳转,路由配置如下:

// entry/src/main/resources/base/profile/route_map.json
{
  "routerMap": [
    {
      "name": "MainPage",
      "pageSourceFile": "src/main/ets/pages/MainPage.ets",
      "buildFunction": "MainPageBuilder",
      "data": {
        "description" : "this is mainPage"
      }
    }
  ]
}

引用配置文件的设置:

// entry/src/main/module.json5
{
  "module": {
    //...
    "routerMap": "$profile:route_map"
}
  1. MainPage首页
// entry/src/main/ets/pages/MainPage.ets
import Home from '../view/Home'
import Setting from '../view/Setting'

@Builder
export function MainPageBuilder() {
  MainPage()
}

@Component
export struct MainPage {
  @State currentIndex: number = 0
  private tabsController: TabsController = new TabsController()

  @Builder
  tabBuilder(title: Resource, index: number, selectedImg: Resource) {
    Column() {
      SymbolGlyph(selectedImg)
        .fontSize('24fp')
        .renderingStrategy(SymbolRenderingStrategy.MULTIPLE_OPACITY)
        .symbolEffect(
          new BounceSymbolEffect(EffectScope.WHOLE, EffectDirection.UP),
          this.currentIndex === index ? true : false
        )
        .fontColor(this.currentIndex === index ? ['#0a59f7'] : ['#66000000'])
      
      Text(title)
        .margin({ top: '4vp' })
        .fontSize('10fp')
        .fontWeight(500)
        .fontColor(this.currentIndex === index ? '#0a59f7' : '#66000000')
    }
    .backgroundColor('#F1F3F5')
    .justifyContent(FlexAlign.Center)
    .height('56vp')
    .width('100%')
    .onClick(() => {
      this.currentIndex = index
      this.tabsController.changeIndex(this.currentIndex)
    })
  }

  build() {
    NavDestination() {
      Tabs({
        barPosition: BarPosition.End,
        controller: this.tabsController
      }) {
        TabContent() {
          Home()
        }
        .padding({
          left: '12vp',
          right: '12vp'
        })
        .backgroundColor('#F1F3F5')
        .tabBar(this.tabBuilder(
          $r('app.string.Tab_Bar_One'), 
          0, 
          $r('sys.symbol.house_fill')
        ))

        TabContent() {
          Setting()
        }
        .padding({
          left: '12vp',
          right: '12vp'
        })
        .backgroundColor('#F1F3F5')
        .tabBar(this.tabBuilder(
          $r('app.string.Tab_Bar_Two'), 
          1, 
          $r('sys.symbol.person_crop_circle_fill_1')
        ))
      }
      .margin({ bottom: '64vp' })
      .width('100%')
      .height('100%')
      .barHeight('80vp')
      .barMode(BarMode.Fixed)
      .onChange((index: number) => {
        this.currentIndex = index
      })
    }
    .height('100%')
    .hideTitleBar(true)
    .backgroundColor('#F1F3F5')
  }
}

关键代码说明:

  • TabContent() { Home() },第一个tab页面内容为Home组件。
  • TabContent() { Setting() },第二个tab页面内容为Setting组件。
  1. 实现HTTP工具类

首先,我们可以继续在network文件夹下,创建一个HttpUtils.ets文件(HTTP工具类),我们将在该文件中实现关于HTTP网络请求的主要功能。

简单分析一下该工具类需要完成的主要功能:

  • 使用HTTP发起get请求,获取一张箭头图片并保存在沙箱路径中,并返回其沙箱路径对应的uri,以便使用箭头图片作为首页列表的布局元素。
  • 使用HTTP发起post请求,获取官网中的图片与文本信息,并将其封装在实体类数组中,以便将这些信息作为首页列表的布局元素。

具体代码如下:

// entry/src/main/ets/common/network/HttpUtils.ets
import { http } from '@kit.NetworkKit'
import { fileIo, fileUri } from '@kit.CoreFileKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import ResponseData from '../../viewmodel/ResponseData/ResponseData'
import ListInfo from '../../viewmodel/ResponseData/ListInfo'

const TAG: string = 'HttpUtils'
const IMAGE_URL: string = 'https://developer.huawei.com/system/modules/' +
  'org.opencms.portal.template.core/' +
  'resources/harmony/img/jiantou_right.svg'
const RESOURCE_URL: string = 'https://svc-drcn.developer.huawei.com/' +
  'community/servlet/consumer' +
  '/partnerActivityService/v1/developer/activity/terminalActivities/list'

/**
 * 头部信息类。
 */
class Header {
  contentType: string

  constructor(contentType: string) {
    this.contentType = contentType
  }
}

/**
 * HttpUtils 提供通过 HTTP 访问网络的能力。
 */
export class HttpUtils {
  httpRequest: http.HttpRequest

  constructor() {
    this.httpRequest = http.createHttp()
  }

  /**
   * 通过 HTTP 发起 GET 请求的方法。
   */
  async getHttpRequest(cacheDir: string): Promise<string> {
    let responsePictureUri: string = ''
    await this.httpRequest.request(IMAGE_URL, { method: http.RequestMethod.GET })
      .then((data: http.HttpResponse) => {
        let filePath = cacheDir + '/test.svg'
        let file = fileIo.openSync(
          filePath, 
          fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE
        )
        responsePictureUri = fileUri.getUriFromPath(filePath)
        fileIo.writeSync(file.fd, data.result as ArrayBuffer)
        fileIo.closeSync(file.fd)
      })
    return responsePictureUri
  }

  /**
   * 通过 HTTP 发起 POST 请求的方法。
   */
  async postHttpRequest(): Promise<ListInfo[]> {
    let responseData: Array<ListInfo> = []
    await this.httpRequest.request(RESOURCE_URL,
      {
        method: http.RequestMethod.POST, extraData: {
        'status': '1',
        'belong': '1',
        'language': 'cn',
        'needTop': 1,
        'displayChannel': [1, 3],
        'count': 4,
        'pagestart': 1,
        'type': '1,4,5,6'
      },
        header: new Header('application/jsoncharset=UTF-8')
      }).then((data: http.HttpResponse) => {
      let result: ResponseData = JSON.parse(data.result as string)
      responseData = result.value.list
    }).catch((err: Error) => {
      hilog.info(0x0000, TAG, JSON.stringify(err))
    })
    return responseData
  }

  /**
   * 销毁 httpRequest 的方法。
   */
  destroyHttpRequest() {
    this.httpRequest.destroy()
  }
}

关键代码说明:

  • 通过http模块提供的createHttp()方法获取一个HttpRequest对象,HttpRequest类是发起HTTP请求的关键,其中包括request()、requestInStream()、destroy()、on()和off()方法。所以我们封装一个名为HttpUtils的类,并为其添加一个对象httpRequest,并在构造方法中使用createHttp()方法为其赋值,以便后续封装的get请求和post请求能方便的使用到该对象。
  • 成功获取到HttpRequest对象并通过构造方法为对象属性httpRequest赋值后,我们将基于该对象属性实现HTTP请求方法的封装,首先是get请求,主要通过request()方法实现,参数主要包括请求的url,发起请求可选参数HttpRequestOptions,以及一个回调函数。所以我们可以通过封装request()方法来实现通过get请求网络图片。
  • 在成功使用request()方法发起get请求后,我们可以在回调函数中得到一个HttpResponse类型的对象data。为了将get请求得到的图片资源保存至沙箱路径,我们需要使用@kit.CoreFileKit提供的文件管理能力,创建一个test.svg并写入数据,并将其沙箱文件的uri返回以便后续使用。
  • 与get请求一致,我们实现了一个名为postHttpRequest()的方法,其本质依旧是对request()方法的封装,传入网络请求地址url以及网络请求配置参数HttpRequestOptions,并在HttpRequestOptions中规定请求方法为post请求。
  • 自定义Header类来为HttpRequestOptions补充响应头的参数,为其配置请求返回类型以及编码格式。
  • 将request()方法的参数配置好后,我们可以通过回调函数中的HttpResponse类型的对象data获取返回值,由于响应头配置了返回类型为json格式,我们可以通过JSON.parse()将该json字符串转化为实体类,由于json字符串的结构较为复杂,所以我们需要自定义实体类用于承接,具体我们在viewmodel中创建了一个名为ResponseData的文件夹,并在其中封装了三个实体类,其中的属性名与json字符串中的命名保持一致。
  • 在完成了实体类的封装之后,我们只需提取实体类对象中Value属性的list作为返回值。这样,我们就完成postHttpRequest()方法的实现。
  • 在最后,我们使用完HttpRequest对象后,需要主动调用destroy()方法进行销毁,这里我们封装了一个destroyHttpRequest()方法来实现。
  1. 三个实体类
// entry/src/main/ets/viewmodel/ResponseData/ResponseData.ets
import Value from './Value'

export default class ResponseData {
  public code: string
  public value: Value

  constructor(code: string, value: Value) {
    this.code = code
    this.value = value
  }
}
// entry/src/main/ets/viewmodel/ResponseData/Value.ets
import ListInfo from './ListInfo'

export default class Value {
  public list: Array<ListInfo>

  constructor(list: Array<ListInfo>) {
    this.list = list
  }
}
// entry/src/main/ets/viewmodel/ResponseData/ListInfo.ets
export default class ListInfo {
  public activityName: string
  public theme: string
  public indexNavPic: string

  constructor(activityName: string, theme: string, indexNavPic: string) {
    this.activityName = activityName
    this.theme = theme
    this.indexNavPic = indexNavPic
  }
}
  1. Home组件

完成工具类的封装后,我们要通过使用工具类提供的方法来实现首页的列表部分。

// entry/src/main/ets/view/Home.ets
import { common } from '@kit.AbilityKit'
import mainViewModel from '../viewmodel/MainViewModel'
import ItemData from '../viewmodel/ItemData'
import ListInfo from '../viewmodel/ResponseData/ListInfo'
import { HttpUtils } from '../common/network/HttpUtils'

/**
 * Home tab 内容
 */
@Component
export default struct Home {
  @State httpGridItems: Array<ListInfo> = []
  @State pictureUri: string = ''
  private swiperController: SwiperController = new SwiperController()
  private context = getContext(this) as common.UIAbilityContext
  private applicationContext = this.context.getApplicationContext()
  private cacheDir = this.applicationContext.filesDir

  async aboutToAppear(): Promise<void> {
    let httpUtil: HttpUtils = new HttpUtils()
    await httpUtil.postHttpRequest().then((value : Array<ListInfo>)=>{
      this.httpGridItems = value
    })
    await httpUtil.getHttpRequest(this.cacheDir).then((value: string) => {
      this.pictureUri = value
    })
    httpUtil.destroyHttpRequest()
  }

  build() {
    Column() {
      Text($r('app.string.mainPage_tabTitles_home'))
        .width('100%')
        .margin({
          top: '48vp',
          bottom: '12vp'
        })
        .fontWeight(700)
        .fontSize('26fp')

      Scroll() {
        Column() {
          Swiper(this.swiperController) {
            ForEach(mainViewModel.getSwiperImages(), (img: Resource) => {
              Image(img)
                .width('100%')
                .borderRadius('16vp')
            }, (img: Resource) => JSON.stringify(img.id))
          }
          .autoPlay(true)

          Grid() {
            ForEach(mainViewModel.getFirstGridData(), (item: ItemData) => {
              GridItem() {
                Column() {
                  Image(item.img)
                    .width('40vp')
                    .height('40vp')
                  Text(item.title)
                    .fontSize('12fp')
                    .margin({ top: '4vp' })
                }
              }
            }, (item: ItemData) => JSON.stringify(item))
          }
          .backgroundImage($r('app.media.backgroundImage'))
          .backgroundImageSize({
            width: '100%',
            height: '156vp'
          })
          .columnsTemplate('1fr 1fr 1fr 1fr')
          .rowsTemplate('1fr 1fr')
          .columnsGap('8vp')
          .rowsGap('12vp')
          .margin({ top: '12vp' })
          .padding({
            top: '12vp',
            bottom: '12vp'
          })
          .height('156vp')
          .backgroundColor(Color.White)
          .borderRadius('16vp')

          Text($r('app.string.home_list'))
            .fontSize('18fp')
            .fontWeight(700)
            .width('100%')
            .margin({
              top: '18vp',
              bottom: '8vp'
            })

          List() {
            ForEach(this.httpGridItems, (secondItem: ListInfo) => {
              ListItem() {
                Row() {
                  Image(secondItem.indexNavPic)
                    .width('130vp')
                    .height('80vp')
                    .objectFit(ImageFit.TOP_START)
                    .borderRadius('8vp')
                    .margin({ right: '12vp' })

                  Column() {
                    Text(secondItem.activityName)
                      .width('190vp')
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                      .maxLines(1)
                      .fontSize('16fp')
                      .fontWeight(FontWeight.Medium)
                    Text(secondItem.theme)
                      .width('190vp')
                      .textOverflow({ overflow: TextOverflow.Ellipsis })
                      .maxLines(2)
                      .margin({ top: '4vp' })
                      .fontSize('12fp')
                      .fontColor('#99182431')
                    Row() {
                      Image(this.pictureUri)
                        .width('20vp')
                        .opacity(0.5)
                    }
                    .width('170vp')
                    .margin({ top: '10.5vp' })
                    .justifyContent(FlexAlign.End)
                    .alignItems(VerticalAlign.Bottom)
                  }
                  .alignItems(HorizontalAlign.Start)
                }
                .width('100%')
                .padding({
                  left: '12vp',
                  right: '12vp',
                  top: '12vp',
                  bottom: '12vp'
                })
                .justifyContent(FlexAlign.SpaceBetween)
              }
              .margin({ bottom: '8vp' })
              .borderRadius('16vp')
              .backgroundColor('#ffffff')
              .borderRadius($r('app.float.home_backgroundImage_borderRadius'))
              .align(Alignment.TopStart)
              .width('100%')
            }, (secondItem: ListInfo) => JSON.stringify(secondItem))
          }
          .scrollBar(BarState.Off)
          .width('100%')
        }
      }
      .height('660vp')
      .align(Alignment.TopStart)
      .scrollBar(BarState.Off)
    }
    .justifyContent(FlexAlign.Start)
    .alignItems(HorizontalAlign.Start)
    .width('100%')
    .height('100%')
  }
}

关键代码说明:

  • 在Home组件的aboutToAppear生命周期中,通过postHttpRequest()和getHttpRequest()方法,分别获取到列表项中的文字以及图片数据源,和箭头图片的uri,并定义两个状态变量httpGridItems和pictureUri用来获取返回的结果。注意,使用完记得通过封装好的destroyHttpRequest()方法销毁HttpRequest对象。
  • 获取到List所需资源并存储到状态变量后,我们通过List组件配合ForEach进行循环渲染,从而实现首页List的渲染。

七、使用RCP请求网络数据

我们不仅可以通过HTTP访问网络,还可以通过RCP发起网络请求实现相同的功能。

  1. 实现思路

同样的,我们可以在network目录下创建一个RCP工具类(RCPUtils.ets),在该文件下实现RCP请求网络数据的功能。

与HTTP工具类实现类似,我们也简单分析一下该工具类需要完成的主要功能:

  • 使用RCP发起get请求,获取一张箭头图片并保存在沙箱路径中,并返回其沙箱路径对应的uri,以便使用箭头图片作为首页列表的布局元素。
  • 使用RCP发起post请求,获取官网中的图片与文本信息,并将其封装在实体类数组中,以便将这些信息作为首页列表的布局元素。
  1. RCP工具类
// entry/src/main/ets/common/network/RCPUtils.ets
import { rcp } from '@kit.RemoteCommunicationKit'
import { fileIo, fileUri } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import ResponseData from '../../viewmodel/ResponseData/ResponseData'
import ListInfo from '../../viewmodel/ResponseData/ListInfo'

const TAG: string = 'RCPUtils'
const list_source: string = 'https://svc-drcn.developer.huawei.com/' +
  'community/servlet/consumer/' +
  'partnerActivityService/v1/developer/activity/terminalActivities/list'

/**
 * RCPUtils 提供通过 RCP 访问网络的能力
 */
export class RCPUtils {
  rcpSession: rcp.Session

  constructor() {
    this.rcpSession = rcp.createSession()
  }

  /**
   * 通过 RCP 发起 GET 请求的方法
   */
  async getRCPRequest(cacheDir: string): Promise<string> {
    let responsePictureUri: string = ''
    await this.rcpSession.get('https://developer.huawei.com/system/' +
      'modules/org.opencms.portal.template.core/' +                       
      'resources/harmony/img/jiantou_right.svg')
      .then((response) => {
        let filePath = cacheDir + '/test.svg'
        let file = fileIo.openSync(
          filePath, 
          fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE
        )
        responsePictureUri = fileUri.getUriFromPath(filePath)
        fileIo.writeSync(file.fd, response.body)
        fileIo.closeSync(file.fd)
      }).catch((err: BusinessError) => {
        hilog.error(
          0x0000, 
          TAG, 
          `错误: 错误代码 ${err.code}, 错误信息 ${JSON.stringify(err)}`
        )
      })
    return responsePictureUri
  }

  /**
   * 通过 RCP 发起 POST 请求的方法
   */
  async postRCPRequest(): Promise<ListInfo[]> {
    let responseData: Array<ListInfo> = []
    let requestContent: rcp.RequestContent = {
      'status': '1',
      'belong': '1',
      'language': 'cn',
      'needTop': 1,
      'displayChannel': [1, 3],
      'count': 4,
      'pagestart': 1,
      'type': '1,4,5,6'
    }
    await this.rcpSession.post(list_source, requestContent).then((response) => {
      let result: ResponseData = response.toJSON() as ResponseData
      responseData = result.value.list
    }).catch((err: BusinessError) => {
      hilog.error(
        0x0000, 
        TAG, 
        `错误: 错误代码 ${err.code}, 错误信息 ${JSON.stringify(err)}`
      )
    })
    return responseData
  }

  /**
   * 关闭 RCP 会话的方法
   */
  destroySession() {
    this.rcpSession.close()
  }
}

关键代码说明:

  • 通过@kit.RemoteCommunicationKit中的rcp模块来使用RCP能力。
  • 在使用RCP发起网络请求前,首先需要获取一个Session类型的网络会话对象,在这里,我们将该类型的对象作为工具类的对象属性,并在构造方法中通过模块提供的rcp.createSession()方法为该对象属性赋值,以便后续方便的使用它。
  • 完成了获取网络会话对象Session后,我们就可以进一步封装get请求和post请求获取网络信息的方法了。网络会话对象Session提供了get()方法与post()方法,可以用于实现发起get请求和post请求,具体实现与http模块类似。
  • 最后,我们还需要像HTTP工具类一样,封装一个销毁会话对象的方法,需要使用会话对象的close()方法。
  1. 使用RCP工具类实现首页列表

完成RCP工具类的封装后,我们也可以通过RCP请求网络数据。

// entry/src/main/ets/view/Home.ets
import { rcp } from '@kit.RemoteCommunicationKit'
import { fileIo, fileUri } from '@kit.CoreFileKit'
import { BusinessError } from '@kit.BasicServicesKit'
import { hilog } from '@kit.PerformanceAnalysisKit'
import ResponseData from '../../viewmodel/ResponseData/ResponseData'
import ListInfo from '../../viewmodel/ResponseData/ListInfo'

const TAG: string = 'RCPUtils'
const list_source: string = 'https://svc-drcn.developer.huawei.com/' +
  'community/servlet/consumer/' +
  'partnerActivityService/v1/developer/activity/terminalActivities/list'

/**
 * RCPUtils 提供通过 RCP 访问网络的能力
 */
export class RCPUtils {
  rcpSession: rcp.Session

  constructor() {
    this.rcpSession = rcp.createSession()
  }

  /**
   * 通过 RCP 发起 GET 请求的方法
   */
  async getRCPRequest(cacheDir: string): Promise<string> {
    let responsePictureUri: string = ''
    await this.rcpSession.get('https://developer.huawei.com/system/modules' +
      '/org.opencms.portal.template.core/' +
      'resources/harmony/img/jiantou_right.svg')
      .then((response) => {
        let filePath = cacheDir + '/test.svg'
        let file = fileIo.openSync(
          filePath, 
          fileIo.OpenMode.CREATE | fileIo.OpenMode.READ_WRITE
        )
        responsePictureUri = fileUri.getUriFromPath(filePath)
        fileIo.writeSync(file.fd, response.body)
        fileIo.closeSync(file.fd)
      }).catch((err: BusinessError) => {
        hilog.error(
          0x0000, 
          TAG, 
          `err: err code is ${err.code}, err message is ${JSON.stringify(err)}`
        )
      })
    return responsePictureUri
  }

  /**
   * 通过 RCP 发起 POST 请求的方法
   */
  async postRCPRequest(): Promise<ListInfo[]> {
    let responseData: Array<ListInfo> = []
    let requestContent: rcp.RequestContent = {
      'status': '1',
      'belong': '1',
      'language': 'cn',
      'needTop': 1,
      'displayChannel': [1, 3],
      'count': 4,
      'pagestart': 1,
      'type': '1,4,5,6'
    }
    await this.rcpSession.post(list_source, requestContent).then((response) => {
      let result: ResponseData = response.toJSON() as ResponseData
      responseData = result.value.list
    }).catch((err: BusinessError) => {
      hilog.error(
        0x0000, 
        TAG, 
        `err: err code is ${err.code}, err message is ${JSON.stringify(err)}`
      )
    })
    return responseData
  }

  /**
   * 关闭 RCP 会话的方法
   */
  destroySession() {
    this.rcpSession.close()
  }
}

关键代码说明:

  • 与HTTP实现渲染类似,只需要在生命周期aboutToAppear中,创建工具类对象,使用工具类封装好的方法发起请求并存储在在状态变量中,然后通过List渲染即可。
  • 这里我们注释掉之前使用HTTP获取网络数据的部分,使用RCP实现相同的功能。

八、视频和代码资源

资源请访问:《HarmonyOS应用开发实战指南(进阶篇)》


网站公告

今日签到

点亮在社区的每一天
去签到