android 窗口类型分析

来源:互联网 发布:淘宝免费申请试用理由 编辑:程序博客网 时间:2024/05/22 12:32

1, 概述

Android窗口类型主要分成了三大类:

1,应用程序窗口。一般应用程序的窗口,比如我们应用程序的Activity的窗口。

2,子窗口。一般在Activity里面的窗口,比如各种菜单等。

3,系统窗口。系统的窗口,比如输入法,Toast,墙纸等。

WindowManager里面窗口的type类型值定义是一个递增保留的连续增大数值。其实就是窗口的Z-ORDER序列(值越大显示的位置越在上面,需要将屏幕想成三维坐标模式)。

2 WindowManager/LayoutParams

2.1 窗口类型

WindowManager(窗口管理)是如何管理Window呢?

WindowManager是一个接口,首先看看里面的关键定义,

窗口类型,

public int type;

应用程序窗口

FIRST_APPLICATION_WINDOW = 1

第一个应用窗口

TYPE_APPLICATION = 2

应用的默认窗口

LAST_APPLICATION_WINDOW = 99

最后的应用窗口

所有acitivty的窗口的值都在[1,99],默认值是TYPE_APPLICATION,WMS在进行窗口叠加时,会动态的改变activity的值。

子窗口

子窗口的Z序和坐标空间都依赖于Activity窗口

所有子窗口的值都在[1000,1999], WMS在进行窗口叠加时,会动态调整子窗口的值。

系统窗口

FIRST_SYSTEM_WINDOW     = 2000

第一个系统窗口

TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW

状态条

TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1

搜索条

TYPE_PHONE= FIRST_SYSTEM_WINDOW+2

来电显示窗口

TYPE_SYSTEM_ALERT= FIRST_SYSTEM_WINDOW+3

警告对话框

TYPE_KEYGUARD= FIRST_SYSTEM_WINDOW+4

屏保

TYPE_TOAST= FIRST_SYSTEM_WINDOW+5

Toast

TYPE_SYSTEM_OVERLAY= FIRST_SYSTEM_WINDOW+6

 

TYPE_PRIORITY_PHONE= FIRST_SYSTEM_WINDOW+7

屏幕保护下的来电显示窗口

TYPE_SYSTEM_DIALOG= FIRST_SYSTEM_WINDOW+8

 

TYPE_KEYGUARD_DIALOG= FIRST_SYSTEM_WINDOW+9

屏幕保护下的对话框窗口

TYPE_SYSTEM_ERROR= FIRST_SYSTEM_WINDOW+10

系统错误窗口

TYPE_INPUT_METHOD= FIRST_SYSTEM_WINDOW+11

输入法窗口

系统窗口远不止上面的表格中所展现的,一共定义了33种。

系统窗口的创建一般不依赖于Activity窗口

所有系统窗口的值都在[2000,2999], WMS在进行窗口叠加时,会动态调整子窗口的值。有些系统窗口只能出现一个,不能添加多个,否则用户会觉得很乱(体验差)。比如输入法窗口,系统状态条窗口等。

2.2 窗口内存缓存类型

public int memoryType; // 保存窗口内存缓存类型

窗口内存缓存一共有4种,

public static final int MEMORY_TYPE_NORMAL = 0; //窗口缓冲位于主内存public static final int MEMORY_TYPE_HARDWARE=1;//窗口缓冲位于可以被DMA访问,或者硬件加速的内存区域public static final int MEMORY_TYPE_GPU = 2; //窗口缓冲位于可被图形加速器访问的区域public static final int MEMORY_TYPE_PUSH_BUFFERS = 3; //窗口缓冲不拥有自己的缓冲区,不能被锁定,缓冲区由本地方法提供

2.3 窗口行为类型

public int flags; //保存窗口的行为

窗口行为类型的行为一共32种,数值都是2的n次幂,所以各种flags可以混合使用,直接按位或是最好的方法。

比如最开始四种行为定义如下,

//Flag:当该window对用户可见的时候,允许锁屏        public static final int FLAG_ALLOW_LOCK_WHILE_SCREEN_ON     = 0x00000001;        //Flag:让该window后所有的东西都成暗淡        public static final int FLAG_DIM_BEHIND        = 0x00000002;        //Flag:让该window后所有东西都模糊(4.0以上已经放弃这种毛玻璃效果)        public static final int FLAG_BLUR_BEHIND        = 0x00000004;        //Flag:让window不能获得焦点,这样用户快就不能向该window发送按键事        public static final int FLAG_NOT_FOCUSABLE      = 0x00000008;

2.4 硬件加速类型

public int privateFlags;  // 保存硬件加速行为

一共有11种。定义如下,

public static final int PRIVATE_FLAG_FAKE_HARDWARE_ACCELERATED = 0x00000001;public static final int PRIVATE_FLAG_FORCE_HARDWARE_ACCELERATED = 0x00000002;public static final int PRIVATE_FLAG_WANTS_OFFSET_NOTIFICATIONS = 0x00000004;

2.5 窗口输入键盘类型

public int softInputMode;  

一共定义了13种键盘在窗口中的显示方法。

public static final int SOFT_INPUT_STATE_UNSPECIFIED = 0;public static final int SOFT_INPUT_STATE_UNCHANGED = 1;

当然, WindowManager的一些其他变量也控制着窗口的显示形式。

3 应用窗口

3.1 一般应用窗口

ActivityThread的主线程ApplicationThread的performLaunchActivity方法会调用Activity的onCreate方法,在onCreate中会调用setContentView等方法加载解析xml资源。从Toast的最简单调用开始,它的调用代码是:
void makeVisible() {        if (!mWindowAdded) {            ViewManager wm = getWindowManager(); // 获取的是WindowManagerImpl对象            wm.addView(mDecor, getWindow().getAttributes()); //  PhoneWindow            mWindowAdded = true;        }        mDecor.setVisibility(View.VISIBLE);    }

getAttributes最后获取的默认type类型是TYPE_APPLICATION。

3.2 Dialog

Dialog是一系列XXXDialog的基类,我们可以new任意Dialog或者通过Activity提供的 onCreateDialog(……)、onPrepareDialog(……)和showDialog(……)等方法来管理我们的Dialog,但是究 其实质都是来源于Dialog基类,所以我们对于各种XXXDialog来说只用分析Dialog的窗口加载就可以了。

Dialog构造方法如下,

Dialog(@NonNull Context context, @StyleRes int themeResId, boolean createContextThemeWrapper) {        if (createContextThemeWrapper) {            if (themeResId == 0) {                final TypedValue outValue = new TypedValue();                context.getTheme().resolveAttribute(R.attr.dialogTheme, outValue, true);                themeResId = outValue.resourceId;            }            mContext = new ContextThemeWrapper(context, themeResId);        } else {            mContext = context; //mContext已经从外部传入的context对象获得值(一般是个Activity)!!!非常重要,先记住!!!        }        mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);        final Window w = new PhoneWindow(mContext); // 创建新的Window        mWindow = w;        w.setCallback(this);        w.setOnWindowDismissedCallback(this);        w.setWindowManager(mWindowManager, null, null);        w.setGravity(Gravity.CENTER);        mListenersHandler = new ListenersHandler(this);    }

看看show方法,

public void show() {        if (mShowing) {            if (mDecor != null) {                if (mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {                    mWindow.invalidatePanelMenu(Window.FEATURE_ACTION_BAR);                }                mDecor.setVisibility(View.VISIBLE);            }            return;        }        mCanceled = false;                if (!mCreated) {            dispatchOnCreate(null);        }        onStart();        mDecor = mWindow.getDecorView();        if (mActionBar == null && mWindow.hasFeature(Window.FEATURE_ACTION_BAR)) {            final ApplicationInfo info = mContext.getApplicationInfo();            mWindow.setDefaultIcon(info.icon);            mWindow.setDefaultLogo(info.logo);            mActionBar = new WindowDecorActionBar(this);        }        WindowManager.LayoutParams l = mWindow.getAttributes();         //和上面分析的Activity一样type为TYPE_APPLICATION        if ((l.softInputMode                & WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION) == 0) {            WindowManager.LayoutParams nl = new WindowManager.LayoutParams();            nl.copyFrom(l);            nl.softInputMode |=                    WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION;            l = nl;        }        try {            mWindowManager.addView(mDecor, l);//把一个View添加到Activity共用的windowManager里面去            mShowing = true;                sendShowMessage();        } finally {        }    }

可以看见Dialog的新Window与Activity的Window的type同样都为TYPE_APPLICATION,上面介绍 WindowManager.LayoutParams时TYPE_APPLICATION的注释明确说过,普通应用程序窗口 TYPE_APPLICATION的token必须设置为Activity的token来指定窗口属于谁。所以可以看见,既然Dialog和 Activity共享同一个WindowManager(也就是上面分析的WindowManagerImpl),而WindowManagerImpl 里面有个Window类型的mParentWindow变量,这个变量在Activity的attach中创建WindowManagerImpl时传入 的为当前Activity的Window,而当前Activity的Window里面的mAppToken值又为当前Activity的token,所以 Activity与Dialog共享了同一个mAppToken值,只是Dialog和Activity的Window(PhoneWindow)对象不同。

为什么一定需要Activity的token 呢,禁止在当前activity中弹出另外一个activity的dialog,因为dialog有时候需要交互的,不能像toast一样(toast无交互).

所以传进去的context对象一定是Activity,如果是其他组件(Service)的context会报错。

4 子窗口

PopWindow是android系统中一个典型的子窗口,它的创建也依赖于Activity。

例如各种菜单(PopupMenu)都是利用PopWindow来实现的。

PopupWindow与Dialog一个不同点是PopupWindow是一个阻塞的对话框,如果你直接在Activity的onCreate等方法中显示它则会报错,所以PopupWindow最好另开启一个新线程去调用。

   PopupWindow一个构造函数如下,

public PopupWindow(View contentView, int width, int height, boolean focusable) {        if (contentView != null) {            mContext = contentView.getContext();//最终这个mContext实质是Activity           mWindowManager = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);        }   // WindowManager        setContentView(contentView); // 初始化赋值        setWidth(width);        setHeight(height);        setFocusable(focusable);    }

显示时,最后都会调用showAsDropDown方法,

public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) {        if (isShowing() || mContentView == null) {            return;        }        TransitionManager.endTransitions(mDecorView);        registerForScrollChanged(anchor, xoff, yoff, gravity);        mIsShowing = true;        mIsDropdown = true;       final WindowManager.LayoutParams p = createPopupLayoutParams(anchor.getWindowToken());           //         preparePopup(p);        final boolean aboveAnchor = findDropDownPosition(anchor, p, xoff, yoff, gravity);        updateAboveAnchor(aboveAnchor);        invokePopup(p);    }

createPopupLayoutParams方法会新建一个WindowManager.LayoutParams对象,然后变量type赋值为mWindowLayoutType,

private int mWindowLayoutType = WindowManager.LayoutParams.TYPE_APPLICATION_PANEL;
private void invokePopup(WindowManager.LayoutParams p) {        if (mContext != null) {            p.packageName = mContext.getPackageName();        }        final PopupDecorView decorView = mDecorView;        decorView.setFitsSystemWindows(mLayoutInsetDecor);        setLayoutDirectionFromAnchor();        mWindowManager.addView(decorView, p);        if (mEnterTransition != null) {            decorView.requestEnterTransition(mEnterTransition);        }    }

PopWindow中没有像Activity及Dialog一样new新的Window, 完全使用了Activity的Window与WindowManager。

5 系统窗口

系统窗口的种类很多,以Toast为例来论述,

首先看Toast的构造方法,

public Toast(Context context) {        mContext = context;        mTN = new TN();        mTN.mY = context.getResources().getDimensionPixelSize(                com.android.internal.R.dimen.toast_y_offset);        mTN.mGravity = context.getResources().getInteger(                com.android.internal.R.integer.config_toastDefaultGravity);    }

内部类TN构造方法如下, 

TN() {            // XXX This should be changed to use a Dialog, with a Theme.Toast            // defined that sets up the layout params appropriately.            final WindowManager.LayoutParams params = mParams;            params.height = WindowManager.LayoutParams.WRAP_CONTENT;            params.width = WindowManager.LayoutParams.WRAP_CONTENT;            params.format = PixelFormat.TRANSLUCENT;            params.windowAnimations = com.android.internal.R.style.Animation_Toast;            params.type = WindowManager.LayoutParams.TYPE_TOAST;            params.setTitle("Toast");            params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON                    | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE                    | WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;        }

主要是为WindowManager.LayoutParams中的变量赋值, type赋值为

params.type = WindowManager.LayoutParams.TYPE_TOAST;

显示时,调用TN的handleShow方法,如下,

public void handleShow() {            if (localLOGV) Log.v(TAG, "HANDLE SHOW: " + this + " mView=" + mView                    + " mNextView=" + mNextView);            if (mView != mNextView) {                // remove the old view if necessary                handleHide();                mView = mNextView;                Context context = mView.getContext().getApplicationContext();                String packageName = mView.getContext().getOpPackageName();                if (context == null) {                    context = mView.getContext();                }                mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);                // We can resolve the Gravity here by using the Locale for getting                // the layout direction                final Configuration config = mView.getContext().getResources().getConfiguration();                final int gravity = Gravity.getAbsoluteGravity(mGravity, config.getLayoutDirection());                mParams.gravity = gravity;                if ((gravity & Gravity.HORIZONTAL_GRAVITY_MASK) == Gravity.FILL_HORIZONTAL) {                    mParams.horizontalWeight = 1.0f;                }                if ((gravity & Gravity.VERTICAL_GRAVITY_MASK) == Gravity.FILL_VERTICAL) {                    mParams.verticalWeight = 1.0f;                }                mParams.x = mX;                mParams.y = mY;                mParams.verticalMargin = mVerticalMargin;                mParams.horizontalMargin = mHorizontalMargin;                mParams.packageName = packageName;                if (mView.getParent() != null) {                    if (localLOGV) Log.v(TAG, "REMOVE! " + mView + " in " + this);                    mWM.removeView(mView);                }                if (localLOGV) Log.v(TAG, "ADD! " + mView + " in " + this);                mWM.addView(mView, mParams);                trySendAccessibilityEvent();            }        }

和前2种窗口内型不同的是,系统窗口不依赖于Activity而存在,并且由系统服务统一管理,只是一个通知提示作用,并不会和Activity进行交互。

在使用Toast时context参数尽量使用getApplicationContext(),可以有效的防止静态引用导致的内存泄漏。

6 addView方法

mWM = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);

通过这种方法获取的服务都是WindowManagerImpl对象,其addView方法如下,

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {        android.util.SeempLog.record_vg_layout(383,params);        applyDefaultToken(params);        mGlobal.addView(view, params, mDisplay, mParentWindow);    }

直接调用WindowManagerGlobal的

public void addView(View view, ViewGroup.LayoutParams params,            Display display, Window parentWindow) {        if (view == null) {            throw new IllegalArgumentException("view must not be null");        }        if (display == null) {            throw new IllegalArgumentException("display must not be null");        }        if (!(params instanceof WindowManager.LayoutParams)) {            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");        }        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;        if (parentWindow != null) {            parentWindow.adjustLayoutParamsForSubWindow(wparams);        } else {            // If there's no parent, then hardware acceleration for this view is            // set from the application's hardware acceleration setting.            final Context context = view.getContext();            if (context != null                    && (context.getApplicationInfo().flags                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;            }        }        ViewRootImpl root;        View panelParentView = null;        synchronized (mLock) {            // Start watching for system property changes.            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();                            }                        }                    }                };                SystemProperties.addChangeCallback(mSystemPropertyUpdater);            }            int index = findViewLocked(view, false);            if (index >= 0) {                if (mDyingViews.contains(view)) {                    // Don't wait for MSG_DIE to make it's way through root's queue.                    mRoots.get(index).doDie();                } else {                    throw new IllegalStateException("View " + view                            + " has already been added to the window manager.");                }                // The previous removeView() had not completed executing. Now it has.            }            // If this is a panel window, then find the window it is being            // attached to for future reference.            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);                    }                }            }            root = new ViewRootImpl(view.getContext(), display); // 初始化            view.setLayoutParams(wparams);            mViews.add(view);            mRoots.add(root);            mParams.add(wparams);        }        // do this last because it fires off messages to start doing things        try {            root.setView(view, wparams, panelParentView);        } catch (RuntimeException e) {            // BadTokenException or InvalidDisplayException, clean up.            synchronized (mLock) {                final int index = findViewLocked(view, false);                if (index >= 0) {                    removeViewLocked(index, true);                }            }            throw e;        }    }

最后的setView方法会调用performTraversals方法完成View的测量,确定位置以及显示,这样一个窗口就显示出来了。

0 0