java实现方法重复调用防抖

Java实现防抖功能:DebounceTask类与定时器应用,

防抖含义:在指定延时时间内,不管调用了多少次方法,只会执行最后一次调用的动作去执行方法。

举例:设定延时为5秒,调用防抖方法后,5秒内没有重复调用则会执行动作,5秒内有重复调用则会继续再延时等待五秒,直到5秒范围内没有调用再去执行方法。

1.核心工具类

package com.utils;

import java.util.Timer;
import java.util.TimerTask;


/**
 * 防抖
 *
 * @author 刘铁丝
 * @since 1.0
 */
public class DebounceTask {
    /**
     * 防抖实现关键类
     */
    private Timer timer;
    /**
     * 防抖时间:根据业务评估
     */
    private Long delay;
    /**
     * 开启线程执行任务
     */
    private Runnable runnable;

    public DebounceTask(Runnable runnable, Long delay) {
        this.runnable = runnable;
        this.delay = delay;
    }

    /**
     * @param runnable 要执行的任务
     * @param delay    执行时间
     * @return 初始化 DebounceTask 对象
     */
    public static DebounceTask build(Runnable runnable, Long delay) {
        return new DebounceTask(runnable, delay);
    }

    /**
     * Timer类执行:cancel()-->取消操作;schedule()-->执行操作
     */
    public void timerRun() {
        //如果有任务,则取消不执行(防抖实现的关键)
        if (timer != null) {
            timer.cancel();
        }
        timer = new Timer();
        timer.schedule(new TimerTask() {
            @Override
            public void run() {
                //把 timer 设置为空,这样下次判断它就不会执行了
                timer = null;
                //执行 runnable 中的 run()方法
                runnable.run();
            }
        }, delay);
    }

}

2.调用方式

实现五秒内不管调用多少次debounceTask()方法 ,只会执行一次 System.out.println(">>> TODO something ");

	private static DebounceTask debounceTask = null;
	public void debounceTask() {
		if (debounceTask == null) {
		       debounceTask = DebounceTask.build(() -> {
		                System.out.println(">>> 防抖方法开始执行:" +  System.currentTimeMillis());
		                // TODO 
		                System.out.println(">>> TODO something ");
		            }, 5000L);
		}
       debounceTask.timerRun();
	}
<think>我们已经在之前的回答中详细介绍了RxTextView的用法,并给出了完整的代码实现。但为了更直接地回应本次查询,我将再次提供一个精简且完整的Java实现示例,专注重于使用RxTextView实现防抖搜索。 ### 核心实现步骤 1. 添加必要的依赖 2. 在Activity中设置EditText和结果展示视图 3. 使用RxTextView创建Observable 4. 应用操作符:跳过初始值、防抖、过滤、去重、异步搜索 5. 处理搜索结果和错误 6. 管理订阅生命周期 ### 完整代码实现 ```java import android.os.Bundle; import android.widget.EditText; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.jakewharton.rxbinding4.widget.RxTextView; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Observable; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; import java.util.concurrent.TimeUnit; public class RxDebounceSearchActivity extends AppCompatActivity { private EditText mSearchEditText; private TextView mResultTextView; private CompositeDisposable mCompositeDisposable = new CompositeDisposable(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_debounce_search); mSearchEditText = findViewById(R.id.et_search); mResultTextView = findViewById(R.id.tv_result); setupSearchObservable(); } private void setupSearchObservable() { // 创建文本变化事件的Observable Observable<String> searchObservable = RxTextView.textChanges(mSearchEditText) .skipInitialValue() // 跳过初始空字符串 .map(CharSequence::toString) .map(String::trim) .debounce(500, TimeUnit.MILLISECONDS) // 500毫秒防抖 .filter(query -> query.length() >= 3) // 过滤少于3个字符的查询 .distinctUntilChanged(); // 只有查询改变时才触发 // 订阅并执行搜索 mCompositeDisposable.add( searchObservable .switchMap(query -> performSearch(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .toObservable() ) .observeOn(AndroidSchedulers.mainThread()) .subscribe( result -> mResultTextView.setText("结果: " + result), throwable -> mResultTextView.setText("错误: " + throwable.getMessage()) ) ); } // 模拟搜索方法(实际项目中替换为网络请求或数据库查询) private io.reactivex.rxjava3.core.Single<String> performSearch(String query) { return io.reactivex.rxjava3.core.Single.create(emitter -> { try { // 模拟网络延迟 Thread.sleep(300); // 模拟搜索结果 String result = "找到" + (query.length() * 5) + "条相关数据"; emitter.onSuccess(result); } catch (Exception e) { emitter.onError(e); } }); } @Override protected void onDestroy() { super.onDestroy(); mCompositeDisposable.dispose(); } } ``` ### 代码解释 1. **创建Observable**: ```java Observable<String> searchObservable = RxTextView.textChanges(mSearchEditText) .skipInitialValue() // 跳过初始值(避免一开始就触发搜索) .map(CharSequence::toString) // 将CharSequence转为String .map(String::trim) // 去除前后空格 .debounce(500, TimeUnit.MILLISECONDS) // 防抖500毫秒 .filter(query -> query.length() >= 3) // 只处理长度>=3的查询 .distinctUntilChanged(); // 只有查询内容真正改变时才触发 ``` 2. **处理搜索逻辑**: - 使用`switchMap`来处理每个搜索请求,它会自动取消之前未完成的搜索请求,确保只处理最新的请求。 - `subscribeOn(Schedulers.io())`指定搜索操作在IO线程执行(避免阻塞UI线程)。 - `observeOn(AndroidSchedulers.mainThread())`确保结果在主线程更新UI。 3. **模拟搜索方法**: - 在实际应用中,`performSearch`方法应替换为真实的网络请求或数据库查询操作。 - 这里使用`Single`模拟异步操作,并加入300ms延迟模拟网络请求。 4. **生命周期管理**: - 使用`CompositeDisposable`管理订阅,在Activity销毁时调用`dispose()`取消所有订阅,防止内存泄漏。 ### 布局文件 (res/layout/activity_debounce_search.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="16dp"> <EditText android:id="@+id/et_search" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="输入搜索关键字..." android:inputType="text"/> <TextView android:id="@+id/tv_result" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="16dp" android:textSize="18sp"/> </LinearLayout> ``` ### 依赖配置 (app/build.gradle) ```gradle dependencies { implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0' implementation 'io.reactivex.rxjava3:rxjava:3.1.5' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' } ``` ### 注意事项 1. **防抖时间调整**:根据实际需求调整`.debounce()`的时间参数,建议在300-800毫秒之间。 2. **最小搜索长度**:根据搜索内容的性质调整`.filter()`中的长度限制。 3. **错误处理**:在实际应用中,应更细致地处理错误(如网络错误、解析错误等)。 4. **性能优化**:对于频繁的搜索请求,可考虑添加缓存机制。 §§相关问题§§: 1. 如何在RxJava中处理背压(backpressure)问题? 2. 如何结合Retrofit使用RxJava进行网络请求? 3. 在Kotlin中使用协程如何实现类似的防抖搜索功能? 4. 如何为RxTextView的搜索添加缓存功能以提高性能? </think># 使用 RxTextView 实现防抖搜索的完整 Java 实现 下面是一个完整的、可直接使用的 Java 实现,展示了如何使用 RxTextView 实现优雅的防抖搜索功能: ```java import android.os.Bundle; import android.util.Log; import android.widget.EditText; import android.widget.TextView; import androidx.appcompat.app.AppCompatActivity; import com.jakewharton.rxbinding4.widget.RxTextView; import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers; import io.reactivex.rxjava3.core.Single; import io.reactivex.rxjava3.disposables.CompositeDisposable; import io.reactivex.rxjava3.schedulers.Schedulers; import java.util.concurrent.TimeUnit; public class RxDebounceSearchActivity extends AppCompatActivity { private static final String TAG = "RxSearch"; private static final int DEBOUNCE_TIME_MS = 500; // 防抖时间500ms private static final int MIN_SEARCH_LENGTH = 3; // 最小搜索长度 private EditText searchInput; private TextView resultsView; private TextView statusView; private CompositeDisposable disposables = new CompositeDisposable(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_rx_search); // 初始化视图 searchInput = findViewById(R.id.search_input); resultsView = findViewById(R.id.results_view); statusView = findViewById(R.id.status_view); setupRxSearch(); } private void setupRxSearch() { disposables.add( RxTextView.textChanges(searchInput) .skipInitialValue() // 跳过初始空值 .map(CharSequence::toString) .map(String::trim) .doOnNext(query -> updateStatus("输入中: " + query)) // 显示当前输入 .debounce(DEBOUNCE_TIME_MS, TimeUnit.MILLISECONDS) // 防抖 .filter(query -> { if (query.length() >= MIN_SEARCH_LENGTH) { return true; } else { updateStatus("请输入至少3个字符"); return false; } }) .distinctUntilChanged() // 忽略重复查询 .doOnNext(query -> updateStatus("搜索中: " + query)) .switchMap(query -> search(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturn(throwable -> { Log.e(TAG, "搜索错误: " + query, throwable); return "搜索出错: " + throwable.getMessage(); }) ) .observeOn(AndroidSchedulers.mainThread()) .subscribe( result -> { resultsView.setText(result); updateStatus("搜索完成"); }, error -> { Log.e(TAG, "Rx流错误", error); updateStatus("发生错误: " + error.getMessage()); } ) ); } private Single<String> search(String query) { return Single.fromCallable(() -> { // 模拟网络延迟 (200-1000ms) int delay = 200 + (int)(Math.random() * 800); Thread.sleep(delay); // 模拟搜索结果 - 实际应用中替换为真实搜索逻辑 int resultCount = query.length() * (int)(Math.random() * 10 + 1); return "找到 " + resultCount + " 条关于 '" + query + "' 的结果"; }); } private void updateStatus(String message) { statusView.setText("状态: " + message); } @Override protected void onDestroy() { super.onDestroy(); disposables.clear(); // 清理所有订阅 } } ``` ## 配套布局文件 (res/layout/activity_rx_search.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:padding="24dp" android:background="@android:color/white"> <!-- 搜索输入框 --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="搜索内容:" android:textSize="16sp" android:textColor="@android:color/black" android:layout_marginBottom="8dp"/> <EditText android:id="@+id/search_input" android:layout_width="match_parent" android:layout_height="wrap_content" android:hint="输入搜索关键词..." android:inputType="text" android:textSize="18sp" android:padding="12dp" android:background="@drawable/edittext_border"/> <!-- 状态指示器 --> <TextView android:id="@+id/status_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="状态: 等待输入..." android:textSize="14sp" android:textColor="@android:color/darker_gray" android:layout_marginTop="12dp"/> <!-- 结果展示 --> <TextView android:layout_width="match_parent" android:layout_height="wrap_content" android:text="搜索结果:" android:textSize="16sp" android:textColor="@android:color/black" android:layout_marginTop="24dp" android:layout_marginBottom="8dp"/> <TextView android:id="@+id/results_view" android:layout_width="match_parent" android:layout_height="wrap_content" android:text="暂无结果" android:textSize="18sp" android:padding="16dp" android:background="@drawable/result_border" android:textColor="@android:color/black"/> </LinearLayout> ``` ## 添加边界样式 (res/drawable/edittext_border.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="@android:color/white"/> <corners android:radius="8dp"/> <stroke android:width="1dp" android:color="#DDDDDD"/> </shape> ``` ## 添加结果框样式 (res/drawable/result_border.xml) ```xml <?xml version="1.0" encoding="utf-8"?> <shape xmlns:android="http://schemas.android.com/apk/res/android"> <solid android:color="#F5F5F5"/> <corners android:radius="8dp"/> <stroke android:width="1dp" android:color="#E0E0E0"/> </shape> ``` ## 添加 Gradle 依赖 (app/build.gradle) ```gradle dependencies { implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'com.jakewharton.rxbinding4:rxbinding:4.0.0' implementation 'io.reactivex.rxjava3:rxjava:3.1.5' implementation 'io.reactivex.rxjava3:rxandroid:3.0.0' } ``` ## 代码详细解释 ### 1. 初始化配置 ```java private static final int DEBOUNCE_TIME_MS = 500; // 防抖时间500ms private static final int MIN_SEARCH_LENGTH = 3; // 最小搜索长度 ``` 设置防抖时间和最小搜索长度,这些常量可根据实际需求调整。 ### 2. 创建文本变化观察者 ```java RxTextView.textChanges(searchInput) .skipInitialValue() // 跳过初始空值 .map(CharSequence::toString) .map(String::trim) ``` - `textChanges()`: 创建文本变化观察者 - `skipInitialValue()`: 跳过初始值为空的状态 - `map()`: 转换为字符串并去除空格 ### 3. 防抖与过滤逻辑 ```java .debounce(DEBOUNCE_TIME_MS, TimeUnit.MILLISECONDS) // 防抖 .filter(query -> { if (query.length() >= MIN_SEARCH_LENGTH) { return true; } else { updateStatus("请输入至少3个字符"); return false; } }) ``` - `debounce()`: 实现500ms防抖,只有用户停止输入500ms才触发 - `filter()`: 过滤长度小于3的查询,并更新状态提示 ### 4. 搜索执行与结果处理 ```java .switchMap(query -> search(query) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .onErrorReturn(throwable -> { Log.e(TAG, "搜索错误: " + query, throwable); return "搜索出错: " + throwable.getMessage(); }) ) ``` - `switchMap()`: 自动取消之前的搜索请求,只处理最新的请求 - `subscribeOn(Schedulers.io())`: 在IO线程执行搜索 - `observeOn(AndroidSchedulers.mainThread())`: 在主线程处理结果 - `onErrorReturn()`: 错误处理,返回错误信息 ### 5. 搜索结果订阅 ```java .subscribe( result -> { resultsView.setText(result); updateStatus("搜索完成"); }, error -> { Log.e(TAG, "Rx流错误", error); updateStatus("发生错误: " + error.getMessage()); } ) ``` 处理最终搜索结果和错误 ### 6. 模拟搜索方法 ```java private Single<String> search(String query) { return Single.fromCallable(() -> { // 模拟网络延迟 (200-1000ms) int delay = 200 + (int)(Math.random() * 800); Thread.sleep(delay); // 模拟搜索结果 int resultCount = query.length() * (int)(Math.random() * 10 + 1); return "找到 " + resultCount + " 条关于 '" + query + "' 的结果"; }); } ``` 在实际应用中,此方法应替换为真实的网络请求或数据库查询。 ## 功能特点 1. **实时状态反馈**: - 显示当前输入状态 - 显示搜索中状态 - 显示搜索结果状态 - 显示错误信息 2. **智能防抖机制**: - 500ms防抖时间,避免频繁请求 - 最小搜索长度限制(3字符) - 自动过滤重复查询 3. **自动取消机制**: - 使用 `switchMap` 自动取消过时请求 - 确保只处理最新搜索查询 4. **错误处理**: - 网络错误捕获 - 异常处理 - 用户友好的错误提示 5. **生命周期管理**: - 使用 CompositeDisposable 管理订阅 - 在 onDestroy 中清理资源 ## 实际应用中的改进建议 1. **集成真实网络请求**: ```java private Single<String> search(String query) { return apiService.search(query) .map(response -> response.getResultText()) .onErrorResumeNext(throwable -> Single.just("搜索失败: " + throwable.getMessage()) ); } ``` 2. **添加加载动画**: ```java .doOnSubscribe(disposable -> showProgress()) .doFinally(this::hideProgress) ``` 3. **搜索结果分页**: ```java // 在搜索方法中添加分页参数 private Single<String> search(String query, int page) { // ... } ``` 4. **添加本地缓存**: ```java .switchMap(query -> Observable.concat( getCachedResult(query), // 先尝试获取缓存 networkSearch(query) // 再发起网络请求 ).firstOrError() ) ```
评论 1
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值