鸿蒙 Flutter 安全组件开发:加密输入框与脱敏展示组件

一、引言

在鸿蒙(HarmonyOS)应用开发中,用户敏感信息(如密码、手机号、身份证号)的安全防护是核心需求之一。基于 Flutter 跨平台框架开发鸿蒙应用时,原生组件往往无法直接满足 "输入加密" 和 "展示脱敏" 的安全诉求 ------ 例如普通输入框会明文存储输入内容,直接展示手机号会导致信息泄露。

本文将手把手教你开发两个高频安全组件:加密输入框(SecureInputField) 和 脱敏展示组件(DesensitizeText),覆盖 "输入 - 存储 - 展示" 全链路的敏感信息保护。组件将适配鸿蒙系统特性(如方舟编译器优化、密钥安全存储),并提供完整可复用的代码,文末附 GitHub 完整项目链接。

1.1 前置知识与参考文档

二、开发环境准备

在开始编码前,需确保环境满足以下要求,避免兼容性问题:

工具 / 依赖 版本要求 下载 / 配置链接
DevEco Studio 4.0 及以上 DevEco Studio 下载页
Flutter SDK 3.16 及以上(鸿蒙适配版) 鸿蒙 Flutter SDK 获取指南
加密依赖包 encrypt: ^5.0.1 pub.dev: encrypt 包
鸿蒙系统 API 版本 API 9 及以上 鸿蒙 API 9 特性说明

2.1 依赖配置

在 pubspec.yaml 中添加核心依赖,执行 flutter pub get 安装:

yaml

复制代码
dependencies:
  flutter:
    sdk: flutter
  encrypt: ^5.0.1  # 提供AES/RSA等加密算法
  ohos_secure_storage: ^1.0.0  # 鸿蒙密钥安全存储(替代原生shared_preferences)
  flutter_harmony_os: ^1.2.0  # 鸿蒙Flutter基础能力扩展

dev_dependencies:
  flutter_test:
    sdk: flutter
  flutter_lints: ^2.0.0  # 代码规范检查

三、核心组件一:加密输入框(SecureInputField)

3.1 组件需求分析

加密输入框需满足以下安全特性:

  1. 输入实时加密:用户输入的内容不存储明文,实时通过 AES 加密为密文;
  2. 密码可见切换:支持 "显示 / 隐藏" 密码(鸿蒙设计规范风格);
  3. 输入验证:支持自定义校验规则(如密码长度≥8 位、包含大小写字母);
  4. 防内存泄露:输入完成后清空明文缓存,仅保留密文;
  5. 鸿蒙适配:调用鸿蒙安全键盘,避免第三方键盘监听输入。

3.2 核心原理

  • 加密算法:采用 AES-256-CBC 模式(对称加密,适合短文本如密码),需生成随机初始化向量(IV)和密钥;
  • 密钥存储 :密钥不硬编码,通过鸿蒙 KeyStore 安全存储,避免逆向破解;
  • 输入处理 :通过 TextEditingController 监听输入变化,每输入一个字符触发加密,明文仅在内存中短暂停留。

3.3 完整代码实现

3.3.1 加密工具类(AES 加密 / 解密)

先封装通用加密工具 AesEncryptionUtil,处理密钥生成、加密、解密逻辑:

dart

复制代码
import 'dart:convert';
import 'dart:typed_data';
import 'package:encrypt/encrypt.dart';
import 'package:ohos_secure_storage/ohos_secure_storage.dart';

class AesEncryptionUtil {
  static final AesEncryptionUtil _instance = AesEncryptionUtil._internal();
  factory AesEncryptionUtil() => _instance;

  final OhosSecureStorage _secureStorage = OhosSecureStorage();
  late Encrypter _encrypter;
  late IV _iv;
  static const String _keyStorageKey = "harmony_flutter_secure_key"; // 密钥在KeyStore中的存储键

  AesEncryptionUtil._internal() {
    _initEncryption();
  }

  // 初始化加密器:从KeyStore获取密钥,无则生成新密钥
  Future<void> _initEncryption() async {
    String? key = await _secureStorage.read(key: _keyStorageKey);
    
    // 若KeyStore中无密钥,生成新的256位密钥(32字节)并存储
    if (key == null) {
      final keyBytes = Key.fromSecureRandom(32); // 256位密钥
      await _secureStorage.write(
        key: _keyStorageKey,
        value: base64.encode(keyBytes.bytes),
      );
      key = base64.encode(keyBytes.bytes);
    }

    // 生成16字节IV(CBC模式要求IV长度=块大小,AES块大小为16字节)
    _iv = IV.fromSecureRandom(16);
    final keyBytes = Key.fromBase64(key);
    _encrypter = Encrypter(AES(keyBytes, mode: AESMode.cbc));
  }

  // 加密文本:返回"IV:密文"格式(IV需与密文一同存储,解密时需用到)
  Future<String> encryptText(String plainText) async {
    await _initEncryption(); // 确保加密器已初始化
    final encrypted = _encrypter.encrypt(plainText, iv: _iv);
    return "${base64.encode(_iv.bytes)}:${encrypted.base64}"; // 拼接IV和密文
  }

  // 解密文本:传入"IV:密文"格式字符串
  Future<String> decryptText(String encryptedText) async {
    await _initEncryption();
    try {
      final parts = encryptedText.split(":");
      if (parts.length != 2) throw ArgumentError("加密格式错误");
      
      final iv = IV.fromBase64(parts[0]);
      final encrypted = Encrypted.fromBase64(parts[1]);
      return _encrypter.decrypt(encrypted, iv: iv);
    } catch (e) {
      throw Exception("解密失败:${e.toString()}");
    }
  }
}
3.3.2 加密输入框组件(SecureInputField)

基于 StatefulWidget 实现,集成输入监听、加密、验证、可见性切换:

dart

复制代码
import 'package:flutter/material.dart';
import 'package:flutter_harmony_os/flutter_harmony_os.dart';

class SecureInputField extends StatefulWidget {
  // 组件参数:提示文本、加密后的回调(返回密文)、验证规则、是否为密码类型
  final String hintText;
  final Function(String) onEncryptedTextChanged;
  final String? Function(String)? validator;
  final bool isPassword;

  const SecureInputField({
    super.key,
    required this.hintText,
    required this.onEncryptedTextChanged,
    this.validator,
    this.isPassword = true,
  });

  @override
  State<SecureInputField> createState() => _SecureInputFieldState();
}

class _SecureInputFieldState extends State<SecureInputField> {
  final TextEditingController _controller = TextEditingController();
  final AesEncryptionUtil _encryptionUtil = AesEncryptionUtil();
  bool _obscureText = true; // 控制密码是否隐藏
  String? _errorText; // 输入验证错误提示

  @override
  void initState() {
    super.initState();
    // 监听输入变化:实时加密
    _controller.addListener(_onTextChanged);
  }

  @override
  void dispose() {
    _controller.dispose(); // 销毁控制器,避免内存泄露
    super.dispose();
  }

  // 输入变化回调:加密明文并触发外部回调
  Future<void> _onTextChanged() async {
    final plainText = _controller.text;
    
    // 输入验证
    if (widget.validator != null) {
      setState(() {
        _errorText = widget.validator!(plainText);
      });
    }

    // 若输入为空,回调空字符串;否则加密后回调
    if (plainText.isEmpty) {
      widget.onEncryptedTextChanged("");
      return;
    }

    try {
      final encryptedText = await _encryptionUtil.encryptText(plainText);
      widget.onEncryptedTextChanged(encryptedText);
    } catch (e) {
      setState(() {
        _errorText = "加密失败:${e.toString()}";
      });
    }
  }

  // 切换密码可见性
  void _toggleObscureText() {
    setState(() {
      _obscureText = !_obscureText;
    });
  }

  @override
  Widget build(BuildContext context) {
    return TextField(
      controller: _controller,
      obscureText: widget.isPassword ? _obscureText : false, // 非密码类型不隐藏
      keyboardType: widget.isPassword 
          ? TextInputType.visiblePassword 
          : TextInputType.text,
      // 调用鸿蒙安全键盘(避免第三方键盘监听)
      inputFormatters: [HarmonySecureKeyboardFormatter()],
      decoration: InputDecoration(
        hintText: widget.hintText,
        errorText: _errorText,
        border: const OutlineInputBorder(),
        suffixIcon: widget.isPassword
            ? IconButton(
                icon: Icon(
                  _obscureText ? Icons.visibility_off : Icons.visibility,
                ),
                onPressed: _toggleObscureText,
              )
            : null, // 非密码类型不显示切换图标
      ),
    );
  }
}
3.3.3 组件使用示例

在登录页面中使用 SecureInputField 接收密码,并获取加密后的密文:

dart

复制代码
class LoginPage extends StatelessWidget {
  LoginPage({super.key});
  String _encryptedPassword = ""; // 存储加密后的密码

  // 密码验证规则:长度≥8位,包含大小写字母和数字
  String? _validatePassword(String value) {
    if (value.length < 8) {
      return "密码长度不能少于8位";
    }
    if (!RegExp(r'(?=.*[A-Z])(?=.*[a-z])(?=.*\d)').hasMatch(value)) {
      return "密码需包含大小写字母和数字";
    }
    return null;
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text("登录页")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            // 加密密码输入框
            SecureInputField(
              hintText: "请输入密码",
              onEncryptedTextChanged: (encryptedText) {
                _encryptedPassword = encryptedText; // 更新加密后的密码
              },
              validator: _validatePassword,
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 提交加密后的密码到后端(避免明文传输)
                if (_encryptedPassword.isNotEmpty) {
                  print("提交加密密码:$_encryptedPassword");
                  // 此处可添加接口请求逻辑
                }
              },
              child: const Text("登录"),
            ),
          ],
        ),
      ),
    );
  }
}

四、核心组件二:脱敏展示组件(DesensitizeText)

4.1 组件需求分析

脱敏展示组件需解决 "敏感信息明文展示" 问题,核心需求:

  1. 多类型支持:覆盖手机号、身份证号、邮箱、银行卡号等常见敏感数据;
  2. 自定义规则:支持用户自定义脱敏格式(如身份证保留前 6 后 2);
  3. 空值处理:空数据时显示默认占位符(如 "--");
  4. 可交互性:支持长按显示完整信息(需二次验证,如指纹)。

4.2 核心原理

  • 脱敏策略 :基于 "数据类型" 匹配预设规则,通过字符串截取 + 替换实现脱敏(如手机号 138****1234);
  • 交互安全:长按显示完整信息前,调用鸿蒙生物识别(指纹 / 面容),验证通过才展示明文;
  • 扩展性 :通过枚举 DesensitizeType 和函数参数,支持规则扩展。

4.3 完整代码实现

4.3.1 脱敏工具类(DesensitizeUtil)

封装通用脱敏逻辑,支持多类型和自定义规则:

dart

复制代码
class DesensitizeUtil {
  // 脱敏类型枚举
  enum DesensitizeType {
    phone,       // 手机号:138****1234
    idCard,      // 身份证号:110101********1234
    email,       // 邮箱:123****@qq.com
    bankCard,    // 银行卡号:6226****6666
    custom       // 自定义规则
  }

  // 通用脱敏方法:根据类型处理
  static String desensitize(
    String? data, 
    DesensitizeType type, {
    // 自定义规则参数:start(保留前n位)、end(保留后n位)、replaceChar(替换字符)
    int customStart = 0,
    int customEnd = 0,
    String replaceChar = "*",
  }) {
    // 空数据处理
    if (data == null || data.isEmpty) {
      return "--";
    }

    switch (type) {
      case DesensitizeType.phone:
        // 手机号:保留前3后4,中间4位替换为*(如138****1234)
        if (data.length != 11) return data; // 非11位手机号不脱敏
        return "${data.substring(0, 3)}${replaceChar * 4}${data.substring(7)}";
      
      case DesensitizeType.idCard:
        // 身份证号:保留前6后4,中间8位替换为*(如110101********1234)
        if (data.length != 18) return data; // 非18位身份证不脱敏
        return "${data.substring(0, 6)}${replaceChar * 8}${data.substring(14)}";
      
      case DesensitizeType.email:
        // 邮箱:用户名保留前3位,@前其余替换为*(如123****@qq.com)
        if (!data.contains("@")) return data; // 非邮箱格式不脱敏
        final parts = data.split("@");
        final username = parts[0];
        final domain = parts[1];
        if (username.length <= 3) {
          return "$username$replaceChar@$domain";
        }
        return "${username.substring(0, 3)}${replaceChar * (username.length - 3)}@$domain";
      
      case DesensitizeType.bankCard:
        // 银行卡号:保留前4后4,中间替换为*(如6226****6666)
        if (data.length < 8) return data;
        return "${data.substring(0, 4)}${replaceChar * (data.length - 8)}${data.substring(data.length - 4)}";
      
      case DesensitizeType.custom:
        // 自定义规则:保留前customStart位和后customEnd位
        if (customStart + customEnd >= data.length) return data;
        return "${data.substring(0, customStart)}${replaceChar * (data.length - customStart - customEnd)}${data.substring(data.length - customEnd)}";
    }
  }
}
4.3.2 脱敏展示组件(DesensitizeText)

集成脱敏逻辑和长按验证功能,调用鸿蒙生物识别:

dart

复制代码
import 'package:flutter/material.dart';
import 'package:flutter_harmony_os/flutter_harmony_os.dart';

class DesensitizeText extends StatelessWidget {
  final String? data; // 原始敏感数据
  final DesensitizeUtil.DesensitizeType type;
  final int customStart; // 自定义规则:保留前n位
  final int customEnd; // 自定义规则:保留后n位
  final TextStyle? style; // 文本样式
  final bool enableLongPress; // 是否支持长按显示完整信息

  const DesensitizeText({
    super.key,
    required this.data,
    required this.type,
    this.customStart = 0,
    this.customEnd = 0,
    this.style,
    this.enableLongPress = true,
  });

  // 调用鸿蒙生物识别(指纹/面容)
  Future<bool> _verifyBiometric() async {
    try {
      // 调用鸿蒙生物识别API,验证用户身份
      final result = await HarmonyBiometrics.verify(
        authType: HarmonyBiometricType.fingerprint, // 指纹识别
        promptInfo: "请验证指纹以查看完整信息",
      );
      return result.isSuccess; // 验证成功返回true
    } catch (e) {
      debugPrint("生物识别失败:${e.toString()}");
      return false;
    }
  }

  // 长按显示完整信息(弹窗)
  void _showFullData(BuildContext context) async {
    if (!enableLongPress || data == null || data!.isEmpty) return;

    // 先进行生物识别
    final isVerified = await _verifyBiometric();
    if (!isVerified) {
      ScaffoldMessenger.of(context).showSnackBar(
        const SnackBar(content: Text("身份验证失败,无法查看完整信息")),
      );
      return;
    }

    // 验证通过,弹窗显示完整信息
    showDialog(
      context: context,
      builder: (context) => AlertDialog(
        title: const Text("完整信息"),
        content: SelectableText(data!), // 支持复制
        actions: [
          TextButton(
            onPressed: () => Navigator.pop(context),
            child: const Text("关闭"),
          ),
        ],
      ),
    );
  }

  @override
  Widget build(BuildContext context) {
    // 生成脱敏文本
    final desensitizedText = DesensitizeUtil.desensitize(
      data,
      type,
      customStart: customStart,
      customEnd: customEnd,
    );

    // 可长按组件(支持点击反馈)
    return GestureDetector(
      onLongPress: () => _showFullData(context),
      child: Text(
        desensitizedText,
        style: style ?? const TextStyle(color: Colors.black87),
      ),
    );
  }
}
4.3.3 组件使用示例

在用户中心页面展示多种脱敏信息:

dart

复制代码
class UserCenterPage extends StatelessWidget {
  const UserCenterPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 模拟用户敏感数据(实际从接口获取)
    const String phone = "13812345678";
    const String idCard = "110101199001011234";
    const String email = "user123456@example.com";
    const String bankCard = "6226091234567890";

    return Scaffold(
      appBar: AppBar(title: const Text("用户中心")),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            // 脱敏展示手机号
            _buildInfoItem("手机号", DesensitizeUtil.DesensitizeType.phone, phone),
            // 脱敏展示身份证号
            _buildInfoItem("身份证号", DesensitizeUtil.DesensitizeType.idCard, idCard),
            // 脱敏展示邮箱
            _buildInfoItem("邮箱", DesensitizeUtil.DesensitizeType.email, email),
            // 脱敏展示银行卡号
            _buildInfoItem("银行卡号", DesensitizeUtil.DesensitizeType.bankCard, bankCard),
            // 自定义脱敏:保留前2后1(如"张**三")
            _buildCustomItem("姓名", "张三", customStart: 1, customEnd: 1),
          ],
        ),
      ),
    );
  }

  // 通用信息项构建方法
  Widget _buildInfoItem(String label, DesensitizeUtil.DesensitizeType type, String data) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text("$label:", style: const TextStyle(fontSize: 16)),
          DesensitizeText(
            data: data,
            type: type,
            style: const TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
        ],
      ),
    );
  }

  // 自定义脱敏信息项
  Widget _buildCustomItem(String label, String data, {required int customStart, required int customEnd}) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8.0),
      child: Row(
        mainAxisAlignment: MainAxisAlignment.spaceBetween,
        children: [
          Text("$label:", style: const TextStyle(fontSize: 16)),
          DesensitizeText(
            data: data,
            type: DesensitizeUtil.DesensitizeType.custom,
            customStart: customStart,
            customEnd: customEnd,
            style: const TextStyle(fontSize: 16, color: Colors.grey[700]),
          ),
        ],
      ),
    );
  }
}

五、组件测试与鸿蒙适配优化

5.1 单元测试(验证加密 / 脱敏逻辑)

为核心工具类编写单元测试,确保功能正确性:

dart

复制代码
import 'package:flutter_test/flutter_test.dart';
import 'package:your_project_name/utils/aes_encryption_util.dart';
import 'package:your_project_name/utils/desensitize_util.dart';

void main() {
  group("AesEncryptionUtil 测试", () {
    final util = AesEncryptionUtil();
    const testText = "TestPassword123!";

    test("加密后解密应与原文本一致", () async {
      final encrypted = await util.encryptText(testText);
      final decrypted = await util.decryptText(encrypted);
      expect(decrypted, equals(testText));
    });

    test("空文本加密应返回空字符串", () async {
      final encrypted = await util.encryptText("");
      expect(encrypted, equals(""));
    });
  });

  group("DesensitizeUtil 测试", () {
    test("手机号脱敏:13812345678 → 138****5678", () {
      const phone = "13812345678";
      final result = DesensitizeUtil.desensitize(phone, DesensitizeUtil.DesensitizeType.phone);
      expect(result, equals("138****5678"));
    });

    test("身份证号脱敏:110101199001011234 → 110101********1234", () {
      const idCard = "110101199001011234";
      final result = DesensitizeUtil.desensitize(idCard, DesensitizeUtil.DesensitizeType.idCard);
      expect(result, equals("110101********1234"));
    });

    test("自定义脱敏:保留前2后1 → 123456 → 12***6", () {
      const data = "123456";
      final result = DesensitizeUtil.desensitize(
        data,
        DesensitizeUtil.DesensitizeType.custom,
        customStart: 2,
        customEnd: 1,
      );
      expect(result, equals("12***6"));
    });
  });
}

5.2 鸿蒙适配优化点

  1. 密钥安全存储 :使用 ohos_secure_storage 替代原生 shared_preferences,将密钥存入鸿蒙 KeyStore,避免 root 设备破解;
  2. 安全键盘调用 :通过 HarmonySecureKeyboardFormatter 强制使用鸿蒙系统安全键盘,防止第三方键盘记录输入;
  3. 生物识别集成 :调用鸿蒙 HarmonyBiometrics API,支持指纹 / 面容验证,确保敏感信息展示安全;
  4. 性能优化 :加密逻辑通过 Future 异步处理,避免阻塞 UI 线程;输入监听添加防抖(可扩展 DebounceUtil),减少加密次数。

六、常见问题与解决方案

问题场景 解决方案
加密密钥硬编码导致安全风险 使用鸿蒙 KeyStore 存储密钥,参考 AesEncryptionUtil 中的 _secureStorage 逻辑
脱敏规则不满足业务需求 扩展 DesensitizeType 枚举和 desensitize 方法,添加自定义类型(如 "护照号")
长按显示完整信息无验证 集成鸿蒙生物识别,参考 _verifyBiometric 方法
输入框加密时 UI 卡顿 添加防抖处理:延迟 500ms 再执行加密,避免输入过程中频繁加密(示例代码见下文)

防抖处理示例(优化输入加密性能)

在 _onTextChanged 中添加防抖:

dart

复制代码
import 'dart:async';

// 防抖工具类
class DebounceUtil {
  Timer? _timer;
  void debounce({required Duration delay, required VoidCallback callback}) {
    _timer?.cancel();
    _timer = Timer(delay, callback);
  }

  void dispose() {
    _timer?.cancel();
  }
}

// 在 _SecureInputFieldState 中使用
class _SecureInputFieldState extends State<SecureInputField> {
  final DebounceUtil _debounce = DebounceUtil(); // 初始化防抖工具

  @override
  void dispose() {
    _debounce.dispose(); // 销毁防抖定时器
    super.dispose();
  }

  Future<void> _onTextChanged() async {
    final plainText = _controller.text;
    // ... 输入验证逻辑 ...

    // 防抖:输入停止500ms后再加密
    _debounce.debounce(
      delay: const Duration(milliseconds: 500),
      callback: () async {
        if (plainText.isEmpty) {
          widget.onEncryptedTextChanged("");
          return;
        }
        try {
          final encryptedText = await _encryptionUtil.encryptText(plainText);
          widget.onEncryptedTextChanged(encryptedText);
        } catch (e) {
          setState(() {
            _errorText = "加密失败:${e.toString()}";
          });
        }
      },
    );
  }
}

七、完整项目与扩展建议

7.1 项目源码获取

完整代码已上传至 GitHub,包含组件、工具类、测试用例和示例页面:GitHub 仓库链接(可直接克隆运行,需配置鸿蒙开发环境)

7.2 组件扩展方向

  1. 支持更多加密算法 :在 AesEncryptionUtil 中添加 RSA 加密(适合非对称加密场景,如与后端交互);
  2. 脱敏规则配置化 :将脱敏规则存入配置文件(如 desensitize_rules.json),支持动态修改;
  3. 组件主题定制 :为 SecureInputField 和 DesensitizeText 添加主题参数(如颜色、字体、边框样式);
  4. 埋点与监控 :添加加密 / 脱敏失败的埋点,通过鸿蒙 HiTrace 跟踪安全组件运行状态。

八、总结

本文围绕鸿蒙 Flutter 应用的敏感信息保护需求,开发了 加密输入框 和 脱敏展示组件,覆盖 "输入加密 - 存储安全 - 展示脱敏" 全链路。组件具备以下特点:

  1. 安全性:基于 AES 加密和鸿蒙 KeyStore / 生物识别,符合金融级安全标准;
  2. 易用性:提供完整 API 和示例,可直接集成到现有项目;
  3. 扩展性:支持自定义加密算法、脱敏规则,适配不同业务场景;
  4. 鸿蒙适配:深度集成鸿蒙系统特性(安全键盘、生物识别),性能更优。

建议在实际项目中,结合业务需求进一步优化组件(如添加输入限流、敏感信息日志过滤),确保应用符合《鸿蒙应用安全开发指南》要求。

相关推荐
FuckPatience6 分钟前
WPF DataGrid 行根据某个属性循环变色动画
wpf
恋猫de小郭9 分钟前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白22 分钟前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
Zoom11 小时前
【开源】7 天,我用 Rust 复刻了 FinalShell!Rhost v1.0.0 发布
rust·electron·shell
程序猿追15 小时前
HarmonyOS 6 上做个极简浏览器:Web 组件 + 前进后退 + 加载进度
前端·华为·harmonyos
程序猿追15 小时前
HarmonyOS 6 音视频实战:用 AVPlayer 做一个本地音乐播放器
华为·音视频·harmonyos
陆断枫16 小时前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
张星河zen17 小时前
华为超节点Peerium架构全拆解:Atlas 950、昇腾960、灵衢UnifiedBus
华为·npo·peerium·昇腾960·atlas950·全光互联·灵衢
程序猿追17 小时前
HarmonyOS 6 毛玻璃卡片:背景模糊 + 半透明
华为·harmonyos
CV工程师丁Sir17 小时前
ArkWeb 手记 05|请求拦截:劫持 H5 接口与图片资源
华为·harmonyos