Android 12系统源码_SystemUI(八)SystemUIVisibility属性

前言

在Android系统中,很多应用都需要根据具体情况来控制状态栏和导航栏的显示和隐藏,又或者将状态栏透明,实现诸如沉浸式、全面屏灯效果,而要实现这些效果,都离不开SystemUIVisibility属性。由于SystemUIVisibilityy属性主要用来控制系统状态栏和导航栏的行为,而状态栏和导航栏都属于SystemUI模块的StatusBar,所以SystemUIVisibility属性的消费者肯定包含StatusBar。另外当状态栏和导航栏发生变化的时候,窗口的布局一般也会跟着发生变化,这就意味着窗口管理者PhoneWindowManager肯定也要消费SystemUIVisibility属性。本篇文章我们就来具体分析一下和这个属性有关的代码。

一、SystemUIVisibility属性常见常见取值

1、为窗口设置SystemUIVisibility属性的方式有两种,一种是直接在窗口的WindowManager.LayoutParams对象的systemUiVisibility属性上进行设置,并通过WindowManager.updateViewLayout()方法使其生效。

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

public interface WindowManager extends ViewManager {
    
    
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    
    
    	//隐藏窗口的所有装饰,比如状态栏和导航栏
        public static final int FLAG_FULLSCREEN      = 0x00000400;

		//控制窗口状态栏、导航栏的显示和隐藏
        public int systemUiVisibility;
    }
}    

2、另一种是在一个已经显示在窗口上的控件中调用setSystemUiVisibility方法,传入如下属性。

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

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
//隐藏导航栏
public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
//隐藏状态栏
public static final int SYSTEM_UI_FLAG_FULLSCREEN = 0x00000004;
}

这种方式最终影响的其实是窗口的WindowManager.LayoutParams对象的subtreeSystemUiVisibility属性。

public interface WindowManager extends ViewManager {
    
    
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    
    
		//控制窗口状态栏、导航栏的显示和隐藏
        public int subtreeSystemUiVisibility;
    }
}    

窗口的状态栏导航栏显示与否,最终其实是受以上两个属性共同影响的。接下来我们具体来分析一下View的setSystemUiVisibility方法是如何生效的。

二、View的setSystemUiVisibility方法调用流程

1、View的setSystemUiVisibility方法如下所示。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
        
    int mSystemUiVisibility;
    protected ViewParent mParent;
    
    public void setSystemUiVisibility(int visibility) {
    
    
        if (visibility != mSystemUiVisibility) {
    
    
            mSystemUiVisibility = visibility;//保存SystemUIVisibility属性
            if (mParent != null && mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
    
    
                mParent.recomputeViewAttributes(this);//通知父控件子控件属性发生了变化
            }
        }
    }
}

setSystemUiVisibility方法首先将属性赋值给mSystemUiVisibility,然后会调用父控件的recomputeViewAttributes方法,通知父控件子控件属性发生了变化。ViewParent是一个接口,在Android中有两个类实现了这个接口,它们分别是ViewGroup和ViewRootImpl。

2、ViewGroup和ViewRootImpl和recomputeViewAttributes方法相关的代码如下所示。

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

public abstract class ViewGroup extends View implements ViewParent, ViewManager {
    
    
    @Override
    public void recomputeViewAttributes(View child) {
    
    
        if (mAttachInfo != null && !mAttachInfo.mRecomputeGlobalAttributes) {
    
    
            ViewParent parent = mParent;
            if (parent != null) parent.recomputeViewAttributes(this);
        }
    }
}

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

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    
    
    final View.AttachInfo mAttachInfo;
    @Override
    public void recomputeViewAttributes(View child) {
    
    
        checkThread();//检测线程是不是UI线程
        if (mView == child) {
    
    
            mAttachInfo.mRecomputeGlobalAttributes = true;//标记需要重新计算本地属性
            if (!mWillDrawSoon) {
    
    
                scheduleTraversals();//进一步调用scheduleTraversals方法。
            }
        }
    }
}

结合Android 9.0系统源码_窗口管理(二)WindowManager对窗口的管理过程,我们知道包括Activity的跟布局DecorView在内的任何View,WindowManager在将它添加到窗口上的过程中,最终都会创建一个ViewRootImpl,并将View设置给ViewRootImpl,这样根View的父类就变成了ViewRootImpl。这就意味着不管任何子View调用recomputeViewAttributes方法,最终所触发的都是ViewRootImpl的recomputeViewAttributes,而ViewRootImpl会进一步调用scheduleTraversals方法。

3、ViewRootImpl和scheduleTraversals方法相关的代码如下所示。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    
        
     final Choreographer mChoreographer;//编舞者
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();//回调对象

    void scheduleTraversals() {
    
    
        if (!mTraversalScheduled) {
    
    
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
   final class TraversalRunnable implements Runnable {
    
    
        @Override
        public void run() {
    
    
            doTraversal();//继续执行doTraversal
        }
    }

    void doTraversal() {
    
    
        if (mTraversalScheduled) {
    
    
            mTraversalScheduled = false;
            mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

            if (mProfile) {
    
    
                Debug.startMethodTracing("ViewAncestor");
            }
			//执行performTraversals
            performTraversals();

            if (mProfile) {
    
    
                Debug.stopMethodTracing();
                mProfile = false;
            }
        }
    }
}

scheduleTraversals方法会为编舞者对象设置回调,最终会等待系统垂直刷新同步信号,回调TraversalRunnable对象的run方法,该方法会调用doTraversal方法,然后进一步调用performTraversals方法。

4、ViewRootImpl的performTraversals方法代码逻辑非常多,这里只列出了我们需要关注的代码。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    
        
    final IWindowSession mWindowSession;//和WMS通信的Binder对象,具体为Session对象
	public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
    final View.AttachInfo mAttachInfo;//控件信息
    
    private void performTraversals() {
    
    
        final View host = mView;
        if (host == null || !mAdded) {
    
    
            return;
        }
        if (mWaitForBlastSyncComplete) {
    
    
            mRequestedTraverseWhilePaused = true;
            return;
        }

        mIsInTraversal = true;
        mWillDrawSoon = true;
        boolean windowSizeMayChange = false;
        WindowManager.LayoutParams lp = mWindowAttributes;//将当前窗口的最新属性赋值给lp
        int desiredWindowWidth;
        int desiredWindowHeight;
        final int viewVisibility = getHostVisibility();
        final boolean viewVisibilityChanged = !mFirst
                && (mViewVisibility != viewVisibility || mNewSurfaceNeeded
                || mAppVisibilityChanged);
        mAppVisibilityChanged = false;
        final boolean viewUserVisibilityChanged = !mFirst &&
                ((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
        WindowManager.LayoutParams params = null;
        ...代码省略...
        //收集mView的属性,判断是否需要更新params
        if (collectViewAttributes()) {
    
    
            params = lp;
        }
        ...代码省略...
        //此方法最终会触发WindowManagerService的relayoutWindow方法
		relayoutWindow(params, viewVisibility, insetsPending);
       	...代码省略...  
       	//测量
     	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
     	...代码省略...  
     	//布局
     	performLayout(lp, mWidth, mHeight);
     	...代码省略...  
     	//绘制
        performDraw();
        ...代码省略...  
     }

    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
    
    
           //调用IWindowSession的relayout方法,该方法最终会触发WindowManagerService的relayoutWindow方法
        int relayoutResult = mWindowSession.relayout(mWindow, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize);        
     }
  }

performTraversals首先调用collectViewAttributes方法收集所有子View的属性,然后调用relayoutWindow方法,该方法最终会触发WindowManagerService的relayoutWindow方法,然后回继续调用触发View测量的performMeasure方法,触发View布局的performLayout方法和触发View绘制的performDraw方法。

三、获取最新的SystemUIVisibility属性

1、ViewRootImpl的collectViewAttributes方法是一个很关键的方法,此方法会重新计算最新的SystemUIVisibility属性。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    

	public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
    final View.AttachInfo mAttachInfo;//控件信息
    
    private boolean collectViewAttributes() {
    
    
  		//判断是否需要重新计算本地属性
        if (mAttachInfo.mRecomputeGlobalAttributes) {
    
    
            //Log.i(mTag, "Computing view hierarchy attributes!");
            mAttachInfo.mRecomputeGlobalAttributes = false;
            boolean oldScreenOn = mAttachInfo.mKeepScreenOn;
            mAttachInfo.mKeepScreenOn = false;
            //清空已经存在的SystemUiVisibility属性
            mAttachInfo.mSystemUiVisibility = 0;
            mAttachInfo.mHasSystemUiListeners = false;
            //重新获取窗口视图mView最新的的SystemUI属性,赋值给mAttachInfo
            mView.dispatchCollectViewAttributes(mAttachInfo, 0);
         	...代码省略...
        }
        return false;
    }
 }

collectViewAttributes首先会清空当前窗口视图mView已经存在的SystemUiVisibility属性,然后调用View的dispatchCollectViewAttributes方法重新获取最新的的SystemUiVisibility属性。

2、View的dispatchCollectViewAttributes方法如下所示。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
    
    int mSystemUiVisibility;

    void performCollectViewAttributes(AttachInfo attachInfo, int visibility) {
    
    
        if ((visibility & VISIBILITY_MASK) == VISIBLE) {
    
    
            if ((mViewFlags & KEEP_SCREEN_ON) == KEEP_SCREEN_ON) {
    
    
                attachInfo.mKeepScreenOn = true;
            }
            //将最新的systemuivisiblity赋予AttachInfo的mSystemUiVisibility 属性
            attachInfo.mSystemUiVisibility |= mSystemUiVisibility;
            //设置最新的SystemUiVisibility监听对象,如果不为空,则将AttachInfo的mHasSystemUiListeners属性设置为true。
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnSystemUiVisibilityChangeListener != null) {
    
    
                attachInfo.mHasSystemUiListeners = true;
            }
        }
    }

}    

3、接着看ViewRootImpl的collectViewAttributes方法。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    

	public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
    final View.AttachInfo mAttachInfo;//控件信息
    
    private boolean collectViewAttributes() {
    
    
  		//判断是否需要重新计算本地属性
        if (mAttachInfo.mRecomputeGlobalAttributes) {
    
    
         	...代码省略...
            //移除被禁用的SystemUiVisibility属性
            mAttachInfo.mSystemUiVisibility &= ~mAttachInfo.mDisabledSystemUiVisibility;
            //让params引用指向mWindowAttributes对象
            WindowManager.LayoutParams params = mWindowAttributes;
            mAttachInfo.mSystemUiVisibility |= getImpliedSystemUiVisibility(params);
            mCompatibleVisibilityInfo.globalVisibility =
                    (mCompatibleVisibilityInfo.globalVisibility & ~View.SYSTEM_UI_FLAG_LOW_PROFILE)
                            | (mAttachInfo.mSystemUiVisibility & View.SYSTEM_UI_FLAG_LOW_PROFILE);
            dispatchDispatchSystemUiVisibilityChanged(mCompatibleVisibilityInfo);
            if (mAttachInfo.mKeepScreenOn != oldScreenOn
                    || mAttachInfo.mSystemUiVisibility != params.subtreeSystemUiVisibility
                    || mAttachInfo.mHasSystemUiListeners != params.hasSystemUiListeners) {
    
    
                applyKeepScreenOnFlag(params);
                //将重新获取的窗口视图mView的SystemUiVisibility保存到窗口的LayoutParams属性中
                params.subtreeSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
                params.hasSystemUiListeners = mAttachInfo.mHasSystemUiListeners;
                //调用View的dispatchWindowSystemUiVisiblityChanged方法
                mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
                return true;
            }
        }
        return false;
    }
 }

在重新获得mView的SystemUiVisibility属性之后,首先会从该属性中移除被禁用的SystemUiVisibility属性,然后让params引用指向mWindowAttributes对象,并将重新获取的保存在mAttachInfo对象中的SystemUiVisibility属性保存到当前窗口的LayoutParams属性中,最后会调用当前View的dispatchWindowSystemUiVisiblityChanged方法。

4、View的dispatchWindowSystemUiVisiblityChanged方法如下所示。

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    
    @Deprecated
    public void dispatchWindowSystemUiVisiblityChanged(int visible) {
    
    
        onWindowSystemUiVisibilityChanged(visible);//调用onWindowSystemUiVisibilityChanged方法
    }
    public void onWindowSystemUiVisibilityChanged(int visible) {
    
    
    	//默认为空实现
    }
}

该方法会进一步调用onWindowSystemUiVisibilityChanged方法,onWindowSystemUiVisibilityChanged方法默认为空实现,但是如果当前mView为DecorView时则不同,DecorView实现了此方法。

四、DecorView更新状态栏和导航栏背景颜色

1、DecorView的onWindowSystemUiVisibilityChanged方法如下所示。

frameworks/base/core/java/com/android/internal/policy/DecorView.java

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    
    

    @Override
    public void onWindowSystemUiVisibilityChanged(int visible) {
    
    
    	//调用updateColorViews方法
        updateColorViews(null /* insets */, true /* animate */);
        updateDecorCaptionStatus(getResources().getConfiguration());
        if (mStatusGuard != null && mStatusGuard.getVisibility() == VISIBLE) {
    
    
            updateStatusGuardColor();
        }
    }

}

onWindowSystemUiVisibilityChanged方法会调用一个updateColorViews这个关键方法。
2、updateColorViews会调用updateColorViewInt方法更新导航栏和状态栏的背景颜色。

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    
    
    
    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
    
    
    		...代码省略...
            //更新导航栏颜色,mNavigationColorViewState为导航栏的相关筛选条件
            Log.d("SystemBar", "updateColorViews mNavigationColorViewState="+mNavigationColorViewState);
            updateColorViewInt(mNavigationColorViewState, calculateNavigationBarColor(appearance),
                    mWindow.mNavigationBarDividerColor, navBarSize,
                    navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
                    0 /* sideInset */, animate && !disallowAnimate,
                    mForceWindowDrawsBarBackgrounds, controller);
            boolean oldDrawLegacy = mDrawLegacyNavigationBarBackground;
            mDrawLegacyNavigationBarBackground = mNavigationColorViewState.visible
                    && (mWindow.getAttributes().flags & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0;
            if (oldDrawLegacy != mDrawLegacyNavigationBarBackground) {
    
    
                if (viewRoot != null) {
    
    
                    viewRoot.requestInvalidateRootRenderNode();
                }
            }
    		...代码省略...
            boolean statusBarNeedsRightInset = navBarToRightEdge
                    && mNavigationColorViewState.present;
            boolean statusBarNeedsLeftInset = navBarToLeftEdge
                    && mNavigationColorViewState.present;
            int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
                    : statusBarNeedsLeftInset ? mLastLeftInset : 0;
            int statusBarColor = calculateStatusBarColor(appearance);
            //更新状态栏颜色,mStatusColorViewState为状态栏的相关筛选条件
            Log.d("SystemBar", "updateColorViews mStatusColorViewState="+mStatusColorViewState);
            updateColorViewInt(mStatusColorViewState, statusBarColor, 0,
                    mLastTopInset, false /* matchVertical */, statusBarNeedsLeftInset,
                    statusBarSideInset, animate && !disallowAnimate,
                    mForceWindowDrawsBarBackgrounds, controller);
    		...代码省略...
    }
        
}

关于DecorView更新状态栏、导航栏背景颜色的过程,后续篇章会具体分析。

五、WindowManagerService的relayoutWindow方法

1、重新回到第二节第4步ViewRootImpl的performTraversals方法中。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
    
    
        
    final IWindowSession mWindowSession;//和WMS通信的Binder对象,具体为Session对象
	public final WindowManager.LayoutParams mWindowAttributes = new WindowManager.LayoutParams();//窗口属性
    final View.AttachInfo mAttachInfo;//控件信息
    
    private void performTraversals() {
    
    
        ...代码省略...
        //收集mView的属性,判断是否需要更新params
        if (collectViewAttributes()) {
    
    
            params = lp;
        }
        ...代码省略...
        //此方法最终会触发WindowManagerService的relayoutWindow方法
		relayoutWindow(params, viewVisibility, insetsPending);
       	...代码省略...  
       	//测量
     	performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
     	...代码省略...  
     	//布局
     	performLayout(lp, mWidth, mHeight);
     	...代码省略...  
     	//绘制
        performDraw();
        ...代码省略...  
     }

    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
    
    
           //调用IWindowSession的relayout方法,该方法最终会触发WindowManagerService的relayoutWindow方法
        int relayoutResult = mWindowSession.relayout(mWindow, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
                mTempControls, mSurfaceSize);        
     }
  }

在调用collectViewAttributes获取最新的systemUIVisibiliy属性之后,会调用relayoutWindow方法,该方法进一步调用IWindowSession的relayout方法,IWindowSession的具体实现类为Session。
2、Session的relayout方法如下所示。

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

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient {
    
    

    final WindowManagerService mService;
    
    @Override
    public int relayout(IWindow window, WindowManager.LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewFlags, int flags, long frameNumber,
            ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
    
    
        int res = mService.relayoutWindow(this, window, attrs,
                requestedWidth, requestedHeight, viewFlags, flags, frameNumber,
                outFrames, mergedConfiguration, outSurfaceControl, outInsetsState,
                outActiveControls, outSurfaceSize);
        return res;
    }
}

relayout方法会进一步调用WindowManagerService的relayoutWindow方法。

3、WindowManagerService的relayoutWindow方法如下所示。

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

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    
    
        
    public int relayoutWindow(Session session, IWindow client, LayoutParams attrs//窗口属性
                              , int requestedWidth, int requestedHeight, int viewVisibility//根View控件是否可见
                              , int flags, long frameNumber, ClientWindowFrames outFrames, MergedConfiguration mergedConfiguration,
                              SurfaceControl outSurfaceControl, InsetsState outInsetsState,
                              InsetsSourceControl[] outActiveControls, Point outSurfaceSize) {
    
    
            ...代码省略...
           final WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
    
    
                return 0;
            }
            final DisplayContent displayContent = win.getDisplayContent();
            final DisplayPolicy displayPolicy = displayContent.getDisplayPolicy();
            ...代码省略...
            //是否应该重新布局
            final boolean shouldRelayout = viewVisibility == View.VISIBLE &&
                    (win.mActivityRecord == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
                            || win.mActivityRecord.isClientVisible());
           ...代码省略...
           if (shouldRelayout) {
    
    
                Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
                //标记
                result = win.relayoutVisibleWindow(result);
                if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
    
    
                    //焦点可能变化了
                    focusMayChange = true;
                }
                if (win.mAttrs.type == TYPE_INPUT_METHOD
                        && displayContent.mInputMethodWindow == null) {
    
    
                    displayContent.setInputMethodWindowLocked(win);
                    imMayMove = true;
                }
                win.adjustStartingWindowFlags();
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            }  
            ...代码省略... 
            if (focusMayChange) {
    
    
                //relayoutWindow add by syl
                Log.d(TAG, "SystemBar: WindowManagerService_relayoutWindow");
                if (updateFocusedWindowLocked(UPDATE_FOCUS_NORMAL, true /*updateInputWindows*/)) {
    
    
                    imMayMove = false;
                }
            }   
            ...代码省略...           
	}
}

relayoutWindow方法会调用一个关键方法updateFocusedWindowLocked。

4、WindowManagerService的updateFocusedWindowLocked方法如下所示。

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
    
    
   
   RootWindowContainer mRoot;

    //更新窗口焦点的变化
    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
    
    
        Log.d("SystemBar", "WindowManagerService: updateFocusedWindowLocked");
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
        boolean changed = mRoot.updateFocusedWindowLocked(mode, updateInputWindows);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        return changed;
    }
}

此方法会进一步调用RootWindowContainer的updateFocusedWindowLocked方法。

5、RootWindowContainer的updateFocusedWindowLocked方法如下所示。
/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java

class RootWindowContainer extends WindowContainer<DisplayContent>
        implements DisplayManager.DisplayListener {
    
    
    // 当窗口焦点发生变化的时候,更新窗口焦点的变化
    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
    
    
        mTopFocusedAppByProcess.clear();
        boolean changed = false;
        int topFocusedDisplayId = INVALID_DISPLAY;
        for (int i = mChildren.size() - 1; i >= 0; --i) {
    
    
        	//循环遍历DisplayContent的updateFocusedWindowLocked方法
            final DisplayContent dc = mChildren.get(i);
            changed |= dc.updateFocusedWindowLocked(mode, updateInputWindows, topFocusedDisplayId);
            final WindowState newFocus = dc.mCurrentFocus;
            if (newFocus != null) {
    
    
                final int pidOfNewFocus = newFocus.mSession.mPid;
                if (mTopFocusedAppByProcess.get(pidOfNewFocus) == null) {
    
    
                    mTopFocusedAppByProcess.put(pidOfNewFocus, newFocus.mActivityRecord);
                }
                if (topFocusedDisplayId == INVALID_DISPLAY) {
    
    
                    topFocusedDisplayId = dc.getDisplayId();
                }
            } else if (topFocusedDisplayId == INVALID_DISPLAY && dc.mFocusedApp != null) {
    
    
                // The top-most display that has a focused app should still be the top focused
                // display even when the app window is not ready yet (process not attached or
                // window not added yet).
                topFocusedDisplayId = dc.getDisplayId();
            }
        }
        if (topFocusedDisplayId == INVALID_DISPLAY) {
    
    
            topFocusedDisplayId = DEFAULT_DISPLAY;
        }
        if (mTopFocusedDisplayId != topFocusedDisplayId) {
    
    
            mTopFocusedDisplayId = topFocusedDisplayId;
            mWmService.mInputManager.setFocusedDisplay(topFocusedDisplayId);
            mWmService.mPolicy.setTopFocusedDisplay(topFocusedDisplayId);
            mWmService.mAccessibilityController.setFocusedDisplay(topFocusedDisplayId);
            ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "New topFocusedDisplayId=%d", topFocusedDisplayId);
        }
        return changed;
    }

6、DisplayContent 的updateFocusedWindowLocked方法如下所示。

class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
    
    

    //当窗口焦点发生变化的时候,更新窗口焦点的变化 
    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows,
            int topFocusedDisplayId) {
    
    
        WindowState newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
        if (mCurrentFocus == newFocus) {
    
    
            return false;
        }
        boolean imWindowChanged = false;
        final WindowState imWindow = mInputMethodWindow;
        if (imWindow != null) {
    
    
            final WindowState prevTarget = mImeLayeringTarget;
            final WindowState newTarget = computeImeTarget(true /* updateImeTarget*/);
            imWindowChanged = prevTarget != newTarget;

            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
                    && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
    
    
                assignWindowLayers(false /* setLayoutNeeded */);
            }

            if (imWindowChanged) {
    
    
                mWmService.mWindowsChanged = true;
                setLayoutNeeded();
                newFocus = findFocusedWindowIfNeeded(topFocusedDisplayId);
            }
        }

        ProtoLog.d(WM_DEBUG_FOCUS_LIGHT, "Changing focus from %s to %s displayId=%d Callers=%s",
                mCurrentFocus, newFocus, getDisplayId(), Debug.getCallers(4));
        final WindowState oldFocus = mCurrentFocus;
        mCurrentFocus = newFocus;

        if (newFocus != null) {
    
    
            mWinAddedSinceNullFocus.clear();
            mWinRemovedSinceNullFocus.clear();

            if (newFocus.canReceiveKeys()) {
    
    
                // Displaying a window implicitly causes dispatching to be unpaused.
                // This is to protect against bugs if someone pauses dispatching but
                // forgets to resume.
                newFocus.mToken.paused = false;
            }
        }

        getDisplayPolicy().focusChangedLw(oldFocus, newFocus);

        if (imWindowChanged && oldFocus != mInputMethodWindow) {
    
    
            // Focus of the input method window changed. Perform layout if needed.
            if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
    
    
            	//准备重新布局
                performLayout(true /*initial*/,  updateInputWindows);
            } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
    
    
                // Client will do the layout, but we need to assign layers
                // for handleNewWindowLocked() below.
                assignWindowLayers(false /* setLayoutNeeded */);
            }
        }

        if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
    
    
            // If we defer assigning layers, then the caller is responsible for doing this part.
            getInputMonitor().setInputFocusLw(newFocus, updateInputWindows);
        }

        adjustForImeIfNeeded();

        // We may need to schedule some toast windows to be removed. The toasts for an app that
        // does not have input focus are removed within a timeout to prevent apps to redress
        // other apps' UI.
        scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);

        if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
    
    
            pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
        }

        // Notify the accessibility manager for the change so it has the windows before the newly
        // focused one starts firing events.
        // TODO(b/151179149) investigate what info accessibility service needs before input can
        // dispatch focus to clients.
        if (mWmService.mAccessibilityController.hasCallbacks()) {
    
    
            mWmService.mH.sendMessage(PooledLambda.obtainMessage(
                    this::updateAccessibilityOnWindowFocusChanged,
                    mWmService.mAccessibilityController));
        }

        return true;
    }

}

7、DisplayContent的performLayout方法如下所示。

class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
    
    
    //对视图进行布局,计算窗口布局的宽和高 add by syl
    void performLayout(boolean initial, boolean updateInputWindows) {
    
    
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "performLayout");
        try {
    
    
            Log.d("SystemBar", "DisplayContent_performLayout");
            performLayoutNoTrace(initial, updateInputWindows);
        } finally {
    
    
            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
        }
    }
   
}    

8、DisplayContent的performLayoutNoTrace方法如下所示。

class DisplayContent extends RootDisplayArea implements WindowManagerPolicy.DisplayContentInfo {
    
    

    private final Consumer<WindowState> mPerformLayout = w -> {
    
    
        // Don't do layout of a window if it is not visible, or soon won't be visible, to avoid
        // wasting time and funky changes while a window is animating away.
        final boolean gone = w.isGoneForLayout();

        if (DEBUG_LAYOUT && !w.mLayoutAttached) {
    
    
            Slog.v(TAG, "1ST PASS " + w + ": gone=" + gone + " mHaveFrame=" + w.mHaveFrame
                    + " mLayoutAttached=" + w.mLayoutAttached
                    + " config reported=" + w.isLastConfigReportedToClient());
            final ActivityRecord activity = w.mActivityRecord;
            if (gone) Slog.v(TAG, "  GONE: mViewVisibility=" + w.mViewVisibility
                    + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
                    + " parentHidden=" + w.isParentWindowHidden());
            else Slog.v(TAG, "  VIS: mViewVisibility=" + w.mViewVisibility
                    + " mRelayoutCalled=" + w.mRelayoutCalled + " visible=" + w.mToken.isVisible()
                    + " visibleRequested=" + (activity != null && activity.mVisibleRequested)
                    + " parentHidden=" + w.isParentWindowHidden());
        }

        // If this view is GONE, then skip it -- keep the current frame, and let the caller know
        // so they can ignore it if they want.  (We do the normal layout for INVISIBLE windows,
        // since that means "perform layout as normal, just don't display").
        if ((!gone || !w.mHaveFrame || w.mLayoutNeeded) && !w.mLayoutAttached) {
    
    
            if (mTmpInitial) {
    
    
                w.resetContentChanged();
            }
            w.mSurfacePlacementNeeded = true;
            w.mLayoutNeeded = false;
            w.prelayout();
            final boolean firstLayout = !w.isLaidOut();
            Log.d("SystemBar", "DisplayContent_mPerformLayout");
            getDisplayPolicy().layoutWindowLw(w, null, mDisplayFrames);
            w.mLayoutSeq = mLayoutSeq;

            // If this is the first layout, we need to initialize the last frames and inset values,
            // as otherwise we'd immediately cause an unnecessary resize.
            if (firstLayout) {
    
    
                // The client may compute its actual requested size according to the first layout,
                // so we still request the window to resize if the current frame is empty.
                if (!w.getFrame().isEmpty()) {
    
    
                    w.updateLastFrames();
                }
                w.onResizeHandled();
                w.updateLocationInParentDisplayIfNeeded();
            }

            if (w.mActivityRecord != null) {
    
    
                w.mActivityRecord.layoutLetterbox(w);
            }

            if (DEBUG_LAYOUT) Slog.v(TAG, "  LAYOUT: mFrame=" + w.getFrame()
                    + " mContainingFrame=" + w.getContainingFrame()
                    + " mDisplayFrame=" + w.getDisplayFrame());
        }
    };

    private final Consumer<WindowState> mPerformLayoutAttached = w -> {
    
    
        if (w.mLayoutAttached) {
    
    
            if (DEBUG_LAYOUT) Slog.v(TAG, "2ND PASS " + w + " mHaveFrame=" + w.mHaveFrame
                    + " mViewVisibility=" + w.mViewVisibility
                    + " mRelayoutCalled=" + w.mRelayoutCalled);
            // If this view is GONE, then skip it -- keep the current frame, and let the caller
            // know so they can ignore it if they want.  (We do the normal layout for INVISIBLE
            // windows, since that means "perform layout as normal, just don't display").
            if ((w.mViewVisibility != GONE && w.mRelayoutCalled) || !w.mHaveFrame
                    || w.mLayoutNeeded) {
    
    
                if (mTmpInitial) {
    
    
                    //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
                    w.resetContentChanged();
                }
                w.mSurfacePlacementNeeded = true;
                w.mLayoutNeeded = false;
                w.prelayout();
                Log.d("SystemBar", "DisplayContent_mPerformLayoutAttached");
                getDisplayPolicy().layoutWindowLw(w, w.getParentWindow(), mDisplayFrames);
                w.mLayoutSeq = mLayoutSeq;
                if (DEBUG_LAYOUT) Slog.v(TAG, " LAYOUT: mFrame=" + w.getFrame()
                        + " mContainingFrame=" + w.getContainingFrame()
                        + " mDisplayFrame=" + w.getDisplayFrame());
            }
        }
    };

    //计算窗口布局的宽和高 add by syl
    private void performLayoutNoTrace(boolean initial, boolean updateInputWindows) {
    
    
        if (!isLayoutNeeded()) {
    
    
            return;
        }
        clearLayoutNeeded();

        if (DEBUG_LAYOUT) {
    
    
            Slog.v(TAG, "-------------------------------------");
            Slog.v(TAG, "performLayout: dw=" + mDisplayInfo.logicalWidth
                    + " dh=" + mDisplayInfo.logicalHeight);
        }

        int seq = mLayoutSeq + 1;
        if (seq < 0) seq = 0;
        mLayoutSeq = seq;

        mTmpInitial = initial;

        // First perform layout of any root windows (not attached to another window).
        //界定窗口的宽和高,设置监听对象 add by syl
        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

        // Now perform layout of attached windows, which usually depend on the position of the
        // window they are attached to. XXX does not deal with windows that are attached to windows
        // that are themselves attached.
        //界定窗口的宽和高,设置监听对象 add by syl
        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);

        // Window frames may have changed. Tell the input dispatcher about it.
        mInputMonitor.setUpdateInputWindowsNeededLw();
        if (updateInputWindows) {
    
    
            mInputMonitor.updateInputWindowsLw(false /*force*/);
        }
    }
}    

9、DisplayPolicy的layoutWindowLw方法如下所示。

public class DisplayPolicy {
    
    
    /**
     * 界定窗口边框的宽和高 
     * Called for each window attached to the window manager as layout is proceeding. The
     * implementation of this function must take care of setting the window's frame, either here or
     * in finishLayout().
     *
     * @param win The window being positioned.
     * @param attached For sub-windows, the window it is attached to; this
     *                 window will already have had layoutWindow() called on it
     *                 so you can use its Rect.  Otherwise null.
     * @param displayFrames The display frames.
     */
    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    
    
        Log.d("SystemBar", "DisplayPolicy_layoutWindowLw");
        if (win == mNavigationBar && !INSETS_LAYOUT_GENERALIZATION) {
    
    
            Log.d("SystemBar", "DisplayPolicy_layoutNavigationBar");
            mNavigationBarPosition = layoutNavigationBar(displayFrames,
                    mBarContentFrames.get(TYPE_NAVIGATION_BAR));
            return;
        }
        if ((win == mStatusBar && !canReceiveInput(win)) && !INSETS_LAYOUT_GENERALIZATION) {
    
    
            Log.d("SystemBar", "DisplayPolicy_layoutStatusBar");
            layoutStatusBar(displayFrames, mBarContentFrames.get(TYPE_STATUS_BAR));
            return;
        }
        if (win.mActivityRecord != null && win.mActivityRecord.mWaitForEnteringPinnedMode) {
    
    
            // Skip layout of the window when in transition to pip mode.
            return;
        }
        final WindowManager.LayoutParams attrs = win.getLayoutingAttrs(displayFrames.mRotation);

        final int type = attrs.type;
        final int fl = attrs.flags;
        final int pfl = attrs.privateFlags;
        final int sim = attrs.softInputMode;


        displayFrames = win.getDisplayFrames(displayFrames);
        final WindowFrames windowFrames = win.getLayoutingWindowFrames();

        sTmpLastParentFrame.set(windowFrames.mParentFrame);
        final Rect pf = windowFrames.mParentFrame;
        final Rect df = windowFrames.mDisplayFrame;
        windowFrames.setParentFrameWasClippedByDisplayCutout(false);

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;

        final InsetsState state = win.getInsetsState();
        if (windowFrames.mIsSimulatingDecorWindow && INSETS_LAYOUT_GENERALIZATION) {
    
    
            // Override the bounds in window token has many side effects. Directly use the display
            // frame set for the simulated layout for this case.
            computeWindowBounds(attrs, state, df, df);
        } else {
    
    
            computeWindowBounds(attrs, state, win.getBounds(), df);
        }
        if (attached == null) {
    
    
            pf.set(df);
            if ((pfl & PRIVATE_FLAG_INSET_PARENT_FRAME_BY_IME) != 0) {
    
    
                final InsetsSource source = state.peekSource(ITYPE_IME);
                if (source != null) {
    
    
                    pf.inset(source.calculateInsets(pf, false /* ignoreVisibility */));
                }
            }
        } else {
    
    
            pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrame() : df);
        }

        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
        // Ensure that windows with a DEFAULT or NEVER display cutout mode are laid out in
        // the cutout safe zone.
        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
    
    
            final boolean attachedInParent = attached != null && !layoutInScreen;
            final boolean requestedFullscreen = !win.getRequestedVisibility(ITYPE_STATUS_BAR);
            final boolean requestedHideNavigation =
                    !win.getRequestedVisibility(ITYPE_NAVIGATION_BAR);

            // TYPE_BASE_APPLICATION windows are never considered floating here because they don't
            // get cropped / shifted to the displayFrame in WindowState.
            final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
                    && type != TYPE_BASE_APPLICATION;
            final Rect displayCutoutSafeExceptMaybeBars = sTmpDisplayCutoutSafeExceptMaybeBarsRect;
            displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
            if (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES) {
    
    
                if (displayFrames.mDisplayWidth < displayFrames.mDisplayHeight) {
    
    
                    displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
                    displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                } else {
    
    
                    displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                    displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                }
            }

            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
                    && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
    
    
                // At the top we have the status bar, so apps that are
                // LAYOUT_IN_SCREEN | LAYOUT_INSET_DECOR but not FULLSCREEN
                // already expect that there's an inset there and we don't need to exclude
                // the window from that area.
                displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
            }
            if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
                    && (cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT
                    || cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES)) {
    
    
                // Same for the navigation bar.
                switch (mNavigationBarPosition) {
    
    
                    case NAV_BAR_BOTTOM:
                        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                        break;
                    case NAV_BAR_RIGHT:
                        displayCutoutSafeExceptMaybeBars.right = Integer.MAX_VALUE;
                        break;
                    case NAV_BAR_LEFT:
                        displayCutoutSafeExceptMaybeBars.left = Integer.MIN_VALUE;
                        break;
                }
            }
            if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
    
    
                // The IME can always extend under the bottom cutout if the navbar is there.
                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
            }
            // Windows that are attached to a parent and laid out in said parent already avoid
            // the cutout according to that parent and don't need to be further constrained.
            // Floating IN_SCREEN windows get what they ask for and lay out in the full screen.
            // They will later be cropped or shifted using the displayFrame in WindowState,
            // which prevents overlap with the DisplayCutout.
            if (!attachedInParent && !floatingInScreenWindow) {
    
    
                sTmpRect.set(pf);
                pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
                windowFrames.setParentFrameWasClippedByDisplayCutout(!sTmpRect.equals(pf));
            }
            // Make sure that NO_LIMITS windows clipped to the display don't extend under the
            // cutout.
            df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        }

        // TYPE_SYSTEM_ERROR is above the NavigationBar so it can't be allowed to extend over it.
        // Also, we don't allow windows in multi-window mode to extend out of the screen.
        if ((fl & FLAG_LAYOUT_NO_LIMITS) != 0 && type != TYPE_SYSTEM_ERROR
                && !win.inMultiWindowMode()) {
    
    
            df.left = df.top = -10000;
            df.right = df.bottom = 10000;
        }

        if (DEBUG_LAYOUT) Slog.v(TAG, "Compute frame " + attrs.getTitle()
                + ": sim=#" + Integer.toHexString(sim)
                + " attach=" + attached + " type=" + type
                + String.format(" flags=0x%08x", fl)
                + " pf=" + pf.toShortString() + " df=" + df.toShortString());

        if (!sTmpLastParentFrame.equals(pf)) {
    
    
            windowFrames.setContentChanged(true);
        }

        win.computeFrameAndUpdateSourceFrame(displayFrames);
        if (INSETS_LAYOUT_GENERALIZATION && attrs.type == TYPE_STATUS_BAR) {
    
    
            if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
    
    
                // Make sure that the zone we're avoiding for the cutout is at least as tall as the
                // status bar; otherwise fullscreen apps will end up cutting halfway into the status
                // bar.
                displayFrames.mDisplayCutoutSafe.top = Math.max(
                        displayFrames.mDisplayCutoutSafe.top,
                        windowFrames.mFrame.bottom);
            }
        }
    }
}

猜你喜欢

转载自blog.csdn.net/abc6368765/article/details/129859538
今日推荐