IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 移动开发 -> 分析一次kotlin-android-extensions引起的空指针问题 -> 正文阅读

[移动开发]分析一次kotlin-android-extensions引起的空指针问题

背景

最近开发遇到一个问题,下面图片的recycleview在滚动的时候需要动态的去滚动上面的分类recycleview,如下图,结果是代码里虽然写了在底部rv滚动的时候已计算出对应的分类rv_tab的position,并调用了rv_tab?.smoothScrollToPosition(parentPosition),为何没有生效?
在这里插入图片描述
代码逻辑也很清晰:

        //初始化滤镜浮层下面的分类
        filterList.apply {
            layoutManager = CenterLayoutManager(context, RecyclerView.HORIZONTAL, false)
            adapter = filterItemAdapter
            filterItemAdapter.also {
                it.setExposureHelper(filterExposureHelper)
                it.setOnItemClickListener { holder, position, item ->
                    applyFilter(position, item)
                }
            }
            addOnScrollListener(object : RecyclerView.OnScrollListener() {
                override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                    super.onScrolled(recyclerView, dx, dy)
                    // xxxx忽略无关代码
                    if (needNotifyTabChange) {
                    //问题代码是下面这句
                        rv_tab?.smoothScrollToPosition(parentPosition)
                        onFilterTabClicked(parentPosition, false)
                    }
                }

                override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
                    super.onScrollStateChanged(recyclerView, newState)
                    //xxxx忽略无关代码
                }
            })
        }

分析

rv_tab?.smoothScrollToPosition(parentPosition)代码有问题,因为在onScrolled回调里面,所以打log看,结果发现rv_tab为空,那自然是无法滚动到我们想要的位置,那问题来了,通过kotlin-android-extensions(以下简称KAE)。大家都知道,在fragment或者activity里面本质上是有一个HashMap/SparseArray用来缓存当前页面的控件,在onDestroy/onDestroyView中clear。于是

猜想1

那会不会是因为这个fragment走了onDestroyView造成的?打log,答案NO

猜想2

那换种思路,我直接用findViewById(),不用id来直接用行不行?答案YES
继续分析,为啥?
在这里插入图片描述

还原成原来的KAE方式id直接去调用方法,然后Tools------>Kotlin------>Show Kotlin Bytecode------>Decompile 再反编译成java
在这里插入图片描述
继续跟下去
在这里插入图片描述
这里就出现了比较奇怪的现象。正常情况下KAE生成的代码是这样的:
kotlin代码

        rv_tab.adapter = xxxxAdapter()

对应的java代码

      RecyclerView var1 = (RecyclerView)this._$_findCachedViewById(id.rv_tab);
      xxxx无关代码

这个很好理解,this是当前的fragment,_$_findCachedViewById是一个方法

   public View _$_findCachedViewById(int var1) {
      if (this._$_findViewCache == null) {
         this._$_findViewCache = new HashMap();
      }

      View var2 = (View)this._$_findViewCache.get(var1);
      if (var2 == null) {
         View var10000 = this.getView();
         if (var10000 == null) {
            return null;
         }

         var2 = var10000.findViewById(var1);
         this._$_findViewCache.put(var1, var2);
      }

      return var2;
   }

根据上面代码可以看到正常情况下,通过控件id直接去调用方法,会先去从HashMap中取,取不到的话再去判断fragment.getView方法,如果返回的也是空,则此时控件为空,不加?的话则直接崩溃(根源就是 public View _$_findCachedViewById(int var1) 这个方法应该标注@Nullable)。如果fragment.getView不为空,则从fragment的根布局findViewById(),并放到map中,供下次直接取来用。

再回过头来看我们的

                  var10000 = (RecyclerView)((View)this.$this_apply).findViewById(id.rv_tab);
               if (var10000 != null) {
                  var10000.smoothScrollToPosition(parentPosition);
               }

是不是发现了不一样,竟然不是通过_findCachedViewById方法取,会不会跟我前面的嵌套有关系?

        //初始化滤镜浮层下面的分类
      filterList.apply {
      	xxxx无关代码
          addOnScrollListener(object : RecyclerView.OnScrollListener() {
              override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
                  super.onScrolled(recyclerView, dx, dy)
                  val parentPosition = findFirstVisiblePosition(firstVisiblePosition)
                  rv_tab?.smoothScrollToPosition(parentPosition)
                  onFilterTabClicked(parentPosition, false)
              }
          })
      }
                  var10000 = (RecyclerView)((View)this.$this_apply).findViewById(id.rv_tab);

根据上面的截图也可以看出来,这里的this_applyxxx.applyxxx也就是addOnScrollListener这个方法的调用者,即filterList这个控件(也就是gif中下面的那个recycleview)
也就是说等同于下面这样,很明显temp为空!

                        val temp = (this@apply).findViewById<RecyclerView>(R.id.rv_tab)
                        temp?.smoothScrollToPosition(parentPosition)

为了继续验证,于是我把相关代码提到apply外面
果然一切就舒服了
在这里插入图片描述
和同事讨论后,同事发现

import kotlinx.android.synthetic.main.clip_fragment_cv_filter_layout.*
import kotlinx.android.synthetic.main.clip_fragment_cv_filter_layout.view.*

如果把上面的第二行干掉,反编译后发现也是正常的 通过_$_findCachedViewById()来取控件,所以也是正常的,那问题来了,我格式化、并且把无用的包去掉之后,这个xxxx.view.*的导入包还是在的。删掉后也可以正常编译,这就有点诡异。
于是我去看了KAE源码,想弄明白这里面的原因,但是没有找到,如果有大神遇到过,并且知道原因,求赐教。

总结

KAE引起的NPE在我们项目中出现概率极高,有时候可能是内存或者配置更改之类的引起了生命周期的变化,存有控件ID的map被清掉并且mView也为空了,此时再去引用,这就要求我们在耗时操作比如属性动画、handler/view postDelay、异步回调等场景中至少要aaa?.call(),而在apply种调用匿名函数的情景应该要被避免,因为我们不能依赖于xxxxx.view.不被导入来判断,毕竟有些自定义view的导包就只有xxxxx.view.。目前还有一个可以探讨的策略,即通过lint规则来匹配。

import kotlinx.android.synthetic.main.xxxxxx_layout.*
import kotlinx.android.synthetic.main.xxxxxx_layout.view.*

参考

https://juejin.cn/post/6844904057815957517
https://www.kotlincn.net/docs/reference/android-overview.html
https://github.com/JetBrains/kotlin/tree/master/plugins/android-extensions

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-10 12:30:55  更:2021-11-10 12:32:42 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/24 4:10:33-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码