Activity的Window創(chuàng)建和添加過(guò)程-源碼分析

之前介紹過(guò)Activity的啟動(dòng)過(guò)程,一直講到了Actiivty的oCreate方法執(zhí)行,Activity的啟動(dòng)過(guò)程完成。本文就順著啟動(dòng)過(guò)程的完成,介紹下Activity的Window創(chuàng)建和添加的過(guò)程。為什么講這個(gè)呢,因?yàn)锳ctivity啟動(dòng)過(guò)程完成到onCreate方法的執(zhí)行,Activity的頁(yè)面還沒(méi)有顯示出來(lái),而Activity的頁(yè)面布局的顯示,就是由Window、WIndowManager和WindowManagerService這一套機(jī)制來(lái)管理的,也就是通常說(shuō)的WMS機(jī)制。
在Android系統(tǒng)中,一個(gè)Activity通常就表示一個(gè)頁(yè)面,這個(gè)頁(yè)面實(shí)際是由Window來(lái)管理的。每個(gè)Activity都對(duì)應(yīng)著一個(gè)Window。Window是一個(gè)抽象類,具體實(shí)現(xiàn)類是PhoneWindow,它對(duì)View進(jìn)行管理。確切的來(lái)講是,PhoneWindow包含一個(gè)DecorView類型的成員,它代表這個(gè)Window的頂層View,是一個(gè)FrameLayout。DecorView的布局結(jié)構(gòu)包含兩部分:標(biāo)題欄(title)和內(nèi)容欄(content)。根據(jù)設(shè)置的主題不同,這兩部分也會(huì)有不同的呈現(xiàn)。但內(nèi)容欄是一定存在的,并且它的id是固定的,完整id都是android.R.id.content。

需要說(shuō)明一下:本文的源碼分析是基于Android 8.0版本,也就是API Level26的源碼。

Window的創(chuàng)建過(guò)程

熟悉Activity啟動(dòng)過(guò)程的同學(xué)應(yīng)該都記得,在ActivityThread的performLaunchActivity方法,會(huì)調(diào)用Activity的attach方法。與Activity相關(guān)聯(lián)的Window對(duì)象就是在attach方法中創(chuàng)建的。

final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
    attachBaseContext(context);
    ...
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    ...
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
     ...

    mWindow.setColorMode(info.colorMode);
}

可以看到,在attach方法中創(chuàng)建了一個(gè)PhoneWindow對(duì)象,并為它設(shè)置回調(diào)接口Callback和WindowManager。由于Activity類實(shí)現(xiàn)了Window.Callback接口,因此當(dāng)Window接收到相關(guān)的事件觸發(fā)時(shí)就會(huì)調(diào)用Activity的相應(yīng)方法。Callback接口中的方法很多,有幾個(gè)是我們比較常見(jiàn)的,比如dispatchTouchEvent、onAttachedToWindow、onDetachedFromWindow等。

Window的添加過(guò)程

上面已經(jīng)介紹了Activity關(guān)聯(lián)的Window對(duì)象是在什么時(shí)候創(chuàng)建的,接下來(lái),我們看看Activity的Window的添加過(guò)程。
在日常開(kāi)發(fā)中,我們都會(huì)在Activity的onCreate方法中,通過(guò)setContentView來(lái)給Activity設(shè)置視圖布局,來(lái)指定頁(yè)面顯示什么內(nèi)容。
我們需要先看看ActivityThread的performLaunchActivity方法

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ...
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback);
        ...
        if (r.isPersistable()) {
                    mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
                } else {
                    mInstrumentation.callActivityOnCreate(activity, r.state);
                }
}

在Activity的attach方法返回后,程序會(huì)調(diào)用mInstrumentation.callActivityOnCreate方法,而這個(gè)方法最終會(huì)觸發(fā)Activity的onCreate回調(diào)。而在onCreate中,會(huì)調(diào)用setContentView方法,開(kāi)始Activity的Window添加過(guò)程。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity的setContentView方法內(nèi)部調(diào)用的mWindow的setContentView方法,這個(gè)mWindow對(duì)象就是在attach方法中創(chuàng)建的PhoneWindow對(duì)象。所以,接下來(lái)要看到PhoneWindow的setContentView方法

public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }


    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

PhoneWindow的setContentView中,首先判斷mContentParent是否存在,否則調(diào)用installDecor方法。這個(gè)mContentParent指的就是DecorView的內(nèi)容欄。它的賦值就只有一個(gè)地方,就是在installDecor方法中。

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
        mDecor.setIsRootNamespace(true);
        if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
            mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
        }
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    ...
}

如果mDecor為null,就先調(diào)用generateDecor方法創(chuàng)建DecorView

protected DecorView generateDecor(int featureId) {
    ...
    return new DecorView(context, featureId, this, getAttributes());
}

DecorView對(duì)象創(chuàng)建之后,再判斷mContentParent對(duì)象是否存在,否則調(diào)用generateLayout方法

protected ViewGroup generateLayout(DecorView decor) {
    ...
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ...
    return contentParent;
}

generateLayout方法的代碼很多,我們先只關(guān)注相關(guān)的重點(diǎn),generateLayout最后會(huì)返回一個(gè)ViewGroup對(duì)象contentParent,而contentParent的id是ID_ANDROID_CONTENT,它的值就是com.android.internal.R.id.content。這就是DecorView的內(nèi)容欄id為android.R.id.content的由來(lái),也是Activity的SetContentView方法名的由來(lái)。
現(xiàn)在回頭看到PhoneWindow的setContentView方法,調(diào)用installDecor方法,創(chuàng)建好DecorView和mContentParent之后,會(huì)調(diào)用 mLayoutInflater.inflate(layoutResID, mContentParent),將Activity的布局視圖添加到mContentParent中。然后回調(diào)Activity的onContentChanged方法通知Activity,視圖已經(jīng)發(fā)生改變。
以上就是,Activity的視圖布局已經(jīng)添加到DecorView的過(guò)程。這個(gè)過(guò)程是在Activity的onCreate方法中完成的。但這個(gè)時(shí)候,Activity的視圖布局還沒(méi)有顯示出來(lái)。這是因?yàn)?,DecorView還沒(méi)有被WindowManager正式添加到窗口中。這里說(shuō)的窗口,并不是指WIndow和它的子類PhoneWindow,而是一個(gè)抽象的概念。窗口更多表示的是一種抽象的功能集合。雖然在Activity的attach方法中,WIndow對(duì)象就已經(jīng)被創(chuàng)建了,但這個(gè)時(shí)候由于DecorView并沒(méi)有被WindowManager識(shí)別,所以此時(shí)的WIndow無(wú)法提供具體功能,因?yàn)樗€無(wú)法接受外界的輸入信息。我們知道,在Activity執(zhí)行onResume方法之后,視圖才能完全顯示,并和用戶正常交互,那onResume方法是在什么地方回調(diào)了呢,熟悉Activity啟動(dòng)過(guò)程的同學(xué),應(yīng)該會(huì)有印象,在ActivityThread的handleLaunchActivity方法中,

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    ...
    Activity a = performLaunchActivity(r, customIntent);
    if (a != null) {
    r.createdConfig = new Configuration(mConfiguration);
    reportSizeConfigurations(r);
    Bundle oldState = r.state;
    handleResumeActivity(r.token, false, r.isForward,
            !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    ...
}

執(zhí)行完performLaunchActivity方法返回一個(gè)Activity的實(shí)例,接下來(lái)判斷如果創(chuàng)建的Activity實(shí)例不為null,就會(huì)執(zhí)行handleResumeActivity方法

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ...
    r = performResumeActivity(token, clearHide, reason);
    if (r != null) {
        final Activity a = r.activity;
        ...
        if (r.activity.mVisibleFromClient) {
            r.activity.makeVisible();
        }
    ...
}

在handleResumeActivity方法中,會(huì)調(diào)用performResumeActivity方法,這個(gè)方法中又經(jīng)過(guò)層層調(diào)用,最終會(huì)回調(diào)Activity的onResume方法。這就是Activity的onResume方法的回調(diào)時(shí)機(jī)。這里不是本文的重點(diǎn),就沒(méi)有列出相關(guān)的源碼。
handleResumeActivity中,在performResumeActivity方法執(zhí)行之后,會(huì)調(diào)用Activity的makeVisible方法

void makeVisible() {
    if (!mWindowAdded) {
        ViewManager wm = getWindowManager();
        wm.addView(mDecor, getWindow().getAttributes());
        mWindowAdded = true;
    }
    mDecor.setVisibility(View.VISIBLE);
}

在makeVisible方法中,會(huì)調(diào)用WindowManager的addView方法,將DecorView正式添加到窗口中。同時(shí)DecorView設(shè)置為可見(jiàn)。
這里先介紹下WindowManager,它是一個(gè)接口,繼承自ViewManager。從ViewManager接口繼承了三個(gè)方法addView、updateViewLayout和removeView。這三個(gè)方法都與窗口管理直接相關(guān)。而WindowManager的實(shí)現(xiàn)類是WindowManagerImpl。所以Activity的makeVisible方法中,調(diào)用的實(shí)際上是WindowManagerImpl的addView方法

@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

在WindowManagerImpl的addView方法內(nèi)部,調(diào)用的是WindowManagerGlobal的addView方法。WindowManagerImpl通過(guò)橋接模式,將功能實(shí)現(xiàn)委托給了WindowManagerGlobal。WindowManagerGlobal是一個(gè)單例,說(shuō)明一個(gè)進(jìn)程中只有一個(gè)WindowManagerGlobal實(shí)例。而每一個(gè)Window都會(huì)有一個(gè)相關(guān)聯(lián)的WindowManagerImpl實(shí)例。WindowManagerGlobal的addView方法如下:

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        final Context context = view.getContext();
        if (context != null
                && (context.getApplicationInfo().flags
                        & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }
    ViewRootImpl root;
    View panelParentView = null;
    ...
        int index = findViewLocked(view, false);
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                // Don't wait for MSG_DIE to make it's way through root's queue.
                mRoots.get(index).doDie();
            } else {
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

WindowManagerGlobal的addView方法主要完成三個(gè)步驟:

  1. 檢查參數(shù)是否合法,如果是子Window還需要調(diào)整一些布局參數(shù)
  2. 創(chuàng)建ViewRootImpl,并將傳進(jìn)來(lái)的View添加到mViews列表里
  3. 通過(guò)ViewRootImpl來(lái)更新界面并完成WIndow的添加過(guò)程。

上來(lái)的代碼中,涉及到WindowManagerGlobal類幾個(gè)重要的列表

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

簡(jiǎn)單說(shuō)明一下,mViews存放的是所有窗口所對(duì)應(yīng)的View,比如Activity的窗口所對(duì)應(yīng)的DecorView。mRoots存放的是所有窗口所對(duì)應(yīng)的ViewRootImpl對(duì)象。mParams存放的是所有窗口所對(duì)應(yīng)的布局參數(shù)。而在這三個(gè)列表中,索引值相同的一組View、ViewRootImpl和LayoutParam對(duì)象,可以看做是一個(gè)窗口的概念。mDyingViews則存儲(chǔ)了正在被刪除的View,或者說(shuō)是那些已經(jīng)調(diào)用了removeView方法但刪除操作還沒(méi)有完成的Window對(duì)象。
View和LayoutParams應(yīng)該都不會(huì)陌生,關(guān)于ViewRootImpl,熟悉View繪制機(jī)制的同學(xué),也會(huì)對(duì)它有印象。通俗的說(shuō)法是,一個(gè)頁(yè)面的繪制流程,就是從ViewRootImpl開(kāi)始。而實(shí)際上ViewRootImpl承擔(dān)的職責(zé)包括以下幾點(diǎn):

  • 作為View層級(jí)(View hierarchy)的頂層(top-level)管理View層級(jí)
  • 觸發(fā)View的測(cè)量、布局和繪制
  • 輸入事件的中轉(zhuǎn)站
  • 負(fù)責(zé)與WMS進(jìn)程間通信
    介紹完WindowManagerGlobal中這幾個(gè)重要的列表之后,我們接著看addView方法。將View、ViewRootImpl和LayoutParams對(duì)象分別添加到列表之后,會(huì)調(diào)用ViewRootImpl的setView方法
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    requestLayout();
    ...
    try {
    mOrigWindowType = mWindowAttributes.type;
    mAttachInfo.mRecomputeGlobalAttributes = true;
    collectViewAttributes();
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
            getHostVisibility(), mDisplay.getDisplayId(),
            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
            mAttachInfo.mOutsets, mInputChannel);
    ...
}

setView方法的代碼很多,本文中暫時(shí)只關(guān)注兩點(diǎn)。
調(diào)用requestLayout方法會(huì)觸發(fā)View層級(jí)的繪制遍歷。這個(gè)對(duì)于了解View的繪制過(guò)程的同學(xué)肯定不陌生。簡(jiǎn)單介紹下,requestLayout方法內(nèi)部會(huì)調(diào)用scheduleTraversals方法。scheduleTraversals方法實(shí)際上就是View繪制過(guò)程的入口。
然后會(huì)調(diào)用mWindowSession對(duì)象的addToDisplay方法,mWindowSession的類型是IWindowSession,它是一個(gè)Binder對(duì)象,用于進(jìn)程間通信,IWindowSession是Client端的代理。它的Server端實(shí)現(xiàn)是Session。此前的代碼都是運(yùn)行在Actiivty所在的app進(jìn)程,而Session的addToDisplay方法則是運(yùn)行在WMS所在的SystemServer進(jìn)程中。


image.png

從上圖中可以看出,app進(jìn)程中的ViewRootImpl對(duì)象要想與WMS進(jìn)行通信需要經(jīng)過(guò)Session,確切的說(shuō),通過(guò)調(diào)用Session的addToDisplay方法

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
        int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets,
        Rect outOutsets, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId,
            outContentInsets, outStableInsets, outOutsets, outInputChannel);
}

addToDisplay方法內(nèi)部,直接調(diào)用了mService的addWindow方法,并將Session對(duì)象本身作為第一個(gè)參數(shù)傳進(jìn)去。這個(gè)mService就是WMS的實(shí)例。每一個(gè)app進(jìn)程都會(huì)對(duì)應(yīng)一個(gè)Session對(duì)象,它用來(lái)表示app進(jìn)程與WMS的通信渠道。WMS會(huì)用ArrayList來(lái)存放這些Session對(duì)象。接下來(lái)的工作就交給WMS來(lái)處理。WMS會(huì)為這個(gè)要添加的窗口分配Surface,并確定窗口的顯示次序,而真正負(fù)責(zé)顯示界面視圖的是畫(huà)布Surface,而不是窗口本身。WMS會(huì)將它所管理的Surface交由SurfaceFlinger處理,SurfaceFlinger會(huì)將這些Surface混合并繪制到屏幕上。
WindowManagerService的addWindow方法代碼非常多,有興趣的同學(xué)可以自行查看源碼。這里只是概括性的介紹addWindow方法中主要做了四件事:

  • 對(duì)要添加的窗口進(jìn)行檢查,確保窗口和一些參數(shù)滿足條件,否則就直接返回相應(yīng)的提示信息,不再往下執(zhí)行。
  • WindowToken的相關(guān)處理,比如有的窗口類型需要提供WindowToken,沒(méi)有提供的話也會(huì)直接返回相應(yīng)的提示信息,不再往下執(zhí)行。有的窗口類型需要由WMS隱式創(chuàng)建WindowToken。
  • WindowState的創(chuàng)建和相關(guān)處理,將WIndowToken和WindowState相關(guān)聯(lián)。
  • 創(chuàng)建和配置DisplayContent,完成窗口添加到系統(tǒng)前的準(zhǔn)備工作。

關(guān)于WindowToken
要了解WindowToken,首先要了解ActivityRecord的內(nèi)部類Token,它繼承自IApplicationToken.Stub。很明顯,它是基于Binder機(jī)制。Binder除了用于跨進(jìn)程通信之外,另一個(gè)用途就是在多個(gè)進(jìn)程中標(biāo)識(shí)同一個(gè)對(duì)象,這里的Token主要是用于后者。一個(gè)Token對(duì)象(ActivityRecord的成員變量appToken)標(biāo)識(shí)了一個(gè)ActivityRecord對(duì)象。而WindowToken中,IBinder類型token對(duì)象標(biāo)識(shí)的也是ActivityRecord中的這個(gè)Token對(duì)象appToken。有興趣的朋友可以由此去了解。

關(guān)于Surface、Window和View概念的理解
以下是由安卓framework開(kāi)發(fā)者Dianne Hackborn在stackoverflow給出的關(guān)于Window和View的定義,由此可以更好的理解WIndow和View的關(guān)系
A Surface is an object holding pixels that are being composited to the screen. Every window you see on the screen (a dialog, your full-screen activity, the status bar) has its own surface that it draws in to, and Surface Flinger renders these to the final display in their correct Z-order. A surface typically has more than one buffer (usually two) to do double-buffered rendering: the application can be drawing its next UI state while the surface flinger is compositing the screen using the last buffer, without needing to wait for the application to finish drawing.
A window is basically like you think of a window on the desktop. It has a single Surface in which the contents of the window is rendered. An application interacts with the Window Manager to create windows; the Window Manager creates a Surface for each window and gives it to the application for drawing. The application can draw whatever it wants in the Surface; to the Window Manager it is just an opaque rectangle.
A View is an interactive UI element inside of a window. A window has a single view hierarchy attached to it, which provides all of the behavior of the window. Whenever the window needs to be redrawn (such as because a view has invalidated itself), this is done into the window's Surface. The Surface is locked, which returns a Canvas that can be used to draw into it. A draw traversal is done down the hierarchy, handing the Canvas down for each view to draw its part of the UI. Once done, the Surface is unlocked and posted so that the just drawn buffer is swapped to the foreground to then be composited to the screen by Surface Flinger.

到此,本文對(duì)Activity的Window創(chuàng)建和添加過(guò)程分析就講到這里。

本文參考
《Android開(kāi)發(fā)藝術(shù)探索》
《Android進(jìn)階解密》
深入理解Activity——Token之旅
https://stackoverflow.com/questions/4576909/understanding-canvas-and-surface-concepts#answers

最后編輯于
?著作權(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),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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