Invalidate、postInvalidate、requestLayout

requestLayout

當(dāng)前我們對(duì)View位置、大小進(jìn)行操作后會(huì)調(diào)用requestLayout通知窗口。

    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();

        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            // Only trigger request-during-layout logic if this is the view requesting it,
            // not the views in its parent hierarchy
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) { //viewRoot 已經(jīng)處于Layout過(guò)程則添加如等待隊(duì)列
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }
        //設(shè)置標(biāo)志位,需要布局及重繪
        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();//責(zé)任鏈模式,上報(bào)requestLayout
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

從代碼中可以看出requestLayout通過(guò)責(zé)任鏈模式上報(bào)到可以處理的實(shí)現(xiàn)中去。這樣我門(mén)大概可以整理出大概執(zhí)行流程

  1. View.requestLayout
  2. ViewRootImpl.requestLayout會(huì)判斷是否已經(jīng)在進(jìn)行l(wèi)ayout過(guò)程,已在進(jìn)行則忽視
  3. ViewRootImpl.scheduleTraversals
  4. ViewRootImpl.doTraversal scheduleTraversals()通過(guò)Choreographer在下一幀調(diào)用方式回調(diào)
  5. ViewRootImpl.performTraversals

ViewRootImpl.performTraversals的分析就不再累述了,大概就是requestLayout使mLayoutRequested標(biāo)志位置為true,performTraversals將走完整套流程。稍微需要說(shuō)在requestLayout中對(duì)mHandlingLayoutInLayoutRequest標(biāo)志位的判斷原因。

    boolean requestLayoutDuringLayout(final View view) {
        if (view.mParent == null || view.mAttachInfo == null) {
            return true;
        }
        if (!mLayoutRequesters.contains(view)) {
            mLayoutRequesters.add(view);
        }
        if (!mHandlingLayoutInLayoutRequest) {
            return true;
        } else {
            return false;
        }
    }

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ....
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

            mInLayout = false;
            int numViewsRequestingLayout = mLayoutRequesters.size();
            if (numViewsRequestingLayout > 0) {//仍有requestLayout未處理
                ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
                        false);
                if (validLayoutRequesters != null) {
                    // 阻止二次傳遞,將新的requestLayout放到下一幀處理
                    mHandlingLayoutInLayoutRequest = true;

                    // 刷新 validLayoutRequesters 中的view,view.requestLayout()重新measure及l(fā)ayout
                    .....
                    
                    mInLayout = true;
                    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

                    mHandlingLayoutInLayoutRequest = false;

                    // 仍未處理的requestLayout在下一幀處理
                    validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
                    if (validLayoutRequesters != null) {
                        final ArrayList<View> finalRequesters = validLayoutRequesters;
                        //通過(guò)getRunQueue.post,在下一幀繼續(xù)進(jìn)行requestLayout
                        .....
                    }
                }

            }
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
    }

可以看到如果ViewRootImpl已經(jīng)在處理layout過(guò)程了,這時(shí)View.requestLayout將會(huì)調(diào)用ViewRootImpl.requestLayoutDuringLayout讓ViewRootImpl在performLayout中處理這些View的請(qǐng)求。
View.requestLayout設(shè)置的標(biāo)志位PFLAG_FORCE_LAYOUTPFLAG_INVALIDATED將在measure、layout、draw流程中判斷時(shí)候執(zhí)行對(duì)應(yīng)流程。

    public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        //判斷是否需要強(qiáng)制布局,requestLayout時(shí)mPrivateFlags添加標(biāo)志位 PFLAG_FORCE_LAYOUT
        final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
        ....
        if (forceLayout || needsLayout) {
            // first clears the measured dimension flag
            mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
            ...
            if (cacheIndex < 0 || sIgnoreMeasureCache) {
                // measure ourselves, this should set the measured dimension flag back
                onMeasure(widthMeasureSpec, heightMeasureSpec);
                mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            } else {
                long value = mMeasureCache.valueAt(cacheIndex);
                // Casting a long to int drops the high 32 bits, no mask needed
                setMeasuredDimensionRaw((int) (value >> 32), (int) value);
                mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
            }

            .....

            mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;//請(qǐng)求layout流程
        }
        ....
    }

    public void layout(int l, int t, int r, int b) {
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }

        .....
        //setFrame可能導(dǎo)致重繪
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //measure完成后mPrivateFlags添加標(biāo)志位 PFLAG_LAYOUT_REQUIRED
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            .....
            //layout完成,取消layout標(biāo)志位
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
            .....
        }
        //measure、layout完成,取消requestLayout的forceLayout標(biāo)志位
        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;

        if ((mPrivateFlags3 & PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT) != 0) {
            mPrivateFlags3 &= ~PFLAG3_NOTIFY_AUTOFILL_ENTER_ON_LAYOUT;
            notifyEnterOrExitForAutoFillIfNeeded(true);
        }
    }

    protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;

        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            ....

            // Invalidate our old position
            invalidate(sizeChanged);

            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            ....

            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
                // If we are visible, force the DRAWN bit to on so that
                // this invalidate will go through (at least to our parent).
                // This is because someone may have invalidated this view
                // before this call to setFrame came in, thereby clearing
                // the DRAWN bit.
                mPrivateFlags |= PFLAG_DRAWN;
                invalidate(sizeChanged);
                // parent display list may need to be recreated based on a change in the bounds
                // of any child
                invalidateParentCaches();
            }

            .....
        }
        return changed;
    }

從源碼可以看出,requestLayout必然導(dǎo)致measure、layout過(guò)程(measure完成添加標(biāo)志位PFLAG_LAYOUT_REQUIRED),是否發(fā)生重繪由setFrame中invalidate(sizeChanged)決定。

postInvalidate、Invalidate

postInvalidate通過(guò)ViewRootImpl.dispatchInvalidateDelayed發(fā)送MSG_INVALIDATE的Message,最后起調(diào)View.invalidate。而invalidate調(diào)用的invalidateInternal()。
因?yàn)閜ostInvalidate是通過(guò)Handler封裝的,所以可以在異步線(xiàn)程中調(diào)用。

invalidateInternal

//--------View.invalidateInternal(0, 0, mRight - mLeft, mBottom - mTop, true, true)
    void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
            boolean fullInvalidate) {
        .....

        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)) == (PFLAG_DRAWN | PFLAG_HAS_BOUNDS)
                || (invalidateCache && (mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == PFLAG_DRAWING_CACHE_VALID)
                || (mPrivateFlags & PFLAG_INVALIDATED) != PFLAG_INVALIDATED
                || (fullInvalidate && isOpaque() != mLastIsOpaque)) {
            if (fullInvalidate) {
                mLastIsOpaque = isOpaque();
                mPrivateFlags &= ~PFLAG_DRAWN;
            }

            mPrivateFlags |= PFLAG_DIRTY;   //設(shè)置標(biāo)志位PFLAG_DIRTY置1

            if (invalidateCache) {//invalidate調(diào)用時(shí)為true
                mPrivateFlags |= PFLAG_INVALIDATED;  //標(biāo)志位PFLAG_INVALIDATED置1
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;//標(biāo)志位PFLAG_DRAWING_CACHE_VALID置0
            }

            // Propagate the damage rectangle to the parent view.
            final AttachInfo ai = mAttachInfo;
            final ViewParent p = mParent;
            if (p != null && ai != null && l < r && t < b) {
                final Rect damage = ai.mTmpInvalRect;
                damage.set(l, t, r, b); //將繪制區(qū)域傳遞給父布局
                p.invalidateChild(this, damage);  //轉(zhuǎn)遞給父布局
            }

            .......
        }
    }

invalidateInternal方法主要標(biāo)志PFLAG_DIRTY標(biāo)志位后,將重繪區(qū)域傳給parent處理,ViewGroup#invalidateChild處理接下來(lái)邏輯。

//-----------ViewGroup#invalidateChild
    public final void invalidateChild(View child, final Rect dirty) {
        final AttachInfo attachInfo = mAttachInfo;
        ......
        ViewParent parent = this;
        if (attachInfo != null) {
            ......
            // Mark the child as dirty, using the appropriate flag
            // Make sure we do not set both flags at the same time
            int opaqueFlag = isOpaque ? PFLAG_DIRTY_OPAQUE : PFLAG_DIRTY;

            if (child.mLayerType != LAYER_TYPE_NONE) {
                mPrivateFlags |= PFLAG_INVALIDATED;
                mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            }
            //保存child左上角坐標(biāo)
            final int[] location = attachInfo.mInvalidateChildLocation;
            location[CHILD_LEFT_INDEX] = child.mLeft;
            location[CHILD_TOP_INDEX] = child.mTop;
            ........

            do {
                View view = null;
                if (parent instanceof View) {
                    view = (View) parent;
                }
                .......

                // If the parent is dirty opaque or not dirty, mark it dirty with the opaque
                // flag coming from the child that initiated the invalidate
                if (view != null) {
                    if ((view.mViewFlags & FADING_EDGE_MASK) != 0 &&
                            view.getSolidColor() == 0) {
                        opaqueFlag = PFLAG_DIRTY;
                    }
                    if ((view.mPrivateFlags & PFLAG_DIRTY_MASK) != PFLAG_DIRTY) {
                        //對(duì)當(dāng)前View的標(biāo)記位進(jìn)行設(shè)置
                        view.mPrivateFlags = (view.mPrivateFlags & ~PFLAG_DIRTY_MASK) | opaqueFlag;
                    }
                }
                //責(zé)任鏈模式上報(bào),最終ViewRootImpl停止
                parent = parent.invalidateChildInParent(location, dirty);
                if (view != null) {
                    // Account for transform on current parent
                    Matrix m = view.getMatrix();
                    if (!m.isIdentity()) {
                        RectF boundingRect = attachInfo.mTmpTransformRect;
                        boundingRect.set(dirty);
                        m.mapRect(boundingRect);
                        //更新dirty信息區(qū)域信息
                        dirty.set((int) Math.floor(boundingRect.left),
                                (int) Math.floor(boundingRect.top),
                                (int) Math.ceil(boundingRect.right),
                                (int) Math.ceil(boundingRect.bottom));
                    }
                }
            } while (parent != null);
        }
    }

這里主要是先標(biāo)志子view位置,向上回溯父容器,然后求得父容器和子View需要重繪的區(qū)域的并集(dirty),最后在ViewRootImpl#invalidateChildInParent中停止。

//----------ViewGroup#invalidateChildInParent
    public ViewParent invalidateChildInParent(final int[] location, final Rect dirty) {
        if ((mPrivateFlags & (PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID)) != 0) {
            // either DRAWN, or DRAWING_CACHE_VALID
            if ((mGroupFlags & (FLAG_OPTIMIZE_INVALIDATE | FLAG_ANIMATION_DONE))
                    != FLAG_OPTIMIZE_INVALIDATE) {
                //將dirty中的坐標(biāo)轉(zhuǎn)化為父容器中的坐標(biāo),考慮mScrollX和mScrollY的影響
                dirty.offset(location[CHILD_LEFT_INDEX] - mScrollX,
                        location[CHILD_TOP_INDEX] - mScrollY);
                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == 0) {
                    //求并集,結(jié)果是把子視圖的dirty區(qū)域轉(zhuǎn)化為父容器的dirty區(qū)域
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }

                final int left = mLeft;
                final int top = mTop;

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    if (!dirty.intersect(0, 0, mRight - left, mBottom - top)) {
                        dirty.setEmpty();
                    }
                }
                //記錄當(dāng)前視圖的mLeft和mTop值,在下一次循環(huán)中會(huì)把當(dāng)前值再向父容器的坐標(biāo)轉(zhuǎn)化
                location[CHILD_LEFT_INDEX] = left;
                location[CHILD_TOP_INDEX] = top;
            } else {

                if ((mGroupFlags & FLAG_CLIP_CHILDREN) == FLAG_CLIP_CHILDREN) {
                    dirty.set(0, 0, mRight - mLeft, mBottom - mTop);
                } else {
                    // in case the dirty rect extends outside the bounds of this container
                    dirty.union(0, 0, mRight - mLeft, mBottom - mTop);
                }
                location[CHILD_LEFT_INDEX] = mLeft;
                location[CHILD_TOP_INDEX] = mTop;

                mPrivateFlags &= ~PFLAG_DRAWN;
            }
            //設(shè)置PFLAG_DRAWING_CACHE_VALID標(biāo)志位置0
            mPrivateFlags &= ~PFLAG_DRAWING_CACHE_VALID;
            
            if (mLayerType != LAYER_TYPE_NONE) {//viewGroup需要重繪PFLAG_INVALIDATED標(biāo)志位置1
                mPrivateFlags |= PFLAG_INVALIDATED;
            }

            return mParent;
        }

        return null;
    }
//---------ViewRootImpl#invalidateChildInParent
    public ViewParent invalidateChildInParent(int[] location, Rect dirty) {
        checkThread();
        if (DEBUG_DRAW) Log.v(mTag, "Invalidate child: " + dirty);

        if (dirty == null) {
            invalidate();
            return null;
        } else if (dirty.isEmpty() && !mIsAnimating) {
            return null;
        }

        if (mCurScrollY != 0 || mTranslator != null) {
            mTempRect.set(dirty);
            dirty = mTempRect;
            if (mCurScrollY != 0) {
                dirty.offset(0, -mCurScrollY);
            }
            if (mTranslator != null) {
                mTranslator.translateRectInAppWindowToScreen(dirty);
            }
            if (mAttachInfo.mScalingRequired) {
                dirty.inset(-1, -1);
            }
        }

        invalidateRectOnScreen(dirty);

        return null;
    }

可以看到ViewGroup#invalidateChildInParent主要的作用為通過(guò)offset方法,把當(dāng)前dirty區(qū)域的坐標(biāo)轉(zhuǎn)化為父容器中的坐標(biāo),接著調(diào)用union方法,把子dirty區(qū)域與父容器的區(qū)域求并集。即dirty區(qū)域轉(zhuǎn)化為父容器區(qū)域并返回當(dāng)前視圖的父容器,以便進(jìn)行下一次循環(huán)。
最后在ViewRootImpl#invalidateChildInParent所做工作與ViewGroup差不多,調(diào)整dirty區(qū)域并保存在mDirty中,最后調(diào)用了scheduleTraversals方法,觸發(fā)View的工作流程。因?yàn)闆](méi)有添加measure和layout的標(biāo)記位,因此對(duì)應(yīng)流程不會(huì)執(zhí)行,而是直接從draw流程開(kāi)始。

    private void draw(boolean fullRedrawNeeded) {
        .......
        if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
            if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.isEnabled()) {
                ......
                mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
            } else {
                ........
                if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset, scalingRequired, dirty)) {
                    return;
                }
            }
        }
        .........
    }

在draw流程中,因?yàn)?code>!dirty.isEmpty()通過(guò)mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
最終會(huì)調(diào)用ThreadedRenderer#updateViewTreeDisplayList(View view),而此時(shí)的view為RootView。

    private void updateViewTreeDisplayList(View view) {
        view.mPrivateFlags |= View.PFLAG_DRAWN;
        view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
                == View.PFLAG_INVALIDATED;
        view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
        view.updateDisplayListIfDirty();
        view.mRecreateDisplayList = false;
    }

通過(guò)View#updateDisplayListIfDirty()下發(fā)找到需要重繪的View。
因?yàn)樾枰猧nvalidate的View的標(biāo)志位PFLAG_DRAWING_CACHE_VALID 為0,PFLAG_INVALIDATED為1,而該view的父族ViewGroup的PFLAG_DRAWING_CACHE_VALID為0,PFLAG_INVALIDATED為0。所以只有需要重繪的view的mRecreateDisplayList==true,其父族mRecreateDisplayList==false。
最終下發(fā)找到需要重繪的view/viewgroup

    public RenderNode updateDisplayListIfDirty() {
        final RenderNode renderNode = mRenderNode;
        .....
        //與需要invalidate的view同族的ViewGreoup的標(biāo)志PFLAG_DRAWING_CACHE_VALID都為0
        if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
                || !renderNode.isValid()
                || (mRecreateDisplayList)) {
            // Don't need to recreate the display list, just need to tell our
            // children to restore/recreate theirs
            if (renderNode.isValid()
                    && !mRecreateDisplayList) {
                mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                mPrivateFlags &= ~PFLAG_DIRTY_MASK;
                dispatchGetDisplayList();//下發(fā)需要重繪的view

                return renderNode; // no work needed
            }

            // If we got here, we're recreating it. Mark it as such to ensure that
            // we copy in child display lists into ours in drawChild()
            mRecreateDisplayList = true;

            int width = mRight - mLeft;
            int height = mBottom - mTop;
            int layerType = getLayerType();

            final DisplayListCanvas canvas = renderNode.start(width, height);
            canvas.setHighContrastText(mAttachInfo.mHighContrastText);

            try {
                if (layerType == LAYER_TYPE_SOFTWARE) {
                    buildDrawingCache(true);
                    Bitmap cache = getDrawingCache(true);
                    if (cache != null) {
                        canvas.drawBitmap(cache, 0, 0, mLayerPaint);
                    }
                } else {
                    computeScroll();

                    canvas.translate(-mScrollX, -mScrollY);
                    mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
                    mPrivateFlags &= ~PFLAG_DIRTY_MASK;

                    // Fast path for layouts with no backgrounds
                    if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
                        dispatchDraw(canvas);   //-------ViewGroup一般為PFLAG_SKIP_DRAW,直接下發(fā)
                        drawAutofilledHighlight(canvas);
                        if (mOverlay != null && !mOverlay.isEmpty()) {
                            mOverlay.getOverlayView().draw(canvas);
                        }
                        if (debugDraw()) {
                            debugDrawFocus(canvas);
                        }
                    } else {
                        draw(canvas);   //-------view起調(diào)draw流程
                    }
                }
            } finally {
                renderNode.end(canvas);
                setDisplayListProperties(renderNode);
            }
        } else {
            mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        }
        return renderNode;
    }

    protected void dispatchGetDisplayList() {
        final int count = mChildrenCount;
        final View[] children = mChildren;
        for (int i = 0; i < count; i++) {
            final View child = children[i];
            if (((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null)) {
                recreateChildDisplayList(child);//重置標(biāo)志位并下發(fā)
            }
        }
        if (mOverlay != null) {
            View overlayView = mOverlay.getOverlayView();
            recreateChildDisplayList(overlayView);
        }
        if (mDisappearingChildren != null) {
            final ArrayList<View> disappearingChildren = mDisappearingChildren;
            final int disappearingCount = disappearingChildren.size();
            for (int i = 0; i < disappearingCount; ++i) {
                final View child = disappearingChildren.get(i);
                recreateChildDisplayList(child);
            }
        }
    }

    private void recreateChildDisplayList(View child) {
        child.mRecreateDisplayList = (child.mPrivateFlags & PFLAG_INVALIDATED) != 0;//需要重繪的view為true
        child.mPrivateFlags &= ~PFLAG_INVALIDATED;
        child.updateDisplayListIfDirty();
        child.mRecreateDisplayList = false;
    }


?著作權(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)容