安卓學習 - Fragment的懶加載

轉(zhuǎn)載請附上原博客連接 http://www.itdecent.cn/p/837cdd4cf371

懶加載的應(yīng)用場景和作用:

在我們開發(fā)APP的過程中,經(jīng)常會用到ViewPager+Fragment的UI結(jié)構(gòu)(微信主界面就是這樣組成的)。通過左右滑動,ViewPager加載不同的Fragment進行顯示。ViewPager在加載當前應(yīng)當顯示的Fragment的時候,會同時將該Fragment前后相鄰的Fragment也加載到內(nèi)存中。這樣,當幾個Fragment中都有很多耗費資源的初始化操作時,可能會因網(wǎng)絡(luò)阻塞造成當前頁面初始化緩慢的問題。因此我們需要只在Fragment用戶可見的時候才加載數(shù)據(jù),這就是懶加載。

首先我們來證實一下在使用Fragment+ViewPager的時候。ViewPager會提前加載當前Fragment左右相鄰的Fragment。

我們像往常一樣,在主界面中使用ViewPager,如下面代碼所示:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="com.tinymonster.lazyloadtest.MainActivity">

    <android.support.v4.view.ViewPager
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:id="@+id/viewPager"
        />
</LinearLayout>

自定義Fragment,在生命周期內(nèi)打印信息。

public class MyFragment extends Fragment{
    private String data;

    public MyFragment(){
    }

    public MyFragment(String data){
        this.data=data;
    }

    @Override
    public void onAttach(Context context) {
        super.onAttach(context);
        Log.e(data,"onAttach");
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        Log.e(data,"onCreate");
    }

    @Nullable
    @Override
    public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        Log.e(data,"onCreateView");
        View view=inflater.inflate(R.layout.my_fragment,null);
        TextView textView=(TextView)view.findViewById(R.id.text);
        textView.setText(data);
        return view;
    }

    @Override
    public void onStart() {
        super.onStart();
        Log.e(data,"onStart");
    }

    @Override
    public void onResume() {
        super.onResume();
        Log.e(data,"onResume");
    }

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(data,"setUserVisibleHint:"+isVisibleToUser);
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
        Log.e(data,"onDestroyView");
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        Log.e(data,"onDestroy");
    }

}

在主界面代碼中給ViewPager添加Fragment,并且設(shè)置首先顯示第三個Fragment。

public class MainActivity extends AppCompatActivity {
    private ViewPager viewPager;
    private MyAdapter myAdapter;
    List<Fragment> list=new ArrayList<>();
    @Override

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        viewPager=(ViewPager)findViewById(R.id.viewPager);
        list.add(new MyFragment("Fragment_1"));
        list.add(new MyFragment("Fragment_2"));
        list.add(new MyFragment("Fragment_3"));
        list.add(new MyFragment("Fragment_4"));
        list.add(new MyFragment("Fragment_5"));
        myAdapter=new MyAdapter(getSupportFragmentManager(),list);
        viewPager.setAdapter(myAdapter);
        viewPager.setCurrentItem(2);
    }
}

運行程序,打印信息如下。


打印生命周期信息

我們從打印信息中可以看出,ViewPager在加載Fragment_3的同時,也調(diào)用的Fragment_2和Fragment_4的生命周期(onAttach,onCreate,onCreateView,onStart,onResume)。因此,如果Fragment_2,F(xiàn)ragment_3和Fragment_4的初始化中有很多耗費網(wǎng)絡(luò)資源的操作時,可能造成網(wǎng)絡(luò)阻塞,使得需要顯示的Fragment_3遲遲不能初始化。

ViewPager源碼分析

為什么會在加載當前Fragment的時候也把他左右兩個Fragment也加載到內(nèi)存中呢?下面我們看一下ViewPager的源碼。
首先我們看ViewPager中的onMeasure()。

protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        // 沒有指定的情況下寬高為0 ;
        setMeasuredDimension(getDefaultSize(0, widthMeasureSpec),
                getDefaultSize(0, heightMeasureSpec));

        final int measuredWidth = getMeasuredWidth();
        final int maxGutterSize = measuredWidth / 10;
        mGutterSize = Math.min(maxGutterSize, mDefaultGutterSize);

        // 得到可用空間
        int childWidthSize = measuredWidth - getPaddingLeft() - getPaddingRight();
        int childHeightSize = getMeasuredHeight() - getPaddingTop() - getPaddingBottom();

        //計算每一個孩子所用空間
        int size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp != null && lp.isDecor) {
                    final int hgrav = lp.gravity & Gravity.HORIZONTAL_GRAVITY_MASK;
                    final int vgrav = lp.gravity & Gravity.VERTICAL_GRAVITY_MASK;
                    int widthMode = MeasureSpec.AT_MOST;
                    int heightMode = MeasureSpec.AT_MOST;
                    boolean consumeVertical = vgrav == Gravity.TOP || vgrav == Gravity.BOTTOM;
                    boolean consumeHorizontal = hgrav == Gravity.LEFT || hgrav == Gravity.RIGHT;

                    if (consumeVertical) {
                        widthMode = MeasureSpec.EXACTLY;
                    } else if (consumeHorizontal) {
                        heightMode = MeasureSpec.EXACTLY;
                    }

                    int widthSize = childWidthSize;
                    int heightSize = childHeightSize;
                    if (lp.width != LayoutParams.WRAP_CONTENT) {
                        widthMode = MeasureSpec.EXACTLY;
                        if (lp.width != LayoutParams.MATCH_PARENT) {
                            widthSize = lp.width;
                        }
                    }
                    if (lp.height != LayoutParams.WRAP_CONTENT) {
                        heightMode = MeasureSpec.EXACTLY;
                        if (lp.height != LayoutParams.MATCH_PARENT) {
                            heightSize = lp.height;
                        }
                    }
                    final int widthSpec = MeasureSpec.makeMeasureSpec(widthSize, widthMode);
                    final int heightSpec = MeasureSpec.makeMeasureSpec(heightSize, heightMode);
                    child.measure(widthSpec, heightSpec);

                    if (consumeVertical) {
                        childHeightSize -= child.getMeasuredHeight();
                    } else if (consumeHorizontal) {
                        childWidthSize -= child.getMeasuredWidth();
                    }
                }
            }
        }
// 合成測量規(guī)格
        mChildWidthMeasureSpec = MeasureSpec.makeMeasureSpec(childWidthSize, MeasureSpec.EXACTLY);
        mChildHeightMeasureSpec = MeasureSpec.makeMeasureSpec(childHeightSize, MeasureSpec.EXACTLY);

        // Make sure we have created all fragments that we need to have shown.
        mInLayout = true;
        //填充
        populate();
        mInLayout = false;

        // 測量調(diào)用每一個孩子的measure()方法
        size = getChildCount();
        for (int i = 0; i < size; ++i) {
            final View child = getChildAt(i);
            if (child.getVisibility() != GONE) {
                if (DEBUG) {
                    Log.v(TAG, "Measuring #" + i + " " + child + ": " + mChildWidthMeasureSpec);
                }

                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                if (lp == null || !lp.isDecor) {
                    final int widthSpec = MeasureSpec.makeMeasureSpec(
                            (int) (childWidthSize * lp.widthFactor), MeasureSpec.EXACTLY);
                    child.measure(widthSpec, mChildHeightMeasureSpec);
                }
            }
        }
    }

ViewPager的onMeasure()方法進行了如下操作:1.確定了自身可用空間,2.遍歷每一個裝飾view,測量他們的大小和計算剩余空間,3.調(diào)用populate()方法根據(jù)頁面緩存顯示進行頁面銷毀與重建,4.測量每一個孩子。

下面我們再看一下populate()方法。

void populate() {
        // 將當前選中position放入
        populate(mCurItem);
    }
    void populate(int newCurrentItem) {
        // 舊的選中的 ItemInfo
        ItemInfo oldCurInfo = null;
        if (mCurItem != newCurrentItem) {
            //infoForPosition方法得到 舊 ItemInfo
            oldCurInfo = infoForPosition(mCurItem);
            mCurItem = newCurrentItem;
        }

        if (mAdapter == null) {
            //排序繪制view的順序
            sortChildDrawingOrder();
            return;
        }

        if (mPopulatePending) {
            if (DEBUG) Log.i(TAG, "populate is pending, skipping for now...");
            sortChildDrawingOrder();
            return;
        }

        // 不在window中時
        if (getWindowToken() == null) {
            return;
        }
// 調(diào)用 startUpdate 方法
        mAdapter.startUpdate(this);
      //頁的限制
        final int pageLimit = mOffscreenPageLimit;
        //開始頁
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //總數(shù)
        final int N = mAdapter.getCount();
        // 結(jié)束頁
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);
         // 如果預(yù)期數(shù)量不一致
        if (N != mExpectedAdapterCount) {
            String resName;
            try {
                //得到資源名稱
                resName = getResources().getResourceName(getId());
            } catch (Resources.NotFoundException e) {
                resName = Integer.toHexString(getId());
            }
            throw new IllegalStateException("The application's PagerAdapter changed the adapter's"
                    + " contents without calling PagerAdapter#notifyDataSetChanged!"
                    + " Expected adapter item count: " + mExpectedAdapterCount + ", found: " + N
                    + " Pager id: " + resName
                    + " Pager class: " + getClass()
                    + " Problematic adapter: " + mAdapter.getClass());
        }

        // Locate the currently focused item or add it if needed.
        int curIndex = -1;
        ItemInfo curItem = null;
        for (curIndex = 0; curIndex < mItems.size(); curIndex++) {
             //得到當前 ItemInfo
            final ItemInfo ii = mItems.get(curIndex);
            //如果 position 大于 mCurItem
            if (ii.position >= mCurItem) {
                // cutlItem=ii
                if (ii.position == mCurItem) curItem = ii;
                break;
            }
        }
// 如果等于空  添加
        if (curItem == null && N > 0) {
            curItem = addNewItem(mCurItem, curIndex);
        }
// 如果 curItem 不為空
        if (curItem != null) {
            float extraWidthLeft = 0.f;
            //-1
            int itemIndex = curIndex - 1;
            //ii  如果itemIndex 大于 0 取出,否則null
            ItemInfo ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
            //得到可用寬度
            final int clientWidth = getClientWidth();
           // 計算左邊需要的寬度
            final float leftWidthNeeded = clientWidth <= 0 ? 0 :
                    2.f - curItem.widthFactor + (float) getPaddingLeft() / (float) clientWidth;
            // 從'選中的下標-1' --> 0  遍歷
            for (int pos = mCurItem - 1; pos >= 0; pos--) {
                //如果額外的 大于 需要的   和  pos 小于 startPos
                if (extraWidthLeft >= leftWidthNeeded && pos < startPos) {
                    if (ii == null) {
                        break;
                    }
                    // 移除
                    if (pos == ii.position && !ii.scrolling) {
                        mItems.remove(itemIndex);
                        mAdapter.destroyItem(this, pos, ii.object);
                        if (DEBUG) {
                            Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                    + " view: " + ((View) ii.object));
                        }
                        itemIndex--;
                        curIndex--;
                        ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                    }
                } else if (ii != null && pos == ii.position) {    // 如果選中的上一個有 infoItem 
                    // extraWidthLeft 加上  ii.widthFactor
                    extraWidthLeft += ii.widthFactor;
                    itemIndex--;
                    //拿上一個 infoItem
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                } else {
                    // 新增一個 INfoItem
                    ii = addNewItem(pos, itemIndex + 1);
                    // 將占用的寬度比例加進來
                    extraWidthLeft += ii.widthFactor;
                    curIndex++;
                    ii = itemIndex >= 0 ? mItems.get(itemIndex) : null;
                }
            }
            // 右邊消耗的比例
            float extraWidthRight = curItem.widthFactor;
            //右邊index
            itemIndex = curIndex + 1;
            if (extraWidthRight < 2.f) {
                ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                // 計算右邊需要的寬度
                final float rightWidthNeeded = clientWidth <= 0 ? 0 :
                        (float) getPaddingRight() / (float) clientWidth + 2.f;
                for (int pos = mCurItem + 1; pos < N; pos++) {
                    // 如果 pos大于 endPos
                    if (extraWidthRight >= rightWidthNeeded && pos > endPos) {
                        if (ii == null) {
                            break;
                        }
                        //移除
                        if (pos == ii.position && !ii.scrolling) {
                            mItems.remove(itemIndex);
                            mAdapter.destroyItem(this, pos, ii.object);
                            if (DEBUG) {
                                Log.i(TAG, "populate() - destroyItem() with pos: " + pos
                                        + " view: " + ((View) ii.object));
                            }
                            ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                        }
                    } else if (ii != null && pos == ii.position) {
                        // 相等 那么取出數(shù)據(jù)
                        extraWidthRight += ii.widthFactor;
                        itemIndex++;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    } else {
                        // 否則 新建 infoItem
                        ii = addNewItem(pos, itemIndex);
                        itemIndex++;
                        extraWidthRight += ii.widthFactor;
                        ii = itemIndex < mItems.size() ? mItems.get(itemIndex) : null;
                    }
                }
            }
            // 計算偏移量
            calculatePageOffsets(curItem, curIndex, oldCurInfo);
        }

        if (DEBUG) {
            Log.i(TAG, "Current page list:");
            for (int i = 0; i < mItems.size(); i++) {
                Log.i(TAG, "#" + i + ": page " + mItems.get(i).position);
            }
        }
        // 通知適配器 哪個個是主頁
        mAdapter.setPrimaryItem(this, mCurItem, curItem != null ? curItem.object : null);
       // 結(jié)束更新
        mAdapter.finishUpdate(this);

        // Check width measurement of current pages and drawing sort order.
        // Update LayoutParams as needed.
        final int childCount = getChildCount();
        // 遍歷view
        for (int i = 0; i < childCount; i++) {
            final View child = getChildAt(i);
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            lp.childIndex = i;
            if (!lp.isDecor && lp.widthFactor == 0.f) {
                // 0 means requery the adapter for this, it doesn't have a valid width.
                final ItemInfo ii = infoForChild(child);
                if (ii != null) {
                    // 設(shè)置屬性
                    lp.widthFactor = ii.widthFactor;
                    lp.position = ii.position;
                }
            }
        }
        //排序
        sortChildDrawingOrder();

        if (hasFocus()) {
            View currentFocused = findFocus();
            ItemInfo ii = currentFocused != null ? infoForAnyChild(currentFocused) : null;
            if (ii == null || ii.position != mCurItem) {
                for (int i = 0; i < getChildCount(); i++) {
                    View child = getChildAt(i);
                    ii = infoForChild(child);
                   //如果拿到當前view
                    if (ii != null && ii.position == mCurItem) {
                         //設(shè)置聚焦
                        if (child.requestFocus(View.FOCUS_FORWARD)) {
                            break;
                        }
                    }
                }
            }
        }
    }

上面一段代碼很長,主要內(nèi)容就是首先根據(jù)mOffscreenPageLimit(頁面限制,用于確定緩存Fragment的數(shù)量)和mCurItem(當前應(yīng)顯示頁碼)計算出了應(yīng)加載的Fragment的頁碼數(shù),

//頁的限制
        final int pageLimit = mOffscreenPageLimit;
        //開始頁
        final int startPos = Math.max(0, mCurItem - pageLimit);
        //總數(shù)
        final int N = mAdapter.getCount();
        // 結(jié)束頁
        final int endPos = Math.min(N - 1, mCurItem + pageLimit);

然后根據(jù)計算得到的開始頁和結(jié)束頁調(diào)用Adapter移除或者加載Fragment。
原來ViewPager中有一個變量(mOffscreenPageLimit),用來確定加載的Fragment數(shù)量,那么我們修改這個變量不久行了嗎?
繼續(xù)往下看,ViewPager中有提供設(shè)置mOffscreenPageLimit的函數(shù)。

//默認的緩存頁面數(shù)量(常量)
private static final int DEFAULT_OFFSCREEN_PAGES = 1;

//緩存頁面數(shù)量(變量)
private int mOffscreenPageLimit = DEFAULT_OFFSCREEN_PAGES;

public void setOffscreenPageLimit(int limit) {
    //當我們手動設(shè)置的limit數(shù)小于默認值1時,limit值會自動被賦值為默認值1(即DEFAULT_OFFSCREEN_PAGES)
    if (limit < DEFAULT_OFFSCREEN_PAGES) {
        Log.w(TAG, "Requested offscreen page limit " + limit + " too small; defaulting to "+ DEFAULT_OFFSCREEN_PAGES);
        limit = DEFAULT_OFFSCREEN_PAGES;
    }

    if (limit != mOffscreenPageLimit) {
        //經(jīng)過前面的攔截判斷后,將limit的值設(shè)置給mOffscreenPageLimit,用于
        mOffscreenPageLimit = limit;
        populate();
    }
}

根據(jù)上面代碼我們可知,如果我們設(shè)置的mOffscreenPageLimit小于DEFAULT_OFFSCREEN_PAGE,就強制給mOffscreenPageLimit賦值為DEFAULT_OFFSCREEN_PAGE。這個DEFAULT_OFFSCREEN_PAGE的值就是1。
現(xiàn)在終于真相大白了,ViewPager通過一定的邏輯判斷來確保至少會預(yù)加載左右兩側(cè)相鄰的1個頁面,我們不能通過簡單設(shè)置mOffscreenPageLimit來達到懶加載的目的。

懶加載實現(xiàn)原理

還記得我們前面再Fragment中打印的生命周期嗎?我還在這個函數(shù)setUserVisibleHint(boolean isVisibleToUser)中打印了信息。根據(jù)函數(shù)名我們可以猜出來這個函數(shù)與用戶可見有關(guān)。查看這個函數(shù)的源碼

    /**
     * Set a hint to the system about whether this fragment's UI is currently visible
     * to the user. This hint defaults to true and is persistent across fragment instance
     * state save and restore.
     *
     * <p>An app may set this to false to indicate that the fragment's UI is
     * scrolled out of visibility or is otherwise not directly visible to the user.
     * This may be used by the system to prioritize operations such as fragment lifecycle updates
     * or loader ordering behavior.</p>
     *
     * <p><strong>Note:</strong> This method may be called outside of the fragment lifecycle.
     * and thus has no ordering guarantees with regard to fragment lifecycle method calls.</p>
     *
     * @param isVisibleToUser true if this fragment's UI is currently visible to the user (default),
     *                        false if it is not.
     */
    public void setUserVisibleHint(boolean isVisibleToUser) {
        if (!mUserVisibleHint && isVisibleToUser && mState < STARTED
                && mFragmentManager != null && isAdded()) {
            mFragmentManager.performPendingDeferredStart(this);
        }
        mUserVisibleHint = isVisibleToUser;
        mDeferStart = mState < STARTED && !isVisibleToUser;
    }

翻譯上面一段注釋:設(shè)置一個提示用來表示這個Fragment是否被用戶可見。應(yīng)用程序可以將此設(shè)置為false,以指示Fragment的UI被滾動到不可見的位置,或者用戶無法直接看到。系統(tǒng)可以使用它來對諸如片段生命周期更新或加載器排序行為等操作進行優(yōu)先級排序。這個函數(shù)可以在Fragment的生命周期以外調(diào)用。

總之,當Fragment從可見變?yōu)椴豢梢娛腔蛘邚牟豢梢娮優(yōu)榭梢姇r都會自動調(diào)用這個函數(shù),可見是輸入值為TRUE,不可見時輸入值為FALSE。因此我們可以根據(jù)這個函數(shù)的輸入值來判斷Fragment是否可見,從而進行懶加載。

因為Fragment在由可見變?yōu)椴豢梢?,和由不可見變?yōu)榭梢姷臅r候都會調(diào)用setUserVisibleHint()函數(shù),因此我們可以在這個函數(shù)實現(xiàn)懶加載的邏輯。在這個函數(shù)中,我們判斷只有當Fragment可見 & Fragment的View已經(jīng)加載完成 & Fragment沒有加載過數(shù)據(jù) 的條件下才進行數(shù)據(jù)加載。

根據(jù)這樣的思路,我們設(shè)置了幾個標記位:

    private boolean isViewInit; //view是否已經(jīng)加載完成
    private boolean isVisible; //是否可見
    private boolean isFirstLoad = true;//是否第一次加載數(shù)據(jù),默認為TURE,既默認為第一次加載數(shù)據(jù)

設(shè)置了一個函數(shù)來模擬從網(wǎng)絡(luò)加載數(shù)據(jù)

    /**
     * 模擬加載數(shù)據(jù)
     */
    protected void loadData(){
        Log.e(data,"加載數(shù)據(jù)!");
    }

然后將懶加載的邏輯寫成下面一個函數(shù)

    /**
     * 懶加載邏輯
     */
    private void lazyLoad() {
        if (!isViewInit || !isVisible || !isFirstLoad) {   //view加載完成&可見&第一次加載時才加載數(shù)據(jù)
            return;
        }
        loadData();   //加載數(shù)據(jù)
        isFirstLoad = false;   //更新標志位
    }

注意,別忘記了在onActivityCreated()中調(diào)用懶加載函數(shù),因為第一個要顯示的Fragment在由不可見變?yōu)榭梢娬{(diào)用setUserVisibleHint()函數(shù)時,該Fragment的View還沒有初始化完成,導致第一個顯示的Fragment無法第一次出現(xiàn)時無法加載數(shù)據(jù),必須重新滑動一下才能加載數(shù)據(jù)。

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        isViewInit=true;    //設(shè)置標志位,view表示初始化完成
        lazyLoad();    //懶加載
    }

setUserVisibleHint()函數(shù)中調(diào)用懶加載。

    @Override
    public void setUserVisibleHint(boolean isVisibleToUser) {
        super.setUserVisibleHint(isVisibleToUser);
        Log.e(data,"setUserVisibleHint:"+isVisibleToUser);
        if(isVisibleToUser){
            isVisible=true;
            lazyLoad();
        }else {
            isVisible=false;
        }
    }

以上就是我對懶加載的理解。如果你覺得我寫的有什么不對或者有什么補充的請留言。
代碼已上傳至GitHub https://github.com/Tiny-Monster/LazyLoadTest
參考博客
https://blog.csdn.net/chengkun_123/article/details/73694936
http://www.itdecent.cn/p/b8fe093a9d4b

最后編輯于
?著作權(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)容