1. 你现在的执行顺序

PopupWindow 调用 setPanelData →

mWebPanelImpl = new NadWebPanelHandler(...);

进入NadWebPanelHandler构造方法:

  • mPanelBrowserView = NadWebPanelRuntime.getPanelBrowserViewCreator().createWebPanelContainer(activity);
  • mPanelScrollView = new PanelScrollView(activity); ← 现在是在这里构造的 PanelScrollView

PanelScrollView构造方法里:

public PanelScrollView(Context context) {
    super(context);
    initScrollContainer(); // 这里你直接调
}

如果你在initScrollContainer()立刻调用keyboardListener(),就会进入:

mNadWebPanelContainer.getWindowVisibleDisplayFrame(windowRect);

但是这时候mNadWebPanelContainer还是 null

2. 为什么它是 null?

关键是:
mNadWebPanelContainer不是mPanelBrowserView本身,而是PanelScrollView里的一个成员,它的赋值(findViewByIdaddView)是在更晚的地方才发生的——initBrowserContainer() 里。

看你的代码,在 NadWebPanelHandler.createPanelView() 中才有:

mPanelScrollView?.initBrowserContainer(it, listener)
rootView.addView(it, layoutParams)

这时候:

  • initBrowserContainer()才会把mNadWebPanelContainer 赋值
  • addView()才把整个PanelScrollView放进 View 树

所以在构造 PanelScrollViewinitScrollContainer()keyboardListener() 的时机,
mNadWebPanelContainer 还没被初始化,直接用就 NPE 了。

3. 为什么放到 onPreDraw 不会 NPE?

因为 onPreDraw 触发的时机已经是:

  • createPanelView()完成(mPanelBrowserView已加进mPanelScrollView
  • initBrowserContainer()执行过(mNadWebPanelContainer已赋值)
  • 整个 View 树完成 measure/layout

所以那时候mNadWebPanelContainer一定非空。

4. 总结一下

你“以为不会 NPE”是因为看到了mPanelBrowserView先于mPanelScrollView创建,
mNadWebPanelContainer并不是 mPanelBrowserView,它的赋值逻辑晚得多。
构造 PanelScrollView 时它还是 null,直到initBrowserContainer()才赋值。

851ea4d5190797bec2b71a08e8476b1a.svg

为什么你这段代码里 onPreDraw 是安全的

你的调用链是:

createPanelView()
    → initBrowserContainer()
        → onCreateBrowserContainer()
            → addView(frameLayout)  // 给 PanelScrollView 加子 View

onPreDraw触发的时候:

  • 这个frameLayout已经是mNadWebPanelContainer
  • addView()已完成并同步加入子 View 列表
  • 本帧 measure/layout 也已跑过

调用addView()时,会触发一次requestLayout(),请求重新布局。onPreDraw 在每帧都会触发一次,并且永远在 addView 之后,这是由 Android 的绘制时序保证的。

这块有点绕,总结一下就是 addView 才会触发 onPreDraw。而我们是在initBrowserContainer做 addView,所以 onPreDraw 时,肯定执行了initBrowserContainer,这时候就赋值了,因此不会 NPE。实际上和 addView 关系不大,只是 addView 时,保证了mNadWebPanelContainer不为空。

更新: 2025-08-13 10:08:36
原文: https://www.yuque.com/dongpozhouzi-mshe3/zhm85g/rhl5pyq8tspmcvdc


相关笔记