ViewModel 的重建恢復(fù)原理

1. 從 ViewModelProvider(this).get(xxViewModel.class) 講起

首先,我們看看 ViewModelProvider 的構(gòu)造函數(shù):

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

上面代碼我們有兩個地方需要搞清楚,ViewModelStoreViewModelProvider.NewInstanceFactory。大體上我們可以猜測到,前者是存儲 ViewModel 實(shí)例的,后者是構(gòu)造 ViewModel 實(shí)例的工廠。

1.1 ViewModelStore(存儲 ViewModel 的實(shí)例)

ViewModelProvider 的構(gòu)造函數(shù)接收一個 ViewModelStoreOwner,而 ViewModelStoreOwner 接口只有一個 getViewModelStore 方法,它返回一個 ViewModelStore。所以我們看看 ViewModelStore(顧名思義可以猜到它就是用來存儲 ViewModel 實(shí)例的):

public class ViewModelStore {
    private final HashMap<String, ViewModel> mMap = new HashMap<>();
    final void put(String key, ViewModel viewModel) {
        ViewModel oldViewModel = mMap.put(key, viewModel);
        if (oldViewModel != null) {
            oldViewModel.onCleared();
        }
    }
    final ViewModel get(String key) {
        return mMap.get(key);
    }
    Set<String> keys() {
        return new HashSet<>(mMap.keySet());
    }
    /**
     *  Clears internal storage and notifies ViewModels that they are no longer used.
     */
    public final void clear() {
        for (ViewModel vm : mMap.values()) {
            vm.clear();
        }
        mMap.clear();
    }
}

實(shí)現(xiàn)非常簡單:只有一個用于存儲 ViewModelhash 表:

  • key 是由 ViewModelProvider 生成的:DEFAULT_KEY + ":" + canonicalName,其中 DEFAULT_KEY = androidx.lifecycle.ViewModelProvider.DefaultKey。
  • valueViewModel 的實(shí)例。

有點(diǎn)類似 ThreadLocal 的實(shí)現(xiàn)原理,本質(zhì)上是有一個 Entry,它存儲了 ThreadLocalvalue 的映射

因此,ViewModelStore 的職責(zé)是保存 ViewModel 的實(shí)例,而 ViewModelStoreowner 類,則必須保證在發(fā)生配置改變時(shí),發(fā)生重建后,依然返回同一個 ViewModelStore 實(shí)例(這樣,我們就可以返回同一個 ViewModel)。我們的 ComponentActivity 就是一個 ViewModelStoreOwner,我們在后面小節(jié)將會看看在重建后,它是如何返回同一個 ViewModelStore 實(shí)例的。

1.2 ViewModelProvider.Factory(構(gòu)造 ViewModel 實(shí)例的工廠方法)

ViewModelProvider 的帶兩個參數(shù)的構(gòu)造函數(shù)的第二個參數(shù)是一個 Factory

/**
 * Implementations of {@code Factory} interface are responsible to instantiate ViewModels.
 */
public interface Factory {
    /**
     * Creates a new instance of the given {@code Class}.
     * <p>
     *
     * @param modelClass a {@code Class} whose instance is requested
     * @param <T>        The type parameter for the ViewModel.
     * @return a newly created ViewModel
     */
    @NonNull
    <T extends ViewModel> T create(@NonNull Class<T> modelClass);
}

從注釋和接口代碼中我們可以看出,它是一個實(shí)例化 ViewModel 的工廠,只有一個產(chǎn)生 ViewModel 實(shí)例的 create 方法。從方法簽名中我們就可以看出,它接收 Class,通過反射之類的方法來構(gòu)造 ViewModel。通常我們默認(rèn)使用的是 ViewModelProvider.NewInstanceFactory

/**
 * Simple factory, which calls empty constructor on the give class.
 */
public static class NewInstanceFactory implements Factory {
    private static NewInstanceFactory sInstance;
    /**
     * Retrieve a singleton instance of NewInstanceFactory.
     *
     * @return A valid {@link NewInstanceFactory}
     */
    @NonNull
    static NewInstanceFactory getInstance() {
        if (sInstance == null) {
            sInstance = new NewInstanceFactory();
        }
        return sInstance;
    }
    @SuppressWarnings("ClassNewInstance")
    @NonNull
    @Override
    public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
        //noinspection TryWithIdenticalCatches
        try {
            return modelClass.newInstance();
        } catch (InstantiationException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        } catch (IllegalAccessException e) {
            throw new RuntimeException("Cannot create an instance of " + modelClass, e);
        }
    }
}

可以看到,默認(rèn)的 NewInstanceFactory 只是簡單地反射調(diào)用 ViewModel 的無參構(gòu)造函數(shù)來實(shí)例化傳入的 ViewModel。

2. Activity 中 ViewModel 的創(chuàng)建和獲取

我們從常用的獲取 ViewModel 方法出發(fā):

ChronometerViewModel chronometerViewModel
        = new ViewModelProvider(this).get(ChronometerViewModel.class);
??
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
    String canonicalName = modelClass.getCanonicalName();
    if (canonicalName == null) {
        throw new IllegalArgumentException("Local and anonymous classes can not be ViewModels");
    }
    // 這里的 get 方法就生成了我們上面 ViewModelStore 中的 key 值了
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
??
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    // 從 ViewModelStore 中獲取 ViewModel 實(shí)例
    ViewModel viewModel = mViewModelStore.get(key);
    // 如果獲取到,且確實(shí)是我們想要獲取的 ViewModel 類型,則直接返回
    if (modelClass.isInstance(viewModel)) {
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        return (T) viewModel;
    } else {
        //noinspection StatementWithEmptyBody
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    // 如果沒有獲取到 ViewModel 實(shí)例,則調(diào)用 Factory 進(jìn)行創(chuàng)建
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    // 將創(chuàng)建出來的 ViewModel 存入 ViewModelStore
    mViewModelStore.put(key, viewModel);
    return (T) viewModel;
}

從上面可以看到,當(dāng)我們調(diào)用 new ViewModelProvider(this).get(xxViewModel.class); 來實(shí)例化并獲取 xxViewModel 實(shí)例時(shí),其實(shí)就是從 1.1ViewModelStore 中去取,如果沒有就用 1.2newInstanceFactory 新構(gòu)造一個,并放入 1.1ViewModelStore 以方便以后獲取同一個實(shí)例。

所以,配置重建后,能夠獲取同一個 ViewModel 實(shí)例的關(guān)鍵在于能夠獲取到同一個 ViewModelStore 的實(shí)例。

3. Activity 中 ViewModel 的保存和恢復(fù)

上一節(jié)中我們說了,ViewModel 的保存和恢復(fù)是本質(zhì)上依賴于 ViewModelStore 的保存和恢復(fù)。而 ViewModelStore 的保存和恢復(fù)則是利用了 Activity.onRetainNonConfigurationInstance 機(jī)制。

3.1 ViewModelStore 的保存

我們來看支持 ViewModel 機(jī)制的 ComponentActivity.onRetainNonConfigurationInstance 中是如何保存 ViewModelStore 的:

@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {
    Object custom = onRetainCustomNonConfigurationInstance();
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // No one called getViewModelStore(), so see if there was an existing
        // ViewModelStore from our last NonConfigurationInstance
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
    if (viewModelStore == null && custom == null) {
        return null;
    }
    // 將 ViewModelStore 保存在 NonConfigurationInstances 中。保存了 ViewModelStore 也就保存了 ViewModel
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

從上面我們可以看到,ViewModelStore保存是依賴于 onRetainNonConfigurationInstance 的,在 onRetainNonConfigurationInstance 回調(diào)時(shí),構(gòu)建了新的 NonConfigurationInstance 實(shí)例,并將現(xiàn)在的 ViewModelStore 賦值給它的 nci.viewModelStore 屬性。

3.2 ViewModelStore 的恢復(fù)

ViewModelStoreViewModel 的恢復(fù)其實(shí)就是當(dāng)我們使用 ViewModelProvider 來獲取 ViewModel 實(shí)例時(shí),內(nèi)部自己進(jìn)行恢復(fù)的。我們來看看 ViewModelProvider 的構(gòu)造方法:

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

上面?zhèn)魅?Activity 作為 ViewModelStoreOwner,當(dāng)調(diào)用它的 getViewModelStore 時(shí) ViewModelStore 也就被恢復(fù)了:

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (getApplication() == null) {
        throw new IllegalStateException("Your activity is not yet attached to the "
                + "Application instance. You can't request ViewModel before onCreate call.");
    }
    if (mViewModelStore == null) {
        // 這里獲得的 NonConfigurationInstance 就是之前通過 onRetainNonConfigurationInstance 保存的
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            // Restore the ViewModelStore from NonConfigurationInstances
            mViewModelStore = nc.viewModelStore;
        }
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

我們通過方法 getLastNonConfigurationInstance 獲取到之前保存的 NonConfigurationInstances,從而取出其中的 ViewModelStore

4. onRetainNonConfigurationInstance 機(jī)制

通過上面的分析,我們知道,保存恢復(fù) ViewModelStoreViewModel 的關(guān)鍵在于 onRetainNonConfigurationInstancegetLastNonConfigurationInstance 機(jī)制是如何起作用的?

通過查找方法引用,我們可以看到 onRetainNonConfigurationInstance 方法其實(shí)是在 Activity.retainNonConfigurationInstances 方法中被調(diào)用的:

NonConfigurationInstances retainNonConfigurationInstances() {
    Object activity = onRetainNonConfigurationInstance();
    HashMap<String, Object> children = onRetainNonConfigurationChildInstances();
    FragmentManagerNonConfig fragments = mFragments.retainNestedNonConfig();
    // We're already stopped but we've been asked to retain.
    // Our fragments are taken care of but we need to mark the loaders for retention.
    // In order to do this correctly we need to restart the loaders first before
    // handing them off to the next activity.
    mFragments.doLoaderStart();
    mFragments.doLoaderStop(true);
    ArrayMap<String, LoaderManager> loaders = mFragments.retainLoaderNonConfig();
    if (activity == null && children == null && fragments == null && loaders == null
            && mVoiceInteractor == null) {
        return null;
    }
    // 注意 Activity 中的 NonConfigurationInstances 類與 ComponentActivity 中的 NonConfigurationInstances 類不一樣
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;  // 這里將 onRetainNonConfigurationInstance 返回值保存了下來,其中就包含了 ViewModelStore
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

Activity.retainNonConfigurationInstances 方法是在 ActivityThread.performDestroyActivity 方法中被調(diào)用的。而 ComponentActivity 中的 mLastNonConfigurationInstances 則是在 ComponentActivity.attach 方法中,由 ActivityThread.performLaunchActivity 時(shí)主動調(diào)用傳入進(jìn)來的。

因此綜上所述,ViewModelStoreActivity.onDestroy() 銷毀時(shí),通過 onRetainNonConfigurationInstance 時(shí)的回調(diào)被保存在了 NonConfigurationInstances 實(shí)例中,該實(shí)例被 ActivityThread 持有。下次 Activity 重建時(shí),由 ActivityThread.performLaunchActivity 方法中調(diào)用 Activity.attach 方法,再將 NonConfigurationInstances 實(shí)例傳給重建后的 Activity。

5. Activity 中 ViewModel 的銷毀

通過上節(jié)我們知道 ViewModel 可以跨越 Activity 的生命周期和重建,那么 ActivityViewModel 怎么知道需要在什么時(shí)候銷毀呢?

getLifecycle().addObserver(new LifecycleEventObserver() {
    @Override
    public void onStateChanged(@NonNull LifecycleOwner source,
            @NonNull Lifecycle.Event event) {
        if (event == Lifecycle.Event.ON_DESTROY) {
            if (!isChangingConfigurations()) {
                getViewModelStore().clear();
            }
        }
    }
});

如上面代碼所示,ViewModel 正是借助了生命周期的回調(diào),在 ON_DESTROY 回調(diào)時(shí),通過 isChangingConfigurations 方法判斷該 Activity 是否正處于因配置改變而重建中,如果不是的話,則可以清除 ViewModel。

6. Fragment 中 ViewModel 的創(chuàng)建和獲取

由于 Fragment 同樣也是實(shí)現(xiàn)了 ViewModelStoreOwner 接口,因此同在 Activity 中創(chuàng)建和獲取 ViewModel 一樣,給 ViewModelProvider 的構(gòu)造函數(shù)傳入自己的實(shí)例即可:

mSeekBarViewModel = new ViewModelProvider(this).get(SeekBarViewModel.class);

如果 Fragment 要和其宿主 Activity 共用一個 ViewModel,則可以向 ViewModelProvider 的構(gòu)造函數(shù)傳入其宿主 Activity 即可:

mSeekBarViewModel = new ViewModelProvider(requireActivity()).get(SeekBarViewModel.class);

由上面的 (2) 我們知道,創(chuàng)建和獲取 ViewModel 的關(guān)鍵在于 getViewModelStore 方法,而這個方法的實(shí)現(xiàn)上,ActivityFragment 是不相同的:

  • FragmentgetViewModelStore 是從 FragmentManager.getViewModelStore 中獲取的。
  • ActivitygetViewModelStore 是從 ComponentActivity.getViewModelStore 獲取的,它是直接存儲在 mViewModelStore 中的(根據(jù) (4)mViewModelStore 是在 Activity attach 時(shí),由 ActivityThread 傳入的)。

因此我們下面分析的重點(diǎn)應(yīng)該在于 FragmentManager.getViewModelStore 的機(jī)制。

6.1 FragmentActivity 在初始化時(shí)做了什么

Fragment 是依附于 FragmentActivity 的,因此對 FragmentManager 的操作一定也是通過 FragmentActivity 來完成的。

我們可以注意到在 FragmentActivity.onCreate 中調(diào)用了 mFragments.attachHost

mFragments.attachHost(null /*parent*/);

最終調(diào)用到了 FragmentManager.attachController (注意,這里的 FragmentManager 就是根 Fragment 中的 mFragmentManager):

// 該方法在 FragmentManager 中。host 為 FragmentActivity.HostCallbacks。
void attachController(@NonNull FragmentHostCallback<?> host,
        @NonNull FragmentContainer container, @Nullable final Fragment parent) {
    ...
    // Get the FragmentManagerViewModel
    if (parent != null) { // FragmentActivity 中傳入的 parent 為 null,因此不會走這里
        mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
    } else if (host instanceof ViewModelStoreOwner) { // ??調(diào)用到這里
        // 這里最終獲取的是 FragmentActivity 的 ViewModelStore
        ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
        // mNonConfig 是這個 FragmentManager 對應(yīng)的 Fragment 的 ViewModel。
        mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
    } else {
        mNonConfig = new FragmentManagerViewModel(false);
    }
}

因?yàn)樵?FragmentActivity 中傳入的 parentnull,且 FragmentActivity.HostCallbacks 實(shí)現(xiàn)了 ViewModelStoreOwner,因此會走到上面的第二個條件分支。

FragmentActivity.HostCallbacks.getViewModelStore 就是調(diào)用的 FragmentActivity.getViewModelSotre(是的,HostCallbacks 就是繞了一下而已),因此上面代碼創(chuàng)建的實(shí)例 mNonConfig: FragmentManagerViewModel,其實(shí)存入到了兩個地方:
1. FragmentActivityViewModelStore 中,所以 FragmentActivity.mViewModelStore 中不僅有 Activity 環(huán)境下創(chuàng)建的 ViewModel ,還包含了一個(或多個)根 Fragment 對應(yīng)的 FragmentManagerViewModel。
2. FragmentManager.mNonConfig(注意 mNonConfig 是一個 FragmentManagerViewModel 的實(shí)例) 中

這兩點(diǎn)很重要。

6.2 在根 Fragment 中獲取 ViewModel

我們知道關(guān)鍵點(diǎn)在于調(diào)用的 Fragment.getViewModelStore

public ViewModelStore getViewModelStore() {
    if (mFragmentManager == null) {
        throw new IllegalStateException("Can't access ViewModels from detached fragment");
    }
    // 這里的 mFragmentManager 實(shí)例就是 FragmentActivity.HostCallbacks 內(nèi)部類中的 mFragmentManager,
    // 也就是在 (6.1) 節(jié)中我們提到擁有 FragmentManagerViewModel 的那個 FragmentManager
    return mFragmentManager.getViewModelStore(this);
}

// FragmentManager ??
ViewModelStore getViewModelStore(@NonNull Fragment f) {
    // 這里的 mNonConfig 根據(jù) (6.1) 節(jié),是一個 FragmentManagerViewModel 的實(shí)例
    return mNonConfig.getViewModelStore(f);
}

從上面代碼和注釋中我們可以知道

  • FragmentgetViewModelStore 最終是從 FragmentManagerViewModel.getViewModelStore 中獲取的。這與 FragmentActivity: ComponentActivitygetViewModelStore 來源不一樣,因此在 FragmentActivityFragment 中都獲取同樣類型(類名一樣)的 ViewModel,獲取出來的實(shí)例卻是不一樣的。
  • 根據(jù) (6.1) 我們可以知道,根 Fragment.getViewModelStore 的最終調(diào)用處 FragmentManagerViewModel.getViewModelStore 中的 FragmentManagerViewModel 實(shí)例,其實(shí)在 FragmentActivity.onCreate 時(shí)就已經(jīng)存入了 FragmentActivityViewModelStore 中。也就是說它們之間的結(jié)構(gòu)是下面這樣的:
    FragmentManagerViewModel 與 Activity 的關(guān)系

所以,Activity 中創(chuàng)建的 ViewModel 是來自于它的 mViewModelStore。而根 Fragment 中創(chuàng)建的 ViewModel 是來自于它的宿主 ActivitymViewModelStore.FragmentManagerViewModel.mViewModelStores。

這里 mViewModelStores 是復(fù)數(shù)的原因是,一個 Activity 可能有多個根 Fragment

6.3 Fragment 創(chuàng)建時(shí)做了什么

(6.2) 最后的結(jié)論我們知道,FragmentActivityonCreate 時(shí)就做好了如下引用鏈:activity: FragmentActivitymViewModelStore: ViewModelStore → 里面有一個 key 對應(yīng)的是 FragmentManagerViewModelFragmentManagerViewModel.mViewModelStores

Q: 那么 根Fragment 是否也對 子Fragment 做了同樣的準(zhǔn)備?
各個層級的 Fragment 與它對應(yīng)的 FragmentManager 有如下對應(yīng)關(guān)系:

  • 根FragmentFragmentActivity.FragmentController.mHost.mFragmentManager 以及它自己的 mFragmentManager: FragmentManagerImpl
  • 子Fragment父Fragment.getChildFragmentManager 以及 它自己的 mFragmentManager: FragmentManagerImpl。

當(dāng) 根Fragment 關(guān)聯(lián)到 Activity 上時(shí),會調(diào)用其 performAttach 方法:

// androidx.fragment.app.Fragment
void performAttach() {
    // 類比于 FragmentActivity.onCreate 中的 mFragments.attachHost -> mHost.mFragmentManager.attachController
    mChildFragmentManager.attachController(mHost, new FragmentContainer() {
        @Override
        @Nullable
        public View onFindViewById(int id) {
            if (mView == null) {
                throw new IllegalStateException("Fragment " + this + " does not have a view");
            }
            return mView.findViewById(id);
        }
        @Override
        public boolean onHasView() {
            return (mView != null);
        }
    }, this);
    mState = ATTACHED;
    mCalled = false;
    onAttach(mHost.getContext());
    if (!mCalled) {
        throw new SuperNotCalledException("Fragment " + this
                + " did not call through to super.onAttach()");
    }
}

之后,它們通過各自的 FragmentManager 操作,在各層級的 FragmentManagerViewModel 之間建立了如下圖的關(guān)系。

FragmentManagerViewModel 之間的關(guān)系

所以,各層級的 FragmentViewModel 能夠恢復(fù),也是受益于 ActivityViewModelStore 的恢復(fù)。

番外:理解 FragmentHostCallback 和 FragmentController

從上面的代碼中,我們可以注意到在 FragmentActivity 中有一個內(nèi)部類 HostCallbacks,這個類繼承自 FragmentHostCallback,但是它的所有實(shí)現(xiàn)方法都只是調(diào)用它的外部類 FragmentActivity 的相應(yīng)方法而已,它自己沒有任何實(shí)質(zhì)的操作。

FragmentHostCallback 的類注釋中我們可以知道,該類主要是為了面向 Fragment 宿主提供一個統(tǒng)一的 Fragment 相關(guān)信息的回調(diào),包括 onGetLayoutInflater,onShouldSaveFragmentState 等。這些回調(diào)有的來自 Fragment 類,有的來自 FragmentManager 類,因此 FragmentHostCallbackFragment 向外暴露信息的回調(diào)的整合點(diǎn)。目前 FragmentHostCallback 的唯一實(shí)現(xiàn)類是 FragmentActivity 的內(nèi)部類 HostCallbacks。內(nèi)部類可以直接調(diào)用它的外部類,這樣,Fragment 的相關(guān)信息回調(diào)就通過 FragmentActivity.HostCallbacks 傳給了 FragmentActivity。

FragmentHostCallback 類不僅向宿主類提供 Fragment 的相關(guān)事件回調(diào),它還持有一個 FragmentManager 的實(shí)例 mFragmentManager,并且該實(shí)例在 FragmentHostCallback 中沒有任何使用。我們查看 mFragmentManager 使用的地方可以發(fā)現(xiàn)都是在 FragmentController 中。

FragmentController 通過構(gòu)造函數(shù)傳入 FragmentHostCallback 的實(shí)例 mHost,再通過 mHost 調(diào)用到 mFragmentManager。我們看看 FragmentController 的方法,可以發(fā)現(xiàn)幾乎都是宿主 FragmentActivity 主動調(diào)用的 生命周期方法 以及 需要從 Fragment 中獲取數(shù)據(jù)的方法,例如獲取 FragmentManager 實(shí)例。

FragmentController.png

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

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