首页
统计
留言
友链
更多
图片
关于
Search
1
设置 Git 全局代理
49 阅读
2
软件开发 | flutter使用camera插件在安卓11以上的机器上调用availableCameras无法正常获得usb相机列表
48 阅读
3
scoop包管理器
47 阅读
4
Word图片压缩
45 阅读
5
利用深度搜索求解回溯问题的通用模板
45 阅读
默认分类
测试
技术开发
杂项
技术分享
相关配置
后端开发
异常处理
软件开发
登录
Search
标签搜索
java
golang
scoop
mysql
flutter
git
vue
hexo
sublime text
cpp
windows
redis
mc
mod
fabric
word
计算机网络
go
html
camera
Wisansiiz
累计撰写
32
篇文章
累计收到
2
条评论
首页
栏目
默认分类
测试
技术开发
杂项
技术分享
相关配置
后端开发
异常处理
软件开发
页面
统计
留言
友链
图片
关于
搜索到
4
篇与
的结果
2026-07-23
Pi Coding Agent:极简而可扩展的终端编程助手
从安装、日常使用到 Skills、Extensions 与会话树,认识 Pi Coding Agent 的核心设计与使用方式。
2026年07月23日
3 阅读
0 评论
0 点赞
2026-07-05
Android Studio 镜像加速 Gradle 下载
解决 Android Studio 首次同步和 Gradle 依赖下载慢的问题
2026年07月05日
3 阅读
0 评论
0 点赞
2026-06-14
开发 | Flutter本地数据持久化怎么选
背景Flutter 应用只要不是纯展示页面,基本都会遇到本地数据持久化的问题。比如:保存用户登录状态保存主题、语言、开关配置缓存接口返回的数据保存聊天记录、账单、笔记等业务数据离线时继续使用,联网后再同步这些场景看起来都叫“保存到本地”,但实际适合的方案不一样。如果一开始选错了,后面很容易出现数据结构难维护、查询困难、迁移麻烦的问题。常见方案Flutter 里常见的本地持久化方案大概有这几类:方案适合场景不适合场景shared_preferences少量配置、开关、token复杂业务数据文件存储日志、缓存、导入导出文件频繁查询和更新SQLite结构化数据、本地业务表简单配置项Drift类型安全的 SQLite 封装很小的 demoHive/Isar本地对象存储、轻量缓存强关系型查询我的习惯是先按照“数据复杂度”和“查询需求”来选。shared_preferencesshared_preferences 适合保存少量键值数据,比如主题、语言、是否首次启动。final prefs = await SharedPreferences.getInstance(); await prefs.setBool('is_dark_mode', true); await prefs.setString('token', 'xxx'); final isDarkMode = prefs.getBool('is_dark_mode') ?? false; final token = prefs.getString('token');它的优点是简单,缺点也很明显:不适合存复杂对象,更不适合存列表型业务数据。比较适合保存:theme_modelocaleaccess_tokenfirst_launchlast_login_user_id不建议保存:聊天消息账单明细商品列表复杂用户资料文件存储文件存储适合保存一些不需要复杂查询的内容,比如日志、图片缓存、导入导出的 JSON。final directory = await getApplicationDocumentsDirectory(); final file = File('${directory.path}/notes.json'); await file.writeAsString(jsonEncode([ {'title': 'Flutter', 'content': 'local storage'}, ])); final content = await file.readAsString(); final data = jsonDecode(content);这种方式直观,但问题是查询、更新、删除都要自己处理。数据少的时候没问题,数据一多就会变得很难维护。如果数据本身天然就是文件,比如用户导出的备份、图片、日志,那文件存储很合适。如果是业务表数据,还是应该考虑数据库。SQLiteSQLite 适合保存结构化业务数据。比如一个记账应用,账单记录就很适合放到 SQLite:CREATE TABLE ledger_entries ( id INTEGER PRIMARY KEY AUTOINCREMENT, title TEXT NOT NULL, amount REAL NOT NULL, type TEXT NOT NULL, created_at INTEGER NOT NULL );它的优点是:支持条件查询支持排序、分页支持事务支持索引数据结构清晰缺点是直接写 SQL 会比较繁琐,字段改动后迁移也要自己处理。Drift如果项目里本地数据比较重要,我更倾向于用 Drift。它本质上还是 SQLite,但提供了更好的类型安全和 Dart 层封装。表定义大概是这样:class LedgerEntries extends Table { IntColumn get id => integer().autoIncrement()(); TextColumn get title => text()(); RealColumn get amount => real()(); TextColumn get type => text()(); DateTimeColumn get createdAt => dateTime()(); }查询可以写成:Future<List<LedgerEntry>> getEntries() { return (select(ledgerEntries) ..orderBy([ (table) => OrderingTerm.desc(table.createdAt), ])) .get(); }插入数据:Future<int> insertEntry(LedgerEntriesCompanion entry) { return into(ledgerEntries).insert(entry); }Drift 的好处是数据层更清楚,适合业务长期迭代。缺点是上手成本比 shared_preferences 高,也需要配置代码生成。怎么选可以简单按这个规则判断:需求推荐方案保存几个配置项shared_preferences保存 token、主题、语言shared_preferences保存日志或导出文件文件存储保存列表数据SQLite 或 Drift需要条件查询、分页、排序SQLite 或 Drift项目会长期维护Drift只是快速 demoshared_preferences 或简单文件还有一个比较实用的判断方法:如果你开始想给数据加 id、createdAt、updatedAt,并且需要按条件查询,那基本就该用数据库了。分层建议不要在页面里直接操作数据库。比较清晰的结构是:presentation 页面和状态 domain 实体和业务接口 data 本地数据库、接口实现页面只调用 repository:final entries = await ledgerRepository.getEntries();具体数据来自 SQLite、接口还是缓存,页面不用关心。这样后面从本地数据切到服务端同步,也不会大面积改 UI。小结Flutter 本地持久化不要只看“哪个库简单”,要先看数据类型。我的选择习惯是:少量配置用 shared_preferences文件内容用文件存储业务数据用 SQLite长期维护的业务数据用 Drift如果只是保存一个开关,用数据库会显得太重;如果已经是账单、聊天记录、笔记这类数据,还继续用 JSON 文件或键值存储,后面维护成本会越来越高。
2026年06月14日
1 阅读
0 评论
0 点赞
2025-09-24
软件开发 | flutter使用camera插件在安卓11以上的机器上调用availableCameras无法正常获得usb相机列表
flutter使用camera插件在安卓11以上的机器上调用availableCameras无法正常获得usb相机列表问题描述: flutter_camera,在安卓7和10上都能正常获取相机列表,但是在安卓11上确因为验证失败无法读取相机, 可能 是高版本的安卓权限更加严格,usb相机必须使用external解决办法:ctrl+左键进入源文件 // 自己的代码 Future<bool> getCamera() async { try { _cameras = await availableCameras(); if (_cameras.isEmpty) { return false; } return true; } catch (e) { return false; } }// 源文件1, 继续跟进 Future<List<CameraDescription>> availableCameras() async { return CameraPlatform.instance.availableCameras(); }从实现里找到安卓平台添加这么一段代码,写法和他原来一样就行,把类型改为externalelse if ((await proxy .newCameraSelector(requireLensFacing: LensFacing.external) .filter(<CameraInfo>[cameraInfo])).isNotEmpty) { cameraLensDirection = CameraLensDirection.external; }
2025年09月24日
48 阅读
0 评论
0 点赞