深入纯血鸿蒙HarmonyOS Next开发初体验

214 阅读3分钟

前端上手纯血鸿蒙开发初体验,鸿蒙相关语法跟TS是一样的,提供的包也是丰富,也就意味着需要了解熟悉的API也是很多,可以慢慢在开发过程中去熟悉你所需要的API运用,不然干看记不住也不知道实际如何使用。

在这开发过程中进行了分享记录开发过程,如何更快速的投入业务实现中,还有更多可拓展探讨的空间学习,以下是我在业务开发过程想要分享的内容。

类型问题

比起我们写TS,鸿蒙是强制类型约束提醒,只要声明都需要进行类型定义。可快速类型定义:

// 提示: Object literal must correspond to some explicitly declared class or interface (arkts-no-untyped-obj-literals) <ArkTSCheck>
const previewProfile = {
  width: 1080,   // 根据实际设备调整
  height: 1920   // 建议使用设备支持的分辨率
};

// 修改后
const previewProfile:Object = Object({
  width: 1080,   // 根据实际设备调整
  height: 1920   // 建议使用设备支持的分辨率
});

全局事件通知实现

需求: 监听软件回到前后台,将事件传给到离线H5的包 (通过watch的方式实现)

// EntryAbility.ets - 获取到监听的方法
import { UIAbility } from '@kit.AbilityKit'
import { appStorage } from '../appStorage/AppStorage'
export default class EntryAbility extends UIAbility {
onForeground(): void {
    // 应用从后台进入前台
    // 通过事件中心触发全局事件
    appStorage.set('isAppForeground',true)
  }
}

// AppStorage.ets - 数据进行store化存储
class StorageList {
  deviceList: ProductItem[] = []
  resetPhones: ProductItem[] = []

  set<T>(key:string, value:T){
    AppStorage.setOrCreate(key, value)
  }
}

export const appStorage = new StorageList()

// 通知信息监听 - 通过watch的方式去监听数据的变化
@StorageLink('isAppForeground') @Watch('handleForegroundChange') isForeground: boolean = false;
 handleForegroundChange():void {
    if(this.isForeground) {
      this.webController.runJavaScript('onForegroundHarmonyToH5()')
    }
  }

内嵌离线包H5如何实现

技术点: 1. 如何让原有H5的window事件调用(摄像头等)生效 2. 桥接实现


import WebView from '@ohos.web.webview'
import { webview } from '@kit.ArkWeb';
import router from '@ohos.router'
import window from '@ohos.window'
import common from '@ohos.app.ability.common'
import { audio } from '@kit.AudioKit'
import deviceInfo from '@ohos.deviceInfo';

// 桥接的方法处理
webController: WebView.WebviewController = new WebView.WebviewController()
this.webController.runJavaScript(`changeVolume(${false})`)

Web({
  // todo 切换成正式本地启动包
  src: "",
  controller: this.webController
})
// 这种方式开启才可以去调浏览器原生方法window.navigator等
  .javaScriptAccess(true)       // 启用 JavaScript
  .domStorageAccess(true)
  .fileAccess(true)
  .mediaPlayGestureAccess(true)
  .enableNativeMediaPlayer({
    enable: true,   // 启用本地媒体接管
    shouldOverlay: false
  })
  // 调用摄像头需要监听对应的权限返回
  .onPermissionRequest((event) => {  // WebView 内部权限处理
    if (event.request.getAccessibleResource().includes('TYPE_VIDEO_CAPTURE')) {
      // 弹窗询问用户
      promptAction.showDialog({
        title: '权限申请',
        message: '需要访问摄像头',
        buttons: [{ text: '拒绝' , color: '#333'}, { text: '允许', color: '#333' }]
      }).then((result) => {
        if (result.index === 1) {
          // 授予摄像头权限
          event.request.grant(['TYPE_VIDEO_CAPTURE']);
        } else {
          event.request.deny();
        }
      });
    }
  })
  .onControllerAttached(async () => {
  // 加载离线包
    this.webController.loadUrl(`resource://rawfile/cloud/index.html#/main?cloudId=${this.cloudId}&token=${this.token}&baseUrl=${this.baseUrl}&package=${this
      .package}&devicePwd=${this.devicePwd}&currentPhoneName=${this.currentPhoneName}&userId=${this.userId}&channel=${getDefaultChannel()}`)
  // 加载本地H5的项目可进行联调
    // this.webController.loadUrl(`http://172.16.80.72:5173/#/main?cloudId=${this.cloudId}&token=${this.token}&baseUrl=${this.baseUrl}&package=${this
    //   .package}&devicePwd=${this.devicePwd}&currentPhoneName=${this.currentPhoneName}&userId=${this.userId}&channel=${getDefaultChannel()}`)

开发页面过程

image.png

页面开发预览

  1. 编辑器自带的预览器
  2. 设备下载的虚拟手机
  3. 物理机器。 这种需要在文件 - 项目结构 - project - signing Config进行添加config配置,插入物理机器后可以直接生成的,后面打包成测试包、生产包也是需要在这对应进行配置

页面开发

import getSystemBarHeight from '../utils/GetSystemBarHeight'
import { NavHeader } from '../common/NavHeader'
import { deleteUserLoginOut } from '../apis/app'
import { showAlert } from '../common/AlertDialog'
import authStore from '../stores/auth/index'
import router from '@ohos.router'
import { authListData, authLIST } from '../views/AboutPage/utils/list'
import  {PermissionUtil  } from '../views/AboutPage/utils/uploadInformation'
import UserStore from '../stores/UserStore'
import  { common } from '@kit.AbilityKit'
import { abilityAccessCtrl } from '@kit.AbilityKit';
import { camera } from '@kit.CameraKit';

@Entry
@Component
struct Auth {
  // @Consume isLogin: boolean
  @StorageProp('authListStr') authListStr: AuthOpenStatus = {}
  @State authList:AuthOpenStatus = {}
  private isManual: boolean = false; // 标记是否为用户操作
  // 顶部导航栏高度
  @State titleBarPadding:string = '0'
  @State myAuthListData: authLIST[] = authListData
  async aboutToAppear(): Promise<void> {
    // 获取系统顶部导航栏的高度
    this.titleBarPadding = getSystemBarHeight()
    this.authList = await authStore.getAuthList() as AuthOpenStatus  || {}
    this.handleAuthOpen('privacy', true)
  }

  async handleAuthOpen(key: string, value: boolean):Promise<void> {
    this.authList[key] = value
    await authStore.setAuthList(this.authList)
  }
  
  build() {
    Column() {
      NavHeader({ title: '权限管理' })
      Column(){
        ForEach(this.myAuthListData, (item: authLIST, index: number) => {
          Row() {
            Text(item.text)
              .fontSize(15)
              .fontColor(Color.Black)
            Toggle({ type: ToggleType.Switch, isOn: this.authList[item.type] })
              .hitTestBehavior(HitTestMode.None) // 阻止默认事件 - Block
              .onChange(async (isOn: boolean) => {
                console.info('此处不会触发,因为已禁用点击事件');
                return
              })
          }
          .onClick(() => {
            if (!this.authList[item.type] !== (item.type === 'privacy')) { // 仅在开启操作时触发弹窗
              // 如果是退出登录
              showAlert({
                title: item.tip,
                message: item.message,
                autoCancel: false,
                cancelBtnText: '确认',
                confirmBtnText: '取消',
                confirmFunc: () => {
                  if(item.type !== 'privacy') {
                    this.handleAuthOpen(item.type, false)
                  }
                },
                cancelFunc: async () => {
                  if(item.type === 'gps') {
                    let location = await PermissionUtil.request(
                      getContext(this) as common.UIAbilityContext,
                      ['ohos.permission.LOCATION', 'ohos.permission.APPROXIMATELY_LOCATION']
                    );
                    if(location) { this.handleAuthOpen(item.type, true)}
                  }
                  if (item.type === 'camera') {
                    let cameraData = await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this) as common.UIAbilityContext, ['ohos.permission.CAMERA']);
                    const grantCameraDataStatus = cameraData.authResults;
                    if (grantCameraDataStatus.every(status => status === 0)) {
                      this.handleAuthOpen(item.type, true)
                    }
                  }
                  if (item.type === 'micro'){
                    const microData = await abilityAccessCtrl.createAtManager().requestPermissionsFromUser(getContext(this) as common.UIAbilityContext,  ['ohos.permission.MICROPHONE']);
                    const grantMicroStatus = microData.authResults;
                    if (grantMicroStatus.every(status => status === 0)) {
                      this.handleAuthOpen(item.type, true)
                    }
                  }
                  if(item.type === 'privacy') {
                    // 退出登录
                    // ToDo: 埋点
                    await deleteUserLoginOut()
                    // localStore.clearUserInfo()
                    UserStore.clearUserInfo()
                    // 登陆成功后存储登录状态
                    await UserStore.setLoginStatus(false)
                    router.back()
                    // this.isLogin = false
                  }
                },
              })
            }
            if(this.authList[item.type]) {
              this.handleAuthOpen(item.type, false)
            }
          })
          .justifyContent(FlexAlign.SpaceBetween)
          .width('100%')
          .padding({
            top: '21vp',
            bottom: '21vp',
            left: '12vp',
            right: '12vp'
          })
          .backgroundColor('#FFFFFF')
          .margin({bottom: '20vp'})
        })
      }
      .backgroundColor('#F9F9F9')
      .height('100%')
      .padding(16)

    }
    .height('100%')
    .width('100%')
    .padding({
      top: this.titleBarPadding
    })
  }
}

工具介绍

运用插件CodeGenie,挺好用的,围绕都是鸿蒙的点去回答

image.png

学习地址

  1. DevEco Studio使用指南
  2. 学习arkTs 、harmonyOS
  3. 视频教程 HarmonyOS第一课
  4. 组件参考(基于ArkTS的声明式开发范式)
  5. OpenHarmony三方库中心仓
  6. 鸿蒙图标库
  7. 鸿蒙音效库