如何在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
相关产品推荐
相关产品推荐

