为什么Handler post执行逻辑中Toast消息会重复显示两次?
问题原因
- 核心问题是你 post 到主线程的
Runnable直接访问了跨线程共享的全局变量progressStatus,没有做值快照。匿名内部类访问外部变量是引用传递,不是值拷贝,Runnable被主线程执行时读取的是当前最新的progressStatus,不是 post 时刻的值。
举个实际执行场景:
工作线程把
progressStatus加到 99 时,post 了一个更新进度的Runnable,这个Runnable还没来得及在主线程执行,工作线程已经进入下一轮循环把progressStatus加到了 100,又 post 了第二个Runnable。等主线程依次执行这两个Runnable时,读到的progressStatus都是 100,就会触发两次 toast 逻辑。
- 次要原因是
progressStatus没有做线程可见性保护,没有加volatile修饰,也没有同步锁,可能出现工作线程的修改没有及时同步到主线程,或者计数异常的问题。 - 还有一种可能是你重复启动了这个工作线程,两次循环都跑到了100,各触发一次toast。
修复方案
方案1:post时拷贝当前进度值(最稳妥)
每次 post Runnable 前,把当前的进度存为 final 局部变量,Runnable 内部只访问这个局部快照,就不会读到后续更新的进度值:
new Thread(new Runnable() { @Override public void run() { while (progressStatus < 100) { progressStatus += 1; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } // 拷贝当前进度为final局部变量 final int currentProgress = progressStatus; handler.post(new Runnable(){ @Override public void run() { pb = dialog.findViewById(R.id.no_connectiondProgressBar); pb.setProgress(currentProgress); // 用快照值判断,不是全局变量 if (currentProgress == 100) { final Toast toast = new Toast(getApplicationContext()); toast.setDuration(Toast.LENGTH_LONG); View custom_view = getLayoutInflater().inflate(R.layout.custom_toast, null); toast.setView(custom_view); toast.show(); dialog.dismiss(); } } }); } } }).start();
方案2:把toast触发逻辑移到工作线程
仅在进度到100的时候,才post显示toast的任务到主线程,避免每次都做判断,性能更好:
new Thread(new Runnable() { @Override public void run() { while (progressStatus < 100) { progressStatus += 1; try { Thread.sleep(50); } catch (InterruptedException e) { e.printStackTrace(); } final int currentProgress = progressStatus; handler.post(new Runnable(){ @Override public void run() { pb = dialog.findViewById(R.id.no_connectiondProgressBar); pb.setProgress(currentProgress); } }); // 工作线程直接判断,只post一次toast任务 if (progressStatus == 100) { handler.post(new Runnable() { @Override public void run() { final Toast toast = new Toast(getApplicationContext()); toast.setDuration(Toast.LENGTH_LONG); View custom_view = getLayoutInflater().inflate(R.layout.custom_toast, null); toast.setView(custom_view); toast.show(); dialog.dismiss(); } }); } } } }).start();
额外优化建议
给全局变量 progressStatus 加上 volatile 修饰,保证多线程之间的可见性,避免出现工作线程的进度更新主线程读取不到的问题:
private volatile int progressStatus = 0;
内容的提问来源于stack exchange,提问作者Jin Appsdev
相关产品推荐
相关产品推荐

