美文网首页
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