为了账号安全,请及时绑定邮箱和手机立即绑定

Android 从源码看懂窗口绘制流程

标签:
Android

前言

Android开发最息息相关的就是界面,平时开发总会使用各种布局和视图来组合成我们的屏幕效果,在Android的知识体系中,View扮演着很重要的角色,简单来理解,View是Android在视觉上的呈现。那Android是如何将这些View一步步绘制到屏幕上的呢,这就涉及到本文所要讲的窗口绘制工作原理。

先上一张结构图,带着这张结构图去分析源码:


webp

Activity结构图

 

源码分析

我们最经常接触的往往是View这一层,在它之上有Activity,Activity一般都要在onCreate中调用setContentView,那就从setContentView进去看究竟做了啥:

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

可以看到实际上是调用了getWindow得到的对象再去setContentView,所以其实在View和Activity中间还隔着一层Window,它是Activity与View之间的桥梁,是一个抽象类,PhoneWindow是它的唯一实现子类,我们在Activity中所使用的View其实都是通过PhoneWindow来呈现的,它就类似于View的一个载体,就像PS中的画板,而View就是具体要绘制的内容。

那么PhoneWindow里面又是什么构成呢?继续顺着刚才setContentView看进去:

    @Override
    public void setContentView(int layoutResID) {        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            //忽略代码
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }        //忽略代码
    }

可以看到先是installDecor(),installDecor源码如下:

private void installDecor() {
   if (mDecor == null) {
     //new一个DecorView
     mDecor = generateDecor();
     mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
     mDecor.setIsRootNamespace(true);
     if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
       mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
     }
   }
   if (mContentParent == null) {
     mContentParent = generateLayout(mDecor);
        }        //忽略代码}

可以看到这里首先创建了一个DecorView,然后调用generateLayout,详细看下generateLayout:

protected ViewGroup generateLayout(DecorView decor) {        //忽略代码
        int layoutResource;
        ...        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) {            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_title_icons;
            }
            ...
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = R.layout.screen_custom_title;
            }
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title;
            }
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else {
            layoutResource = R.layout.screen_simple;
        }
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);    if (contentParent == null) {        throw new RuntimeException("Window couldn't find content container view");
    }    //忽略代码
    return contentParent;
}

可以看到先是根据feature类型来为layoutResource赋值,随便选择一个layout文件打开如下:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:fitsSystemWindows="true"
    android:orientation="vertical">
    <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:id="@android:id/content"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
         android:foregroundInsidePadding="false"
         android:foregroundGravity="fill_horizontal|top"
         android:foreground="?android:attr/windowContentOverlay" /></LinearLayout>

这个布局文件里最外层是一个LinearLayout,里面还包含着一个id为R.id.content的FrameLayout,我们继续回到代码中,刚才赋值了layoutResource后,接着调用了DecorView的onResourcesLoaded,先看下里面做了啥:

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {        //...忽略代码
        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 {            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;        //...忽略代码
    }    @Override
    public void onDraw(Canvas c) {        super.onDraw(c);        // When we are resizing, we need the fallback background to cover the area where we have our
        // system bar background views as the navigation bar will be hidden during resizing.
        mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c,
                mWindow.mContentParent);
    }

可以看到这里将我们传进来的layoutResource资源inflate成了View,然后将其addViewDecorView自身并且在onDraw中绘制了出来,换句话说就是把刚才那个layout文件添加到DecorView中并绘制。
那么刚才那个layout文件用来做什么的呢?回到上一步,onResroucesLoaded之后,通过findViewById查找id为R.id.content的View(看到没,这里就是我们刚才那个layout文件中的R.id.content,所以实际上就是先将layout文件add到DecorView中去,然后再将其content给find出来),然后将其返回,赋值给刚才分析过的installDecor方法中的mContentParent,然后现在回头看回我们一开始的setContentView:

    @Override
    public void setContentView(int layoutResID) {        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {            //忽略代码
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }        //忽略代码
    }

我们发现了mContentParent的影子!,mLayoutInflater.inflate(layoutResID, mContentParent);,它将layoutResID的资源通过inflate添加到了我们的mContentParent(inflate的参数解读见我另一篇文章:https://www.jianshu.com/p/3f871d95489c

所以其实我们平时setContetntView传进去的R.layout.xxx最终都会添加到R.id.content这个View上面,这也就是为什么它命名为setContentView的原因了,而R.id.content又是被包裹在一层LinearLayout里面,然后这个LinearLayout文件又会被通过addView添加到DecorView这个FrameLayout中,原来 DecorView 就是承载了我们布局文件的一个顶级大佬。

 

总结

现在再回过头看文章开头那个结构图,是否清晰了很多呢~这里只是从setContentView分析了从Activity到Window再到DecorView的层级和绘制流程,接下来还有关于View的绘制再慢慢总结,不得不说源码里还是有很多值得借鉴的地方,多看源码对自己的理解会更有帮助,不能知其然不知其所以然。



作者:Android小Y
链接:https://www.jianshu.com/p/a8f4b7e90063


点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消