onMeasure() 为什么会执行多次?Android 一个困惑很久的问题!

前言

我们知道父布局根据自身和子布局的要求给子布局生成测量模式和测量尺寸,并封装在MeasureSpec对象里,最终传递给子布局让它最后确定自身的尺寸。
很自然就会想到,既然子布局是从父布局拿的测量结果,父布局又从它的父布局拿测量结果,最终到ViewTree的顶点根View是谁测量的呢?
循着这个问题,从源码角度一探究竟。

通过本篇文章,你将了解到:

1、Window 尺寸测量。
2、根View 尺寸测量。
3、Window、ViewRootImpl、View 三者关系。

1、Window 尺寸测量

一个小Demo

通过WindowManager.addView(xx)展示一个悬浮窗:

private void showView() {

    //获取WindowManager实例
    wm = (WindowManager) App.getApplication().getSystemService(Context.WINDOW_SERVICE);

    //设置LayoutParams属性
    layoutParams = new WindowManager.LayoutParams();
    //宽高尺寸
    layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;
    layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;
    layoutParams.format = PixelFormat.TRANSPARENT;
    //设置背景阴暗
    layoutParams.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
    layoutParams.dimAmount = 0.6f;

    //Window类型
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
    } else {
        layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
    }

    //构造TextView
    TextView myView = new TextView(this);
    myView.setText("hello window");
    //设置背景为红色
    myView.setBackgroundResource(R.color.colorRed);
    FrameLayout.LayoutParams myParam = new FrameLayout.LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, 400);
    myParam.gravity = Gravity.CENTER;
    myView.setLayoutParams(myParam);

    //myFrameLayout 作为rootView
    FrameLayout myFrameLayout = new FrameLayout(this);
    //设置背景为绿色
    myFrameLayout.setBackgroundColor(Color.GREEN);
    myFrameLayout.addView(myView);

    //添加到window
    wm.addView(myFrameLayout, layoutParams);
}

上述代码简单概述如下:

1、构造TextView,设置其背景为红色。
2、构造FrameLayout,设置其背景为绿色。
3、将TextView作为子View添加到FrameLayout。
4、将FrameLayout作为RootView(根View)添加到Window里。

注意到:

wm.addView(myFrameLayout, layoutParams);

layoutParams 里重点关注宽、高字段的值,我们知道这是给Window的尺寸约束,以宽为例,设置不同的值,看看其效果:
1、wrap_content

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;

layoutParams.width = ViewGroup.LayoutParams.WRAP_CONTENT;

[图片上传失败…(image-f8d0f1-1616851800488)]

可以看出:RootView(FrameLayout) 包裹着TextView,两者宽度一致。

2、match_parent

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;

layoutParams.width = ViewGroup.LayoutParams.MATCH_PARENT;

可以看出:RootView(FrameLayout) 宽充满屏幕。

3、设置具体的值

layoutParams.height = ViewGroup.LayoutParams.WRAP_CONTENT;

layoutParams.width = 800;

可以看出:RootView(FrameLayout) 宽没充满屏幕,屏幕宽1080px。

结合上述三张图,我们有理由相信,wm.addView(myFrameLayout, layoutParams) 里的layoutParams 是用来约束myFrameLayout(RootView),那么Window尺寸是怎么来的呢?

Window 尺寸的确定

从wm.addView(xx)开始分析,WindowManager 是个接口,其实现类是:WindowManagerImpl。

#WindowManagerImpl.java

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    //赋值token,在启动Dialog/PopupDialog 会判断该值
    applyDefaultToken(params);
    //mGlobal 为单例,管理所有的ViewRootImpl、RootView
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

接着看WindowManagerGlobal 的处理:

#WindowManagerGlobal.java

public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        ...
        //构造ViewRootImpl 对象
        root = new ViewRootImpl(view.getContext(), display);

        //view 作为RootView
        //将传进来的wparams作为该RootView的LayoutParams
        view.setLayoutParams(wparams);

        //记录对象
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //ViewRootImpl 关联RootView
            root.setView(view, wparam
Android 自定义 View 的开发中,多次调用 `requestLayout()` 确实会对性能造成一定影响。这是因为每次调用 `requestLayout()` 都会触发视图树的重新测量和布局流程,系统会从当前视图向上递归请求整个视图层级的重新布局,最终导致 `onMeasure()` 和 `onLayout()` 方法被多次调用。 如果在短时间内频繁调用 `requestLayout()`,尤其是在自定义 View 中因状态变化频繁触发该方法,会导致 UI 线程被大量布局计算占用,从而影响帧率,甚至可能引发界面卡顿现象。这种性能损耗在布局层级复杂或子视图较多的情况下尤为明显,特别是在使用 `RelativeLayout` 时,其子视图可能会被多次调用 `onMeasure()`,进一步加剧性能问题 [^1]。 此外,多次调用 `requestLayout()` 也会增加内存分配和回收的压力,特别是在异步加载或复杂动画场景中,若未进行合理优化,可能造成主线程阻塞或渲染延迟。 为了避免因频繁调用 `requestLayout()` 导致的性能问题,可以采取以下优化策略: - **合并多次请求**:将多个布局请求合并为一次,避免短时间内重复调用 `requestLayout()`。 - **状态变更控制**:确保仅在视图尺寸或布局参数真正发生变化时才调用 `requestLayout()`。 - **使用 `ViewStub`**:对于非即时需要的视图,使用 `ViewStub` 延迟加载,避免不必要的布局计算 [^3]。 - **异步加载布局**:在不影响用户体验的前提下,使用 `AsyncLayoutInflater` 异步加载复杂布局,降低主线程负担 [^4]。 ### 示例代码:避免多次 requestLayout ```java // 不推荐的方式:多次调用 requestLayout() view1.setLayoutParams(params1); view1.requestLayout(); view2.setLayoutParams(params2); view2.requestLayout(); // 推荐方式:合并布局请求 view1.setLayoutParams(params1); view2.setLayoutParams(params2); viewParent.requestLayout(); // 请求父布局一次即可 ``` 通过合理控制 `requestLayout()` 的调用频率,可以有效提升自定义 View 的性能表现,确保 UI 渲染流畅。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值