前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >在 Fragment 当中使用 Kotlin-Android-Extensions 需要注意的

在 Fragment 当中使用 Kotlin-Android-Extensions 需要注意的

作者头像
bennyhuo
发布2020-02-20 13:19:45
1.6K0
发布2020-02-20 13:19:45
举报
文章被收录于专栏:BennyhuoBennyhuo

自从有了 kotlin-android-extensions,小伙伴们的感觉就是一个字,爽!再也不用什么 findViewById 了,也不用什么反射和注解注入了,吾有奇招,黄油刀们速速退散!

1. 何为 kotlin-android-extensions ?

如果你不知道我在说什么,我简单提一句,我们在 xml 布局当中定义了一个 id 为 logoutView 的按钮:

代码语言:javascript
复制
<Button
    android:id="@+id/logoutView"
    ...
    android:text="退出登录"/>

通常来讲,如果你想要在你的代码当中操作这个 View,例如给他设置一个点击事件,你需要先 findViewById 找到它的引用,然后 setOnClickListener,对吧。可是有了 kotlin-android-extensions 之后,我们可以直接在 ActivityFragmentView 当中使用这个 logoutView 了。

代码语言:javascript
复制
logoutView.onClick {
    AccountManager.logout()
            .subscribe {
               ...
            }
}

有人这时候难免会有疑问,我们既然从来没有定义过这个变量 logoutView,那它是从哪里来的呢?

关于这个问题,我在将近一年前的一篇文章当中提到过,就是一些编译期的黑魔法啦,不信我们来看下刚才那段 Kotlin 代码对应的字节码:

代码语言:javascript
复制
L5
    LINENUMBER 43 L5
    ALOAD 0
    GETSTATIC com/bennyhuo/kae/R$id.logoutView : I
    INVOKEVIRTUAL com/bennyhuo/kae/view/UserDetailActivity._$_findCachedViewById (I)Landroid/view/View;
    CHECKCAST android/widget/Button
    DUP
    LDC "logoutView"
    INVOKESTATIC kotlin/jvm/internal/Intrinsics.checkExpressionValueIsNotNull (Ljava/lang/Object;Ljava/lang/String;)V
    CHECKCAST android/view/View
    ACONST_NULL
    ...

发现了什么?

原来编译器为我们生成了一个叫做 _$_findCachedViewById 的方法,如果你深入查看这个方法的实现,你还会发现有个缓存来存储找到的 View,也就是说在我们使用 logoutView 的时候,第一次会最终调用到 findViewById,后面再使用它的话就直接从缓存中获取了。

这里也有个比较有意思的小尝试,你可以在你的 Activity 当中定义一个方法:

代码语言:javascript
复制
fun `_$_findCachedViewById`(id: Int): View{
    return RelativeLayout(this)
}

看看编译期会怎么报答你。

2. 在 Fragment 中使用 Kae 有什么毛病?

好啦,介绍到此,我们来说说问题。前面提到的实际上是 Activity 的实现, Activity 本身就有 findViewById ,所以这里面似乎不会有什么问题出现,而 Fragment 就会稍微麻烦一些,它需要用它的 ViewfindViewById,下面给大家看一段代码,看看有什么问题:

代码语言:javascript
复制
override fun onViewCreated(view: View?, savedInstanceState: Bundle?) {
    super.onViewCreated(view, savedInstanceState)
    RESTfulService.user(id) 
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe { user ->
                userNameView.text = user.name
                ...
            }
}

这段代码的问题在于,如果网络不太好,这个网络请求可能在 10s 甚至更久才返回,而这期间也许我已经离开了这个 Fragment 页面,那么结果会怎样呢?

当然是空指针。是的,你没看错,就是你熟悉的空指针。这次 Kotlin 让你毫无防备的给你一刀,其实它也不愿意的,且让我们来看看这空指针是哪里来的。

代码语言:javascript
复制
...
userNameView.text = user.name
...

注意这一行,我们访问 userNameView ,本质上相当于调用前面提到的编译期为 Fragment 生成的一个方法,这个方法会先从缓存查找,接着再去 FragmentView 中查找,那么问题来了,我们退出这个 Fragment 以后,它的生命周期已经结束,这时候,编译期生成的缓存会被清空:

代码语言:javascript
复制
public _$_clearFindViewByIdCache()V
    ALOAD 0
    GETFIELD com/bennyhuo/kae/view/fragments/RepoFragment._$_findViewCache : Ljava/util/HashMap;
    IFNULL L0
    ALOAD 0
    GETFIELD com/bennyhuo/kae/view/fragments/RepoFragment._$_findViewCache : Ljava/util/HashMap;
    INVOKEVIRTUAL java/util/HashMap.clear ()V
    ...
代码语言:javascript
复制
public synthetic onDestroyView()V
    ALOAD 0
    INVOKESPECIAL com/bennyhuo/kae/view/common/CommonViewPagerFragment.onDestroyView ()V
    ALOAD 0
    INVOKEVIRTUAL com/bennyhuo/kae/view/fragments/RepoFragment._$_clearFindViewByIdCache ()V
    ...

注意看到 FragmentonDestroyView 被调用时,缓存被清空了。

换句话说,这时候 userNameView 只能重新去 findViewById 了,然而 ——

代码语言:javascript
复制
...
    INVOKEVIRTUAL android/support/v4/app/Fragment.getView ()Landroid/view/View;
    ...
    INVOKEVIRTUAL android/view/View.findViewById (I)Landroid/view/View;

这时候 Fragment.getView 必然返回 null,所以就会遇到空指针。

3. 我们该怎么办?

对于这个问题,如果我们强制要求 FragmentgetView 不返回 null,这样是不会出现空指针了,但长时间的持有 UI 引用,可能会导致内存泄露。换句话说, null 是不可避免的。

所以解决方法当然是离开页面就取消请求啊,这样刚刚那段操作 UI 的代码就不会在 Fragment 已经退出之后再执行了。

当然,还有一种思路,上文当中我用到了 RxJava,我可以通过自定义一个 UI 生命周期相关的 Scheduler,在生命周期发生变化时,一方面可以统一取消请求,另一方面,也可以控制在 UI 已经无效时,所有请求的回调都不会被执行。


本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-01-15,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 Kotlin 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 何为 kotlin-android-extensions ?
  • 2. 在 Fragment 中使用 Kae 有什么毛病?
  • 3. 我们该怎么办?
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档