加入收藏
举报
当前仅显示指定条件回帖 [ 展开查看全部 ]
02-10 14:41
#
文件名称:
组件展示流程.md
所在目录:
Android技术点 / android基础
文件大小:
11.83 KB
下载地址:
zytc2009/BigTeam_learning
   
免责声明:本网站仅提供指向 GitHub 上的文件的链接,所有文件的版权归原作者所有,本网站不对文件内容的合法性、准确性或安全性承担任何责任。
文本预览:
[Toc]
### Activity展示
结构:Window/Decorview/ViewRoot
![activity_ui层次](..\images\activity_ui层次.png)
**WMS作用**:分配surface,掌管surface显示顺序及位置尺寸等,控制窗口动画,输入时间分发
我们开始看Activity代码:
setConentView方法:getWindow().setConentView()
attach方法创建的window=new PhoneWindow

我们看一下PhoneWindow的setConentView
void setConentView(int layoutId){
if(mContentParent == null){
installDecor();// decorview = new Decorview; decorview.addView();
}
layoutInflate.inflate(layoutId, mContentParent);
}
void installDecor(){
mDecor= new Decorview(getContext());
View in = mLayoutInflater.inflate(layoutResourse, null);
mDecor.adddView(in,...);
mContentParent = findViewById(ID_ANDROID_CONTENT);
}

看一下ActivityThread的handleResumeActivity()
View decor = r.window.getDecorView();
ViewManager wm = a.getWindowManager();
wm.addView(decor) 调用 mGlobal.addView();

再看WindowManagerGlobal的addView
void addView(View view,Layoutparas params,...){
ViewRootImpl root = new ViewRootImpl(view.getContext());
root.setView(view, wParams,panelParentView );
}

ViewRootImpl的setView方法
setView(View view,...){
if(mView == null){//只能管理一个view
mView = view;
requestLayout();//触发绘制
...
mWindowSession.addToDisplay(mWindow, ...);
...
}
}
先介绍requestLayout
void requestLayout(){
...
checkThread();//检查是否是同一线程
scheduleTraversals();
}

void scheduleTraversals(){
...
mChoreographer.postCallBack(.., mTraversalRunnable, null);
}

mTraversalRunnable:doTraversal()->performTraversal()

performTraversal(){
...
relayoutWindow(params, ...);//mwindowsSession.relayout(..., mSurface) 执行完这个方法,mSurface就能用了,surfaceFlinger 就可以渲染了
...
performMeasure(childWidthMeasureSpec, ...);//执行view的messure()
...
performLayout(lp, desiredWindowWidth);//执行view的layout(),requestLayout()
。。。
performDraw();//执行view的draw()
。。。
}

然后看 mWindowSession.addToDisplay(mWindow, ...)
mWindowSession是windowsmanager创建的,在openSession()方法
### UI刷新
UI线程启动:
ui线程是刷新ui所在的线程,UI是单线程刷新的,否则需要上锁同步
刷新方式:
activity.runOnUiThread(Runnable action){
if(当前线程 != mUiThread){
mHandler.post(action)
}else{
action.run()
}
}

View.post(Runnable r)

handler.post(Runnable r);

AsyncTask
//activity创建的handler
```
finale Handler mHandler = new Handler();
final void attach(Context context, ...){
mUIThread = 当前线程
}
```
对于activity来说,ui线程就是主线程
ViewRootImpl 在onresume之后才创建
ActivityThread.handleResumeActivity -> windowManagerImpl.addView -> windowManagerGlobal.addView -> ViewRootImpl创建
Activity的Decorview对应的ViewRootImpl是在主线程创建的
能不能刷新ui主要取决于ViewRootImpl创建所在线程
小测验:
new Thread(){
Looper.prepare();
getWindowManager().addView(view, params);
//可以修改ui,可以添加点击,但是必须在线程里处理,原因是什么呢?
Looper.loop();
}
### View的渲染流程
#### view结构图
![view_root_tree](..\images\view_root_tree.png)
#### 绘制流程
整个 View 树的绘图流程在ViewRoot.java类的performTraversals()函数展开,其绘制流程如下:(图用的别人的,见文后链接)
![view_display](..\images\view_display.png)
UI绘制机制是入口在哪里呢?就是ViewRootImpl类的performTraversals()方法。在这个方法内部,分别调用measure、layout、draw方法来进行View的三大工作流程。(见上文分析)
> ```rust
> ViewRoot
> -> performTraversal()
> -> performMeasure()
> -> performLayout()
> -> perfromDraw()
> -> View/ViewGroup measure()
> -> View/ViewGroup onMeasure()
> -> View/ViewGroup layout()
> -> View/ViewGroup onLayout()
> -> View/ViewGroup draw()
> -> View/ViewGroup onDraw()
> ```
#### VIew的绘制事件
draw()->drawBackground->onDraw() 绘制内容->dispatchDraw( ) viewgroup绘制子view-> onDrawForeground
ViewGroup中dispatchDraw()->drawChild(canvas,child, drawingTime(动画时间))
> View的绘制是从上往下一层层迭代下来的。
>
> DecorView–>ViewGroup(— >ViewGroup)–>View ,按照这个流程从上往下,依次measure(测量),layout(布 局),draw(绘制)
然后看viewgroup和view中的几个关键方法:
**测量方法执行流程**:
**final** measure()->onMeasure()
View (ViewGroup) 的 measure 方法,最终的测量是通过回调 onMeasure 方法实现的,这个通常由 View 的特定子类自己实现,可以通过重写这个方法实现自定义 View。
**view排布**:
layout()->onLayout()
ViewGroup中layout方法**是final类型**
Layout 过程用来确定 View 在父容器的布局位置,他是父容器获取子 View 的位置参数后,调用子 View 的 layout 方法并将位置参数传入实现的。
**刷新方法执行流程**:
Draw 操作用来将控件绘制出来,绘制的流程从 performDraw 方法开始。performDraw 方法在类 ViewRootImpl 内,其核心代码如下。
![image-20200713085137993](..\images\view_performdraw.png)
最终调用到每个 View 的 draw 方法绘制每个具体的 View,绘制基本上可以分为六个步骤。
![image-20200713085254383](..\images\view_draw.png)
**函数调用链:**
![image-20200713085559145](..\images\view_draw_调用链.png)
> draw()->drawBackground->onDraw() 绘制内容->dispatchDraw( ) viewgroup绘制子view-> onDrawForeground
>
> ViewGroup中dispatchDraw()->drawChild(canvas,child, drawingTime(动画时间))
用requestLayout方法,会标记当前View及父容器,同时逐层向上提交,直到ViewRootImpl处理该事件,ViewRootImpl会调用三大流程,从measure开始,对于每一个含有标记位的view及其子View都会进行测量、布局、绘制。
当子View调用了invalidate方法后,会为该View添加一个标记位,同时不断向父容器请求刷新,父容器通过计算得出自身需要重绘的区域,直到传递到ViewRootImpl中,最终触发performTraversals方法,进行开始View树重绘流程(只绘制需要重绘的视图)。
一般来说,如果View确定自身不再适合当前区域,比如说它的LayoutParams发生了改变,需要父布局对其进行重新测量、布局、绘制这三个流程,往往使用requestLayout。而invalidate则是刷新当前View,使当前View进行重绘,不会进行测量、布局流程,因此如果View只需要重绘而不需要测量,布局的时候,使用invalidate方法往往比requestLayout方法更高效。
#### 添加和移除view
然后我们看addView和removeView时候view的刷新情况
```
public void addView(View child, int index, LayoutParams params) {
requestLayout();
invalidate(true);
addViewInner(child, index, params, false);
}
public void removeView(View view) {
if (removeViewInternal(view)) {
requestLayout();
invalidate(true);
}
}
```
这两个方法,都有requestLayout,我们看看requestLayout究竟做了什么事
```
public void requestLayout() {
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
...
//记录发起requestlayout的view
mAttachInfo.mViewRequestingLayout = this;
}
//做标记
mPrivateFlags |= PFLAG_FORCE_LAYOUT;
mPrivateFlags |= PFLAG_INVALIDATED;
//通知父view
if (mParent != null && !mParent.isLayoutRequested()) {
mParent.requestLayout();
}
if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
//清除发起requestlayout的view记录
mAttachInfo.mViewRequestingLayout = null;
}
}
```
在requestLayout方法中,首先先为当前View设置上PFLAG_FORCE_LAYOUT的标记位,表示当前的View需要重新绘制。接下来会判断当前View树是否正在布局流程,当父布局已经开始重新布局的时候,不会继续传递重新布局的请求,而是带着FORCE_LAYOUT的标记等待重新绘制的流程走到这里。当并没有已经在重新布局的时候,接着调用mParent.requestLayout方法,为父容器添加PFLAG_FORCE_LAYOUT标记位,而父容器又会调用它的父容器的requestLayout方法.
requestLayout的事件会层层上传,直到DecorView,即根View,而根View又会传递给ViewRootImpl。
即是说任何一个View的requestLayout事件,最终会被ViewRootImpl接收并得到处理。
#### View Scroller原理
View自带的scrollTo和scrollBy能实现View内容的滑动,而Scroller则是基于这两个方法而产生的一个辅助类,能使scrollTo/By的滑动变成弹性滑动一样,所以Scroller说到底也是用了View的scrollTo。
Scroller类的基本使用流程可以总结如下:
![image-20200713085808417](..\images\view_scroller.png)
(1)首先通过Scroller类的startScroll()开始一个滑动动画控制,里面进行了一些轨迹参数的设置和计算;
(2)在调用startScroll()的后面调用invalidate();引起视图的重绘操作,从而触发ViewGroup中的computeScroll()被调用;
(3)在computeScroll()方法中,先调用Scroller类中的computeScrollOffset()方法,里面根据当前消耗时间进行轨迹坐标的计算,然后取得计算出的当前滑动的偏移坐标,调用View的scrollTo()方法进行滑动控制,最后也需要调用invalidate();进行重绘。
#### MeasureSpec
MeasureSpec是View定义的一个静态内部类,用来说明如何测量这个View。MeasureSpec代表一个32位的int,高两位代表SpecMode,测量模式,低 30位代表SpecSize,在某种测量模式下的规格大小。
MeasureSpec提供打包和解包的方法,可以将一组SpecMode和SpecSize通过makeMeasureSpec方法打包成MeasureSpec,也可以将一个MeasureSpec通过getMode和getSize进行解包获得对应的值。

三种测量模式:
UNSPECIFIED:不指定测量模式,父视图没有限制子视图的大小,子视图可以是想要的任何尺寸,通常用于系统内部,应用开发中很少使用到。
EXACTLY:精确测量模式,当该视图的 layout_width 或者 layout_height 指定为具体数值或者 match_parent 时生效,表示父视图已经决定了子视图的精确大小,这种模式下 View 的测量值就是 SpecSize 的值。
AT_MOST:最大值模式,当前视图的 layout_width 或者 layout_height 指定为 wrap_content 时生效,此时子视图的尺寸可以是不超过父视图运行的最大尺寸的任何尺寸。
对 DecorView 而言,它的 MeasureSpec 由窗口尺寸和其自身的 LayoutParams 共同决定;对于普通的 View,它的 MeasureSpec 由父视图的 MeasureSpec 和其本身的 LayoutParams 共同决定。
#### 自定义 View:
1. onMeasure()方法用于测量自己宽高,前提是继承View。如果继承系统已经有的控件比如TextView,Button等等 则不需要重写,因为系统已经给你计算好了。
2. onDraw()方法用于绘制自己想实现的样式。
3. onTouch()用于用户和控件的交互处理。
#### 自定义 ViewGroup:
1. onMeasure方法,for循环获取所有子view,然后根据子view的宽高来计算自己的宽高。
2. onDraw() 一般不需要,默认是不会调用的。如果需要绘制就要实现dispatchDraw()来进行绘制。
3. onLayout()用来摆放子view,前提view是可见。
4. 很多情况下不是不会继承ViewGroup的,一般都是继承系统控件。
参考文章:
1.https://blog.csdn.net/Rayht/article/details/80782697
点赞 回复
回帖
支持markdown部分语法 ?