天天看點

Window和WindowManager深入了解

Window表示一個懸浮視窗,在開發直接接觸的并不多,但是當我們需要使用在桌面上顯示一個懸浮窗的東西的時候Window就派上用場了。Window是一個抽象類,它的具體實作時PopupWindow類,建立Window很簡單,隻需要通過WindowManager即可完成。WindowManager是外界通路Window的入口。

Window和WindowManager之間的聯系

為了分析Window的工作機制,使用WindowManager來添加一個Window

floatingButton=new Button(this);
floatingButton.setText("123");
mLayoutParams=new WindowManager.LayoutParams(LayoutParams.WRAP_CONTENT,LayoutParams.WRAP_CONTENT,0,0,PixelFormat.TRANSPARENT);
mLayoutParams.flags=LayoutParams.FLAG_NOT_TOUCH_MODAL|LayoutParams.FLAG_NOT_FOCUSABLE|LayoutParams.FLAG_SHOW_WHEN_LOCKED;
mLayoutParams.gravity=Gravity.LEFT|Gravity.TOP;
mLayoutParams.x=100;
mLayoutParams.y=300;
mWindowManager.addView(floatingButton,mLayoutParams);
           

在建構WindowManager.LayoutParams時,其中的type和flags是非常重要的.

flags參數有很多選項,用來控制Window的顯示特性

  • FLAG_NOT_FOCUSABLE——表示此Window不需要擷取焦點,不接受各種輸入事件,此标記會同時啟用FLAG_NOT_TOUCH_MODAL,最終事件會直接傳遞給下層具有焦點的Window
  • FLAG_NOT_TOUCH_MODAL——自己Window區域内的事件自己處理,自己Window區域外的事件傳遞給底層Window處理。一般這個選項會預設開啟,否則其他Window無法收到事件
  • FLAG_SHOW_WHEN_LOCKED——可以讓Window顯示在鎖屏上

type參數時int類型,表示Window的類型。Window有三種類型

  • 應用Window——對應着一個Activity,層級範圍1~99;
  • 子Window——不能獨立存在,需要附屬在特定的父Window種,比如Dialog就是一個子Window。層級範圍1000~1999;
  • 系統WIndow——需要聲明權限才能建立,如Toast和系統狀态欄都是系統Window,層級範圍2000~2999;

Window是分層的,層級大的Window會覆寫在層級小的Window上面。

WindowManager提供的功能很簡單,常用的隻有三個方法,即添加View,更新View和删除View。這三個方法定義在ViewManager種,而WindowManager繼承自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);
}
           

應用懸浮窗執行個體:

public class MainActivity extends AppCompatActivity implements View.OnClickListener, View.OnTouchListener {

    private static final String TAG="TestActivity";

    private Button addBtn,rmvBtn;

    private ImageView imageView;
    private WindowManager windowManager;
    private WindowManager.LayoutParams layoutParams;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //當API大于23時需要在代碼中動态申請權限
        if(Build.VERSION.SDK_INT>=Build.VERSION_CODES.M){
            Intent intent=new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            startActivityForResult(intent,100);
        }else {
            initView();

        }

    }

    private void initView() {
        addBtn=findViewById(R.id.add_btn);
        rmvBtn=findViewById(R.id.remocve_btn);
        addBtn.setOnClickListener(this);
        rmvBtn.setOnClickListener(this);

        windowManager=(WindowManager) getApplicationContext().getSystemService(Context.WINDOW_SERVICE);
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode==100){
            initView();
        }
    }

    @Override
    public void onClick(View v) {
        switch (v.getId()){
            case R.id.add_btn:
                addView();
                break;
            case R.id.remocve_btn:
                removeView();
                break;
        }
    }

    private void removeView() {
        windowManager.removeViewImmediate(imageView);
    }

    private void addView() {
        imageView = new ImageView(this);
        imageView.setBackgroundResource(R.mipmap.ic_launcher);
        layoutParams = new WindowManager.LayoutParams(WindowManager.LayoutParams.WRAP_CONTENT
                , WindowManager.LayoutParams.WRAP_CONTENT, 2099,
                WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
                        | WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED
                , PixelFormat.TRANSPARENT);
        layoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY;
        layoutParams.gravity = Gravity.TOP | Gravity.LEFT;
        layoutParams.x = 0;
        layoutParams.y = 300;
        imageView.setOnTouchListener(this);
        windowManager.addView(imageView, layoutParams);
    }

    @Override
    public boolean onTouch(View v, MotionEvent event) {

        int rawX=(int) event.getRawX();
        int rawY=(int) event.getRawY();

        switch (event.getAction()){
            case MotionEvent.ACTION_MOVE: {
                layoutParams.x = rawX;
                layoutParams.y = rawY;
                windowManager.updateViewLayout(imageView, layoutParams);
                break;
            }
            default:
                break;
        }
        return true;
    }
}
           

在XML中直接添加了兩個Button

實作效果:

Window和WindowManager深入了解
Window和WindowManager深入了解

Window的内部機制

window是一個抽象概念,每一個Window都對應着一個View和ViewRootImpl,Window和View通過ViewRootImpl來建立聯系。是以Window并不是實際存在的,而是以View的形式存在。 在實際使用中無法直接通路Window,對Window的通路必須同通過WindowManager。在WindowManager住要提供了三個接口方法,addView,updateViewLayout,removeView,是以我們就從Window的添加,更新,删除來看它的内部機制。

Window添加的過程

在WindowManager中真正實作三大操作的是WindowManagerImpl類,在WindowManagerImpl中Window的三大操作如下:

@Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), 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);	  
    }
           

在代碼中看到了一個mGlobal,發現這一些列操作都是由他來實作的,來看看mGlobal是何方神聖。

這個mGlobal是一個WindowManagerGlobal類的執行個體,那麼繼續檢視在這個類中是如何實作我們的三大操作的呢?

WindowManagerGlobal中的addView如下:

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
            //檢查參數是否合法,如果是子Window那麼還需要調整一些布局參數
        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) {
        //當是子Window的時候需要調整參數通過adjustLayoutParamsForSubWindow這個方法
            parentWindow.adjustLayoutParamsForSubWindow(wparams);
        } else {
            //如果沒有父視圖,則此視圖的硬體加速将從應用程式的硬體加速設定中設定。
            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);
                    }
                }
            }
            //下列操作将Window的一系列對象添加到清單中
            root = new ViewRootImpl(view.getContext(), display);
          
            view.setLayoutParams(wparams);
            //mView存儲的是所有Window對應的View
            mViews.add(view);
            //mRoots存儲的是所有Window所對應的ViewRootImpl
            mRoots.add(root);
            //mParams存儲的是所有Window所對應的布局參數
            mParams.add(wparams);

            // do this last because it fires off messages to start doing things
            try {
            //通過ViewRootImpl的執行個體root.setView來更新界面并完成Window的添加過程
                root.setView(view, wparams, panelParentView);
            } catch (RuntimeException e) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }
           

通過ViewRootImpl将View添加到清單,并通過ViewRootImpl來更新界面并完成Window的添加,在ViewRootImpl的執行個體的setView方法中來完成,在setView的内部會通過requestLayout來完成異步重新整理請求。

@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();
            mLayoutRequested = true;
            //View的繪制入口
            scheduleTraversals();
        }
    }
           

檢視scheduleTraversals的實作:

try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    setFrame(mTmpFrame);
                } 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();
                    }
                }
           

通過WindowSession最終來實作Window的添加過程,WindowSession是一個IWindowSession,是一個Binder對象,真正的實作類是Session,是以Window的添加過程就是一次IPC調用

session内部的會通過WindowManagerService來實作Window的添加

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

最終,Window的添加請求移交給了WindowManagerService手上,在WindowManagerService内部會為每一個應用保留一個單獨的Session。Window的添加就到此為止了。

圖解:

Window和WindowManager深入了解

對于Window的删除操作和更新操作其實都是類似的。

繼續閱讀