Android WindowManager 解析

WindowManagerService 源碼
https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/services/core/java/com/android/server/wm/WindowManagerService.java

窗口動畫由 WMS 的動畫子系統(tǒng)來負(fù)責(zé),動畫子系統(tǒng)的管理者為 WindowAnimator。
https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/services/core/java/com/android/server/wm/WindowAnimator.java
WindowManagerPolicy
https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java
WindowManagerGlobal
https://cs.android.com/android/platform/superproject/+/android14-qpr3-release:frameworks/base/core/java/android/view/WindowManagerGlobal.java

WindowManager 解析

AMS(ActivityManagerService)應(yīng)用進(jìn)程的啟動、切換和調(diào)度、四大組件的啟動和管理
WMS(WindowManagerService)管理的整個系統(tǒng)所有窗口的UI
PMS(PackageManagerService)處理包管理相關(guān)的工作,常見的比如安裝、卸載應(yīng)用等

初識WMS

因?yàn)樗许撁娴恼故径际腔赪indow的,在退出后臺之后會在前臺有一個懸浮窗口,我們通過WindowManager完成

WindowManager涉及到的類圖關(guān)系
類圖

Window的創(chuàng)建

Activity android.app.Activity (https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/app/Activity.java) 88877 line

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
    final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
            IBinder shareableActivityToken) {
        attachBaseContext(context);

        mFragments.attachHost(null /*parent*/);
        mActivityInfo = info;
        // 創(chuàng)建Window
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(mWindowControllerCallback);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
        if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
            mWindow.setSoftInputMode(info.softInputMode);
        }
        if (info.uiOptions != 0) {
            mWindow.setUiOptions(info.uiOptions);
        }
        ...
      //獲得WindowManager
        mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();
        mCurrentConfig = config;

        mWindow.setColorMode(info.colorMode);
        mWindow.setPreferMinimalPostProcessing(
                (info.flags & ActivityInfo.FLAG_PREFER_MINIMAL_POST_PROCESSING) != 0);

        getAutofillClientController().onActivityAttached(application);
        setContentCaptureOptions(application.getContentCaptureOptions());
    }

Activity. attach() 創(chuàng)建Window 獲得WindowManager,拿到的就是WindowManagerImpl實(shí)例
https://cs.android.com/android/platform/superproject/main/+/main:frameworks/base/core/java/android/view/Window.java

 public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
            boolean hardwareAccelerated) {
        mAppToken = appToken;
        mAppName = appName;
        mHardwareAccelerated = hardwareAccelerated;
        if (wm == null) {
            wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
        }
        mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }

WindowManager

WindowManager是一個接口類,繼承接口ViewManager,分別用來添加、更新和刪除,而且Android 所有的控件ViewGroup 也是繼承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);
}

addView 添加流程

初始化獲取WindowManager

    private fun initWindowManager() {
        mWindowManager = mContext.getSystemService(WINDOW_SERVICE) as WindowManager
       val mInterPhoneFloatingWindowView = InterPhoneFloatingWindowView(mContext)
       val layoutParams = WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE or WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS,
                PixelFormat.RGBA_8888 //OPAQUE
            )
       mWindowManager?.addView(mInterPhoneFloatingWindowView!!, layoutParams)
    }

WindowManager 的實(shí)現(xiàn)類 WindowManagerImpl
WindowManagerImpl

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

這里的 mGlobal 是

private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

WindowManagerImpl雖然是WindowManager的實(shí)現(xiàn)類,但是卻沒有實(shí)現(xiàn)什么功能,而是將功能實(shí)現(xiàn)委托給了WindowManagerGlobal,這里用到的就是橋接模式,而且 WindowManagerGlobal是一個單例,說明在一個進(jìn)程中只有一個WindowManagerGlobal實(shí)例
mViews:存儲當(dāng)前應(yīng)用中所有窗口的根視圖(View)。
mRoots:存儲每個窗口對應(yīng)的 ViewRootImpl,負(fù)責(zé)窗口的繪制和事件分發(fā)。
mParams:存儲每個窗口的布局參數(shù)(WindowManager.LayoutParams),用于描述窗口的屬性。
WindowManagerGlobal.addView()

 public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow, int userId) {
         ViewRootImpl root;
           ...
         IWindowSession windowlessSession = null;
            // If there is a parent set, but we can't find it, it may be coming
            // from a SurfaceControlViewHost hierarchy.
            if (wparams.token != null && panelParentView == null) {
                for (int i = 0; i < mWindowlessRoots.size(); i++) {
                    ViewRootImpl maybeParent = mWindowlessRoots.get(i);
                    if (maybeParent.getWindowToken() == wparams.token) {
                        windowlessSession = maybeParent.getWindowSession();
                        break;
                    }
                }
            }

            if (windowlessSession == null) {
                root = new ViewRootImpl(view.getContext(), display);
            } else {
                root = new ViewRootImpl(view.getContext(), display,
                        windowlessSession, new WindowlessWindowLayout());
            }

            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, userId);   // 設(shè)置View
            } catch (RuntimeException e) {
                final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);
                // BadTokenException or InvalidDisplayException, clean up.
                if (viewIndex >= 0) {
                    removeViewLocked(viewIndex, true);
                }
                throw e;
            }
}

ViewRootImpl

   public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
            int userId) {
    ...
       res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), userId,
                            mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,
                            mTempControls, attachedFrame, compatScale);
    ...
}

Session 是IWindowSession 實(shí)現(xiàn)類

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient{
  @Override
    public int addToDisplay(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId,
                UserHandle.getUserId(mUid), requestedVisibleTypes, outInputChannel, outInsetsState,
                outActiveControls, outAttachedFrame, outSizeCompatScale);
    }
    @Override
    public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibleTypes, outInputChannel, outInsetsState, outActiveControls,
                outAttachedFrame, outSizeCompatScale);
    }
}

WindowManagerService 解析

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
        
    public int addWindow(Session session, IWindow client, LayoutParams attrs, int viewVisibility,
            int displayId, int requestUserId, @InsetsType int requestedVisibleTypes,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl.Array outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
       outActiveControls.set(null, false /* copyControls */);
        int[] appOp = new int[1];
        final boolean isRoundedCornerOverlay = (attrs.privateFlags
                & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
        int res = mPolicy.checkAddPermission(attrs.type, isRoundedCornerOverlay, attrs.packageName,
                appOp);//調(diào)用 WindowManagerPolicy 的 checkAddPermission() 方法來檢查權(quán)限  
        if (res != ADD_OKAY) {
            return res;
        }
    ...
     // 通過DisplayId來獲得窗口要添加到哪個DisplayContent上
        final DisplayContent displayContent = getDisplayContentOrCreate(displayId, attrs.token);
            if (displayContent == null) {
                ProtoLog.w(WM_ERROR, "Attempted to add window to a display that does "
                        + "not exist: %d. Aborting.", displayId);
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
        //  type 在 FIRST_SUB_WINDOW 與 LAST_SUB_WINDOW 之間(1000~1999),說明是子窗口
          if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is not a window: "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    ProtoLog.w(WM_ERROR, "Attempted to add window with token that is a sub-window: "
                            + "%s.  Aborting.", attrs.token);
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }
} 

創(chuàng)建獲取WindowToken

ActivityRecord activity = null;
            final boolean hasParent = parentWindow != null;
            // Use existing parent window token for child windows since they go in the same token
            // as there parent window so we can apply the same policy on them.
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);
            // If this is a child window, we want to apply the same type checking rules as the
            // parent window type.
            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

            boolean addToastWindowRequiresToken = false;

            final IBinder windowContextToken = attrs.mWindowContextToken;

            if (token == null) {
                if (!unprivilegedAppCanCreateTokenWith(parentWindow, callingUid, type,
                        rootType, attrs.token, attrs.packageName)) {
                    return WindowManagerGlobal.ADD_BAD_APP_TOKEN;
                }
                if (hasParent) {
                    // Use existing parent window token for child windows.
                    token = parentWindow.mToken;
                } else if (mWindowContextListenerController.hasListener(windowContextToken)) {
                    // Respect the window context token if the user provided it.
                    final IBinder binder = attrs.token != null ? attrs.token : windowContextToken;
                    final Bundle options = mWindowContextListenerController
                            .getOptions(windowContextToken);
                    token = new WindowToken.Builder(this, binder, type)
                            .setDisplayContent(displayContent)
                            .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                            .setRoundedCornerOverlay(isRoundedCornerOverlay)
                            .setFromClientToken(true)
                            .setOptions(options)
                            .build();
                } else {
                    final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                    token = new WindowToken.Builder(this, binder, type)
                            .setDisplayContent(displayContent)
                            .setOwnerCanManageAppTokens(session.mCanAddInternalSystemWindow)
                            .setRoundedCornerOverlay(isRoundedCornerOverlay)
                            .build();
                }
            }

WindowState的創(chuàng)建和處理

 final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], attrs, viewVisibility, session.mUid, userId,
                    session.mCanAddInternalSystemWindow);
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
       // 根據(jù)窗口的type對窗口的LayoutParams的一些成員變量進(jìn)行修改
            displayPolicy.adjustWindowParamsLw(win, win.mAttrs);
            attrs.flags = sanitizeFlagSlippery(attrs.flags, win.getName(), callingUid, callingPid);
            attrs.inputFeatures = sanitizeInputFeatures(attrs.inputFeatures, win.getName(),
                    callingUid, callingPid, win.isTrustedOverlay());
            win.setRequestedVisibleTypes(requestedVisibleTypes);
        //檢查是否可以把這個窗口添加到系統(tǒng)中
        res = displayPolicy.validateAddingWindowLw(attrs, callingPid, callingUid);
            if (res != ADD_OKAY) {
                return res;
            }
    ...
          //將 WindowState 添加到該 WindowState 對應(yīng)的 WindowToken 中
            win.mSession.onWindowAdded(win);
            mWindowMap.put(client.asBinder(), win);

WindowState 添加到 mWindowMap 中,然后將 WindowState 添加到該 WindowState 對應(yīng)的
WindowToken 中(實(shí)際是保存在 WndowToken 的父類 WindowContainer 中),這樣WindowToken就包含了同一個組件的WindowState

updateViewLayout 更新

  @Override
    public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyTokens(params);
        mGlobal.updateViewLayout(view, params);
    }

frameworks/base/core/java/android/view/WindowManagerGlobal.java 更新

    public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
          //查找
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
          //替換
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);   //更新
        }
    }

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

 public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView){
        scheduleTraversals();
}

scheduleTraversals() 最后會調(diào)用 performTraversals() 來開始 View 的測量、布局和繪制

   private void performTraversals() {
       relayoutResult = relayoutWindow(params, viewVisibility, insetsPending)  
       ...
        measureHierarchy(host, lp, mView.getContext().getResources(),
                    desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
       ...
           performLayout(lp, mWidth, mHeight);
            ...
            performDraw();
}
   private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
            final Resources res, final int desiredWindowWidth, final int desiredWindowHeight,
            boolean forRootSizeOnly) {
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); //
            
}
   
    private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mMeasuredWidth = mView.getMeasuredWidth();
        mMeasuredHeight = mView.getMeasuredHeight();
        mViewMeasureDeferred = false;
    }

performTraversals中會依次調(diào)用performMeasure、performLayout、performDraw方法來完成對setView方法設(shè)置進(jìn)來的view的測量、布局、繪制。所以View的繪制是ViewRootImpl完成的,另外當(dāng)手動調(diào)用invalidate,postInvalidate,requestInvalidate也會最終調(diào)用performTraversals,來重新繪制View

 private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
           relayoutResult = mWindowSession.relayout(mWindow, params,
                        requestedWidth, requestedHeight, viewVisibility,
                        insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
                        mRelayoutSeq, mLastSyncSeqId, mRelayoutResult);
}

Session 內(nèi)部肯定會利用 WindowManagerService 來完成 Window 的更新。

   @Override
    public int relayout(IWindow window, WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewFlags, int flags, int seq,
            int lastSyncSeqId, WindowRelayoutResult outRelayoutResult) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, mRelayoutTag);
        int res = mService.relayoutWindow(this, window, attrs, requestedWidth,
                requestedHeight, viewFlags, flags, seq, lastSyncSeqId, outRelayoutResult);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        return res;
    }
WindowManagerService

DisplayPolicy

DisplayContent

  private int relayoutWindowInner(Session session, IWindow client, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags, int seq,
            int lastSyncSeqId, ClientWindowFrames outFrames,
            MergedConfiguration outMergedConfiguration, SurfaceControl outSurfaceControl,
            InsetsState outInsetsState, InsetsSourceControl.Array outActiveControls,
            Bundle outBundle, WindowRelayoutResult outRelayoutResult) {
            synchronized (mGlobalLock) {
              // 獲取WindowState
              final WindowState win = windowForClientLocked(session, client, false);
              ...
            win.setRequestedSize(requestedWidth, requestedHeight);
            displayPolicy.adjustWindowParamsLw(win, attrs);
          }
}

DisplayContent:表示一個顯示設(shè)備的內(nèi)容,負(fù)責(zé)管理窗口集合及其布局。
DisplayPolicy:提供窗口布局和行為的策略,定義系統(tǒng)裝飾區(qū)域和窗口顯示規(guī)則。

問題

1.布局外點(diǎn)擊事件處理
2.動畫卡頓,每個手機(jī)位置消耗的時間不一直,

WindowManager.LayoutParams 中的 type 定義了窗口的類型。不同類型的窗口具有不同的行為和權(quán)限。常見的窗口類型包括:

TYPE_APPLICATION:標(biāo)準(zhǔn)應(yīng)用窗口。
TYPE_APPLICATION_OVERLAY:顯示在應(yīng)用窗口上層的窗口。
TYPE_SYSTEM_ALERT:系統(tǒng)級別的警告窗口,通常用于顯示系統(tǒng)通知。
TYPE_SYSTEM_OVERLAY:系統(tǒng)級別的覆蓋層窗口。
TYPE_TOAST:用于顯示 Toast 消息的窗口。

問題1

常用的 WindowManager.LayoutParams.flags 標(biāo)志
  1. FLAG_NOT_FOCUSABLE
    作用:使窗口無法獲得焦點(diǎn)。通常用于非交互性窗口,如懸浮窗。
    使用場景:當(dāng)你希望一個窗口顯示在屏幕上,但不希望它攔截用戶的輸入或焦點(diǎn)(如懸浮窗、聊天氣泡等)時,可以使用這個標(biāo)志。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE;
  2. FLAG_KEEP_SCREEN_ON
    作用:讓窗口的內(nèi)容保持屏幕常亮,防止屏幕進(jìn)入休眠狀態(tài)。
    使用場景:適用于需要長時間顯示內(nèi)容的窗口,如視頻播放、實(shí)時地圖、電子書閱讀器等。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON;
  3. FLAG_FULLSCREEN
    作用:使窗口占據(jù)整個屏幕,隱藏狀態(tài)欄。
    使用場景:用于全屏顯示的場景,如視頻播放器、游戲窗口等。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_FULLSCREEN;
  4. FLAG_DIM_BEHIND
    作用:使窗口后面的區(qū)域變暗,通常用于對話框或提示窗口。
    使用場景:用于當(dāng)窗口打開時,暗化背后的內(nèi)容。例如,當(dāng)彈出對話框時,背景通常會變暗以增強(qiáng)焦點(diǎn)效果。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_DIM_BEHIND;
  5. FLAG_LAYOUT_IN_SCREEN
    作用:讓窗口的內(nèi)容完全填充整個屏幕。
    使用場景:當(dāng)你希望窗口內(nèi)容占滿整個屏幕時,通常和 FLAG_NOT_FOCUSABLE 一起使用。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
  6. FLAG_LAYOUT_INSET_DECOR
    作用:使窗口的布局考慮到屏幕的裝飾區(qū)域(如狀態(tài)欄、導(dǎo)航欄等),即窗口內(nèi)容不會延伸到這些區(qū)域。
    使用場景:如果你希望確保窗口內(nèi)容不覆蓋系統(tǒng)的裝飾區(qū)域(例如狀態(tài)欄或?qū)Ш綑冢?,可以使用此?biāo)志。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_LAYOUT_INSET_DECOR;
  7. FLAG_ALT_FOCUSABLE_IM
    作用:使窗口可以獲得焦點(diǎn),并且輸入法能夠被自動彈出。通常與 FLAG_NOT_FOCUSABLE 配合使用。
    使用場景:適用于需要輸入框但不需要立即獲得焦點(diǎn)的窗口。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_ALT_FOCUSABLE_IM;
  8. FLAG_NOT_TOUCHABLE
    作用:使窗口不可觸摸,用戶無法與該窗口交互。
    使用場景:當(dāng)你希望展示一個窗口但不希望用戶進(jìn)行任何交互時,使用這個標(biāo)志。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
  9. FLAG_NOT_TOUCH_MODAL
    作用:使窗口在接收觸摸事件時不會阻止下層窗口接收觸摸事件。通常與 FLAG_NOT_FOCUSABLE 一起使用,允許下層窗口繼續(xù)接受觸摸事件。
    使用場景:當(dāng)你希望讓某個窗口透明并且不會干擾用戶與下層窗口的交互時。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL;
  10. FLAG_WATCH_OUTSIDE_TOUCH
    作用:如果窗口外部被觸摸,會觸發(fā)窗口的 onTouchEvent()。
    使用場景:當(dāng)你需要監(jiān)控用戶點(diǎn)擊窗口之外的區(qū)域時使用。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH;
  11. FLAG_SHOW_WHEN_LOCKED
    作用:允許窗口在鎖屏狀態(tài)下顯示。通常用于顯示某些系統(tǒng)提示或應(yīng)用通知。
    使用場景:當(dāng)你希望在鎖屏?xí)r展示窗口內(nèi)容時(例如顯示緊急通知)。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED;
  12. FLAG_TURN_SCREEN_ON
    作用:讓屏幕在顯示窗口時自動點(diǎn)亮(如果當(dāng)前屏幕關(guān)閉)。
    使用場景:在需要顯示重要窗口時,自動點(diǎn)亮屏幕。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON;
  13. FLAG_SECURE
    作用:保護(hù)窗口內(nèi)容,防止其被截圖或錄屏。屏幕截圖、錄制、或者內(nèi)容截取將被禁用。
    使用場景:通常用于保護(hù)敏感信息,如金融應(yīng)用、支付窗口等。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_SECURE;
  14. FLAG_SCALED
    作用:允許窗口根據(jù)設(shè)備的顯示比例進(jìn)行縮放。
    使用場景:如果應(yīng)用的窗口大小不適配設(shè)備的分辨率,可以使用這個標(biāo)志來自動調(diào)整窗口的顯示比例。
    java
    復(fù)制代碼
    params.flags = WindowManager.LayoutParams.FLAG_SCALED;
  15. FLAG_TOUCHABLE
    作用:使窗口可以接收觸摸事件(通常默認(rèn)會啟用)
layoutParams.flags =
                layoutParams.flags or WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH

this.setOnTouchListener(object : OnTouchListener {
            @SuppressLint("WrongConstant")
            override fun onTouch(v: View?, event: MotionEvent?): Boolean {
                if (event?.action == MotionEvent.ACTION_OUTSIDE) {
                
                    return true
                }
                return false
            }

        })

問題2.

 public void setCanPlayMoveAnimation(boolean enable) {
            if (enable) {
                privateFlags &= ~PRIVATE_FLAG_NO_MOVE_ANIMATION;
            } else {
                privateFlags |= PRIVATE_FLAG_NO_MOVE_ANIMATION;
            }
        }
   public boolean canPlayMoveAnimation() {
            return (privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0;
        } 

WindowState
frameworks/base/services/core/java/com/android/server/wm/WindowState.java

void handleWindowMovedIfNeeded() {
...
canPlayMoveAnimation()
}
   private boolean canPlayMoveAnimation() {
        // During the transition from pip to fullscreen, the activity windowing mode is set to
        // fullscreen at the beginning while the task is kept in pinned mode. Skip the move
        // animation in such case since the transition is handled in SysUI.
        final boolean hasMovementAnimation = getTask() == null
                ? getWindowConfiguration().hasMovementAnimations()
                : getTask().getWindowConfiguration().hasMovementAnimations();
        return mToken.okToAnimate()
                && (mAttrs.privateFlags & PRIVATE_FLAG_NO_MOVE_ANIMATION) == 0
                && !isDragResizing()
                && hasMovementAnimation
                && !mWinAnimator.mLastHidden
                && !mSeamlesslyRotated;
    }

frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

private final Consumer<WindowState> mApplySurfaceChangesTransaction = w -> {
     WindowState.handleWindowMovedIfNeeded()
}

解決方法:
PRIVATE_FLAG_NO_MOVE_ANIMATION 永遠(yuǎn)不要為窗口的位置變化制作動畫。在API 34 生效, minSdk 28 只能使用反射處理

  val className = "android.view.WindowManager\$LayoutParams"
            try {
                val layoutParamsClass = Class.forName(className)
                val privateFlags = layoutParamsClass.getField("privateFlags")
                val noAnim = layoutParamsClass.getField("PRIVATE_FLAG_NO_MOVE_ANIMATION")
                var privateFlagsValue = privateFlags.getInt(layoutParams)
                val noAnimFlag = noAnim.getInt(layoutParams)
                privateFlagsValue = privateFlagsValue or noAnimFlag
                privateFlags.setInt(layoutParams, privateFlagsValue)
            } catch (e: Exception) {
                Log.e("IMInterPhoneFloatingWindowManager", " IMInterPhoneFloatingWindowManager init noAnim error : ${e.localizedMessage}")
            }

設(shè)置該窗口上位置變化是否可以播放動畫。如果禁用,窗口將立即移動到新位置,而不會產(chǎn)生動畫。

總結(jié)

1.窗口添加流程

調(diào)用 WindowManager.addView,最終調(diào)用到 WindowManagerGlobal.addView。
創(chuàng)建 ViewRootImpl,將視圖綁定到其上。
將窗口的 View、ViewRootImpl 和布局參數(shù)存儲到全局列表中。
調(diào)用系統(tǒng)服務(wù) WindowManagerService 完成窗口的實(shí)際添加。

2.窗口更新流程

調(diào)用 WindowManager.updateViewLayout,最終調(diào)用到 WindowManagerGlobal.updateViewLayout。
根據(jù)視圖查找到對應(yīng)的 ViewRootImpl。
更新窗口的布局參數(shù),通過 WindowManagerService 通知系統(tǒng)更新窗口。

3.窗口移除流程

調(diào)用 WindowManager.removeView,最終調(diào)用到 WindowManagerGlobal.removeView。
找到視圖對應(yīng)的 ViewRootImpl,銷毀窗口并釋放資源。
從全局列表中移除相關(guān)的視圖和布局參數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容