Android9.0系統(tǒng)源碼WMS(四)NavigationBar和StatusBar窗口大小與布局分析

前言

NavigationBar 和 StatusBar 都屬于 SystemBar,也叫做 decor,就是說給 App 裝飾的意思。一般的 window 的布局是在 PhoneWindowManager 的 layoutWindowLw() 方法中,而 SystemBar 是在 beginLayoutLw() 方法中布局。

當前最上層的 Activity 可以修改 SystemBar 的 visibility,可以調(diào)用 View#setSystemUiVisibility() 方法,系統(tǒng)也有一些針對 SystemBar visibility 的策略。最終的 visibility 保存在 PhoneWindowManager 中的 mLastSystemUiFlags 變量中。

一、簡單認識Android9中的DisplayFrames

mDisplayId 是跟物理屏幕相關(guān)的,DEFAULT_DISPLAY 的值是 0,mDisplayWidth 是物理屏幕寬度,mDisplayHeight 是物理屏幕高度。

public class DisplayFrames {
    public final int mDisplayId;

    /**
     * The current size of the screen; really; extends into the overscan area of the screen and
     * doesn't account for any system elements like the status bar.
     */
    //當前的屏幕大小,包括過掃描區(qū)域。過掃描區(qū)域在輸出到 TV 時會用到,
    // 對于移動設(shè)備來說 mOverscan 大小就是物理設(shè)備的大小 (0,0)-(dw,dh)。
    public final Rect mOverscan = new Rect();

    /**
     * The current visible size of the screen; really; (ir)regardless of whether the status bar can
     * be hidden but not extending into the overscan area.
     */
    //當前可見的屏幕大小,其實就是 (0,0)-(dw,dh)。
    public final Rect mUnrestricted = new Rect();

    /** Like mOverscan*, but allowed to move into the overscan region where appropriate. */
    //是應(yīng)用可顯示的區(qū)域,包含 StatusBar 的區(qū)域,不包含 NavigationBar 區(qū)域。
    public final Rect mRestrictedOverscan = new Rect();
    /**
     * The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar
     * can't be hidden; in that case it effectively carves out that area of the display from all
     * other windows.
     */
    //一般情況下 mRestrictedOverscan 與 mRestricted 相同
    public final Rect mRestricted = new Rect();

    /**
     * During layout, the current screen borders accounting for any currently visible system UI
     * elements.
     */
    //是布局過程中,當前畫面的邊界,包含 Translucent(半透明)區(qū)域。一般情況下 NavigationBar 區(qū)域不是 Translucent,而 StatusBar 是 Translucent。
    public final Rect mSystem = new Rect();

    /** For applications requesting stable content insets, these are them. */
    //是應(yīng)用窗口的顯示區(qū)域,不包含 StatusBar 和 NavigationBar。
    public final Rect mStable = new Rect();

    /**
     * For applications requesting stable content insets but have also set the fullscreen window
     * flag, these are the stable dimensions without the status bar.
     */
    //是當Activity設(shè)置Fullscreen flag 時候的窗口顯示區(qū)域,這時 StatusBar 會隱藏。
    public final Rect mStableFullscreen = new Rect();

    /**
     * During layout, the current screen borders with all outer decoration (status bar, input method
     * dock) accounted for.
     */
    // 是布局的時候除去外部裝飾的窗口(例如 StatusBar 和輸入法窗口)。
    public final Rect mCurrent = new Rect();

    /**
     * During layout, the frame in which content should be displayed to the user, accounting for all
     * screen decoration except for any space they deem as available for other content. This is
     * usually the same as mCurrent*, but may be larger if the screen decor has supplied content
     * insets.
     */
    // 是當前應(yīng)該給用戶顯示的窗口,通常與 mCurrent 相同。當裝飾窗口提供內(nèi)容插入的時候,有可能比 mCurrent 更大。
    public final Rect mContent = new Rect();

    /**
     * During layout, the frame in which voice content should be displayed to the user, accounting
     * for all screen decoration except for any space they deem as available for other content.
     */
    //mVoiceContent 通常與 mContent 相同。
    public final Rect mVoiceContent = new Rect();

    /** During layout, the current screen borders along which input method windows are placed. */
    //mDock 是輸入法布局時的邊界。
    public final Rect mDock = new Rect();

    /** The display cutout used for layout (after rotation) */
    //用于劉海屏布局的剪刀工具,Android 9.0 新加入的。
    @NonNull public WmDisplayCutout mDisplayCutout = WmDisplayCutout.NO_CUTOUT;

    /** The cutout as supplied by display info */
    //用于劉海屏布局的剪刀工具,Android 9.0 新加入的。
    @NonNull public WmDisplayCutout mDisplayInfoCutout = WmDisplayCutout.NO_CUTOUT;

    /**
     * During layout, the frame that is display-cutout safe, i.e. that does not intersect with it.
     */
    //是在劉海屏上可以安全顯示的區(qū)域,即這個區(qū)域與劉海區(qū)域沒有交集。
    public final Rect mDisplayCutoutSafe = new Rect();

二、系統(tǒng)對SystemBar的布局

1、Android9系統(tǒng)主要是在PhoneWindowManager的beginLayoutLw() 方法中對系統(tǒng)的SystemBar進行布局的。

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

public class PhoneWindowManager implements WindowManagerPolicy {
   ...代碼省略...
    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
        displayFrames.onBeginLayout();
        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
        mDockLayer = 0x10000000;
        mStatusBarLayer = -1;
        final Rect pf = mTmpParentFrame;
        final Rect df = mTmpDisplayFrame;
        final Rect of = mTmpOverscanFrame;
        final Rect vf = mTmpVisibleFrame;
        final Rect dcf = mTmpDecorFrame;
        vf.set(displayFrames.mDock);
        of.set(displayFrames.mDock);
        df.set(displayFrames.mDock);
        pf.set(displayFrames.mDock);
        dcf.setEmpty();  // Decor frame N/A for system bars.
        if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
            // For purposes of putting out fake window up to steal focus, we will
            // drive nav being hidden only by whether it is requested.
            //獲取窗口systemui的標記類型
            final int sysui = mLastSystemUiFlags;
            //navigationBar是否可見
            boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
            //navigationBar是否是半透明的
            boolean navTranslucent = (sysui & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
            boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
            boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
            //navigationBar是否允許隱藏
            boolean navAllowedHidden = immersive || immersiveSticky;
            navTranslucent &= !immersiveSticky;  // transient trumps translucent
            boolean isKeyguardShowing = isStatusBarKeyguard() && !mKeyguardOccluded;
            if (!isKeyguardShowing) {
                navTranslucent &= areTranslucentBarsAllowed();
            }
            boolean statusBarExpandedNotKeyguard = !isKeyguardShowing && mStatusBar != null
                    && mStatusBar.getAttrs().height == MATCH_PARENT
                    && mStatusBar.getAttrs().width == MATCH_PARENT;

            if (navVisible || navAllowedHidden) {
                if (mInputConsumer != null) {
                    mHandler.sendMessage(
mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer));
                    mInputConsumer = null;
                }
            } else if (mInputConsumer == null && mStatusBar != null && canHideNavigationBar()) {
                mInputConsumer = mWindowManagerFuncs.createInputConsumer(mHandler.getLooper(),
                        INPUT_CONSUMER_NAVIGATION,
                        (channel, looper) -> new HideNavInputEventReceiver(channel, looper));
InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
            }

            navVisible |= !canHideNavigationBar();
            //調(diào)用layoutNavigationBar對NavigationBar進行布局
            boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
            if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
            //調(diào)用layoutStatusBar對StatusBar進行布局
            updateSysUiVisibility |= layoutStatusBar(
                    displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
            if (updateSysUiVisibility) {
                updateSystemUiVisibilityLw();
            }
        }
        layoutScreenDecorWindows(displayFrames, pf, df, dcf);
        if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
                    displayFrames.mStable.top);
        }
    }
   ...代碼省略...
}

beginLayoutLw方法中首先會判斷NavigationBar是否可見以及是否半透明,然后會
調(diào)用layoutNavigationBar對NavigationBar進行布局,隨后還會調(diào)用layoutStatusBar對StatusBar進行布局。

三、布局導(dǎo)航欄的關(guān)鍵方法layoutNavigationBar

1、下面是layoutNavigationBar的相關(guān)代碼,layoutNavigationBar方法首先會調(diào)用 navigationBarPosition()方法返回 NavigationBar 的位置,根據(jù)返回的位置的不同所執(zhí)行的布局方式也會不同。這里以布局在下邊為例,會計算 NavigationBar 的 top,然后更新 mTmpNavigationFrame和 DisplayFrames 的相關(guān)屬性。其中比較重要的是更新DisplayFrames.mDock變量,然后用這個變量設(shè)置 mCurrent、mVoiceContent、mContent` 等變量。

    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
            boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
            boolean statusBarExpandedNotKeyguard) {
        if (mNavigationBar == null) {
            return false;
        }
        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();
        //根據(jù)屏幕旋轉(zhuǎn)角度,我們需要為導(dǎo)航欄設(shè)置相對應(yīng)的合適位置和大小
        final int rotation = displayFrames.mRotation;
        final int displayHeight = displayFrames.mDisplayHeight;
        final int displayWidth = displayFrames.mDisplayWidth;
        final Rect dockFrame = displayFrames.mDock;
        //獲取導(dǎo)航欄的位置
        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);

        //cutoutSafeUnrestricted是安全的窗口(Android9針對劉海平新增的),當沒有Overscan的時候與mUnrestricted相同
        //即cutoutSafeUnrestricted.bottom的值與DisplayFrames.mDisplayHeight值相同。
        final Rect cutoutSafeUnrestricted = mTmpRect;
        cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
        cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

        //導(dǎo)航欄在底部
        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
            //計算導(dǎo)航欄的top,
            final int top = cutoutSafeUnrestricted.bottom - getNavigationBarHeight(rotation, uiMode);
            //mTmpNavigationFrame就是NavigationBar所對應(yīng)的窗口區(qū)域。
            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
            //mStable對應(yīng)應(yīng)用窗口的顯示區(qū)域,mFullscreen對應(yīng)應(yīng)用窗口全屏的顯示區(qū)域,這里的設(shè)置使得應(yīng)用窗口正常狀態(tài)和全屏的時候都在導(dǎo)航欄的上方
            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
            if (transientNavBarShowing) {
                mNavigationBarController.setBarShowingLw(true);
            } else if (navVisible) {
                //如果NavigationBar可見的話,更新dockFrame、mRestricted、mRestrictedOverscan的bottom值
                mNavigationBarController.setBarShowingLw(true);
                dockFrame.bottom = displayFrames.mRestricted.bottom = displayFrames.mRestrictedOverscan.bottom = top;
            } else {
                // We currently want to hide the navigation UI - unless we expanded the status bar.
                mNavigationBarController.setBarShowingLw(statusBarExpandedNotKeyguard);
            }
            if (navVisible && !navTranslucent && !navAllowedHidden
                    && !mNavigationBar.isAnimatingLw()
                    && !mNavigationBarController.wasRecentlyTranslucent()) {
                // If the opaque nav bar is currently requested to be visible and not in the process
                // of animating on or off, then we can tell the app that it is covered by it.
                displayFrames.mSystem.bottom = top;
            }
        } else if (mNavigationBarPosition == NAV_BAR_RIGHT) {
            ...代碼省略...
        } else if (mNavigationBarPosition == NAV_BAR_LEFT) {
            ...代碼省略...
        }

        //使用dockFrame的參數(shù)去更新mCurrent,mVoiceContent,mContent
        displayFrames.mCurrent.set(dockFrame);
        displayFrames.mVoiceContent.set(dockFrame);
        displayFrames.mContent.set(dockFrame);
        mStatusBarLayer = mNavigationBar.getSurfaceLayer();
        //計算NavigationBar的contentFrame大小
        mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
                mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf,
                mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe,
                displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);
        //將NavigationBar的contentFrame綁定到mNavigationBarController。
        mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());
        if (DEBUG_LAYOUT) Slog.i(TAG, "mNavigationBar frame: " + mTmpNavigationFrame);
        return mNavigationBarController.checkHiddenLw();
    }

NavigationBar 的 top 是 cutoutSafeUnrestricted.bottom 減去 NavigationBar 高度的結(jié)果。cutoutSafeUnrestricted 是安全的窗口(Android 9 針對劉海平新增的),當沒有 Overscan 的時候與 mUnrestricted 相同,即 cutoutSafeUnrestricted.bottom 的值與 DisplayFrames.mDisplayHeight 值相同。計算 NavigationBar 的 top 后設(shè)置 mTmpNavigationFrame。mTmpNavigationFrame 就是 NavigationBar 的窗口區(qū)域。
之后更新 mStable 和 mFullscreen 的 bottom 值為 top。mStable 就是應(yīng)用窗口的顯示區(qū)域,這就是 NavigationBar 占用應(yīng)用顯示區(qū)域的原因。
如果 NavigationBar 可見的話更新 dockFrame、mRestricted、mRestrictedOverscan 的 bottom 值。然后用 dockFrame 去更新 mCurrent、mVoiceContent、mContent。
最后調(diào)用 computeFrameLw() 方法計算 NavigationBar 的 contentFrame 大小,然后綁定到 mNavigationBarController。

四、布局狀態(tài)欄的關(guān)鍵方法layoutStatusBar

StatusBar 的布局在 PhoneWindowManager 的 beginLayoutLw() 方法中,調(diào)用 layoutStatusBar() 方法。

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

public class PhoneWindowManager implements WindowManagerPolicy {
    @Override
    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
        boolean updateSysUiVisibility |= layoutNavigationBar(displayFrames, uiMode, dcf,
                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
        updateSysUiVisibility |= layoutStatusBar(
                    displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
        if (updateSysUiVisibility) {
            updateSystemUiVisibilityLw();
        }
    }

    private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
            Rect dcf, int sysui, boolean isKeyguardShowing) {
        // decide where the status bar goes ahead of time
        if (mStatusBar == null) {
            return false;
        }
        // apply any navigation bar insets
        of.set(displayFrames.mUnrestricted);
        df.set(displayFrames.mUnrestricted);
        pf.set(displayFrames.mUnrestricted);
        vf.set(displayFrames.mStable);

        mStatusBarLayer = mStatusBar.getSurfaceLayer();

        // Let the status bar determine its size.
        mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
                vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
                dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
                displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);

        // For layout, the status bar is always at the top with our fixed height.
        displayFrames.mStable.top = displayFrames.mUnrestricted.top
                + mStatusBarHeightForRotation[displayFrames.mRotation];
        // Make sure the status bar covers the entire cutout height
        displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
                displayFrames.mDisplayCutoutSafe.top);

        // Tell the bar controller where the collapsed status bar content is
        mTmpRect.set(mStatusBar.getContentFrameLw());
        mTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
        mTmpRect.top = mStatusBar.getContentFrameLw().top;  // Ignore top display cutout inset
        mTmpRect.bottom = displayFrames.mStable.top;  // Use collapsed status bar size
        mStatusBarController.setContentFrame(mTmpRect);

        boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
        boolean statusBarTranslucent = (sysui
                & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
        if (!isKeyguardShowing) {
            statusBarTranslucent &= areTranslucentBarsAllowed();
        }

        // If the status bar is hidden, we don't want to cause windows behind it to scroll.
        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
            // Status bar may go away, so the screen area it occupies is available to apps but just
            // covering them when the status bar is visible.
            final Rect dockFrame = displayFrames.mDock;
            dockFrame.top = displayFrames.mStable.top;
            displayFrames.mContent.set(dockFrame);
            displayFrames.mVoiceContent.set(dockFrame);
            displayFrames.mCurrent.set(dockFrame);

            if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
                    "dock=%s content=%s cur=%s", dockFrame.toString(),
                    displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));

            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                    && !mStatusBarController.wasRecentlyTranslucent()) {
                // If the opaque status bar is currently requested to be visible, and not in the
                // process of animating on or off, then we can tell the app that it is covered by it.
                displayFrames.mSystem.top = displayFrames.mStable.top;
            }
        }
        return mStatusBarController.checkHiddenLw();
}}

首先設(shè)定 of /* overscanFrame /、df / displayFrame /、pf / parentFrame /、vf / visibleFrame */,其中 of、df、pf 設(shè)定為 displayFrames.mUnrestricted,即屏幕大小。vf 設(shè)定為 displayFrames.mStable,mStable 的大小在調(diào)用 layoutNavigationBar() 方法后變成了除去 NavigationBar 的窗口。然后調(diào)用 mStatusBar.computeFrameLw() 方法計算 StatusBar 的 mContentFrame 大小。mContentFrame 在 IME 不存在時與 mDecorFrame 相同,IME 存在時是 mDecorFrame 除去 IME 窗口的大小。

 // apply any navigation bar insets
    of.set(displayFrames.mUnrestricted);
    df.set(displayFrames.mUnrestricted);
    pf.set(displayFrames.mUnrestricted);
    vf.set(displayFrames.mStable);
    
    mStatusBarLayer = mStatusBar.getSurfaceLayer();
    
    // Let the status bar determine its size.
    mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
            vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
            dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
            displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);

下一步修改 displayFrames.mStable,即應(yīng)用的窗口。因為 StatusBar 默認顯示在頂部的,所以修改 mStable.top 值。

    // For layout, the status bar is always at the top with our fixed height.
    displayFrames.mStable.top = displayFrames.mUnrestricted.top
            + mStatusBarHeightForRotation[displayFrames.mRotation];
    // Make sure the status bar covers the entire cutout height
    displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
            displayFrames.mDisplayCutoutSafe.top);

計算 StatusBar 的可顯示區(qū)域,綁定到 mStatusBarController。

    // Tell the bar controller where the collapsed status bar content is
    mTmpRect.set(mStatusBar.getContentFrameLw());
    mTmpRect.intersect(displayFrames.mDisplayCutoutSafe);
    mTmpRect.top = mStatusBar.getContentFrameLw().top;  // Ignore top display cutout inset
    mTmpRect.bottom = displayFrames.mStable.top;  // Use collapsed status bar size
    mStatusBarController.setContentFrame(mTmpRect);

判斷 StatusBar 是否是短暫顯示的(Transient)或是半透明的(Translucent)。其中 sysui 就是 mLastSystemUiFlags。

    boolean statusBarTransient = (sysui & View.STATUS_BAR_TRANSIENT) != 0;
    boolean statusBarTranslucent = (sysui
            & (View.STATUS_BAR_TRANSLUCENT | View.STATUS_BAR_TRANSPARENT)) != 0;
    if (!isKeyguardShowing) {
        statusBarTranslucent &= areTranslucentBarsAllowed();
    }

如果 StatusBar 是可見的,且不是暫時顯示的,則修改 displayFrames 的 mDock、mContent、mVoiceContent、mCurrent,其實就是除去 StatusBar 的窗口大小。還有,如果 StatusBar 是不透明的,則修改 mSystem.top 值為 mStable.top 值,這時 Activity 使用 @android:style/Theme.NoTitleBar.Fullscreen 也不會隱藏 StatusBar。

    // If the status bar is hidden, we don't want to cause windows behind it to scroll.
    if (mStatusBar.isVisibleLw() && !statusBarTransient) {
        // Status bar may go away, so the screen area it occupies is available to apps but just
        // covering them when the status bar is visible.
        final Rect dockFrame = displayFrames.mDock;
        dockFrame.top = displayFrames.mStable.top;
        displayFrames.mContent.set(dockFrame);
        displayFrames.mVoiceContent.set(dockFrame);
        displayFrames.mCurrent.set(dockFrame);
    
        if (DEBUG_LAYOUT) Slog.v(TAG, "Status bar: " + String.format(
                "dock=%s content=%s cur=%s", dockFrame.toString(),
                displayFrames.mContent.toString(), displayFrames.mCurrent.toString()));
    
        if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                && !mStatusBarController.wasRecentlyTranslucent()) {
            // If the opaque status bar is currently requested to be visible, and not in the
            // process of animating on or off, then we can tell the app that it is covered by it.
            displayFrames.mSystem.top = displayFrames.mStable.top;
        }
    }

參考文章:https://xianzhu21.space/developer/navigationbar-statusbar-frame-and-layout/

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

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

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