Android Studio搜索异常:快速输入下划线致ListView条目重复
从你描述的情况来看——快速连续输入下划线_时ListView出现重复条目,慢速输入正常——这个问题几乎可以确定是搜索请求的并发堆积+数据源未正确清空导致的。下面是具体的分析和解决办法:
问题根源
当你快速输入时,每输入一个字符(包括下划线)都会触发一次搜索请求,但前一次的JDBC查询可能还在后台线程执行中(JDBC操作本身是阻塞的,尤其是数据库查询耗时的时候),新的请求又发起了。如果你的代码没有处理这种并发情况,旧请求的结果会在新请求结果返回之后才更新到ListView,或者两次结果都被添加到了同一个数据源里,就会出现条目重复。
另外,下划线作为SQL里的通配符(比如LIKE查询中的匹配符),如果你的JDBC查询用了LIKE '%keyword%',输入下划线时可能需要额外转义处理,导致查询耗时比普通字符更长,这也是为什么只有输入下划线时容易触发重复——旧查询的执行时间被拉长,更容易和新请求堆积。
具体解决方案
1. 给输入添加防抖(Debounce)
这是最直接的办法,避免用户快速输入时频繁发起搜索请求。设置一个合理的延迟(比如200-300ms),只有当用户停止输入超过这个时间,才真正执行搜索。
示例代码(Android平台,用Handler实现):
private Handler searchHandler = new Handler(Looper.getMainLooper()); private Runnable pendingSearchTask; // 绑定SearchView的文本变化监听 searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { @Override public boolean onQueryTextSubmit(String query) { performSearch(query); return true; } @Override public boolean onQueryTextChange(String newText) { // 移除之前待执行的搜索任务 if (pendingSearchTask != null) { searchHandler.removeCallbacks(pendingSearchTask); } // 创建新的搜索任务,延迟300ms执行 pendingSearchTask = () -> performSearch(newText); searchHandler.postDelayed(pendingSearchTask, 300); return true; } }); // 执行搜索的核心方法 private void performSearch(String keyword) { // 开启后台线程执行JDBC查询 new Thread(() -> { // 注意:如果是下划线,需要做SQL转义,避免通配符影响 String escapedKeyword = keyword.replace("_", "\\_"); List<Drug> searchResults = jdbcDrugSearcher.search(escapedKeyword); // 回到UI线程更新ListView runOnUiThread(() -> { // 关键:先清空旧数据源,再添加新结果 drugList.clear(); drugList.addAll(searchResults); drugAdapter.notifyDataSetChanged(); }); }).start(); }
2. 取消未完成的查询任务
如果防抖还不够(比如某些查询确实耗时较长),可以在发起新搜索前,取消上一次还在执行的JDBC任务。比如用ExecutorService配合Future来管理任务:
private Future<?> currentSearchTask; private ExecutorService executor = Executors.newSingleThreadExecutor(); private void performSearch(String keyword) { // 取消上一次未完成的任务 if (currentSearchTask != null && !currentSearchTask.isDone()) { currentSearchTask.cancel(true); } // 提交新的搜索任务 currentSearchTask = executor.submit(() -> { String escapedKeyword = keyword.replace("_", "\\_"); List<Drug> searchResults = jdbcDrugSearcher.search(escapedKeyword); // 注意:任务被取消时不要再更新UI if (!Thread.currentThread().isInterrupted()) { runOnUiThread(() -> { drugList.clear(); drugList.addAll(searchResults); drugAdapter.notifyDataSetChanged(); }); } }); }
3. 强制清空数据源再更新
不管有没有并发问题,每次更新ListView前都先清空数据源集合,这是兜底的办法。确保旧的查询结果不会残留,新结果是完全替换而非追加。
比如在更新UI的代码里,一定要先调用drugList.clear(),再addAll(searchResults),最后notifyDataSetChanged()——这一步很多开发者容易忽略,尤其是在快速输入时,旧结果还没来得及清空就被新结果追加,直接导致重复。
内容的提问来源于stack exchange,提问作者Mr. Benbenbenbenben

