View.doOnAttach回调是否能保证findViewById可找到视图?
问题解答
1. View.doOnAttach 回调不能保证视图可被 findViewById 找到
View.doOnAttach 的触发时机是视图被附加到 Window 时(对应 onAttachedToWindow 方法调用),但这个时机并不等同于视图的 ID 已经被纳入视图树的全局查找映射中。
Android 视图树的 ID 注册逻辑是在父容器执行 addView 的过程中逐步完成的,而 doOnAttach 可能在父容器还未完成当前视图的 ID 注册流程时就被触发。这种情况下,从父布局或根视图调用 findViewById(myFrameLayoutId) 就会返回 null,这就是你遇到罕见崩溃的原因。
2. 延迟操作到视图可被 findViewById 找到的方案
方案一:直接持有目标 View 的引用(最优解)
既然是你自己通过代码创建的 FrameLayout,完全可以直接持有它的实例,不需要通过 findViewById 查找。比如:
val myFrame = FrameLayout(context).apply { id = R.id.myFrameLayoutId doOnAttach { // 直接用 myFrame 实例执行操作,无需 findViewById setupFrame(myFrame) } } parentView.addView(myFrame)
这种方式完全避免了查找失败的问题,效率也最高。
方案二:使用 View.post() 延迟执行
View.post() 会将 Runnable 放入视图的消息队列,等待视图完成布局绘制、ID 注册等后续流程后再执行,此时 findViewById 就能可靠找到目标视图:
myFrame.doOnAttach { view -> view.post { val foundFrame = findViewById<FrameLayout>(R.id.myFrameLayoutId) foundFrame?.let { frame -> // 执行依赖操作 } } }
方案三:监听全局布局完成
通过 ViewTreeObserver 监听视图树的全局布局完成事件,此时整个视图树的结构和 ID 注册都已完成:
myFrame.doOnAttach { view -> val observer = view.viewTreeObserver observer.addOnGlobalLayoutListener(object : ViewTreeObserver.OnGlobalLayoutListener { override fun onGlobalLayout() { // 先移除监听,避免重复触发 if (observer.isAlive) { observer.removeOnGlobalLayoutListener(this) } val foundFrame = findViewById<FrameLayout>(R.id.myFrameLayoutId) foundFrame?.let { frame -> // 执行依赖操作 } } }) }
内容的提问来源于stack exchange,提问作者Lingviston
相关产品推荐
相关产品推荐

