HarmonyOS鸿蒙Next中CSDN17年老兵之我与华为的十年之约【鸿蒙极客新势力申请】

HarmonyOS鸿蒙Next中CSDN17年老兵之我与华为的十年之约【鸿蒙极客新势力申请】 #我要成为鸿蒙极客#

CSDN17年老兵之我与华为的十年之约【鸿蒙极客新势力申请】

1. 概述

首先,欢迎大家百无聊赖的打开这篇帖子。我要首先申明,我不是标题党。这篇帖子的缘由,本身是为了加入鸿蒙极客为初衷,顺便和大家分享分享我的一些新路历程和牢骚。关于标题的内容呢,我接下来会为大家一一讲解。

2. 个人经历

  • 缘起:那年,拿着JAVA的我,满心欢喜的对着初恋说,我以后要走这条路。没想到,那时的我还没精通JAVA,确投入了C的怀抱。多年后,早已对JAVA熟悉的我,确看着那个熟悉的QQ头像,不敢点开。是的,这就是我的起点,一个桌面端开发者。也和很多人一样,心中有一个爱而不得的初恋。经历过C和汇编的洗礼以后,慢慢沉浸在C#的开发中。其中,也在github上开源了几个项目,或许项目受众太小,也没什么人关注。最多Star的一个也才21个。
  • 图片
  • 缘中:由于工作关系,也为了那个每个男孩子的电子梦。接触了电路,电子。也使得我的工作点开始转移到嵌入式开发。接触过嵌入式的人都知道,每天的工作都是枯燥、乏味的。所以每天下班都喜欢倒腾新的玩意,这个时候起,就开始了安卓的开发和小程序的开发。由于都是公司应用和未上架的私下流通应用。这里就不放出贴图了。现在的很多人都很喜欢说自己开发了多少多少IOS应用,也有人问我:你会不会开发IOS啊。每到此时,我都会回答,IOS我一点都不会,也不想去了解。或许,自己心里一直有一个芥蒂,IOS比较一直都是国外的,所以一直不愿意去接触他。
  • 十年之约:某天,百无聊赖的刷着新闻的时候,突然出现的一句话:国产编程语言,仓颉。那一刻,我心里仿佛有什么东西被触动了。就是想学仓颉,想用仓颉。可是,现实,你们懂的,不管多么期待,这一等,就等了很多年。这期间,没事就登录华为看看,不知不觉,热爱IDC的我,在华为的服务器,也达成了一群IDC佬喜欢说的十年之约。
  • 17年老兵:经过时间的洗礼,岁月的磨砺。打开自己的CSDN一看,原来,我已经在CSDN待了17年,图片这期间还有很多很多曾经喜欢的论坛,讨论过,开源过,吵架过的论坛也在时间的洗礼中,逐一走向了关闭的命运。而自己,也因为不爱惜自己,在春节后的某一天,也住进了糖尿病科室的病床。看着手机,想想自己。也想为自己或者身边的病友做点什么。经过讨论,糖人助手。这个APP概念就开始萌生了。

3. 前期准备

  • 看着全家都用的华为,和新升级的鸿蒙纯血系统。这一次,别和我谈什么IOS,什么小程序。拿出我的十年之约的华为服务器就是干,我就是要全部用华为来干。
  • 开发语言: ArkTS
  • UI 框架: ArkUI
  • 目标平台: HarmonyOS 5

4. 过程中遇到的问题(新手向)

  • 前期准备工作一定要考虑好应用的方向和特点,不然现在的审核状态,你很难通过审核的。
  • 多看开发文档,多看开发文档,多看开发文档,重要的事情说三遍。我来给你举个活生生的例子,下面是我最初的华为账号登录代码:
import { authentication, loginComponentManager } from '[@kit](/user/kit).AccountKit';

@Entry
@Component
struct NewLoginPage {
  private loginController: loginComponentManager.LoginWithHuaweiIDButtonController =
    new loginComponentManager.LoginWithHuaweiIDButtonController();

  aboutToAppear(): void {
    // 配置华为登录控制器
    this.loginController.onClickLoginWithHuaweiIDButton = () => {
      this.startHuaweiLogin();
    };

    // 页面加载时自动检测华为账号
    this.tryHuaweiSilentSignIn();
  }

  private async tryHuaweiSilentSignIn(): Promise<void> {
    try {
      // 获取当前系统中已登录的华为账号
      const accountInfo = await HuaweiAccountService.getCurrentHuaweiAccount();
      if (accountInfo) {
        this.huaweiAccount = this.maskAccount(accountInfo.account);
      }
    } catch (error) {
      console.info('华为账号检测:' + JSON.stringify(error));
    }
  }

  // 账号脱敏处理
  private maskAccount(account: string): string {
    if (account.length <= 5) return account;
    const prefix: string = account.substring(0, 3);
    const suffix: string = account.substring(account.length - 2);
    const maskLength: number = account.length - 5;
    let mask: string = '';
    for (let i = 0; i < maskLength; i++) { mask += '米'; }
    return prefix + mask + suffix;
  }

  build() {
    Column() {
      // 华为官方登录按钮组件
      LoginWithHuaweiIDButton({
        params: {
          style: loginComponentManager.Style.BUTTON_RED,
          borderRadius: 24,
          loginType: loginComponentManager.LoginType.QUICK_LOGIN
        },
        controller: this.loginController
      })
      .width('100%').height(48)
    }
  }
}

结果你猜怎么找?我只看了登录实现,没看这个权限我能不能用,个人开发者不能这么一键登录,我瞬间,啊???? 还好,华为是为大家考虑的,还准备了其他方案,下面是我后面的华为登录方案实现:

  import { authentication } from '[@kit](/user/kit).AccountKit';

  // 使用 authentication.HuaweiIDProvider 和 AuthenticationController
  const authRequest: authentication.HuaweiIDProvider = new authentication.HuaweiIDProvider();
  const request: authentication.AuthorizationWithHuaweiIDRequest =
    authRequest.createAuthorizationWithHuaweiIDRequest();

  request.scopes = ['openid', 'profile'];
  request.permissions = ['idtoken'];

  const controller: authentication.AuthenticationController =
    new authentication.AuthenticationController(getContext(this));

  controller.executeRequest(request).then((response) => {
    // 获取 authorizationCode, openID, unionID, idToken
  });

诸如此类事件,比比皆是,所以,大家多看清楚一点文档,绝对不是坏事。

  • 还有一点,如果你有条件的话,例如你有什么交流群之类的话,多和前辈交流,沟通,询问,如果你真的是一个好学的人的话,很多人,其实都会毫不吝啬的告诉你的,现在交流的生态圈,大家都还是很融洽的。
  • ArkTS 对代码有严格的类型检查要求,以下是给大家踩过的坑:

4.1. 类型注解规范

// ❌ 错误:隐式类型
const count = 5;
let items = [];

// ✅ 正确:显式类型注解
const count: number = 5;
let items: FoodItem[] = [];

4.2. 禁止使用 any 类型

// ❌ 错误
const deletePromises: Promise<any>[] = [];
Promise.all(promises).then((res: any) => {});

// ✅ 正确:定义具体接口类型
interface RemoveFavoriteResponse {
  success: boolean;
  message?: string;
  data?: FoodFavoriteItem;
}

const deletePromises: Promise<RemoveFavoriteResponse>[] = [];
Promise.all(promises).then((responses: RemoveFavoriteResponse[]): void => {
  // 处理响应
});

4.3. 禁止使用解构语法

// ❌ 错误
const [source, idStr] = key.split(':');
const { name, age } = user;

// ✅ 正确
const parts: string[] = key.split(':');
const source: string = parts[0];
const idStr: string = parts[1];

const name: string = user.name;
const age: number = user.age;

4.4. 循环语法规范

// ❌ 避免使用 forEach 和 for...of
items.forEach(item => { /* ... */ });
for (const item of items) { /* ... */ }

// ✅ 推荐使用传统 for 循环
for (let i = 0; i < items.length; i++) {
  const item: ItemType = items[i];
  // 处理逻辑
}

4.5. Promise 回调类型

// ❌ 错误:缺少类型注解
.then((res) => {})
.catch((err) => {})

// ✅ 正确:显式声明参数和返回类型
.then((responses: ApiResponse[]): void => {
  // 处理逻辑
})
.catch((error: Error | string): void => {
  const message: string = this.extractErrorMessage(error);
});

5. 个人在应用中使用的代码分享,希望对各位有帮助。

5.1.ArkData Kit (@kit.ArkData) - 数据持久化

使用 Preferences 存储用户配置、主题设置等轻量级数据。

import { preferences } from '[@kit](/user/kit).ArkData';

class ThemeService {
  private static readonly PREFERENCES_NAME: string = 'theme_preferences';
  private static readonly KEY_THEME_MODE: string = 'theme_mode';

  static async saveThemeMode(context: Context, mode: ThemeMode): Promise<void> {
    const prefs: preferences.Preferences = await preferences.getPreferences(
      context,
      ThemeService.PREFERENCES_NAME
    );
    await prefs.put(ThemeService.KEY_THEME_MODE, mode);
    await prefs.flush();
  }

  static async getThemeMode(context: Context): Promise<ThemeMode> {
    const prefs: preferences.Preferences = await preferences.getPreferences(
      context,
      ThemeService.PREFERENCES_NAME
    );
    const mode: string = await prefs.get(
      ThemeService.KEY_THEME_MODE,
      'system'
    ) as string;
    return mode as ThemeMode;
  }
}

5.2. NetworkKit (@kit.NetworkKit) - 网络通信

封装统一的 HTTP 请求服务,支持自动故障转移。

import { http } from '[@kit](/user/kit).NetworkKit';

class DatabaseService {
  private static readonly BASE_URL: string = 'http://129.204.27.11:1516';

  static async request<T>(
    endpoint: string,
    method: http.RequestMethod,
    data?: object
  ): Promise<ApiResponse<T>> {
    const httpRequest: http.HttpRequest = http.createHttp();

    try {
      const response: http.HttpResponse = await httpRequest.request(
        `${this.BASE_URL}${endpoint}`,
        {
          method: method,
          header: {
            'Content-Type': 'application/json'
          },
          extraData: data ? JSON.stringify(data) : undefined,
          connectTimeout: 10000,
          readTimeout: 30000
        }
      );

      if (response.responseCode === 200) {
        const result: string = response.result as string;
        return JSON.parse(result) as ApiResponse<T>;
      } else {
        return { success: false, message: `HTTP ${response.responseCode}` };
      }
    } finally {
      httpRequest.destroy();
    }
  }
}

5.3. CoreFileKit (@kit.CoreFileKit) - 文件操作

用于图片处理、数据备份恢复等场景。

import { fileIo, fileUri } from '[@kit](/user/kit).CoreFileKit';

class BackupService {
  static async exportData(context: Context, data: string): Promise<string> {
    const fileName: string = `backup_${Date.now()}.json`;
    const filePath: string = `${context.filesDir}/${fileName}`;

    const file: fileIo.File = await fileIo.open(
      filePath,
      fileIo.OpenMode.CREATE | fileIo.OpenMode.WRITE_ONLY
    );

    await fileIo.write(file.fd, data);
    await fileIo.close(file);

    return fileUri.getUriFromPath(filePath);
  }
}

5.4. 图片选择与处理

import { photoAccessHelper } from '[@kit](/user/kit).MediaLibraryKit';
import { image } from '[@kit](/user/kit).ImageKit';

class ImagePickerService {
  static async selectAndProcessImage(context: Context): Promise<string> {
    // 创建图片选择器
    const photoPicker: photoAccessHelper.PhotoViewPicker =
      new photoAccessHelper.PhotoViewPicker();

    const result: photoAccessHelper.PhotoSelectResult = await photoPicker.select({
      MIMEType: photoAccessHelper.PhotoViewMIMETypes.IMAGE_TYPE,
      maxSelectNumber: 1
    });

    if (result.photoUris.length === 0) {
      throw new Error('未选择图片');
    }

    const uri: string = result.photoUris[0];

    // 读取并压缩图片
    const imageSource: image.ImageSource = image.createImageSource(uri);
    const pixelMap: image.PixelMap = await imageSource.createPixelMap();

    // 转换为 Base64
    const packer: image.ImagePacker = image.createImagePacker();
    const buffer: ArrayBuffer = await packer.packing(pixelMap, {
      format: 'image/jpeg',
      quality: 80
    });

    return ArrayBufferToBase64(buffer);
  }
}

5.5. 主题服务实现

// ThemeService.ets
import { preferences } from '[@kit](/user/kit).ArkData';

type ThemeChangeCallback = (colors: ThemeColors) => void;

class ThemeService {
  private static instance: ThemeService | null = null;
  private currentMode: ThemeMode = 'system';
  private listeners: ThemeChangeCallback[] = [];

  static getInstance(): ThemeService {
    if (!ThemeService.instance) {
      ThemeService.instance = new ThemeService();
    }
    return ThemeService.instance;
  }

  async initialize(context: Context): Promise<void> {
    const prefs: preferences.Preferences = await preferences.getPreferences(
      context, 'theme_prefs'
    );
    this.currentMode = await prefs.get('mode', 'system') as ThemeMode;
  }

  getCurrentColors(): ThemeColors {
    switch (this.currentMode) {
      case 'light':
        return LIGHT_THEME_COLORS;
      case 'dark':
        return DARK_THEME_COLORS;
      case 'system':
        return this.getSystemThemeColors();
    }
  }

  private getSystemThemeColors(): ThemeColors {
    // 获取系统当前主题设置
    const isDark: boolean = this.isSystemDarkMode();
    return isDark ? DARK_THEME_COLORS : LIGHT_THEME_COLORS;
  }

  async setThemeMode(context: Context, mode: ThemeMode): Promise<void> {
    this.currentMode = mode;

    // 持久化
    const prefs: preferences.Preferences = await preferences.getPreferences(
      context, 'theme_prefs'
    );
    await prefs.put('mode', mode);
    await prefs.flush();

    // 通知所有监听者
    const colors: ThemeColors = this.getCurrentColors();
    for (let i = 0; i < this.listeners.length; i++) {
      this.listeners[i](colors);
    }
  }

  addListener(callback: ThemeChangeCallback): void {
    this.listeners.push(callback);
  }

  removeListener(callback: ThemeChangeCallback): void {
    const index: number = this.listeners.indexOf(callback);
    if (index > -1) {
      this.listeners.splice(index, 1);
    }
  }
}

5.6. 页面中应用主题

@Entry
@Component
struct SettingsPage {
  @State themeColors: ThemeColors = LIGHT_THEME_COLORS;
  @State currentThemeMode: ThemeMode = 'system';

  private themeService: ThemeService = ThemeService.getInstance();
  private themeCallback: (colors: ThemeColors) => void =
    (colors: ThemeColors) => { this.themeColors = colors; };

  aboutToAppear(): void {
    this.themeColors = this.themeService.getCurrentColors();
    this.themeService.addListener(this.themeCallback);
  }

  aboutToDisappear(): void {
    this.themeService.removeListener(this.themeCallback);
  }

  build() {
    Column() {
      // 主题选择
      Row() {
        Text('主题模式').fontSize(16)
        Blank()
        Select([
          { value: '跟随系统' },
          { value: '浅色模式' },
          { value: '深色模式' }
        ])
        .selected(this.getThemeIndex())
        .onSelect((index: number) => this.onThemeSelect(index))
      }
      .padding(16)
      .backgroundColor(this.themeColors.surface)
    }
    .backgroundColor(this.themeColors.background)
  }

  private onThemeSelect(index: number): void {
    const modes: ThemeMode[] = ['system', 'light', 'dark'];
    this.themeService.setThemeMode(getContext(this), modes[index]);
  }
}

5.7. 收藏数据管理

interface FoodFavoriteItem {
  id: number;
  user_id: number;
  food_id: number;
  food_source: FoodSource;
  food_name: string;
  created_at: string;
  // 关联的食物详情
  food_detail?: FoodDetail;
}

type FoodSource = 'food_database' | 'shiwuyingyang';

class FoodFavoriteService {
  // 添加收藏
  static async addFavorite(
    userId: number,

更多关于HarmonyOS鸿蒙Next中CSDN17年老兵之我与华为的十年之约【鸿蒙极客新势力申请】的实战教程也可以访问 https://www.itying.com/category-93-b0.html

4 回复

可以,老兵,我也是CSDN老兵。

更多关于HarmonyOS鸿蒙Next中CSDN17年老兵之我与华为的十年之约【鸿蒙极客新势力申请】的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


老兵好,

HarmonyOS鸿蒙Next是华为推出的新一代操作系统,旨在构建全场景智慧生态。CSDN老兵与华为的十年之约体现了开发者对鸿蒙生态的长期关注与支持。鸿蒙Next采用分布式架构和ArkTS语言,支持一次开发、多端部署,提升开发效率。该申请属于鸿蒙极客新势力计划,旨在汇聚开发者力量,共同推动鸿蒙生态发展。

感谢您分享如此详尽的HarmonyOS Next开发经历和宝贵经验。作为一名拥有17年开发经验的资深技术人,您从桌面端到嵌入式,再到鸿蒙原生应用开发的转型之路,以及对国产技术的执着,令人敬佩。

您分享的“糖人助手”开发实践和代码片段,为HarmonyOS Next的开发者提供了极具价值的参考。特别是以下几点,对社区开发者很有帮助:

  1. 权限与API适配:您提到的LoginWithHuaweiIDButtonauthentication.HuaweiIDProvider的区别,是典型的开发初期易踩的坑。这提醒所有开发者,在调用Kit API前,务必在官方文档中确认其使用条件和权限要求,尤其是对于个人开发者账号。

  2. ArkTS编码规范:您总结的类型注解、禁用any、循环语法等规范,正是HarmonyOS Next应用通过方舟编译器进行静态检查和高性能运行的关键。遵循这些规范能有效避免运行时错误,并优化应用性能。

  3. Kit的工程化使用:您对@kit.ArkData(数据持久化)、@kit.NetworkKit(网络通信)、@kit.CoreFileKit(文件操作)等核心Kit的封装示例,展示了如何构建可维护、可测试的服务层。特别是主题服务的实现,清晰地演示了如何利用Preferences进行状态管理和跨页面同步。

  4. 问题排查思路:您“多看开发文档”的建议和与社区交流的经验,是高效解决问题的核心路径。HarmonyOS开发者社区和官方论坛是获取支持、交流心得的重要平台。

您将个人健康管理的需求转化为开发动力,并成功上架应用,是“技术服务于人”的完美体现。您分享的不仅是代码,更是一位老兵对技术生态的贡献和热忱。

期待您未来更多的技术分享。祝“糖人助手”项目持续迭代,帮助更多用户,也祝您在鸿蒙生态中继续开拓,收获更多成果。

回到顶部