序章:意外的邂逅

2021年的一个秋日午后,我正在咖啡店里刷着技术论坛,突然看到了一条关于鸿蒙系统开源的消息。作为一个有着五年Android开发经验的程序员,我对这个消息的第一反应并不是兴奋,而是疑惑——又一个新的操作系统?我们真的需要吗?

然而,当我深入了解鸿蒙的技术理念时,一个词深深震撼了我:万物互联。不是简单的设备连接,而是真正意义上的分布式软总线、统一的设备发现机制、跨设备的应用迁移...这些概念让我意识到,这可能是移动开发的下一个时代。

那天晚上,我做了一个改变我人生轨迹的决定:从Android转向鸿蒙开发。现在回想起来,那是我技术生涯中最重要的转折点之一。

img

第一章:初次相遇的惊喜与困惑

第一次Hello World的激动

还记得配置好DevEco Studio,创建第一个鸿蒙项目时的紧张感。不同于Android Studio的熟悉界面,这里的一切都是崭新的。ArkTS、ArkUI、Stage模型...每一个概念都需要重新学习。

当我第一次在真机上看到"Hello World"成功运行时,那种激动的心情至今还记得很清楚。虽然只是简单的文字显示,但那意味着我已经踏进了鸿蒙开发的大门。

// 我的第一个鸿蒙应用 - 永远记得这个简单的开始
@Entry
@Component 
struct Index {
  @State message: string = 'Hello HarmonyOS World!'
  
  build() {
    Row() {
      Column() {
        Text(this.message)
          .fontSize(50)
          .fontWeight(FontWeight.Bold)
          .fontColor(Color.Red)
      }
      .width('100%')
    }
    .height('100%')
  }
}

那一刻,我在朋友圈写道:"人生第一个鸿蒙应用成功运行,虽然只有Hello World,但感觉打开了新世界的大门!"没想到这条朋友圈后来成为了很多朋友了解鸿蒙的起点。

声明式UI的"啊哈"时刻

从Android的命令式UI开发转向鸿蒙的声明式UI开发,最初我是非常不适应的。习惯了findViewById、setText这种直接操作控件的方式,突然要用状态驱动UI更新,思维需要完全转换。

我清楚地记得理解状态管理的那个瞬间。那天晚上我在做一个简单的计数器应用,一直纠结于如何更新界面。当我突然意识到只需要改变@State变量,界面就会自动刷新时,那种"啊哈"的感觉让我兴奋得睡不着觉。

// 让我理解状态管理的经典计数器例子
@Entry
@Component
struct Counter {
  @State count: number = 0
  
  build() {
    Column({ space: 20 }) {
      Text(`当前计数:${this.count}`)
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
      
      Row({ space: 15 }) {
        Button('-')
          .onClick(() => {
            this.count-- // 就这么简单!界面自动更新
          })
          .width(60)
        
        Button('+')
          .onClick(() => {
            this.count++ // 不需要手动刷新UI
          })
          .width(60)
      }
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
}

那天我在技术群里激动地分享:"鸿蒙的声明式UI真的太优雅了!状态变化,界面自动更新,再也不用写那些繁琐的UI刷新代码了!"群里的小伙伴们都被我的热情感染,纷纷开始关注鸿蒙开发。

第一次遇到的"鸿蒙式困惑"

当然,初学阶段也遇到了很多困惑。最大的困惑来自于对分布式概念的理解。什么是分布式软总线?设备发现机制是如何工作的?跨设备迁移到底是什么意思?

我记得为了理解这些概念,我看了无数的技术文档、视频教程,甚至把官方文档打印出来反复阅读。那段时间,我的桌子上堆满了各种鸿蒙相关的资料,同事们都开玩笑说我的工位像个"鸿蒙研究所"。

最终让我理解分布式概念的是一个简单的例子:手机上的音乐播放,可以无缝迁移到智能音箱上继续播放。当我第一次在模拟器上实现这个功能时,那种震撼是无法言喻的。这不是简单的投屏或者蓝牙连接,而是应用状态的完整迁移!

第二章:深入学习的酸甜苦辣

啃文档的深夜时光

为了系统学习鸿蒙开发,我制定了一个"百日计划"——每天至少花2小时学习鸿蒙相关技术。这意味着下班后的时间几乎都要献给鸿蒙了。

那些啃文档的深夜,现在回想起来既心酸又甜蜜。心酸的是真的很累,经常看文档看到眼睛发涩;甜蜜的是每当理解一个新概念、掌握一个新技能时,那种成就感是无与伦比的。

我记得最痛苦的一次是学习分布式数据管理。分布式数据库、数据同步、冲突解决...这些概念对于我这个从客户端转过来的开发者来说,真的是天书一般。我花了整整一周的晚上,反复阅读文档,做实验,才勉强理解其中的原理。

// 让我头疼了一周的分布式数据管理代码
import distributedData from '@ohos.data.distributedKVStore'

class DistributedDataManager {
  private kvManager: distributedData.KVManager | null = null
  private kvStore: distributedData.KVStore | null = null
  
  async initKVStore(): Promise<void> {
    try {
      // 创建KVManager
      const config: distributedData.KVManagerConfig = {
        bundleName: 'com.example.myapp'
      }
      this.kvManager = distributedData.createKVManager(config)
      
      // 创建分布式数据库
      const options: distributedData.Options = {
        createIfMissing: true,
        encrypt: false,
        backup: false,
        autoSync: true,
        kvStoreType: distributedData.KVStoreType.DEVICE_COLLABORATION,
        schema: '',
        securityLevel: distributedData.SecurityLevel.S2
      }
      
      this.kvStore = await this.kvManager.getKVStore('myStore', options)
      console.info('分布式数据库初始化成功')
      
      // 监听数据变化
      this.kvStore.on('dataChange', distributedData.SubscribeType.SUBSCRIBE_TYPE_ALL, (data) => {
        console.info('数据发生变化:', JSON.stringify(data))
        this.handleDataChange(data)
      })
      
    } catch (error) {
      console.error('分布式数据库初始化失败:', error)
    }
  }
  
  async putData(key: string, value: string): Promise<void> {
    if (this.kvStore) {
      try {
        await this.kvStore.put(key, value)
        console.info(`数据保存成功: ${key} = ${value}`)
      } catch (error) {
        console.error('数据保存失败:', error)
      }
    }
  }
  
  async getData(key: string): Promise<string | null> {
    if (this.kvStore) {
      try {
        const value = await this.kvStore.get(key) as string
        console.info(`数据获取成功: ${key} = ${value}`)
        return value
      } catch (error) {
        console.error('数据获取失败:', error)
        return null
      }
    }
    return null
  }
  
  private handleDataChange(data: distributedData.ChangeNotification): void {
    // 处理数据变化
    data.insertEntries?.forEach(entry => {
      console.info(`新增数据: ${entry.key} = ${entry.value.value}`)
    })
    
    data.updateEntries?.forEach(entry => {
      console.info(`更新数据: ${entry.key} = ${entry.value.value}`)
    })
    
    data.deleteEntries?.forEach(entry => {
      console.info(`删除数据: ${entry.key}`)
    })
  }
}

当我终于理解了这套机制,并成功实现了两个设备间的数据同步时,我激动地发了一条微博:"终于搞懂分布式数据管理了!两台设备的数据能够自动同步,这种感觉就像魔法一样!"

第一个像样项目的诞生

学习了几个月基础知识后,我决定做一个真正的项目来检验学习成果。我选择了做一个"智能待办"应用——不仅能在手机上管理任务,还能跨设备同步,支持语音输入,甚至可以在智能手表上快速查看。

这个项目的开发过程充满了挑战。从界面设计到数据管理,从语音识别到设备协同,每一个功能都需要深入研究。我记得为了实现语音识别功能,我研究了整整三天的ASR(自动语音识别)接口。

// 智能待办应用的核心数据结构
interface TodoItem {
  id: string
  title: string
  content: string
  completed: boolean
  priority: Priority
  createTime: number
  updateTime: number
  deviceId: string
}

enum Priority {
  LOW = 0,
  MEDIUM = 1,
  HIGH = 2,
  URGENT = 3
}

@Component
struct TodoItemComponent {
  @ObjectLink todoItem: TodoItem
  @Prop onToggleComplete: (id: string) => void
  @Prop onDelete: (id: string) => void
  @Prop onEdit: (id: string) => void
  
  build() {
    Row({ space: 12 }) {
      // 完成状态复选框
      Checkbox({ name: 'checkbox', group: 'checkboxGroup' })
        .select(this.todoItem.completed)
        .selectedColor(this.getPriorityColor())
        .onChange((value: boolean) => {
          this.onToggleComplete(this.todoItem.id)
        })
        .width(24)
        .height(24)
      
      Column({ space: 4 }) {
        // 任务标题
        Text(this.todoItem.title)
          .fontSize(16)
          .fontWeight(FontWeight.Medium)
          .fontColor(this.todoItem.completed ? Color.Gray : Color.Black)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
          .maxLines(1)
          .decoration({ type: this.todoItem.completed ? TextDecorationType.LineThrough : TextDecorationType.None })
        
        // 任务内容
        if (this.todoItem.content) {
          Text(this.todoItem.content)
            .fontSize(14)
            .fontColor(Color.Gray)
            .textOverflow({ overflow: TextOverflow.Ellipsis })
            .maxLines(2)
        }
        
        // 时间和设备信息
        Row({ space: 8 }) {
          Text(this.formatTime(this.todoItem.createTime))
            .fontSize(12)
            .fontColor(Color.Gray)
          
          if (this.todoItem.deviceId !== DeviceManager.getCurrentDeviceId()) {
            Text('来自其他设备')
              .fontSize(12)
              .fontColor(Color.Blue)
          }
        }
      }
      .alignItems(HorizontalAlign.Start)
      .layoutWeight(1)
      
      // 优先级标识
      Row() {
        Text(this.getPriorityText())
          .fontSize(10)
          .fontColor(Color.White)
          .padding({ left: 6, right: 6, top: 2, bottom: 2 })
      }
      .backgroundColor(this.getPriorityColor())
      .borderRadius(8)
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .shadow({ radius: 4, color: Color.Gray, offsetX: 0, offsetY: 2 })
    .gesture(
      SwipeGesture({ direction: SwipeDirection.Horizontal, speed: 100 })
        .onAction((event: GestureEvent) => {
          if (event.offsetX > 0) {
            // 右滑编辑
            this.onEdit(this.todoItem.id)
          } else {
            // 左滑删除
            this.onDelete(this.todoItem.id)
          }
        })
    )
  }
  
  private getPriorityColor(): Color {
    switch (this.todoItem.priority) {
      case Priority.LOW: return Color.Green
      case Priority.MEDIUM: return Color.Orange  
      case Priority.HIGH: return Color.Red
      case Priority.URGENT: return '#FF4444'
      default: return Color.Gray
    }
  }
  
  private getPriorityText(): string {
    switch (this.todoItem.priority) {
      case Priority.LOW: return '低'
      case Priority.MEDIUM: return '中'
      case Priority.HIGH: return '高'
      case Priority.URGENT: return '急'
      default: return ''
    }
  }
  
  private formatTime(timestamp: number): string {
    const date = new Date(timestamp)
    const now = new Date()
    const diffMs = now.getTime() - date.getTime()
    const diffHours = Math.floor(diffMs / (1000 * 60 * 60))
    
    if (diffHours < 1) {
      return '刚刚'
    } else if (diffHours < 24) {
      return `${diffHours}小时前`
    } else if (diffHours < 24 * 7) {
      const diffDays = Math.floor(diffHours / 24)
      return `${diffDays}天前`
    } else {
      return date.toLocaleDateString()
    }
  }
}

经过一个多月的开发,这个智能待办应用终于完成了。当我第一次看到手机上创建的任务自动同步到平板上时,那种成就感真的无法用言语表达。我立刻截图发到了技术群里,获得了一片赞声。

社区交流的温暖时光

在学习鸿蒙开发的过程中,鸿蒙开发者社区给了我很大的帮助。每当遇到问题时,我都会在社区里求助,热心的开发者们总是不厌其烦地给出详细的解答。

我记得有一次,我在实现跨设备迁移功能时遇到了一个奇怪的bug,应用在迁移过程中总是崩溃。我在社区发帖求助后,一位华为的技术专家不仅给出了解决方案,还详细解释了背后的原理,让我对分布式架构有了更深的理解。

后来,随着技术的提升,我也开始在社区帮助其他开发者。每当看到新手提出我曾经遇到过的问题时,我都会耐心地给出解答。这种互帮互助的氛围让我深深地爱上了鸿蒙开发者社区。

我还记得第一次在社区发技术分享文章时的紧张。那篇关于"鸿蒙应用架构最佳实践"的文章,我反复修改了好几遍才发布。没想到获得了很多赞和评论,很多开发者说我的分享很有帮助。那一刻,我意识到分享知识不仅能帮助别人,也能让自己得到成长。

第三章:技术突破的"神来之笔"

分布式音乐播放器的灵感闪现

2022年春天的一个周末,我正在家里听音乐,突然想起鸿蒙的跨设备迁移功能。如果能做一个真正的分布式音乐播放器,让音乐在不同设备间无缝切换,那该多酷啊!

这个想法一出现就在我脑海中挥之不去。我立刻打开电脑,开始构思这个应用的架构。经过一周的设计和开发,一个初级版本的分布式音乐播放器诞生了。

// 分布式音乐播放器的核心迁移逻辑
import continuationManager from '@ohos.continuation.continuationManager'
import featureAbility from '@ohos.ability.featureAbility'

interface MusicPlayerState {
  currentSong: string
  playlist: string[]
  currentIndex: number
  position: number // 播放位置,毫秒
  isPlaying: boolean
  volume: number
}

class DistributedMusicPlayer {
  private playerState: MusicPlayerState = {
    currentSong: '',
    playlist: [],
    currentIndex: 0,
    position: 0,
    isPlaying: false,
    volume: 50
  }
  
  private continuationExtraParams = {
    deviceConnectStatus: continuationManager.DeviceConnectState.CONNECTED,
    targetBundle: 'com.example.musicplayer'
  }
  
  async registerContinuation(): Promise<void> {
    try {
      const token = await continuationManager.registerContinuation(this.continuationExtraParams)
      console.info('设备迁移注册成功,token:', token)
      
      // 监听迁移请求
      continuationManager.on('continueStateChange', (token, state) => {
        console.info('迁移状态变化:', token, state)
        if (state === continuationManager.ContinuationState.LOCAL_CONNECTED) {
          this.onMigrationRequest()
        }
      })
      
    } catch (error) {
      console.error('设备迁移注册失败:', error)
    }
  }
  
  private async onMigrationRequest(): Promise<void> {
    console.info('收到迁移请求,准备迁移播放状态')
    
    // 获取当前播放位置
    this.playerState.position = await this.getCurrentPosition()
    
    // 暂停当前播放
    this.pauseMusic()
    
    // 确认迁移
    const wantValue = {
      bundleName: 'com.example.musicplayer',
      abilityName: 'MainAbility',
      parameters: {
        migrationData: JSON.stringify(this.playerState),
        isMigration: true
      }
    }
    
    try {
      await featureAbility.continueAbility(wantValue)
      console.info('音乐播放迁移成功')
    } catch (error) {
      console.error('迁移失败:', error)
    }
  }
  
  async restoreFromMigration(migrationData: string): Promise<void> {
    try {
      this.playerState = JSON.parse(migrationData) as MusicPlayerState
      
      // 恢复播放列表
      await this.loadPlaylist(this.playerState.playlist)
      
      // 跳转到指定歌曲和位置
      await this.playSongAtIndex(this.playerState.currentIndex)
      await this.seekTo(this.playerState.position)
      
      // 恢复播放状态
      if (this.playerState.isPlaying) {
        await this.resumeMusic()
      }
      
      console.info('音乐播放状态恢复成功')
      
      // 显示迁移成功提示
      this.showMigrationSuccessToast()
      
    } catch (error) {
      console.error('恢复播放状态失败:', error)
    }
  }
  
  private async getCurrentPosition(): Promise<number> {
    // 获取当前播放位置的具体实现
    return this.audioPlayer?.currentTime || 0
  }
  
  private pauseMusic(): void {
    if (this.audioPlayer && this.playerState.isPlaying) {
      this.audioPlayer.pause()
      this.playerState.isPlaying = false
    }
  }
  
  private async resumeMusic(): Promise<void> {
    if (this.audioPlayer) {
      await this.audioPlayer.play()
      this.playerState.isPlaying = true
    }
  }
  
  private showMigrationSuccessToast(): void {
    // 显示迁移成功的Toast提示
    console.info('音乐播放已从其他设备迁移至当前设备')
  }
}

当我第一次成功地在手机上播放音乐,然后无缝迁移到平板继续播放时,那种激动是难以言表的。音乐没有中断,从手机暂停的那一秒继续在平板上播放,就像魔法一样!

我立刻录制了一个演示视频发到朋友圈,引起了很多朋友的关注。有朋友评论说:"这个功能太酷了,什么时候能用上?"还有朋友问我是不是华为的员工,哈哈。

语音助手的创新实现

受到分布式音乐播放器成功的鼓舞,我决定挑战一个更复杂的项目——智能语音助手。不仅要能理解用户的语音指令,还要能控制家里的各种智能设备。

这个项目的最大挑战是语音识别和自然语言处理。虽然鸿蒙提供了ASR接口,但如何准确理解用户意图并执行相应操作,需要很多额外的工作。

我花了两个月时间开发了一套基于关键词匹配和规则引擎的语音指令解析系统。虽然不如专业的NLP系统智能,但在特定场景下表现还不错。

// 语音助手的指令解析系统
interface VoiceCommand {
  action: string      // 动作:开、关、调节等
  device: string      // 设备:灯、空调、电视等  
  parameter?: string  // 参数:亮度、温度等
  value?: string     // 数值:50%、26度等
}

class VoiceCommandParser {
  private actionKeywords = new Map<string, string[]>([
    ['open', ['打开', '开启', '开', '启动']],
    ['close', ['关闭', '关', '停止', '关掉']],
    ['adjust', ['调节', '设置', '调整', '改成', '调成']],
    ['increase', ['增加', '调高', '提高', '加大']],
    ['decrease', ['减少', '调低', '降低', '减小']]
  ])
  
  private deviceKeywords = new Map<string, string[]>([
    ['light', ['灯', '电灯', '照明', '灯光']],
    ['airconditioner', ['空调', '冷气', '暖气']],
    ['tv', ['电视', '电视机', 'TV']],
    ['music', ['音乐', '音响', '播放器']],
    ['curtain', ['窗帘', '遮光帘']]
  ])
  
  private parameterKeywords = new Map<string, string[]>([
    ['brightness', ['亮度', '明度']],
    ['temperature', ['温度', '度数']],
    ['volume', ['音量', '声音', '音响']]
  ])
  
  parseCommand(voiceText: string): VoiceCommand | null {
    console.info('解析语音指令:', voiceText)
    
    // 清理文本
    const cleanText = voiceText.replace(/[,。!?]/g, '').toLowerCase()
    
    // 识别动作
    const action = this.extractAction(cleanText)
    if (!action) {
      console.warn('无法识别动作')
      return null
    }
    
    // 识别设备
    const device = this.extractDevice(cleanText)
    if (!device) {
      console.warn('无法识别设备')
      return null
    }
    
    // 识别参数和数值
    const parameter = this.extractParameter(cleanText)
    const value = this.extractValue(cleanText)
    
    const command: VoiceCommand = {
      action,
      device,
      parameter,
      value
    }
    
    console.info('解析结果:', JSON.stringify(command))
    return command
  }
  
  private extractAction(text: string): string | null {
    for (const [action, keywords] of this.actionKeywords) {
      for (const keyword of keywords) {
        if (text.includes(keyword)) {
          return action
        }
      }
    }
    return null
  }
  
  private extractDevice(text: string): string | null {
    for (const [device, keywords] of this.deviceKeywords) {
      for (const keyword of keywords) {
        if (text.includes(keyword)) {
          return device
        }
      }
    }
    return null
  }
  
  private extractParameter(text: string): string | null {
    for (const [parameter, keywords] of this.parameterKeywords) {
      for (const keyword of keywords) {
        if (text.includes(keyword)) {
          return parameter
        }
      }
    }
    return null
  }
  
  private extractValue(text: string): string | null {
    // 提取数字和单位
    const numberMatch = text.match(/(\d+)([%度]?)/g)
    if (numberMatch) {
      return numberMatch[0]
    }
    
    // 提取相对值
    if (text.includes('最大') || text.includes('最高')) return 'max'
    if (text.includes('最小') || text.includes('最低')) return 'min'
    if (text.includes('一半') || text.includes('中等')) return '50%'
    
    return null
  }
}

// 语音助手主控制器
class VoiceAssistant {
  private commandParser = new VoiceCommandParser()
  private deviceController = new SmartDeviceController()
  
  async processVoiceInput(voiceText: string): Promise<string> {
    try {
      // 解析语音指令
      const command = this.commandParser.parseCommand(voiceText)
      if (!command) {
        return '抱歉,我没有理解您的指令,请重新说一遍'
      }
      
      // 执行指令
      const result = await this.executeCommand(command)
      return result ? '指令执行成功' : '指令执行失败,请检查设备状态'
      
    } catch (error) {
      console.error('处理语音指令失败:', error)
      return '指令执行过程中出现错误,请稍后重试'
    }
  }
  
  private async executeCommand(command: VoiceCommand): Promise<boolean> {
    console.info('执行指令:', JSON.stringify(command))
    
    switch (command.device) {
      case 'light':
        return await this.controlLight(command)
      case 'airconditioner': 
        return await this.controlAirConditioner(command)
      case 'tv':
        return await this.controlTV(command)
      case 'music':
        return await this.controlMusic(command)
      default:
        console.warn('不支持的设备类型:', command.device)
        return false
    }
  }
  
  private async controlLight(command: VoiceCommand): Promise<boolean> {
    switch (command.action) {
      case 'open':
        return await this.deviceController.turnOnLight()
      case 'close':
        return await this.deviceController.turnOffLight()
      case 'adjust':
        if (command.parameter === 'brightness' && command.value) {
          const brightness = this.parseValue(command.value)
          return await this.deviceController.setLightBrightness(brightness)
        }
        break
    }
    return false
  }
  
  private parseValue(value: string): number {
    if (value === 'max') return 100
    if (value === 'min') return 0
    
    const match = value.match(/(\d+)/)
    return match ? parseInt(match[1]) : 50
  }
}

虽然这个语音助手还很简陋,但当我第一次对着手机说"打开客厅的灯",灯真的亮了的时候,那种成就感让我激动得跳了起来。我觉得自己就像一个魔法师,用声音控制着周围的一切。

第四章:那些年,我为鸿蒙开发"熬的夜"

赶项目的疯狂72小时

2022年底,公司决定开发一款基于鸿蒙的企业办公应用。作为团队中唯一有鸿蒙开发经验的人,我被委以重任,负责核心功能的开发。

项目的时间非常紧张,从立项到上线只有一个月。为了赶进度,我经历了人生中最疯狂的一次熬夜——连续72小时几乎没有睡觉,只是偶尔在椅子上打个盹。

那三天三夜,我就像着了魔一样,完全沉浸在代码的世界里。困了就喝咖啡,饿了就叫外卖,累了就趴在桌子上眯一会儿。我的同事们都说我像个"编程机器人"。

最困难的是第二天晚上,我在实现文件同步功能时遇到了一个诡异的bug。文件在某些情况下会同步失败,但错误信息非常模糊。我反复调试了6个小时,才发现是编码格式的问题。解决这个bug的瞬间,我激动得大喊了一声"终于搞定了!",把旁边的同事都吓了一跳。

// 那个让我调试了6小时的文件同步功能
import fileio from '@ohos.fileio'
import distributedData from '@ohos.data.distributedKVStore'

interface FileMetadata {
  fileName: string
  filePath: string
  fileSize: number
  lastModified: number
  checksum: string
  deviceId: string
}

class DistributedFileSync {
  private kvStore: distributedData.KVStore | null = null
  private fileMetadataKey = 'file_metadata_'
  
  async syncFile(localFilePath: string): Promise<boolean> {
    try {
      // 读取文件信息
      const fileStats = fileio.statSync(localFilePath)
      const fileName = localFilePath.split('/').pop() || ''
      
      // 计算文件校验和
      const checksum = await this.calculateChecksum(localFilePath)
      
      const metadata: FileMetadata = {
        fileName,
        filePath: localFilePath,
        fileSize: fileStats.size,
        lastModified: fileStats.mtime,
        checksum,
        deviceId: await this.getCurrentDeviceId()
      }
      
      // 保存元数据到分布式存储
      const metadataKey = this.fileMetadataKey + fileName
      await this.kvStore?.put(metadataKey, JSON.stringify(metadata))
      
      // 读取文件内容并同步
      // 这里就是出bug的地方,原来是编码问题!
      const buffer = fileio.readSync(fileio.openSync(localFilePath, 0o2), new ArrayBuffer(fileStats.size))
      const base64Content = this.arrayBufferToBase64(buffer)
      
      await this.kvStore?.put(`file_content_${fileName}`, base64Content)
      
      console.info(`文件同步成功: ${fileName}`)
      return true
      
    } catch (error) {
      console.error('文件同步失败:', error)
      return false
    }
  }
  
  async downloadFile(fileName: string, targetPath: string): Promise<boolean> {
    try {
      // 获取文件内容
      const base64Content = await this.kvStore?.get(`file_content_${fileName}`) as string
      if (!base64Content) {
        console.error('文件内容不存在:', fileName)
        return false
      }
      
      // 转换为ArrayBuffer
      const buffer = this.base64ToArrayBuffer(base64Content)
      
      // 写入本地文件
      const fd = fileio.openSync(targetPath, 0o102, 0o666)  // 创建并写入
      fileio.writeSync(fd, buffer)
      fileio.closeSync(fd)
      
      console.info(`文件下载成功: ${fileName} -> ${targetPath}`)
      return true
      
    } catch (error) {
      console.error('文件下载失败:', error)
      return false
    }
  }
  
  private async calculateChecksum(filePath: string): Promise<string> {
    // 简单的校验和计算,实际项目中建议使用MD5SHA
    const buffer = fileio.readSync(fileio.openSync(filePath, 0o2), new ArrayBuffer(1024))
    let sum = 0
    const uint8Array = new Uint8Array(buffer)
    for (let i = 0; i < uint8Array.length; i++) {
      sum += uint8Array[i]
    }
    return sum.toString(16)
  }
  
  private arrayBufferToBase64(buffer: ArrayBuffer): string {
    const bytes = new Uint8Array(buffer)
    let binary = ''
    for (let i = 0; i < bytes.byteLength; i++) {
      binary += String.fromCharCode(bytes[i])
    }
    // 在浏览器环境中使用btoa,在鸿蒙中需要使用其他方法
    return this.btoa(binary)
  }
  
  private base64ToArrayBuffer(base64: string): ArrayBuffer {
    const binary = this.atob(base64)
    const buffer = new ArrayBuffer(binary.length)
    const bytes = new Uint8Array(buffer)
    for (let i = 0; i < binary.length; i++) {
      bytes[i] = binary.charCodeAt(i)
    }
    return buffer
  }
  
  // 这些编码函数的实现花了我很长时间调试
  private btoa(str: string): string {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    let result = ''
    let i = 0
    while (i < str.length) {
      const a = str.charCodeAt(i++)
      const b = i < str.length ? str.charCodeAt(i++) : 0
      const c = i < str.length ? str.charCodeAt(i++) : 0
      
      const bitmap = (a << 16) | (b << 8) | c
      result += chars.charAt((bitmap >> 18) & 63)
      result += chars.charAt((bitmap >> 12) & 63)
      result += i - 2 < str.length ? chars.charAt((bitmap >> 6) & 63) : '='
      result += i - 1 < str.length ? chars.charAt(bitmap & 63) : '='
    }
    return result
  }
  
  private atob(base64: string): string {
    // Base64解码实现
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
    let result = ''
    let i = 0
    
    base64 = base64.replace(/[^A-Za-z0-9+/]/g, '')
    
    while (i < base64.length) {
      const encoded1 = chars.indexOf(base64.charAt(i++))
      const encoded2 = chars.indexOf(base64.charAt(i++))
      const encoded3 = chars.indexOf(base64.charAt(i++))
      const encoded4 = chars.indexOf(base64.charAt(i++))
      
      const bitmap = (encoded1 << 18) | (encoded2 << 12) | (encoded3 << 6) | encoded4
      result += String.fromCharCode((bitmap >> 16) & 255)
      
      if (encoded3 !== 64) {
        result += String.fromCharCode((bitmap >> 8) & 255)
      }
      if (encoded4 !== 64) {
        result += String.fromCharCode(bitmap & 255)
      }
    }
    return result
  }
}

最终,在我和团队的共同努力下,项目如期完成了。虽然那72小时的熬夜让我疲惫不堪,但看到应用成功运行的那一刻,所有的辛苦都值了。

深夜debug的孤独与坚持

每个开发者都有过在深夜独自debug的经历。那些夜晚,办公室里只有我一个人,伴随着电脑风扇的嗡嗡声和远处偶尔传来的车声。

我记得最长的一次debug持续了整整一个通宵。问题是应用在某些设备上会随机崩溃,但日志信息非常有限。我反复分析代码,添加日志,重新编译,测试...这个循环不知道重复了多少次。

凌晨3点的时候,我已经有些恍惚了。但就在那时,我突然想到一个可能的原因——内存泄漏。我立刻检查了内存管理相关的代码,果然发现了问题所在。

那一刻,我激动得差点哭出来。不是因为解决了bug,而是因为那种坚持不懈终于得到回报的感觉。我在朋友圈发了一条消息:"凌晨3点18分,bug终于被我抓住了!坚持就是胜利!"

版本发布前的紧张时刻

每次版本发布前的那几天,都是最紧张的时刻。所有的功能都要最终测试,所有的bug都要修复,所有的文档都要完善。

我记得有一次,就在版本发布的前一天晚上,测试团队发现了一个严重的数据同步问题。如果不修复这个问题,整个版本就要延期发布。

那天晚上,我一直工作到了凌晨5点。困得眼皮都睁不开了,但我知道不能放弃。我喝了一杯又一杯的咖啡,坚持分析问题、修改代码、测试验证。

当我最终修复了这个问题,看到测试通过的绿色提示时,心中的石头终于落地了。虽然只睡了2个小时,但我还是准时出现在了第二天的发布会上。

第五章:鸿蒙开发让我"变"了

思维方式的改变

学习鸿蒙开发不仅提升了我的技术能力,更重要的是改变了我的思维方式。从单设备思维转向多设备思维,从孤立应用转向分布式系统,这种思维上的转变对我的影响很深远。

现在当我看到任何产品或服务时,都会下意识地思考:这个功能能否在多个设备间协同?用户的使用场景是否可以跨设备延续?如何让体验更加无缝?

比如在使用其他应用时,如果发现某个功能无法在手机和平板间同步,我就会觉得很别扭。我已经习惯了鸿蒙那种"一次开发,多端部署"的便利性。

生活习惯的改变

鸿蒙开发也改变了我的生活习惯。我开始更加关注智能设备的互联互通,家里的智能设备也越来越多。

我开发了一个个人用的智能家居控制应用,早上起床时自动拉开窗帘、播放新闻,晚上回家时自动开灯、调节温度。这些功能让我的生活变得更加便利和智能化。

朋友们来我家做客时,都会被这些智能化的功能惊讶到。他们说我的家就像科幻电影里的场景一样。我总是很自豪地告诉他们,这些都是用鸿蒙技术实现的。

职业规划的改变

鸿蒙开发也让我重新思考了自己的职业规划。原来我只是想做一个普通的移动应用开发者,但现在我有了更大的梦想——成为万物互联时代的技术专家。

我开始系统地学习物联网、边缘计算、人工智能等相关技术。我意识到,未来的世界将是一个万物互联的世界,而鸿蒙正是这个世界的操作系统。

2023年初,我决定离开原来的公司,加入一家专注于鸿蒙生态的初创企业。虽然工资没有涨多少,但我相信这个选择会让我在未来的技术浪潮中占据更好的位置。

社交圈的改变

因为鸿蒙开发,我的社交圈也发生了很大变化。我结识了很多志同道合的开发者,大家经常在一起讨论技术、分享经验、合作项目。

我们成立了一个鸿蒙开发者小组,每周都会组织技术分享会。在这个圈子里,我不仅学到了很多技术知识,也收获了真挚的友谊。

有一次,我们小组一起去华为松山湖基地参观,看到鸿蒙团队的工作环境和技术展示,那种震撼是难以言喻的。我们都被鸿蒙的技术愿景深深感动,更加坚定了深耕这个领域的决心。

第六章:我带"新人"玩鸿蒙开发的故事

第一次当"师傅"的经历

2023年春天,公司来了一个刚毕业的实习生小王。他对鸿蒙开发很感兴趣,但完全没有相关经验。作为团队里的鸿蒙"专家",我被指派负责带他入门。

第一次当师傅,我既兴奋又紧张。我精心准备了一套学习计划,从基础概念开始,循序渐进地介绍鸿蒙开发的各个方面。

小王是个很聪明的孩子,学习能力很强。但他也提出了很多我从未想过的问题。比如:"为什么鸿蒙要用ArkTS而不是直接用TypeScript?""分布式软总线的底层实现原理是什么?"

这些问题让我重新思考了很多基础概念。为了给他准确的答案,我重新研读了很多技术文档,甚至去看了一些源码。这个过程不仅帮助了他,也让我的理解更加深入。

新手的"可爱"问题

教新人的过程中,我遇到了很多有趣的问题。小王刚开始学习时,经常问一些让我哭笑不得的问题。

比如,他问我:"老师,为什么我的应用在模拟器上运行正常,但在真机上就崩溃了?"我检查了一下,发现他在代码中写了一个死循环,而模拟器的性能比较强,能够暂时支撑,但真机很快就内存溢出了。

还有一次,他花了整整一个下午调试一个布局问题,最后发现是因为他把宽度单位写成了"px"而不是"vp"。看着他恍然大悟的表情,我想起了自己刚学习时的种种囧事。

见证成长的欣慰

最让我欣慰的是看到小王一点点成长。从刚开始连最基本的组件都不会用,到后来能够独立完成复杂的功能模块,这个过程大概用了三个月。

他的第一个独立项目是一个简单的天气应用。虽然功能很基础,但他用了很多鸿蒙的特色功能:卡片服务、通知推送、多设备同步等。当他在演示会上展示这个应用时,我真的为他感到骄傲。

// 小王的第一个独立项目 - 天气应用的核心代码
@Entry
@Component
struct WeatherMainPage {
  @State currentWeather: WeatherInfo = new WeatherInfo()
  @State forecast: WeatherInfo[] = []
  @State isLoading: boolean = false
  @State cityName: string = '北京'
  
  private weatherService = new WeatherService()
  private locationManager = new LocationManager()
  
  aboutToAppear() {
    this.getCurrentLocation()
  }
  
  build() {
    Column() {
      // 标题栏
      Row() {
        Text('天气')
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
        
        Blank()
        
        Button('刷新')
          .onClick(() => this.refreshWeather())
      }
      .width('100%')
      .padding(16)
      
      // 加载状态
      if (this.isLoading) {
        Row() {
          LoadingProgress()
            .width(30)
            .height(30)
          Text('加载中...')
            .margin({ left: 12 })
        }
        .justifyContent(FlexAlign.Center)
        .height(100)
      } else {
        // 当前天气
        this.buildCurrentWeather()
        
        // 天气预报
        this.buildForecast()
      }
      
      Blank()
      
      // 底部操作按钮
      Row({ space: 12 }) {
        Button('添加卡片')
          .onClick(() => this.addWeatherCard())
        
        Button('设置提醒')
          .onClick(() => this.setWeatherAlert())
      }
      .padding(16)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F0F0F0')
  }
  
  @Builder buildCurrentWeather() {
    Column({ space: 8 }) {
      Text(this.cityName)
        .fontSize(16)
        .fontColor(Color.Gray)
      
      Text(`${this.currentWeather.temperature}°`)
        .fontSize(48)
        .fontWeight(FontWeight.Bold)
      
      Text(this.currentWeather.description)
        .fontSize(18)
        .fontColor(Color.Gray)
      
      Row({ space: 20 }) {
        Text(`湿度 ${this.currentWeather.humidity}%`)
        Text(`风速 ${this.currentWeather.windSpeed} km/h`)
      }
      .fontSize(14)
      .fontColor(Color.Gray)
    }
    .width('100%')
    .padding(20)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .margin(16)
  }
  
  @Builder buildForecast() {
    Column() {
      Text('7天预报')
        .fontSize(16)
        .fontWeight(FontWeight.Bold)
        .alignSelf(ItemAlign.Start)
        .margin({ bottom: 8 })
      
      List() {
        ForEach(this.forecast, (weather: WeatherInfo, index: number) => {
          ListItem() {
            Row() {
              Text(this.formatDate(weather.date))
                .layoutWeight(1)
              
              Image(this.getWeatherIcon(weather.type))
                .width(24)
                .height(24)
              
              Text(`${weather.minTemp}°/${weather.maxTemp}°`)
                .width(80)
                .textAlign(TextAlign.End)
            }
            .width('100%')
            .padding(12)
          }
        })
      }
      .width('100%')
    }
    .width('100%')
    .padding(16)
    .backgroundColor(Color.White)
    .borderRadius(12)
    .margin({ left: 16, right: 16, bottom: 16 })
  }
  
  private async getCurrentLocation() {
    try {
      this.isLoading = true
      const location = await this.locationManager.getCurrentLocation()
      this.cityName = location.cityName
      await this.loadWeatherData()
    } catch (error) {
      console.error('获取位置失败:', error)
      this.showToast('获取位置失败,使用默认城市')
      await this.loadWeatherData()
    } finally {
      this.isLoading = false
    }
  }
  
  private async loadWeatherData() {
    try {
      const current = await this.weatherService.getCurrentWeather(this.cityName)
      const forecast = await this.weatherService.getForecast(this.cityName)
      
      this.currentWeather = current
      this.forecast = forecast
    } catch (error) {
      console.error('获取天气数据失败:', error)
      this.showToast('获取天气数据失败')
    }
  }
  
  private async refreshWeather() {
    await this.loadWeatherData()
    this.showToast('天气数据已更新')
  }
  
  private async addWeatherCard() {
    // 添加桌面卡片的实现
    try {
      await this.weatherService.addWeatherCard(this.currentWeather)
      this.showToast('天气卡片添加成功')
    } catch (error) {
      console.error('添加卡片失败:', error)
      this.showToast('添加卡片失败')
    }
  }
  
  private formatDate(timestamp: number): string {
    const date = new Date(timestamp)
    const today = new Date()
    const diffDays = Math.floor((timestamp - today.getTime()) / (1000 * 60 * 60 * 24))
    
    if (diffDays === 0) return '今天'
    if (diffDays === 1) return '明天'
    if (diffDays === 2) return '后天'
    
    return `${date.getMonth() + 1}/${date.getDate()}`
  }
}

// 小王特别用心设计的天气信息类
class WeatherInfo {
  temperature: number = 0
  description: string = ''
  humidity: number = 0
  windSpeed: number = 0
  type: string = ''
  date: number = Date.now()
  minTemp: number = 0
  maxTemp: number = 0
}

看到他的代码越写越规范,功能越做越完善,我真的很有成就感。这让我想起了一句话:"教学相长"。在教他的过程中,我也收获了很多。

传承的意义

通过带新人的经历,我深刻理解了技术传承的意义。每一个技术专家都是从新手开始的,我们有责任帮助后来者更快地成长。

现在小王已经成为了团队的正式员工,他也开始带新的实习生了。看到这种传承在延续,我感到非常欣慰。

我们团队还建立了一个"鸿蒙新手训练营",定期为新员工提供培训。这个训练营已经培养了十几位鸿蒙开发者,他们现在都在各自的岗位上发光发热。

第七章:给鸿蒙开发写一封"信"

亲爱的鸿蒙:

时光荏苒,我们相遇已经快三年了。今天想给你写封信,说说我们之间的故事,说说你带给我的改变和成长。

还记得我们初次相遇时的青涩吗?那时的我只是一个普通的Android开发者,对你充满了好奇和疑惑。你那全新的概念、独特的架构、前沿的理念,都让我感到既兴奋又忐忑。

刚开始学习你的时候,我承认,我有过抱怨。为什么要用ArkTS?为什么界面写法完全不同?为什么要学这么多新概念?那些深夜里啃文档的时光,我不止一次想要放弃。

但是你很包容,很耐心。每当我遇到问题时,你的社区里总有热心的开发者为我解答;每当我感到困惑时,你的文档和示例总能指引我方向。渐渐地,我开始理解你的设计哲学,开始欣赏你的技术美学。

你知道吗?当我第一次实现跨设备迁移功能时,那种震撼至今难忘。音乐从手机无缝切换到平板继续播放,就像魔法一样!那一刻我真正理解了什么叫"万物互联",什么叫"分布式"。

你教会了我很多东西。不仅仅是技术技能,更是一种全新的思维方式。从孤立的应用思维转向生态化的系统思维,从单设备体验转向多设备协同体验。你让我看到了未来科技发展的方向。

当然,我们之间也不是没有摩擦。有时候你的文档不够完善,让我找不到想要的信息;有时候你的工具有bug,让我的开发进度受阻;有时候你的生态还不够丰富,让我找不到合适的第三方库。

但这些小问题并不能掩盖你的光芒。我看到你在不断成长,不断完善。从鸿蒙2.0到3.0再到4.0,每一个版本都有显著的提升。你的开发工具越来越好用,你的文档越来越完善,你的生态越来越丰富。

更让我感动的是,你不仅改变了我的技术能力,还改变了我的生活方式。我的家变得更智能了,我的工作变得更高效了,我的思维变得更开阔了。你让我看到了技术改变生活的可能性。

现在我已经成为了你的忠实拥趸。我在公司推广你的技术,我在社区分享使用你的经验,我向朋友们介绍你的优势。看到越来越多的开发者加入你的生态,我由衷地感到高兴。

对于未来,我对你充满了期待。我希望你能继续保持创新精神,在人工智能、边缘计算、万物互联等领域持续发力。我希望你的生态能够更加繁荣,吸引更多优秀的开发者和企业加入。我希望你能真正成为万物互联时代的操作系统。

我也会继续与你同行。无论你走到哪里,无论遇到什么困难,我都会支持你、陪伴你。因为我相信,你代表着技术发展的未来方向。

最后,想对你说一声谢谢。谢谢你让我成为了更好的开发者,谢谢你让我看到了更广阔的技术世界,谢谢你给了我实现梦想的舞台。

愿我们的友谊地久天长,愿你的未来更加辉煌!

此致
敬礼!

你的忠实伙伴
2024年某个深夜

第八章:鸿蒙开发教会我的那些事

耐心——面对bug不急躁

鸿蒙开发最大的收获之一就是学会了耐心。刚开始做Android开发时,遇到bug我总是很急躁,恨不得立刻解决。但鸿蒙开发教会了我,有些问题需要时间,需要反复思考和试验。

我记得有一次,为了解决一个分布式数据同步的问题,我连续调试了三天。每次以为找到了原因,但验证后发现还是不对。那种挫败感让我几乎要放弃。

但最终我还是坚持了下来。我学会了系统性地分析问题,学会了从多个角度思考原因,学会了耐心地验证每一个假设。当最终解决问题时,那种成就感让我明白:耐心是开发者最重要的品质之一。

分享——在社区里交流经验收获颇丰

鸿蒙开发也让我学会了分享。刚开始时,我总是习惯于一个人默默研究,遇到问题也不愿意求助。但在鸿蒙社区里,我发现分享和交流能带来意想不到的收获。

第一次在社区分享技术文章时,我很紧张,担心自己的理解不够深入,担心会被专家批评。但没想到收到了很多正面的反馈,甚至有几位资深开发者主动联系我,与我深入讨论技术问题。

通过这些交流,我不仅学到了新知识,还结识了很多志同道合的朋友。我开始意识到,知识只有分享出去才能发挥最大的价值。

创新——用新思路解决开发难题

鸿蒙的分布式理念也培养了我的创新思维。在传统的移动开发中,我们总是从单个应用的角度思考问题。但鸿蒙让我学会了从系统的角度、从生态的角度思考问题。

比如在做那个智能音乐播放器时,我没有简单地复制传统的播放器功能,而是思考如何利用鸿蒙的特色能力创造全新的体验。跨设备迁移、多设备协同播放、智能推荐等功能,都是基于这种创新思维产生的。

系统思维——从全局角度看问题

最重要的是,鸿蒙开发培养了我的系统思维。我开始学会从全局的角度看问题,不再局限于某个具体的技术点或功能模块。

在设计应用架构时,我会考虑如何与其他应用协同,如何利用系统提供的能力,如何为用户创造一体化的体验。这种思维方式不仅在技术工作中有用,在生活中也让我受益良多。

第九章:那些藏在鸿蒙开发里的"小确幸"

代码编译成功的瞬间

每个开发者都有过这样的经历:修改了一大段代码后,忐忑不安地点击编译按钮,然后屏住呼吸等待结果。当看到"Build Successful"的提示时,那种如释重负的感觉真的太棒了。

在鸿蒙开发中,这种感觉更加强烈。因为鸿蒙的编译过程相对复杂,涉及到ArkTS编译、资源打包、签名等多个步骤。每次成功编译都像是闯过了一关。

第一次在真机上看到效果

虽然模拟器很方便,但真机的体验是不可替代的。我永远记得第一次把自己开发的鸿蒙应用安装到真机上时的激动心情。

看着自己写的代码在真实的设备上运行,响应用户的每一次点击,那种成就感是无法言喻的。特别是当朋友们试用我的应用,给出正面反馈时,那种满足感让我觉得所有的努力都值了。

功能突然跑通的惊喜

还有一种小确幸是调试了很久的功能突然跑通的瞬间。可能是改了一个小小的参数,可能是修正了一个拼写错误,功能就神奇地工作了。

我记得调试分布式数据同步功能时,折腾了整整一周都没有成功。就在我准备放弃的时候,突然发现是权限配置的问题。修改了一行配置代码后,两台设备间的数据开始正常同步了。那一刻,我高兴得跳了起来!

收到用户积极反馈的温暖

虽然大部分时候我们开发的应用只有少数人使用,但偶尔收到用户的积极反馈时,那种温暖的感觉真的很棒。

有一次,我开发的智能待办应用帮助一个朋友提高了工作效率。他专门给我发了一个长长的感谢微信,详细描述了这个应用如何改善了他的生活。读完那条消息,我感动得差点流泪。

发现开发小技巧的兴奋

在开发过程中,偶尔会发现一些提高效率的小技巧。比如某个快捷键的组合,某个工具的隐藏功能,某个代码片段的巧妙用法。

这些发现虽然小,但能显著提高开发效率。每次发现这样的技巧时,我都会立刻记录下来,并分享给团队的其他成员。看到大家因为我的分享而提高效率时,我也感到很开心。

和同行交流的收获

参加技术会议或者在社区里和其他开发者交流时,经常会有意外的收获。可能是学到了一个新的解决方案,可能是了解了一个新的工具,可能是对某个概念有了更深的理解。

这些交流让我意识到,技术的世界很大,总有值得学习的地方。每次这样的交流都让我充满学习的动力。

结语:感恩这段美好的旅程

写到这里,这封给鸿蒙开发的"独家记忆"信就要结束了。回顾这三年的学习和开发历程,我深深感慨于时间的飞逝和成长的可贵。

从一个Android开发者转型为鸿蒙开发者,从对分布式概念的一无所知到能够熟练运用各种鸿蒙特色功能,从独自摸索到成为团队的技术骨干,这个过程虽然充满挑战,但也充满了收获和成长。

鸿蒙开发不仅仅是一项技术技能,更是一扇通往未来的大门。它让我看到了万物互联的美好前景,让我体验到了技术改变生活的神奇力量,让我在职业发展的道路上找到了新的方向。

更重要的是,鸿蒙开发让我结识了一群志同道合的朋友,让我体验到了技术社区的温暖和力量,让我学会了分享和传承。这些收获远比技术本身更加宝贵。

如今,鸿蒙生态正在蓬勃发展,越来越多的设备、越来越多的应用、越来越多的开发者加入其中。作为这个生态的早期参与者和见证者,我为能够参与这个历史进程而感到自豪。

对于那些还在犹豫是否要学习鸿蒙开发的朋友们,我想说:这绝对是一个值得的选择。虽然学习过程可能会有困难,但收获一定会超出你的预期。鸿蒙不仅仅是一个操作系统,更是一个充满可能性的未来世界。

愿每一个怀着梦想的开发者都能在鸿蒙的世界里找到属于自己的位置,愿每一个努力学习的新手都能在这个生态中实现自己的价值,愿我们都能成为万物互联时代的建设者和见证者。

这就是我和鸿蒙开发的独家记忆,这段旅程还在继续,故事也远未结束。让我们一起期待更加精彩的未来!


写于2025年初秋,献给所有热爱鸿蒙开发的朋友们

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐