国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

Android 9系統(tǒng)源碼_SystemUI(十)SystemUIVisibility屬性

這篇具有很好參考價值的文章主要介紹了Android 9系統(tǒng)源碼_SystemUI(十)SystemUIVisibility屬性。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

前言

在Android系統(tǒng)中,很多應(yīng)用都需要根據(jù)具體情況來控制狀態(tài)欄和導(dǎo)航欄的顯示和隱藏,又或者將狀態(tài)欄透明,實現(xiàn)諸如沉浸式、全面屏燈效果,而要實現(xiàn)這些效果,都離不開SystemUIVisibility屬性。由于SystemUIVisibilityy屬性主要用來控制系統(tǒng)狀態(tài)欄和導(dǎo)航欄的行為,而狀態(tài)欄和導(dǎo)航欄都屬于SystemUI模塊的StatusBar,所以SystemUIVisibility屬性的消費者肯定包含StatusBar。另外當(dāng)狀態(tài)欄和導(dǎo)航欄發(fā)生變化的時候,窗口的布局一般也會跟著發(fā)生變化,這就意味著窗口管理者PhoneWindowManager肯定也要消費SystemUIVisibility屬性。本篇文章我們就來具體分析一下和這個屬性有關(guān)的代碼。

一、SystemUIVisibility屬性常見常見取值

1、為窗口設(shè)置SystemUIVisibility屬性的方式有兩種,一種是直接在窗口的WindowManager.LayoutParams對象的systemUiVisibility屬性上進(jìn)行設(shè)置,并通過WindowManager.updateViewLayout()方法使其生效。

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

public interface WindowManager extends ViewManager {
    public static class LayoutParams extends ViewGroup.LayoutParams implements Parcelable {
    	//隱藏窗口的所有裝飾,比如狀態(tài)欄和導(dǎo)航欄
        public static final int FLAG_FULLSCREEN      = 0x00000400;

		//控制窗口狀態(tài)欄、導(dǎo)航欄的顯示和隱藏
        public int systemUiVisibility;
    }
}    

2、另一種是在一個已經(jīng)顯示在窗口上的控件中調(diào)用setSystemUiVisibility方法,傳入如下屬性。

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

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
//隱藏導(dǎo)航欄
public static final int SYSTEM_UI_FLAG_HIDE_NAVIGATION = 0x00000002;
//隱藏狀態(tài)欄
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 {
		//控制窗口狀態(tài)欄、導(dǎo)航欄的顯示和隱藏
        public int subtreeSystemUiVisibility;
    }
}    

窗口的狀態(tài)欄導(dǎo)航欄顯示與否,最終其實是受以上兩個屬性共同影響的。接下來我們具體來分析一下View的setSystemUiVisibility方法是如何生效的。

二、View的setSystemUiVisibility方法調(diào)用流程

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);//通知父控件子控件屬性發(fā)生了變化
            }
        }
    }
}

setSystemUiVisibility方法首先將屬性賦值給mSystemUiVisibility,然后會調(diào)用父控件的recomputeViewAttributes方法,通知父控件子控件屬性發(fā)生了變化。ViewParent是一個接口,在Android中有兩個類實現(xiàn)了這個接口,它們分別是ViewGroup和ViewRootImpl。

2、ViewGroup和ViewRootImpl和recomputeViewAttributes方法相關(guān)的代碼如下所示。

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;//標(biāo)記需要重新計算本地屬性
            if (!mWillDrawSoon) {
                scheduleTraversals();//進(jìn)一步調(diào)用scheduleTraversals方法。
            }
        }
    }
}

結(jié)合Android 9.0系統(tǒng)源碼_窗口管理(二)WindowManager對窗口的管理過程,我們知道包括Activity的跟布局DecorView在內(nèi)的任何View,WindowManager在將它添加到窗口上的過程中,最終都會創(chuàng)建一個ViewRootImpl,并將View設(shè)置給ViewRootImpl,這樣根View的父類就變成了ViewRootImpl。這就意味著不管任何子View調(diào)用recomputeViewAttributes方法,最終所觸發(fā)的都是ViewRootImpl的recomputeViewAttributes,而ViewRootImpl會進(jìn)一步調(diào)用scheduleTraversals方法。

3、ViewRootImpl和scheduleTraversals方法相關(guān)的代碼如下所示。

public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks,
        AttachedSurfaceControl {
        
     final Choreographer mChoreographer;//編舞者
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();//回調(diào)對象

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
            //等待系統(tǒng)垂直刷新同步信號,回調(diào)TraversalRunnable對象的run方法
            mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
            notifyRendererOfFramePending();
            pokeDrawLockIfNeeded();
        }
    }
    
   final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();//繼續(xù)執(zhí)行doTraversal
        }
    }

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

            if (mProfile) {
                Debug.startMethodTracing("ViewAncestor");
            }
			//執(zhí)行performTraversals
            performTraversals();

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

scheduleTraversals方法會為編舞者對象設(shè)置回調(diào),最終會等待系統(tǒng)垂直刷新同步信號,回調(diào)TraversalRunnable對象的run方法,該方法會調(diào)用doTraversal方法,然后進(jìn)一步調(diào)用performTraversals方法。

4、ViewRootImpl的performTraversals方法代碼邏輯非常多,這里只列出了我們需要關(guān)注的代碼。

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;//將當(dāng)前窗口的最新屬性賦值給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;
        }
        ...代碼省略...
        //此方法最終會觸發(fā)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 {
        	...代碼省略...         
        	//調(diào)用IWindowSession的relayout方法,該方法最終會觸發(fā)WindowManagerService的relayoutWindow方法
             int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
               (int) (mView.getMeasuredWidth() * appScale + 0.5f),
               (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
               insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
               mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
               mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
               mPendingMergedConfiguration, mSurface);
     }
  }

performTraversals首先調(diào)用collectViewAttributes方法收集所有子View的屬性,然后調(diào)用relayoutWindow方法,該方法最終會觸發(fā)WindowManagerService的relayoutWindow方法,然后回繼續(xù)調(diào)用觸發(fā)View測量的performMeasure方法,觸發(fā)View布局的performLayout方法和觸發(fā)View繪制的performDraw方法。

三、獲取最新的SystemUIVisibility屬性

1、ViewRootImpl的collectViewAttributes方法是一個很關(guān)鍵的方法,此方法會重新計算最新的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;
            //清空已經(jīng)存在的SystemUiVisibility屬性
            mAttachInfo.mSystemUiVisibility = 0;
            mAttachInfo.mHasSystemUiListeners = false;
            //重新獲取窗口視圖mView最新的的SystemUI屬性,賦值給mAttachInfo
            mView.dispatchCollectViewAttributes(mAttachInfo, 0);
         	...代碼暫時省略...
        }
        return false;
    }
 }

collectViewAttributes首先會清空當(dāng)前窗口視圖mView已經(jīng)存在的SystemUiVisibility屬性,然后調(diào)用View的dispatchCollectViewAttributes方法重新獲取最新的的SystemUiVisibility屬性。

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

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    
    int mSystemUiVisibility;
    
    void dispatchCollectViewAttributes(AttachInfo attachInfo, int visibility) {
        performCollectViewAttributes(attachInfo, visibility);
    }
    
    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;
            //設(shè)置最新的SystemUiVisibility監(jiān)聽對象,如果不為空,則將AttachInfo的mHasSystemUiListeners屬性設(shè)置為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);
            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;
                //調(diào)用View的dispatchWindowSystemUiVisiblityChanged方法
                mView.dispatchWindowSystemUiVisiblityChanged(mAttachInfo.mSystemUiVisibility);
                return true;
            }
        }
        return false;
    }
 }

在重新獲得mView的SystemUiVisibility屬性之后,首先會從該屬性中移除被禁用的SystemUiVisibility屬性,然后讓params引用指向mWindowAttributes對象,并將重新獲取的保存在mAttachInfo對象中的SystemUiVisibility屬性保存到當(dāng)前窗口的LayoutParams屬性中,最后會調(diào)用當(dāng)前View的dispatchWindowSystemUiVisiblityChanged方法。

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

public class View implements Drawable.Callback, KeyEvent.Callback,
        AccessibilityEventSource {
    @Deprecated
    public void dispatchWindowSystemUiVisiblityChanged(int visible) {
        onWindowSystemUiVisibilityChanged(visible);//調(diào)用onWindowSystemUiVisibilityChanged方法
    }
    public void onWindowSystemUiVisibilityChanged(int visible) {
    	//默認(rèn)為空實現(xiàn)
    }
}

該方法會進(jìn)一步調(diào)用onWindowSystemUiVisibilityChanged方法,onWindowSystemUiVisibilityChanged方法默認(rèn)為空實現(xiàn),但是如果當(dāng)前mView為DecorView時則不同,DecorView實現(xiàn)了此方法。

四、DecorView更新狀態(tài)欄和導(dǎo)航欄背景顏色

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) {
    	//調(diào)用updateColorViews方法
        updateColorViews(null /* insets */, true /* animate */);
    }

}

onWindowSystemUiVisibilityChanged方法會調(diào)用一個updateColorViews這個關(guān)鍵方法。

2、updateColorViews會調(diào)用updateColorViewInt方法更新導(dǎo)航欄和狀態(tài)欄的背景顏色。

    public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
    
    WindowInsets updateColorViews(WindowInsets insets, boolean animate) {
    	//獲取窗口的SystemUIVisibility屬性
        int sysUiVisibility = attrs.systemUiVisibility | getWindowSystemUiVisibility();
        //判斷窗口類型是否是輸入法
        final boolean isImeWindow = mWindow.getAttributes().type == WindowManager.LayoutParams.TYPE_INPUT_METHOD;
        //判斷窗口類型不是浮動窗口和輸入法,則讓SystemUIVisibility屬性生效
        if (!mWindow.mIsFloating || isImeWindow) {
        	//獲取是否禁止窗口動畫的標(biāo)記
            boolean disallowAnimate = !isLaidOut();
            disallowAnimate |= ((mLastWindowFlags ^ attrs.flags)
                    & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;
            mLastWindowFlags = attrs.flags;
            if (insets != null) {
                mLastTopInset = getColorViewTopInset(insets.getStableInsetTop(),
                        insets.getSystemWindowInsetTop());
                mLastBottomInset = getColorViewBottomInset(insets.getStableInsetBottom(),
                        insets.getSystemWindowInsetBottom());
                mLastRightInset = getColorViewRightInset(insets.getStableInsetRight(),
                        insets.getSystemWindowInsetRight());
                mLastLeftInset = getColorViewRightInset(insets.getStableInsetLeft(),
                        insets.getSystemWindowInsetLeft());

                // Don't animate if the presence of stable insets has changed, because that
                // indicates that the window was either just added and received them for the
                // first time, or the window size or position has changed.
                boolean hasTopStableInset = insets.getStableInsetTop() != 0;
                disallowAnimate |= (hasTopStableInset != mLastHasTopStableInset);
                mLastHasTopStableInset = hasTopStableInset;

                boolean hasBottomStableInset = insets.getStableInsetBottom() != 0;
                disallowAnimate |= (hasBottomStableInset != mLastHasBottomStableInset);
                mLastHasBottomStableInset = hasBottomStableInset;

                boolean hasRightStableInset = insets.getStableInsetRight() != 0;
                disallowAnimate |= (hasRightStableInset != mLastHasRightStableInset);
                mLastHasRightStableInset = hasRightStableInset;

                boolean hasLeftStableInset = insets.getStableInsetLeft() != 0;
                disallowAnimate |= (hasLeftStableInset != mLastHasLeftStableInset);
                mLastHasLeftStableInset = hasLeftStableInset;

                mLastShouldAlwaysConsumeNavBar = insets.shouldAlwaysConsumeNavBar();
            }

            boolean navBarToRightEdge = isNavBarToRightEdge(mLastBottomInset, mLastRightInset);
            boolean navBarToLeftEdge = isNavBarToLeftEdge(mLastBottomInset, mLastLeftInset);
            int navBarSize = getNavBarSize(mLastBottomInset, mLastRightInset, mLastLeftInset);
            //更新導(dǎo)航欄顏色,mNavigationColorViewState為導(dǎo)航欄的相關(guān)篩選條件
            updateColorViewInt(mNavigationColorViewState, sysUiVisibility,
                    mWindow.mNavigationBarColor, mWindow.mNavigationBarDividerColor, navBarSize/*導(dǎo)航欄高度*/,
                    navBarToRightEdge || navBarToLeftEdge, navBarToLeftEdge,
                    0 /* sideInset */, animate && !disallowAnimate, false /* force */);

            boolean statusBarNeedsRightInset = navBarToRightEdge
                    && mNavigationColorViewState.present;
            boolean statusBarNeedsLeftInset = navBarToLeftEdge
                    && mNavigationColorViewState.present;
            int statusBarSideInset = statusBarNeedsRightInset ? mLastRightInset
                    : statusBarNeedsLeftInset ? mLastLeftInset : 0;
            //更新狀態(tài)欄顏色,mStatusColorViewState為狀態(tài)欄的相關(guān)篩選條件
            updateColorViewInt(mStatusColorViewState, sysUiVisibility,
                    calculateStatusBarColor(), 0, mLastTopInset/*狀態(tài)欄高度*/,
                    false /* matchVertical */, statusBarNeedsLeftInset, statusBarSideInset,
                    animate && !disallowAnimate,
                    mForceWindowDrawsStatusBarBackground);
        }
    		...代碼省略...
    }
        
}

關(guān)于DecorView更新狀態(tài)欄、導(dǎo)航欄背景顏色的具體過程,請參考Android 9.0系統(tǒng)源碼_SystemUI(八)PhoneWindow更新狀態(tài)欄和導(dǎo)航欄背景顏色的流程解析。

五、WindowManagerService的relayoutWindow方法

1、重新回到第二節(jié)第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;
        }
        ...代碼省略...
        //此方法最終會觸發(fā)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 {
        	...代碼省略...         
        	//調(diào)用IWindowSession的relayout方法,該方法最終會觸發(fā)WindowManagerService的relayoutWindow方法
             int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
               (int) (mView.getMeasuredWidth() * appScale + 0.5f),
               (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
               insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
               mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
               mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
               mPendingMergedConfiguration, mSurface);
     }
  }

在調(diào)用collectViewAttributes獲取最新的systemUIVisibiliy屬性之后,會調(diào)用relayoutWindow方法,該方法進(jìn)一步調(diào)用IWindowSession的relayout方法,IWindowSession的具體實現(xiàn)類為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方法會進(jìn)一步調(diào)用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, int seq, LayoutParams attrs/**窗口屬性**/,
            int requestedWidth, int requestedHeight, int viewVisibility/**根View控件是否可見**/, int flags,
            long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
            Surface outSurface) {
        int result = 0;
        boolean configChanged;
        //是否有狀態(tài)欄的使用權(quán)限
        final boolean hasStatusBarPermission =
                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR)
                        == PackageManager.PERMISSION_GRANTED;
        //是否有狀態(tài)欄服務(wù)的使用權(quán)限
        final boolean hasStatusBarServicePermission =
                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                        == PackageManager.PERMISSION_GRANTED;

        long origId = Binder.clearCallingIdentity();
        final int displayId;
        synchronized(mWindowMap) {
            //獲取當(dāng)前要操作的窗口對象
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            //獲取窗口所屬的屏幕設(shè)備id
            displayId = win.getDisplayId();
            //窗口動畫
            WindowStateAnimator winAnimator = win.mWinAnimator;
            if (viewVisibility != View.GONE) {
                win.setRequestedSize(requestedWidth, requestedHeight);
            }

            win.setFrameNumber(frameNumber);
            int attrChanges = 0;
            int flagChanges = 0;
            if (attrs != null) {
                //如果窗口屬性不為空,這里會對窗口的相關(guān)屬性進(jìn)行一次預(yù)處理
                mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);
                // if they don't have the permission, mask out the status bar bits
                if (seq == win.mSeq) {
                    int systemUiVisibility = attrs.systemUiVisibility
                            | attrs.subtreeSystemUiVisibility;
                    if ((systemUiVisibility & DISABLE_MASK) != 0) {
                        if (!hasStatusBarPermission) {
                            systemUiVisibility &= ~DISABLE_MASK;
                        }
                    }
                    win.mSystemUiVisibility = systemUiVisibility;
                }
                if (win.mAttrs.type != attrs.type) {
                    throw new IllegalArgumentException(
                            "Window type can not be changed after the window is added.");
                }

                // Odd choice but less odd than embedding in copyFrom()
                if ((attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY)
                        != 0) {
                    attrs.x = win.mAttrs.x;
                    attrs.y = win.mAttrs.y;
                    attrs.width = win.mAttrs.width;
                    attrs.height = win.mAttrs.height;
                }
                //檢測窗口標(biāo)記和樣式是否發(fā)生了變化
                flagChanges = win.mAttrs.flags ^= attrs.flags;
                attrChanges = win.mAttrs.copyFrom(attrs);
                if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
                        | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
                    win.mLayoutNeeded = true;
                }
                if (win.mAppToken != null && ((flagChanges & FLAG_SHOW_WHEN_LOCKED) != 0
                        || (flagChanges & FLAG_DISMISS_KEYGUARD) != 0)) {
                    win.mAppToken.checkKeyguardFlagsChanged();
                }
                if (((attrChanges & LayoutParams.ACCESSIBILITY_TITLE_CHANGED) != 0)
                        && (mAccessibilityController != null)
                        && (win.getDisplayId() == DEFAULT_DISPLAY)) {
                    // No move or resize, but the controller checks for title changes as well
                    mAccessibilityController.onSomeWindowResizedOrMovedLocked();
                }

                if ((flagChanges & PRIVATE_FLAG_HIDE_NON_SYSTEM_OVERLAY_WINDOWS) != 0) {
                    updateNonSystemOverlayWindowsVisibilityIfNeeded(
                            win, win.mWinAnimator.getShown());
                }
            }
            ...代碼省略... 
            if (focusMayChange) {
                //System.out.println("Focus may change: " + win.mAttrs.getTitle());
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                        false /*updateInputWindows*/)) {
                    imMayMove = false;
                }
                //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
            }
            ...代碼省略...           
	}
}

relayoutWindow方法會調(diào)用一個關(guān)鍵方法updateFocusedWindowLocked。

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

public class WindowManagerService extends IWindowManager.Stub
        implements Watchdog.Monitor, WindowManagerPolicy.WindowManagerFuncs {
   
    //窗口管理策略的接口類WindowManagerPolicy(WMP),它用來定義一個窗口策略所要遵循的通用規(guī)范。
    final WindowManagerPolicy mPolicy;
    //根窗口
    RootWindowContainer mRoot;
    
    private WindowManagerService(Context context, InputManagerService inputManager,
	            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
	            WindowManagerPolicy policy) {
	            ...代碼省略...
	            mPolicy = policy;
		        mRoot = new RootWindowContainer(this);
	            ...代碼省略...
	}
    
    boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        //獲取當(dāng)前最新的焦點窗口
        WindowState newFocus = mRoot.computeFocusedWindow();
        if (mCurrentFocus != newFocus) {//如果窗口焦點發(fā)生了變化
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
            // This check makes sure that we don't already have the focus
            // change message pending.
            mH.removeMessages(H.REPORT_FOCUS_CHANGE);
            mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
            // TODO(multidisplay): Focused windows on default display only.
            final DisplayContent displayContent = getDefaultDisplayContentLocked();
            boolean imWindowChanged = false;
            if (mInputMethodWindow != null) {//如果輸入法窗口不為空
                final WindowState prevTarget = mInputMethodTarget;
                final WindowState newTarget =
                        displayContent.computeImeTarget(true /* updateImeTarget*/);

                imWindowChanged = prevTarget != newTarget;

                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                    final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
                    imWindowChanged |=
                            prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
                }
            }

            if (imWindowChanged) {//輸入法窗口發(fā)生了變化
                mWindowsChanged = true;
                displayContent.setLayoutNeeded();
                newFocus = mRoot.computeFocusedWindow();
            }

            if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
                    mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
            final WindowState oldFocus = mCurrentFocus;
            mCurrentFocus = newFocus;
            mLosingFocus.remove(newFocus);

            if (mCurrentFocus != null) {
                mWinAddedSinceNullFocus.clear();
                mWinRemovedSinceNullFocus.clear();
            }
            //調(diào)用WindowManagerPolicy的focusChangedLw方法
            int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);

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

            if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
                // The change in focus caused us to need to do a layout.  Okay.
                displayContent.setLayoutNeeded();
                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
                    displayContent.performLayout(true /*initial*/, updateInputWindows);
                }
            }

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

            displayContent.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.
            displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);

            Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            return true;
        }
        return false;
    }

}

updateFocusedWindowLocked首先是獲取最新的焦點窗口,之后還會判斷當(dāng)前窗口焦點是否發(fā)生了變化,如果發(fā)生了變化,則會調(diào)用WindowManagerPolicy的focusChangedLw方法。

六、PhoneWindowManager的updateSystemUiVisibilityLw方法

1、WindowManagerPolicy是一個抽象接口。

frameworks/base/services/core/java/com/android/server/policy/WindowManagerPolicy.java

public interface WindowManagerPolicy extends WindowManagerPolicyConstants {
    /**
     * 一個新的窗口持有了焦點
     */
    public int focusChangedLw(WindowState lastFocus, WindowState newFocus);
}

2、結(jié)合Android 9.0系統(tǒng)源碼_窗口管理(一)WindowManagerService的啟動流程這篇文章我們可以知道,SystemServer在創(chuàng)建WindowManagerService對象的時候,將PhoneWindowManager對象實例賦值給了mPolicy。

frameworks/base/service/java/com/android/server/SystemServer.java

public final class SystemServer {	
	  private void startOtherServices() {
			 ...代碼省略...
	          wm = WindowManagerService.main(context, inputManager,
	                 mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
	                 !mFirstBoot, mOnlyCore, new PhoneWindowManager());
	          ...代碼省略...
	 }
 }

3、來看下PhoneWindowManager是如何實現(xiàn)focusChangedLw方法的。

frameworks/base/services/core/java/com/android/server/policy/PhoneWindowManager.java

public class PhoneWindowManager implements WindowManagerPolicy {
    @Override
    public int focusChangedLw(WindowState lastFocus, WindowState newFocus) {
        mFocusedWindow = newFocus;
        if ((updateSystemUiVisibilityLw()&SYSTEM_UI_CHANGING_LAYOUT) != 0) {
            // If the navigation bar has been hidden or shown, we need to do another
            // layout pass to update that window.
            return FINISH_LAYOUT_REDO_LAYOUT;
        }
        return 0;
    }
    
    //更新窗口的SystemUiVisibility屬性參數(shù)
    private int updateSystemUiVisibilityLw() {
        //獲取當(dāng)前的焦點窗口
        WindowState winCandidate = mFocusedWindow != null ? mFocusedWindow
                : mTopFullscreenOpaqueWindowState;
        //如果不存在焦點窗口則直接返回
        if (winCandidate == null) {
            return 0;
        }
        if (winCandidate.getAttrs().token == mImmersiveModeConfirmation.getWindowToken()) {
            winCandidate = isStatusBarKeyguard() ? mStatusBar : mTopFullscreenOpaqueWindowState;
            if (winCandidate == null) {
                return 0;
            }
        }
        final WindowState win = winCandidate;
        if ((win.getAttrs().privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 && mKeyguardOccluded) {
            return 0;
        }

        int tmpVisibility = PolicyControl.getSystemUiVisibility(win, null)
                & ~mResettingSystemUiFlags
                & ~mForceClearedSystemUiFlags;
        if (mForcingShowNavBar && win.getSurfaceLayer() < mForcingShowNavBarLayer) {
            tmpVisibility &= ~PolicyControl.adjustClearableFlags(win, View.SYSTEM_UI_CLEARABLE_FLAGS);
        }
        //獲取和SystemUIVisibility相關(guān)的各種窗口參數(shù)
        final int fullscreenVisibility = updateLightStatusBarLw(0 /* vis */,
                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState);
        final int dockedVisibility = updateLightStatusBarLw(0 /* vis */,
                mTopDockedOpaqueWindowState, mTopDockedOpaqueOrDimmingWindowState);
        mWindowManagerFuncs.getStackBounds(
                WINDOWING_MODE_UNDEFINED, ACTIVITY_TYPE_HOME, mNonDockedStackBounds);
        mWindowManagerFuncs.getStackBounds(
                WINDOWING_MODE_SPLIT_SCREEN_PRIMARY, ACTIVITY_TYPE_STANDARD, mDockedStackBounds);
        final int visibility = updateSystemBarsLw(win, mLastSystemUiFlags, tmpVisibility);
        final int diff = visibility ^ mLastSystemUiFlags;
        final int fullscreenDiff = fullscreenVisibility ^ mLastFullscreenStackSysUiFlags;
        final int dockedDiff = dockedVisibility ^ mLastDockedStackSysUiFlags;
        final boolean needsMenu = win.getNeedsMenuLw(mTopFullscreenOpaqueWindowState);
        if (diff == 0 && fullscreenDiff == 0 && dockedDiff == 0 && mLastFocusNeedsMenu == needsMenu
                && mFocusedApp == win.getAppToken()
                && mLastNonDockedStackBounds.equals(mNonDockedStackBounds)
                && mLastDockedStackBounds.equals(mDockedStackBounds)) {
            return 0;
        }
        mLastSystemUiFlags = visibility;
        mLastFullscreenStackSysUiFlags = fullscreenVisibility;
        mLastDockedStackSysUiFlags = dockedVisibility;
        mLastFocusNeedsMenu = needsMenu;
        mFocusedApp = win.getAppToken();
        final Rect fullscreenStackBounds = new Rect(mNonDockedStackBounds);
        final Rect dockedStackBounds = new Rect(mDockedStackBounds);
        mHandler.post(new Runnable() {
                @Override
                public void run() {
                    StatusBarManagerInternal statusbar = getStatusBarManagerInternal();
                    if (statusbar != null) {
                        //最終會觸發(fā)狀態(tài)欄管理服務(wù)StatusBarManagerService的setSystemUiVisibility方法,
                        //通知狀態(tài)欄和底部欄進(jìn)行樣式調(diào)整
                        statusbar.setSystemUiVisibility(visibility, fullscreenVisibility,
                                dockedVisibility, 0xffffffff, fullscreenStackBounds,
                                dockedStackBounds, win.toString());
                        statusbar.topAppWindowChanged(needsMenu);
                    }
                }
            });
        return diff;
    }
}

PhoneWindowManager的focusChangedLw方法直接調(diào)用了updateSystemUiVisibilityLw方法,此方法會對窗口的SystemUiVisibility屬性做一些處理,最終調(diào)用狀態(tài)欄管理服務(wù)StatusBarManagerService的setSystemUiVisibility方法,通知狀態(tài)欄和底部欄進(jìn)行樣式調(diào)整。

4、updateSystemUiVisibilityLw方法中有用到對SystemUIVisibility屬性做處理的幾個關(guān)鍵方法:updateLightStatusBarLw、chooseNavigationColorWindowLw、updateLightNavigationBarLw和updateSystemBarsLw,這里一并貼出,有興趣的可以看下。

public class PhoneWindowManager implements WindowManagerPolicy {
    private int updateLightStatusBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming) {
        final boolean onKeyguard = isStatusBarKeyguard() && !mKeyguardOccluded;
        final WindowState statusColorWin = onKeyguard ? mStatusBar : opaqueOrDimming;
        if (statusColorWin != null && (statusColorWin == opaque || onKeyguard)) {
            // If the top fullscreen-or-dimming window is also the top fullscreen, respect
            // its light flag.
            vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            vis |= PolicyControl.getSystemUiVisibility(statusColorWin, null)
                    & View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
        } else if (statusColorWin != null && statusColorWin.isDimming()) {
            // Otherwise if it's dimming, clear the light flag.
            vis &= ~View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
        }
        return vis;
    }

    @VisibleForTesting
    @Nullable
    static WindowState chooseNavigationColorWindowLw(WindowState opaque,
            WindowState opaqueOrDimming, WindowState imeWindow,
            @NavigationBarPosition int navBarPosition) {
        // If the IME window is visible and FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS is set, then IME
        // window can be navigation color window.
        final boolean imeWindowCanNavColorWindow = imeWindow != null
                && imeWindow.isVisibleLw()
                && navBarPosition == NAV_BAR_BOTTOM
                && (PolicyControl.getWindowFlags(imeWindow, null)
                & WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) != 0;

        if (opaque != null && opaqueOrDimming == opaque) {
            // If the top fullscreen-or-dimming window is also the top fullscreen, respect it
            // unless IME window is also eligible, since currently the IME window is always show
            // above the opaque fullscreen app window, regardless of the IME target window.
            // TODO(b/31559891): Maybe we need to revisit this condition once b/31559891 is fixed.
            return imeWindowCanNavColorWindow ? imeWindow : opaque;
        }

        if (opaqueOrDimming == null || !opaqueOrDimming.isDimming()) {
            // No dimming window is involved. Determine the result only with the IME window.
            return imeWindowCanNavColorWindow ? imeWindow : null;
        }

        if (!imeWindowCanNavColorWindow) {
            // No IME window is involved. Determine the result only with opaqueOrDimming.
            return opaqueOrDimming;
        }

        // The IME window and the dimming window are competing.  Check if the dimming window can be
        // IME target or not.
        if (LayoutParams.mayUseInputMethod(PolicyControl.getWindowFlags(opaqueOrDimming, null))) {
            // The IME window is above the dimming window.
            return imeWindow;
        } else {
            // The dimming window is above the IME window.
            return opaqueOrDimming;
        }
    }

    @VisibleForTesting
    static int updateLightNavigationBarLw(int vis, WindowState opaque, WindowState opaqueOrDimming,
            WindowState imeWindow, WindowState navColorWin) {

        if (navColorWin != null) {
            if (navColorWin == imeWindow || navColorWin == opaque) {
                // Respect the light flag.
                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
                vis |= PolicyControl.getSystemUiVisibility(navColorWin, null)
                        & View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
            } else if (navColorWin == opaqueOrDimming && navColorWin.isDimming()) {
                // Clear the light flag for dimming window.
                vis &= ~View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR;
            }
        }
        return vis;
    }
    
    private int updateSystemBarsLw(WindowState win, int oldVis, int vis) {
        final boolean dockedStackVisible =
                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_SPLIT_SCREEN_PRIMARY);
        final boolean freeformStackVisible =
                mWindowManagerInternal.isStackVisible(WINDOWING_MODE_FREEFORM);
        final boolean resizing = mWindowManagerInternal.isDockedDividerResizing();

        // We need to force system bars when the docked stack is visible, when the freeform stack
        // is visible but also when we are resizing for the transitions when docked stack
        // visibility changes.
        mForceShowSystemBars = dockedStackVisible || freeformStackVisible || resizing;
        final boolean forceOpaqueStatusBar = mForceShowSystemBars && !mForceStatusBarFromKeyguard;

        // apply translucent bar vis flags
        WindowState fullscreenTransWin = isStatusBarKeyguard() && !mKeyguardOccluded
                ? mStatusBar
                : mTopFullscreenOpaqueWindowState;
        vis = mStatusBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
        vis = mNavigationBarController.applyTranslucentFlagLw(fullscreenTransWin, vis, oldVis);
        final int dockedVis = mStatusBarController.applyTranslucentFlagLw(
                mTopDockedOpaqueWindowState, 0, 0);

        final boolean fullscreenDrawsStatusBarBackground =
                drawsStatusBarBackground(vis, mTopFullscreenOpaqueWindowState);
        final boolean dockedDrawsStatusBarBackground =
                drawsStatusBarBackground(dockedVis, mTopDockedOpaqueWindowState);

        // prevent status bar interaction from clearing certain flags
        int type = win.getAttrs().type;
        boolean statusBarHasFocus = type == TYPE_STATUS_BAR;
        if (statusBarHasFocus && !isStatusBarKeyguard()) {
            int flags = View.SYSTEM_UI_FLAG_FULLSCREEN
                    | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION
                    | View.SYSTEM_UI_FLAG_IMMERSIVE
                    | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY
                    | View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR;
            if (mKeyguardOccluded) {
                flags |= View.STATUS_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSLUCENT;
            }
            vis = (vis & ~flags) | (oldVis & flags);
        }

        if (fullscreenDrawsStatusBarBackground && dockedDrawsStatusBarBackground) {
            vis |= View.STATUS_BAR_TRANSPARENT;
            vis &= ~View.STATUS_BAR_TRANSLUCENT;
        } else if ((!areTranslucentBarsAllowed() && fullscreenTransWin != mStatusBar)
                || forceOpaqueStatusBar) {
            vis &= ~(View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT);
        }

        vis = configureNavBarOpacity(vis, dockedStackVisible, freeformStackVisible, resizing);

        // update status bar
        boolean immersiveSticky =
                (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
        final boolean hideStatusBarWM =
                mTopFullscreenOpaqueWindowState != null
                && (PolicyControl.getWindowFlags(mTopFullscreenOpaqueWindowState, null)
                        & WindowManager.LayoutParams.FLAG_FULLSCREEN) != 0;
        final boolean hideStatusBarSysui =
                (vis & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;
        final boolean hideNavBarSysui =
                (vis & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;

        final boolean transientStatusBarAllowed = mStatusBar != null
                && (statusBarHasFocus || (!mForceShowSystemBars
                        && (hideStatusBarWM || (hideStatusBarSysui && immersiveSticky))));

        final boolean transientNavBarAllowed = mNavigationBar != null
                && !mForceShowSystemBars && hideNavBarSysui && immersiveSticky;

        final long now = SystemClock.uptimeMillis();
        final boolean pendingPanic = mPendingPanicGestureUptime != 0
                && now - mPendingPanicGestureUptime <= PANIC_GESTURE_EXPIRATION;
        if (pendingPanic && hideNavBarSysui && !isStatusBarKeyguard() && mKeyguardDrawComplete) {
            // The user performed the panic gesture recently, we're about to hide the bars,
            // we're no longer on the Keyguard and the screen is ready. We can now request the bars.
            mPendingPanicGestureUptime = 0;
            mStatusBarController.showTransient();
            if (!isNavBarEmpty(vis)) {
                mNavigationBarController.showTransient();
            }
        }

        final boolean denyTransientStatus = mStatusBarController.isTransientShowRequested()
                && !transientStatusBarAllowed && hideStatusBarSysui;
        final boolean denyTransientNav = mNavigationBarController.isTransientShowRequested()
                && !transientNavBarAllowed;
        if (denyTransientStatus || denyTransientNav || mForceShowSystemBars) {
            // clear the clearable flags instead
            clearClearableFlagsLw();
            vis &= ~View.SYSTEM_UI_CLEARABLE_FLAGS;
        }

        final boolean immersive = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
        immersiveSticky = (vis & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
        final boolean navAllowedHidden = immersive || immersiveSticky;

        if (hideNavBarSysui && !navAllowedHidden
                && getWindowLayerLw(win) > getWindowLayerFromTypeLw(TYPE_INPUT_CONSUMER)) {
            // We can't hide the navbar from this window otherwise the input consumer would not get
            // the input events.
            vis = (vis & ~View.SYSTEM_UI_FLAG_HIDE_NAVIGATION);
        }

        vis = mStatusBarController.updateVisibilityLw(transientStatusBarAllowed, oldVis, vis);

        // update navigation bar
        boolean oldImmersiveMode = isImmersiveMode(oldVis);
        boolean newImmersiveMode = isImmersiveMode(vis);
        if (win != null && oldImmersiveMode != newImmersiveMode) {
            final String pkg = win.getOwningPackage();
            mImmersiveModeConfirmation.immersiveModeChangedLw(pkg, newImmersiveMode,
                    isUserSetupComplete(), isNavBarEmpty(win.getSystemUiVisibility()));
        }

        vis = mNavigationBarController.updateVisibilityLw(transientNavBarAllowed, oldVis, vis);

        final WindowState navColorWin = chooseNavigationColorWindowLw(
                mTopFullscreenOpaqueWindowState, mTopFullscreenOpaqueOrDimmingWindowState,
                mWindowManagerFuncs.getInputMethodWindowLw(), mNavigationBarPosition);
        vis = updateLightNavigationBarLw(vis, mTopFullscreenOpaqueWindowState,
                mTopFullscreenOpaqueOrDimmingWindowState,
                mWindowManagerFuncs.getInputMethodWindowLw(), navColorWin);

        return vis;
    }
}

七、狀態(tài)欄管理服務(wù)StatusBarManagerService發(fā)送SystemUIVisibility屬性

1、在第六節(jié)第2步的最后,PhoneWindowManager先是調(diào)用getStatusBarManagerInternal方法獲取StatusBarManagerInternal的對象實例,然后再調(diào)用該對象的setSystemUiVisibility方法,PhoneWindowManager的getStatusBarManagerInternal方法如下所示。

public class PhoneWindowManager implements WindowManagerPolicy {
	//獲取StatusBarManagerInternal對象實例
    StatusBarManagerInternal getStatusBarManagerInternal() {
        synchronized (mServiceAquireLock) {
            if (mStatusBarManagerInternal == null) {
                mStatusBarManagerInternal = LocalServices.getService(StatusBarManagerInternal.class);
            }
            return mStatusBarManagerInternal;
        }
    }
}

getStatusBarManagerInternal就是將存儲在LocalServices的StatusBarManagerInternal對象實例取出然后返回。

2、StatusBarManagerInternal是一個抽象接口。

frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerInternal.java

public interface StatusBarManagerInternal {
	
    void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask,
            Rect fullscreenBounds, Rect dockedBounds, String cause);

}

3、系統(tǒng)最早是在狀態(tài)欄管理服務(wù)StatusBarManagerService的構(gòu)造方法中將StatusBarManagerInternal的對象實例存儲到LocalServices中的。

frameworks/base/services/core/java/com/android/server/statusbar/StatusBarManagerService.java

public class StatusBarManagerService extends IStatusBarService.Stub {

    public StatusBarManagerService(Context context, WindowManagerService windowManager) {
        mContext = context;
        mWindowManager = windowManager;
		//將mInternalService存儲到LocalServices中。
        LocalServices.addService(StatusBarManagerInternal.class, mInternalService);
        LocalServices.addService(GlobalActionsProvider.class, mGlobalActionsProvider);
    }
    
    private final StatusBarManagerInternal mInternalService = new StatusBarManagerInternal() {
        private boolean mNotificationLightOn;
        @Override
        public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
                int mask,
                Rect fullscreenBounds, Rect dockedBounds, String cause) {
            //進(jìn)一步調(diào)用StatusBarManagerService的setSystemUiVisibility方法。
            StatusBarManagerService.this.setSystemUiVisibility(vis, fullscreenStackVis,
                    dockedStackVis, mask, fullscreenBounds, dockedBounds, cause);
        }
     }
     
    private void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask,
            Rect fullscreenBounds, Rect dockedBounds, String cause) {
        // also allows calls from window manager which is in this process.
        enforceStatusBarService();

        if (SPEW) Slog.d(TAG, "setSystemUiVisibility(0x" + Integer.toHexString(vis) + ")");

        synchronized (mLock) {
        	//進(jìn)一步調(diào)用updateUiVisibilityLocked方法。
            updateUiVisibilityLocked(vis, fullscreenStackVis, dockedStackVis, mask,
                    fullscreenBounds, dockedBounds);
            disableLocked(
                    mCurrentUserId,
                    vis & StatusBarManager.DISABLE_MASK,
                    mSysUiVisToken,
                    cause, 1);
        }
    }
}

由以上代碼可知StatusBarManagerInternal的setSystemUiVisibility方法會進(jìn)一步調(diào)用StatusBarManagerService的setSystemUiVisibility方法,StatusBarManagerService的setSystemUiVisibility方法會繼續(xù)調(diào)用updateUiVisibilityLocked。

4、來看下StatusBarManagerService的updateUiVisibilityLocked方法。

public class StatusBarManagerService extends IStatusBarService.Stub {

    private volatile IStatusBar mBar;
    
    //設(shè)置監(jiān)聽
    @Override
    public void registerStatusBar(IStatusBar bar, List<String> iconSlots,
            List<StatusBarIcon> iconList, int switches[], List<IBinder> binders,
            Rect fullscreenStackBounds, Rect dockedStackBounds) {
	        enforceStatusBarService();
	        Slog.i(TAG, "registerStatusBar bar=" + bar);
	        mBar = bar;//為mBar賦值
 			...代碼省略...
        }
    }
    
    private void updateUiVisibilityLocked(final int vis,
            final int fullscreenStackVis, final int dockedStackVis, final int mask,
            final Rect fullscreenBounds, final Rect dockedBounds) {
        if (mSystemUiVisibility != vis
                || mFullscreenStackSysUiVisibility != fullscreenStackVis
                || mDockedStackSysUiVisibility != dockedStackVis
                || !mFullscreenStackBounds.equals(fullscreenBounds)
                || !mDockedStackBounds.equals(dockedBounds)) {
            mSystemUiVisibility = vis;
            mFullscreenStackSysUiVisibility = fullscreenStackVis;
            mDockedStackSysUiVisibility = dockedStackVis;
            mFullscreenStackBounds.set(fullscreenBounds);
            mDockedStackBounds.set(dockedBounds);
            mHandler.post(new Runnable() {
                    public void run() {
                        if (mBar != null) {//判斷mBar是否為空
                            try {
                            	//調(diào)用IStatusBar的setSystemUiVisibility方法,觸發(fā)監(jiān)聽回調(diào)
                                mBar.setSystemUiVisibility(vis, fullscreenStackVis, dockedStackVis,
                                        mask, fullscreenBounds, dockedBounds);
                            } catch (RemoteException ex) {
                            }
                        }
                    }
                });
        }
    }

}

updateUiVisibilityLocked方法會調(diào)用類型為IStatusBar的屬性對象mBar的setSystemUiVisibility方法,mBar對象是在registerStatusBar方法中被賦值的,而且IStatusBar是一個aidl。

frameworks/base/core/java/com/android/internal/statusbar/IStatusBar.aidl

oneway interface IStatusBar
{
	//通知SystemUIVisibility屬性發(fā)生了變化
    void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis, int mask,
            in Rect fullscreenBounds, in Rect dockedBounds, String packageName);
}

八、系統(tǒng)狀態(tài)欄StatusBar設(shè)置監(jiān)聽回調(diào),獲取最新的SystemUIVisibility屬性

1、我們在Android 9.0系統(tǒng)源碼_SystemUI(二)系統(tǒng)狀態(tài)欄導(dǎo)航欄的創(chuàng)建和添加這篇文章中有詳細(xì)介紹StatusBar創(chuàng)建狀態(tài)欄和導(dǎo)航欄的過程,這里我們只列出了和獲取SystemUIVisibility屬性相關(guān)的代碼。

frameworks/base/packages/SystemUI/src/com/android/systemui/SystemBars.java

public class StatusBar extends SystemUI implements DemoMode,
        DragDownHelper.DragDownCallback, ActivityStarter, OnUnlockMethodChangedListener,
        OnHeadsUpChangedListener, CommandQueue.Callbacks, ZenModeController.Callback,
        ColorExtractor.OnColorsChangedListener, ConfigurationListener, NotificationPresenter {
        
    protected CommandQueue mCommandQueue;
    protected IStatusBarService mBarService;

    @Override
    public void start() {
      	...代碼省略
        mBarService = IStatusBarService.Stub.asInterface(ServiceManager.getService(Context.STATUS_BAR_SERVICE));
        mCommandQueue = getComponent(CommandQueue.class);
        //mCommandQueue將當(dāng)前StatusBar 對象添加為事件回調(diào)對象
        mCommandQueue.addCallbacks(this);
        //調(diào)用StatusBarManagerService的registerStatusBar方法,設(shè)置mCommandQueue為setSystemUiVisibility回調(diào)對象
        mBarService.registerStatusBar(mCommandQueue, iconSlots, icons, switches, binders,
                    fullscreenStackBounds, dockedStackBounds);
       ...代碼省略
   }
   
   //StatusBarManagerService的setSystemUiVisibility方法最終會回調(diào)到這里。
   @Override
   public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
       //至此StatusBar才終于成功收到了應(yīng)用所設(shè)置的SystemUiVisibility屬性,然后根據(jù)具體的屬性顯示對應(yīng)的樣式
       ..代碼省略...
    }
}

2、StatusBar的start方法首先會為類型為IStatusBarService的屬性對象mBarService賦值,IStatusBarService是一個aidl。

frameworks/base/core/java/com/android/internal/statusbar/IStatusBarService.aidl

interface IStatusBarService
{
    // You need the STATUS_BAR_SERVICE permission
    void registerStatusBar(IStatusBar callbacks, out List<String> iconSlots,
            out List<StatusBarIcon> iconList,
            out int[] switches, out List<IBinder> binders, out Rect fullscreenStackBounds,
            out Rect dockedStackBounds);
}

通過它我們可以調(diào)用到狀態(tài)欄管理服務(wù)StatusBarManagerService的registerStatusBar方法。StatusBar指定了類型為CommandQueue的屬性對象mCommandQueue為StatusBarManagerService的setSystemUiVisibility的回調(diào)對象,這樣StatusBarManagerService對象首先會回調(diào)CommandQueue的setSystemUiVisibility方法。

3、CommandQueue和setSystemUiVisibility方法相關(guān)的代碼如下所示。

frameworks/base/packages/SystemUI/src/com/android/systemui/statusbar/CommandQueue.java

public class CommandQueue extends IStatusBar.Stub {

    private static final int MSG_SET_SYSTEMUI_VISIBILITY       = 6 << MSG_SHIFT;
    private ArrayList<Callbacks> mCallbacks = new ArrayList<>();//回調(diào)對象集合

	//回調(diào)對象
    public interface Callbacks {
        default void setSystemUiVisibility(int vis, int fullscreenStackVis,
                int dockedStackVis, int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
        }
    }
    
    //添加回調(diào)對象到mCallbacks集合中
    public void addCallbacks(Callbacks callbacks) {
        mCallbacks.add(callbacks);
        callbacks.disable(mDisable1, mDisable2, false /* animate */);
    }
    
    //StatusBarManagerService對象首先會回調(diào)此方法。
    public void setSystemUiVisibility(int vis, int fullscreenStackVis, int dockedStackVis,
            int mask, Rect fullscreenStackBounds, Rect dockedStackBounds) {
        synchronized (mLock) {
            // Don't coalesce these, since it might have one time flags set such as
            // STATUS_BAR_UNHIDE which might get lost.
            SomeArgs args = SomeArgs.obtain();
            args.argi1 = vis;
            args.argi2 = fullscreenStackVis;
            args.argi3 = dockedStackVis;
            args.argi4 = mask;
            args.arg1 = fullscreenStackBounds;
            args.arg2 = dockedStackBounds;
            //發(fā)送消息
            mHandler.obtainMessage(MSG_SET_SYSTEMUI_VISIBILITY, args).sendToTarget();
        }
    }
    private final class H extends Handler {
        private H(Looper l) {
            super(l);
        }

        public void handleMessage(Message msg) {
            final int what = msg.what & MSG_MASK;
            switch (what) {
            		...代碼省略...
             		case MSG_SET_SYSTEMUI_VISIBILITY:
	                    SomeArgs args = (SomeArgs) msg.obj;
	                    //依次調(diào)用集合中對象的setSystemUiVisibility方法,因為StatusBar有調(diào)用addCallbacks方法,
	                    //因此這里會觸發(fā)StatusBar的setSystemUiVisibility方法。
	                    for (int i = 0; i < mCallbacks.size(); i++) {
	                        mCallbacks.get(i).setSystemUiVisibility(args.argi1, args.argi2, args.argi3,
	                                args.argi4, (Rect) args.arg1, (Rect) args.arg2);
	                    }
	                    args.recycle();
                    break; 
                    ...代碼省略...
            }
        }
  }
}

CommandQueue的addCallbacks方法會將回調(diào)對象(例如前面的StatusBar)添加到mCallbacks集合中,當(dāng)CommandQueued的setSystemUiVisibility方法被回調(diào)的時候,會通過Handler發(fā)送消息,并在Handler的handleMessage方法的對應(yīng)消息分支中依次調(diào)用集合中回調(diào)對象的setSystemUiVisibility方法,因為StatusBar有調(diào)用addCallbacks方法將自己添加到回調(diào)集合中,因此這里會觸發(fā)StatusBar的setSystemUiVisibility方法,至此StatusBar才終于成功收到了應(yīng)用所設(shè)置的SystemUiVisibility屬性,然后根據(jù)具體的屬性顯示對應(yīng)的樣式。文章來源地址http://www.zghlxwxcb.cn/news/detail-665089.html

到了這里,關(guān)于Android 9系統(tǒng)源碼_SystemUI(十)SystemUIVisibility屬性的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進(jìn)行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • Android 12 源碼分析 —— 應(yīng)用層 二(SystemUI大體組織和啟動過程)

    Android 12 源碼分析 —— 應(yīng)用層 二(SystemUI大體組織和啟動過程)

    在前一篇文章中,我們介紹了SystemUI怎么使用IDE進(jìn)行編輯和調(diào)試。這是分析SystemUI的最基礎(chǔ),希望讀者能盡量掌握。 本篇文章,將會介紹SystemUI的大概組織架構(gòu),以及它的啟動過程。本篇文章讀完,將會知道: SystemUI為什么選擇使用Dagger2 SystemUI怎么新建一個模塊 SystemUI的啟動

    2024年02月06日
    瀏覽(39)
  • Android SystemUI源碼分析與修改,作為Android程序員應(yīng)該怎樣去規(guī)劃自己的學(xué)習(xí)路線

    Android SystemUI源碼分析與修改,作為Android程序員應(yīng)該怎樣去規(guī)劃自己的學(xué)習(xí)路線

    systemui:keyCode=“4” android:layout_weight=“0” systemui:glowBackground=“@drawable/ic_sysbar_highlight” android:contentDescription=“@string/accessibility_back” / 音量減的布局如下,這里先把Visibility定義為Gone,然后在代碼中控制是否顯示: com.android.systemui.statusbar.policy.KeyButtonView android:id=“@+id/sub”

    2024年04月15日
    瀏覽(51)
  • Android 12 源碼分析 —— 應(yīng)用層 四(SystemUI的基本布局設(shè)計及其基本概念)

    Android 12 源碼分析 —— 應(yīng)用層 四(SystemUI的基本布局設(shè)計及其基本概念)

    更新歷史 日期 內(nèi)容 1 2023-9-11 增加文中提及的漸變動畫的效果圖 在上兩篇文章中,我們介紹SystemUI的啟動過程,以及基本的組件依賴關(guān)系?;镜囊蕾囮P(guān)系請讀者一定要掌握,因為后面的文章,將會時常出現(xiàn)這些依賴關(guān)系的使用,屆時將會一筆帶過,而不會詳細(xì)說明他們的實

    2024年02月08日
    瀏覽(23)
  • Android 12.0 SystemUI控制系統(tǒng)手勢左右滑返回功能

    ? 在12.0的系統(tǒng)rom定制化產(chǎn)品開發(fā)中,在10.0以后系統(tǒng)默認(rèn)手勢中有三鍵導(dǎo)航和系統(tǒng)手勢導(dǎo)航,在系統(tǒng)systemui設(shè)置默認(rèn)系統(tǒng)手勢導(dǎo)航以后,左右滑動手勢返回功能 是在SystemUI中具體實現(xiàn)的,現(xiàn)在有需要要求控制左右滑動手勢返回功能的啟用和禁用,所以要分析手勢返回功能的具體

    2024年02月03日
    瀏覽(25)
  • Android 設(shè)置系統(tǒng)SystemUI 頂部StatusBar狀態(tài)欄透明一體化

    Android 設(shè)置系統(tǒng)SystemUI 頂部StatusBar狀態(tài)欄透明一體化

    當(dāng)你開啟其他應(yīng)用的時候,執(zhí)行的是onPause()方法,當(dāng)返回Launcher的時候執(zhí)行的是onRestart()方法, /frameworks/base/package/SystemUI/…. PhoneStatusBar.java //sendBroadcast change systemUI statusbar color start Intent intent=new Intent(“change_statusbar_black”); sendBroadcast(intent); status_bar.xml 的默認(rèn)背景色是透明的 Pow

    2024年04月17日
    瀏覽(30)
  • Android 10.0 系統(tǒng)systemui狀態(tài)欄下拉左滑顯示通知欄右滑顯示控制中心模塊的流程分析

    Android 10.0 系統(tǒng)systemui狀態(tài)欄下拉左滑顯示通知欄右滑顯示控制中心模塊的流程分析

    ? 在android10.0的系統(tǒng)rom定制化開發(fā)中,在系統(tǒng)原生systemui進(jìn)行自定義下拉狀態(tài)欄布局的定制的時候,需要在systemui下拉狀態(tài)欄下滑的時候,根據(jù)下滑坐標(biāo)來 判斷當(dāng)前是滑出通知欄還是滑出控制中心模塊,所以就需要根據(jù)屏幕寬度,來區(qū)分x坐標(biāo)值為多少是左滑出通知欄或者右滑

    2023年04月09日
    瀏覽(93)
  • Android 12.0 系統(tǒng)systemui狀態(tài)欄下拉左滑顯示通知欄右滑顯示控制中心模塊的流程分析

    ? 在android12.0的系統(tǒng)rom定制化開發(fā)中,在系統(tǒng)原生systemui進(jìn)行自定義下拉狀態(tài)欄布局的定制的時候,需要在systemui下拉狀態(tài)欄下滑的時候,根據(jù)下滑坐標(biāo)來 判斷當(dāng)前是滑出通知欄還是滑出控制中心模塊,所以就需要根據(jù)屏幕寬度,來區(qū)分x坐標(biāo)值為多少是左滑出通知欄或者右滑

    2024年02月09日
    瀏覽(127)
  • Android 讀取系統(tǒng)屬性詳解

    ????? ???Android 系統(tǒng)屬性主要有兩種:SettingsProvider 和 SystemProperties 。 /data/system/users/0/ 下的對應(yīng)xml文件: settings_global.xml settings_secure.xml settings_system.xml 狀態(tài)值進(jìn)行存儲: frameworks/base/packages/SettingsProvider/src/com/android/providers/settings/DatabaseHelper.java 設(shè)置的成員定義: framework

    2024年02月05日
    瀏覽(15)
  • Android 11 添加系統(tǒng)屬性

    在初識Android 屬性一文中提到,系統(tǒng)會默認(rèn)加載以下文件 要弄清楚我們應(yīng)該在哪里添加系統(tǒng)屬性,就要知道這些文件是怎么生成的。以/system/build.prop文件為例,來分析下其生成過程 屬性文件的生成邏輯集中在buildmakecoreMakefile文件中 注釋1處,將ADDITIONAL_BUILD_PROPERTIES定義的屬

    2024年04月08日
    瀏覽(20)
  • Android SystemUI梳理

    Android SystemUI梳理

    ?? 團隊博客: 汽車電子社區(qū) ??在Android系統(tǒng)中SystemUI是一個系統(tǒng)級的APP,它提供了系統(tǒng)的用戶界面,由system_server進(jìn)程啟動。SystemUI本身不屬于system_server進(jìn)程,它是一個獨立的進(jìn)程。它的HMI包括了狀態(tài)欄、導(dǎo)航欄、通知欄、鎖屏、近期任務(wù)等等。 ??SystemServer是一個由Zog

    2024年01月20日
    瀏覽(20)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包