Activity Window DecorView ViewRo

2019-01-01  本文已影响8人  leilifengxingmw

注意:

  1. 本文部分内容直接摘抄自 简析Window、Activity、DecorView以及ViewRoot之间的错综关系

职责简介

Activity

Activity并不负责视图控制,它只是控制生命周期和处理事件。真正控制视图的是Window。一个Activity包含了一个Window,Window才是真正代表一个窗口。Activity就像一个控制器,统筹视图的添加与显示,以及通过其他回调方法,来与Window、以及View进行交互。

Window

Window是视图的承载器,内部持有一个 DecorView,而这个DecorView才是 view 的根布局。Window是一个抽象类,实际在Activity中持有的是其子类PhoneWindow。

PhoneWindow的类结构.png

PhoneWindow中有个内部类DecorView,通过创建DecorView来加载Activity中设置的布局R.layout.activity_main
Window 通过WindowManager将DecorView加载其中,并将DecorView交给ViewRoot,进行视图绘制以及其他交互。

DecorView

DecorView是FrameLayout的子类,它可以被认为是Android视图树的根节点视图。DecorView作为顶级View,它内部包含一个竖直方向的LinearLayout,一般情况下在这个LinearLayout里面有上下三个部分,上面是个ViewStub,延迟加载的视图(应该是设置ActionBar,根据Theme设置),中间的是标题栏(根据Theme设置,有的布局没有),下面的是内容栏@android:id/content。具体情况和Android版本及主体有关,以其中一个布局为例,如下所示:

C:\Users\android\AppData\Local\Android\Sdk\platforms\android-27\data\res\layout\screen_title.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />

    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">

        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />

    </FrameLayout>

   <!--内容栏,熟悉的字眼@android:id/content-->
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />

</LinearLayout>

在Activity中通过setContentView所设置的布局文件其实就是被加到内容栏(就是上面的id为content的FrameLayout)之中的,成为其唯一子View,在代码中可以通过content来得到对应加载的布局。

ViewRoot

ViewRoot可能比较陌生,但是其作用非常重大。所有View的绘制以及事件分发等交互都是通过它来执行或传递的。ViewRoot对应ViewRootImpl类,它是连接WindowManagerService和DecorView的纽带,View的三大流程(测量(measure),布局(layout),绘制(draw))均通过ViewRoot来完成。

ViewRoot并不属于View树的一份子。从源码实现上来看,它既非View的子类,也非View的父类,但是,它实现了ViewParent接口,这让它可以作为View的名义上的父视图。

Window、Activity、DecorView以及ViewRoot四者之间的关系如下图所示

Activity_Window_DecorView_ViewRoot.png

DecorView的创建

这部分内容主要讲DecorView是怎么一层层嵌套在Actvity,PhoneWindow中的,以及DecorView如何加载内部布局。

先是从Activity的setContentView()开始

public void setContentView(@LayoutRes int layoutResID) {
    //1. 交给window来设置视图
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}
public Window getWindow() {
        return mWindow;
}

PhoneWindow类中的两个成员变量先记一下

//当前窗口的顶层视图
private DecorView mDecor;
//@android:id/content所对应的FrameLayout
private ViewGroup mContentParent;

PhoneWindow的setContentView()方法精简版

 @Override
    public void setContentView(int layoutResID) {

        //1 如果mContentParent 为null
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //mContentParent 不为null,移除掉所有的子View
            mContentParent.removeAllViews();
        }
        //...

        //为mContentParent添加子View,即Activity中设置的布局文件
        mLayoutInflater.inflate(layoutResID, mContentParent);
        
        //...
        
        //Callback是Window类中的一个接口,用户可以通过接口中的回调方法,执行一些操作,比如事件分发。Activity是实现了这个接口的。
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

通过上面的流程我们大致可以了解先在PhoneWindow中创建了一个DecroView,其中创建的过程中可能根据Theme不同,加载不同的布局格式,例如有没有Title,或有没有ActionBar等,然后再向mContentParent中加入子View,即Activity中设置的布局。到此位置,视图一层层嵌套添加上了。

接下来看一下PhoneWindow的installDecor()方法精简版

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            //...
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            //...
        }
    }

接下来看一下PhoneWindow的generateDecor()方法

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

很简单,创建了一个DecorView。要注意的是我们传入的第二个参数是 -1 。

接下来看一下PhoneWindow的generateDecor()方法

protected ViewGroup generateLayout(DecorView decor) {
        // 从主体中获取样式信息
        TypedArray a = getWindowStyle();
        //...

        //初始化一些主体样式
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }

        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }
        //...

       // Inflate the window decor. 
       int layoutResource;
       int features = getLocalFeatures();
        // System.out.println("Features: 0x" + Integer.toHexString(features));
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
        } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                //...
                layoutResource = R.layout.screen_title_icons;
            }
        } else if(/**/) {
                //...
        }
    //...
    View in = mLayoutInflater.inflate(layoutResource, null);
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in;
   //1. ID_ANDROID_CONTENT就是@android:id/content,这个常量是在Window类中定义的
   ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   //...
   //返回contentParent
   return contentParent;
}
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

generateLayout方法的逻辑:先从主题中获取样式,然后根据样式,加载对应的布局到DecorView中,然后从中获取contentParent返回。

以上就是DecorView的创建过程。

DecorView的显示

以上仅仅是将DecorView建立起来。通过setContentView()设置的界面,为什么在onResume()之后才对用户可见呢?这就要从ActivityThread开始说起了。

ActivityThread的handleLaunchActivity方法精简版

 private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
     //...
    Activity a = performLaunchActivity(r, customIntent);
    //...
    if (a != null) {
        handleResumeActivity(r.token, false, r.isForward,
                    !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }
    //...

}

接下来看一下ActivityThread的performLaunchActivity方法精简版,这个方法内部会执行Activity的attach方法和onCreate方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
         //...
        //创建Activity实例
        Activity activity = null;
        try {
            java.lang.ClassLoader cl = appContext.getClassLoader();
            activity = mInstrumentation.newActivity(
                    cl, component.getClassName(), r.intent);
            //...
        } catch (Exception e) {
            //...
        }

        try {
             //...
            if (activity != null) {
               //...
              //1. activity调用attach方法
                activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
               //...
              //2. 这里会使Activity的onCreate方法被调用
              mInstrumentation.callActivityOnCreate(activity, r.state);
             //...
            mActivities.put(r.token, r);

        } catch (SuperNotCalledException e) {
            throw e;

        } catch (Exception e) {
            //...
        }
        
        return activity;
    }
  1. activity调用attach方法

Activity的attach方法精简版

final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback) {
      
        //...
        //创建Window对象
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
       //...
      //添加callback对象
      mWindow.setCallback(this);
    }
  1. 这里会使Activity的onCreate方法被调用。onCreate方法里面执行了setContentView方法。

接下来看一下ActivityThread的handleResumeActivity方法精简版

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
  //...  
  //1. 在这个方法里activity调用了onResume方法
  r = performResumeActivity(token, clearHide, reason);
  if (r != null) {
       final Activity a = r.activity;
       if (r.window == null && !a.mFinished && willBeVisible) {
                //为r.window赋值
                r.window = r.activity.getWindow();
                //为decor 赋值
                View decor = r.window.getDecorView();
                //设置decor不可见 
                decor.setVisibility(View.INVISIBLE);
                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();
                a.mDecor = decor;
                l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
                //...  
                if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        //被添加进WindowManager了,但是这个时候,还是不可见的
                        wm.addView(decor, l);
                    } 
                }
               //...  
            }
         // The window is now visible if it has been added, we are not
        // simply finishing, and we are not starting another activity.
        if (!r.activity.mFinished && willBeVisible
                && r.activity.mDecor != null && !r.hideForNow) {
            //...  
            r.activity.mVisibleFromServer = true;
            mNumVisibleActivities++;
            if (r.activity.mVisibleFromClient) {
               //在这里,执行了重要的操作,使得DecorView可见。千呼万唤始出来啊。
                r.activity.makeVisible();
            }
        }
   } 
}

当我们执行了Activity.makeVisible()方法之后,界面才对我们是可见的。

Activity的makeVisible方法

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            //1. ViewManager添加View
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        //DecorView设置可见性
        mDecor.setVisibility(View.VISIBLE);
    }

到此DecorView便可见,显示在屏幕中。但是在这其中,ViewManager添加View起到了重要的作用,因为其内部创建了一个ViewRootImpl对象,负责绘制显示各个子View。

  1. ViewManager添加View

ViewManager是一个接口,是WindowManager的父接口,WindowManager对应WindowManagerImpl。

WindowManagerImpl类的addView方法。

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

内部调用mGlobal的addView方法。mGlobal是一个WindowManagerGlobal对象。

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);
           //将DecorView交给ViewRootImpl  
           root.setView(view, wparams, panelParentView);
        }
    }

看到其中实例化了ViewRootImpl对象,然后调用其setView()方法。其中setView()方法经过一些列折腾,最终调用了performTraversals()方法,然后依照下图流程层层调用,完成绘制,最终界面才显示出来。

ViewRootImpl类的setView方法精简版

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            //...
           //1. 调用requestLayout方法
            requestLayout();
           //... 
        }
}

ViewRootImpl类的requestLayout方法精简版

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //1. 调用scheduleTraversals方法
            scheduleTraversals();
        }
    }

ViewRootImpl类的scheduleTraversals方法精简版

void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            //...
            //1. 注意mTraversalRunnable是一个TraversalRunnable对象
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            //...
        }
    }

TraversalRunnable是ViewRootImpl的一个内部类

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            //1.调用ViewRootImpl类的doTraversal方法
            doTraversal();
        }
    }

ViewRootImpl类的doTraversal方法精简版

void doTraversal() {
        if (mTraversalScheduled) {
           //...
          //调用performTraversals方法
           performTraversals();
         //...
        }
    }

ViewRootImpl类的performTraversals方法精简版

private void performTraversals() {

  //...
  performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  //...
  performLayout(lp, mWidth, mHeight);
  //...
  performDraw();
  //...
}
performTravels.png

其实ViewRootImpl的作用不止如此,还有许多功能,如事件分发。

要知道,当用户点击屏幕产生一个触摸行为,这个触摸行为则是通过底层硬件来传递捕获,然后交给ViewRootImpl,接着将事件传递给DecorView,而DecorView再交给PhoneWindow,PhoneWindow再交给Activity,然后接下来就是我们常见的View事件分发了。

触摸事件的传递顺序是从硬件到ViewRootImpl的。

从ViewRootImpl开始触摸事件传递顺序如下图所示:


触摸事件传递顺序.png

注意:最后一个步骤

DecorView的superDispatchTouchEvent方法

public boolean superDispatchTouchEvent(MotionEvent event) {
            return super.dispatchTouchEvent(event);
        }

因为DecorView是继承自FrameLayout的。所以从现在开始ViewGroup开始处理事件分发。

总结

以上通过源码形式介绍了Window、Activity、DecorView以及ViewRoot之间的错综关系,以及如何创建并显示DecorView。通过以上了解可以知道,Activity就像个控制器,不负责视图部分。Window像个承载器,装着内部视图。DecorView就是个顶层视图,是所有View的最外层布局。ViewRoot像个连接器,负责沟通,通过硬件的感知来通知视图,进行用户之间的交互。

参考链接
简析Window、Activity、DecorView以及ViewRoot之间的错综关系
Android View源码解读:浅谈DecorView与ViewRootImpl

上一篇下一篇

猜你喜欢

热点阅读