Android使用Geoquery查询Firestore时startAfter导致查询重复执行问题
问题根源
你遇到的重复查询/重复返回结果问题,核心是两个错误逻辑导致的:
- GeoFireUtils返回的
GeoQueryBounds是多个独立的地理哈希查询区间(通常为4~9个),每个区间的查询结果和分页游标完全独立,你用全局统一的lastVisible作为所有区间的startAfter锚点,完全不符合多边界查询的分页逻辑 - 分页查询中同时使用
startAt(b.startHash)和startAfter(lastVisible),游标锚点和区间起始条件冲突,会导致部分相邻区间的查询重复命中同一批数据,出现重复执行、重复返回的问题
修复方案
1. 新增全局变量存储每个边界的独立状态
// 存储每个geohash边界对应的分页游标 private List<DocumentSnapshot> boundaryCursors = new ArrayList<>(); // 标记每个边界是否已经查询完毕无更多数据 private List<Boolean> boundaryFinished = new ArrayList<>(); // 分页请求锁,避免滚动时重复触发请求 private boolean isLoading = false;
2. 修正首次查询逻辑
if (isLoading) return; isLoading = true; List<GeoQueryBounds> bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM); // 初始化每个边界的状态 boundaryCursors.clear(); boundaryFinished.clear(); for (int i = 0; i < bounds.size(); i++) { boundaryCursors.add(null); boundaryFinished.add(false); } final List<Task<QuerySnapshot>> tasks = new ArrayList<>(); for (int i = 0; i < bounds.size(); i++) { GeoQueryBounds b = bounds.get(i); Query firstQuery = db.collection("posts") .whereEqualTo("country_code", sharedpreferences.getString("country_code", "")) .orderBy("geohash") .startAt(b.startHash) .endAt(b.endHash) .limit(10); final int index = i; firstQuery.get().addOnCompleteListener(task -> { if (task.isSuccessful() && task.getResult() != null) { QuerySnapshot snapshot = task.getResult(); if (!snapshot.isEmpty()) { // 存储当前边界专属的分页游标 boundaryCursors.set(index, snapshot.getDocuments().get(snapshot.size() - 1)); } else { // 标记当前边界无更多数据,后续分页跳过 boundaryFinished.set(index, true); } } // 待所有任务完成后合并结果,用文档ID去重后更新列表 // 全部处理完后重置 isLoading = false }); tasks.add(firstQuery.get()); }
3. 修正分页查询逻辑
if (isLoading) return; isLoading = true; List<GeoQueryBounds> bounds = GeoFireUtils.getGeoHashQueryBounds(center, radiusInM); final List<Task<QuerySnapshot>> nextTasks = new ArrayList<>(); for (int i = 0; i < bounds.size(); i++) { // 跳过已经没有更多数据的边界 if (boundaryFinished.get(i)) continue; GeoQueryBounds b = bounds.get(i); Query nextQuery = db.collection("posts") .whereEqualTo("country_code", sharedpreferences.getString("country_code", "")) .orderBy("geohash") .endAt(b.endHash); // 用当前边界专属的游标分页,不要用全局lastVisible DocumentSnapshot cursor = boundaryCursors.get(i); if (cursor != null) { nextQuery = nextQuery.startAfter(cursor); } else { nextQuery = nextQuery.startAt(b.startHash); } nextQuery = nextQuery.limit(10); final int index = i; nextQuery.get().addOnCompleteListener(task -> { if (task.isSuccessful() && task.getResult() != null) { QuerySnapshot snapshot = task.getResult(); if (!snapshot.isEmpty()) { boundaryCursors.set(index, snapshot.getDocuments().get(snapshot.size() - 1)); } else { boundaryFinished.set(index, true); } } // 待所有任务完成后合并结果,用文档ID去重后追加到列表 // 全部处理完后重置 isLoading = false }); nextTasks.add(nextQuery.get()); }
额外注意
合并所有边界返回的结果时,必须用文档ID做去重处理,相邻的geohash查询区间天然存在少量重叠,会返回部分相同的文档。
内容的提问来源于stack exchange,提问作者Nasir
相关产品推荐
相关产品推荐

