鸿蒙最新知识点整理——Axios、生命周期、lazyForEach、录音转文字功能

631 阅读5分钟

6月13日讨论内容整理

一、Axios的相关知识点

src/main/ets/utils/http.ets:

import axios, { InternalAxiosRequestConfig, AxiosError, AxiosResponse, AxiosRequestConfig } from '@ohos/axios'// 实例化 通用配置
const httpInstance = axios.create({
  // 美蔻的基地址
  baseURL: 'https://meikou-api.itheima.net/',
  // 超时事件最长多久不响应,就认为是失败
  timeout: 5000
})
​
// 拦截器配置
// 请求拦截器
// token配置等
httpInstance.interceptors.request.use((config: InternalAxiosRequestConfig) => {
  return config
}, (error: AxiosError) => {
  return Promise.reject(error)
})
​
// 添加响应拦截器
// 错误统一处理等
httpInstance.interceptors.response.use((response: AxiosResponse) => {
  return response
}, (error: AxiosError) => {
  return Promise.reject(error)
})
​
// 通用的响应类型,外层都是相同的
// result 不同,利用泛型来动态设置即可
interface HttpResponse<T> {
  code: string
  msg: string
  result: T // result通过泛型动态设置
}
​
// 通用的请求方法,将 类型,返回值,参数都封装到一起 简化调用
// AxiosRequestConfig<null> 参数类型
// Promise<AxiosResponse<HttpResponse<T>, null>> 返回值类型
// D=null 设置默认值
function fetchData<T, D = null>(config: AxiosRequestConfig<D>): Promise<AxiosResponse<HttpResponse<T>, null>> {
  return httpInstance <null, AxiosResponse<HttpResponse<T>>, D>(config)
}
​
​
export { httpInstance, HttpResponse, fetchData }

在上面的代码中,我们可以对Axois的特性做一个分析

(1)Axios较http原生请求方式封装的区别:

a. http 请求 ⅰ. 基地址 ⅱ. 请求和响应拦截器 b. axios 二次封装 ⅰ. 基地址 ⅱ. 请求和响应拦截器 ⅲ. 查询参数传递:params ⅳ. 请求体参数传递:data

根据以上封装内容可知,Axios的封装中多了查询参数和请求体参数两部分

(2)对实操代码的分析可得

①Axios自带拦截器功能,主要有两种用途:可以进行token的拦截、错误代码的拦截

②http封装的内容构成:

a.实例化通用配置(设置基地址、超时响应时间)

b.拦截器

c.设置一个泛型接口HttpResponse

d.通用请求方法

e.将上面设置的一个常量、一个接口、一个函数方法导出

③通用请求方法fetchData<T,D=null>

其中T是响应数据的类型,D是请求数据的类型,默认是null

1718294113967.png

二、鸿蒙的生命周期相关概念

以下图表可以很好地表名生命周期函数的运行顺序及相关关系

具体知识参考文章《鸿蒙开发基础 - 关于生命周期钩子详解》:juejin.cn/post/737252…

1718294247836.png

其中容易忽略的一个生命周期函数:onBackPress()

使用onBackPress,做一个弹窗提醒用户,要return一个true阻断默认的返回我们项目里,在做笔记本相关的内容时,当点击退出按钮的时候会用到,作为防止用户误操作的提醒。

1718294387878.png

三、lazyForEach的使用方法

具体方法简要概括如下:

①在获取数据getData方法中正常使用forEach渲染数据的

②在提交给服务器的参数中设置要查询的页数pageSize

③将内部操作数据的方法类型,替换为我们希望的数据

这个我们希望的数据最后要实现的接口是IDataSource,在IDataSource的内置方法中设置有监听数据的注册与注销的函数,用于控制需要渲染出的数据长度

1718296076810.png

四、如何使用录音转文字功能

features/home/src/main/ets/components/AudioSearchComp.ets:

①设置3种录制状态,月用于控制系统运行功能

②设置开启录制、关闭录制方法;申请录制权限 (进入页面即执行)

③Button的长按手势的属性方法.gesture(LongPressGesture().方法),调用开启录制、关闭录制的方法 分为开启、结束、取消三种情况

features/home/src/main/ets/views/SearchView.ets:

④组件被调用

1718297564617.png

案例源码:

features/home/src/main/ets/components/AudioSearchComp.ets:

import { KeyboardAvoidMode, window } from '@kit.ArkUI';
import { audio } from '@kit.AudioKit';
import { speechRecognizer } from '@kit.CoreSpeechKit';
import { permissionPlugin } from '@mk/basic/Index';
​
// import { permissionPlugin } from '@mk/basic';export enum VoiceState {
  DEFAULT, // 默认
  VOICING, // 录音
  VOICEOVER // 录完了
}
​
@Component
export struct AudioSearchComp {
  @State voiceState: VoiceState = VoiceState.DEFAULT
  keyword: string = ''
  audioCapturer: audio.AudioCapturer | null = null
  asrEngine: speechRecognizer.SpeechRecognitionEngine | null = null
  // 改变
  onChange: (keyword: string) => void = () => {
  }
  // 完成
  onComplete: (keyword: string) => void = () => {
  }
​
  // 开启录制
  async startRecord() {
    // 开始识别
    this.asrEngine = await speechRecognizer.createEngine({
      language: 'zh-CN',
      online: 1
    })
    // 保存组件的 this,后续通过_this来使用组件
    const _this = this
    // 语音转换文字的回调函数
    this.asrEngine.setListener({
      onStart(sessionId: string, eventMessage: string) {
        console.info(`onStart, sessionId: ${sessionId} eventMessage: ${eventMessage}`);
      },
      onEvent(sessionId: string, eventCode: number, eventMessage: string) {
        console.info(`onEvent, sessionId: ${sessionId} eventCode: ${eventCode} eventMessage: ${eventMessage}`);
      },
      // 有转换结果了
      onResult(sessionId: string, result: speechRecognizer.SpeechRecognitionResult) {
        console.info(`onResult, sessionId: ${sessionId} sessionId: ${JSON.stringify(result)}`);
        // 把 录制结果传递出去即可,使用 上面保存的_this
        _this.onChange(result.result)
        _this.voiceState=VoiceState.VOICING
        // 录音的文本保存到 keyword 变量中,后续即可通过 keyword 来获取
        _this.keyword = result.result
      },
      // 转换完毕了
      onComplete(sessionId: string, eventMessage: string) {
        _this.voiceState = VoiceState.DEFAULT
        _this.onComplete(_this.keyword)
        console.info(`onComplete, sessionId: ${sessionId} eventMessage: ${eventMessage}`);
      },
      onError(sessionId: string, errorCode: number, errorMessage: string) {
        console.error(`onError, sessionId: ${sessionId} errorCode: ${errorCode} errorMessage: ${errorMessage}`);
      }
    })
​
​
    const recognizerParams: speechRecognizer.StartParams = {
      sessionId: '10000',
      audioInfo: {
        audioType: 'pcm',
        sampleRate: 16000,
        soundChannel: 1,
        sampleBit: 16
      }
    }
    // 开启监听 结合啥概念茶农的参数
    this.asrEngine?.startListening(recognizerParams)
​
    // 开始录音
    const audioStreamInfo: audio.AudioStreamInfo = {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_16000,
      channels: audio.AudioChannel.CHANNEL_1,
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
    }
    const audioCapturerInfo: audio.AudioCapturerInfo = {
      source: audio.SourceType.SOURCE_TYPE_MIC,
      capturerFlags: 0
    }
    const audioCapturerOptions: audio.AudioCapturerOptions = {
      streamInfo: audioStreamInfo,
      capturerInfo: audioCapturerInfo
    }
​
    this.audioCapturer = await audio.createAudioCapturer(audioCapturerOptions)
    // 录音的数据 传递给 语音转文字的工具
    this.audioCapturer.on('readData', (buffer) => {
      console.log('mk-logger', buffer.byteLength)
      this.asrEngine?.writeAudio('10000', new Uint8Array(buffer))
    })
    await this.audioCapturer.start()
    this.voiceState = VoiceState.VOICING
  }
​
  // 关闭录制
  async closeRecord() {
    this.audioCapturer?.stop()
    this.audioCapturer?.release()
    this.asrEngine?.finish('10000')
    this.asrEngine?.cancel('10000')
    this.asrEngine?.shutdown()
    if (this.keyword) {
      this.voiceState = VoiceState.DEFAULT
      this.keyword = ''
    } else {
      this.voiceState = VoiceState.VOICEOVER
    }
  }
​
  aboutToAppear(): void {
    permissionPlugin.requestPermissions([
      'ohos.permission.MICROPHONE'
    ])
      .then(() => {
        window.getLastWindow(getContext())
          .then(win => {
            win.getUIContext()
              .setKeyboardAvoidMode(KeyboardAvoidMode.RESIZE)
          })
      })
    // })
  }
​
  build() {
    Column() {
      // 根据状态 更改显示的文本
      if (this.voiceState !== VoiceState.DEFAULT) {
        Column({ space: 16 }) {
          if (this.voiceState === VoiceState.VOICING) {
            Text('请说,我在聆听...')
              .fontSize(14)
          } else if (this.voiceState === VoiceState.VOICEOVER && this.keyword === '') {
            Text('未检测到语音,请长按按钮重试')
              .fontSize(14)
          }
          Text() {
            Span('你可以这样说:')
            Span('太阳眼镜/冬款连衣裙')
              .fontColor($r('app.color.gray'))
          }
          .fontSize(12)
        }
        .justifyContent(FlexAlign.Center)
        .height(150)
      }
      Blank()
      Button() {
        Row({ space: 4 }) {
          Image($r('sys.media.ohos_ic_public_voice'))
            .width(16)
            .aspectRatio(1)
            .fillColor($r('app.color.white'))
          if (this.voiceState === VoiceState.VOICING) {
            Text('松开立即搜索')
              .fontSize(14)
              .fontColor($r('app.color.white'))
          } else {
            Text('长按语音搜索')
              .fontSize(14)
              .fontColor($r('app.color.white'))
          }
        }
      }
      .padding({ left: 12, right: 12 })
      .height(36)
      .linearGradient({ angle: 135, colors: [[$r('app.color.linear_begin'), 0], [$r('app.color.linear_end'), 1]] })
      .margin({ bottom: 16 })
      .gesture(LongPressGesture()// 长按手势
        .onAction(() => {
          this.startRecord()
          // 开启
          // this.voiceState = VoiceState.VOICING
        })
        .onActionEnd(() => {
          this.closeRecord()
          // 结束
          // this.voiceState = VoiceState.VOICEOVER
          // this.keyword = '么么哒'
        })
        .onActionCancel(() => {
          this.closeRecord()
          // 取消
        }))
​
    }
    .layoutWeight(1)
    .width('100%')
    .backgroundImage($r('app.media.search_bg'))
    .backgroundImageSize(ImageSize.Contain)
    .backgroundImagePosition(Alignment.Bottom)
    .onVisibleAreaChange([0, 1], () => {
      this.keyword = ''
      this.voiceState = VoiceState.DEFAULT
    })
  }
}

features/home/src/main/ets/views/SearchView.ets:

import { Log } from '@abner/log'
import { router, window } from '@kit.ArkUI'
import { AudioSearchComp } from '../components/AudioSearchComp'@Entry
@Component
export struct SearchView {
  @StorageProp('safeTop') safeTop: number = 0
  // 关键词
  @State keyword: string = '鞋子,帽子,裤子'
​
  // 修改 状态栏的文字的颜色
  aboutToAppear(): void {
    window.getLastWindow(getContext())
      .then((win) => {
        win.setWindowSystemBarProperties({ statusBarContentColor: '#FFFFFF' })
      })
  }
​
  aboutToDisappear(): void {
    window.getLastWindow(getContext())
      .then((win) => {
        win.setWindowSystemBarProperties({ statusBarContentColor: '#000000' })
      })
  }
​
  toSearchResult(value: string) {
    router.pushUrl({
      url: 'pages/SearchResultPage',
      params: {
        keyword: value
      }
    })
  }
​
  build() {
    Column() {
      // search
      Row() {
        Image($r('app.media.ic_public_left'))
          .width(24)
          .aspectRatio(1)
          .fillColor($r('app.color.white'))
          .margin(13)
          .onClick(() => {
            router.back()
          })
​
        Search({ placeholder: '商品关键字...', value: this.keyword })
          .searchIcon({ src: $r('app.media.ic_public_search'), color: $r('app.color.gray') })// 搜索图标
          .placeholderColor($r('app.color.gray'))// 提示文本颜色
          .placeholderFont({ size: 14 })// 提示文本大小
          .searchButton('搜索', { fontSize: 14, fontColor: $r('app.color.red') })// 搜索按钮
          .backgroundColor($r('app.color.white'))// 背景色
          .textFont({ size: 14 })// 文字大小
          .layoutWeight(1)// 占比
          .padding(0)
          .margin(0)
          .height(36)
          .caretStyle({ color: $r('app.color.red') })
          .defaultFocus(true)// 默认获取焦点
          .onSubmit((value) => {
            // Log.info(value)// value可以获取输入的内容
            this.toSearchResult(value)
          })
​
​
      }
      .padding({ top: this.safeTop, right: 16 })
      .linearGradient({ angle: 135, colors: [[$r('app.color.linear_begin'), 0], [$r('app.color.linear_end'), 1]] })
​
      // 语音搜索组件
      AudioSearchComp({
        onChange: (keyword: string) => {
          Log.info('转换的文本是:' + keyword)
          this.keyword = keyword
        },
        onComplete: (keyword: string) => {
          // AlertDialog.show({
          //   message: '最后的信息为:' + keyword
          // })
          // 跳转到搜索页即可
          this.toSearchResult(keyword)
        }
      })
​
    }
  }
}