美文网首页
kotlin 开发踩一小坑,朋友们请注意!

kotlin 开发踩一小坑,朋友们请注意!

作者: xiawe_i | 来源:发表于2018-11-25 13:32 被阅读62次
image

工作项目中已经使用了 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

相关文章

网友评论

      本文标题:kotlin 开发踩一小坑,朋友们请注意!

      本文链接:https://www.haomeiwen.com/subject/lntpqqtx.html