安卓学习笔记之理解Window与WindowManager

原创 2016年08月28日 21:10:17

Window的概念

Window表示的是一个窗口的概念,它是一个抽象类,它的具体实现是PhoneWindow。创建一个Window需要通过WindowManager来完成。WindowManager是外界访问Window的入口,Window的具体实现位于WindowManagerService,WindowManager与WindowManagerService的交互是一个IPC过程。Android中所有的View都是Window来呈现的,不管是Activity、Toast还是Dialog,它们的视图都是附加到Window上的,因此Window是View的直接管理者
View的事件分发机制中的事件传递:单击事件由Activity内部的Window -> Decor View -> View

WindowManager

1. 添加view到Window示例

使用WindowManager添加一个view到Window
自定义浮窗 需要权限android.permission.SYSTEM_ALERT_WINDOW

    /**
     * 显示浮窗
     * @param content   要填充的文本内容
     * @param layoutId   用于创建窗体View的布局
     */
    public  void show(String content,int layoutId) {
          wm = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE);
        params = new WindowManager.LayoutParams();
        screenHeight = AppInfoUtils.getScreenSize(context).height;
        screenWidth = AppInfoUtils.getScreenSize(context).width;
        // 加载布局
        view = View.inflate(mContext, layoutId, null);
        // 设置浮窗params属性
        params.width = WindowManager.LayoutParams.WRAP_CONTENT;
        params.height = WindowManager.LayoutParams.WRAP_CONTENT;
        params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
        params.type = WindowManager.LayoutParams.TYPE_PHONE;
        params.gravity = Gravity.TOP+Gravity.LEFT; // 将重心设置为左上方
        params.format = PixelFormat.TRANSLUCENT;    // 半透明
        params.x = sp.getInt("startX", 0);      // 设置显示位置
        params.y = sp.getInt("startY", 0);
        TextView tvLocation =  (TextView) view.findViewById(R.id.tv_toast_location);
        tvLocation.setText(content);
        // 将View添加到窗体管理器
        wm.addView(view, params);
    }

2. 参数解析

1、LayoutParams.Flags参数表示Window的属性,通过设置它的选项可以控制Window的显示特性。如下几种常见选项:

FLAG_NOT_FOCUSABLE

不许获得焦点

FLAG_NOT_TOUCHABLE

不接受触摸屏事件

FLAG_NOT_TOUCH_MODAL

当窗口可以获得焦点(没有设置 FLAG_NOT_FOCUSALBE 选项)时,仍然将窗口范围之外的点设备事件(鼠标、触摸屏)发送给后面的窗口处理。否则它将独占所有的点设备事件,而不管它们是不是发生在窗口范围内。

FLAG_SHOW_WHEN_LOCKED

当屏幕锁定时,窗口可以被看到。这使得应用程序窗口优先于锁屏界面。可配合FLAG_KEEP_SCREEN_ON选项点亮屏幕并直接显示在锁屏界面之前。可使用FLAG_DISMISS_KEYGUARD选项直接解除非加锁的锁屏状态。此选项只用于最顶层的全屏幕窗口。

FLAG_DIM_BEHIND

  窗口之后的内容变暗

FLAG_BLUR_BEHIND

窗口之后的内容变模糊。

2、Type参数表示Window的类型,有3种主要类型:
1)Application_windows (应用Window):

    值在 FIRST_APPLICATION_WINDOW 和 LAST_APPLICATION_WINDOW 之间。
    是通常的、顶层的应用程序窗口。必须将 token 设置成 activity 的 token 。  

2)Sub_windows (子Window):

    取值在 FIRST_SUB_WINDOW 和 LAST_SUB_WINDOW 之间。与顶层窗口相关联,token 必须设置为它所附着的宿主窗口的 token。

3)System_windows (系统Window):

    取值在 FIRST_SYSTEM_WINDOW 和 LAST_SYSTEM_WINDOW 之间。

3、Window的层次

每个Window都有对应的z-ordered,层次大的会覆盖到层次小的Window上面。在三类Window中应用Window的层级范围在1~99之间,子Window的范围在1000~1999之间,系统Window的层级范围在2000~2999之间。

要使Window位于所有Window的最顶层,采用较大的层级即可,系统Window的层级是最大的,一般选用TYPE_SYSTEM_OVERLAY或TYPE_SYSTEM_ERROR,同时要声明权限android.permission.SYSTEM_ALERT_WINDOW。如下示例

    params.type = WindowManager.LayoutParams.TYPE_SYSTEM_ERROR;

4、WindowManager提供的常用方法
WindowManager继承自ViewManager,提供了添加view、删除view和更新view,这三个方法都是定义在ViewManager。

public interface ViewManager
{
    /**
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

Window的内部机制

Window是一个抽象的概念,每一个Window都对应着一个view和ViewRootImpl,Window与View通过ViewRootImpl建立起联系,Window是以View作为实体存在,实际使用WindowManager访问来Window,外部无法直接访问Window。WindowManager提供了三个针对View的接口方法addView、updateViewLayout和removeView,分析Window的内部机制从Window的添加、更新和删除开始。

Window的添加过程

Window的添加依赖于WindowManager,而WindowManager是一个接口,它的具体实现类是WindowManagerImpl,在WindowManagerImpl中实现了如下几个操作view的方法
@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);
    }

由上可知,WindowManagerImpl将操作view的实现都委托给了WindowManagerGlobal(即mGlobal),下面来看一下WindowManagerGlobal的addView方法,完整代码如下

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
// 1、---
        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;
// 2、---
        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);
            }
// 3、---
            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.
            }
// 4、---
            // 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);
                    }
                }
            }

// 5、---
            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }
// 6、---
        // 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;
        }
    }

上面addView方法大概做了如下几件事:
1、检查参数是否合法,并判断当前添加的是否为子Window(parentWindow是否为空),若为子Window则为其做相关调整,否则为其开启硬件加速
2、监视系统属性的变化
3、通过findViewLocked获取mViews中view的索引,看添加的view是否在mViews的集合里,如果获取的index>=0,此view存在,接着判断要删除的集合是否包含此view,若包含则直接执行doDie()删除当前view,若不包含则会抛出异常(此view正在被删除,还没有完成)
4、判断添加的是否为panel window,若是则找出以备后查
5、将Window的一系列参数添加到集合中,几种集合如下:

    mViews:存储了所有Window所对应的View
    mRoots:存储了所有Window所对应的ViewRootImpl
    mParams:存储了所有Window所对应的布局参数
    mDyingViews:存储的是即将被删除的View对象或正在被删除的View对象

6、通过ViewRootImpl的setView方法来完成界面的更新,并完成Window的添加。

在setView内部会通过requestLayout方法来完成异步刷新请求,scheduleTraversals实际是View的绘制入口。

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

然后会接着执行如下代码,WindowSession最终完成Window的添加,mWindowSession的类型是IWindowSession,它是一个Binder对象,真正的实现类是Session,因此Window的添加的过程是一个IPC调用

try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

在Session内部会调用WindowManagerService的addWindow方法进行Window方法添加,具体的过程在WindowManagerService中实现了。WindowManagerService会为每个应用保留一个单独的Session。

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
            InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
                outContentInsets, outStableInsets, outInputChannel);
    }

到此Window的添加就完成了。大致走了如下流程

WindowManager -> WindowManagerImpl -> WindowManagerGlobal>addView -> ViewRootImpl>setView>requestLayout -> (IPC)Session>addToDisplay -> WindowManagerService>addWindow

Window的删除过程

删除的过程与添加类似,通过WindowManagerGlobal来实现删除,下面看它的removeView方法

public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

上述方法在要移除的view不为空的情况下,通过findViewLocked查找view在mViews(上述)中的索引,然后通过removeViewLocked进行删除。看一下这两个方法:

private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }
private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

从removeViewLocked方法可以看出,删除操作是由ViewRootImpl来完成的,删除分为两种,分别为同步删除(removeViewImmediate)和异步删除(removeView),在ViewRootImpl的die(immediate)方法中进行判断。如果为同步则直接调用doDie方法进行删除,否则会发送一个消息进行异步处理,同时执行mDyingViews.add(view)

**
     * @param immediate True, do now if not in traversal. False, put on queue and do later.
     * @return True, request has been queued. False, request has been completed.
     */
    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(TAG, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

在doDie方法内部调用dispatchDetachedFromWindow()方法删除Window,最后调用WindowManagerGlobal的doRemoveView方法进行数据刷新,包括mRoots,mViews,mParams和mDyingViews,需要将当前Window所关联的这三类对象从集合中删除

void doDie() {
        checkThread();
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            ... 
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

在dispatchDetachedFromWindow方法中真正执行删除操作,内部作了如下几件事:

1、垃圾回收的相关工作,如清理数据和消息、移除回调和监听。
2、调用Wiew的dispatchDetachedFromWindow方法,它的方法内部会调用onDetachedFromWindow()方法,当view从Window被移除,此方法就会被调用,可以在此方法中做一些资源回收工作,诸如终止动画、线程
3、通过Session的remove方法移除Window:mWindowSession.remove(mWindow),此过程是一个IPC过程,最终会调用WindowManagerService的removeWindow方法。

到此,Window的删除过程就已经完成了,大致流程

WindowManager -> (实现类)WindowManagerImpl ->(委托类) WindowManagerGlobal>removeView>removeViewLocked  ->  ViewRootImpl>doDie>dispatchDetachedFromWindow  ->  (IPC) Session>remove  ->  WindowManagerService>removeWindow

Window的更新过程

同创建、删除Window类似,更新Window的实施者依然是WindowManagerGlobal,下面看它的updateViewLayout方法

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        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);
        }
    }

更新过程首先要替换旧的params,接着通过ViewRootImpl的setLayoutParams方法进行更新ViewRootImpl中的params,在setLayoutParams方法内通scheduleTraversals进行Vew的重新布局(测量、布局、绘制),并会通过如下流程来更新Window的视图

scheduleTraversals-> doTraversal -> performTraversals-> relayoutWindow-> mWindowSession.relayout -> mService.relayoutWindow

到此Window的更新就完成了,大致流程如下:

WindowManager -> WindowManagerImpl  ->  WindowManagerGlobal>updateViewLayout -> ViewRootImpl>setLayoutParams>scheduleTraversals>doTraversal>performTraversals>relayoutWindow -> (IPC)Session>relayout  -> WindowManagerService>relayoutWindow

后记:此篇参考了安卓开发艺术探索,融入个人总结所成,如有错误请不吝赐教。特此说明。更多细节可查阅android源码。
版权声明:本文为博主原创文章,未经博主允许不得转载。 举报

相关文章推荐

WindowManager 基本应用

关键字 : WindowManager,浮动窗体,工具面板,SystemUI状态栏 在群里面久了,经常会听到很多人说了做一个什么窗体,什么弹出框,或者上拉,下拉的框体.下面大致介绍一下.这里面先大致...

Android中获取应用程序(包)的信息-----PackageManager的使用(一)

转载请注明出处:http://blog.csdn.net/qinjuning                 本节内容是如何获取Android系统...

我是如何成为一名python大咖的?

人生苦短,都说必须python,那么我分享下我是如何从小白成为Python资深开发者的吧。2014年我大学刚毕业..

Android 带你彻底理解 Window 和 WindowManager

任何 View 都是附属在一个 Window 上面的,Window 表示一个窗口的概念,也是一个抽象的概念,Window 并不是实际存在的,它是以 View 的形式存在的。WindowManager ...

Android应用程序启动过程源代码分析

前文简要介绍了Android应用程序的Activity的启动过程。在Android系统中,应用程序是由Activity组成的,因此,应用程序的启动过程实际上就是应用程序中的默认Activity的启动过...

如何让ListView的item不可点击

1. 如果是listView的id是使用系统默认的id,如下, 则可以在实现此listView的adapter里override isEnabled()方法: <ListView android...

Android窗口管理服务WindowManagerService的简要介绍和学习计划

在前一个系列文章中,我们从个体的角度来分析了Android应用程序窗口的实现框架。事实上,如果我们从整体的角度来看,Android应用程序窗口的实现要更复杂,因为它们的类型和作用不同,且会相互影响。在...

Android LayoutInflater原理分析,带你一步步深入了解View(一)

有不少朋友跟我反应,都希望我可以写一篇关于View的文章,讲一讲View的工作原理以及自定义View的方法。没错,承诺过的文章我是一定要兑现的,而且在View这个话题上我还准备多写几篇,尽量能将这个知...

Android系统服务-WindowManager

WindowManager是Android中一个重要的服务(Service )。WindowManager Service 是全局的,是唯一的。它将用户的操作,翻译成为指令,发送给呈现在界面上的各个W...

笔记89--WindowManager详解一

一、WindowManager家族关系 public interface WindowManager implements ViewManager。 二、自我介绍 应用程序与窗口管理器的接口。 三、获...
  • mwj_88
  • mwj_88
  • 2014-12-18 16:42
  • 2455

Android:最全面的 Webview 详解

前言 现在很多App里都内置了Web网页(Hyprid App),比如说很多电商平台,淘宝、京东、聚划算等等,如下图 那么这种该如何实现呢?其实这是Android里一个叫WebView的组件实现的。今...
返回顶部
收藏助手
不良信息举报
您举报文章:深度学习:神经网络中的前向传播和反向传播算法推导
举报原因:
原因补充:

(最多只允许输入30个字)