You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在Handler的Runnable中正确使用wait()和notify()?Service上传数据求助

解决Service中Handler+Runnable重复触发的问题

嘿,我来帮你搞定这个困扰!你遇到的核心问题是:当前的写法会在上传逻辑还没执行完时就触发下一次Runnable,因为你在run()方法的最后直接调用了handler.postDelayed(this, 1000)——不管你的上传操作耗时多久,1秒后新的Runnable就会被执行,自然会和未完成的上传重叠。

下面给你两种可行的解决方案,从简单到复杂,你可以根据自己的需求选择:

方案一:上传完成后再触发下一次(推荐)

这是最简洁的方式,核心思路是等本次上传完全结束后,再安排下一次执行,彻底避免重叠。需要注意的是:耗时的上传逻辑一定要放到子线程,不然会阻塞主线程导致ANR。

修改后的代码示例:

public class ServiceUploadData extends Service {
    private static final String TAG = "ServiceUploadData";
    private Handler handler = new Handler();
    // 用单线程池执行上传,保证每次只有一个上传任务在运行
    private ExecutorService uploadExecutor = Executors.newSingleThreadExecutor();

    private Runnable periodicUpdate = new Runnable() {
        @Override
        public void run() {
            Log.v(TAG, "Service Upload Data Running Now");
            
            // 把上传逻辑放到子线程执行
            uploadExecutor.execute(() -> {
                try {
                    // 这里写你的实际逻辑:检查本地DB、发送数据
                    // 比如 uploadPendingDataFromLocalDB();
                    Thread.sleep(2000); // 模拟上传耗时2秒
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    // 上传完成后,回到主线程触发下一次任务
                    handler.postDelayed(periodicUpdate, 1000);
                }
            });
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand");
        handler.post(periodicUpdate);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        // 销毁时清理资源,避免内存泄漏
        uploadExecutor.shutdown();
        handler.removeCallbacks(periodicUpdate);
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

方案二:用wait()/notify()实现同步控制

如果你一定要用wait()和notify()来做同步,需要一个全局锁对象来协调主线程和上传子线程的执行节奏。这种写法相对复杂,但能精确控制执行时机:

public class ServiceUploadData extends Service {
    private static final String TAG = "ServiceUploadData";
    private Handler handler = new Handler();
    // 用于同步的锁对象
    private final Object uploadLock = new Object();
    private boolean isUploadFinished = false;

    private Runnable periodicUpdate = new Runnable() {
        @Override
        public void run() {
            Log.v(TAG, "Service Upload Data Running Now");
            isUploadFinished = false;

            // 启动子线程执行上传
            new Thread(() -> {
                synchronized (uploadLock) {
                    try {
                        // 执行上传逻辑:检查本地DB、发送数据
                        Thread.sleep(2000); // 模拟耗时
                        isUploadFinished = true;
                        uploadLock.notify(); // 通知主线程上传完成
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }).start();

            // 主线程等待上传完成
            synchronized (uploadLock) {
                while (!isUploadFinished) {
                    try {
                        uploadLock.wait(); // 阻塞等待子线程的notify
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                // 上传完成后,安排下一次执行
                handler.postDelayed(this, 1000);
            }
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        Log.v(TAG, "onStartCommand");
        handler.post(periodicUpdate);
        return START_STICKY;
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        handler.removeCallbacks(periodicUpdate);
        // 销毁时唤醒等待的线程,避免内存泄漏
        synchronized (uploadLock) {
            uploadLock.notify();
        }
    }

    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
}

额外注意点

  • 禁止在主线程执行耗时操作:上传、数据库查询这类耗时任务一定要放到子线程,否则会导致主线程阻塞,甚至触发ANR(应用无响应)。
  • Service销毁时清理资源:务必在onDestroy()方法中移除Handler的未执行回调、关闭线程池,避免内存泄漏。
  • 灵活选择执行间隔策略:如果需要的是「每1秒尝试一次,但如果上次上传还在执行就跳过」,可以用一个volatile修饰的标志位isUploading来判断,在run()开头检查标志位,未在上传才执行任务,然后继续定时触发下一次。

内容的提问来源于stack exchange,提问作者flix

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.11 08:48:54