【Android 源码解析】浅谈DecorView与ViewRootImpl

一、前言

对于Android开发者而言,View无疑是开发中经常接触的,包括它的事件分发机制、测量、布局、绘制流程等。如果要自定义一个View,那么应该对以上流程有所了解、研究。在深入接触View的测量、布局、绘制这三个流程之前,我们从Activity入手,看看从Activity创建后到View的绘制之前,所要经历的步骤。

 

二、从setContentView说起

一般地,我们在Activity中,会在onCreate()方法中写下这样一句:

setContentView(R.layout.activity_main);

显然,这是为Activity设置一个我们定义好的activity_main.xml布局,我们跟踪一下源码,看看这个方法是怎样做的,

Activity#setContentView

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
}

Activity#getWindow

public Window getWindow() {
        return mWindow;
    }

从上面看出,setContentView调用了mWindow的setContentView()方法,那么这个mWindow又是什么?

追踪一下源码,发现mWindow是Window类型的,但它是一个抽象类,setContentView也是一个抽象方法,我们我们要找Window类的实现类才行。我们在Actvity中查找一下mWindow在哪里被赋值了,可以发现在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) {
        attachBaseContext(context);

        //...

        mWindow = new PhoneWindow(this, window);
        
        //..
    }

我们只看关键部分,PhoneWindow是Window的实现类,那么我们在PhoneWindow类中找到setContentView方法,看看它是如何实现的,PhoneWindow#setContentView

@Override
    public void setContentView(int layoutResID) {
        //先判断mContentParent是否存在,如果为null,则installDecor()
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //...
        } else {
            //把setContentView里设置的布局,添加到mContentParent上
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        //...
    }

那么这个mContentParent是什么?我们来看下其注释:

// This is the view in which the window contents are placed. It is either
    // mDecor itself, or a child of mDecor where the contents go.
    ViewGroup mContentParent;

它是DecorView或者DecorView的子元素。

这里先梳理一下以上内容:Activity通过PhoneWindow的setContentView方法来设置布局,而设置布局之前,回先判断mContentParent是否存在,而我们设置的布局是mContentParent的子元素。

创建DecorView

接着上面提到的installDecor()方法,我们看看它的源码,PhoneWindow#install

private void installDecor() {
        mForceDecorInstall = false;
        //如果mDecor为空,就实例化mDecor
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        //如果mContentParent为空,就实例化mContentParent
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);

            //...
            
            }
        }
    }

首先,如果mDecor是空,会实例化mDecor。

protected DecorView generateDecor(int featureId) {
        return new DecorView(context, featureId, this, getAttributes());
}

DecorView继承于FrameLayout,由此可知它是一个ViewGroup。

其实,DecorView是整个ViewTree的最顶层View,它是一个FrameLayout布局,代表了整个应用的界面。

而该DecorView只有一个子元素LinearLayout,这个LinearLayout有标题View和内容View两个元素,而内容View则是上面提到的mContentParent。想知道为什么是这样,可通过查看源码PhoneWindow#generateLayout():

protected ViewGroup generateLayout(DecorView decor) {

        // ...

        int layoutResource;
        int features = getLocalFeatures();

        //...

        //这个layout就是一个LinearLayout
        layoutResource = R.layout.screen_simple;

        //调用这个方法,是把这个LinearLayout添加到DecorView中
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);

        //这个ID_ANDROID_CONTENT就是com.android.internal.R.id.content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }

        return contentParent;
    }

可以看到,generateLayout方法主要就是把一个LinearLayout添加到DecorView中,然后返回内容View。具体是由mDecor.onResourcesLoaded()操作。

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        //...

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
    }

这里把R.layout.screen_simple的布局源码也贴一下:

<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" />  

    <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>

到目前为止,通过setContentView方法,创建了DecorView加载了我们提供的布局(addView),但是,我们的View还是不可见的,因为我们仅仅加载了布局,并没有对View进行任何的测量、布局、绘制工作。

 

在View进行测量流程之前,还要进行一个步骤,那就是把DecorView添加至Window,然后触发

ViewRootImpl#performTraversals方法,在该方法内部会测量、布局、绘制。

将DecorView添加至Window

我们已经知道怎么把创建DecorView,现在我们要把这个DecorView添加到Window对象上。而要了解这个过程,我们要先了解一下Activity的创建过程:

首先,在ActivityThread#handleLaunchActivity中启动Activity,在里面会调用Activity#onCreate方法,从而完成上述DecorView的创建,当onCreate执行完毕,在handleLaunchActivity方法会继续调用ActivityThread#handleResumeActivity方法,我们来看源码:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
        // ...

        ActivityClientRecord r = mActivities.get(token);
        r = performResumeActivity(token, clearHide, reason);

        if (r != null) {
            final Activity a = r.activity;

            // ...
            if (r.window == null && !a.mFinished && willBeVisible) {
                //获取Window对象
                r.window = r.activity.getWindow();
                //获取DecorView对象
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //获取windowManager对象
                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 && !a.mWindowAdded) {
                    a.mWindowAdded = true;
                    //调用windowManager的addView方法
                    wm.addView(decor, l);
                }

            }

        }
    }

在该方法内部,获取Activity的window对象、DecorView对象,以及windowManagerImpl对象,然后调用

WindowMangerImpl#addView方法:

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

}

其实是调用了WindowMangerGlobal#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);
        }

        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }

这里调用ViewRootImpl#setView方法。在这个方法里,

最后,欢迎一起交流学习,我的邮箱zhshan@ctrip.com

 

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值