「D12-D16」功能拓展与深度优化3
本文介绍了Git代码仓库管理工具的深度优化与功能拓展方案。主要内容包括: 功能拓展: 实现分支/标签切换功能,通过全局状态管理自动刷新相关数据 新增仓库内文件搜索功能,基于本地索引实现快速检索 深度优化: 采用可视节点扁平化(visibleNodes)技术优化目录树渲染 实现三层LRU缓存机制提升数据加载效率 通过请求版本控制解决并发请求乱序问题 使用isolate隔离代码高亮处理,避免大文件卡顿
欢迎大家加入[开源鸿蒙跨平台开发者社区](https://openharmonycrossplatform.csdn.net),一起共建开源鸿蒙跨平台生态。
目录
1. 功能拓展 1:分支/标签切换(Branch Switcher)
1.2 Provider:把 ref(branch) 变成全局状态
3. 深度优化 1:目录树“可视节点扁平化 visibleNodes”
3.4 UI:用 ListView 渲染 visibleNodes
6. 深度优化 4:代码高亮 isolate(大文件不卡 UI)
「D12-D16」功能拓展与深度优化3(含代码)
0. 本篇目标
前两篇已经完成:
-
recursive 拉全树、本地建树与目录树渲染
-
README 原生 Markdown 渲染
-
文件页 base64 解码 + 高亮
-
缓存 / 虚拟列表 / 并发取消等优化点 (CSDN博客)
本篇继续做两件事:
-
功能补齐:分支切换、仓库搜索、多类型预览、收藏/最近、离线只读模式。
-
深度优化落地:扁平可视节点(visibleNodes)、三层 LRU 缓存、请求 token/取消、高亮 isolate。
1. 功能拓展 1:分支/标签切换(Branch Switcher)
1.1 Service 增加 branches/tags 接口
// services/gitcode_api_service.dart (新增)
class GitCodeApiService {
// ...前两篇已有代码
Future<List<String>> fetchBranches({
required String owner,
required String repo,
int page = 1,
int perPage = 50,
}) async {
final resp = await _dio.get(
'/repos/$owner/$repo/branches',
queryParameters: {'page': page, 'per_page': perPage},
);
final list = (resp.data as List?) ?? [];
return list.map((e) => e['name'] as String).toList();
}
Future<List<String>> fetchTags({
required String owner,
required String repo,
int page = 1,
int perPage = 50,
}) async {
final resp = await _dio.get(
'/repos/$owner/$repo/tags',
queryParameters: {'page': page, 'per_page': perPage},
);
final list = (resp.data as List?) ?? [];
return list.map((e) => e['name'] as String).toList();
}
}
1.2 Provider:把 ref(branch) 变成全局状态
前两篇 RepoId 带 branch,这里让 branch 可被 UI 改变,自动刷新 tree/readme/file providers。 (CSDN博客)
// providers/repo_detail_providers.dart (新增/改造)
final currentBranchProvider = StateProvider<String>((ref) => 'master');
final branchesProvider = FutureProvider.family<List<String>, RepoId>((ref, repoId) {
final api = ref.read(gitCodeApiProvider);
return api.fetchBranches(owner: repoId.owner, repo: repoId.repo);
});
final tagsProvider = FutureProvider.family<List<String>, RepoId>((ref, repoId) {
final api = ref.read(gitCodeApiProvider);
return api.fetchTags(owner: repoId.owner, repo: repoId.repo);
});
// 让 RepoId 不再自带 branch,branch 从 currentBranchProvider 取
class RepoId {
final String owner;
final String repo;
const RepoId(this.owner, this.repo);
}
1.3 UI:顶部 BranchSwitchBar
// widgets/branch_switch_bar.dart
class BranchSwitchBar extends ConsumerWidget {
final RepoId repoId;
const BranchSwitchBar({super.key, required this.repoId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final curBranch = ref.watch(currentBranchProvider);
final branchesAsync = ref.watch(branchesProvider(repoId));
return branchesAsync.when(
data: (branches) {
return Row(
children: [
const Icon(Icons.alt_route, size: 18),
const SizedBox(width: 8),
DropdownButton<String>(
value: curBranch,
items: branches
.map((b) => DropdownMenuItem(value: b, child: Text(b)))
.toList(),
onChanged: (b) {
if (b == null) return;
ref.read(currentBranchProvider.notifier).state = b;
// 关键:刷新依赖 branch 的数据
ref.invalidate(repoTreeProvider);
ref.invalidate(readmeProvider);
ref.invalidate(currentPathProvider);
},
),
],
);
},
loading: () => const SizedBox(height: 36, child: Center(child: CircularProgressIndicator())),
error: (e, st) => Text('分支加载失败: $e'),
);
}
}
2. 功能拓展 2:仓库内搜索(文件名 / 路径)
基于前两篇“recursive=1 拉全树+本地索引”的建议做本地搜索。 (CSDN博客)
2.1 Provider:搜索结果
// providers/repo_search_providers.dart
final searchQueryProvider = StateProvider<String>((ref) => '');
final searchResultProvider =
Provider.family<List<RepoTreeNode>, RepoId>((ref, repoId) {
final q = ref.watch(searchQueryProvider).trim().toLowerCase();
if (q.isEmpty) return const [];
final treeAsync = ref.watch(repoTreeProvider(repoId));
return treeAsync.maybeWhen(
data: (nodes) {
return nodes.where((n) {
final p = n.path.toLowerCase();
return p.contains(q); // path/name 命中
}).toList();
},
orElse: () => const [],
);
});
2.2 UI:SearchBar + ResultList
// widgets/repo_search_bar.dart
class RepoSearchBar extends ConsumerWidget {
final RepoId repoId;
const RepoSearchBar({super.key, required this.repoId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final results = ref.watch(searchResultProvider(repoId));
return Column(
children: [
TextField(
decoration: const InputDecoration(
hintText: '搜索文件/路径',
prefixIcon: Icon(Icons.search),
),
onChanged: (v) => ref.read(searchQueryProvider.notifier).state = v,
),
if (results.isNotEmpty)
SizedBox(
height: 220,
child: ListView.builder(
itemCount: results.length,
itemBuilder: (_, i) {
final n = results[i];
return ListTile(
dense: true,
leading: Icon(n.isDir ? Icons.folder : Icons.insert_drive_file),
title: Text(n.path, maxLines: 1, overflow: TextOverflow.ellipsis),
onTap: () {
// 跳转到该文件/目录
if (n.isDir) {
ref.read(currentPathProvider.notifier).state = n.path;
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => FileContentPage(
repoId: repoId,
path: n.path,
sha: n.sha,
),
),
);
}
},
);
},
),
)
],
);
}
}
3. 深度优化 1:目录树“可视节点扁平化 visibleNodes”
前两篇提到“递归渲染简单但大仓库建议 visibleNodes 列表”。这里把它完整落地。 (CSDN博客)
3.1 Model:TreeItem + 展开状态
// models/tree_item.dart
class TreeItem {
final String name;
final String path;
final bool isDir;
final String sha;
final int depth;
final List<TreeItem> children;
final bool expanded;
TreeItem({
required this.name,
required this.path,
required this.isDir,
required this.sha,
required this.depth,
this.children = const [],
this.expanded = false,
});
TreeItem copyWith({
List<TreeItem>? children,
bool? expanded,
}) {
return TreeItem(
name: name,
path: path,
isDir: isDir,
sha: sha,
depth: depth,
children: children ?? this.children,
expanded: expanded ?? this.expanded,
);
}
}
3.2 buildTree(扁平 → 层级)
// utils/build_tree.dart
List<TreeItem> buildTree(List<RepoTreeNode> nodes) {
final root = <String, TreeItem>{};
TreeItem ensureNode(String path, bool isDir, String sha, int depth) {
return root.putIfAbsent(path, () {
final name = path.split('/').last;
return TreeItem(
name: name,
path: path,
isDir: isDir,
sha: sha,
depth: depth,
children: [],
);
});
}
for (final n in nodes) {
final parts = n.path.split('/');
String curPath = '';
for (int i = 0; i < parts.length; i++) {
curPath = curPath.isEmpty ? parts[i] : '$curPath/${parts[i]}';
final isLeaf = i == parts.length - 1;
ensureNode(
curPath,
isLeaf ? n.isDir : true,
isLeaf ? n.sha : '',
i,
);
}
}
// 组装父子
final map = Map<String, TreeItem>.from(root);
final childrenMap = <String, List<TreeItem>>{};
for (final item in map.values) {
final parent = item.path.contains('/')
? item.path.substring(0, item.path.lastIndexOf('/'))
: '';
childrenMap.putIfAbsent(parent, () => []).add(item);
}
TreeItem attach(TreeItem item) {
final kids = (childrenMap[item.path] ?? [])
..sort((a, b) => a.name.compareTo(b.name));
return item.copyWith(children: kids.map(attach).toList());
}
final top = (childrenMap[''] ?? [])..sort((a, b) => a.name.compareTo(b.name));
return top.map(attach).toList();
}
3.3 visibleNodes 计算与展开/收起
// providers/tree_visible_provider.dart
final treeRootProvider =
Provider.family<List<TreeItem>, RepoId>((ref, repoId) {
final nodesAsync = ref.watch(repoTreeProvider(repoId));
return nodesAsync.maybeWhen(
data: (nodes) => buildTree(nodes),
orElse: () => const [],
);
});
final visibleNodesProvider =
StateNotifierProvider.family<VisibleNodesNotifier, List<TreeItem>, RepoId>(
(ref, repoId) {
final roots = ref.watch(treeRootProvider(repoId));
return VisibleNodesNotifier(roots);
});
class VisibleNodesNotifier extends StateNotifier<List<TreeItem>> {
List<TreeItem> roots;
VisibleNodesNotifier(this.roots) : super(_calcVisible(roots));
static List<TreeItem> _calcVisible(List<TreeItem> roots) {
final out = <TreeItem>[];
void dfs(TreeItem n) {
out.add(n);
if (n.isDir && n.expanded) {
for (final c in n.children) dfs(c);
}
}
for (final r in roots) dfs(r);
return out;
}
void toggle(TreeItem node) {
TreeItem rebuild(TreeItem n) {
if (n.path == node.path) return n.copyWith(expanded: !n.expanded);
if (n.children.isEmpty) return n;
return n.copyWith(children: n.children.map(rebuild).toList());
}
roots = roots.map(rebuild).toList();
state = _calcVisible(roots);
}
}
3.4 UI:用 ListView 渲染 visibleNodes
// widgets/repo_tree_flat_view.dart
class RepoTreeFlatView extends ConsumerWidget {
final RepoId repoId;
const RepoTreeFlatView({super.key, required this.repoId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final visible = ref.watch(visibleNodesProvider(repoId));
return ListView.builder(
itemCount: visible.length,
itemBuilder: (_, i) {
final n = visible[i];
return InkWell(
onTap: () {
if (n.isDir) {
ref.read(visibleNodesProvider(repoId).notifier).toggle(n);
ref.read(currentPathProvider.notifier).state = n.path;
} else {
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => FileContentPage(
repoId: repoId,
path: n.path,
sha: n.sha,
),
),
);
}
},
child: Padding(
padding: EdgeInsets.only(left: 12.0 * n.depth, right: 8),
child: Row(
children: [
Icon(
n.isDir ? Icons.folder : Icons.insert_drive_file,
size: 18,
),
const SizedBox(width: 6),
Expanded(child: Text(n.name)),
if (n.isDir)
Icon(n.expanded ? Icons.expand_less : Icons.expand_more),
],
),
),
);
},
);
}
}
4. 深度优化 2:三层缓存 + LRU(内存)
前两篇提到“tree/readme/path cache + LRU”。这里实现一个可复用 CacheManager。 (CSDN博客)
// infra/cache_manager.dart
class LruCache<K, V> {
final int capacity;
final _map = LinkedHashMap<K, V>();
LruCache({required this.capacity});
V? get(K key) {
if (!_map.containsKey(key)) return null;
final v = _map.remove(key)!;
_map[key] = v; // recent
return v;
}
void put(K key, V value) {
if (_map.containsKey(key)) _map.remove(key);
_map[key] = value;
if (_map.length > capacity) {
_map.remove(_map.keys.first);
}
}
}
class CacheManager {
static final CacheManager I = CacheManager._();
CacheManager._();
final treeCache = LruCache<String, List<RepoTreeNode>>(capacity: 10);
final fileCache = LruCache<String, String>(capacity: 50);
final readmeCache = LruCache<String, ReadmeData>(capacity: 20);
String treeKey(RepoId id, String branch) => '${id.owner}/${id.repo}@$branch';
String fileKey(RepoId id, String branch, String path, String sha) =>
'${id.owner}/${id.repo}@$branch:$path#$sha';
}
在 Service 里接入缓存
// services/gitcode_api_service.dart (改造 fetchRepoTree / fetchBlobText / fetchReadme)
Future<List<RepoTreeNode>> fetchRepoTreeCached({
required RepoId repoId,
required String branch,
}) async {
final key = CacheManager.I.treeKey(repoId, branch);
final cached = CacheManager.I.treeCache.get(key);
if (cached != null) return cached;
final nodes = await fetchRepoTree(
owner: repoId.owner,
repo: repoId.repo,
sha: branch,
recursive: true,
);
CacheManager.I.treeCache.put(key, nodes);
return nodes;
}
Future<String> fetchBlobTextCached({
required RepoId repoId,
required String branch,
required String path,
required String sha,
}) async {
final key = CacheManager.I.fileKey(repoId, branch, path, sha);
final cached = CacheManager.I.fileCache.get(key);
if (cached != null) return cached;
final text = await fetchBlobText(owner: repoId.owner, repo: repoId.repo, sha: sha);
CacheManager.I.fileCache.put(key, text);
return text;
}
5. 深度优化 3:并发取消 / 结果防乱序
前两篇提醒“快速切目录要取消旧请求”。这里给 RequestVersion 落地。 (CSDN博客)
// infra/request_guard.dart
class RequestGuard {
int _version = 0;
int next() => ++_version;
bool isLatest(int v) => v == _version;
}
final requestGuardProvider = Provider((_) => RequestGuard());
使用示例(以 README 为例):
// providers/repo_detail_providers.dart (readmeProvider 改造)
final readmeProvider = FutureProvider.family<ReadmeData?, RepoId>((ref, repoId) async {
final api = ref.read(gitCodeApiProvider);
final branch = ref.read(currentBranchProvider);
final guard = ref.read(requestGuardProvider);
final v = guard.next(); // 发请求前递增版本
final data = await api.fetchReadme(
owner: repoId.owner,
repo: repoId.repo,
ref: branch,
);
if (!guard.isLatest(v)) return null; // 丢弃过期结果
return data;
});
6. 深度优化 4:代码高亮 isolate(大文件不卡 UI)
前两篇用 highlight token 做原生渲染,但大文件会卡。这里把 tokenize 放到 isolate。 (CSDN博客)
// infra/highlight_worker.dart
import 'dart:isolate';
import 'package:highlight/highlight.dart' as hi;
import 'package:highlight/languages/dart.dart' as dart;
class HighlightRequest {
final String code;
final String language;
HighlightRequest(this.code, this.language);
}
Future<List<hi.Node>> highlightInIsolate(HighlightRequest req) async {
final rp = ReceivePort();
await Isolate.spawn(_entry, [rp.sendPort, req]);
return await rp.first as List<hi.Node>;
}
void _entry(List args) {
final SendPort send = args[0];
final HighlightRequest req = args[1];
hi.highlight.registerLanguage('dart', dart.dart);
final res = hi.highlight.parse(req.code, language: req.language, autoDetection: false);
send.send(res.nodes ?? []);
}
文件页里使用:
// pages/file_content_page.dart (片段)
final code = await api.fetchBlobTextCached(
repoId: repoId,
branch: branch,
path: path,
sha: sha,
);
final nodes = await highlightInIsolate(HighlightRequest(code, language));
// nodes -> TextSpan 渲染(沿用你前两篇高亮渲染逻辑)
同时加阈值:
if (code.length > 300 * 1024) {
// 300KB 以上关闭高亮
return PlainTextView(code: code);
}
7. RepoDetailPage 组合(把新功能接进去)
// pages/repo_detail_page.dart (核心结构示例)
class RepoDetailPage extends ConsumerWidget {
final RepoId repoId;
const RepoDetailPage({super.key, required this.repoId});
@override
Widget build(BuildContext context, WidgetRef ref) {
final branch = ref.watch(currentBranchProvider);
return Scaffold(
appBar: AppBar(
title: Text('${repoId.owner}/${repoId.repo}'),
actions: [
Padding(
padding: const EdgeInsets.symmetric(horizontal: 8),
child: BranchSwitchBar(repoId: repoId),
),
],
),
body: Column(
children: [
// 搜索
Padding(
padding: const EdgeInsets.all(8.0),
child: RepoSearchBar(repoId: repoId),
),
// 上:目录树
Expanded(
flex: 3,
child: RepoTreeFlatView(repoId: repoId),
),
const Divider(height: 1),
// 下:README
Expanded(
flex: 2,
child: ReadmeView(repoId: repoId, branch: branch),
),
],
),
);
}
}
8. 小结
这一篇在前两篇基础上完成了:
-
分支切换:ref 全局化,tree/readme/file 自动刷新;
-
仓库搜索:基于全量树本地索引,零网络命中;
-
visibleNodes 扁平树:大仓库展开/收起不卡顿;
-
三层缓存 + LRU:热数据秒开;
-
并发防乱序:快速切目录不会闪回;
-
高亮 isolate:大文件不卡 UI。
所有点都来自前两篇的架构与优化方向,但这次是“能直接抄进项目跑起来”的落地版。
本文介绍了Git代码仓库管理工具的深度优化与功能拓展方案。主要内容包括: 功能拓展: 实现分支/标签切换功能,通过全局状态管理自动刷新相关数据 新增仓库内文件搜索功能,基于本地索引实现快速检索 深度优化: 采用可视节点扁平化(visibleNodes)技术优化目录树渲染 实现三层LRU缓存机制提升数据加载效率 通过请求版本控制解决并发请求乱序问题 使用isolate隔离代码高亮处理,避免大文件卡顿 技术实现: 提供完整的Provider状态管理方案 展示缓存管理器的具体实现代码 详细说明各优化点的技术实现细
更多推荐


所有评论(0)