android addView是如何渲染到屏幕上的(1)——LayoutInflactor和PhoneWindow

          我们通常在Activity的onCreate() 里面调用setContentView,把一个xml设置为Activity的布局。或者在Dialog的Builder里面setView,把一个view设置给dialog作为内容视图。另外,还有往ViewGroup里addView(),添加子view的方法。这些方法里,Activity的setContentView,Dialog的setView,都是在控件生命周期执行过程中设置,这时view树还没有被构建,而ViewGroup的addView则往往是view树已经构建并且显示在窗口上之后被调用。要了解ViewGroup的addView() 的流程,绕不过了解view树从无到有的过程。所以首先还是看看 Activity.setContentView():

 一、Activity.setContentView():

public class Activity extends ContextThemeWrapper
    public void setContentView(View view) {
        getWindow().setContentView(view);
        ……
    }
}
public class PhoneWindow extends Window implements MenuBuilder.Callback {  
    private DecorView mDecor;
    ViewGroup mContentParent;
    private LayoutInflater mLayoutInflater;
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        ……
        installDecor();
        ……
        mLayoutInflater.inflate(layoutResID, mContentParent);
        ……
    }
   private void installDecor() {
        if (mDecor == null) {
            //1.跳过Context的获取过程,简单知道在这里是创建了一个DecorView
            mDecor =  new DecorView(context, -1, this, getAttributes());
            ……
        } else {
            //2.绑定window
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            //3.初始化布局视图,包括decorView和展示开发人员指定的view的content布局
            mContentParent = generateLayout(mDecor);
            //……省略有titleContent布局的情况
        }
    }

}

        Activity的setContentView()方法中是调用的getWindow()方法的setContentView。getWindow()返回的是Window的实现类是PhoneWindow。在PhoneWindow里,有两个与view绘制息息相关的成员变量:

  • DecorView mDecor :是Window的顶层视图,分为title和content两个部分
  • ViewGroup mContentParent :这是放置窗口内容的视图,是mDecor的子view,对应布局文件中R.id.content的节点,是个FramLayout。
  • LayoutInflater mLayoutInflater;  // 先跳过,分析完mDecor 和mContentParent再看。

        PhoneWindow.setContentView 主要就做了两件事情:

  • 初始化 decor——installDecor()
    •  先创建decorView (new DecorView)
    • 然后将decorView和window绑定,
    • 最后generateLayout()——初始化decorview,并且用开发人员传入的layoutResId初始化mContentParent 。
  •  mLayoutInflater.inflate()    // 先跳过,分析完mDecor 和mContentParent再看。

          先简单看看创建decorView 的创建和mContentParent 的初始化过程generateLayout() :

public class PhoneWindow extends Window implements MenuBuilder.Callback { 
    //这个id在 main_layout 会有,用来存放decorView中的content部分
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
    protected ViewGroup generateLayout(DecorView decor) {
        final Context context = getContext();
        // layoutResource是布局资源id,它就是整个Activity的布局,
        // 其中含有title区域和content区域,
        // content区域就是用来显示通过setContentView设置进来的内容区域,也就是要显示的视图
        int layoutResource;
        //会由SDK等属性选择不同的顶层视图布局,如FEATURE_NO_TITLE则选择没有title的布局文件等
        //从代码里可以看到有非常多的布局文件,比如
        layoutResource = new TypedValue().resourceId;
        layoutResource = R.layout.screen_title_icons;
        layoutResource = R.layout.screen_progress;
        layoutResource = R.layout.screen_custom_title;
        layoutResource = getWindowStyle().getResourceId(
                R.styleable.Window_windowActionBarFullscreenDecorLayout,
                R.layout.screen_action_bar);
        layoutResource = R.layout.screen_title;
        layoutResource = R.layout.screen_simple_overlay_action_mode;
        layoutResource = R.layout.screen_simple;

        //调用decorView的加载layoutResource的方法,调用完成后dicorView就包含了这个布局文件的view
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT);
        //……省略一些对mDecor设置属性,设置title的代码
        return contentParent;
    }
}

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    private PhoneWindow mWindow;
    ViewGroup mContentRoot;
    DecorView(Context context, int featureId, PhoneWindow window, WindowManager.LayoutParams params) {
        mWindow = phoneWindow;
    }
    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        final View root = inflater.inflate(layoutResource, null);
        //在没有标题试图的情况下,layoutResource 的视图会直接添加到decorView的子view
        addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) root;
    }
}

        generateLayout() 将layoutResId这个布局的视图附加到 contentParent中,这个layoutResId就是开发人员在onCreate函数中设置给Activity的那个布局资源id,也这是说Google预先设置了一些布局资源,这些布局资源里面有一个留给开发者们设置窗口内容的区域,也就是content区域,我们通过setContentView设置的布局会被添加到content布局中——也就是mContentParent。

        到 generateLayout()完成为止,以mDecor为根的视图就已经创建完成。

        接下来就是前面跳过的,PhoneWindow.setContentView() 做的第二件事情:mLayoutInflater.inflate() 。LayoutInflactor能够将布局XML文件实例化为其相应的视图对象,是理解android UI渲染非常重要的一环。LayoutInflactor 是在PhoneWindow 的构造函数中被初始化的,LayoutInflater.inflactor() 在setContentView() 中被调用。

public abstract class LayoutInflater {
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
        return inflate(resource, root, root != null);
    }
    
    public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
        return inflate(parser, root, root != null);
    }
    
    //参数1为xml解析器,参数2为要解析布局的父视图,参数3为是否将要解析的视图添加到父视图中
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
}

         LayoutInflater.inflate() 有三个参数参数1 @LayoutRes int resource 为xml解析器,参数2 @Nullable ViewGroup root 为要解析布局的父视图,参数3 boolean attachToRoot 为是否将要解析的视图添加到父视图中。root为NULL的时候默认不添加到父视图,不为NULL则默认添加。

public class LayoutInflater extends LayoutInflater {
    public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
        if (view != null) {
            return view;
        }
        XmlResourceParser parser = res.getLayout(resource);
        try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }

    public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            ……
                final String name = parser.getName();
                //……  跳过单独处理merge标签部分
                    // Temp 是xml里面的root
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    // Inflate xml里面的root节点下的所有子节点
                    rInflateChildren(parser, temp, attrs, true);
                    // 把xml添加到root
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }
            return result;
        }
    }
}

        上述的inflate方法中,主要有下面几步:
(1)解析xml中的根标签(第一个元素);
(2)如果根标签是merge,那么调用rInflate进行解析,rInflate会将merge标签下的所有子View直接添加到根标签中;
(3)如果标签是普通元素,调用createViewFromTag()对该元素进行解析;
(4)调用rInflate() 解析temp根元素下的所有子View,并且将这些子View都添加到temp下;
(5)返回解析到的根视图。

        createViewFromTag() 根据完整路径的类名通过反射机制构造View对象.

        rInflate()负责解析temp视图下的所有子View —— 我们的窗口中是一棵视图树,LayoutInflater需要解析完这棵树,这个功能就交给了rInflate方法。rInflate通过深度优先遍历来构造视图树,每解析到一个View元素就会递归调用rInflate,直到这条路径下的最后一个元素,然后再回溯过来将每个View元素添加到它们的parent中—— root.addView():

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    private View[] mChildren;
    // mChildern 数组里有效子view的个数,剩余的是null或者无效。
    private int mChildrenCount;
    public void addView(View child, int index, LayoutParams params) {
        // addViewInner() ,当子view设置新的 LayoutParams里会调用 child.requestLayout() 。但是这一层会自己先调用 requestLayout(),所以 child's request will be blocked at our level
        requestLayout();
        invalidate(true);
        // 根据 index mChildrenCount  children.length 分情况将 child 加入到数组对应index下
        addViewInner(child, index, params, false);
    }
    private void addViewInner(View child, int index, LayoutParams params,
            boolean preventRequestLayout) {
        ……
        addInArray(child, index);//把view添加到mChildren 数组里面
    }
}

        通过rInflate的解析之后,整棵视图树就构建完毕,所有的view以树的结构存储在viewGroup的view数组 mChildren 里面——(再之后,无论是事件分发,还是绘制,都以mChildren 这个view数组为基础,也就是以view的树形数据结构为基础),setContentView() 的过程终于解暑了!根view添加到mContentParent里(最开始setContentView() 里传入的)。

        到这里,PhoneWindow 里的mDecorView已经准备好了,但是还没有显示到屏幕上。所以到底什么时候显示?当然是看生命周期啦!看看onResume做了什么:        

public final class ActivityThread extends ClientTransactionHandler {    
    final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward,
                                    boolean reallyResume) {
        // 1.performResumeActivity() 最终调用Activity的onResume方法
        ActivityClientRecord r = performResumeActivity(token, clearHide);
        if (r != null) {
            final Activity a = r.activity;
            if (r.window == null && !a.mFinished && willBeVisible) {
                // 2.获取了Activity的Window
                r.window = r.activity.getWindow();
                // 3.获取了Window的DecorView,也就是最顶级的视图
                View decor = r.window.getDecorView();
                // 4.将DecorView先隐藏
                decor.setVisibility(View.INVISIBLE);
                // 5.获取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 (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    // 将DecorView添加到窗口中
                    wm.addView(decor, l);
                }
            }
            if (!r.activity.mFinished && willBeVisible
                    && r.activity.mDecor != null && !r.hideForNow) {
                // 代码省略
                if (r.activity.mVisibleFromClient) {
                    // 使得Activity变得可见,其实是设置DecorView变为可见
                    r.activity.makeVisible();
                }
            }
            // 最后通知ActivityManagerNative,该Activity己经变为resume状态
            if (reallyResume) {
                try {
                    ActivityManagerNative.getDefault().activityResumed(token);
                } catch (RemoteException ex) {
                }
            }
        }
    }
}


        handleResumeActivity()这个函数里,先通过performResumeActivity() 去调用了Activity里面开发人员写的那个onResume() 方法,然后看上面 2.3.4!正是我们上面跟了半天的PhoneWindow和它的mDecorView啊!再接着,出现了一个新的朋友,WindowManager, handleResumeActivity() 里把熟悉的DecorView添加到WindowManager了,最后将Activity的DecorView设置为可见,并且通知
ActivityManagerService渲染视图,因此,在onResume函数之后,Activity就显示在屏幕上了。

        新朋友WindowManager是个新的世界,另起一篇跟代码。为了巩固这个setContentView() - PhoneWindow-mDecorView 的流程,我们简单看看Dialog。

二、Dialog的setView

当我们要弹出一个Dialog,调用代码一般是这么写的:

View MYVIEW = ……;
AlertDialog popup = new AlertDialog.Builder(getContext()).setView(MYVIEW).create();
popup.show();

AlertDialog 同样是有一个mDecorView作为跟布局,在builder中设置,最后show的时候才会触发生命周期。

public class Dialog {
    final Window mWindow;
    View mDecor;
    public void show() {
        ……
        if (!mCreated) {
            // 4.执行onCreate生命周期
            dispatchOnCreate(null);
        } else {
            final Configuration config = mContext.getResources().getConfiguration();
            mWindow.getDecorView().dispatchConfigurationChanged(config);
        }
        onStart();
        // 5. 获取DecorView 并 将mDecor添加到WindowManager中
        mDecor = mWindow.getDecorView();
        ……
        mWindowManager.addView(mDecor, l);
        ……
    }
}

在show函数中主要做了如下几个事情:
(1)通过dispatchOnCreate函数来调用AlertDialog的onCreate函数;
(2)然后调用AlertDialog的onStart函数;
(3)最后将Dialog的DecorView添加到WindowManager中。

        onCreate() 调用了 AlertController 的installContent(),初始化AlertDialog布局中的各个部分,包括id为custom的节点,在installContent()调用之后整个 Dialog 的视图内容全部设置到布局中。onStart() 结束之后,mWindowManager.addView(mDecor, l);WindowManager会将Window对象的DecorView添加到用户的窗口上,并且显示出来。

        看吧,和Activity 手法就是一摸一样,也到了WindowManager的部分。

总结

        1.Activity和Dialog都持有一个Window对象,PhoneWindow最重要的是持有了一个mDecorView。(PhoneWindow 并不具备多少 View 相关的能力)

        2.用户设置给activity的xml,最终会通过LayoutInflactor解析,成为一个个的view节点,存储在viewgroup的view数组 mChildren里面,这是一个树的数据结构。

        3.当Activity和Dialog生命周期走到onResume时,PhoneWindow的 mDecorView会被设置给WindowManager。

[ AndroidUI渲染专题其他内容 ]

        (二)WindowManager和ViewRootImpl

        (三)surface

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值