View的绘制流程分析(源码级)
View 在什么时候添加到屏幕(window)上的
结论:从源码分析得知 View 是在 Activity 的 onResume() 执行后才会绘制到屏幕上, 在 (ViewRootImpl)root.setView(view, wparams, panelParentView, userId); 里面的编舞者 把view送到屏幕上
ActivityThread.handleResumeActivityperformResumeActivityr.activity.performResume(r.startsNotResumed, reason);mInstrumentation.callActivityOnResume(this);activity.onResume();View decor = r.window.getDecorView();WindowManagerImpl.addView(decor, r.window.getAttributes());mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId());root = new ViewRootImpl(view.getContext(), display);mViews.add(view); // DecorViewmRoots.add(root); // ViewRootImplmParams.add(wparams); // WindowManager.LayoutParamsroot.setView(view, wparams, panelParentView, userId);ActivityThread.handleResumeActivity performResumeActivity r.activity.performResume(r.startsNotResumed, reason); mInstrumentation.callActivityOnResume(this); activity.onResume(); View decor = r.window.getDecorView(); WindowManagerImpl.addView(decor, r.window.getAttributes()); mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId()); root = new ViewRootImpl(view.getContext(), display); mViews.add(view); // DecorView mRoots.add(root); // ViewRootImpl mParams.add(wparams); // WindowManager.LayoutParams root.setView(view, wparams, panelParentView, userId);ActivityThread.handleResumeActivity performResumeActivity r.activity.performResume(r.startsNotResumed, reason); mInstrumentation.callActivityOnResume(this); activity.onResume(); View decor = r.window.getDecorView(); WindowManagerImpl.addView(decor, r.window.getAttributes()); mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId()); root = new ViewRootImpl(view.getContext(), display); mViews.add(view); // DecorView mRoots.add(root); // ViewRootImpl mParams.add(wparams); // WindowManager.LayoutParams root.setView(view, wparams, panelParentView, userId);
WindowManagerImpl、WindowManagerGlobal、ViewRootImpl 职责
WindowManagerImpl: 确定 View 属于哪个屏幕,哪个父窗口
WindowManagerGlobal:管理整个进程 所有的窗口信息
ViewRootImpl:WindowManagerGlobal 的实际操作者,操作自己的窗口(只有一个)
ViewRootImpl.setViewrequestLayout();checkThread();//确保在当前线程与view创建线程(默认主线程) 是相同的,否则抛出异常scheduleTraversals();// 插入消息屏障 ,消息链 来了个消息屏障(优先级最高的),消息链就需要开个口子(开口子的地方一般有指定的)让其插队mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();// 编舞者 发送回调方法,这回调方法 运行一次 就会 自动移除mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);doTraversal()performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制notifyRendererOfFramePending();HardwareRenderer.nNotifyFramePending(mNativeProxy);pokeDrawLockIfNeeded()mWindowSession.pokeDrawLock(mWindow);res = mWindowSession.addToDisplayAsUser // 把 窗口添加到 WMS 上WindowManagerService.addWindowview.assignParent(this); // 指定ViewRootImpl 为父容器ViewRootImpl.setView requestLayout(); checkThread();//确保在当前线程与view创建线程(默认主线程) 是相同的,否则抛出异常 scheduleTraversals(); // 插入消息屏障 ,消息链 来了个消息屏障(优先级最高的),消息链就需要开个口子(开口子的地方一般有指定的)让其插队 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 编舞者 发送回调方法,这回调方法 运行一次 就会 自动移除 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); doTraversal() performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制 notifyRendererOfFramePending(); HardwareRenderer.nNotifyFramePending(mNativeProxy); pokeDrawLockIfNeeded() mWindowSession.pokeDrawLock(mWindow); res = mWindowSession.addToDisplayAsUser // 把 窗口添加到 WMS 上 WindowManagerService.addWindow view.assignParent(this); // 指定ViewRootImpl 为父容器ViewRootImpl.setView requestLayout(); checkThread();//确保在当前线程与view创建线程(默认主线程) 是相同的,否则抛出异常 scheduleTraversals(); // 插入消息屏障 ,消息链 来了个消息屏障(优先级最高的),消息链就需要开个口子(开口子的地方一般有指定的)让其插队 mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); // 编舞者 发送回调方法,这回调方法 运行一次 就会 自动移除 mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); doTraversal() performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制 notifyRendererOfFramePending(); HardwareRenderer.nNotifyFramePending(mNativeProxy); pokeDrawLockIfNeeded() mWindowSession.pokeDrawLock(mWindow); res = mWindowSession.addToDisplayAsUser // 把 窗口添加到 WMS 上 WindowManagerService.addWindow view.assignParent(this); // 指定ViewRootImpl 为父容器
面试题:UI更新只能在主线程执行吗?
答:不是的,根据checkThread() 函数可知,只要在 view的 创建线程下 更新都可以,或者在checkThread之前执行
ViewRootImpl 构造方法mThread = Thread.currentThread(); //拿到创建它的线程,MainThread(默认)// 脏区域:收集哪些地方需要更改的// 比如说 TextView 改变了值,这块区域就变为脏区域,// 下次绘制的时候就可以更好的确定哪些view变化了,需要进行重新绘制的mDirty = new Rect();mAttachInfo = new View.AttachInfo() // 保存当前窗口的一些信息ViewRootImpl 构造方法 mThread = Thread.currentThread(); //拿到创建它的线程,MainThread(默认) // 脏区域:收集哪些地方需要更改的 // 比如说 TextView 改变了值,这块区域就变为脏区域, // 下次绘制的时候就可以更好的确定哪些view变化了,需要进行重新绘制的 mDirty = new Rect(); mAttachInfo = new View.AttachInfo() // 保存当前窗口的一些信息ViewRootImpl 构造方法 mThread = Thread.currentThread(); //拿到创建它的线程,MainThread(默认) // 脏区域:收集哪些地方需要更改的 // 比如说 TextView 改变了值,这块区域就变为脏区域, // 下次绘制的时候就可以更好的确定哪些view变化了,需要进行重新绘制的 mDirty = new Rect(); mAttachInfo = new View.AttachInfo() // 保存当前窗口的一些信息
所以说UI更新,可分三种情况进行更新:
- 在与View创建的相同的线程里更新
- 在checkThread() 之前( 例如onCreate() 、onResume() ),也能在其他线程里更新
- 在子线程中,新建 ViewRootImpl 对象
mChoreographer编舞者 把view送到屏幕上
//更新UI的消息优先级是最高的,所以用异步消息和消息屏障插入 更新UI消息mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);doTraversal()performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制// 执行 遍历performTraversals@ViewRootImpl.java// 1、预测量(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)才会最多 测量三次windowSizeMayChange |= measureHierarchy()1、设置一个值,进行第一次测量2、获取一个状态值3、改变大小 baseSize = (baseSize+desiredWindowWidth)/2;4、进行第二次测量5、如果还不满意,直接给自己的最大值,第三次测量(不确定的)如果 windowSizeMayChange = true // 表示还要测量// 2、布局窗口relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);// 3、控件树测量performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);onMeasure // 必定会调用 setMeasuredDimension() 设置参数// 4、布局performLayout(lp, mWidth, mHeight);host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());onLayout(changed, l, t, r, b);child.layout // ViewGroup 管子view的布局// 5、绘制performDraw();draw()// 这里的滚动,为了防止核心的控件被遮住而进行的滚动。比如:输入框输入的时候,view向上滚动scrollToRectOrFocus(null, false);if (isHardwareEnabled()) { // 硬件加速绘制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else { // 软件绘制drawSoftware(...);/** Draw traversal performs several drawing steps which must be executed* in the appropriate order:** 1. 绘制背景 Draw the background* 2. If necessary, save the canvas' layers to prepare for fading* 3. Draw view's content* 4. Draw children* 5. If necessary, draw the fading edges and restore layers* 6. Draw decorations (scrollbars for instance)* 7. If necessary, draw the default focus highlight*/mView.draw(canvas);// Step 3, draw the contentonDraw(canvas);// Step 4, draw the childrendispatchDraw(canvas);}//更新UI的消息优先级是最高的,所以用异步消息和消息屏障插入 更新UI消息 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); doTraversal() performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制 // 执行 遍历 performTraversals@ViewRootImpl.java // 1、预测量(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)才会最多 测量三次 windowSizeMayChange |= measureHierarchy() 1、设置一个值,进行第一次测量 2、获取一个状态值 3、改变大小 baseSize = (baseSize+desiredWindowWidth)/2; 4、进行第二次测量 5、如果还不满意,直接给自己的最大值,第三次测量(不确定的) 如果 windowSizeMayChange = true // 表示还要测量 // 2、布局窗口 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 3、控件树测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); onMeasure // 必定会调用 setMeasuredDimension() 设置参数 // 4、布局 performLayout(lp, mWidth, mHeight); host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); onLayout(changed, l, t, r, b); child.layout // ViewGroup 管子view的布局 // 5、绘制 performDraw(); draw() // 这里的滚动,为了防止核心的控件被遮住而进行的滚动。比如:输入框输入的时候,view向上滚动 scrollToRectOrFocus(null, false); if (isHardwareEnabled()) { // 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { // 软件绘制 drawSoftware(...); /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. 绘制背景 Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) * 7. If necessary, draw the default focus highlight */ mView.draw(canvas); // Step 3, draw the content onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); }//更新UI的消息优先级是最高的,所以用异步消息和消息屏障插入 更新UI消息 mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); doTraversal() performTraversals(); // 绘制view。预测量 -> 布局窗口 -> 控件树测量 -> 布局 -> 绘制 // 执行 遍历 performTraversals@ViewRootImpl.java // 1、预测量(lp.width == ViewGroup.LayoutParams.WRAP_CONTENT)才会最多 测量三次 windowSizeMayChange |= measureHierarchy() 1、设置一个值,进行第一次测量 2、获取一个状态值 3、改变大小 baseSize = (baseSize+desiredWindowWidth)/2; 4、进行第二次测量 5、如果还不满意,直接给自己的最大值,第三次测量(不确定的) 如果 windowSizeMayChange = true // 表示还要测量 // 2、布局窗口 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); // 3、控件树测量 performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); mView.measure(childWidthMeasureSpec, childHeightMeasureSpec); onMeasure // 必定会调用 setMeasuredDimension() 设置参数 // 4、布局 performLayout(lp, mWidth, mHeight); host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight()); onLayout(changed, l, t, r, b); child.layout // ViewGroup 管子view的布局 // 5、绘制 performDraw(); draw() // 这里的滚动,为了防止核心的控件被遮住而进行的滚动。比如:输入框输入的时候,view向上滚动 scrollToRectOrFocus(null, false); if (isHardwareEnabled()) { // 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this); } else { // 软件绘制 drawSoftware(...); /* * Draw traversal performs several drawing steps which must be executed * in the appropriate order: * * 1. 绘制背景 Draw the background * 2. If necessary, save the canvas' layers to prepare for fading * 3. Draw view's content * 4. Draw children * 5. If necessary, draw the fading edges and restore layers * 6. Draw decorations (scrollbars for instance) * 7. If necessary, draw the default focus highlight */ mView.draw(canvas); // Step 3, draw the content onDraw(canvas); // Step 4, draw the children dispatchDraw(canvas); }
padding 与 margin 所占空间区别
如果是View:加上自己的 padding(占用view 本身的空间)
如果是容器:加上孩子的 margin(孩子设置的 margin 占用的是 父容器的空间)
自定义 ViewGroup 是不执行 onDraw 方法
为什么会不执行了?看以下源码分析:
// 硬件加速绘制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);@ViewRootImpl.javaupdateRootDisplayList(view, callbacks);@ThreadedRenderer.javaupdateViewTreeDisplayList(view);view.updateDisplayListIfDirty();updateDisplayListIfDirty@View.javamView.mPrivateFlags // 根据这个标志位 判断是否跳过draw方法if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {dispatchDraw(canvas);child.draw(canvas, this, drawingTime);@ViewGroup.java // 调用 三参数的 draw函数} else {draw(canvas);}// 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);@ViewRootImpl.java updateRootDisplayList(view, callbacks);@ThreadedRenderer.java updateViewTreeDisplayList(view); view.updateDisplayListIfDirty(); updateDisplayListIfDirty@View.java mView.mPrivateFlags // 根据这个标志位 判断是否跳过draw方法 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); child.draw(canvas, this, drawingTime);@ViewGroup.java // 调用 三参数的 draw函数 } else { draw(canvas); }// 硬件加速绘制 mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);@ViewRootImpl.java updateRootDisplayList(view, callbacks);@ThreadedRenderer.java updateViewTreeDisplayList(view); view.updateDisplayListIfDirty(); updateDisplayListIfDirty@View.java mView.mPrivateFlags // 根据这个标志位 判断是否跳过draw方法 if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) { dispatchDraw(canvas); child.draw(canvas, this, drawingTime);@ViewGroup.java // 调用 三参数的 draw函数 } else { draw(canvas); }
结论:因为跳过了单参数的 draw(canvas) 方法,只执行dispatchDraw(canvas)
如何知道系统服务的实际实现类
在 系统服务注册文件 SystemServiceRegistry.java
//系统服务注册文件 SystemServiceRegistry.javaregisterService(Context.WINDOW_SERVICE, WindowManager.class,new CachedServiceFetcher<WindowManager>() {@Overridepublic WindowManager createService(ContextImpl ctx) {return new WindowManagerImpl(ctx); // WindowManagerImpl 实体对象}});Activity.attachmWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE)(注意了:获取系统服务),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);mWindowManager = mWindow.getWindowManager(); // 所以这里 得到的是 WindowManagerImpl 实体对象//系统服务注册文件 SystemServiceRegistry.java registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx); // WindowManagerImpl 实体对象 }}); Activity.attach mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE)(注意了:获取系统服务), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager(); // 所以这里 得到的是 WindowManagerImpl 实体对象//系统服务注册文件 SystemServiceRegistry.java registerService(Context.WINDOW_SERVICE, WindowManager.class, new CachedServiceFetcher<WindowManager>() { @Override public WindowManager createService(ContextImpl ctx) { return new WindowManagerImpl(ctx); // WindowManagerImpl 实体对象 }}); Activity.attach mWindow.setWindowManager( (WindowManager)context.getSystemService(Context.WINDOW_SERVICE)(注意了:获取系统服务), mToken, mComponent.flattenToString(), (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0); mWindowManager = mWindow.getWindowManager(); // 所以这里 得到的是 WindowManagerImpl 实体对象
© 版权声明
文章版权归作者所有,未经允许请勿转载,侵权请联系 admin@trc20.tw 删除。
THE END