WindowManagerService本地窗口動(dòng)畫

一、前言

android的WindowManagerService(簡稱wms)是系統(tǒng)框架一個(gè)非常龐大復(fù)雜的一個(gè)系統(tǒng)模塊,它主要由三大塊組成:wms數(shù)據(jù)結(jié)構(gòu),wms大遍歷,wms的窗口動(dòng)畫


wms總體圖.png

wms數(shù)據(jù)結(jié)構(gòu)就是wms的所有WindowState(繼承windowcontainer)集合的數(shù)據(jù)結(jié)構(gòu),比如有ActivityRecord(包含1個(gè)或者多個(gè)WindowState),比如有WindowState,其中ActivityRecord具體表現(xiàn)實(shí)例就是Activity,WindowState具體表現(xiàn)實(shí)例有狀態(tài)欄、導(dǎo)航鍵、輸入法等。


window.png

wms大遍歷(performSurfacePlacement)就是對(duì)當(dāng)前所有存在的window進(jìn)行窗口大小計(jì)算和窗口繪制狀態(tài)更新,最后把窗口Surface更新到surfaceflinger。

wms的窗口動(dòng)畫是其中一個(gè)比較重要的子功能,wms的窗口動(dòng)畫負(fù)責(zé)窗口間的切換動(dòng)畫的實(shí)現(xiàn)。

接下來我們從android動(dòng)畫原理開始來逐步介紹wms的窗口動(dòng)畫

二、android動(dòng)畫的一個(gè)demo

android動(dòng)畫主要有三種類型:view的動(dòng)畫、window的動(dòng)畫、畫布對(duì)象的動(dòng)畫(ondraw里面的畫圖api)
首先我們來看一個(gè)android動(dòng)畫的簡單實(shí)現(xiàn)的demo

Choreographer mChoreographer = Choreographer.getInstance();
Animation mAnimation = null;

public void start(Animation anim) {
    mAnimation = anim;
    scheduleAnimation();
}

private void scheduleAnimation() {
    mChoreographer.postFrameCallback(Choreographer.CALLBACK_ANIMTION, mUpdateRunnable, null);
}

private Runnable mUpdateRunnable = new Runnable() {
    @Override
    public void run() {
        if (mAnimation != null) {
            long time = SystemClock.uptimeMillis();
            Transformation transform = new Transformation();
            //根據(jù)當(dāng)前time計(jì)算transform
            boolean more = mAnimation.getTransformation(time, transform);
            
            //根據(jù)transform進(jìn)行渲染,改變view的屬性(大小、位置、透明度等)?改變窗口的屬性(大小、位置、透明度等)?
            PERFORM_RENDER_WITH_TRANSFORMATION(transform);
            
            //通過time的計(jì)算可以計(jì)算出動(dòng)畫是否繼續(xù)還是結(jié)束
            if (more) {
                scheduleAnimation();
            } else {
                mAnimation = null;
            }
        }
    }
};

從這個(gè)例子可以看出android的動(dòng)畫就是借用Choreographer來通過vsync原理逐幀控制動(dòng)畫的播放(需要對(duì)Choreographer有一定的了解),中間update變量transform包含了動(dòng)畫的基本元素:Matrix、透明度,然后根據(jù)這兩個(gè)元素對(duì)顯示對(duì)象(view或者畫布對(duì)象或者window?)進(jìn)行當(dāng)前時(shí)間的繪制,逐幀顯示,最終用戶看到的就是一個(gè)動(dòng)畫,從systrace可以看到


動(dòng)畫systrace.png

ValueAnimator屬性動(dòng)畫的實(shí)現(xiàn)原理也是類似于這個(gè)demo的實(shí)現(xiàn)

三、WindowManagerService窗口動(dòng)畫機(jī)制

android的WindowManagerService窗口動(dòng)畫機(jī)制一直在優(yōu)化進(jìn)步,主要體現(xiàn)在:
1、在androidP以前的版本,主要是通過WindowAnimator主動(dòng)畫類中的mChoreographer來通過vsync原理逐幀控制窗口動(dòng)畫的播放
具體窗口的動(dòng)畫變化由WindowStateAnimator的stepAnimationLocked來控制,通過改變窗口的大小、位置、透明度(通過SurfaceControl代理實(shí)現(xiàn)對(duì)surfaceflinger的調(diào)用),來最終達(dá)到窗口動(dòng)畫的實(shí)現(xiàn)


wms歷史版本動(dòng)畫時(shí)序圖.png

有興趣的可以去仔細(xì)研究下這部分代碼的實(shí)現(xiàn),雖然是歷史版本的舊代碼,但是這個(gè)對(duì)wms的學(xué)習(xí)理解有很大的幫助。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimator.java */

/** Locked on mService.mWindowMap. */
private void animateLocked(long frameTimeNs) {

這個(gè)方案有個(gè)很大的缺陷,那就是動(dòng)畫的所有實(shí)現(xiàn)的代碼都包含在wms的主鎖mGlobalLock里面,從動(dòng)畫主要方法的命名后綴locked可以得知,那么意味動(dòng)畫會(huì)跟wms其他所有流程搶CPU資源,就容易導(dǎo)致wms主鎖的卡頓,在某些復(fù)雜的用戶場景下,容易導(dǎo)致手機(jī)的卡頓,給用戶帶來糟糕的體驗(yàn)。

2、在androidP及之后的版本,google對(duì)窗口動(dòng)畫進(jìn)行了重構(gòu),主要思想是通過ValueAnimator屬性動(dòng)畫來播放窗口動(dòng)畫,把窗口動(dòng)畫播放從wms主鎖脫離出來,這樣動(dòng)畫就不會(huì)占用wms資源,從而達(dá)到優(yōu)化系統(tǒng)框架運(yùn)行速度的效果,同時(shí)把部分動(dòng)畫放到app遠(yuǎn)端播放(比如狀態(tài)欄、導(dǎo)航鍵動(dòng)畫,比如多任務(wù)動(dòng)畫),達(dá)到系統(tǒng)和APP雙端協(xié)調(diào)播放復(fù)雜的跨端動(dòng)畫效果

3、wms的新窗口動(dòng)畫主要分為兩種類型,LocalAnimationAdapter和RemoteAnimationAdapter,分別實(shí)現(xiàn)了wms本地窗口動(dòng)畫和遠(yuǎn)程窗口動(dòng)畫。遠(yuǎn)程窗口動(dòng)畫機(jī)制,主要是為了實(shí)現(xiàn)android的兩個(gè)新功能特意開發(fā)的機(jī)制,一個(gè)是從桌面點(diǎn)擊app圖標(biāo)進(jìn)入app的入場動(dòng)畫和app退出的出場動(dòng)畫,一個(gè)是在app界面,通過拖動(dòng)底部指示條進(jìn)入桌面的滑動(dòng)效果動(dòng)畫,這兩個(gè)動(dòng)畫效果最先是iphone實(shí)現(xiàn)的,google為了仿iphone的實(shí)現(xiàn),所以開發(fā)了遠(yuǎn)程動(dòng)畫機(jī)制,最終能達(dá)到iphone的動(dòng)畫效果,提高了android手機(jī)的復(fù)雜動(dòng)畫效果

4、本文主要介紹android新動(dòng)畫的流程和實(shí)現(xiàn)的原理,主要介紹了LocalAnimationAdapter本地窗口動(dòng)畫實(shí)現(xiàn)原理

四、新動(dòng)畫機(jī)制Local窗口動(dòng)畫流程

本地窗口動(dòng)畫具體場景:可以在設(shè)置首頁點(diǎn)擊其中一項(xiàng)菜單,進(jìn)入設(shè)置某項(xiàng)子菜單,然后就會(huì)有一個(gè)Local窗口動(dòng)畫的播放

LocalAnimationAdapter,字面意思就是wms本地窗口動(dòng)畫,該動(dòng)畫在SurfaceAnimationThread(線程名android.anim.lf)線程播放,注意看該類的注釋
(本文剩余源碼基于androidS原生源碼)

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationThread.java */

/**
 * Thread for running {@link SurfaceAnimationRunner} that does not hold the window manager lock.
 */
public final class SurfaceAnimationThread extends ServiceThread {

這個(gè)才是新動(dòng)畫機(jī)制的核心要義,不占用wms主鎖,就不會(huì)占用wms的資源,這個(gè)已經(jīng)是對(duì)wms很大的優(yōu)化了,android歷史版本因?yàn)閣ms鎖卡頓的問題太多了

接下來我們通過閱讀源碼來分析LocalAnimationAdapter的實(shí)現(xiàn)流程,先看下整體的Local窗口動(dòng)畫時(shí)序圖


Local窗口動(dòng)畫時(shí)序圖.png

1、動(dòng)畫播放源頭類AppTransitionController的方法handleAppTransitionReady
在一次wms大遍歷(performSurfacePlacement)流程結(jié)束之后,就會(huì)檢查app transition是否已經(jīng)準(zhǔn)備好,opening 的app準(zhǔn)備好需要滿足app的starting窗口是否已經(jīng)displayed或者app的window是否已經(jīng)alldrawn,只要滿足其中一個(gè)條件,就說明app的窗口動(dòng)畫流程可以開始了了。

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

    void handleAppTransitionReady() {
        mTempTransitionReasons.clear();
        //檢查app transition是否已經(jīng)準(zhǔn)備好
        if (!transitionGoodToGo(mDisplayContent.mOpeningApps, mTempTransitionReasons)
                || !transitionGoodToGo(mDisplayContent.mChangingContainers,
                        mTempTransitionReasons)) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "AppTransitionReady");

        ProtoLog.v(WM_DEBUG_APP_TRANSITIONS, "**** GOOD TO GO");

首先會(huì)獲取當(dāng)前需要opening和closing的app window列表(ActivityRecord類型)

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

        final ActivityRecord topOpeningApp =
                getTopApp(mDisplayContent.mOpeningApps, false /* ignoreHidden */);
        final ActivityRecord topClosingApp =
                getTopApp(mDisplayContent.mClosingApps, false /* ignoreHidden */);

然后在applyAnimations方法里面對(duì)window列表進(jìn)行遍歷WindowContainer的動(dòng)畫applyAnimation方法的調(diào)用

/*frameworks/base/services/core/java/com/android/wm/AppTransitionController.java */

     private void applyAnimations(ArraySet<WindowContainer> wcs, ArraySet<ActivityRecord> apps,
            @TransitionOldType int transit, boolean visible, LayoutParams animLp,
            boolean voiceInteraction) {
        final int wcsCount = wcs.size();
        for (int i = 0; i < wcsCount; i++) {
            final WindowContainer wc = wcs.valueAt(i);
            final ArrayList<ActivityRecord> transitioningDescendants = new ArrayList<>();
            for (int j = 0; j < apps.size(); ++j) {
                final ActivityRecord app = apps.valueAt(j);
                if (app.isDescendantOf(wc)) {
                    transitioningDescendants.add(app);
                }
            }
            wc.applyAnimation(animLp, transit, visible, voiceInteraction, transitioningDescendants);
        }
    }

2、在WindowContainer,會(huì)先收集getAnimationAdpater當(dāng)前window的動(dòng)畫適配器

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

    protected void applyAnimationUnchecked(WindowManager.LayoutParams lp, boolean enter,
            @TransitionOldType int transit, boolean isVoiceInteraction,
            @Nullable ArrayList<WindowContainer> sources) {
 
        final Pair<AnimationAdapter, AnimationAdapter> adapters = getAnimationAdapter(lp,
                transit, enter, isVoiceInteraction);

如果是普通的窗口動(dòng)畫,比如app內(nèi)部activity的切換,當(dāng)前的場景是設(shè)置主菜單跳轉(zhuǎn)子菜單,根據(jù)當(dāng)前場景獲取到具體的transit,transit=TRANSIT_OLD_ACTIVITY_OPEN,然后再結(jié)合enter為true或者false,可以最終可以找到設(shè)置主菜單的動(dòng)畫xml資源是activity_open_exit.xml,設(shè)置子菜單的動(dòng)畫xml資源是activity_open_enter.xml,在獲取到具體xml資源名字后,通過AnimationUtils.loadAnimation方法把xml資源轉(zhuǎn)成Animation對(duì)象。
之后就會(huì)創(chuàng)建一個(gè)WindowAnimationSpec對(duì)象,并把Animation對(duì)象作為構(gòu)造方法的第一個(gè)參數(shù)傳給了WindowAnimationSpec

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

                final Animation a = loadAnimation(lp, transit, enter, isVoiceInteratction);
                AnimationAdapter adapter = new LocalAnimationAdapter(
                        //創(chuàng)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù)
                        new WindowAnimationSpec(a, mTmpPoint, mTmpRect,
                                getDisplayContent().mAppTransition.canSkipFirstFrame(),
                                appRootTaskClipMode, true /* isAppAnimation */, windowCornerRadius),
                        getSurfaceAnimationRunner());

這里創(chuàng)建LocalAnimationAdapter對(duì)象的時(shí)候同時(shí)創(chuàng)建了一個(gè)WindowAnimatonSpec對(duì)象作為LocalAnimationAdapter的初始化參數(shù),這個(gè)類WindowAnimatonSpec比較重要,是在后續(xù)窗口動(dòng)畫播放的時(shí)候具體的實(shí)現(xiàn)類,后面再分析
在獲取到具體的Adaper對(duì)象之后,就開始執(zhí)行startAnimation方法,這個(gè)方法里面主要調(diào)用了mSurfaceAnimator對(duì)象,來實(shí)現(xiàn)startAnimation

/*frameworks/base/services/core/java/com/android/wm/WindowContainer.java */

        mSurfaceAnimator.startAnimation(t, anim, hidden, type, animationFinishedCallback,
                mSurfaceFreezer);

tip: WindowContainer這個(gè)類是wms的最重要類之一,它是所有window的基類,充分學(xué)習(xí)理解該類可以對(duì)wms的所有window的樹狀圖有一定的理解

3、SurfaceAnimator類,字面上的意思就是window動(dòng)畫實(shí)現(xiàn)是交給它來實(shí)現(xiàn)surfacecontrol的動(dòng)畫(舊窗口動(dòng)畫是通過WindowSurfaceController控制surfacecontrol),該類的就是窗口動(dòng)畫的中控,它的主要作用是在startAnimation的時(shí)候,對(duì)要進(jìn)行動(dòng)畫的surfacecontrol創(chuàng)建一個(gè)parent的surfacecontrol類型的mLeash對(duì)象,leash的翻譯是用皮帶系住的意思,相當(dāng)于把要進(jìn)行動(dòng)畫的surfacecontrol用皮帶系住,通過操控mLeash對(duì)象來實(shí)現(xiàn)窗口的大小、位置、透明度等動(dòng)畫屬性的改變。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    void startAnimation(Transaction t, AnimationAdapter anim, boolean hidden,
            @AnimationType int type,
            @Nullable OnAnimationFinishedCallback animationFinishedCallback,
            @Nullable SurfaceFreezer freezer) {
        cancelAnimation(t, true /* restarting */, true /* forwardCancel */);
        mAnimation = anim;
        mAnimationType = type;
        mAnimationFinishedCallback = animationFinishedCallback;
        final SurfaceControl surface = mAnimatable.getSurfaceControl();
        mLeash = freezer != null ? freezer.takeLeashForAnimation() : null;
        if (mLeash == null) {
            //重點(diǎn)關(guān)注這個(gè)mLeash對(duì)象,該對(duì)象是窗口動(dòng)畫專屬surfacecontrol包裝對(duì)象
            mLeash = createAnimationLeash(mAnimatable, surface, t, type,
                    mAnimatable.getSurfaceWidth(), mAnimatable.getSurfaceHeight(), 0 /* x */,
                    0 /* y */, hidden, mService.mTransactionFactory);
            mAnimatable.onAnimationLeashCreated(t, mLeash);
        }
        mAnimatable.onLeashAnimationStarting(t, mLeash);
        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);
    }

然后在動(dòng)畫結(jié)束之后,mLeash對(duì)象會(huì)走銷毀的流程,同時(shí)動(dòng)畫的surfacecontrol進(jìn)行reparent還原操作。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

    static boolean removeLeash(Transaction t, Animatable animatable, @NonNull SurfaceControl leash,
            boolean destroy) {
        boolean scheduleAnim = false;
        final SurfaceControl surface = animatable.getSurfaceControl();
        final SurfaceControl parent = animatable.getParentSurfaceControl();
        final boolean reparent = surface != null;
        if (reparent) {
            if (surface.isValid() && parent != null && parent.isValid()) {
                t.reparent(surface, parent);
                scheduleAnim = true;
            }
        }

窗口動(dòng)畫中控最終調(diào)用了動(dòng)畫的適配類LocalAnimationAdapter的startAnimation方法。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimator.java */

        mAnimation.startAnimation(mLeash, t, type, mInnerAnimationFinishedCallback);

4、LocalAnimationAdapter是窗口動(dòng)畫的適配類,繼承自AnimationAdaper,LocalAnimationAdapter實(shí)現(xiàn)的是wms本地窗口動(dòng)畫,所以LocalAnimationAdapter可以理解成本地窗口動(dòng)畫的中轉(zhuǎn)類。在startAnimation方法里面,調(diào)用了SurfaceAnimationRunner來最終實(shí)現(xiàn)動(dòng)畫的播放。

/*frameworks/base/services/core/java/com/android/wm/LocalAnimationAdapter.java */

    @Override
    public void startAnimation(SurfaceControl animationLeash, Transaction t,
            @AnimationType int type, OnAnimationFinishedCallback finishCallback) {
        mAnimator.startAnimation(mSpec, animationLeash, t,
                () -> finishCallback.onAnimationFinished(type, this));
    }

5、SurfaceAnimationRunner是本地窗口動(dòng)畫真正的實(shí)現(xiàn)類,主要需要關(guān)注的方法是startAnimationLocked,首先這個(gè)方法已經(jīng)通過mChoreographer切換到SurfaceAnimationThread線程來執(zhí)行,然后創(chuàng)建了ValueAnimator屬性動(dòng)畫對(duì)象,交由ValueAnimator屬性動(dòng)畫對(duì)象的addUpdateListener方法來實(shí)現(xiàn)逐幀控制動(dòng)畫mLeash對(duì)象(surfacecontrol類型)的變化,具體的update方法的實(shí)現(xiàn)是在WindowAnimatonSpec類的apply方法里面。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

    private void startAnimationLocked(RunningAnimation a) {
        final ValueAnimator anim = mAnimatorFactory.makeAnimator();

        anim.overrideDurationScale(1.0f);
        anim.setDuration(a.mAnimSpec.getDuration());
        anim.addUpdateListener(animation -> {
            synchronized (mCancelLock) {
                if (!a.mCancelled) {
                    final long duration = anim.getDuration();
                    long currentPlayTime = anim.getCurrentPlayTime();
                    if (currentPlayTime > duration) {
                        currentPlayTime = duration;
                    }
                    applyTransformation(a, mFrameTransaction, currentPlayTime);
                }
            }

            scheduleApplyTransaction();
        });

        ………………
        a.mAnim = anim;
        mRunningAnimations.put(a.mLeash, a);

        //窗口動(dòng)畫最終調(diào)用了屬性動(dòng)畫播放
        anim.start();
        anim.doAnimationFrame(mChoreographer.getFrameTime());
    }

再來看下apply方法的具體實(shí)現(xiàn),通過之前以具體xml資源創(chuàng)建的mAnimation對(duì)象,根據(jù)當(dāng)前時(shí)間片currentPlayTime獲取到當(dāng)前的tmp.transformation,對(duì)leash對(duì)象實(shí)現(xiàn)了Matrix(大小,位置),Alpha,Crop等transformation變化,再通過Transaction 交給surfaceflinger顯示,從而實(shí)現(xiàn)了動(dòng)畫當(dāng)前時(shí)間片的顯示效果。對(duì)比舊動(dòng)畫機(jī)制,這個(gè)transformation變化是在WindowStateAnimator類里面實(shí)現(xiàn)的。為什么要重點(diǎn)關(guān)注這個(gè)方法呢?因?yàn)槿绻翱趧?dòng)畫出bug了(位置大小不對(duì)?透明度異常?),就可以在這個(gè)方法里面打印window的相關(guān)參數(shù)來初步定位原因。

/*frameworks/base/services/core/java/com/android/wm/WindowAnimatonSpec.java */

    @Override
    public void apply(Transaction t, SurfaceControl leash, long currentPlayTime) {
        final TmpValues tmp = mThreadLocalTmps.get();
        tmp.transformation.clear();
        mAnimation.getTransformation(currentPlayTime, tmp.transformation);
        tmp.transformation.getMatrix().postTranslate(mPosition.x, mPosition.y);
        t.setMatrix(leash, tmp.transformation.getMatrix(), tmp.floats);
        t.setAlpha(leash, tmp.transformation.getAlpha());

        boolean cropSet = false;
        if (mRootTaskClipMode == ROOT_TASK_CLIP_NONE) {
            if (tmp.transformation.hasClipRect()) {
                t.setWindowCrop(leash, tmp.transformation.getClipRect());
                cropSet = true;
            }
        } else {
            mTmpRect.set(mRootTaskBounds);
            if (tmp.transformation.hasClipRect()) {
                mTmpRect.intersect(tmp.transformation.getClipRect());
            }
            t.setWindowCrop(leash, mTmpRect);
            cropSet = true;
        }
    }

6、ValueAnimator類,從上面的介紹可以得知,窗口動(dòng)畫的最終本質(zhì)就是一個(gè)ValueAnimator屬性動(dòng)畫,理解了這一點(diǎn),就相當(dāng)于把窗口動(dòng)畫簡單化了,最終的實(shí)現(xiàn)就類比于我們普通app的屬性動(dòng)畫的實(shí)現(xiàn)(app屬性動(dòng)畫的對(duì)象是view,窗口屬性動(dòng)畫的對(duì)象是window),只不過整個(gè)流程比較復(fù)雜而已,但是最終的實(shí)現(xiàn)原理是一樣的,殊途同歸,這個(gè)才是android窗口動(dòng)畫機(jī)制的精髓所在。

/*frameworks/base/services/core/java/com/android/wm/SurfaceAnimationRunner.java */

ValueAnimator anim = mAnimatorFactory.makeAnimator();
anim.addUpdateListener(animation -> {
            applyTransformation(a, mFrameTransaction, currentPlayTime);
        });
anim.start();

總結(jié)

本文只是講解了WindowManagerService窗口動(dòng)畫之本地窗口動(dòng)畫的原生實(shí)現(xiàn)流程,主要是WindowManagerService的子類比較多,所以我們從動(dòng)畫的源頭handleAppTransitionReady一步一步分析了它的整個(gè)流程,從整個(gè)流程來看,本地窗口動(dòng)畫的邏輯比較清晰,線路也比較單一,比較容易學(xué)習(xí)和理解,最終我們看到,窗口動(dòng)畫的原理就是一個(gè)屬性動(dòng)畫,在動(dòng)畫update方法里面操控了窗口surface的屬性變化,從而實(shí)現(xiàn)了窗口動(dòng)畫的逐幀播放。另外還有一個(gè)重點(diǎn)需要關(guān)注到,那就是wms的窗口動(dòng)畫不需要占用wms主鎖,而且是單獨(dú)線程,這樣的設(shè)計(jì)也能在一定程度上優(yōu)化系統(tǒng)卡頓的問題。

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

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

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