android窗口_android悬浮窗口 - CSDN
精华内容
参与话题
  • Android窗口机制

    2020-03-01 16:17:07
    参考: ...Android窗口机制(一)——Window,PhoneWindow,DecorView理解 Window窗口机制(二)——Window,WindowManager理解 Window窗口机制(三)——WindowManager,ViewRootImpl,View理解 ...

    DecorView通过PhoneWindow与WindowManagerService通信原理图

    在这里插入图片描述

    每个Activity都有一个Window对象,这个对象是PhoneWindow类型的。
    每个Window对象里面都维护着一个WindowManager对象。

    Activity里面添加一个View是通过WindowManager的addView()方法实现的

    相关关键类

    WindowManagerService
    高层级,系统窗口管理服务

    Window
    一个顶级窗口的外观和行为策略的一个抽象基类。

    Window并不是真实存在的,它表示一种抽象的功能集合,View才是Android中的视图呈现形式,绘制到屏幕上的是View不是Window,但是View不能单独存在,它必需依附在Window这个抽象的概念上面,Android中需要依赖Window提供视图的有Activity,Dialog,Toast,PopupWindow,StatusBarWindow(系统状态栏),输入法窗口等,因此Activity,Dialog等都维护着一个Window对象。当我们调用Activity的setContentView()时,其实最终会调用Window的setContentView(),当我们调用Activity的findViewById()时,其实最终调用的是Window的findViewById()。
    对 Window 的操作是通过 WindowManager 来完成的。

    WindowManager
    WindowManager是一个接口,里面常用的方法有:添加View,更新View和删除View,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);
    }
    
    

    WindowManagerImpl
    低层次,负责与系统窗口管理服务进行操作通信,与Context进行关联。
    是WindowManager的实现类,大部分工作都通过WindowManagerGlobal来实现。
    WindowManagerGlobal
    低层次,负责与系统窗口管理服务进行操作通信,不与Context进行关联。

    ViewRootImpl
    ViewRootImpl是一个视图层次结构的顶部,它实现了View与WindowManager之间所需要的协议,作为WindowManagerGlobal中大部分的内部实现,也就说WindowManagerGlobal方法最后还是大部分调用了ViewRootImpl。
    类似 ApplicationThread 负责跟AMS通信一样,ViewRootImpl 的一个重要职责就是跟 WMS 通信,它通过静态变量 sWindowSession(IWindowSession实例,在WindowManagerGlobal中创建)与 WMS 进行通信。

    源码分析

    ActivityThread.handleResumeActivity()方法 :

      @Override
        public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward, String reason) {
        ...//其他代码
              if (a.mVisibleFromClient) {
                    if (!a.mWindowAdded) {
                        a.mWindowAdded = true;
                        wm.addView(decor, l);
                    } else {
                        // The activity will get a callback for this {@link LayoutParams} change
                        // earlier. However, at that time the decor will not be set (this is set
                        // in this method), so no action will be taken. This call ensures the
                        // callback occurs with the decor set.
                        a.onWindowAttributesChanged(l);
                    }
                }
        ...//其他代码
     }
    

    调用WindowManagerImpl.addView() :

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

    调用WindowManagerGlobal.addView():

        public void addView(View view, ViewGroup.LayoutParams params,
                Display display, Window parentWindow) {
            ...//其他代码
                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.
                    if (index >= 0) {
                        removeViewLocked(index, true);
                    }
                    throw e;
                }
            }
        }
    

    调用ViewRootImpl.java的setView方法

        /**
         * We have one child
         */
        public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
            synchronized (this) {
            
             		...//其他代码
             		
             		
                    // Schedule the first layout -before- adding to the window
                    // manager, to make sure we do the relayout before receiving
                    // any other events from the system.
                    requestLayout(); //请求布局
                    if ((mWindowAttributes.inputFeatures
                            & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                        mInputChannel = new InputChannel();
                    }
                    mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                            & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                    try {
                        mOrigWindowType = mWindowAttributes.type;
                        mAttachInfo.mRecomputeGlobalAttributes = true;
                        collectViewAttributes();
                        res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                                getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                                mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                                mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                    } 
    
    			    ...//其他代码
    
    }
    

    res = mWindowSession.addToDisplay() 调用了Session的addToDisplay()方法:

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

    调用mService.addWindow()方法添加Window,这个mService其实就是WindowManagerService对象,也就是说布局最终是由 WindowManagerService 来添加的。
    分析到这里可以看到,ViewRootImpl是通过 mWindowSession(来自WindowManagerGlobal的sWindowSession对象)和WindowManagerService通信的。

    参考:
    ViewManager、ViewRootImp、WindowManagerImpl、WindowSession等详细解析
    Android窗口机制(一)——Window,PhoneWindow,DecorView理解
    Window窗口机制(二)——Window,WindowManager理解
    Window窗口机制(三)——WindowManager,ViewRootImpl,View理解
    Android 窗口机制
    Android窗口机制(四)ViewRootImpl与View和WindowManager
    android WindowManagerService
    Window, WindowManager和WindowManagerService之间的关系

    android window(三)ViewRootImpl
    ViewRootImpl的独白,我不是一个View(布局篇)

    ViewRootImpl源码分析事件分发

    面试进阶之窗口机制

    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/Window.java
    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/WindowManagerImpl.java
    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/WindowManagerGlobal.java
    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/core/java/android/view/ViewRootImpl.java
    http://androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/services/core/java/com/android/server/wm/Session.java
    https://www.androidos.net.cn/android/9.0.0_r8/xref/frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

    展开全文
  • android 窗口类型分析

    千次阅读 2017-01-15 20:31:11
    Android窗口类型主要分成了三大类: 1,应用程序窗口。一般应用程序的窗口,比如我们应用程序的Activity的窗口。 2,子窗口。一般在Activity里面的窗口,比如各种菜单等。 3,系统窗口。系统的窗口,比如输入法,...

    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的测量,确定位置以及显示,这样一个窗口就显示出来了。

    展开全文
  • android悬浮窗口的实现

    万次阅读 2017-06-03 10:07:13
    当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小...

    转自:http://blog.csdn.net/stevenhu_223/article/details/8504058

    当我们在手机上使用360安全卫士时,手机屏幕上时刻都会出现一个小浮动窗口,点击该浮动窗口可跳转到安全卫士的操作界面,而且该浮动窗口不受其他activity的覆盖影响仍然可见(多米音乐也有相关的和主界面交互的悬浮小窗口)。那么这种不受Activity界面影响的悬浮窗口是怎么实现的呢?

        竟然它能悬浮在手机桌面,且不受Activity界面的影响,说明该悬浮窗口是不隶属于Activity界面的,也就是说,他是隶属于启动它的应用程序所在进程。如360App所在的应用进程,当杀掉它所在的应用进程时,它才会消失。

         悬浮窗口的实现涉及到WindowManager(基于4.0源码分析),它是一个接口,实现类有WindowManagerImplCompatModeWrapper(WindowManagerImpl的内部类),LocalWindowManagerWindow的内部类),它们之间的关系如下图的类图:

        

     

    WindowManagerImpl:

          1.是WindowManager的实现类,windowmanager的大部分操作都在这里实现,但是并不会直接调用,而是作为LocalWindowManager和WindowManagerImpl.CompatModeWrapper的成员变量来使用。

           2.在WindowManagerImpl中有3个数组View[],ViewRoot[],WindowManager.LayoutParams[],分别用来保存每个图层的数据。

           3.WindowManagerImpl最重要的作用就是用来管理View,LayoutParams, 以及ViewRoot这三者的对应关系。

    LocalWindowManager:

         在源码的Activity类中,有一个重要的成员变量mWindow(它的实现类为PhoneWindow),同时也有一个成员变量mWindowManager(跟踪源码可知它是一个LocalWindowManager),而在PhoneWindow中同时也有和Activity相同名字的mWindowManager成员变量而且Activity中的mWindowManager是通过Window类中的setWindowManager函数初始化获取的。

        所以,在Activity中的LocalWindowManager的生命周期是小于Activity的生命周期的而且在ActivityThread每创建一个Activity时都有该Activity对应的一个属于它的LocalWindowManager

        对LocalWindowManager的小结:

          1.该类是Window的内部类,父类为CompatModeWrapper,同样都是实现WindowManager接口。

           2.每个Activity中都有一个mWindowManager成员变量,Window类中 也有相应的同名字的该成员变量。该变量是通过调用Window的setWindowManager方法初始化得到的,实际上是一个LocalWindowManger对象。

           3.也就说,每生成的一个Activity里都会构造一个其相应LocalWindowManger来管理该Activity承载的图层。(该对象可以通过Activity.getWindowManager或getWindow().getWindowManager获取)

             4.LocalWindowMangers 的生命周期小于Activity的生命周期,(因为mWindowManager是Window的成员变量,而mWindow又是Activity的成员变量),所以,如果我们在一个LocalwindowManager中手动添加了其他的图层, 在Activity的finish执行之前, 应该先调用LocalwindowManager的removeView, 否则会抛出异常。

    CompatModeWrapper:

        该类就是实现悬浮窗口的重要类了。

        跟踪源码可知:

          1.CompatModeWrapper相当于是一个壳,而真正实现大部分功能的是它里面的成员变量mWindowManager(WindowManagerImpl类)。

          2.该对象可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到。(注:如果是通过activity.getSystemService(Context.WINDOW_SERVICE)得到的只是属于Activity的LocalWindowManager)。

          3.这个对象的创建是在每个进程开始的时候, 通过ContextImpl中的静态代码块创建的, 它使用了单例模式, 保证每个application只有一个。

          4.通过该类可以实现创建添加悬浮窗口,也就是说,在退出当前Activity时,通过该类创建的视图还是可见的,它是属于整个应用进程的视图,存活在进程中,不受Activity的生命周期影响。

     

    ok,在通过上面对WindowManager接口的实现类做一些简要的介绍后,接下来就动手编写实现悬浮窗口的App。既然我们知道可以通过getApplication().getSystemService(Context.WINDOW_SERVICE)得到CompatModeWrapper,然后实现应用添加悬浮窗口视图。那么,具体的实现操作可以在Activity或者Service中(这两者都是可以创建存活在应用进程中的Android重要组件)实现。

     

    下面的App程序代码实现通过主Activity的启动按钮,启动一个Service,然后在Service中创建添加悬浮窗口:

           要获取CompatModeWrapper,首先得在应用程序的AndroidManifest.xml文件中添加权限<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

          MainActivity的代码如下:

    [java] view plain copy
    1. public class MainActivity extends Activity   
    2. {  
    3.   
    4.     @Override  
    5.     public void onCreate(Bundle savedInstanceState)  
    6.     {  
    7.         super.onCreate(savedInstanceState);  
    8.         setContentView(R.layout.main);  
    9.         //获取启动按钮  
    10.         Button start = (Button)findViewById(R.id.start_id);  
    11.         //获取移除按钮  
    12.         Button remove = (Button)findViewById(R.id.remove_id);  
    13.         //绑定监听  
    14.         start.setOnClickListener(new OnClickListener()   
    15.         {  
    16.               
    17.             @Override  
    18.             public void onClick(View v)   
    19.             {  
    20.                 // TODO Auto-generated method stub  
    21.                 Intent intent = new Intent(MainActivity.this, FxService.class);  
    22.                 //启动FxService  
    23.                 startService(intent);  
    24.                 finish();  
    25.             }  
    26.         });  
    27.           
    28.         remove.setOnClickListener(new OnClickListener()   
    29.         {  
    30.               
    31.             @Override  
    32.             public void onClick(View v)   
    33.             {  
    34.                 //uninstallApp("com.phicomm.hu");  
    35.                 Intent intent = new Intent(MainActivity.this, FxService.class);  
    36.                 //终止FxService  
    37.                 stopService(intent);  
    38.             }  
    39.         });  
    40.           
    41.     }  
    42. }  

         FxService的代码如下:

    [java] view plain copy
    1. package com.phicomm.hu;  
    2.   
    3. import android.app.Service;  
    4. import android.content.Intent;  
    5. import android.graphics.PixelFormat;  
    6. import android.os.Handler;  
    7. import android.os.IBinder;  
    8. import android.util.Log;  
    9. import android.view.Gravity;  
    10. import android.view.LayoutInflater;  
    11. import android.view.MotionEvent;  
    12. import android.view.View;  
    13. import android.view.WindowManager;  
    14. import android.view.View.OnClickListener;  
    15. import android.view.View.OnTouchListener;  
    16. import android.view.WindowManager.LayoutParams;  
    17. import android.widget.Button;  
    18. import android.widget.LinearLayout;  
    19. import android.widget.Toast;  
    20.   
    21. public class FxService extends Service   
    22. {  
    23.   
    24.     //定义浮动窗口布局  
    25.     LinearLayout mFloatLayout;  
    26.     WindowManager.LayoutParams wmParams;  
    27.     //创建浮动窗口设置布局参数的对象  
    28.     WindowManager mWindowManager;  
    29.       
    30.     Button mFloatView;  
    31.       
    32.     private static final String TAG = "FxService";  
    33.       
    34.     @Override  
    35.     public void onCreate()   
    36.     {  
    37.         // TODO Auto-generated method stub  
    38.         super.onCreate();  
    39.         Log.i(TAG, "oncreat");  
    40.         createFloatView();        
    41.     }  
    42.   
    43.     @Override  
    44.     public IBinder onBind(Intent intent)  
    45.     {  
    46.         // TODO Auto-generated method stub  
    47.         return null;  
    48.     }  
    49.   
    50.     private void createFloatView()  
    51.     {  
    52.         wmParams = new WindowManager.LayoutParams();  
    53.         //获取的是WindowManagerImpl.CompatModeWrapper  
    54.         mWindowManager = (WindowManager)getApplication().getSystemService(getApplication().WINDOW_SERVICE);  
    55.         Log.i(TAG, "mWindowManager--->" + mWindowManager);  
    56.         //设置window type  
    57.         wmParams.type = LayoutParams.TYPE_PHONE;   
    58.         //设置图片格式,效果为背景透明  
    59.         wmParams.format = PixelFormat.RGBA_8888;   
    60.         //设置浮动窗口不可聚焦(实现操作除浮动窗口外的其他可见窗口的操作)  
    61.         wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;        
    62.         //调整悬浮窗显示的停靠位置为左侧置顶  
    63.         wmParams.gravity = Gravity.LEFT | Gravity.TOP;         
    64.         // 以屏幕左上角为原点,设置x、y初始值,相对于gravity  
    65.         wmParams.x = 0;  
    66.         wmParams.y = 0;  
    67.   
    68.         //设置悬浮窗口长宽数据    
    69.         wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  
    70.         wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  
    71.   
    72.          /*// 设置悬浮窗口长宽数据 
    73.         wmParams.width = 200; 
    74.         wmParams.height = 80;*/  
    75.      
    76.         LayoutInflater inflater = LayoutInflater.from(getApplication());  
    77.         //获取浮动窗口视图所在布局  
    78.         mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);  
    79.         //添加mFloatLayout  
    80.         mWindowManager.addView(mFloatLayout, wmParams);  
    81.         //浮动窗口按钮  
    82.         mFloatView = (Button)mFloatLayout.findViewById(R.id.float_id);  
    83.           
    84.         mFloatLayout.measure(View.MeasureSpec.makeMeasureSpec(0,  
    85.                 View.MeasureSpec.UNSPECIFIED), View.MeasureSpec  
    86.                 .makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));  
    87.         Log.i(TAG, "Width/2--->" + mFloatView.getMeasuredWidth()/2);  
    88.         Log.i(TAG, "Height/2--->" + mFloatView.getMeasuredHeight()/2);  
    89.         //设置监听浮动窗口的触摸移动  
    90.         mFloatView.setOnTouchListener(new OnTouchListener()   
    91.         {  
    92.               
    93.             @Override  
    94.             public boolean onTouch(View v, MotionEvent event)   
    95.             {  
    96.                 // TODO Auto-generated method stub  
    97.                 //getRawX是触摸位置相对于屏幕的坐标,getX是相对于按钮的坐标  
    98.                 wmParams.x = (int) event.getRawX() - mFloatView.getMeasuredWidth()/2;  
    99.                 Log.i(TAG, "RawX" + event.getRawX());  
    100.                 Log.i(TAG, "X" + event.getX());  
    101.                 //减25为状态栏的高度  
    102.                 wmParams.y = (int) event.getRawY() - mFloatView.getMeasuredHeight()/2 - 25;  
    103.                 Log.i(TAG, "RawY" + event.getRawY());  
    104.                 Log.i(TAG, "Y" + event.getY());  
    105.                  //刷新  
    106.                 mWindowManager.updateViewLayout(mFloatLayout, wmParams);  
    107.                 return false;  //此处必须返回false,否则OnClickListener获取不到监听  
    108.             }  
    109.         });   
    110.           
    111.         mFloatView.setOnClickListener(new OnClickListener()   
    112.         {  
    113.               
    114.             @Override  
    115.             public void onClick(View v)   
    116.             {  
    117.                 // TODO Auto-generated method stub  
    118.                 Toast.makeText(FxService.this"onClick", Toast.LENGTH_SHORT).show();  
    119.             }  
    120.         });  
    121.     }  
    122.       
    123.     @Override  
    124.     public void onDestroy()   
    125.     {  
    126.         // TODO Auto-generated method stub  
    127.         super.onDestroy();  
    128.         if(mFloatLayout != null)  
    129.         {  
    130.             //移除悬浮窗口  
    131.             mWindowManager.removeView(mFloatLayout);  
    132.         }  
    133.     }  
    134.       
    135. }  

          悬浮窗口的布局文件为R.layout.float_layout,所以,如果我们想设计一个非常美观的悬浮窗口,可以在该布局文件里编写。当然,也可以使用自定义View来设计(哈哈,少年们,在此基础上发挥想象吧)。

         上面代码的效果图如下:左边为启动界面。点击“启动悬浮窗口”按钮,会启动后台service创建悬浮窗口,同时finish当前Activity,这样一个悬浮窗口就创建出来了,该窗口可实现任意位置移动,且可点击监听创建Toast提示(当然,也可以启动一个Activity)。若要移除已创建的窗口,可点击“移除悬浮窗口按钮”,或者强制禁止该应用进程。

          

     

    同样的,在一个Activity里绘制悬浮视图,不过下面的代码主要还是验证区分LocalWindowManger和CompatModeWrapper添加的视图。

          LocalWindowManger可通过activity.getSystemService(Context.WINDOW_SERVICE)或getWindow().getWindowManager获取。当我们通过LocalWindowManger添加视图时,退出Activity,添加的视图也会随之消失。

            验证代码如下:

    [java] view plain copy
    1. package com.phicomm.hu;  
    2.   
    3. import android.app.Activity;  
    4. import android.content.Context;  
    5. import android.content.Intent;  
    6. import android.graphics.PixelFormat;  
    7. import android.os.Bundle;  
    8. import android.util.Log;  
    9. import android.view.Gravity;  
    10. import android.view.LayoutInflater;  
    11. import android.view.MotionEvent;  
    12. import android.view.View;  
    13. import android.view.WindowManager;  
    14. import android.view.View.OnClickListener;  
    15. import android.view.View.OnTouchListener;  
    16. import android.view.WindowManager.LayoutParams;  
    17. import android.widget.Button;  
    18. import android.widget.LinearLayout;  
    19.   
    20. public class FloatWindowTest extends Activity   
    21. {  
    22.     /** Called when the activity is first created. */  
    23.       
    24.     private static final String TAG = "FloatWindowTest";  
    25.     WindowManager mWindowManager;  
    26.     WindowManager.LayoutParams wmParams;  
    27.     LinearLayout mFloatLayout;  
    28.     Button mFloatView;  
    29.     @Override  
    30.     public void onCreate(Bundle savedInstanceState)   
    31.     {  
    32.         super.onCreate(savedInstanceState);  
    33.         //createFloatView();  
    34.         setContentView(R.layout.main);  
    35.           
    36.         Button start = (Button)findViewById(R.id.start);  
    37.         Button stop = (Button)findViewById(R.id.stop);  
    38.           
    39.         start.setOnClickListener(new OnClickListener()   
    40.         {  
    41.               
    42.             @Override  
    43.             public void onClick(View v)  
    44.             {  
    45.                 // TODO Auto-generated method stub  
    46.                 createFloatView();  
    47.                 //finish();  
    48.                 //handle.post(r);  
    49.             }  
    50.         });  
    51.           
    52.         stop.setOnClickListener(new OnClickListener()  
    53.         {  
    54.               
    55.             @Override  
    56.             public void onClick(View v)   
    57.             {  
    58.                 // TODO Auto-generated method stub  
    59.                 if(mFloatLayout != null)  
    60.                 {  
    61.                     mWindowManager.removeView(mFloatLayout);  
    62.                     finish();  
    63.                 }     
    64.         }  
    65.         });  
    66.           
    67.           
    68.     }  
    69.       
    70.     private void createFloatView()  
    71.     {  
    72.         //获取LayoutParams对象  
    73.         wmParams = new WindowManager.LayoutParams();  
    74.           
    75.         //获取的是LocalWindowManager对象  
    76.         mWindowManager = this.getWindowManager();  
    77.         Log.i(TAG, "mWindowManager1--->" + this.getWindowManager());  
    78.         //mWindowManager = getWindow().getWindowManager();  
    79.         Log.i(TAG, "mWindowManager2--->" + getWindow().getWindowManager());  
    80.        
    81.         //获取的是CompatModeWrapper对象  
    82.         //mWindowManager = (WindowManager) getApplication().getSystemService(Context.WINDOW_SERVICE);  
    83.         Log.i(TAG, "mWindowManager3--->" + mWindowManager);  
    84.         wmParams.type = LayoutParams.TYPE_PHONE;  
    85.         wmParams.format = PixelFormat.RGBA_8888;;  
    86.         wmParams.flags = LayoutParams.FLAG_NOT_FOCUSABLE;  
    87.         wmParams.gravity = Gravity.LEFT | Gravity.TOP;  
    88.         wmParams.x = 0;  
    89.         wmParams.y = 0;  
    90.         wmParams.width = WindowManager.LayoutParams.WRAP_CONTENT;  
    91.         wmParams.height = WindowManager.LayoutParams.WRAP_CONTENT;  
    92.           
    93.         LayoutInflater inflater = this.getLayoutInflater();//LayoutInflater.from(getApplication());  
    94.           
    95.         mFloatLayout = (LinearLayout) inflater.inflate(R.layout.float_layout, null);  
    96.         mWindowManager.addView(mFloatLayout, wmParams);  
    97.         //setContentView(R.layout.main);  
    98.         mFloatView = (Button)mFloatLayout.findViewById(R.id.float_id);  
    99.           
    100.         Log.i(TAG, "mFloatView" + mFloatView);  
    101.         Log.i(TAG, "mFloatView--parent-->" + mFloatView.getParent());  
    102.         Log.i(TAG, "mFloatView--parent--parent-->" + mFloatView.getParent().getParent());  
    103.         //绑定触摸移动监听  
    104.         mFloatView.setOnTouchListener(new OnTouchListener()   
    105.         {  
    106.               
    107.             @Override  
    108.             public boolean onTouch(View v, MotionEvent event)   
    109.             {  
    110.                 // TODO Auto-generated method stub  
    111.                 wmParams.x = (int)event.getRawX() - mFloatLayout.getWidth()/2;  
    112.                 //25为状态栏高度  
    113.                 wmParams.y = (int)event.getRawY() - mFloatLayout.getHeight()/2 - 40;  
    114.                 mWindowManager.updateViewLayout(mFloatLayout, wmParams);  
    115.                 return false;  
    116.             }  
    117.         });  
    118.           
    119.         //绑定点击监听  
    120.         mFloatView.setOnClickListener(new OnClickListener()  
    121.         {  
    122.               
    123.             @Override  
    124.             public void onClick(View v)   
    125.             {  
    126.                 // TODO Auto-generated method stub  
    127.                 Intent intent = new Intent(FloatWindowTest.this, ResultActivity.class);  
    128.                 startActivity(intent);  
    129.             }  
    130.         });  
    131.           
    132.     }  
    133. }  

        将上面的代码相关注释部分取消,然后运行代码查看Log信息,那么就可以知道问题所在了(每一个Activity对应一个LocalWindowManger,每一个App对应一个CompatModeWrapper),所以要实现在App所在进程中运行的悬浮窗口,当然是得要获取CompatModeWrapper,而不是LocalWindowManger

                         本文相关的完整代码下载链接:http://download.csdn.net/detail/stevenhu_223/4996970

          


    展开全文
  • Android窗口管理

    千次阅读 2013-08-09 15:51:58
    Android系统中,从设计的角度来看,窗口管理系统是基于C/S模式的。整个窗口系统分为服务端和客户端两大部分,客户端负责请求创建窗口和使用窗口,服务端完成窗口的维护,窗口显示等。   在Client...

    一、 概述

    在Android系统中,从设计的角度来看,窗口管理系统是基于C/S模式的。整个窗口系统分为服务端和客户端两大部分,客户端负责请求创建窗口和使用窗口,服务端完成窗口的维护,窗口显示等。

     


    在Client端,并不是直接和WindowManagerService交互,而是直接和本地对象WindowManager交互,然后由WindowManager完成和WindowManagerService的交互。对于Android应用来说这个交互是透明的,应用不能感知到WindowManagerService的存在

     



    二、 窗口的定义

    在android的应用框架中,窗口主要分为两种:

    第一种是应用窗口:一个activity有一个主窗口,弹出的对话框也有一个窗口,Menu菜单也是一个窗口。在同一个activity中,主窗口、对话框、Menu窗口之间通过该activity关联起来。和应用相关的窗口表示类是PhoneWindow和Window,PhoneWindow继承于Window,针对手机屏幕做了一些优化工作。PhoneWindow只是一个窗口封装类,里面核心的是mDecorView这个变量,mDecorView是一个顶层的View,窗口的添加就是通过调用getDecorView()获取到mDecorView并且调用WindowManager.addView()把该View添加到WindowManager中。

    第二种是公共界面的窗口:如最近运行对话框、关机对话框、状态栏下拉栏、锁屏界面等。这些窗口都是系统级别的窗口,不从属于任何应用,和activity没有任何关系。这种窗口没有任何窗口类来封装,直接调用WindowManager.addView()来把一个view添加到WindowManager中。

    在应用初始化的时候,会首先生成一个Activity对象,此时该activity还没有属于他的一个窗口。紧接着通过调用attach()函数,在attach()函数里面该activity会调用PolicyManager.makeNewWindow()创建一个新的PhoneWindow,然后在activity的onCreate()生命周期里,一般应用都会调用setContentView()设置该activity的显示界面。在setContentView()里,框架会自动生成一个布局,该布局文件包含了如标题栏、ActionBar等元素,最重要的是包含了应用的contentView。这个布局对应的就是PhoneWindow里面的mDecorView。最后在activity将要显示出来之前,通过getWindow().getDecorView()获取到DecorView,并通过WindowManager.addView()把DecorView添加到WindowManager中。


     

    Activity添加客户端窗口时序图

     

     



    三、  窗口管理

    Android的窗关管理是基于C/S模式的,并且使用独立进程的方式实现。窗口管理的服务端WindowManagerService运行在独立的进程system_server里,当应用程序需要创建窗口时,通过进程通信的方式请求WindowManagerService创建窗口,由WindowManagerService向应用程序传递和窗口相关的交互消息。所有程序的窗口都在服务端管理,窗口的显示和控制都在WindowManagerService里处理。

    WindowManagerService主要完成了以下几部分功能:

    1.      窗口的添加和删除

    2.      窗口的显示和隐藏控制

    3.      Z-order顺序管理

    4.      焦点窗口和焦点应用的管理

    5.      输入法窗口管理和墙纸窗口管理

    6.      转场动画

    7.      系统消息收集和分发

     

    服务端的实现代码是在/framework/base/services/java/com/android/server/wm/里,核心的几个类是:

    WindowManagerService.java

    WindowState.java

    WindowToken.java

    AppWindowToken.java

    Session.java

    InputManager.java

    InputMonitor.java

     

    类解释:

    WindowManagerService负责完成窗口的管理工作;

    WindowState和客户端窗口一一对应,应用调用WindowManager.addView()时,最终会在WindowManagerService添加一个WindowState与之一一对应。

    WindowToken是一个句柄,保存了所有具有同一个token的WindowState。应用请求WindowManagerService添加窗口的时候,提供了一个token,该token标识了被添加窗口的归属,WindowManagerService为该token生成一个WindowToken对象,所有token相同的WindowState被关联到同一个WindowToken。如输入法添加窗口时,会传递一个mCurrToken,墙纸服务添加窗口时,会传递一个newConn.mToken。

    AppWindowToken继承于WindowToken,专门用于标识一个Activity。AppWindowToken里的token实际上就是指向了一个Activity。ActivityManagerService通知应用启动的时候,在服务端生成一个token用于标识该Activity,并且把该token传递到应用客户端,客户端的Activity在申请添加窗口时,以该token作为标识传递到WindowManagerService。同一个Activity中的主窗口、对话框窗口、菜单窗口都关联到同一个AppWindowToken。

    Session表示一个客户端和服务端的交互会话。一般来说不同的应用通过不同的会话来和WindowManagerService交互,但是处于同一个进程的不同应用通过同一个Session来交互。

    InputManager和InputMonitor负责上层的消息分发功能。

     

    WindowManagerService内部的几个重要成员变量:

    ArrayList<WindowState>         mWindows

    HashMap<IBinder, WindowState>         mWindowMap

    ArrayList<WindowToken>        mTokenList

    ArrayList<AppWindowToken>         mAppTokens

     

    mWindows保存了系统中所有的WindowState;

    mWindowMap保存了每个WindowState和客户端窗口的映射关系,客户端应用请求窗口操作时,通过mWindowMap查询到对应的WindowState;

    mTokenList保存了所有的WindowToken

    mAppTokens保存了所有的AppWindowToken


     


    窗口管理服务端主要类图

     

    一个Activity从启动到添加窗口的整个流程如下:

    ActivityManagerService在接收到启动Activity请求时,首先生成一个token作为该Activity的唯一标识。然后调用WindowManagerService向其添加一个AppWindowToken,此AppWindowToken封装了Activity的token。接着AMS启动应用客户端进程并把token传递到该进程,在客户端进程里完成Activity的初始化。在Activity的attach()函数中,Activity完成PhoneWindow的创建,并且把token传递给PhoneWindow。在Activity调用WindowManager.addView()时,在WindowManager内部会把token和该View关联,真正向WindowManagerService申请创建窗口的时候,再把token传递给WindowManagerService。WindowManagerService接收到创建窗口的请求的时候,通过mTokenMap查询对应该token的AppWindowToken,如果为空则抛出异常,否则创建一个WindowState并完成初始化工作和其他数据结构的调整工作。在这个过程中,token贯穿了服务端的AMS、WMS和客户端的Activity、Window。

     


    Activity启动过程中创建窗口的时序图

     



     

    四、            WMS中服务端和客户端的交互接口和数据结构

    应用请求创建窗口时,和应用直接交互的是WindowManager对象。WindowManager只是一个接口,调用addView()创建窗口时正真交互的是WindowManagerImpl对象。WindowManagerImpl管理单个应用的所有本地窗口。应用调用addView()创建窗口时,WindowManagerImpl会生成一个ViewRoot对象与之相对应,并且把相应的参数LayoutParams保存起来。

    addView()的执行流程如下:

    (1)    检查所添加的窗口是否已经添加过,不允许重复添加;

    (2)    如果所添加窗口为子窗口类型,找到其父窗口,并保存在内部变量中;

    (3)    创建一个新的ViewRoot,并保存对应的View(DecorView)和LayoutParams;

    (4)    调用ViewRoot的setView()方法,完成真正意义上的添加工作。

     

    ViewRoot本质上是一个Handler,并且实现了ViewParent接口。ViewRoot的主要功能是:

    1.      负责分发消息事件,如Key、Motion事件等;

    2.      负责和WMS的交互,分发WMS的交互命令;

    3.      作为DecorView的parent,对DecorView进行draw、measure、layout等操作;

     

    在addView()的第3、4步完成之后,ViewRoot就全权接管了和WMS的交互工作,DecorView不需要做任何交互动作。ViewRoot和WMS之间的双向对话,主要是通过以下两个数据结构进行的:

    IWindowSession

    IWindow

    这两个数据结构都是标准的aidl接口,用于进程之间的同步通信。IWindowSession负责ViewRoot到WMS的单向请求,IWindow则用于WMS回调ViewRoot。在ViewRoot对象内部,存在着一个IWindowSession的静态成员和一个IWindow的非静态成员,所以一个进程里只有一个IWindowSession对象,但是可以有多个IWindow对象。

    Window、WindowManager、DecorView、ViewRoot、IWindowSession、IWindowSession、WindowState、WindowManagerService之间的关系可用下图来表示:


     


     

    在ViewRoot的构造函数中,调用getWindowSession()初始化静态成员sWindowSession和非静态成员mWindow。在第4步调用setView()方法时,ViewRoot会调用sWindowSession.add()方法,把IWindow添加到WMS中,WMS就会生成一个WindowState与之一一对应,并且把IWindow对象保存到WindowState内部作为回调的接口。之后所有WMS的命令,都会通过直接访问IWindow接口,以消息的形式分发到ViewRoot,ViewRoot来完成相应的处理,或对DecorView进行操作,或完成后通过sWindowSession报告给WMS。

     

     

    一个窗口从添加到显示可用以下时序图表示:

     

     


      窗口添加过程时序图

     

     

     


    到此为止,整个窗口管理系统整体架构可表示如下:


     

    窗口管理系统整体架构图

     

     

     

    五、            WindowState和Surface

     

    从Client端调用WindowManager的addView()方法到WMS完成WindowState的初始化,在这整个过程中,只是完成了一个窗口数据结构的创建,也就是说,到现在为止,Client端的窗口和Server端的窗口已经建立了一种相对固定的连接关系,并且Client端和Server端之间能够正常通信,WMS能够透明的对Client端的窗口进行操作,同时WMS也能够接收Client端窗口的命令,对WindowState进行相应的调整。

    一个WindowState想要显示在屏幕上,必须申请一个显示缓存,这个显示缓存的管理和维护是在底层图形模块实现的,在java层有一个操作的封装对象Surface。WindowState申请到Surface对象之后,会将此Surface对象的相关数据拷贝到Client端的ViewRoot中,ViewRoot中也维护了一个Surface对象,实际上这两个对象是指向同一块显示缓存。ViewRoot有了这块显示缓存的引用之后,即可以通过lockCanvas来获取绘画画布,绘制完毕之后通过unlockAndPostCanvas来将绘制内容刷新到显示缓存中。也就是说,Client端窗口和Server端窗口共用一个Surface,Client负责绘制Surface的内容,Server负责控制Surface在屏幕上的大小位置等。

    ViewRoot通过IWindowSession的relayout()接口来向WMS发送请求命令,包括窗口的显示和隐藏,窗口的布局信息如位置大小,同时还会接收WMS的处理结果。WMS会根据屏幕大小和Client请求的布局参数来决定窗口最终的布局信息,同时也会根据Client请求的显示隐藏命令来返回一个有效的或者无效的Surface对象。通常一个窗口的显示过程为:

    1.      Client请求显示窗口,并且传递布局参数;

    2.      WMS根据布局参数,申请一个Surface对象并返回给Client;

    3.      Client对Surface进行绘画操作,完成后告诉WMS;

    4.      WMS将Surface显示在屏幕上,并且进行层级等相应调整;

     


     

    窗口显示过程时序图

     

    一个横跨Activity、View、ViewRoot、IWindowSession、IWindow、WindowState、WindowManagerService、Surface的整体概念如下如所示:


    窗口管理系统完整架构图

    展开全文
  • Android7.0多窗口实现原理(一)

    千次阅读 2018-08-30 19:18:14
    Android N(7.0)版本开始,系统支持了多窗口功能。在有了多窗口支持之后,用户可以同时打开和看到多个应用的界面。并且系统还支持在多个应用之间进行拖拽。在大屏幕设备上,这一功能非常实用。 在Android N中...
  • Android窗口和视图

    万次阅读 2017-06-07 14:56:48
    Android 设备中,我们经常会看到各种各样的窗口或者说视图。例如,我们打开一个应用,会打开主 Activity,我们可以在多个 Activity 中来回切换;我们可以从菜单键打开一个菜单的小窗口;我们经常使用 Dialog 或者...
  • Android8.0多窗口调研

    千次阅读 2018-05-10 11:17:21
    Android8.0多窗口调研一、概述Android8.0上面原生的多窗口功能支持四种模式:全屏、分屏、画中画、FreeForm模式。多窗口主要涉及ActivityManagerService、WindowManagerService、Input三个模块。以下分析基于Android...
  • Android窗口的创建过程

    千次阅读 2013-10-23 22:02:21
    Android窗口分为三类:应用窗口,子窗口,系统窗口 应用窗口一般需要和一个Activity对应,子窗口是有父窗口窗口,系统窗口时由系统创建的窗口,用户是无法创建系统窗口的(Toast,输入法窗口,壁纸窗口除外) ...
  • Android系统中,Activity窗口的大小是由WindowManagerService服务来计算的。WindowManagerService服务会根据屏幕及其装饰区的大小来决定Activity窗口的大小。一个Activity窗口只有知道自己的大小之后,才能对它...
  • 在前一文中,我们分析了Activity组件的切换...再进一步地,如果一个窗口是附加在另外一个窗口之上的,那么被附加窗口所设置的动画也会同时传递给该窗口。本文就详细分析WindowManagerService服务显示窗口动画的原理。
  • Android系统中,壁纸窗口和输入法窗口一样,都是一种特殊类型的窗口,而且它们都是喜欢和一个普通的Activity窗口缠绵在一起。大家可以充分地想象这样的一个3W场景:输入法窗口在上面,壁纸窗口在下面,Activity...
  • 通过前面几篇文章的学习,我们知道了在Android系统中,无论是普通的Activity窗口,还是特殊的输入法窗口和壁纸窗口,它们都是被WindowManagerService服务组织在一个窗口堆栈中的,其中,Z轴位置较大的窗口排列在Z轴...
  • Android窗口化activity

    千次阅读 2014-11-13 14:32:37
    Android窗口化activity有两种: 1.当使用一般主题,既不是
  • Android窗口抖动之动画实现

    千次阅读 2016-01-08 15:35:04
    Android窗口抖动之动画实现 Android的窗口抖动,可以通过写xml动画配置文件实现,比如常见的微信摇一摇,其实就是通过Android动画的一些基本属性如translate,cycleInterpolator实现的。 现在写一个代码,实现一个...
  • 与Activity类似,Android系统中的窗口也是以堆栈的形式组织在WindowManagerService服务中的,其中,Z轴位置较低的窗口位于Z轴位置较高的窗口的下面。在本文中,我们就详细分析WindowManagerService服务是如何以堆栈...
  • Android系统中,同一时刻只有一个Activity组件是处于激活状态的,因此,当ActivityManagerService服务激活了一个新的Activity组件时,它就需要通知WindowManagerService服务将该Activity组件的窗口显示出来,这会...
  • 窗口结构说明 每一个Activity都包含一个Window对象,Window对象...它是Android中的最基本的窗口系统,每个Activity 均会创建一个PhoneWindow对象,是Activity和整个View系统交互的接口。 DecorView:顶层视图,将要显示
  • Framework定义了三种窗口类型,三种类型的定义在WindowManager类中。  第一种为应用窗口。所谓的应用窗口是指该窗口对应一个Activity,由于加载Activity是 由 AmS完成的,因此,对于应用程序来讲,要创建一个应用类...
  • 窗口动画和过渡动画是指在窗口(activity或dialog)切换时的显示动画,窗口动画的范围相对较广,包括activity和dialog,而过渡动画只包括activity。 第一种方法是调用overridePendingTransition的方法,记得要在...
  • 第四篇 ANDROID窗口管理服务实现机制  窗口管理是ANDROID框架一个重要部分,主要包括如下功能:  (1)Z-ordered的维护  (2)窗口的创建、销毁  (3)窗口的绘制、布局  (4)Token管理,AppToken  (5)...
1 2 3 4 5 ... 20
收藏数 163,064
精华内容 65,225
关键字:

android窗口