HarmonyOS 超级终端与服务卡片开发:打造无缝多设备交互体验

HarmonyOS 超级终端与服务卡片开发:打造无缝多设备交互体验 #星光不负码向未来#

参与#星光不负码上未来#征文活动

欢迎继续探索 HarmonyOS 进阶系列 !在上篇《HarmonyOS 分布式与 AI 集成》中,我们实现了跨设备同步的智能备忘录应用。本篇将深入探讨 HarmonyOS 的超级终端(Super Device)服务卡片(Service Widget) ,通过一个 跨设备协作的音乐播放器应用,展示如何利用超级终端实现设备间无缝连接,并通过服务卡片提供快捷交互入口,带来流畅的鸿蒙生态体验。

本文基于 HarmonyOS NEXT API 12+ ,使用 ArkTSDevEco Studio 2025 ,结合 超级终端 API服务卡片框架,实现音乐播放器的多设备协同和卡片化交互。让我们开始吧!


前置准备

工具 版本要求 下载链接
DevEco Studio 2025.1+ 华为开发者官网
JDK 17 内置于 DevEco Studio
HarmonyOS 设备 手机/平板/手表/车机 华为 Mate 60 / MatePad / Watch 4 / AITO M9
模拟器 API 12+ DevEco Studio 内置
HMS Core SDK 6.13+ 自动集成

项目结构

复制代码
music-player-app
├── entry/src/main/ets
│   ├── MainAbility
│   │   ├── pages
│   │   │   ├── Player.ets
│   │   │   └── Settings.ets
│   │   ├── services
│   │   │   └── MusicSyncService.ets
│   │   └── widgets
│   │       └── MusicWidget.ets
│   └── resources
│       └── base
│           └── media
│               └── icon.png
├── module.json5
└── build-profile.json5

安装环境

  • 安装 DevEco Studio:从 华为开发者官网 下载。
  • 配置模拟器:Tools > Device Manager > Create Simulator(支持手机/平板/手表)。
  • 验证:运行默认 "Hello World" 项目,确保模拟器或设备正常连接。

步骤 1:配置超级终端权限

module.json5 中添加超级终端和媒体相关权限:

json5 复制代码
{
  "module": {
    "name": "entry",
    "type": "entry",
    "mainElement": "MainAbility",
    "deviceTypes": [
      "phone",
      "tablet",
      "wearable",
      "car"
    ],
    "requestPermissions": [
      {
        "name": "ohos.permission.DISTRIBUTED_DEVICE_STATE_CHANGE",
        "reason": "$string:permission_device_state_reason",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "always"
        }
      },
      {
        "name": "ohos.permission.MEDIA_LOCATION",
        "reason": "$string:permission_media_reason"
      }
    ],
    "metaData": {
      "customizeData": [
        {
          "name": "Widget",
          "value": "com.example.musicplayerapp.widgets.MusicWidget"
        }
      ]
    }
  }
}

说明

  • DISTRIBUTED_DEVICE_STATE_CHANGE:监听设备连接状态。
  • MEDIA_LOCATION:访问音乐文件。
  • metaData:注册服务卡片。

步骤 2:实现超级终端协同(分布式设备管理)

MusicSyncService.ets 中实现设备发现与音乐播放同步:

typescript 复制代码
// entry/src/main/ets/MainAbility/services/MusicSyncService.ets
import deviceManager from '@ohos.distributedHardware.deviceManager'
import distributedData from '@ohos.data.distributedData'
import { BusinessError } from '@kit.BasicServicesKit'

class MusicSyncService {
  private deviceManager: deviceManager.DeviceManager | null = null
  private kvStore: distributedData.KVStore | null = null
  private readonly STORE_ID = 'music_player_store'

  async init(context: any): Promise<void> {
    try {
      // 初始化设备管理
      this.deviceManager = await deviceManager.createDeviceManager(context.bundleName)
      this.deviceManager.on('deviceStateChange', (data) => {
        console.info(`Device ${data.deviceId} ${data.deviceState === 1 ? 'online' : 'offline'}`)
      })

      // 初始化分布式存储
      const kvManager = distributedData.createKVManager({ context, bundleName: context.bundleName })
      this.kvStore = await kvManager.getKVStore(this.STORE_ID, {
        createIfMissing: true,
        autoSync: true
      })
    } catch (error) {
      console.error(`Init failed: ${(error as BusinessError).message}`)
    }
  }

  async syncPlaybackState(state: PlaybackState): Promise<void> {
    if (!this.kvStore) return
    await this.kvStore.put('playback_state', JSON.stringify(state))
  }

  async getPlaybackState(): Promise<PlaybackState | null> {
    if (!this.kvStore) return null
    const state = await this.kvStore.get('playback_state')
    return state ? JSON.parse(state) : null
  }

  async getAvailableDevices(): Promise<deviceManager.DeviceInfo[]> {
    if (!this.deviceManager) return []
    return await this.deviceManager.getTrustedDeviceList()
  }
}

interface PlaybackState {
  trackId: string
  position: number
  isPlaying: boolean
}

export const musicSyncService = new MusicSyncService()

亮点

  • 动态监听设备状态,实时更新可用设备列表。
  • 使用 KVStore 同步播放状态(如曲目、进度、播放/暂停)。

步骤 3:实现音乐播放器 UI

Player.ets 中实现响应式播放器界面:

typescript 复制代码
// entry/src/main/ets/MainAbility/pages/Player.ets
import media from '@ohos.multimedia.media'
import router from '@ohos.router'

@Entry
@Component
struct Player {
  @State playbackState: PlaybackState = { trackId: 'track1', position: 0, isPlaying: false }
  @State devices: deviceManager.DeviceInfo[] = []
  private audioPlayer: media.AVPlayer | null = null

  aboutToAppear() {
    musicSyncService.init(this.context)
    this.loadDevices()
    this.initPlayer()
    this.loadPlaybackState()
  }

  async loadDevices() {
    this.devices = await musicSyncService.getAvailableDevices()
  }

  async loadPlaybackState() {
    const state = await musicSyncService.getPlaybackState()
    if (state) {
      this.playbackState = state
    }
  }

  async initPlayer() {
    this.audioPlayer = await media.createAVPlayer()
    this.audioPlayer.on('play', () => {
      this.playbackState.isPlaying = true
      musicSyncService.syncPlaybackState(this.playbackState)
    })
    this.audioPlayer.on('seekDone', (position) => {
      this.playbackState.position = position
      musicSyncService.syncPlaybackState(this.playbackState)
    })
  }

  build() {
    Column() {
      Text(`Now Playing: ${this.playbackState.trackId}`)
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
        .margin(10)

      Progress(this.playbackState.position, 100)
        .width('80%')
        .margin(10)

      Row() {
        Button(this.playbackState.isPlaying ? 'Pause' : 'Play')
          .fontSize(18)
          .backgroundColor(this.playbackState.isPlaying ? '#F44336' : '#4CAF50')
          .onClick(() => {
            this.playbackState.isPlaying ? this.audioPlayer?.pause() : this.audioPlayer?.play()
          })

        Button('Settings')
          .fontSize(18)
          .backgroundColor('#2196F3')
          .onClick(() => {
            router.pushUrl({ url: 'pages/Settings' })
          })
      }
      .justifyContent(FlexAlign.SpaceBetween)
      .width('100%')

      List() {
        ForEach(this.devices, (device: deviceManager.DeviceInfo) => {
          ListItem() {
            Text(device.deviceName)
              .fontSize(16)
          }
        })
      }
      .margin(10)
    }
    .width('100%')
    .padding(20)
    .backgroundColor('#F5F5F5')
  }
}

说明

  • 使用 @ohos.multimedia.media 播放音频。
  • 响应式 UI,支持手机、平板、车机等设备。


图源:华为开发者社区 - 音乐播放器 UI 示例


步骤 4:实现服务卡片

MusicWidget.ets 中实现桌面服务卡片:

typescript 复制代码
// entry/src/main/ets/MainAbility/widgets/MusicWidget.ets
import formBindingData from '@ohos.app.form.formBindingData'

@Component
struct MusicWidget {
  @State playbackState: PlaybackState = { trackId: 'track1', position: 0, isPlaying: false }

  build() {
    Column() {
      Text(this.playbackState.trackId)
        .fontSize(14)
        .fontWeight(FontWeight.Medium)

      Button(this.playbackState.isPlaying ? 'Pause' : 'Play')
        .fontSize(12)
        .width(80)
        .height(30)
        .onClick(() => {
          this.playbackState.isPlaying = !this.playbackState.isPlaying
          musicSyncService.syncPlaybackState(this.playbackState)
        })
    }
    .width(160)
    .height(100)
    .backgroundColor('#FFFFFF')
  }
}

export default {
  onCreate() {
    const formData = {
      trackId: 'track1',
      isPlaying: false
    }
    return formBindingData.createFormBindingData({ data: formData })
  },

  onUpdate(formId: string) {
    // 更新卡片状态
  },

  onDestroy(formId: string) {
    // 清理资源
  }
}

说明

  • 服务卡片支持桌面快捷交互。
  • 通过 formBindingData 动态更新卡片内容。

步骤 5:构建与多设备调试

  1. 构建 HAP 包

    • Build > Build Hap > Generate Signed Hap。
    • 输出 entry.hapform.hap(服务卡片)。
  2. 多设备部署

    • 手机/平板:通过 DevEco Studio 直接安装。
    • 手表/车机:使用远程调试(Tools > Remote Device)。
    • 验证超级终端:确保设备通过华为账户绑定在同一局域网。
  3. 测试协同

    • 在手机上播放/暂停音乐,观察手表卡片和车机界面同步。
    • 检查日志:Tools > Logcat,过滤 "MusicSyncService"。

进阶与最佳实践

  • 性能优化

    • 缓存播放状态:

      typescript 复制代码
      @StorageLink('playbackCache') playbackState: PlaybackState
  • 错误处理

    • 添加重试机制:

      typescript 复制代码
      async syncPlaybackState(state: PlaybackState, retries: number = 3): Promise<void> {
        for (let i = 0; i < retries; i++) {
          try {
            await this.kvStore.put('playback_state', JSON.stringify(state))
            return
          } catch (error) {
            console.error(`Retry ${i + 1}: ${(error as BusinessError).message}`)
          }
        }
      }
  • 卡片动态更新

    • 使用 FormProvider 推送更新:

      typescript 复制代码
      import FormProvider from '@ohos.app.form.FormProvider'
      FormProvider.updateForm(formId, formBindingData.createFormBindingData({ data }))
  • 资源推荐


总结

通过本篇,你掌握了:

  • 超级终端:实现手机、手表、车机间的音乐播放同步。
  • 服务卡片:提供桌面快捷交互入口。
  • 多设备适配:响应式 UI 适配不同设备。

下一期预告:《HarmonyOS 云服务与推送通知》------让你的应用随时与用户保持连接!

有问题?欢迎在评论区交流!喜欢请点赞分享~

(最后更新:2025 年 10 月 24 日)

相关推荐
红宝村村长4 小时前
【学习笔记】大模型
深度学习·1024程序员节
2501_929382654 小时前
[Switch大气层]纯净版+特斯拉版 20.5.0大气层1.9.5心悦整合包 固件 工具 插件 前端等资源合集
前端·游戏·switch·1024程序员节·单机游戏
limingade4 小时前
手机摄像头如何识别体检的色盲检查图的数字和图案(下)
android·1024程序员节·色盲检查图·手机摄像头识别色盲图案·android识别色盲检测卡·色盲色弱检测卡
毕设源码-赖学姐4 小时前
【开题答辩全过程】以基于Hadoop的电商数据分析系统为例,包含答辩的问题和答案
大数据·hadoop·分布式·1024程序员节
第七序章4 小时前
【C + +】unordered_set 和 unordered_map 的用法、区别、性能全解析
数据结构·c++·人工智能·算法·哈希算法·1024程序员节
熊文豪4 小时前
昇腾NPU部署GPT-OSS-20B混合专家模型:从环境配置到性能优化的完整实践指南
昇腾·1024程序员节·昇腾npu·gpt-oss-20b
coldriversnow4 小时前
uniapp video 加载完成后全屏播放
1024程序员节
汤姆yu4 小时前
基于python大数据技术的医疗数据分析与研究
大数据·1024程序员节·医疗数据分析·医疗预测
失败又激情的man4 小时前
爬虫逆向之X音a_bogus参数分析
爬虫·1024程序员节