天天看点

Window的创建过程分析

Window就是窗口,是一个抽象类,它的具体实现类是PhoneWindow。它的作用是

显示和管理view,不管是activity,toast,dialog他们的view都是依附在Window上的,都是通过Window呈现的。DecorView的事件也是从Window传递过去的。平时如果想要显示一个悬浮窗,就可以使用到Window。

创建一个Window的步骤如下:

1.首先要确定是要显示系统窗口还是应用窗口,如果是要显示系统窗口则要获取权限,如果用户未授权,则会报错

a.在manifest文件中声明需要使用到的权限

//弹出系统类型的Window悬浮窗需要的权限

<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
           

b.在6.0系统上要进行动态授权

private fun checkWindowPermission(context:Activity): Boolean {
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
            if(!Settings.canDrawOverlays(context)){
                //还未授权,需要跳转到权限设置页面
                ToastUtils.longToast(context,"当前无权限,请授权")
                context.startActivityForResult(Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION, Uri.parse("package:"+context.packageName)),0)
                return false
            }
            return true
        }
        return true
    }
           

2.创建window

//6.0以上系统
if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
    windowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
    btn = new Button(context);
    btn.setText("悬浮窗");
    btn.setGravity(Gravity.CENTER);
    btn.setBackground(getResources().getDrawable(R.color.red));

    final WindowManager.LayoutParams layoutParams = new WindowManager.LayoutParams();
    if(Build.VERSION.SDK_INT>Build.VERSION_CODES.O){
        //8.0以上的系统
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
       //这里如果指定window级别是应用级的,
        // 那么就会报Unable to add window -- token null is not valid; is your activity running? 这个异常
       // layoutParams.type =1; running? 这个异常
    }else {
        layoutParams.type = WindowManager.LayoutParams.TYPE_PHONE;
          //这里如果指定window级别是应用级的,
        // 那么就会报Unable to add window -- token null is not valid; is your activity running? 这个异常
        // layoutParams.type =1;running? 这个异常
    }
    //这里falgs 如果是仅仅设置成FLAG_NOT_TOUCH_MODAL,那么按返回键,onDestory方法不会回调
    layoutParams.flags =  FLAG_NOT_FOCUSABLE | FLAG_NOT_TOUCH_MODAL;
    layoutParams.width = WindowManager.LayoutParams.WRAP_CONTENT;
    layoutParams.height = 300;

    layoutParams.gravity = Gravity.LEFT | Gravity.TOP;
    layoutParams.x = 300;
    layoutParams.y = 300;
    windowManager.addView(btn,layoutParams);
    //获取当前窗口可视区域大小
    Rect rect = new Rect();
    btn.getWindowVisibleDisplayFrame(rect);
    LogUtil.i("rect.width()=${rect.width()}      rect.height()= ${rect.height()}");
    btn.post(new Runnable() {
        @Override
        public void run() {
            WindowId windowId = btn.getWindowId();
            LogUtil.e("windowId================   "+windowId);
            ViewParent parent = btn.getParent();
            LogUtil.e("parent================   "+parent);
        }
    });


    btn.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    downX = (int) event.getRawX();
                    downY = (int) event.getRawY();
                    break;
                case MotionEvent.ACTION_MOVE:
                    int moveX = (int) event.getRawX();
                    int moveY = (int) event.getRawY();
                    layoutParams.x += moveX - downX;
                    layoutParams.y += moveY - downY;
                    windowManager.updateViewLayout(btn,layoutParams);
                    downX = moveX;
                    downY = moveY;
                    break;
                case MotionEvent.ACTION_UP:
                    if(event.getRawX() == downX && event.getRawY() == downY){
                        LogUtil.e("单击");
                        makeActivityFromBackgroundToForeground();
                    }
                    break;
            }
            return false;
        }
    });
}

           

上面的代码中,设置了Window的flags是用于控制window的显示特性的,这里我们详细的介绍下,Window常用的几种flags

WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE

这种标记表示window不需要获取焦点,也不需要接收各种输入事件,此标记会同时启用FLAG_NOT_TOUCH_MODAL,

最终事件会传递到下层的具有焦点的window。

WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL

此模式下,系统将当前window区域以外的单击事件传递个底层的window,当前window区域的事件自己处理,一般需要开启这个标记

WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED

这个标记表示window可以显示在锁屏的界面上.

WindowManger.LayoutParams.type是用于区分window的类型的,window的类型分为三类,

应用Window: type的取值范围是1~99 比如 dialog

子Window : type的取值范围是1000~1999 不能单独存在,需要依附在父window上,比如 popwindow

系统Window: type的取值范围是2000~2999 需要权限才能创建

type的取值越大,则现在的层次就更在上面。上面的window会覆盖下面的window。所以想要弹出的window不被应用的window遮盖,就将type的值设置到2000~2999之间。这时的window就是系统级别的,需要在manifest文件中使用权限,并且在6.0的版本中,动态获取权限。

上面的代码中,如果将WindowManger.LayoutParams.type的值设置为1,也就是将Window的类型设置为应用Window,那么就会报错,报错的原因可以查看这篇文章的分析。

介绍完基本使用后,我们来看看window的创建过程,

Window的添加过程:首先要看windowManager.addView()方法,这个方法是用于添加view的。

下面我们看看WindowManger的addView方法,点击这个方法,结果跳转到了ViewManger接口中了,

下面看看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);
}
           

可以看到这个接口定义了三个方法,分别是添加、更新、删除view的方法,所以,WindowManger实际操的对象都是view。WindowManager其实也是一个接口,它继承了ViewManager接口,所以WindowManager才拥有ViewManager的三个方法。但是WindowManager毕竟只是一个接口,具体的addView的操作肯定是在其实现类中完成的,WindowManager的实现类就是WindowMangerImpl,下面我们看看WindowManagerImpl类的addView方法

/frameworks/base/core/java/android/view/WindowManagerImpl.java

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;
    // ... 省略部分代码
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

    // ... 省略部分代码
}
           

从这个方法中可以看到,它的addView的操作是交给了WindowManagerGlobal来完成的,这里使用了桥接设计模式。

下面看看WindowManagerGlobal类的addView方法:

/frameworks/base/core/java/android/view/WindowManagerGlobal.java
public final class WindowManagerGlobal {

 private static WindowManagerGlobal sDefaultWindowManager;
 private static IWindowManager sWindowManagerService;
 private static IWindowSession sWindowSession;

 private final Object mLock = new Object();

 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>();
 private final ArraySet<View> mDyingViews = new ArraySet<View>();

 // ... 省略部分代码

 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;
        
	// ... 省略部分代码

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // ... 省略部分代码

            int index = findViewLocked(view, false); // 1
            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.");
                }
            }

            // ... 省略部分代码

            root = new ViewRootImpl(view.getContext(), display); //2 

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

            try {
                root.setView(view, wparams, panelParentView);   // 3
            } catch (RuntimeException e) {
               // ...省略部分代码
            }
        }
    }
}
           

这个方法中,前面个if判断,是做参数检查。

注释1 处,会调用findViewLocked方法,查找view是否被添加,如果已经添加,继续判断这个view是否是在即将从Window移除的view,如果是,则将其移除,如果这个view不是即将被从Widow中移除的那个对象,则会报view+ " has already been added to the window manager.异常。在注释2处,会创建ViewRootImpl对象,接着设置view的params,在将view添加到mViews这个集合中,这个mViews集合是专门存放所有Window对应的view的,接着将前面创建的ViewRootImpl对象root添加到mRoots集合中,这个mRoots集合是用于存放所有Window对应的ViewRootImpl对象的。接着将wparams添加到mParams集合中,这个mParams集合中,存储了所有Window对应的params。完成了这些操作后, 在注释3 处,调用ViewRootImpl的setView方法,下面看看ViewRootImpl的setView方法的源码:

ViewRootImpl类中
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
		// ...省略部分代码

                int res; /* = WindowManagerImpl.ADD_OKAY; */

                //  1  通过handler发送消息完成view的measure,layout,draw
                requestLayout();

                try {
                    // ...省略部分代码

		            // 2  通过mWindowSession.addToDisplay方法内部通过WindowManagerService完成了window的添加
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                }

		// ... 省略部分代码
         
            }
        }
    }

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

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
         // ...省略部分代码
          // 关键代码
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
          // ...省略部分代码
        }
    }
           

注释1出,通过调用调用requestLayout方法,接着调用了scheduleTraversals()方法,这个方法中

通过mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);这个方法最终会回调到mTraversalRunnable这个Runnable的run方法。下面接着看mTraversalRunnable的run方法:

ViewRootImpl类中

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
        if (mTraversalScheduled) {
            // ...省略部分代码
	     
	       // 这个方法最终会执行performMeasure,performLayout,performDraw三个方法,完成view的测量、布局、绘制
            performTraversals();

           // ...省略部分代码
        }
    }

 private void performTraversals() {
	// ... 省略部分代码

	try {
               // ... 省略部分代码
               
		       // 关键点 relayoutWindow方法内部通过WindowManagerService完成Window的更新
                relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  
            } catch (RemoteException e) {
            }

        if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
           
	        // ... 省略部分代码
           
            if (!mStopped || mReportNextDraw) {
               // ...省略部分代码
                if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                        || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                        updatedConfiguration) {
					// ...省略部分代码

                    // 关键点 
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                    // ...省略部分代码

                    if (measureAgain) {
			           // 关键点 
                        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                    }
                  // ...省略部分代码
                }
            }
        }

       // ...省略部分代码
        if (didLayout) {
	       // 关键代码
            performLayout(lp, mWidth, mHeight);
	    
	    // ... 省略部分代码
        }

       // ...省略部分代码
       
        if (!cancelDraw && !newSurface) {
           // ...省略部分代码
	    // 关键代码
            performDraw();
        }
        // ...省略部分代码
    }
           

通过这一系列的方法的调用,ViewRootImpl的requestLayout方法方法其实就是完成了view的测量,布局和绘制过程。

接着继续看ViewRootImpl的setView方法中的注释2处

ViewRootImpl类中
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
		        // ...省略部分代码

                int res; /* = WindowManagerImpl.ADD_OKAY; */

                //  1  通过handler发送消息完成view的measure,layout,draw
                requestLayout();

                try {
                  // ...省略部分代码

		            // 2  通过mWindowSession.addToDisplay方法了添加window
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                }

		 // ... 省略部分代码
         
            }
        }
    }
           

mWindowSession是WindowManagerSerivice的Session对象在客户端的一个代理对象,它是IWindowSession的类型变量,它是IWindowSession也是一个Binder,所以mWindowSession也是一个binder,mWindowSession最终通过IPC和WindowManagerService进行通信,在SystemServer进程中,调用Session的addToDisplay方法,这个方法会调用到WindowManagerService的addWindow方法,完成对window的添加。

总结:window的添加过程,首先是通过WindowManager.addView方法发起的,由于WindowManager是个接口,实际

的addView方法是它的实现类WindowManagerImpl完成的,WindowManagerImpl的addView方法内部又通过WindowManagerGlobal类的addView方法来完成view的添加,WindowManagerGlobal内部又通过ViewRootImpl的setView方法来对view进行添加。setView方法内部,调用mWindowSession的relayoutWindow对view进行跟新,在接着完成view的测量,布局,和绘制。完成这个步骤后,在通过mWindowSession.addToDisplay()方法发起远程调用,最终调用SystemServer进程的Session类的addToDisplay()方法,这个方法内部,有调用WindowManagerService的addWindow()方法来完成window的添加。这就是Window的整个添加过程。WindowManagerService内部会为每个应用保留一个Session。