天天看點

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。