DecorView对象的创建详解

来源:互联网 发布:dnf深度优化win 10 编辑:程序博客网 时间:2024/05/14 21:05

DecorVIew 的创建

我们通过一个示例来看看顶层View(DecorView)是怎么创建出来的。

示例:

public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }  }  

在上面我们一般都会调用setContentView方法来设置布局。该方法调用了Activity的setContentView方法,我们去看看:

public class Activity extends ContextThemeWrapper        implements LayoutInflater.Factory2,        Window.Callback, KeyEvent.Callback,        OnCreateContextMenuListener, ComponentCallbacks2,        Window.OnWindowDismissedCallback {....... public void setContentView(int layoutResID) {        getWindow().setContentView(layoutResID);//调用与之关联Window中的setContentView()方法        initWindowDecorActionBar();    }...... }

在父类Activity中其实就是调用与之关联的Window(真正的对象类型是PhoneWindow)setContentView()来创建DecorView对象。进入源码看看实现的过程:

进入Window类(实现对象类型是PhoneWindow)的setContentView():

 public class PhoneWindow extends Window implements MenuBuilder.Callback {     ......    public void setContentView(int layoutResID) {        if (mContentParent == null) {            installDecor();        } else {            mContentParent.removeAllViews();        }        mLayoutInflater.inflate(layoutResID, mContentParent);        final Callback cb = getCallback();        if (cb != null) {            cb.onContentChanged();        }    }     ...... }

可以看出,首先判断mContentParent 是否为null,如果是则调用installDecor(),否则移除所有的VIew。然后通过mLayoutInflater解析我们的布局文件设置到mContentParent。

接下来调用getCallback()获取CallBack对象。还记得Activity实现了Window.callBack吗?在创建Activity对象的时候,实现Window.callBack。然后在创建PhoneWindow对象的时候调用了setCallBack(this).因此这里获取到CallBack对象其实就是当前Activity本身。这里就是当PhoneWindow接收到系统的事件回调到Activity。

mContentParent 是什么呢?从这里就能看出来mContentParent是个ViewGroup且包裹我们整个布局文件(layout.xml)。

进入installDecor:

 public class PhoneWindow extends Window implements MenuBuilder.Callback {     ......      private void installDecor() {        if (mDecor == null) {            mDecor = generateDecor();            mDecor.setIsRootNamespace(true);        }        if (mContentParent == null) {            mContentParent = generateLayout(mDecor);            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);            if (mTitleView != null) {              //根据FEATURE_NO_TITLE隐藏,或者设置mTitleView的值                ......                }              }        }    }     ......

代码可能有长,但主要的操作就generateDecor()创建出mDecor,mDecor是DecorView对象,而DecorView继承于FrameLayout。

generateLayout(mDecor)传入mDecor对象,设置mContentParent 。
最后就是根据FEATURE_NO_TITLE标记来是否显示Title。

进入generateDecor()方法:

protected DecorView generateDecor() {        return new DecorView(getContext(), -1);    }

简单粗暴,就是new出了一个DecorView对象。保存在成员变量mDecor中。
DecorView是Activity的顶级View,一般来说它内部包含标题栏和内容栏(layout.xml,即mContentParent)。内容栏是一定存在的,并且具体的id是‘content’。因此这个时候创建出的DecorView还是一个空白的FrameLayout;

接下来就是调用了generateLayout(mDecor)来初始化DecorView的结构。进入该方法:

  protected ViewGroup generateLayout(DecorView decor) {      ......       View in = mLayoutInflater.inflate(layoutResource, null);        decor.addView(in, new ViewGroup.LayoutParams(FILL_PARENT, FILL_PARENT));        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);      ......  }

这个方法很长,主要的就是加载具体的布局文件,然后添加到DecorView中。这些布局文件中都包含一个id为content的FrameLayout,将其引用返回给mContentParent。

这样DecorView的结构接初始化完成了。

回到setContentView方法中, 调用了mLayoutInflater.inflate(layoutResID, mContentParent);在这里就是把我们写的布局文件通过inflater加入到mContentParent中。

这样我们写的布局文件成功的添加到DecorView中的mContentParent。现在只是完成了DecorView的创建并初始化,我们还需要把这个创建并初始化完DecorView添加并显示到屏幕上,这里我们就需要用到WindowManager。但是现在的DecorView不能被WM所识别,还无法接收外界输入的信息。在ActivityThread的handleResumeActivity方法中,首先会调用Activity.onResume方法,接着调用Activity.makeVisible方法。正在makeVisible方法中DecorView真正的完成了添加和显示这个两个过程,Activity视图才能被用户所看到:

 void makeVisible() {        if (!mWindowAdded) {            ViewManager wm = getWindowManager();            wm.addView(mDecor, getWindow().getAttributes());            mWindowAdded = true;        }        mDecor.setVisibility(View.VISIBLE);    }

调用了 wm.addView(mDecor, getWindow().getAttributes());这里的wm真实的对象类型是WindowManagerImpl,在创建PhoneWindow对象的文章可以知道。

进入WindowManagerImpl.addView 方法:

    @Override    public void addView(View view, ViewGroup.LayoutParams params{    mGlobal.addView(view, params, mDisplay, mParentWindow);}

实际上是调用了 WindowManagerGlobal.addView。

public void addView(View view, ViewGroup.LayoutParams params,        Display display, Window parentWindow) {     ......        if (view == null) {            ......        }     ......        int index = findViewLocked(view, false);//查找是否添加过相同的View        if (index >= 0) {        }       root = new ViewRootImpl(view.getContext(), display);//创建View与之对应的ViewRoot对象       view.setLayoutParams(wparams);       mViews.add(view);//保存view到WindowManagerGlobal       mRoots.add(root);//保存ViewRoot到WindowManagerGlobal       mParams.add(wparams);//保存布局参数到WindowManagerGlobal     .....    root.setView(view, wparams, panelParentView);    ......}

这个方法很长,把不必要的删了。主要的操作,验证view的合法性,

在10行查找该view是否已经添加过至窗口。

然后创建该 View 对应的 ViewRoot,ViewRoot 控制着一个视图的结构(每次 addView 都会创建一个),里面包含了与 WindowManager 通信的 Binder 对象、View 所在界面的 ContextImpl、该视图结构的顶端的 DecorView等信息;

在19-21行把添加的 View、创建的 ViewRootImpl、布局参数添加到 WindowManagerGlobal 中去。用于判断以后添加的view是否已经被添加过。

最后就是ViewRootImpl.setView:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {    synchronized (this) {        if (mView == null) {            mView = view; // 为 mView 赋值,在这里其实是DecorView           ......              }           ......          requestLayout(); // 首次调度执行 layout,这里会触发 onAttachToWindow 和 创建 Surface            ......            try {                ......             res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,                        getHostVisibility(), mDisplay.getDisplayId(),                        mAttachInfo.mContentInsets, mInputChannel);              } catch (RemoteException e) {                ......            }            ......        }    }}

在这里方法中,做了两个重要的操作,一、在8行中调用了requestLayout() 首次调度执行 layout。二、在12行中,通过与WMS通信把窗体添加到屏幕,至于是怎么实现的,这里我们不深究。

我们看看ViewRootImpl中requestLayout()方法:

   /**     * {@inheritDoc}     */    public void requestLayout() {        checkThread();        mLayoutRequested = true;        scheduleTraversals();    }

这里调用scheduleTraversals()方法:

 public void scheduleTraversals() {        if (!mTraversalScheduled) {            mTraversalScheduled = true;            sendEmptyMessage(DO_TRAVERSAL);        }    }

这里发了一个 sendEmptyMessage(DO_TRAVERSAL)消息,由于ViewRootImpl是继承与Handler的,那我们进入ViewRootImpl.handleMessage()方法:

 @Override    public void handleMessage(Message msg) {        switch (msg.what) {         ........        case DO_TRAVERSAL:            if (mProfile) {                Debug.startMethodTracing("ViewAncestor");            }            final long traversalStartTime;            if (ViewDebug.DEBUG_LATENCY) {                traversalStartTime = System.nanoTime();                mLastDrawDurationNanos = 0;            }            performTraversals();            if (ViewDebug.DEBUG_LATENCY) {                long now = System.nanoTime();                mLastTraversalFinishedTimeNanos = now;            }            if (mProfile) {                Debug.stopMethodTracing();                mProfile = false;            }            break;      ......   }

在这个方法中, 在16行调用performTraversals()这个方法,该方法就是系统进行View 树遍历工作的核心函数,这个函数内部逻辑很复杂,但是主体逻辑很清晰,其执行的过程可简单的概括为:是否需要重新计算视图的大小(measure)、是否需要重新布局视图的位置(layout),以及是否需要重绘(Draw)。就是我们常说的View的绘制。

因此首次调用View的绘制,是通过在ViewRootImpl.setView()函数中调用了requestLayout()为开端的。

DecorView生成的过程:

这里写图片描述

把已经创建好并初始化好的DecorView添加并显示到屏幕的过程:

这里写图片描述

END。

1 0
原创粉丝点击