android10 contentView的绘制

这篇具有很好参考价值的文章主要介绍了android10 contentView的绘制。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

正常情况下我们在Activity的onCreate方法中调用了setContentView,在setContentView的流程后,xml被转成了View并添加到了DecorView,但是并没有真正的绘制。
绘制流程从ActivityThread.handleResumeActivity开始:

  @Override
  public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
            ...
             final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);   //1:从这会走到Activity.onResume
             ...
            if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }
        }
            ...
   }

如果从1处往下追踪可以看到会先调用Activity的onResume方法,再往后走才会进行View的绘制和添加到Window。先略过,看主流程:
DecorView被传进了WindowManager(wm.addView(decor, l)),而WindowManager是一个抽象类,addView方法属于他的父类ViewManager(也是抽象方法)。这个wm的实现者是谁呢? ViewManager wm = a.getWindowManager()->Activity中的方法:

 public WindowManager getWindowManager() {
        return mWindowManager;
 }
 final void attach(Context context, ActivityThread aThread, ...
  mWindowManager = mWindow.getWindowManager() ;//mWindow的类型为Window
}

Window中的方法:

   public WindowManager getWindowManager() {
        return mWindowManager;
    }
    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        ...
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

因此上面的wm实现者为WindowManagerImpl,WindowManagerImpl.addView:

 @Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

执行到WindowManagerGlobal.addView:

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ...
        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {
            ...
            root = new ViewRootImpl(view.getContext(), display);
            view.setLayoutParams(wparams);
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
            // do this last because it fires off messages to start doing things
            try {
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

new ViewRootImpl并调用了它的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
        ...
        // Schedule the first layout -before- adding to the window
        // manager, to make sure we do the relayout before receiving
        // any other events from the system.
        requestLayout();
        try { 
           //与wms通信,请求系统添加窗口
           res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                    getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                    mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                    mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                   mTempInsets);
           setFrame(mTmpFrame);
        }
        ...
 }

方法很长,但是最关键的是调用了requestLayout和mWindowSession.addToDisplay。

 @Override
 public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
}

requestLayout先是进行了线程检查,接着调用了scheduleTraversals:

void checkThread() {
if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
}
void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            if (!mUnbufferedInputDispatch) {
                scheduleConsumeBatchedInput();
            }
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
}

在开始layout前checkThread方法先检查了当前线程是否是创建View的线程,如果不是则抛出异常(也就是只有主线程才能更新UI)。接着scheduleTraversals执行了mTraversalRunnable,即TraversalRunnable:

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
}

因此scheduleTraversals->doTraversal:

 void doTraversal() {
     ...
     performTraversals();
     ...
}

performTraversals开始遍历,这个方法有800多行:

private void performTraversals() {
   WindowManager.LayoutParams lp = mWindowAttributes;
    int desiredWindowWidth;  //所需的窗口宽度
    int desiredWindowHeight;  //所需的窗口高度
    ....
    if (mFirst) { //第一次遍历
            mFullRedrawNeeded = true;
            mLayoutRequested = true;

            final Configuration config = mContext.getResources().getConfiguration();
            if (shouldUseDisplaySize(lp)) {
             //如果lp.type=TYPE_STATUS_BAR_PANEL、TYPE_INPUT_METHOD或TYPE_VOLUME_OVERLAY则使用屏幕宽高作为所需
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }

            // We used to use the following condition to choose 32 bits drawing caches:
            // PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
            // However, windows are now always 32 bits by default, so choose 32 bits
            mAttachInfo.mUse32BitDrawingCache = true;
            mAttachInfo.mWindowVisibility = viewVisibility;
            mAttachInfo.mRecomputeGlobalAttributes = false;
            mLastConfigurationFromResources.setTo(config);
            mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
            // Set the layout direction if it has not been set before (inherit is the default)
            if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
                host.setLayoutDirection(config.getLayoutDirection());
            }
            host.dispatchAttachedToWindow(mAttachInfo, 0);
            mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
            dispatchApplyInsets(host);
        } else {
           //如果不是第一次遍历则使用frame的宽高,frame保存了上一次的大小
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }
        ...
         if (layoutRequested) {
           if (mFirst) {
                // make sure touch mode code executes by setting cached value
                // to opposite of the added touch mode.
                mAttachInfo.mInTouchMode = !mAddedTouchMode;
                ensureTouchModeLocally(mAddedTouchMode); //确保开启触摸模式
            } else {
             ...
            
            }
            //measureHierarchy方法将会进行测量,最多三次
             windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
         }
         if (didLayout) {//layout
            performLayout(lp, mWidth, mHeight);
            ...
         }
         if (!cancelDraw) { //draw
            ...
            performDraw();
        }
        ...
        if (computesInternalInsets) {
        try {
                    mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
                            contentInsets, visibleInsets, touchableRegion);
                } catch (RemoteException e) {
                }
        }
}

省略大量代码,measureHierarchy、performLayout、performDraw分别进行了测量、布局和绘制的工作;

测量:

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
 if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
             final DisplayMetrics packageMetrics = res.getDisplayMetrics();
             //获取一个默认值
            res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
            int baseSize = 0;
            if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
                baseSize = (int)mTmpValue.getDimension(packageMetrics);
            }
            if (baseSize != 0 && desiredWindowWidth > baseSize) {
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
                //第一次测量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    goodMeasure = true;
                } else {
                   //第二次测量
                    baseSize = (baseSize+desiredWindowWidth)/2;
                    childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                        if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                        goodMeasure = true;
                    }
                }
            }
 }
 if (!goodMeasure) {
            childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            //第三次测量,使用ip.width/height
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
                windowSizeMayChange = true;
            }
    }
  return windowSizeMayChange;
 }
 
  private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
           ...   
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

measureHierarchy中共有三次调用performMeasure,performMeasure中调用了View的measure。
第一次:读取一个默认值R.dimen.config_prefDialogWidth(对话框大小)进行测量。
第二次:如果View.MEASURED_STATE_TOO_SMALL,给出的大小不足则扩大一点再次测量。
第三次:如果还是无法满足,则将窗口的全部分给该View。

performLayout和performDraw最终调用了View的layout和draw方法。
绘制工作完成后,回头看ViewRootImpl.setView的mWindowSession.addToDisplay:
mWindowSession的类型为IWindowSession,它是一个Binder对象,Client端的代理,它的服务端实现为Session,因为可以在它的赋值处看到:

sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            });

而openSession的实现则在WindowManagerService中:

    @Override
    public IWindowSession openSession(IWindowSessionCallback callback) {
        return new Session(this, callback);
    }

Session.addToDisplay:

    @Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel,
            InsetsState outInsetsState) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel,
                outInsetsState);
    }

mService即WindowManagerService,到这里,View的绘制完成后跨进程调用了wms的addWindow方法,wms会为这个窗口分配Surface,最终交给SurfaceFlinger处理绘制到屏幕上。
流程图:

android10 contentView的绘制,windows
涉及的类:
Window:抽象类,定义了窗口的外观和行为策略(一个窗口具备什么功能、方法)。是View的直接管理者,View必须依附于Window这个抽象概念。他的唯一实现者是PhoneWindow。
WindowManager:接口类,继承了ViewManager接口。提供给应用层管理Widow(增加View到Window或移除、更新View)。
ViewManager:接口类,定义了addView、updateViewLayout、removeView三个接口方法。
WindowManagerImpl:WindowManager的实现者,创建了ViewRootImpl,重写了getDefaultDisplay方法,确定View 属于哪个屏幕,哪个父窗口。实际addView、removeView等操作交给WindowManagerGlobal执行。
WindowManagerGlobal:保存了View、ViewRootImpl、WindowManager.LayoutParams三个ArrayList列表,管理整个进程 所有的窗口信息。一个应用只会创建一个。通过它调用ViewRootImpl的方法。
ViewRootImpl:View树的树根并管理View树、触发View的测量布局和绘制、输入响应的中转站、通过session与WMS进行进程间通信(binder)。
WindowManagerService:窗口的最终管理者,它负责窗口的启动添加和删除,窗口的大小和层级也是由他管理。窗口的操作最终交给他实现。

相关问题:
1.为什么在子线程更新会报错?
因为调用了ViewRootImpl的checkThread,大致流程:
View.invalidate --> … --> parent.invalidateChildInParent --> ViewRootImpl.invalidateChildInParent --> checkThread();

2.如何实现在子线程刷新Ui?
在ViewRootImpl 创建之前调用。由于handlerResumeActivity会先执行回调即activity的onResume方法,再往后走才会创建ViewRootImpl,因此在onresume方法之前子线程更新ui的流程是view.requestlayout->parent.requestlayout,parent即ViewRooltImpl,但此时并未创建不会执行到viewRootImpl的requestlayout也就不会执行checkThread方法。所有onResuem方法之前可以在子线程更新ui。或者在需要刷新Ui的子线程创建ViewRootImpl。文章来源地址https://www.toymoban.com/news/detail-832148.html

到了这里,关于android10 contentView的绘制的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【Android】之【View绘制】

    简单的可以说,如 measure,layout,draw 分别对应测量,布局,绘制三个过程。 ① measure:测量。系统会先根据xml布局文件和代码中对控件属性的设置,来获取或者计算出每个View和ViewGrop的尺寸,并将这些尺寸保存下来。 ② layout:布局。根据测量出的结果以及对应的参数,来确

    2024年02月08日
    浏览(36)
  • 灵魂画师,Android绘制流程——Android高级UI(1)

    绘制流程从何而起 Activity 的界面结构在哪里开始形成 绘制流程如何运转起来 接下来我们就一个个目标来 conquer。 我们一说到 绘制流程 ,就会想到或是听过 onMeasure 、 onLayout 、 onDraw 这三个方法,但是有没想过为什么我们开启一个App或是点开一个Activity,就会触发这一系列流

    2024年04月17日
    浏览(73)
  • 阿赵UE学习笔记——10、Blender材质和绘制网格体

    阿赵UE学习笔记目录   大家好,我是阿赵。   之前介绍了虚幻引擎的材质和材质实例。这次来介绍一个比较有趣的内置的Blender材质。   在用Unity的时候,我做过一个多通道混合地表贴图的效果,而要做过一个刷顶点颜色混合地表和水面的效果。   在虚幻引擎里面,

    2024年01月19日
    浏览(40)
  • Android 之自定义绘制一

    绘制的基本要素 onDraw(Canvas) 绘制方法 Canvas 绘制工具 Paint 调整风格 粗细等 坐标系: x y ,3D 会有z轴,x  左到右,y 上至下,与数学中y颠倒 尺寸单位: 布局中 dp ,sp ,代码中 px;dp 为了适配不同的尺寸 绘制的关键: draw(Canvas )......(关键类:Paint) Paint.ANTI_ALIAS_FLAG 抗锯齿 paint.setFlag 像

    2024年02月11日
    浏览(50)
  • WPF+Halcon 培训项目实战(10):HS组件绘制图案

    为了更好地去学习WPF+Halcon,我决定去报个班学一下。原因无非是想换个工作。相关的教学视频来源于下方的Up主的提供的教程。这里只做笔记分享,想要源码或者教学视频可以和他联系一下。 微软系列技术教程 WPF 年度公益课程 Halcon开发 CSDN博客专栏 个人学习的Gitee 项目地址

    2024年02月03日
    浏览(45)
  • android:绘制圆角矩形,椭圆形

    一、前言:在我们工作中会有绘制不同圆角的按钮图形,具体该怎么做之前也只是了解个大概,今天看了一节课,听完老师讲的我自己又写了一遍,记录一下。 二、代码展示: 首先先创建一个DrawableShapeActivity 相对应的xml 以及两个形状xml:shape_oval_rose.xml shape_ract_gold.xml

    2024年02月06日
    浏览(43)
  • win10 uwp 简单制作一个 Path 路径绘制的图标按钮

    本文告诉大家在 UWP 或 WinUI 3 里面如何简单制作一个由 Path 几何路径图形绘制的图标按钮 先在资源里面定义按钮的样式,重写 Template 属性,通过在 Template 里面放入 Path 绑定 Data 到内容从而实现让 Path 显示集合路径图形,代码如下 接下来有路径资源可以先在资源字典里面定义

    2024年02月08日
    浏览(34)
  • Android中的view绘制流程,简单理解

    Android中的View类代表用户界面中基本的构建块。一个View在屏幕中占据一个矩形区域、并且负责绘制和事件处理。View是所有widgets的基础类,widgets是我们通常用于创建和用户交互的组件,比如按钮、文本输入框等等。子类ViewGroup是所有布局(layout)的基础类。layout是一个不看见

    2024年02月09日
    浏览(35)
  • Android:绘制自定义View人脸识别框

    项目开发需要自定义View实现一个人脸框,代码实现很平常,一些细节记录一下,方便以后查阅。 代码实现: FaceView.java 注意: 这里我把FaceView的layout_width、layout_height都设置成了\\\"match_parent\\\" 这里面有个 坑 容易踩到 本次开发时,xml中FaceView控件往上一直到第一层父布局宽、高

    2024年02月09日
    浏览(64)
  • Android中绘制的两个天气相关的View

    开发天气相关软件时候,做了两个自定义View,这里进行记录,由于涉及类较多,这里仅包含核心代码,需要调整后才可以运行,自定义View范围仅包含网格相关UI。需要注意的是横向坐标需要25个数据,来表示一天24小时。最后一个数据表示0点,效果如下: 降雨的效果: 该效果

    2024年02月15日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包