ViewModel原理分析

2020年最后一篇,哈哈。

本篇文章主要分析ViewModel在Activity从销毁到重建时是如何保存并恢复的。

源码版本:androidx.lifecycle:lifecycle-viewmodel-ktx:2.2.0
使用的Android版本是 28

先说下结论

  1. 当Activity销毁重建的时候,会调用 ActivityThread 的 performDestroyActivity 方法,保存了一个 Activity.NonConfigurationInstances 对象在 ActivityClientRecord 对象中(ActivityClientRecord 对象应该是在 ActivityThread prepareRelaunchActivity 方法中创建的 )。
  2. 当重新创建Activity的时候,ActivityThread会调用performLaunchActivity方法。内部会调用Activity的 attach 方法并传入了 ActivityClientRecord对象中的Activity.NonConfigurationInstances 对象为为当前Activity实例的 mLastNonConfigurationInstances 赋值。
  3. 接下来在Activity的onCreate方法中,会从Activity的mLastNonConfigurationInstances.activity 获取销毁的Activity保存下来的ViewModelStore对象。而ViewModel 对象保存了创建过的ViewModel。所以新的Activity获取到的是老的ViewModel对象。

我们看一下ActivityClientRecord对象中保存的内容就大概清楚了。

ActivityClientRecord.png

基本流程源码分析

首先创建一个简单的ViewModel类。然后通过一个例子来对比一下普通对象和ViewModel对象。

class NameViewModel : ViewModel() {

    //创建一个持有String类型数据的LiveData
    val currentName: MutableLiveData<String> = MutableLiveData("Hello world")
}

简单使用

class ViewModelActivity : AppCompatActivity() {
    //普通的字符串对象
    private var normalText = "Hello world"
    private lateinit var model: NameViewModel

    private lateinit var tvViewModelText: TextView
    private lateinit var tvNormalText: TextView

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_view_model)
        tvNormalText = findViewById(R.id.tv_normal_text)
        tvViewModelText = findViewById(R.id.tv_view_model_text)
        //在onCreate中获取NameViewModel对象。
        model = ViewModelProvider(this).get(NameViewModel::class.java)

        //给TextView设置text
        tvNormalText.text = normalText
        tvViewModelText.text = model.currentName.value

        //观察LiveData
        model.currentName.observe(this, Observer<String> { t -> tvViewModelText.text = t })
    }

    fun onClick(view: View) {
        model.currentName.value = "Hello world clicked"
        normalText = "Hello world clicked"
        tvNormalText.text = normalText
    }
}
  1. 开始我们将普通对象normalText和ViewModel中的LiveData持有的字符串都赋值为 Hello world
  2. 点击按钮将两个值都赋值为Hello world clicked
  3. 旋转屏幕Activity重建,普通对象normalText被重新初始化为Hello world。ViewModel中的LiveData持有的字符串还是Hello world clicked
ViewModel配置变化.gif

结论:普通对象在Activity由于配置信息发生变化而重新创建的时候,会被重新初始化。ViewModel对象则会保存下来,不会因为Activity重建而重新初始化。

接下来开始分析其中的原理

//获取ViewModel对象
model = ViewModelProvider(this).get(NameViewModel::class.java)

ViewModelProvider的两个构造函数。

public ViewModelProvider(@NonNull ViewModelStoreOwner owner) {
    //注释1处,调用两个参数的构造函数
    this(owner.getViewModelStore(), owner instanceof HasDefaultViewModelProviderFactory
            ? ((HasDefaultViewModelProviderFactory) owner).getDefaultViewModelProviderFactory()
            : NewInstanceFactory.getInstance());
}

public ViewModelProvider(@NonNull ViewModelStore store, @NonNull Factory factory) {
    //注释2处,Factory用来创建ViewModels
    mFactory = factory;
    mViewModelStore = store;
}

注释1处,AppCompatActivity的间接父类ComponentActivity实现了ViewModelStoreOwner接口。

public class ComponentActivity extends androidx.core.app.ComponentActivity implements
    LifecycleOwner,
    ViewModelStoreOwner,
    SavedStateRegistryOwner,
    OnBackPressedDispatcherOwner {
        
    private ViewModelStore mViewModelStore;
        
    @NonNull
    @Override
    public ViewModelStore getViewModelStore() {
        if (mViewModelStore == null) {
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // 从NonConfigurationInstances中恢复ViewModelStore 
                mViewModelStore = nc.viewModelStore;
            }
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
        return mViewModelStore;
    }
        
    //...
    
}

这里我们先简单认为ComponentActivity的getViewModelStore()方法,返回了一个ViewModelStore对象。后面还会仔细分析这一部分。

ViewModelProvider的构造函数的注释1处,AppCompatActivity及其父类都没有实现HasDefaultViewModelProviderFactory接口。所以注释2处传入的Factory默认是NewInstanceFactory.ViewModelProvider的一个单例类。

接下来我们看下ViewModelProvider的get方法


private static final String DEFAULT_KEY =
            "androidx.lifecycle.ViewModelProvider.DefaultKey";

@MainThread
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");
    }
    //根据类名获取ViewModel对象
    return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
     //注释1处,从ViewModelStore中获取
     ViewModel viewModel = mViewModelStore.get(key);

     if (modelClass.isInstance(viewModel)) {//类型匹配
         if (mFactory instanceof OnRequeryFactory) {
             ((OnRequeryFactory) mFactory).onRequery(viewModel);
         }
         //直接返回
         return (T) viewModel;
     } else {
         //类型不匹配,丢弃viewModel
         if (viewModel != null) {
             // TODO: log a warning.
         }
     }
     //注释2处,根据类对象创建ViewModel对象
     if (mFactory instanceof KeyedFactory) {
         viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
     } else {
         viewModel = (mFactory).create(modelClass);
     }
     //加入缓存
     mViewModelStore.put(key, viewModel);
     //返回创建的对象
     return (T) viewModel;
}

逻辑也很简单,注释1处根据传入的modelClass对象的类名称先从ViewModelStore中获取对象。

ViewModelStore类:就是一个存放ViewModel的HashMap。

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();
    }
}

如果获取不到,就通过反射创建ViewModel对象并加入到ViewModelStore中来保存。

NewInstanceFactory的create方法,通过反射创建ViewModel对象。

@NonNull
@Override
public <T extends ViewModel> T create(Class<T> modelClass) {
    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);
    }
}

到现在,铺垫的差不多了,该进入正题了。我们回过头来看ComponentActivity的getViewModelStore()方法。

核心原理

@NonNull
@Override
public ViewModelStore getViewModelStore() {
    if (mViewModelStore == null) {
        //注释1处
        NonConfigurationInstances nc = (NonConfigurationInstances) 
                             getLastNonConfigurationInstance();
        if (nc != null) {
            //从NonConfigurationInstances中恢复ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        //注释2处,如果mViewModelStore依然为null,则创建新的ViewModelStore对象。
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

注释1处,调用Activity的getLastNonConfigurationInstance方法。

/**
 * 获取先前由{@link#onRetainNonConfigurationInstance()}方法返回的和配置信息无关
 * (non-configuration)的实例数据。
 * 当新的Activity的实例的{@link#onCreate}和{@link#onStart}方法被调用的时候,
 * 可以调用这个方法来获取前一个被销毁的Activity通过{@link#onRetainNonConfigurationInstance()}方法
 * 保存的一些数据。
 **/
@Nullable
public Object getLastNonConfigurationInstance() {
    return mLastNonConfigurationInstances != null
            ? mLastNonConfigurationInstances.activity : null;
}

如果mLastNonConfigurationInstances不为null,返回mLastNonConfigurationInstancesactivity字段,否则返回null。

我们先看一下Activity.NonConfigurationInstances类

static final class NonConfigurationInstances {
    //这个activity字段是Object类型
    Object activity;
    HashMap<String, Object> children;
    FragmentManagerNonConfig fragments;
    ArrayMap<String, LoaderManager> loaders;
    VoiceInteractor voiceInteractor;
}

我们知道当Activity配置信息发生变化的时候,Activity会销毁然后重新创建,Activity.NonConfigurationInstances类就是用来在Activity销毁的时候用来保存一些有用的数据的。和Bundle相比,Activity.NonConfigurationInstances几乎可以保存任意对象(比如Bitmap)。

接下来,我们看一下Activity.NonConfigurationInstances对象是怎么被保存的。

当Activity因配置信息发生变化销毁然后重新创建,调用ActivityThread的handleRelaunchActivity方法。

@Override
public void handleRelaunchActivity(ActivityClientRecord tmp, PendingTransactionActions pendingActions) {

    //...
     handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                    pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
    //...
}

ActivityThread的handleRelaunchActivityInner方法。

private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
            List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
            PendingTransactionActions pendingActions, boolean startsNotResumed,
            Configuration overrideConfig, String reason) {
    //...
    //注释1处
    handleDestroyActivity(r.token, false, configChanges, true, reason);

   //...
}

注意:注释1处,传入的倒数第二个参数为true。

ActivityThread的handleDestroyActivity方法。

@Override
public void handleDestroyActivity(IBinder token, boolean finishing, int configChanges,
        boolean getNonConfigInstance, String reason) {

    ActivityClientRecord r = performDestroyActivity(token, finishing,
                    configChanges, getNonConfigInstance, reason);
    //...

}

ActivityThread的performDestroyActivity方法。

ActivityClientRecord performDestroyActivity(IBinder token, boolean finishing,
            int configChanges, boolean getNonConfigInstance, String reason) {

    ActivityClientRecord r = mActivities.get(token);

    if (getNonConfigInstance) {
        try {
            //注释1处
            r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        } catch (Exception e) {
            //...
        }
   }

}

注释1处,getNonConfigInstance为true,会调用Activity的retainNonConfigurationInstances方法。

NonConfigurationInstances retainNonConfigurationInstances() {
    //注释1处
    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;
    }
    //注释2处,正常返回一个Activity.NonConfigurationInstances对象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.activity = activity;
    nci.children = children;
    nci.fragments = fragments;
    nci.loaders = loaders;
    if (mVoiceInteractor != null) {
        mVoiceInteractor.retainInstance();
        nci.voiceInteractor = mVoiceInteractor;
    }
    return nci;
}

注释1处,获取Activity.NonConfigurationInstances的activity字段要保存的信息。

ComponentActivity重写了Activity的onRetainNonConfigurationInstance方法。

@Override
@Nullable
public final Object onRetainNonConfigurationInstance() {

    Object custom = onRetainCustomNonConfigurationInstance();
    //获取当前ViewModelStore对象
    ViewModelStore viewModelStore = mViewModelStore;
    if (viewModelStore == null) {
        // viewModelStore为null,说明当前Activity实例没有调用过getViewModelStore()
        //来创建ViewModelStore对象,所以检查前一个Activity销毁的时候是否在
        //NonConfigurationInstances中保存有ViewModelStore对象。
        NonConfigurationInstances nc =
                (NonConfigurationInstances) getLastNonConfigurationInstance();
        if (nc != null) {
            viewModelStore = nc.viewModelStore;
        }
    }
    //如果没有需要保存的,就返回null
    if (viewModelStore == null && custom == null) {
        return null;
    }
    //注释1处,正常返回一个ComponentActivity.NonConfigurationInstances对象
    NonConfigurationInstances nci = new NonConfigurationInstances();
    nci.custom = custom;
    nci.viewModelStore = viewModelStore;
    return nci;
}

注释1处,正常返回一个ComponentActivity.NonConfigurationInstances对象。注意和Activity的Activity.NonConfigurationInstances不一样。

ComponentActivity.NonConfigurationInstances类

static final class NonConfigurationInstances {
    Object custom;
    //用来保存创建的ViewModelStore
    ViewModelStore viewModelStore;
}

我们回到ActivityThread的performDestroyActivity方法。最终是将一个Activity.NonConfigurationInstances对象保存到了ActivityClientRecord中。这个ActivityClientRecord如下所示:

ActivityClientRecord.png

然后我们看是怎么保存的数据是如何恢复的。

当重新创建Activity的时候,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);
   //...
}

调用了Activity的attach方法。并传入了我们在销毁上一个Activity的时候保存在ActivityClientRecord中的lastNonConfigurationInstances对象。

Activity的attach方法

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) {

    attachBaseContext(context);
    //...
    //为当前Activity实例的mLastNonConfigurationInstances赋值。
    mLastNonConfigurationInstances = lastNonConfigurationInstances;

}

Activity的attach方法在Activity的onCreate方法之前被调用。在Activity的attach方法内部为当前Activity实例的mLastNonConfigurationInstances赋值。

重新创建的ViewModelActivity的onCreate方法中,我们创建ViewModelProvider会调用ComponentActivity的getViewModelStore方法。

model = ViewModelProvider(this).get(NameViewModel::class.java)

@NonNull
@Override
public ViewModelStore getViewModelStore() {
        
    if (mViewModelStore == null) {
        //注释1处
        NonConfigurationInstances nc =(NonConfigurationInstances) 
                getLastNonConfigurationInstance();
        if (nc != null) {
            //从NonConfigurationInstances中恢复ViewModelStore
            mViewModelStore = nc.viewModelStore;
        }
        //注释2处,如果mViewModelStore依然为null,则创建新的ViewModelStore对象。
        if (mViewModelStore == null) {
            mViewModelStore = new ViewModelStore();
        }
    }
    return mViewModelStore;
}

这个时候,注释1处会从Activity的mLastNonConfigurationInstances.activity获取销毁的Activity保存下来的mViewModelStore对象。

然后ViewModelProvider的get方法,也就是从mViewModelStore中获取ViewModel对象。

@MainThread
public <T extends ViewModel> T get(@NonNull String key, @NonNull Class<T> modelClass) {
    //注释1处,mViewModelStore中获取
    ViewModel viewModel = mViewModelStore.get(key);

    if (modelClass.isInstance(viewModel)) {//类型匹配
        if (mFactory instanceof OnRequeryFactory) {
            ((OnRequeryFactory) mFactory).onRequery(viewModel);
        }
        //直接返回
        return (T) viewModel;
    } else {
        //类型不匹配,丢弃缓存中的viewModel
        if (viewModel != null) {
            // TODO: log a warning.
        }
    }
    //注释2处,根据类对象创建ViewModel对象
    if (mFactory instanceof KeyedFactory) {
        viewModel = ((KeyedFactory) (mFactory)).create(key, modelClass);
    } else {
        viewModel = (mFactory).create(modelClass);
    }
    //加入缓存
    mViewModelStore.put(key, viewModel);
    //返回创建的对象
    return (T) viewModel;
}

注释1处,这个时候获取的ViewModel对象不为空,所以不会重新创建ViewModel对象。

总结一下:

  1. 当Activity由于配置信息发生改变而重建的时候,会保存一个Activity.NonConfigurationInstances对象到ActivityClientRecord中。

  2. AppCompatActivity的间接父类ComponentActivity在此过程中保存了一个ComponentActivity.NonConfigurationInstancesActivity.NonConfigurationInstances中。ComponentActivity.NonConfigurationInstances中保存了AppCompatActivity当前的ViewModelStore对象。ViewModelStore对象中存储着当前AppCompatActivity的所有ViewModel对象。

  3. 在新创建的AppCompatActivity的attach方法中从ActivityClientRecord中获取了保存的ViewModelStore对象。该方法在onCreate方法之前被调用。

  4. 在新创建的AppCompatActivity的onCreate方法中可以获取到恢复了的ViewModelStore对象。从而可以获取对应的ViewModel对象。

其他

为啥Activity正常销毁的时候不保存Activity.NonConfigurationInstances对象?

public class DestroyActivityItem extends ActivityLifecycleItem {

    private boolean mFinished;
    private int mConfigChanges;

    @Override
    public void execute(ClientTransactionHandler client, IBinder token,
            PendingTransactionActions pendingActions) {
        Trace.traceBegin(TRACE_TAG_ACTIVITY_MANAGER, "activityDestroy");

        //注释1处,这里的getNonConfigInstance为fasle
        client.handleDestroyActivity(token, mFinished, mConfigChanges,
                false /* getNonConfigInstance */, "DestroyActivityItem");
        Trace.traceEnd(TRACE_TAG_ACTIVITY_MANAGER);
    }
}

当Activity正常结束,不需要重新创建的时候,的时候会执行DestroyActivityItem,调用ActivityThread的handleDestroyActivity方法的时候传入的getNonConfigInstance参数为false,不会保存Activity.NonConfigurationInstances对象。

ViewModelStore何时清空保存的ViewModel

ComponentActivity的构造函数

public ComponentActivity() {
    Lifecycle lifecycle = getLifecycle();
        
    getLifecycle().addObserver(new LifecycleEventObserver() {
        @Override
        public void onStateChanged(LifecycleOwner source, Lifecycle.Event event) {
            //注释1处
            if (event == Lifecycle.Event.ON_DESTROY) {
                if (!isChangingConfigurations()) {
                    getViewModelStore().clear();
                }
            }
        }
    });
    //...
}

ComponentActivity监听了声明周期变化,当ComponentActivity销毁的时候,如果不是因为配置发生变化而销毁的话,ViewModelStore就清空保存的ViewModel对象。

参考链接:

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,752评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,100评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,244评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,099评论 1 286
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,210评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,307评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,346评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,133评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,546评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,849评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,019评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,702评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,331评论 3 319
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,030评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,260评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,871评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,898评论 2 351

推荐阅读更多精彩内容