AR 眼镜之-HID 蓝牙戒指-接入方案

TPM2-Startup-学习手册.docx 内容概要:本文深入解析了TPM 2.0中首个必要命令TPM2_Startup的工作机制,涵盖其在芯片初始化中的核心作用、启动类型决策逻辑、内部九个初始化阶段的详细流程,以及DRTM/H-CRTM安全机制的实现原理。文档结合源码(ibmswtpm2-tpm2)与实测报文,系统阐述了Startup如何通过状态校验、locality控制、PCR管理、密码学初始化、DA防护、ACT定时器、NV索引处理等环节,确保TPM在安全可信的状态下启动,并重点剖析了commitNonce重置、DA计时器跨重启续接、ACT减半防攻击等关键安全设计。; 适合人群:对TPM安全芯片感兴趣的零基础或初级开发者,以及从事可信计算、固件安全、系统启动安全相关工作的研发人员。; 使用场景及目标:①理解TPM为何必须以Startup作为第一条命令及其安全意义;②掌握SU_CLEAR、SU_STATE、SU_RESUME、SU_RESTART、SU_RESET等启动类型的决策逻辑与状态迁移;③深入学习DRTM与H-CRTM机制,理解动态信任根的建立过程;④分析DA防护、ACT定时器、有序NV索引等子系统的初始化行为与安全考量;⑤通过实验复现各类启动场景,提升对TPM底层机制的实战理解。; 阅读建议:建议结合文档提供的源码注释、实测报文和动手实验逐步验证,重点关注Startup与Shutdown的配对关系、状态持久化机制(NV)以及各类标志位(如g_DrtmPreStartup、g_StartupLocality3)的作用,注意区分规范定义与具体实现间的差异,并利用附录常量与术语表辅助理解。 立即下载

📂 前言

        在 AR 眼镜的交互方案中,手势控制是核心痛点之一,传统的触控板、语音控制、按键等方式各有局限性,而HID 蓝牙戒指作为一种新型输入设备,能够提供更自然、更精准的操作体验。本文将详细介绍三种渐进式接入方案,从简单到复杂,逐步实现戒指控制 AR 眼镜的目标。

        核心流程:

        戒指支持的手势事件:

  • 前滑:向前滑动

  • 后滑:向后滑动

  • 进入:点击/确认

  • 退出:返回/取消


1. 🔱 技术方案概述

1.1 方案对比

方案

适用场景

息屏支持

优点

缺点

方案一:前台界面监听

App 前台运行时

-

实现简单,无需系统权限

App 后台或锁屏时失效

方案二:无障碍服务

前台/后台/锁屏

-

无需修改戒指协议,系统级拦截

息屏时可能失效,需用户授权

方案三:私有蓝牙协议

全场景

Y

功能完整,体验最优

需要戒指厂商提供协议文档

1.2 架构设计


2. ⚛️ 方案一:前台界面监听

2.1 方案原理

        当蓝牙戒指作为 HID 设备连接到手机后,戒指的手势操作会转化为标准的指针事件(PointerEvent)或按键事件(KeyEvent),通过在 Flutter 界面中使用 Listener 组件,可以直接捕获这些事件。

2.2 实现方案

        创建一个专门的中转 Widget,负责:

  1. 监听戒指发出的指针事件(滑动、点击、长按)

  2. 识别手势类型(单击、双击、长按、滑动)

  3. 将手势转换为控制指令

  4. 通过蓝牙/Wi-Fi 转发给 AR 眼镜

2.2.1 核心代码:Focus.Listener
  Widget build(BuildContext context) {
    return Focus(
      focusNode: _focusNode,
      autofocus: true,
      onFocusChange: (hasFocus) {
        logI("焦点状态改变: $hasFocus", tag: "RingToGlassesControllerWidget");
      },
      child: Listener(
        // 2. 指针按下时,开启手势判定管道
        onPointerDown: _handlePointerDown,
        // 3. 指针移动时,检查是否移动过大需取消点击
        onPointerMove: _handlePointerMove,
        // 4. 指针抬起,识别单击或双击
        onPointerUp: _handlePointerUp,

        // 5. 娱乐模式下的纯移动事件(用于滑动)
        onPointerHover: (PointerHoverEvent event) {
          if (event.delta.dy > 1.0) {
            _internalEventStream.add("HOVER_DOWN");
          } else if (event.delta.dy < -1.0) {
            _internalEventStream.add("HOVER_UP");
          }
        },
      ),
    );
  }
2.2.2 完整代码:RingToGlassesControllerWidget.dart
import 'dart:async';
import 'package:flutter/gestures.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:mb_app/device/manager/device_setting_manager.dart';
import 'package:mb_app/device/view/widgets/glasses_remote_controller_popup.dart';

class RingToGlassesControllerWidget extends StatefulWidget {
  const RingToGlassesControllerWidget({Key? key}) : super(key: key);

  @override
  State<RingToGlassesControllerWidget> createState() => _RingToGlassesControllerWidgetState();
}

class _RingToGlassesControllerWidgetState extends State<RingToGlassesControllerWidget> {
  final FocusNode _focusNode = FocusNode();

  final List<String> _consoleLogs = [];


  bool _isConnectedToGlasses = false;
  bool _isConnecting = false;

  final StreamController<String> _internalEventStream = StreamController<String>.broadcast();
  StreamSubscription<String>? _eventForwardingSub;

  Offset? _pointerDownPosition;
  DateTime? _pointerDownTime;
  Timer? _longPressTimer;
  Timer? _clickDelayTimer;
  DateTime? _lastClickTime;

  static const Duration _longPressThreshold = Duration(milliseconds: 500);
  static const Duration _clickThreshold = Duration(milliseconds: 250);
  static const Duration _doubleClickThreshold = Duration(milliseconds: 300);
  static const double _moveTolerance = 15.0;

  @override
  void initState() {
    super.initState();
    WidgetsBinding.instance.addPostFrameCallback((_) {
      if (mounted) _focusNode.requestFocus();
    });
    _eventForwardingSub = _internalEventStream.stream.listen((command) {
      _sendDataToGlasses(command);
    });
    _connectToGlasses();
  }

  @override
  void dispose() {
    _focusNode.dispose();
    _eventForwardingSub?.cancel();
    _internalEventStream.close();
    _longPressTimer?.cancel();
    _clickDelayTimer?.cancel();
    _disconnectFromGlasses();
    super.dispose();
  }

  void _connectToGlasses() async {
    if (_isConnectedToGlasses || _isConnecting) return;
    setState(() {
      _isConnecting = true;
      _logToConsole("正在尝试建立与眼镜的无线通信链路...");
    });
    await Future.delayed(const Duration(seconds: 2));
    if (!mounted) return;
    setState(() {
      _isConnecting = false;
      _isConnectedToGlasses = true;
      _logToConsole("🟢 眼镜连接成功!可以开始中转指环指令。");
    });
  }

  void _sendDataToGlasses(String command) {
    if (!_isConnectedToGlasses) return;
    
    int code = RemoteControlKeyCode.touchBarForward;
    if (command == "HOVER_DOWN") {
      code = RemoteControlKeyCode.touchBarForward;
    } else if (command == "HOVER_UP") {
      code = RemoteControlKeyCode.touchBarBackward;
    } else if (command == "CONFIRM_CLICK" || command == "TOUCH_CLICK") {
      code = RemoteControlKeyCode.touchBarSingleClick;
    } else if (command == "TOUCH_DOUBLE_CLICK") {
      code = RemoteControlKeyCode.touchBarDoubleClick;
    } else if (command == "TOUCH_LONG_PRESS") {
      code = RemoteControlKeyCode.touchBarLongPressed;
    }

    unawaited(deviceSettingManager.sendCmdControlVirtualKey(code));
  }

  void _handlePointerDown(PointerDownEvent event) {
    _pointerDownPosition = event.localPosition;
    _pointerDownTime = DateTime.now();
    _longPressTimer?.cancel();
    _longPressTimer = Timer(_longPressThreshold, () {
      if (_pointerDownPosition != null) {
        _internalEventStream.add("TOUCH_LONG_PRESS");
        _cleanupGestureState();
      }
    });
  }

  void _handlePointerMove(PointerMoveEvent event) {
    if (_pointerDownPosition == null) return;
    final double distance = (event.localPosition - _pointerDownPosition!).distance;
    if (distance > _moveTolerance) {
      _longPressTimer?.cancel();
      _pointerDownPosition = null;
    }
  }

  void _handlePointerUp(PointerUpEvent event) {
    _longPressTimer?.cancel();
    if (_pointerDownPosition == null || _pointerDownTime == null) return;
    
    final DateTime now = DateTime.now();
    final Duration pressDuration = now.difference(_pointerDownTime!);
    
    if (pressDuration < _clickThreshold) {
      if (_lastClickTime != null && now.difference(_lastClickTime!) < _doubleClickThreshold) {
        _clickDelayTimer?.cancel();
        _internalEventStream.add("TOUCH_DOUBLE_CLICK");
        _lastClickTime = null;
      } else {
        _lastClickTime = now;
        _clickDelayTimer?.cancel();
        _clickDelayTimer = Timer(_doubleClickThreshold, () {
          _internalEventStream.add("TOUCH_CLICK");
          _lastClickTime = null;
        });
      }
    }
    _cleanupGestureState();
  }

  void _cleanupGestureState() {
    _pointerDownPosition = null;
    _pointerDownTime = null;
    _longPressTimer?.cancel();
  }

  @override
  Widget build(BuildContext context) {
    return Focus(
      focusNode: _focusNode,
      autofocus: true,
      child: Listener(
        onPointerDown: _handlePointerDown,
        onPointerMove: _handlePointerMove,
        onPointerUp: _handlePointerUp,
        onPointerHover: (PointerHoverEvent event) {
          if (event.delta.dy > 1.0) {
            _internalEventStream.add("HOVER_DOWN");
          } else if (event.delta.dy < -1.0) {
            _internalEventStream.add("HOVER_UP");
          }
        },
        onPointerSignal: (PointerSignalEvent pointerSignal) {
          if (pointerSignal is PointerScrollEvent) {
            if (pointerSignal.scrollDelta.dy > 0) {
              _internalEventStream.add("SLIDE_DOWN");
            } else if (pointerSignal.scrollDelta.dy < 0) {
              _internalEventStream.add("SLIDE_UP");
            }
          }
        },
        child: Container(
          padding: const EdgeInsets.all(16.0),
          decoration: BoxDecoration(
            color: Colors.grey.shade100,
            borderRadius: BorderRadius.circular(12),
            border: Border.all(color: Colors.grey.shade300),
          ),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.stretch,
            children: [
              Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: [
                  const Text("OnCtrl 中转中心", style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold)),
                  _buildStatusBadge(),
                ],
              ),
              const SizedBox(height: 12),
              Text(
                _isConnectedToGlasses ? "请操作指环,指令将通过手机自动实时透传至眼镜..." : "等待眼镜连接通道准备就绪...",
                style: TextStyle(color: Colors.grey.shade600, fontSize: 13),
              ),
              const SizedBox(height: 12),
              const Text("实时同步日志:", style: TextStyle(fontSize: 12, fontWeight: FontWeight.w500)),
              const SizedBox(height: 6),
              Expanded(
                child: Container(
                  padding: const EdgeInsets.all(8),
                  decoration: BoxDecoration(
                    color: Colors.black87,
                    borderRadius: BorderRadius.circular(6),
                  ),
                  child: _consoleLogs.isEmpty
                      ? const Center(child: Text("等待指环物理信号...", style: TextStyle(color: Colors.greenAccent, fontSize: 12)))
                      : ListView.builder(
                          itemCount: _consoleLogs.length,
                          itemBuilder: (context, index) => Text(
                            _consoleLogs[index],
                            style: const TextStyle(color: Colors.greenAccent, fontFamily: 'monospace', fontSize: 11),
                          ),
                        ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }

  Widget _buildStatusBadge() {
    if (_isConnecting) {
      return const SizedBox(width: 16, height: 16, child: CircularProgressIndicator(strokeWidth: 2, color: Colors.orange));
    }
    return Container(
      padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
      decoration: BoxDecoration(
        color: _isConnectedToGlasses ? Colors.green.shade100 : Colors.red.shade100,
        borderRadius: BorderRadius.circular(4),
      ),
      child: Text(
        _isConnectedToGlasses ? "眼镜在线" : "眼镜离线",
        style: TextStyle(
          color: _isConnectedToGlasses ? Colors.green.shade800 : Colors.red.shade800,
          fontSize: 11,
          fontWeight: FontWeight.bold,
        ),
      ),
    );
  }
}

2.3 方案总结

        适用场景:开发调试阶段、简单演示、用户主动打开控制界面时

        核心问题:当 App 进入后台或手机锁屏时,Listener 将无法接收事件,导致戒指控制失效。


3. 💠 方案二:无障碍服务

3.1 方案原理

        Android 的 AccessibilityService 提供了系统级的事件监听能力,当戒指作为 HID 设备发送按键事件时,无障碍服务可以在任何应用(包括系统界面)中拦截这些事件,无论 App 是否在前台运行。

  • 关键特性:支持前台、后台、锁屏状态下的事件监听

  • 限制:息屏状态下,大多数 Android 设备会暂停无障碍服务

3.2 实现方案

3.2.1 无障碍服务配置:accessibility_service_config.xml
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
    android:accessibilityEventTypes="typeAllMask"
    android:accessibilityFeedbackType="feedbackGeneric"
    android:accessibilityFlags="flagDefault|flagRequestFilterKeyEvents"
    android:canRequestFilterKeyEvents="true"
    android:description="@string/accessibility_service_description"
    android:notificationTimeout="100" />
3.2.2 服务描述:strings.xml
<string name="accessibility_service_description">用于在后台实时转发智能指环的多媒体控制指令至眼镜终端。</string>
3.2.3 核心代码:RingAccessibilityService.kt
class RingAccessibilityService : AccessibilityService() {
    override fun onAccessibilityEvent(event: AccessibilityEvent?) {
        // 娱乐模式下,指环动作可能触发窗口状态改变或焦点变化
        // 如果指环发送特定 Accessibility 类型的节点点击,可以在此拦截
    }

    override fun onInterrupt() {
        Log.e(TAG, "无障碍服务被强行中断")
    }

    override fun onServiceConnected() {
        super.onServiceConnected()
        Log.i(TAG, "🟢 指环后台无障碍中转服务已成功连接!")
    }

    override fun onKeyEvent(event: KeyEvent): Boolean {
        val keyCode = event.keyCode
        val action = event.action

        if (action == KeyEvent.ACTION_DOWN) {
            when (keyCode) {
                KeyEvent.KEYCODE_MEDIA_NEXT -> BTManager.sendCmd(CmdV2ReqControllerKey(0))
                KeyEvent.KEYCODE_MEDIA_PREVIOUS -> BTManager.sendCmd(CmdV2ReqControllerKey(1))
                KeyEvent.KEYCODE_MEDIA_PLAY_PAUSE, KeyEvent.KEYCODE_MEDIA_PLAY -> 
                    BTManager.sendCmd(CmdV2ReqControllerKey(2))
                KeyEvent.KEYCODE_BACK, KeyEvent.KEYCODE_ESCAPE -> 
                    BTManager.sendCmd(CmdV2ReqControllerKey(11))
            }
            Log.i(TAG, "onKeyEvent: keyCode=$keyCode")
            return true
        }
        return super.onKeyEvent(event)
    }

    companion object {
        private const val TAG = "RingAccessibility"
    }
}
3.2.4 注册服务:AndroidManifest.xml
<service
    android:name=".plugin.ring.RingAccessibilityService"
    android:exported="true"
    android:label="指环无障碍控制服务"
    android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
    <intent-filter>
        <action android:name="android.accessibilityservice.AccessibilityService" />
    </intent-filter>
    <meta-data
        android:name="android.accessibilityservice"
        android:resource="@xml/accessibility_service_config" />
</service>
3.2.5 引导用户授权:MainActivity.kt
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    // ... 其他初始化
    openAccessibilitySettings()
}

private fun openAccessibilitySettings() {
    try {
        val intent = Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS).apply {
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        startActivity(intent)
    } catch (e: Exception) {
        e.printStackTrace()
    }
}

3.3 方案总结

        适用场景:需要后台持续监听、锁屏状态下使用

        核心问题:

  1. 用户需要手动在系统设置中开启无障碍权限,流程较复杂

  2. 息屏状态下,部分 Android 厂商会限制无障碍服务运行

  3. 不同厂商的系统对无障碍服务的行为可能存在差异


4. 🎯 方案三:私有蓝牙协议

4.1 方案原理

        目前市面上的 HID 蓝牙戒指大多基于标准 HID 协议工作,将手势转换为键盘/鼠标事件,但这种方式存在以下问题:

  1. 功能受限:只能映射到有限的标准按键

  2. 兼容性差:不同设备对 HID 事件的处理不一致

  3. 体验不佳:无法区分戒指的特定手势(如前滑、后滑)

        最佳方案:与戒指厂商合作,开放私有蓝牙协议,通过 BLE GATT 特征值直接传输手势数据。

4.2 协议设计

// 监听 GATT 特征值变化
ringGatt?.setCharacteristicNotification(gestureCharacteristic, true)
val descriptor = gestureCharacteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805F9B34FB"))
descriptor?.value = BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE
ringGatt?.writeDescriptor(descriptor)

// 接收手势事件
override fun onCharacteristicChanged(gatt: BluetoothGatt, characteristic: BluetoothGattCharacteristic) {
    val data = characteristic.value
    val eventType = data[0].toInt()
    val direction = data[1].toInt()
    val pressure = data[2].toInt()
    
    when (eventType) {
        0x01 -> handlePress(direction)
        0x02 -> handleRelease(direction)
        0x03 -> handleSlide(direction)
        0x04 -> handleLongPress(direction)
    }
}

private fun handleSlide(direction: Int) {
    val command = when (direction) {
        0x01 -> CmdV2ReqControllerKey(0)  // 上滑
        0x02 -> CmdV2ReqControllerKey(1)  // 下滑
        0x03 -> CmdV2ReqControllerKey(2)  // 左滑
        0x04 -> CmdV2ReqControllerKey(3)  // 右滑
        else -> return
    }
    BTManager.sendCmd(command)
}

4.3 方案总结

        适用场景:正式产品、追求最佳体验

        优点:

  1. 功能完整:支持自定义手势,不受标准 HID 限制

  2. 体验最优:低延迟、高精度,支持压力感应

  3. 息屏支持:BLE 连接在息屏状态下仍可保持

  4. 扩展性强:可通过协议升级增加新功能

        缺点:

  1. 需要戒指厂商配合开放协议

  2. 开发成本较高,需要双方联调


5. 📊 方案对比与选择建议

5.1 综合对比

维度

方案一

方案二

方案三

开发周期

1-2 天

3-5 天

2-4 周

用户体验

⭐⭐

⭐⭐⭐

⭐⭐⭐⭐⭐

后台运行

❌

✅

✅

息屏支持

❌

❌

✅

手势种类

有限

有限

丰富

系统兼容性

高

中

高

用户授权

无需

需要

需要

5.2 选择建议

  1. 初期:使用方案一快速验证戒指与眼镜的联动效果

  2. 中期:接入方案二,在 App 后台和锁屏时也能工作

  3. 长期:推动戒指厂商开放私有协议,实现方案三


6. ⚡ 常见问题与解决方案

6.1 戒指连接不稳定

        问题:蓝牙连接频繁断开,尤其在手机息屏后。

        解决方案:

  1. 使用 BLE 长连接,设置合适的连接参数

  2. 在 Android 端申请 FOREGROUND_SERVICE_CONNECTED_DEVICE 权限

  3. 添加电池优化白名单,防止系统杀死蓝牙进程

<uses-permission android:name="android.permission.FOREGROUND_SERVICE_CONNECTED_DEVICE" />
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />

6.2 事件识别不准确

        问题:滑动和点击事件混淆,长按判定不准确。

        解决方案:

  1. 调整手势识别参数(时间阈值、位移阈值)

  2. 使用状态机管理手势状态

  3. 在硬件层面(戒指端)增加防抖处理

6.3 无障碍服务被系统关闭

        问题:部分国产 ROM 会在后台自动关闭无障碍服务。

        解决方案:

  1. 在 App 启动时检查服务状态,未开启则引导用户

  2. 使用前台服务保活,增加服务优先级

  3. 提供详细的开启指引文档


7. 📝 小结

        本文介绍了三种 HID 蓝牙戒指接入 AR 眼镜的方案,从简单到复杂,逐步递进:

  1. 方案一(前台界面监听):实现简单,适合开发调试和基础演示

  2. 方案二(无障碍服务):系统级拦截,支持后台和锁屏状态,但息屏受限

  3. 方案三(私有蓝牙协议):功能完整,体验最优,是正式产品的最佳选择

        在实际项目中,建议采用渐进式策略:先用方案一验证可行性,再用方案二提升用户体验,最终推动戒指厂商开放私有协议实现方案三。

国央企创新负责人如何快速识别关键技术成果并推动落地?.docx 科易网基于AI模型+行业大数据,在技术转移、成果转化、技术经纪、知识产权、产业创新、科技招商等垂直领域的多样化应用场景,推出科技创新领域的AI+工具与数智化解决方案,推动科技创新与产业创新智能化发展。 立即下载

相关推荐

三相电压型PWM整流器PI双闭环控制Matlab仿真

内容概要:本文档系统介绍了三相电压型PWM整流器的PI双闭环控制技术及其在Matlab/Simulink环境下的仿真建模方法,重点阐述了电流内环与电压外环的协同控制机制,旨在实现高功率因数、低电流谐波畸变及稳定的直流母线电压输出。内容涵盖多种典型电力电子变换器的建模与控制策略,包括三相维也纳(Vienna)整流器、双有源桥(DAB)变换器、LLC谐振变换器等,并深入探讨SVPWM调制、滞环电流控制、中点电位平衡、软开关技术、畸变抑制等关键技术。文档提供丰富的仿真模型、Mathcad计算书及参考文献,支持从基础控制结构到高级优化算法(如基于KKT条件的协同最小应力控制)的多层次研究与复现。; 适合人群:电气工程、自动化、电力电子与电力传动等相关专业的高校本科生、研究生,以及从事新能源发电、电动汽车、轨道交通等领域电能变换系统研发的科研人员与工程技术人员;要求具备Matlab/Simulink仿真基础及电力电子、自动控制理论的相关知识背景。; 使用场景及目标:①作为高校课程设计与实验教学的辅助材料,帮助学生掌握PWM整流器双闭环控制系统的设计流程与仿真方法;②支撑科研项目中对高性能整流器控制策略的验证、优化与EI级论文复现;③为工业界在新能源并网、车载充电机、直流配电系统等应用场景中的控制器开发提供仿真依据与参数设计工具;④结合Mathcad计算书实现不同功率等级与电压等级系统的自主建模与参数匹配。; 阅读建议:建议读者按照资源目录顺序循序渐进地学习,先掌握PI双闭环控制的基本架构与仿真调试方法,再逐步拓展至中点平衡控制、电网不平衡工况下的功率调节、软开关优化等高级主题;重点关注“顶级EI复现”案例以提升学术研究深度;推荐结合提供的仿真模型、计算书与参考文献进行对照分析与代码调试,强化理论与实践的融合能力。

Flutter项目中数据统计报表开发的工程化实践与代码规范

在移动应用开发中,数据统计报表与BI功能往往承载着业务分析的核心需求。然而,这类模块开发过程中常伴随结构混乱、逻辑耦合等问题,尤其是在团队协作场景下,如何通过工程化手段和代码规范提升代码质量,成为每个技术负责人需要面对的挑战。本文以Flutter为技术栈,结合一个完整的数据统计报表项目为例,从实际开发流程出发,深入探讨如何通过规范化配置、可复用组件设计和自动化工具集成等方式,系统性提升团队代码质量。

wuchangjian的专栏 254

MacBook用户必看:用Homebrew一键搞定frp内网穿透(含阿里云配置模板)

本文为MacBook用户提供基于Homebrew的frp内网穿透实战指南,详细介绍了从环境准备到阿里云服务器配置的全过程。通过Homebrew一键安装frp客户端,结合阿里云配置模板,实现高效稳定的内网穿透,适用于远程开发、IoT设备调试等多种场景。

weixin_28692817的博客 321

Flutter 插件 device_calendar_plus 开源鸿蒙适配实战:从权限弹窗失灵到循环日程跑通

device_calendar_plus 是一个读写系统日历的 Flutter 联合插件,但无鸿蒙实现。本文完整记录开源鸿蒙适配过程:联合插件架构设计、UIAbility 上下文与权限弹窗、CalendarKit 错误码透传、循环日程实例展开四个关键坑的定位与解决,全部代码已在真机验证,附适配仓库链接。

腾讯云创作之星、华为云云享专家,深耕电商全栈高并发系统实战,分享可商用商城项目源码。 905

手把手讲解RTX5操作系统在GD32F407上的移植

然后将GD32F4xx_Demo_Suites_V2.6.4\GD32407V_START_Demo_Suites\Projects\01_GPIO_Running_LED下面的main函数复制到我们的项目文件夹中;打开对应GD32407V_START_Demo_Suites的文件夹下面的Utilities,复制gd32f407v_start.c和gd32f407v_start.h文件到一开始项目保存的文件夹下面;为了验证我们的操作系统是否被正确添加,是否能够正确运行,我们需要添加示例文件;

Praguejing的博客 1862

Ubuntu 24.04 LTS与Windows 11远程桌面连接:从xrdp闪退到xfce4桌面环境的实战排障

本文详细解决了在Ubuntu 24.04 LTS上使用xrdp连接Windows 11远程桌面时遇到的登录后闪退问题。通过分析日志,定位到默认GNOME桌面环境与xrdp的兼容性冲突,并提供了将桌面环境更换为轻量且兼容性更佳的XFCE4的完整实战解决方案,包括安装、关键配置修改及服务重启步骤,确保远程连接稳定可靠。

mango的博客 806

Flutter学习day8--Navigator路由导航与页面传参

一个稍微像样的 App,都不可能只有一个页面。从列表页点进详情页、从设置页返回、从登录页登录后跳到首页——这些「页面之间怎么跳、数据怎么带过去带回来」的问题,在 Flutter 里全部由负责。这篇文章把 Flutter 的路由导航讲清楚:导航栈的原理、push/pop 的用法、正向与反向传参,以及几个新手最容易踩的坑。

qq_63223749的博客 695

手把手教你用DLP4500和海康相机搭建双目结构光系统(附完整代码获取路径)

本文详细介绍了如何利用DLP4500投影仪和海康工业相机搭建低成本双目结构光三维测量系统。从硬件选型、软件配置到结构光编码解码实现,再到三维重建全流程,提供了完整的实战指南和优化技巧,帮助用户以不到2万元的预算实现0.05mm精度的三维扫描。

weixin_32447969的博客 317

Flutter学习day10--shared_preferences与离线优先

大多数 App 的第一版都长这样:页面数据全部来自网络,网一断,界面直接开天窗。用户不会去研究你的代码写得漂不漂亮,他们只记得"这个 App 断网就白屏"。这一篇我们解决两件事:怎么把数据存到手机本地(shared_preferences),以及怎么用"离线优先"模式让页面断网时依然有内容可看。这两点是 Flutter 客户端数据层的地基,也是网络请求(W4 前几篇)之后顺理成章的下一步。

qq_63223749的博客 279

ViViT视频分类实战:从Tubelet Embedding到4种Attention变体的保姆级解析

本文深入解析ViViT(Video Vision Transformer)在视频分类中的实战应用,重点介绍Tubelet Embedding技术和4种Attention变体的实现细节。通过对比分析Spatio-Temporal、Factorised Encoder等不同Attention机制的性能特点,提供工程优化策略和Kinetics数据集上的实战示例,帮助开发者掌握这一前沿视频理解技术。

weixin_29292535的博客 386

2026年运维工程师常见面试选择题

这篇文章总结了30道Linux运维面试常见问题及解析,涵盖基础命令、系统监控、故障排查、数据库、缓存、容器编排等多个方面。主要内容包括: 基础命令使用:如df查看磁盘、free查看内存、top监控进程等 系统监控与故障排查:负载判断、端口检查、进程管理等 中间件相关:Nginx状态码、MySQL主从复制、Redis缓存问题 容器技术:Docker优势、Kubernetes作用 运维理念:高可用、监控告警、发布策略、备份容灾 工作原则:故障处理优先级、运维工程师核心能力 文章不仅提供标准答案,还解析了各命令的

不务正业的猿 193

Flutter学习day11--Sliver滚动体系与折叠头部

几乎每个 App 的第一屏都是列表:首页信息流、频道页、商品瀑布流、带折叠头部的个人主页。Flutter 把这一整套滚动能力归拢成一个体系——。能不能把滚动写对、写顺,直接决定页面的流畅度和代码的可维护性。这篇要解决三个递进的问题:普通ListView有哪几种构造方式、为什么Column里嵌ListView会报错、以及什么时候该升级到+ Sliver 家族去实现"折叠头部 + 网格 + 列表"这种复合页面。

qq_63223749的博客 261

Vek385评估板(二):板子联网 & memtester安装(LPDDR5X测试)

继上一篇文章:Vek385评估板(一):EDF成功烧录启动及一些问题。 本章完成最主要的两个功能:开发板联网+内存 memtester。因为我是做测试行业的,所以这两个是最想试的。内存测试,除了FPGA测,也可以系统级测,本次在versal上实现这个功能,主要是可以应急。在没有安卓、X86的情况下,可以使用本方案测试。 下一篇会继续做一些其他功能。

千歌叹尽执夏的博客 222

Flutter 搭建配制教程

本文详细介绍了在Windows上搭建Flutter开发环境的完整流程,包括下载并配置Dart SDK与Flutter SDK、设置国内镜像加速下载、配置系统环境变量,以及使用VSCode进行开发。重点说明了如何安装必要插件(Dart、Flutter、CodeRunner等)、创建项目、运行调试,并对比了VSCode与AndroidStudio的适用场景。强调一套Dart代码可跨平台运行,仅在需原生功能时才涉及iOS/Swift或安卓/Kotlin开发,适合初学者快速上手Flutter全栈开发。

u013519290的博客 220

Java 类与对象 -- Java 语言的类与对象、构造器、static、final、包和 JAR

本篇文章我们将深入了解 Java 语言中的类与对象,探讨构造器、`static`、`final`、包和 JAR 文件。这些概念是面向对象编程的核心,对于理解和掌握 Java 至关重要。

栗筝i的博客 5036

visio安装包

Microsoft Office Visio 是一款专业的制图软件。利用经过更新的形状、协作工具和数据链接图表,创建专业级图表以简化复杂的信息。

Flutter 开始支持 Jetpack Compose Platform View

居然开始支持 Jetpack Compose 的了,看起来是 Flutter 在给默认补齐 AndroidX 的三个 ViewTree Owner,让 Compose 能找到生命周期、状态恢复和 ViewModel 的宿主。理论上后面应该会直接支持 Compose 放进 HC、TLHC、VD 和 HCPP 四个场景里里面。

恋猫de小郭的博客 334

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

uni-app 和 Flutter 打包上架 iOS 频繁触发 4.3 (a):原因拆解与合规处理路径

跨端方案让 iOS 上架的门槛降低了,但也带来一个明显的副作用 ——4.3 (a) 的命中率显著高于原生项目。很多开发者不理解:业务代码明明是自己写的,为什么还会被判为重复应用。这篇专门讲跨端场景,把原因和可走的路径讲清楚。

wuxianda1030的博客 187

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

Flutter 即时通讯云服务怎么选:ZEGO、融云、环信、腾讯 im 对比

ZEGO 核心包声明了移动端、Web、Windows、macOS,但 ZIMKit 只声明 Android 和 iOS。腾讯的 TUIKit 是纯 Dart 包,没有自己的平台声明,但它的依赖里包含图片选择、播放器等移动端插件,桌面端仍需逐项验证。本文不从厂商参数表出发,而是从 pub.dev 插件事实、平台声明、UI Kit 成熟度、离线推送和升级成本,对比ZEGO、融云、环信、腾讯云四家 Flutter IM SDK。腾讯覆盖华为、荣耀、小米、OPPO、vivo、魅族、FCM;都要用真机验证点击路由。

RTC之旅 203

Flutter 可以更直接地调用原生 API 了,Dart Direct Native Interop

以前 Dart 层的没有用包装函数可以被 Tree Shaking 删除,但 Native Library 需要整体打包,现在配合 Link Hook 和支持裁剪的原生工具链,就可以进一步剔除没有用到的 Native Symbol,从而优化产物大小。这对依赖 SQLite、加密库、音视频库的插件也有意义,不过它需要相应的构建和链接配置,不是说升级 Dart 后任何原生库都会自动完成裁剪。2024 年:Dart 3.5 开始在完善 C、Java、Kotlin、Objective-C 的直接互操作能力。

恋猫de小郭的博客 275

Flutter学习day9--http与Dio网络请求入门

前几周的界面无论多精致,数据都是写在代码里的常量。真实 App 的核心能力是:列表来自接口、提交落回后端。Flutter 生态里最常用的两条路是官方的http包和社区事实标准的Dio——前者适合理解网络层的本质,后者适合真实项目。这篇把两条路都走一遍,并整理一套可直接复用的请求套路。

qq_63223749的博客 274

【Flutter踩坑排雷】第23课:动画控制器泄漏坑|页面销毁未释放资源,内存持续上涨修复

AnimationController 泄漏是 Flutter 中隐蔽的性能陷阱:不崩溃、不报错,却每帧消耗资源。本文剖析三种典型泄漏场景——忘记 dispose、dispose 顺序错误、误信 TickerMode 能兜底,揭示 dispose 与 cancel、TickerMode 与生命周期的本质区别。通过 verifyTickersWereDisposed、leak_tracker、lint 及 DevTools 等工具,构建从编码到测试的系统性排查体系,并提出按“所有权”与“顺序”分层的修复与预防

jackchuanqi的博客 513

【Flutter踩坑排雷】第19课:路由硬编码坑|字符串路由书写错误,页面跳转失败排查

本文剖析了路由硬编码引发的四种“静默失联”故障:拼写错误、参数类型不匹配、兜底页面掩盖错误及字符串边界问题。根源在于路由名称为运行时验证的字符串契约,缺乏编译期检查。提出从常量集中、类型安全路由到代码生成与静态检查的五层修复方案,并建立以编译期约束为核心的预防体系,强调路由命名、类型安全与自动化门禁的重要性,实现从“运行时错误”到“编译期拦截”的系统性升级。

jackchuanqi的博客 405

从“能跑”到“可维护”:Flutter 三方库鸿蒙化的工程化实践与踩坑复盘

本文复盘了Flutter项目中webRTC三方库迁移至OpenHarmony的实战过程,聚焦GN工具链适配、NAPI异步桥接与工程治理。针对鸿蒙ABI(aarch64-linux-ohos)与Android不兼容问题,通过补丁式修改gcc_toolchain.gni注入musl启动文件与clang_rt库,解决链接崩溃;采用异步初始化避免UI线程阻塞;通过HAR/HSP混合分包策略降低主包体积8MB,结合ohpm精确版本锁定杜绝构建差异。最终沉淀可复用的GN配置、异步模板与决策清单,实现后续库迁移周期压缩至

ASS-ASH的博客 426

SerpBase Flutter 接入教程:给 App 加一个实时搜索框

上周三晚上,我在给自己的工具类 App 加「站内搜不到就走谷歌」的兜底功能。需求很朴素:用户在 App 里输关键词,两秒内看到真实的谷歌结果标题和链接。Flutter 端写 UI 我熟,麻烦的是数据源——总不能自己在后端养爬虫。试了一圈,最后用 SerpBase 的端点半小时跑通了,这篇把过程完整记下来,代码可直接抄。

Y381532662的博客 219

Flutter IM聊天项目中使用StreamContro

从这段经历可以看出,在开发IM类应用时合理选择并正确运用Flutter中的Stream机制至关重要。不仅要掌握其基本原理和高级技巧,还需要根据项目规模和技术栈做出相应设计决策。下一步建议是深入研究RxDart库以增强事件驱动能力;另外了解Flame框架对于游戏类互动界面设计也十分有帮助;最后可以尝试学习如何构建跨平台统一的消息推送服务来提升整体架构质量。

后台模板 231
上一篇: Flutter 架构实践:从 0 到 1 构建智能眼镜应用
下一篇: AR 眼镜之-直播-技术方案
Swuagg
Swuagg 新星创作者: 移动开发技术领域 新星创作者: 移动开发技术领域 领域专家: 操作系统技术领域 领域专家: 操作系统技术领域
博客等级 码龄10年 2355粉丝 153原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值