工作项目中已经使用了 kotlin,有一个 view 需要展示 5 秒钟将其关闭并做一些其他工作,我用了View.postDelayed(runnable , delayMillis) 方法,大家都知道考虑到这是一个延迟任务,有可能在 Runnable 还未执行到的时候用户就把页面关闭了,所以在页面关闭时需要把 Runnable 给移除掉。
简化了例子如下面代码:
private val runnable = {
Toast.makeText(this, "I'm a Runnable", Toast.LENGTH_SHORT).show()
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView.setOnClickListener {
textView.postDelayed(runnable, 5000)
}
}
override fun onDestroy() {
super.onDestroy()
textView.removeCallbacks(runnable)
}
上面这段代码好像没什么问题,上面声明了一个 (以为是)Runnable 对象,一个 lambda 表达式的写法,点击 textView 5 秒后执行 Runnable,run 方法里 show 了一个 Toast,onDestroy 时移除 Runnable。正常的情况在你 移除这个 Runnable 之后,Runnable 里的操作就不会再响应,也就是收不到这个Toast消息了,然而事实并非所愿,关闭页面后依然收到了 Toast。这是为什么?实在找不出哪里有问题,于是我用 Java 代码试了一遍,发现 Java 果然没问题,移除 Runnable 后就不会弹 Toast,既然 Java 没问题,那问题是不是出在 kotlin 语法上?
于是我看 kotlin 编译后的 Java 代码
{
//Runnable被编译成了 Function0
private final Function0 runnable = (Function0)(new Function0() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke() {
this.invoke();
return Unit.INSTANCE;
}
public final void invoke() {
Toast.makeText((Context)MainActivity.this, (CharSequence)"I'm a Runnable", 0).show();
}
});
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296284);
((TextView)this._$_findCachedViewById(id.textView)).setOnClickListener((OnClickListener)(new OnClickListener() {
public final void onClick(View it) {
TextView var10000 = (TextView)MainActivity.this._$_findCachedViewById(id.textView);
Object var10001 = MainActivity.this.runnable;
if (var10001 != null) {
Object var2 = var10001;
//在这里创建了一个Runnable,并且把Function0传了进去
var10001 = new MainActivity$sam$java_lang_Runnable$0((Function0)var2);
}
//这里调用
var10000.postDelayed((Runnable)var10001, 5000L);
}
}));
}
编译后的 Runnable 发现是个Function0 而不是 Runnable, 在调用 postDelayed 方法之前 new 一个 Runnable 它把 Function0 传了进去,这个 Runnable 它的构造方法有一个 Function0 参数,run 方法里调用的是 Function0 的 invoke 方法
final class MainActivity$sam$java_lang_Runnable$0 implements Runnable {
// $FF: synthetic field
private final Function0 function;
//构造方法 参数 Function0
MainActivity$sam$java_lang_Runnable$0(Function0 var1) {
this.function = var1;
}
// $FF: synthetic method
public final void run() {
Intrinsics.checkExpressionValueIsNotNull(this.function.invoke(), "invoke(...)");
}
}
其实这都不是重点,即使这样它依然不应该影响我想要的结果,重点是在 onDestroy 方法里它又 new 一个 runnable 对象,并且 removeCallbacks() 移除的是这个刚 new 出来的 Runnable
super.onDestroy();
TextView var10000 = (TextView)this._$_findCachedViewById(id.textView);
Object var10001 = this.runnable;
if (this.runnable != null) {
Object var1 = var10001;
//新的Runnable对象
var10001 = new MainActivity$sam$java_lang_Runnable$0((Function0)var1);
}
var10000.removeCallbacks((Runnable)var10001);
}
这下就真相大白了,原因是 view.postDelayed 的 Runnable 和 remove 的 Runnable 不是一个对象,所以才会出现移除不掉的情况。
现在看下 Runnable 这样写:
private val runnable = Runnable{
Toast.makeText(this, "I'm a Runnable", Toast.LENGTH_SHORT).show()
}
看出区别了吗?上面的例子是这样的:
private val runnable = {
Toast.makeText(this, "I'm a Runnable", Toast.LENGTH_SHORT).show()
}
‘=’ 后面多了这个 Runnable,再看编译后的 Java 代码
private final Runnable runnable = (Runnable)(new Runnable() {
public final void run() {
Toast.makeText((Context)MainActivity.this, (CharSequence)"I'm a Runnable", 0).show();
}
});
private HashMap _$_findViewCache;
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
this.setContentView(2131296284);
((TextView)this._$_findCachedViewById(id.textView)).setOnClickListener((OnClickListener)(new OnClickListener() {
public final void onClick(View it) {
((TextView)MainActivity.this._$_findCachedViewById(id.textView)).postDelayed(MainActivity.this.runnable, 5000L);
}
}));
}
protected void onDestroy() {
super.onDestroy();
((TextView)this._$_findCachedViewById(id.textView)).removeCallbacks(this.runnable);
}
这回它直接 new了 Runnable 对象,而不是 Function0 了,onDestroy 里 remove 的是同一个 Runnable,所以仅仅是写法上少写了一个 Runnable 就出现这么大的差别,而且还不容易发现,简是直见了鬼。
因为 Runnable 和 Function 刚好只有一个方法,都可以用 lamdba 表达式 ,创建的时候没有声明是 Runnable,所以这个时候程序不知道你要的是 Runnable,只好是 Function,当你调用 postDelayed 的时候才知道你需要一个 Runnable,这时候才给你创建出来。我们在享受 kotlin 语法带来的便捷时也要注意其中的坑。
image.png
网友评论