?
Android?Toast?cancel问题、源码分析和解决方案
?
?
本文中部分内容摘自API ?部分代码来自android.widget.Toast类源代码
?
解决方案中 ToastUtil代码 ?由http://www.linuxidc.com/Linux/2012-01/51925.htm 修改而来(基本没做什么修改 删除了一个方法 ?添加了一个方法)
?
Toast介绍:
A?toast?is?a?view?containing?a?quick?little?message?for?the?user.?The?toast?class?helps?you?create?and?show?those.
When?the?view?is?shown?to?the?user,?appears?as?a?floating?view?over?the?application.?It?will?never?receive?focus.?The?user?will?probably?be?in?the?middle?of?typing?something?else.?The?idea?is?to?be?as?unobtrusive?as?possible,?while?still?showing?the?user?the?information?you?want?them?to?see.?Two?examples?are?the?volume?control,?and?the?brief?message?saying?that?your?settings?have?been?saved.
The?easiest?way?to?use?this?class?is?to?call?one?of?the?static?methods?that?constructs?everything?you?need?and?returns?a?new?Toast?object.
问题:
问题主要出现在通过按钮或事件触发Toast时,若多次点击按钮(或某事件多次触发),会触发一系列Toast,它们会依次缓慢出现在屏幕上,无法消除,令用户看起来很难受。
当然?Toast类中存在消除Toast的方法
public?void?cancel?()
Since:?API?Level?1
Close?the?view?if?it's?showing,?or?don't?show?it?if?it?isn't?showing?yet.?You?do?not?normally?have?to?call?this.?Normally?view?will?disappear?on?its?own?after?the?appropriate?duration.
但是事实证明,API中对cancel类的说明完全是胡扯。
比如我在程序中存放一个ArrayList<Toast>对象用来存放所有的Toast对象,按照API的说法,即使我按了无数次的按钮触发了无数次Toast,但是只要遍历ArrayList?将全部Toast??cancel掉不就一切都清净了?
遗憾的事,即使遍历ArrayList?对每一个Toast对象指向cancel方法,只有当前正在显示的Toast会被cancel掉,队列中的Toast的依然会依次展现。
查看下Toast类的源代码:
首先找到?cancel方法及相关调用链:
?public?void?cancel()?{
????????mTN.hide();
????????//?TODO?this?still?needs?to?cancel?the?inflight?notification?if?any
????}
??/**
???*?schedule?handleHide?into?the?right?thread
???*/
???public?void?hide()?{
????if?(localLOGV)?Log.v(TAG,?"HIDE:?"?+?this);
????mHandler.post(mHide);
?}
?final?Runnable?mHide?=?new?Runnable()?{
????????????public?void?run()?{
????????????????handleHide();
????????????}
????????};
?public?void?handleHide()?{
????????????if?(localLOGV)?Log.v(TAG,?"HANDLE?HIDE:?"?+?this?+?"?mView="?+?mView);
????????????if?(mView?!=?null)?{
????????????????//?note:?checking?parent()?just?to?make?sure?the?view?has
????????????????//?been?added...??i?have?seen?cases?where?we?get?here?when
????????????????//?the?view?isn't?yet?added,?so?let's?try?not?to?crash.
????????????????if?(mView.getParent()?!=?null)?{
????????????????????if?(localLOGV)?Log.v(
????????????????????????????TAG,?"REMOVE!?"?+?mView?+?"?in?"?+?this);
????????????????????mWM.removeView(mView);
????????????????}
????????????????mView?=?null;
????????????}
????????}
看到了么,源代码中显示,执行cancel方法?只会把正在显示的Toast移除,还未被显示的Toast,根本就通不过上面的if条件判断。
API中所说的?Close?the?view?if?it's?showing,?or?don't?show?it?if?it?isn't?showing?yet.
只有前半段是对的,后半段纯属胡扯。
解决方案:
由于Toast中控制显示?隐藏的部分private?class?TN?extends?ITransientNotification.Stub???是个私有内部类,所以继承Toast修改代码是不成了,也听说有高手用反射动态修改这部分的,不知解决情况究竟如何。
这里这个解决办法很简单,既然加入Toast队列的Toast我无法控制,我自己在外部实现一个Toast队列,自己控制它就是了。
简单的实现:全局只有1个Toast对象?由此类中的静态方法控制显示。
每次显示时,cancel掉原来的Toast对象。即,Toast队列中,只有0或1个Toast。每次触发Toast动作,都会立即清除上一个Toast.
package?XXXXXXXXXXXXXXXXXXXXXX;
import?android.content.Context;
import?android.os.Handler;
import?android.os.Looper;
import?android.widget.Toast;
public?class?ToastUtil?{
private?static?Handler?handler?=?new?Handler(Looper.getMainLooper());
private?static?Toast?toast?=?null;
private?static?Object?synObj?=?new?Object();
public?static?void?showMessage(final?Context?act,?final?String?msg)?{
showMessage(act,?msg,?Toast.LENGTH_SHORT);
}
public?static?void?showMessage(final?Context?act,?final?int?msg)?{
showMessage(act,?msg,?Toast.LENGTH_SHORT);
}
public?static?void?showMessage(final?Context?act,?final?int?msg,
final?int?len)?{
new?Thread(new?Runnable()?{
public?void?run()?{
handler.post(new?Runnable()?{
?
@Override
public?void?run()?{
synchronized?(synObj)?{
if?(toast?!=?null)?{
toast.cancel();
toast.setText(msg);
toast.setDuration(len);
}?else?{
toast?=?Toast.makeText(act,?msg,?len);
}
toast.show();
}
}
});
}
}).start();
}
public?static?void?cancelCurrentToast()?{
if?(toast?!=?null)?{
toast.cancel();
}
}
}
?
--------------------------
?
2月28日追加:
实测 在2.2?? 2.3中此方法工作良好。
在4.0系统中效果极差
多次触发Toast? 无法正常显示