锐英源软件
第一信赖

精通

英语

开源

擅长

开发

培训

胸怀四海 

第一信赖

当前位置:锐英源 / 开源技术 / Flutter多端开发快速入门、Flutter基于AI开发时的注意事项
联系方式
固话:0371-63888850
手机:138-0381-0136
Q Q:396806883
微信:ryysoft
微信公众号
微信扫一扫,新经验快学到
服务方向
人工智能数据处理
人工智能培训
小语种语音识别
语音识别标注
语音识别系统
语音识别转文字
软件开发
视觉检测
建筑视觉检测
养殖视觉检测
运动控制卡上位机
机械加工软件
软件开发培训
Java 安卓移动开发
VC++
C#软件
汇编和破解
驱动开发

锐英源精品原创文章,禁止转载和任何形式的非法内容使用,违者必究


Flutter多端开发快速入门、Flutter基于AI开发时的注意事项


背景

前几天,我需要开发手机App,AI推荐了Flutter,并快速生成了代码,连接FastAPI接口,获取数据,绘制结果,花了200左右的积分,迅速完成。AI还贴心地生成了手机拍照功能,拍照照片上传到FastAPI服务器上。现在普遍对AI生成代码不太放心,这里强调两点FastAPI的安装部署和运行,Flutter的内存泄漏问题,不是说Flutter会轻易内存泄漏,Flutter是成熟的平台,我只是说从某个点来讲内存泄漏,让普通程序员和新手怎么查泄漏,有谨慎心态能落地实践。

Dart是Google开发的⼀种面向对象的计算机编程语言,和Java类似。Flutter是Google开源的UI⼯具包,帮助开发者通过⼀套代码库高效构建多平台精美应用,支持移动、Web、桌面和嵌入式平台。 Dart是flutter的程序开发语言。Flutter能被AI推荐,有大量的QQ群,群里有很多爱好者,都证明Flutter的成功。Flutter多端就是Flutter多平台的意思,移动、Web、桌面和嵌入式平台都可以用Flutter开发,所以叫Flutter多端。这里面最重要的是苹果的iOS开发,xcode需要运行在iOS下,一般是用虚拟机来做,环境不太有好。Flutter的一次开发,多端(多平台)兼容,会节省很多时间。如果不用Flutter,需要掌握多门编程语言和多个开发环境,工作量大。


Flutter项目的具体架构

# 牛身检测 Flutter App

一个简单的Flutter应用,用于上传牛只图片并显示检测到的牛身轮廓。

## 项目结构

```
cowapp/
├── lib/
│ ├── main.dart # 应用入口
│ ├── home_page.dart # 主页面(按钮+图片控件)
│ ├── services/
│ │ └── api_service.dart # API服务(与后端通信)
│ └── widgets/
│ └── contour_painter.dart # 轮廓绘制组件
├── pubspec.yaml # 项目配置
└── README.md # 本文件
```

## 功能

1. 点击按钮从图库选择牛只图片
2. 自动上传到后端服务器进行检测
3. 显示检测到的牛身轮廓(绿色线条)
4. 在原图上叠加显示轮廓结果

## 技术栈

- **Flutter**: 前端框架
- **image_picker**: 图片选择
- **http**: 网络请求
- **CustomPainter**: 轮廓绘制

## 运行步骤

### 1. 启动后端服务

```bash
cd d:\cow
python server.py
```

服务将在 `http://localhost:8000` 启动。

### 2. 安装Flutter依赖

```bash
cd d:\cowapp
flutter pub get
```

### 3. 运行Flutter应用

```bash
flutter run
```

## API接口

### POST /view_body_new_service

**请求**:
- Content-Type: multipart/form-data
- Body: 图片文件(字段名: file)

**响应**:
```json
{
"success": true,
"message": "检测成功",
"contour_points": [[x1,y1], [x2,y2], ...],
"image_width": 4096,
"image_height": 3072
}
```

## 注意事项

1. **后端服务地址**: 默认连接到 `http://localhost:8000`,可在 `api_service.dart` 中修改

2. **图片大小**: 支持最大4096x3072分辨率,会自动缩放显示

3. **轮廓绘制**: 使用绿色线条绘制,红色圆点标记起点

4. **缩放处理**: CustomPainter 会根据原图尺寸和显示尺寸自动计算缩放比例,确保轮廓点正确对齐

## 待优化功能

- [ ] 添加进度显示
- [ ] 支持相机拍照
- [ ] 显示更多检测信息(如牛身特征)
- [ ] 支持多张图片批量处理
- [ ] 离线模式缓存

 

Flutter多端开发能力在这个牛身App里有体现,在Windows下可以运行,编译成apk就可以在安卓手机上运行,在Windows下功能测试完成,则安卓手机上的测试就相对简单多了。

 

contour_painter.dart轮廓绘制组件代码解析和内存泄漏分析

import 'package:flutter/material.dart';//导入库
import 'package:flutter/foundation.dart';

class ContourPainter extends CustomPainter {
  final List<List<int>> contourPoints;//坐标数组
  final int imageWidth;
  final int imageHeight;
  final Color contourColor;
  final double strokeWidth;

  ContourPainter({
    required this.contourPoints,
    required this.imageWidth,
    required this.imageHeight,
    this.contourColor = Colors.green,
    this.strokeWidth = 3.0,
  });

  @override
  void paint(Canvas canvas, Size size) {//重写的绘制函数
    if (kDebugMode) {
      print('[ContourPainter] paint 被调用');
      print('[ContourPainter] 画布尺寸: ${size.width}x${size.height}');
      print('[ContourPainter] 原图尺寸: ${imageWidth}x$imageHeight');
      print('[ContourPainter] 轮廓点数: ${contourPoints.length}');
      if (contourPoints.isNotEmpty) {
        print('[ContourPainter] 第一个点: ${contourPoints.first}');
        print('[ContourPainter] 最后一个点: ${contourPoints.last}');
      }
    }

    if (contourPoints.isEmpty || imageWidth == 0 || imageHeight == 0) {
      if (kDebugMode) {
        print('[ContourPainter] 提前返回(数据为空)');
      }
      return;
    }

    // 现在外层 FittedBox 统一缩放,画布 size 应等于原图像素尺寸
    // 这里仍然计算一个 scale 以防御不一致情况
    final scaleX = size.width / imageWidth;
    final scaleY = size.height / imageHeight;
    final scale = (scaleX > 0 && scaleY > 0)
        ? scaleX // 正常情况下 scaleX == scaleY == 1.0(因为 FittedBox)
        : 1.0;

    if (kDebugMode) {
      print('[ContourPainter] scaleX=$scaleX, scaleY=$scaleY, scale=$scale');
    }

    // 创建画笔
    final paint = Paint()
      ..color = contourColor
      ..strokeWidth = strokeWidth // 直接使用像素线宽,FittedBox会整体缩放
      ..style = PaintingStyle.stroke
      ..strokeCap = StrokeCap.round
      ..strokeJoin = StrokeJoin.round;

    // 绘制轮廓路径
    if (contourPoints.length > 1) {
      final path = Path();

      // 移动到第一个点
      final startX = contourPoints[0][0] * scale;
      final startY = contourPoints[0][1] * scale;
      path.moveTo(startX, startY);

      if (kDebugMode) {
        print('[ContourPainter] 路径起点: ($startX, $startY)');
      }

      // 连接所有轮廓点
      for (int i = 1; i < contourPoints.length; i++) {
        final x = contourPoints[i][0] * scale;
        final y = contourPoints[i][1] * scale;
        path.lineTo(x, y);
      }

      // 闭合路径
      path.close();

      // 绘制路径
      canvas.drawPath(path, paint);
      if (kDebugMode) {
        print('[ContourPainter] 路径已绘制');
      }
    }

    // 绘制起点标记(红色圆点)
    if (contourPoints.isNotEmpty) {
      final startX = contourPoints[0][0] * scale;
      final startY = contourPoints[0][1] * scale;

      final startPaint = Paint()
        ..color = Colors.red
        ..style = PaintingStyle.fill;

      canvas.drawCircle(
        Offset(startX, startY),
        6.0, // 固定像素大小,外层会缩放
        startPaint,
      );
    }

    // 绘制所有轮廓点(小圆点),间隔绘制避免太密集
    final pointPaint = Paint()
      ..color = contourColor.withValues(alpha: 0.6)
      ..style = PaintingStyle.fill;

    for (int i = 0; i < contourPoints.length; i++) {
      if (i % 5 == 0) {
        final x = contourPoints[i][0] * scale;
        final y = contourPoints[i][1] * scale;
        canvas.drawCircle(
          Offset(x, y),
          2.0,
          pointPaint,
        );
      }
    }
  }

  @override
  bool shouldRepaint(covariant ContourPainter oldDelegate) {//重写的触发绘制函数
    // 当轮廓点、颜色、线宽、图片尺寸变化时重绘
    return oldDelegate.contourPoints != contourPoints ||
        oldDelegate.contourColor != contourColor ||
        oldDelegate.strokeWidth != strokeWidth ||
        oldDelegate.imageWidth != imageWidth ||
        oldDelegate.imageHeight != imageHeight;
  }
}

dart语言类似Java,当然也像C#,这里面没有指针,使用还是很方便。这里面Path()是常用的资源对象,资源对象用不好会导致内存泄漏。Flutter在频繁分配Path()时会出问题,但是这些代码明显是不频繁的,是可以接受的。为什么不频繁?paint绘制的前提是数据对象改变让shouldRepaint返回真,才绘制,不频繁绘制就不会分配Path()对象,所以代码是正确的。其它的对象不涉及内存泄漏。

友情链接
版权所有 Copyright(c)2004-2021 锐英源软件
公司注册号:410105000449586 豫ICP备08007559号 最佳分辨率 1024*768
地址:郑州市金水区文化路97号郑州大学北校区院内原校办工厂楼101室