Android 顶级视图 DecorView 的前世今生

在Activity的启动过程中会执行ActivityThread#performLaunchActivity方法,其中调用Activity#attach。在attach()方法中实例化Activity持有的mWindow属性为Window的唯一实现类PhoneWindow。

    ActivityThread#performLaunchActivity
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        activity.attach(...);
        ...
    }

    Activity#attach
    final void attach(...) {
        ...
        mWindow = new PhoneWindow(this, window);
        mWindow.setWindowManager(...);
        mWindowManager = mWindow.getWindowManager();
        ...
    }

在ActivityThread#handleResumeActivity中,通过ActivityThread#performResumeActivity方法调用Activity#onResume之后,将会获取DecorView并通过WindowManager添加进ViewRootImpl。

    final void handleResumeActivity(...) {
        ...
        r = performResumeActivity(token, clearHide, reason);
        r.window = r.activity.getWindow();
        View decor = r.window.getDecorView();
        decor.setVisibility(View.INVISIBLE);
        ViewManager wm = a.getWindowManager();
        a.mDecor = decor;
        wm.addView(decor, l);
        ...
        r.activity.makeVisible();
        ...
    }

     PhoneWindow#getDecorView
     public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

r.activity.getWindow()返回的就是在Activity#attach中实例化的PhoneWindow对象。之后调用PhoneWindow#getDecorView去获取DecorView对象,具体过程参见Android setContentView()源码解析。然后填充decorView但是设置为不可见(通过r.activity.makeVisible()设置可见),最后通过WindowManager#addView将decorView添加进ViewRootImpl。ViewRootImpl下文会仔细分析,关于addView的过程参考Android 使用WindowManager实现悬浮窗及源码解析。细心的同学可能会注意到这时的DecorView刚通过WindowManger#addView添加到ViewRoot,其实这也解释了为什么说“在onCreate至onResume过程中,Activity已经对系统可见,但是还没有展示到界面上”的原因。这时因为Activity被装载而且已经执行完attach、onCreate、onStart、甚至执行完onResume,但是DecorView始终没有被添加到Window上。onCreate、onStart、onResume中获取不到控件宽高也是因为这个原因,在WindowManager#addView(内部实例化ViewRoot)之后才会真正的执行layout、measure、draw。

DecorView是FrameLayout的子类,说白了也是继承自View。其余XML中的View/ViewGroup都是被添加进DecorView属于“将View/ViewGroup添加进ViewGroup”类型,不具有代表性,感兴趣的可以参考Android XML布局文件解析过程源码解析

如果在Activity#onCreate中调用setContentView()方法,那么会直接创建DecorView。如果没调用setContentView()方法,那么在ActivityThread#handleResumeActivity中会通过r.window.getDecorView()自动创建DecorView。总之,不管你创建于否,Activity中总会存在由PhoneWindow创建的DecorView。PhoneWindow的作用就是操作View,Activity调用findViewById类似的方法都是通过PhoneWindow间接操作View。如此,DecorView作为View树的顶级视图通过PhoneWindow便和Activity关联了起来。

在WindowManger#addView的过程中,调用了ViewRootImpl#addView。

    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                ...
                mView = view;
                requestLayout();
                res = mWindowSession.addToDisplay(...);
                ...
                }
            }
    }

首先将传递进来的View赋值给全局属性mView,之后用mView属性来操作DecorView。mWindowSession.addToDisplay的过程在Android 使用WindowManager实现悬浮窗及源码解析中已经解析过,简述作用就是将window添加进WindowManagerService的mWindowMap属性进行管理。requestLayout()这个方法就厉害了,可能你会自定义View,可能你看过View绘制的三大流程(measure、layout、draw),可是你知道这些东西的源头吗?没错,都在requestLayout()这里。插个题外话,通过上述的代码可以发现,PhoneWindow也不是直接操作DecorView,中间还隔着个ViewRootImpl。

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }

通常在子线程中更新UI,会爆出checkThread中的错误。但是仔细看这个方法,mThread是在初始化ViewRootImpl时实例化的,等于创建ViewRootImpl的线程。正常情况下我们是在主线程创建的ViewRootImpl,实际上在子线程中也是可以创建的,例如创建Toast。Toast也是一种window,参考Android 高级自定义Toast及源码解析。但是Thread.currentThread()为更新UI时的线程。这两个如果同时在子线程会怎么样呢?什么也不会发生。是的,子线程是可以更新UI的。但是这里对UI的创建线程有要求,只有子线程创建的ViewRootImpl可以在子线程中更新UI。子线程更新UI的示例代码如下:

        new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                // Toast.makeText(MainActivity.this,"一口仨馍",Toast.LENGTH_SHORT).show();
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("Game of Thrones")
                        .setMessage("winter is coming")
                        .setPositiveButton("yes,my lord", null)
                        .show();
                Looper.loop();
            }
        }).start();

scheduleTraversals()方法经过层层调用(mTraversalRunnable->doTraversal->performTraversals)

    private void performTraversals() {
        final View host = mView;
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        performLayout(lp, mWidth, mHeight);
        performDraw();
    }

    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

     private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
         final View host = mView;
         host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    }

    private void performDraw() {
        // draw(fullRedrawNeeded) --> drawSoftware
        mView.draw(canvas);
    }

performTraversals方法巨长,这里只截取View绘制三大流程的起点。mView就是之前缓存的DecorView。之后便开始了View的measure、layout、draw、onMeasure、onLayout、ondraw。。。

更多Framework源码解析,请移步 Framework源码解析系列[目录]

时间: 2024-10-29 14:09:52

Android 顶级视图 DecorView 的前世今生的相关文章

Android自定义视图四:定制onMeasure强制显示为方形

这个系列是老外写的,干货!翻译出来一起学习.如有不妥,不吝赐教! Android自定义视图一:扩展现有的视图,添加新的XML属性 Android自定义视图二:如何绘制内容 Android自定义视图三:给自定义视图添加"流畅"的动画 Android自定义视图四:定制onMeasure强制显示为方形 上一篇开发之后的效果如上图.不过看着这张图,需要注意的不是我们自定义视图展示了什么,而是这个视图的大小和位置.你会看到这个折线图有一个特定的大小(size).这个size是怎么定的呢?现在的代

ANDROID自定义视图——onMeasure流程,MeasureSpec详解

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步,也是十分关键得一步:"测量(Measure)" Measure(): Measure的中文意思就是测量.所以它的

ANDROID自定义视图——仿瀑布布局(附源码)

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考:(ANDROID自定义视图--onMeasure,MeasureSpec源码 流程 思路详解) 第二步的布局,可以参考:(AN

onLayout源码 流程 思路详解(ANDROID自定义视图)

简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量--onMeasure():决定View的大小 2.布局--onLayout():决定View在ViewGroup中的位置 3.绘制--onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 第一步的测量,可以参考我之前的文章:(ANDROID自定义视图--onMeasure流程,MeasureSpec详解) 而这篇文章就来谈谈第二步:"

android学习--视图列表(ListView和ListActivity)

说明: 视图列表(ListView和ListActivity)与AutoComplete.Spinner类似,它们都需要一个供显示的列表项,可以需要借助于内容Adapter提供显示列表项 创建ListView有两种方式: (1)直接使用ListView进行创建 (2)Activity继承ListActivity ListView的常用XML属性 下面分别用两种方式创建ListView 方式一:直接使用ListView进行创建 (1)   main_activity.xml 下面布局两个listV

Android Animations 视图动画使用详解!!!

转自:http://www.open-open.com/lib/view/open1335777066015.html Android Animations 视图动画使用详解 一.动画类型 Android的animation由四种类型组成:alpha.scale.translate.rotate XML配置文件中 alpha 渐变透明度动画效果 scale 渐变尺寸伸缩动画效果 translate 画面转换位置移动动画效果 rotate 画面转移旋转动画效果 Java Code代码中 Alpha

Android View视图系统分析和Scroller和OverScroller分析

Android  View视图系统分析和Scroller和OverScroller分析 View  视图分析 首先,我们知道.在Android中全部的视图资源(无论是Layout还是View),终于的父类都是View类.各式各样的Layout仅仅是对ViewGroup的一中特别的实现.各种View也仅仅是View的特别实现. 而ViewGroup也是对于View的一种实现.所以说全部的View元素在根本上都是一样的.当然这并不等于说View == ViewGroup,就好比仅仅有ViewGrou

Android技术——视图切换(四)“ViewSwitcher+手势识别”实现视图的滑动切换

Android技术--视图切换(一)~(四)项目的源代码在:https://github.com/YongYuIT/MeiNv_Liulanqi 上文<Android技术--视图切换(三)>实现的图片切换,虽然切换时有动画效果,但是却需要使用按钮才能切换.这个实例中,将尝试用手势识别代替按钮来实现图片切换. 这个实例也是基于前三篇文章里的项目添加而来的. /MeiNv_Liulanqi/res/layout/activity_view_switcher_huadong.xml文件: <R

【转】ANDROID自定义视图——onMeasure,MeasureSpec源码 流程 思路详解

原文地址:http://blog.csdn.net/a396901990/article/details/36475213 简介: 在自定义view的时候,其实很简单,只需要知道3步骤: 1.测量——onMeasure():决定View的大小 2.布局——onLayout():决定View在ViewGroup中的位置 3.绘制——onDraw():如何绘制这个View. 而第3步的onDraw系统已经封装的很好了,基本不用我们来操心,只需要专注到1,2两个步骤就中好了. 而这篇文章就来谈谈第一步