AndroidQ SystemUI之锁屏加载(上)滑动锁屏

本篇来分析下Android锁屏的加载流程,锁屏加载比较复杂,涉及framework和SystemUI,这篇主要分析SystemUI部分,锁屏有两种,一种滑动锁屏,另一种密码锁屏,也叫Bouncer。
前面AndroidQ SystemUI之启动 中分析了SystemUI启动过程中会加载一个config数组,里面定义了SystemUI的重要的类,之后遍历此数组,以此调用其Start方法,我们就从StatusBar.start方法开始分析锁屏相关的流程

StatusBar.start

public void start() {
	...
	createAndAddWindows(result);
	...
}
public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
        makeStatusBarView(result);
        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
        mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
    }

makeStatusBarView方法里面会初始化很多SystemUI的View

makeStatusBarView

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
		
		...
        inflateStatusBarWindow(context);
        ...
}

inflateStatusBarWindow

protected void inflateStatusBarWindow(Context context) {
        mStatusBarWindow = (StatusBarWindowView) mInjectionInflater.injectable(
                LayoutInflater.from(context)).inflate(R.layout.super_status_bar, null);
    }

这个方法,加载了整个StatusBar的顶层自定义布局,类型为StatusBarWindowView,继承FrameLayout,布局文件是super_status_bar.xml,

<com.android.systemui.statusbar.phone.StatusBarWindowView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:sysui="http://schemas.android.com/apk/res-auto"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#698541"
    android:fitsSystemWindows="true">

    <com.android.systemui.statusbar.BackDropView
            android:id="@+id/backdrop"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:visibility="gone"
            sysui:ignoreRightInset="true"
            >
        <ImageView android:id="@+id/backdrop_back"
                   android:layout_width="match_parent"
                   android:scaleType="centerCrop"
                   android:layout_height="match_parent" />
        <ImageView android:id="@+id/backdrop_front"
                   android:layout_width="match_parent"
                   android:layout_height="match_parent"
                   android:scaleType="centerCrop"
                   android:visibility="invisible" />
    </com.android.systemui.statusbar.BackDropView>

    <com.android.systemui.statusbar.ScrimView
        android:id="@+id/scrim_behind"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
        />

    <FrameLayout
        android:id="@+id/status_bar_container"
        android:layout_width="match_parent"
        android:layout_height="wrap_content" />

    <include layout="@layout/status_bar_expanded"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:visibility="invisible" />

    <include layout="@layout/brightness_mirror" />

    <com.android.systemui.statusbar.ScrimView
        android:id="@+id/scrim_in_front"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:importantForAccessibility="no"
        sysui:ignoreRightInset="true"
    />

    <LinearLayout
        android:id="@+id/lock_icon_container"
        android:orientation="vertical"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_marginTop="@dimen/status_bar_height"
        android:layout_gravity="top|center_horizontal">
        <com.android.systemui.statusbar.phone.LockIcon
            android:id="@+id/lock_icon"
            android:layout_width="@dimen/keyguard_lock_width"
            android:layout_height="@dimen/keyguard_lock_height"
            android:layout_gravity="center_horizontal"
            android:layout_marginTop="@dimen/keyguard_lock_padding"
            android:contentDescription="@string/accessibility_unlock_button"
            android:src="@*android:drawable/ic_lock"
            android:scaleType="center" />
        <com.android.keyguard.KeyguardMessageArea
            android:id="@+id/keyguard_message_area"
            style="@style/Keyguard.TextView"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_marginTop="@dimen/keyguard_lock_padding"
            android:gravity="center"
            android:singleLine="true"
            android:ellipsize="marquee"
            android:focusable="true" />
    </LinearLayout>
</com.android.systemui.statusbar.phone.StatusBarWindowView>

接着我们看下StatusBarWindowView的onFinishInflate方法

onFinishInflate

 @Override
    protected void onFinishInflate() {
        super.onFinishInflate();
        mStackScrollLayout = findViewById(R.id.notification_stack_scroller);
        mNotificationPanel = findViewById(R.id.notification_panel);
        mBrightnessMirror = findViewById(R.id.brightness_mirror);
        mLockIcon = findViewById(R.id.lock_icon);
    }

这里分别加载了id为notification_stack_scroller,notification_panel,brightness_mirror,lock_icon的View,
notification_stack_scroller是锁屏上承载notification的View
notification_panel是滑动锁屏
brightness_mirror是调节亮度条的View
lock_icon是锁屏上的那个锁一样的View

再回到makeStatusBarView方法

protected void makeStatusBarView(@Nullable RegisterStatusBarResult result) {
		
		...
		//此方法已经完成StatusBar顶层View,已经滑动锁屏相关View的加载
        inflateStatusBarWindow(context);
        //获取滑动锁屏的View
        mNotificationPanel = mStatusBarWindow.findViewById(R.id.notification_panel);
        //获取锁屏上承载通知的View
        mStackScroller = mStatusBarWindow.findViewById(R.id.notification_stack_scroller);
        
        ...
}

makeStatusBarView方法中获取到了滑动锁屏的View,再回到createAndAddWindows

public void createAndAddWindows(@Nullable RegisterStatusBarResult result) {
        makeStatusBarView(result);
        mStatusBarWindowController = Dependency.get(StatusBarWindowController.class);
        mStatusBarWindowController.add(mStatusBarWindow, getStatusBarHeight());
    }

mStatusBarWindowController.add

下面代码很明显了,通过mWindowManager添加了一个窗口,这样SystemUI的顶层自定义ViewGroup就被添加到了WMS,自然,滑动锁屏作为mStatusBarWindow的子View也就被添加了

 public void add(ViewGroup statusBarView, int barHeight) {

        //创建LayoutParams,定义了一些窗口属性
        mLp = new WindowManager.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT,
                barHeight,
                WindowManager.LayoutParams.TYPE_STATUS_BAR,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_TOUCHABLE_WHEN_WAKING
                        | WindowManager.LayoutParams.FLAG_SPLIT_TOUCH
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH
                        | WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS,
                PixelFormat.TRANSLUCENT);
                //创建Token
        mLp.token = new Binder();
        //位置在最顶部
        mLp.gravity = Gravity.TOP;
        mLp.softInputMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
        mLp.setTitle("StatusBar");
        mLp.packageName = mContext.getPackageName();
        mLp.layoutInDisplayCutoutMode = LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS;
        mStatusBarView = statusBarView;
        mBarHeight = barHeight;
        //添加到WMS
        mWindowManager.addView(mStatusBarView, mLp);
        mLpChanged.copyFrom(mLp);
        onThemeChanged();
    }

我们会发现密码锁屏并不是直接写死在xml文件中的,密码锁屏是通过动态加载的,我们接下来分析密码锁屏是如何创建以及添加到Keyguard布局中的
回到最开始的StatusBar的start方法中,在调用createAndAddWindows加载完SyatemUI相关View之后接着会调用startKeyguard方法开启锁屏流程

```java
public void start() {
	...
	createAndAddWindows(result);
	...
	startKeyguard();
}

startKeyguard

protected void startKeyguard() {
        KeyguardViewMediator keyguardViewMediator = getComponent(KeyguardViewMediator.class);
        ...
        mStatusBarKeyguardViewManager = keyguardViewMediator.registerStatusBar(this,
                getBouncerContainer(), mNotificationPanel, mBiometricUnlockController,
                mStatusBarWindow.findViewById(R.id.lock_icon_container));
               
        ...
    }

首先获取了一个锁屏非常重要的类KeyguardViewMediator,这个类主要负责处理锁屏相关事务,接着调用registerStatusBar方法,这里传了一个getBouncerContainer()的ViewGroup过去,这个ViewGroup就是StartBar中加载的SystemUI最顶层ViewGroup,布局为super_status_barStatusBarWindowView,后面创建的密码锁屏会动态添加到这里面

keyguardViewMediator.registerStatusBar

 public StatusBarKeyguardViewManager registerStatusBar(StatusBar statusBar,
            ViewGroup container, NotificationPanelView panelView,
            BiometricUnlockController biometricUnlockController, ViewGroup lockIconContainer) {
        mStatusBarKeyguardViewManager.registerStatusBar(statusBar, container, panelView,
                biometricUnlockController, mDismissCallbackRegistry, lockIconContainer);
        return mStatusBarKeyguardViewManager;
    }

接着调用

StatusBarKeyguardViewManager.registerStatusBar

public void registerStatusBar(StatusBar statusBar,
            ViewGroup container,
            NotificationPanelView notificationPanelView,
            BiometricUnlockController biometricUnlockController,
            DismissCallbackRegistry dismissCallbackRegistry,
            ViewGroup lockIconContainer) {
        mStatusBar = statusBar;
        //StatusBar传递过来的SystemUI顶层ViewGroup
        mContainer = container;
        //锁屏上承载lockIcon的ViewGroup
        mLockIconContainer = lockIconContainer;
        if (mLockIconContainer != null) {
            mLastLockVisible = mLockIconContainer.getVisibility() == View.VISIBLE;
        }
        //生物识别相关
        mBiometricUnlockController = biometricUnlockController;
        //创建密码锁屏Bouncer
        mBouncer = SystemUIFactory.getInstance().createKeyguardBouncer(mContext,
                mViewMediatorCallback, mLockPatternUtils, container, dismissCallbackRegistry,
                mExpansionCallback);
        mNotificationPanelView = notificationPanelView;
        //给滑动锁屏界面设置监听器
        notificationPanelView.setExpansionListener(this);
    }

来看看Bouncer的创建

SystemUIFactory.createKeyguardBouncer

 public KeyguardBouncer createKeyguardBouncer(Context context, ViewMediatorCallback callback,
            LockPatternUtils lockPatternUtils,  ViewGroup container,
            DismissCallbackRegistry dismissCallbackRegistry,
            KeyguardBouncer.BouncerExpansionCallback expansionCallback) {
        return new KeyguardBouncer(context, callback, lockPatternUtils, container,
                dismissCallbackRegistry, FalsingManagerFactory.getInstance(context),
                expansionCallback, KeyguardUpdateMonitor.getInstance(context),
                new Handler(Looper.getMainLooper()));
    }

这里直接new了一个Bouncer对象,我们跟进KeyguardBouncer这个类会发现它并没有继承View或者ViewGroup,说明它并不是真正的密码锁屏的View,KeyguardBouncer其实是用来管理密码锁屏的,从它提供的方法就能看出来,show,hide,inflateView等

public class KeyguardBouncer {
public KeyguardBouncer(Context context, ViewMediatorCallback callback,
            LockPatternUtils lockPatternUtils, ViewGroup container,
            DismissCallbackRegistry dismissCallbackRegistry, FalsingManager falsingManager,
            BouncerExpansionCallback expansionCallback,
            KeyguardUpdateMonitor keyguardUpdateMonitor, Handler handler) {
        mContext = context;
        mCallback = callback;
        mLockPatternUtils = lockPatternUtils;
        mContainer = container;
        mKeyguardUpdateMonitor = keyguardUpdateMonitor;
        mFalsingManager = falsingManager;
        mDismissCallbackRegistry = dismissCallbackRegistry;
        mExpansionCallback = expansionCallback;
        mHandler = handler;
        mKeyguardUpdateMonitor.registerCallback(mUpdateMonitorCallback);
    }
 }

到这里我们发现从StatusBar.start->StatusBar.startKeyguard->KeyguardViewMediator.registerStatusBar->StatusBarKeyguardViewManager.registerStatusBar->SystemUIFactory.createKeyguardBouncer->new KeyguardBouncer
在KeyguardBouncer构造方法中也只是做了些初始化,并没有涉及到和密码锁屏的创建及添加相关的操作

其实不管是滑动锁屏还是密码锁屏加载都是通过KeyguardViewMediator中的doKeyguardLocked方法,此方法非常重要,可以说是整个锁屏的核心方法,任何形式的锁屏,开机启动锁屏,点击power键锁屏,SIM卡状态变化锁屏都会调用此方法,我们就从此方法为入口分析锁屏加载流程

KeyguardViewMediator.doKeyguardLocked

private void doKeyguardLocked(Bundle options) {
       //省略一些不需要加载锁屏的情况,如外部禁用,系统没准备好等
       	....
        showLocked(options);
    }
private void showLocked(Bundle options) {
       
        mShowKeyguardWakeLock.acquire();
        Message msg = mHandler.obtainMessage(SHOW, options);
        mHandler.sendMessage(msg);
    }

通过Handler发送消息

private Handler mHandler = new Handler(Looper.myLooper(), null, true /*async*/) {
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case SHOW:
                    handleShow((Bundle) msg.obj);
                    break;

调用handleShow方法

private void handleShow(Bundle options) {
		  ....
         mStatusBarKeyguardViewManager.show(options);
         ....
    }

StatusBarKeyguardViewManager.show

public void show(Bundle options) {
        mShowing = true;
        ....
        reset(true /* hideBouncerWhenShowing */);
    }

reset

public void reset(boolean hideBouncerWhenShowing) {
        if (mShowing) {
            //不需要显示锁屏的情况
            if (mOccluded && !mDozing) {
                ...
            } else {
                showBouncerOrKeyguard(hideBouncerWhenShowing);
            }
			...
        }
    }

接着调用showBouncerOrKeyguard方法,此方法中就是判断显示滑动锁屏,还是显示密码锁屏,参数hideBouncerWhenShowing代表是否隐藏Bouncer

showBouncerOrKeyguard

 protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
        if (mBouncer.needsFullscreenBouncer() && !mDozing) {
            //隐藏滑动锁屏
            mStatusBar.hideKeyguard();
            //显示密码锁屏
            mBouncer.show(true /* resetSecuritySelection */);
        } else {
           //显示滑动锁屏
            mStatusBar.showKeyguard();
            if (hideBouncerWhenShowing) {
                hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
                mBouncer.prepare();
            }
        }
        updateStates();
    }

什么情况下会直接显示密码锁屏界面呢?

KeyguardBouncer.needsFullscreenBouncer

public boolean needsFullscreenBouncer() {
        ensureView();
        if (mKeyguardView != null) {
            SecurityMode mode = mKeyguardView.getSecurityMode();
            return mode == SecurityMode.SimPin || mode == SecurityMode.SimPuk;
        }
        return false;
    }

当前获取的Bouncer的类型是SimPin或者SimPuk时,通过mKeyguardView.getSecurityMode()获取当前Bouncer的类型时最终是调到KeyguardSecurityModel的getSecurityMode方法,通过KeyguardUpdateMonitor这个类获取当前SIM卡的状态来判断的,当状态是PIN_REQUIRED或者PUK_REQUIRED则说明应该直接显示类型为SecurityMode.SimPin或者SecurityMode.SimPuk的密码锁屏,所以当我们插入一张带密码的SIM卡时会立即显示sim pin界面

if (mIsPukScreenAvailable && SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PUK_REQUIRED))) {
            return SecurityMode.SimPuk;
        }

        if (SubscriptionManager.isValidSubscriptionId(
                monitor.getNextSubIdForState(IccCardConstants.State.PIN_REQUIRED))) {
            return SecurityMode.SimPin;
        }

我们再来看看needsFullscreenBouncer中调用的ensureView方法

protected void ensureView() {
        ...
        //首次调用mRoot为空
        if (mRoot == null || forceRemoval) {
            inflateView();
        }
    }

inflateView

protected void inflateView() {
       //在初始化View之前先移除,以保持干净
        removeView();
        mHandler.removeCallbacks(mRemoveViewRunnable);
        mRoot = (ViewGroup) LayoutInflater.from(mContext).inflate(R.layout.keyguard_bouncer, null);
        mKeyguardView = mRoot.findViewById(R.id.keyguard_host_view);
        mKeyguardView.setLockPatternUtils(mLockPatternUtils);
        mKeyguardView.setViewMediatorCallback(mCallback);
        mContainer.addView(mRoot, mContainer.getChildCount());
        mStatusBarHeight = mRoot.getResources().getDimensionPixelOffset(
                com.android.systemui.R.dimen.status_bar_height);
        mRoot.setVisibility(View.INVISIBLE);
        mRoot.setAccessibilityPaneTitle(mKeyguardView.getAccessibilityTitleForCurrentMode());

        final WindowInsets rootInsets = mRoot.getRootWindowInsets();
        if (rootInsets != null) {
            mRoot.dispatchApplyWindowInsets(rootInsets);
        }
    }

此方法代码很明显,就是加载布局,加载View,然后addView,mRoot是名为keyguard_bouncer的layout,这是Bouncer的根布局

<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/black"
    android:fitsSystemWindows="true">

    <include
        style="@style/BouncerSecurityContainer"
        layout="@layout/keyguard_host_view"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />
</FrameLayout>

这里面直接include了一个名为keyguard_host_view的layout,KeyguardHostView是一个自定义ViewGroup,里面包含KeyguardSecurityContainer,KeyguardSecurityContainer立马包含KeyguardSecurityViewFlipper

<com.android.keyguard.KeyguardHostView
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:androidprv="http://schemas.android.com/apk/res-auto"
    android:id="@+id/keyguard_host_view"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:clipChildren="false"
    android:clipToPadding="false"
    android:importantForAccessibility="yes"> <!-- Needed because TYPE_WINDOW_STATE_CHANGED is sent
                                                  from this view when bouncer is shown -->

    <com.android.keyguard.KeyguardSecurityContainer
        android:id="@+id/keyguard_security_container"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        androidprv:layout_maxHeight="@dimen/keyguard_security_max_height"
        android:clipChildren="false"
        android:clipToPadding="false"
        android:padding="0dp"
        android:fitsSystemWindows="true"
        android:layout_gravity="center">
        <com.android.keyguard.KeyguardSecurityViewFlipper
            android:id="@+id/view_flipper"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clipChildren="false"
            android:clipToPadding="false"
            android:paddingTop="@dimen/keyguard_security_view_top_margin"
            android:paddingStart="@dimen/keyguard_security_view_lateral_margin"
            android:paddingEnd="@dimen/keyguard_security_view_lateral_margin"
            android:gravity="center">
        </com.android.keyguard.KeyguardSecurityViewFlipper>
    </com.android.keyguard.KeyguardSecurityContainer>

</com.android.keyguard.KeyguardHostView>

接着通过addView将mRoot添加到mContainer,mContainer我们前面有分析过,它是StatusBar传递过来的SystemUI顶层ViewGroup,接着mRoot.setVisibility(View.INVISIBLE)让mRoot暂时不可见
ensureView分析完了,此方法也只是初始化一些layout

接着再回到showBouncerOrKeyguard中去

showBouncerOrKeyguard

 protected void showBouncerOrKeyguard(boolean hideBouncerWhenShowing) {
        if (mBouncer.needsFullscreenBouncer() && !mDozing) {
            mStatusBar.hideKeyguard();
            mBouncer.show(true /* resetSecuritySelection */);
        } else {
            mStatusBar.showKeyguard();
            if (hideBouncerWhenShowing) {
                hideBouncer(shouldDestroyViewOnReset() /* destroyView */);
                mBouncer.prepare();
            }
        }
        updateStates();
    }

我们分析了直接显示Bouncer的情况,是在SIM卡状态为PIN_REQUIRED或者PUK_REQUIRED时,如果是普通情况则会调用StatusBar.showKeyguard显示滑动锁屏

StatusBar.showKeyguard

public void showKeyguard() {
        ...
        updateIsKeyguard();
        ...
    }

updateIsKeyguard

private boolean updateIsKeyguard() {
        ...
        boolean shouldBeKeyguard = (mStatusBarStateController.isKeyguardRequested()
                || keyguardForDozing) && !wakeAndUnlocking;
        if (shouldBeKeyguard) {
            if (isGoingToSleep()
                    && mScreenLifecycle.getScreenState() == ScreenLifecycle.SCREEN_TURNING_OFF) {
                // Delay showing the keyguard until screen turned off.
            } else {
                showKeyguardImpl();
            }
        } else {
            return hideKeyguardImpl();
        }
        return false;
    }

满足显示Keyguard的条件shouldBeKeyguard则调用showKeyguardImpl

showKeyguardImpl

public void showKeyguardImpl() {
        ...
        updatePanelExpansionForKeyguard();
       	...
        }
    }

updatePanelExpansionForKeyguard

private void updatePanelExpansionForKeyguard() {
	//状态为KEYGUARD,不是MODE_WAKE_AND_UNLOCK模式,不显示Bouncer
        if (mState == StatusBarState.KEYGUARD && mBiometricUnlockController.getMode()
                != BiometricUnlockController.MODE_WAKE_AND_UNLOCK && !mBouncerShowing) {
            instantExpandNotificationsPanel();
        } else if (mState == StatusBarState.FULLSCREEN_USER_SWITCHER) {
            instantCollapseNotificationPanel();
        }
    }

instantExpandNotificationsPanel

@Override
    public void instantExpandNotificationsPanel() {
        makeExpandedVisible(true);
        //展开滑动锁屏
        mNotificationPanel.expand(false /* animate */);
        
    }

makeExpandedVisible

void makeExpandedVisible(boolean force) {
        ...
        mStatusBarWindowController.setPanelVisible(true);
        ...
    }

StatusBarWindowController.setPanelVisible

public void setPanelVisible(boolean visible) {
        ...
        apply(mCurrentState);
    }

apply

private void apply(State state) {
        applyKeyguardFlags(state);
        applyForceStatusBarVisibleFlag(state);
        applyFocusableFlag(state);
        applyForceShowNavigationFlag(state);
        adjustScreenOrientation(state);
        applyHeight(state);
        applyUserActivityTimeout(state);
        applyInputFeatures(state);
        applyFitsSystemWindows(state);
        applyModalFlag(state);
        applyBrightness(state);
        applyHasTopUi(state);
        applyNotTouchable(state);
        applyStatusBarColorSpaceAgnosticFlag(state);
        if (mLp != null && mLp.copyFrom(mLpChanged) != 0) {
            mWindowManager.updateViewLayout(mStatusBarView, mLp);
        }
        if (mHasTopUi != mHasTopUiChanged) {
            try {
                mActivityManager.setHasTopUi(mHasTopUiChanged);
            } catch (RemoteException e) {
                Log.e(TAG, "Failed to call setHasTopUi", e);
            }
            mHasTopUi = mHasTopUiChanged;
        }
        notifyStateChangedCallbacks();
    }

设置了一些数据,接着调用mNotificationPanel.expand(true)展开滑动锁屏,这样我们就能看到锁屏界面了,到此为止滑动锁屏的加载已经分析完了,只分析了一个大概流程,里面有很多细节的东西需要实际应用到之后再去跟代码

下一篇我们接着分析密码锁屏的创建以及滑动锁屏上滑后显示密码锁屏的的流程相关

发布了39 篇原创文章 · 获赞 57 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/qq_34211365/article/details/104556505