View是如何添加到Activity中

0.055字数 3637阅读 670

转自:http://blog.csdn.net/feiduclear_up/article/details/49201357

前言

所谓的窗口(Window)就是一个显示在手机屏幕上可视化视图的一片区域。在Android中窗口是一个抽象的概念,每一个Activity就对应着一个窗口,而所有的窗口都是由视图(View)来呈现,而我们知道View构成的一个树形结构的视图就组成了一个Activity的界面了。在Android系统中窗口分为三个类型:

  1. 应用窗口:所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。
  2. 子窗口:所谓子窗口指的是必须依附在某个父窗口之上。
  3. 系统窗口:所谓系统窗口指的是由系统进程创建,不依赖于任何应用或者不依附在任何父窗口之上。

在WindowManager类的内部类LayoutParams中定义了以上三种窗口类型,我们暂且不管WindowManager类是干嘛的,直接看其内部类LayoutParams的实现。内部类LayoutParams其实是一组用于描述窗口(Window)参数的数据类,其中包括窗口的大小,类型,特征,软键盘输入法模式,相对位置以及动画,背景图像格式等等。

》转自:http://blog.csdn.net/feiduclear_up/article/details/49201357

前言

所谓的窗口(Window)就是一个显示在手机屏幕上可视化视图的一片区域。在Android中窗口是一个抽象的概念,每一个Activity就对应着一个窗口,而所有的窗口都是由视图(View)来呈现,而我们知道View构成的一个树形结构的视图就组成了一个Activity的界面了。在Android系统中窗口分为三个类型:

  1. 应用窗口:所谓应用窗口指的就是该窗口对应一个Activity,因此,要创建应用窗口就必须在Activity中完成了。
  2. 子窗口:所谓子窗口指的是必须依附在某个父窗口之上。
  3. 系统窗口:所谓系统窗口指的是由系统进程创建,不依赖于任何应用或者不依附在任何父窗口之上。

在WindowManager类的内部类LayoutParams中定义了以上三种窗口类型,我们暂且不管WindowManager类是干嘛的,直接看其内部类LayoutParams的实现。内部类LayoutParams其实是一组用于描述窗口(Window)参数的数据类,其中包括窗口的大小,类型,特征,软键盘输入法模式,相对位置以及动画,背景图像格式等等。

1.窗口参数WinsowManager#LayoutParams

public interface WindowManager extends ViewManager {
    ...........
    public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {

        //窗口的起点坐标
        public int x;
        public int y;

        //以下定义都是描述窗口的类型
        public int type;
        //第一个应用窗口
        public static final int FIRST_APPLICATION_WINDOW = 1;
        //所有程序窗口的base窗口,其他应用程序窗口都显示在它上面
        public static final int TYPE_BASE_APPLICATION   = 1;
        //所有Activity的窗口
        public static final int TYPE_APPLICATION        = 2;
        //目标应用窗口未启动之前的那个窗口
        public static final int TYPE_APPLICATION_STARTING = 3;
        //最后一个应用窗口
        public static final int LAST_APPLICATION_WINDOW = 99;

        //第一个子窗口
        public static final int FIRST_SUB_WINDOW        = 1000;
        // 面板窗口,显示于宿主窗口的上层
        public static final int TYPE_APPLICATION_PANEL  = FIRST_SUB_WINDOW;
        // 媒体窗口(例如视频),显示于宿主窗口下层
        public static final int TYPE_APPLICATION_MEDIA  = FIRST_SUB_WINDOW+1;
        // 应用程序窗口的子面板,显示于所有面板窗口的上层
        public static final int TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW+2;
        //对话框窗口
        public static final int TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW+3;
        //
        public static final int TYPE_APPLICATION_MEDIA_OVERLAY  = FIRST_SUB_WINDOW+4;
        //最后一个子窗口
        public static final int LAST_SUB_WINDOW         = 1999;

        //系统窗口,非应用程序创建
        public static final int FIRST_SYSTEM_WINDOW     = 2000;
        //状态栏,只能有一个状态栏,位于屏幕顶端,其他窗口都位于它下方
        public static final int TYPE_STATUS_BAR         = FIRST_SYSTEM_WINDOW;
        //搜索栏,只能有一个搜索栏,位于屏幕上方
        public static final int TYPE_SEARCH_BAR         = FIRST_SYSTEM_WINDOW+1;
        //电话窗口,它用于电话交互(特别是呼入),置于所有应用程序之上,状态栏之下
        public static final int TYPE_PHONE              = FIRST_SYSTEM_WINDOW+2;
        //系统警告提示窗口,出现在应用程序窗口之上
        public static final int TYPE_SYSTEM_ALERT       = FIRST_SYSTEM_WINDOW+3;
        //锁屏窗口
        public static final int TYPE_KEYGUARD           = FIRST_SYSTEM_WINDOW+4;
        //信息窗口,用于显示Toast
        public static final int TYPE_TOAST              = FIRST_SYSTEM_WINDOW+5;
        //系统顶层窗口,显示在其他一切内容之上,此窗口不能获得输入焦点,否则影响锁屏
        public static final int TYPE_SYSTEM_OVERLAY     = FIRST_SYSTEM_WINDOW+6;
        //电话优先,当锁屏时显示,此窗口不能获得输入焦点,否则影响锁屏
        public static final int TYPE_PRIORITY_PHONE     = FIRST_SYSTEM_WINDOW+7;
        //系统对话框窗口
        public static final int TYPE_SYSTEM_DIALOG      = FIRST_SYSTEM_WINDOW+8;
        //锁屏时显示的对话框
        public static final int TYPE_KEYGUARD_DIALOG    = FIRST_SYSTEM_WINDOW+9;
        //系统内部错误提示,显示在任何窗口之上
        public static final int TYPE_SYSTEM_ERROR       = FIRST_SYSTEM_WINDOW+10;
        //内部输入法窗口,显示于普通UI之上,应用程序可重新布局以免被此窗口覆盖
        public static final int TYPE_INPUT_METHOD       = FIRST_SYSTEM_WINDOW+11;
        //内部输入法对话框,显示于当前输入法窗口之上
        public static final int TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12;
        //墙纸窗口
        public static final int TYPE_WALLPAPER          = FIRST_SYSTEM_WINDOW+13;
        //状态栏的滑动面板
        public static final int TYPE_STATUS_BAR_PANEL   = FIRST_SYSTEM_WINDOW+14;
        //安全系统覆盖窗口,这些窗户必须不带输入焦点,否则会干扰键盘
        public static final int TYPE_SECURE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+15;
        //最后一个系统窗口
        public static final int LAST_SYSTEM_WINDOW      = 2999;

        ........

        //窗口特征标记
        public int flags;
        //当该window对用户可见的时候,允许锁屏
        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;
        //窗口后面的所有内容都变暗
        public static final int FLAG_DIM_BEHIND        = 0x00000002;
        //Flag:窗口后面的所有内容都变模糊
        public static final int FLAG_BLUR_BEHIND        = 0x00000004;
        //窗口不能获得焦点
        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;
        //窗口不接受触摸屏事件
        public static final int FLAG_NOT_TOUCHABLE      = 0x00000010;
        //即使在该window在可获得焦点情况下,允许该窗口之外的点击事件传递到当前窗口后面的的窗口去
        public static final int FLAG_NOT_TOUCH_MODAL    = 0x00000020;
        //当手机处于睡眠状态时,如果屏幕被按下,那么该window将第一个收到触摸事件
        public static final int FLAG_TOUCHABLE_WHEN_WAKING = 0x00000040;
        //当该window对用户可见时,屏幕出于常亮状态
        public static final int FLAG_KEEP_SCREEN_ON     = 0x00000080;
        //:让window占满整个手机屏幕,不留任何边界
        public static final int FLAG_LAYOUT_IN_SCREEN   = 0x00000100;
        //允许窗口超出整个手机屏幕
        public static final int FLAG_LAYOUT_NO_LIMITS   = 0x00000200;
        //window全屏显示
        public static final int FLAG_FULLSCREEN      = 0x00000400;
        //恢复window非全屏显示
        public static final int FLAG_FORCE_NOT_FULLSCREEN   = 0x00000800;
        //开启窗口抖动
        public static final int FLAG_DITHER             = 0x00001000;
        //安全内容窗口,该窗口显示时不允许截屏
        public static final int FLAG_SECURE             = 0x00002000;

        //锁屏时显示该窗口
        public static final int FLAG_SHOW_WHEN_LOCKED = 0x00080000;
        //系统的墙纸显示在该窗口之后
        public static final int FLAG_SHOW_WALLPAPER = 0x00100000;
        //当window被显示的时候,系统将把它当做一个用户活动事件,以点亮手机屏幕
        public static final int FLAG_TURN_SCREEN_ON = 0x00200000;
        //该窗口显示,消失键盘
        public static final int FLAG_DISMISS_KEYGUARD = 0x00400000;
        //当该window在可以接受触摸屏情况下,让因在该window之外,而发送到后面的window的触摸屏可以支持split touch
        public static final int FLAG_SPLIT_TOUCH = 0x00800000;
        //对该window进行硬件加速,该flag必须在Activity或Dialog的Content View之前进行设置
        public static final int FLAG_HARDWARE_ACCELERATED = 0x01000000;
        //让window占满整个手机屏幕,不留任何边界
        public static final int FLAG_LAYOUT_IN_OVERSCAN = 0x02000000;
        //透明状态栏
        public static final int FLAG_TRANSLUCENT_STATUS = 0x04000000;
        //透明导航栏
        public static final int FLAG_TRANSLUCENT_NAVIGATION = 0x08000000;

        ..........
        //软输入法模式
        public int softInputMode;

        //用于描述软键盘显示规则的bite的mask
        public static final int SOFT_INPUT_MASK_STATE = 0x0f;
        //没有软键盘显示的约定规则
        public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;
        //可见性状态softInputMode,请不要改变软输入区域的状态
        public static final int SOFT_INPUT_STATE_UNCHANGED = 1;
        //用户导航(navigate)到你的窗口时隐藏软键盘
        public static final int SOFT_INPUT_STATE_HIDDEN = 2;
        //总是隐藏软键盘
        public static final int SOFT_INPUT_STATE_ALWAYS_HIDDEN = 3;
        //用户导航(navigate)到你的窗口时显示软键盘
        public static final int SOFT_INPUT_STATE_VISIBLE = 4;
        //总是显示软键盘
        public static final int SOFT_INPUT_STATE_ALWAYS_VISIBLE = 5;
        //显示软键盘时用于表示window调整方式的bite的mask
        public static final int SOFT_INPUT_MASK_ADJUST = 0xf0;
        //不指定显示软件盘时,window的调整方式
        public static final int SOFT_INPUT_ADJUST_UNSPECIFIED = 0x00;
        //当显示软键盘时,调整window内的控件大小以便显示软键盘
        public static final int SOFT_INPUT_ADJUST_RESIZE = 0x10;
        //当显示软键盘时,调整window的空白区域来显示软键盘,即使调整空白区域,软键盘还是有可能遮挡一些有内容区域,这时用户就只有退出软键盘才能看到这些被遮挡区域并进行
        public static final int SOFT_INPUT_ADJUST_PAN = 0x20;
        //当显示软键盘时,不调整window的布局
        public static final int SOFT_INPUT_ADJUST_NOTHING = 0x30;
        //用户导航(navigate)到了你的window
        public static final int SOFT_INPUT_IS_FORWARD_NAVIGATION = 0x100;

        //窗口的对齐方式
        public int gravity;

        //期望的位图格式,默认为不透明,参考android.graphics.PixelFormat
        public int format;
        //窗口所使用的动画设置,它必须是一个系统资源而不是应用程序资源,因为窗口管理器不能访问应用程序
        public int windowAnimations;
        //整个窗口的半透明值,1.0表示不透明,0.0表示全透明
        public float alpha = 1.0f;
        //当FLAG_DIM_BEHIND设置后生效,该变量指示后面的窗口变暗的程度,1.0表示完全不透明,0.0表示没有变暗
        public float dimAmount = 1.0f;

        public static final float BRIGHTNESS_OVERRIDE_NONE = -1.0f;
        public static final float BRIGHTNESS_OVERRIDE_OFF = 0.0f;
        public static final float BRIGHTNESS_OVERRIDE_FULL = 1.0f;
        public float screenBrightness = BRIGHTNESS_OVERRIDE_NONE;
        //用来覆盖用户设置的屏幕亮度,表示应用用户设置的屏幕亮度,从0到1调整亮度从暗到最亮发生变化
        public float buttonBrightness = BRIGHTNESS_OVERRIDE_NONE;

        public static final int ROTATION_ANIMATION_ROTATE = 0;
        public static final int ROTATION_ANIMATION_CROSSFADE = 1;
        public static final int ROTATION_ANIMATION_JUMPCUT = 2;
        //屏幕旋转动画
        public int rotationAnimation = ROTATION_ANIMATION_ROTATE;

        //窗口的标示符
        public IBinder token = null;
        //此窗口所在应用的包名
        public String packageName = null;
        //窗口屏幕方向
        public int screenOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;

        //控制status bar是否可见,两种赋值  View#STATUS_BAR_VISIBLE;View#STATUS_BAR_HIDDEN
        public int systemUiVisibility;

        ......

    }
}

分析:WindowManager.LayoutParams继承自ViewGrop.LayoutParams用于描述Android窗口的参数。由上面代码定义我们知道关于窗口有以下几个重要的参数:

  • width:描述窗口的宽度,该变量是父类ViewGroup.LayoutParams的成员变量。
  • height:描述窗口的高度,该变量同样是父类ViewGroup.LayoutParams的成员变量。
  • x:描述窗口的起点X轴的坐标。
  • y:描述窗口起点Y轴的坐标。
  • type:窗口的类型,分为三个大类型:应用窗口,子窗口,系统窗口。
  • flag:窗口特征标记,比如是否全屏,是否隐藏标题栏等。
  • gravity:窗口的对齐方式,居中还是置顶或者置底等等。

接下来从源码角度分析Android系统中的以上三种窗口创建过程。

2.应用窗口创建过程分析

2.1Activity,Window,WindowManager之间的关系

一个应用窗口一般对应一个Activity对象,因此要创建应用窗口就必须创建一个Activity。由Activity的启动过程知道,所有Activity的启动都是有ActivityManagerService(简称Ams)通过Bindler进程间通信机制向客户端进程ActivityThread发送创建新的Activity对象通知,所有Activity的创建都在对应应用程序进程ActivityThread类中完成。Activity类的创建完成之后会调用Activity#attach方法,代码如下:

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {

    private Window mWindow;

    private WindowManager mWindowManager;

    ........
    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) {
        attachBaseContext(context);
        //----- 1 ------
        mWindow = new PhoneWindow(this);
        //----- 2 ------
        mWindow.setCallback(this);
        //----- 3 ------
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        //----- 4 ------   
        mWindow.setWindowManager(            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        //----- 5 ------
        mWindowManager = mWindow.getWindowManager();
        .........
    }
    ........
}

分析:

1. 在attach放法中首先创建一个PhoneWindow对象赋值给Attivity的成员变量mWindow,而Window类是一个抽象类,用于描述屏幕顶层视图和用户行为操作的窗口,而PhoneWindow是实现了抽象类Window的子类。

2. 给Activity窗口Window类型的成员变量mWindow设置事件回调监听,而Activity实现了Window#Callback接口类,如此一来Activity就可以分发处理触摸事件了,这就是为什么按back键当前Activity会finish掉的原因。

3. 给Activity窗口Window类型的成员变量mWindow设置窗口的消失回调监听,用于处理Activity的窗口消失时finish掉当前Activity。

4. 获得当前Activity的窗口管理对象WindowManager,然后给Window类设置窗口管理服务,也就是赋值给Window类的成员变量mWindowManager。

5. 获得Window类的窗口管理对象mWindowManager,然后赋值给Activity的成员变量mWindowManager,由此我们知道Activity的窗口管理和Window类的窗口管理指向的都是同一个WindowManager对象。

Window,WindowManager,Activity关系类图如下:

[图片上传中...(image-c12d5f-1511334343081-2)]

Activity的成员变量mWindow类型为Window,该类就是用于描述应用程序窗口类,因此一个Activity对应着一个Window也就是应用窗口。

Activity的成员变量mWindowManager类型为WindowManager,它用来管理当前Activity的窗口,因此一个Activity也对应着一个WindowManager窗口管理器。有前面分析得知Window类的成员变量mWindowManager和Activity的成员变量mWindowManager都是指向同一个WindowManager对象,而WindowManager对象是调用如下代码获取:

(WindowManager)context.getSystemService(Context.WINDOW_SERVICE)

而我们知道Activity是继承Context类的,ContextImpl类中实现了WindowManager服务的注册,代码如下:

class ContextImpl extends Context {
static {
    registerService(WINDOW_SERVICE, new ServiceFetcher() {
                Display mDefaultDisplay;
                public Object getService(ContextImpl ctx) {
                    Display display = ctx.mDisplay;
                    if (display == null) {
                        if (mDefaultDisplay == null) {
                            DisplayManager dm = (DisplayManager)ctx.getOuterContext().
                                    getSystemService(Context.DISPLAY_SERVICE);
                            mDefaultDisplay = dm.getDisplay(Display.DEFAULT_DISPLAY);
                        }
                        display = mDefaultDisplay;
                    }
                    return new WindowManagerImpl(display);
                }});
    .......
    }
}

有上面代码可知,在ContextImpl类中注册服务是一个静态代码块,也就是说值执行main方法之前就已经完成了WindowManager服务的注册。在整个应用第一次创建Context对象的时候就已经创建WindowManagerImpl对象了,然而WindowManager是个抽象类,具体实现指向WindowManagerImpl。整个应用程序的窗口管理服务都是指向同一个WindowManagerImpl对象,言外之意,不管一个应用程序中有多少个Activity,但只有一个WindowManagerImpl对象。

2.2 Activity窗口添加视图View的过程分析

前面我们说了,一个Activity对应着一个窗口(Window),而窗口只是一个抽象的概念,所有的窗口都有视图(View)来呈现,因此我们来分析下视图View是怎么添加到窗口Window上的?

每个Activity都会调用setContextView方法来加载布局视图,而这其实就是视图View添加到Activity窗口上的一个过程。加载布局视图代码如下Activity#setContentView

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

由于getWindow方法获得的是Activity的成员变量mWindow,而Window对象其实指向PhoneWindow类,因此这里仅仅做了一个转发,其实现在PhoneWindow#setContentView方法中。代码如下:

 public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            //给窗口安装装饰视图DecorView
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            //加载xml布局视图内容到视图容器mContentParent中
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
    }

分析:

1. 调用installDecor方法给窗口安装视图装饰,所谓视图装饰指的就是界面上看到的标题栏,导航栏Actionbar,也就是窗口的标题栏;而视图装饰的内容就是layout.xml布局,也就是窗口所加载的视图View。

installDecor方法实现的代码如下:

 private void installDecor() {
        if (mDecor == null) {
            //
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
         }
        ........
}

该方法中主要做了两件事:

  • 调用generateDecor方法获得DecorView对象,该对象是ViewGroup类型,用于描述窗口Window的顶层视图。
  • 调用generateLayout方法给窗口添加标题栏,同时获得装载窗口内容的容器mContentParent,该成员变量也是ViewGroup类型。

2.调用inflate方法将布局视图内容加载到刚刚获得的内容容器mContentParent上。

到此,Activity窗口Window添加视图View的流程就结束了。现总结一下他们之间的关系:一个Activity中有一个Window对象用于描述当前应用的窗口,而Window是抽象类,实际上指向PhoneWindow类。PhoneWindow类中有一个内部类DecorView用于描述窗口的顶层视图,该类实际上是ViewGroup类型。当往DecorView上添加标题栏和窗口内容容器之后,最后将layout.xml布局添加到窗口的内容容器中,即完成了窗口Window添加视图View了。最后用之前博客中的一张图片来描述以上关系:

这里写图片描述

【转载请注明出处:http://blog.csdn.net/feiduclear_up

CSDN 废墟的树】

2.3 Activity添加窗口Window的过程

有上一小节知道一个Activity对应着一个窗口Window,而窗口Window也完成了视图View的添加,最后视图View是怎么绘制到手机屏幕上的呢?

在Activity创建完成之后,Ams利用Bindler进程间的通信机制通知客户端调用ActivityThread类中的handleResumeActivity方法来启动Activity。代码如下:

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {
        ........
        ActivityClientRecord r = performResumeActivity(token, clearHide);

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

            final int forwardBit = isForward ?
                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;
            boolean willBeVisible = !a.mStartedActivity;
            if (!willBeVisible) {
                try {
                    willBeVisible = ActivityManagerNative.getDefault().willActivityBeVisible(
                            a.getActivityToken());
                } catch (RemoteException e) {
                }
            }
            if (r.window == null && !a.mFinished && willBeVisible) {
                //客户端Activity的窗口Window对象
                r.window = r.activity.getWindow();
                //窗口的顶层视图DecorView对象
                View decor = r.window.getDecorView();
                decor.setVisibility(View.INVISIBLE);
                //客户端Activity窗口的窗口管理器对象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) {
                    //标记客户端Activity添加窗口成功
                    a.mWindowAdded = true;
                    //添加窗口
                    wm.addView(decor, l);
                }
    ........
        }
    }

分析:

首先获得客户端Activity的窗口对象Window,然后获得该窗口的顶层视图DecorView赋值给本地变量decor,再获得客户端Activity的窗口管理对象WindowManager赋值给本地变量wm,之后再来构造窗口Window的参数WindowManager.LayoutParams,将当前窗口的类型type赋值为TYPE_BASE_APPLICATION,由文章开头我们知道该类型就是应用窗口类型。最后调用WindowManager类中的addView方法完成当前窗口Window的添加。

在这个过程中最主要的操作就是调用了WindowManager#addView方法,而WindowManager是个接口类,继承自ViewManager。

ViewManager源码如下:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

该接口类仅仅定义了三个接口方法:

  • addView:添加View视图
  • updateViewLayout:更新视图
  • removeView:移除视图

所以该类是用来管理视图View状态的类,也就是管理视图View的添加,更新,移除。从此处也可以看出,所谓的WindowManager窗口管理其实就是对窗口的视图View进行管理。

WindowManager类源码如下:

public interface WindowManager extends ViewManager {
    ........
     public Display getDefaultDisplay();
     public void removeViewImmediate(View view);
     public static class LayoutParams extends ViewGroup.LayoutParams
            implements Parcelable {
    ........
    }
}

该类继承自ViewManager,也是一个接口类,里面定义了两个接口方法和一个内部类LayoutParams类用来描述窗口Window参数。关于WindowManager真正的实现其实在WindowManagerImpl类中。

WindowManagerImpl源码如下:

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

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
        return new WindowManagerImpl(mDisplay, parentWindow);
    }

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

    @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.updateViewLayout(view, params);
    }
@Override
    public void removeView(View view) {
        mGlobal.removeView(view, false);
    }

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

    @Override
    public Display getDefaultDisplay() {
        return mDisplay;
    }
}

分析:

该来中实现了接口类ViewManager中的三个方法。但是细心的你会发现,其实这三个方法内部将实际操作转发到mClobal对象的三个对应的方法中去了,而mGlobal成员变量的类型是WindowManagerGlobal类。

WindowManagerGlobal类源码:

public final class WindowManagerGlobal {
    private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams =
            new ArrayList<WindowManager.LayoutParams>();
    ........
    //单例模式构造方法
    public static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }
     ........
     //窗口的添加过程
     public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
        if (parentWindow != null) {
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            final Context context = view.getContext();
            if (context != null
                    && context.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.LOLLIPOP) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;
        synchronized (mLock) {    
            if (mSystemPropertyUpdater == null) {
                mSystemPropertyUpdater = new Runnable() {
                    @Override public void run() {
                        synchronized (mLock) {
                            for (int i = mRoots.size() - 1; i >= 0; --i) {
                                mRoots.get(i).loadSystemProperties();
                            }
                        }
                    }
                };

            }
            //不能重复添加窗口
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }       
            }
            //判断当前窗口是否为子窗口,如果是则获得其父窗口并保存在panelParentView变量中
            if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                    wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
                final int count = mViews.size();
                for (int i = 0; i < count; i++) {
                    if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                        panelParentView = mViews.get(i);
                    }
                }
            }
            //每一个窗口对应着一个ViewRootImpl对象
            root = new ViewRootImpl(view.getContext(), display);
            //给当前窗口视图设置参数
            view.setLayoutParams(wparams);
            //保存三个数组
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        try {
            //真正执行窗口的视图View绘制工作的方法
            root.setView(view, wparams, panelParentView);
        } 
    }

 //主要更新当前窗口的参数LayoutParams
 public void updateViewLayout(View view, ViewGroup.LayoutParams params) {

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }
    //从三个数组里面分别移除DecorView对象,ViewRootIpl对象,WindowManager.LayoutParams对象
    public void removeView(View view, boolean immediate) { 
        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }
        }
    }

分析:

在WindowManagerGlobal类中维系着三个数组分别是:

  • mViews:保存着当前应用所有窗口的顶层视图DecorView对象
  • mRoots:保存着当前应用所有窗口的视图绘制类ViewRootImpl
  • mParams:保存着当前应用所有窗口的参数 WindowManager.LayoutParams

在2.1小节的最后我们知道,一个应用中不管有多少个Activity,都共用一个WindowManagerImpl对象,而在WindowManagerImpl类中是单例模式获得WindowManagerGlobal对象,因此:一个应用中也就只有一个WindowManagerGlobal对象了。而在该对象中通过维护以上三个数组来维护一个应用中所有窗口的管理。

addView方法:

  • 首先检查当前窗口是否已经添加过,不允许重复添加窗口。
  • 判断如果添加的窗口为子窗口类型,则找到他的父窗口,然后保存在变量panelParentView中作为后面setView方法的参数。
  • 在该方法中创建ViewRootImpl对象,每一个Window对象都持有一个ViewRootImpl对象,然后为当前窗口视图设置窗口参数,在将当前窗口视图View,ViewRootImpl对象,以及窗口参数分别保存到三个数组里。
  • 最后调用ViewRootImpl#setView方法来完成当前窗口视图的绘制。ViewRootImpl类里面会依次执行measure(测量),layout(布局),draw(绘制)三个方法来完成视图View绘制到手机屏上的一个过程,此处不详解,具体参考博客:从ViewRootImpl类分析View绘制的流程。此处才是真正将视图View绘制到手机屏幕的地方。

updateViewLayout方法:

先找到指定的窗口视图View,然后将旧的参数wparams从数组mParams移除,往数组mParams添加新的窗口参数,最后给窗口视图重新设置一下参数即完成了窗口视图的更新操作。

removeView方法:

依次将三个参数从数组中移除,然后调用相应的方法销毁视图View的绘制。

<a name="t6" target="_blank" style="user-select: text !important; text-decoration: none; box-sizing: border-box; color: rgb(12, 137, 207);"></a>总结

  1. 一个Activity就对应着一个应用窗口PhoneWindow对象,该对象用于描述窗口的。

  2. PhoneWindow类内部有一个DecorView类,该类是ViewGroup类型,用于描述窗口的顶层视图的,该视图才是真正装载layout.xml布局内容的。

  3. 一个应用不管有多少个Activity,都只有一个WindowManager窗口管理器,也就只有一个WindowManagerGlobal对象通过维护三个数组 mViews,mRoots,mParams来管理所有窗口的添加,更新,删除。

  4. 一个窗口对应着一个ViewRootImpl类,该类主要的作用就是将窗口的顶层视图DecorView内容绘制到手机屏幕上。

最后给出以上所有类图关系图如下:

这里写图片描述

推荐阅读更多精彩内容