Android 實現簡單的懸浮窗按鈕(二)

這篇主要分析 Window 的工作機制,WindowManager 添加 Window (View) 的主要流程

以下是Android9.0 的源碼

首先看一下 WindowManager.java

xref: /frameworks/base/core/java/android/view/WindowManager.java

package android.view;
......

@SystemService(Context.WINDOW_SERVICE)
public interface WindowManager extends ViewManager {
......

    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
        
        /**
         * X position for this window.  With the default gravity it is ignored.
         * When using {@link Gravity#LEFT} or {@link Gravity#START} or {@link                 Gravity#RIGHT} or
         * {@link Gravity#END} it provides an offset from the given edge.
         */
        @ViewDebug.ExportedProperty
        public int x;

        /**
         * Y position for this window.  With the default gravity it is ignored.
         * When using {@link Gravity#TOP} or {@link Gravity#BOTTOM} it provides
         * an offset from the given edge.
         */
        @ViewDebug.ExportedProperty
        public int y;
        
        ......

    }
......
}

WindowManager 是一個接口,該接口繼承了 ViewManager,內部類 LayoutParams 繼承了 ViewGroup 的 LayoutParams

通過註釋可以看到,當我們使用 WindowManager.LayoutParams 的 x、y參數時需要通過類似 mParams.gravity=Gravity.LEFT | Gravity.TOP;爲其指明 gravity 參數,否則會出現問題(比如無法拖動懸浮按鈕)

看一下 ViewManager 接口

xref: /frameworks/base/core/java/android/view/ViewManager.java

package android.view;

/** Interface to let you add and remove child views to an Activity. To get an instance
  * of this class, call {@link android.content.Context#getSystemService(java.lang.String) Context.getSystemService()}.
  */
public interface ViewManager
{
    /**
     * Assign the passed LayoutParams to the passed View and add the view to the window.
     * <p>Throws {@link android.view.WindowManager.BadTokenException} for certain programming
     * errors, such as adding a second view to a window without removing the first view.
     * <p>Throws {@link android.view.WindowManager.InvalidDisplayException} if the window is on a
     * secondary {@link Display} and the specified display can't be found
     * (see {@link android.app.Presentation}).
     * @param view The view to be added to this window.
     * @param params The LayoutParams to assign to view.
     */
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

WindowManager 是一個接口,誰實現了這個接口呢(可以搜索關鍵字 "implements WindowManager")

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

package android.view;
......
public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Context mContext;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Context context) {
        this(context, null);
    }
    ......

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

    @Override
    public void removeViewImmediate(View view) {
        mGlobal.removeView(view, true);
    }

添加、刪除 View的操作是通過WindowManagerGlobal來實現的,進入 WindowManagerGlobal

xref: /frameworks/base/core/java/android/view/WindowManagerGlobal.java

package android.view;
......

public final class WindowManagerGlobal {
    private static final String TAG = "WindowManager";
    
    ......


    private final Object mLock = new Object();

    /*
       以下幾個變量比較重要
       mViews 存儲所有 Winow 所對應的 View
       mRoots 存儲所有 Window 所對應的 ViewRootImpl
       mParams 存儲所有 Window 對應的佈局參數
       mDyingViews 存儲正在被刪除的 View 對象(已經調用 removeView 方法但還未完成刪除)
    */
    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 static WindowManagerGlobal getInstance() {
        synchronized (WindowManagerGlobal.class) {
            if (sDefaultWindowManager == null) {
                sDefaultWindowManager = new WindowManagerGlobal();
            }
            return sDefaultWindowManager;
        }
    }

    //獲取 WindowManagerService 服務
    public static IWindowManager getWindowManagerService() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowManagerService == null) {
                sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));
                try {
                    if (sWindowManagerService != null) {
                        ValueAnimator.setDurationScale(
                                sWindowManagerService.getCurrentAnimatorScale());
                    }
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowManagerService;
        }
    }
    
    //獲取 WindowSession
    public static IWindowSession getWindowSession() {
        synchronized (WindowManagerGlobal.class) {
            if (sWindowSession == null) {
                try {
                    InputMethodManager imm = InputMethodManager.getInstance();
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
                                }
                            },
                            imm.getClient(), imm.getInputContext());
                } catch (RemoteException e) {
                    throw e.rethrowFromSystemServer();
                }
            }
            return sWindowSession;
        }
    }
    ......

    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
        ViewRootImpl root;
        View panelParentView = null;
         
        ......

        int index = findViewLocked(view, false);//獲取要添加的View的索引
        //這裏檢查索引值,因爲如果是第一次添加,返回的索引應該爲 -1
        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.
        }


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

          view.setLayoutParams(wparams);//爲View設置佈局參數

            //將Window一系列對象添加到列表
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

               
            //通過ViewRootImpl的 setView 方法來更新界面,完成Window的添加過程
            // 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

xref: /frameworks/base/core/java/android/view/ViewRootImpl.java

package android.view;

......

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {
    private static final String TAG = "ViewRootImpl";

    ......


    public ViewRootImpl(Context context, Display display) {
        mContext = context;
        //在構造方法中獲取WindowSession實例
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        ......
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        ......
    }


    ......


     /**
     * We have one child
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;//getView方法中會直接返回mView,可以通過getView直接獲取該View

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mAttachInfo.mDisplayState = mDisplay.getState();
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);


                ......

                if (panelParentView != null) {
                    mAttachInfo.mPanelParentWindowToken
                            = panelParentView.getApplicationWindowToken();
                }
                mAdded = true;
                int res; /* = WindowManagerImpl.ADD_OKAY; */

                // 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()來完成異步刷新請求
                requestLayout();
                ......
                
                 try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    //通過WindowSession最終完成 Window 的添加過程
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout,  mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
               ......

mWindowSession 是 IWindowSession,是一個 Binder 對象,addToDisplay 是Window添加過程中的一次IPC調用

看一下IWindowSession是由誰來實現的(通過搜索 "IWindowSession.Stub")

可以看到 從 frameworks/base/core 進入了 frameworks/base/services/core

xref: /frameworks/base/services/core/java/com/android/server/wm/Session.java

位於 com.android.server.wm 包下

package com.android.server.wm;

......

/**
 * This class represents an active client session.  There is generally one
 * Session object per process that is interacting with the window manager.
 */
class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
  ......

  final WindowManagerService mService;//定義了WindowManagerService
  final IWindowSessionCallback mCallback;

  //構造函數中初始化 mService
  public Session(WindowManagerService service, IWindowSessionCallback callback,
            IInputMethodClient client, IInputContext inputContext) {
        mService = service;
  
  ......

  }

  ......


  @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) {

        //最終通過 WindowMangerService 添加Window
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,    outFrame,outContentInsets, outStableInsets, outOutsets, outDisplayCutout,  outInputChannel);

    }

......

}

WindowManagerService 也和 Session在同一路徑下

xref: /frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    private static final String TAG = TAG_WITH_CLASS_NAME ? "WindowManagerService" : TAG_WM;
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章