JetPack之ViewModel

1,627 阅读12分钟

ViewModel

1.什么是VeiwModel

ViewModel 类是一种业务逻辑或屏幕级状态容器。它用于将状态公开给界面,以及封装相关的业务逻辑。 它的主要优点是,它可以缓存状态,并可在配置更改后持久保留相应状态。这意味着在 activity 之间导航时或进行配置更改后(例如旋转屏幕时),界面将无需重新提取数据。

简单来说就是保存activity和fargment页面的数据的。

何谓配置变更?

配置变更指的是,应用在运行时,内置的配置参数变更从而触发的Activity重新创建

常见的场景有:旋转屏幕、深色模式切换、屏幕大小变化、更改了默认语言或者时区、更改字体大小或主题颜色等。

何谓异常重建?

异常重建指的是非配置变更情况下导致的 Activity 重新创建。

常见场景大多是因为 内存不足,从而导致后台应用被系统回收 ,当我们切换到前台时,从而触发的重建,这个机制在Android中为 Low Memory Killer 机制,简称 LMK

可以在开发者模式,限制后台任务数为1,从而测试该效果。

2.为什么需要ViewModel?

ViewModel 出现之前,对于 View 逻辑与数据,我们往往都是直接存在 Activity 或者 Fragment 中,优雅一点,会细分到具体的单独类中去承载。当配置变更时,无可避免,会触发界面重绘。相应的,我们的数据在没有额外处理的情况下,往往也会被初始化,然后在界面重启时重新加载。

但如果当前页面需要维护某些状态不被丢失呢,比如 选择、上传状态 等等? 此时问题就变得棘手起来。

稍有经验同学会告诉你,在 onSaveInstanceState 中重写,使用bundle去存储相应的状态啊?

但状态如果少点还可以,多一点就非常头痛,更别提包含继承关系的状态保存。

所以就有了ViewModel,但ViewModel并不是onSaveInstanceState()的替代品

随着 ViewModel 组件推出之后,上述因配置变更而导致的状态丢失问题就迎刃而解。

ViewModel 可以做到在配置变更后依然持有状态。所以,在现在的开发中,我们开始将 View数据 与 逻辑 藏于 ViewModel 中,然后对外部暴漏观察者,比如我们常常会搭配 LiveData 一起使用,以此更容易的保持状态同步。这其实就很接近之后要讲的MVVM架构。

关于 ViewModel 的生命周期,具体如下图所示:

虽然 ViewModel 非常好用,但 ViewModel 也不是万能,其只能避免配置变更时避免状态丢失。比如如果我们的App是因为 内存不足 而被系统kill 掉,此时 ViewModel 也会被清除 。

不过对于这种情况,仍然有以下三个方法可以依然保存我们的状态:

  • 重写 onSaveInstanceState()onRestoreInstanceState();
  • 使用 SavedState,本质上其实还是 onSaveInstanceState()
  • 使用 SavedStateHandle ,本质上是依托于 SaveState 的实现;

上述的后两种都是随着 JetPack 逐步被推出,可以理解为是对原有的onSavexx的封装简化,从而使其变得更易用。

3.ViewModel的使用

首先肯定是导入viewmodel的依赖

 implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1"

然后定义viewmodel,一般情况我们都是结合LiveData一起使用:

 /**
  * ...
  * @author RQ527 (Ran Sixiang)
  * @email 1799796122@qq.com
  * @date 2023/3/19
  * @Description:
  */
 class TestViewModel : ViewModel() {
 ​
     val homeLiveData: LiveData<String>
         get() = _mutableHomeLiveData
     private val _mutableHomeLiveData = MutableLiveData<String>()
 ​
     fun getHomeData() {
         //这里从网络或者本地获取数据
 ​
         _mutableHomeLiveData.value = "我获取到了数据"
     }
 ​
 }

一般是不建议viewmodel暴露mutableLivedata给activtiy,而是内部使用mutableLiveData去修改值,然后转化成LiveData暴露给activity。

注:为了保证Activity不被泄漏,不要在Activity传回调或者view进去,也不要在Viewmodel导入android.的包,(android.arch.除外),这样做是为了不要让ViewModel知晓Android的FrameWork,ViewModel只是用来写点逻辑代码和存数据的。

然后我们在activity去获取viewmodel:

 class MainActivity : AppCompatActivity() {
 ​
     private val mBinding: ActivityMainBinding by lazy { ActivityMainBinding.inflate(layoutInflater) }
 ​
     private val mViewModel by lazy {ViewModelProvider(this)[TestViewModel::class.java]}//懒加载
 ​
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
         setContentView(mBinding.root)
 ​
         mViewModel.homeLiveData.observe(this) {
             mBinding.textView.text = it
         }
         mBinding.button.setOnClickListener {
             mViewModel.getHomeData()
         }
     }
 ​
 }

首先我们用ViewmodelProvider传了lifecycleowner进去,传我们自定义的viewmodel类。创建viewmodel大概就是这样。然后在下面去观察livedata,点击一个按钮获取数据。这是正确的创建viewmodel的方法,你直接new 的话是不正确的,完全没有效果。

ktx提供了很多的扩展函数简化我们创建Viewmodel的步骤,我们先导入依赖:

 implementation"androidx.activity:activity-ktx:1.6.1"//activity
 implementation"androidx.fragment:fragment-ktx:1.5.5"//fragment

然后我们的viewmodel就可以这么创建了:

 //1
 private val mViewModel by viewModels<TestViewModel>()
 ​
 //2
 private val mViewModel by viewModels<TestViewModel>{TestViewModelFactory()}
 ​

是不是比之前精简了很多,其本质也是调用ViewModelProvider.get。

假如你在viewmodel里需要application的话,可以使用AndroidVeiwModel:

 class TestViewModel(application: Application) : AndroidViewModel(application) {
 ​
     val homeLiveData: LiveData<String>
         get() = _mutableHomeLiveData
     private val _mutableHomeLiveData = MutableLiveData<String>()
 ​
     val mApplication: Application
         get() = getApplication()
 ​
     fun getHomeData() {
         Toast.makeText(mApplication, "aaaa", Toast.LENGTH_SHORT).show()
         //这里从网络或者本地获取数据
 ​
         _mutableHomeLiveData.value = "我获取到了数据"
     }
 ​
 }

继承自AndroidViewModel,然后getApplication就可以获取application了。

在Fragment里面的用法和fragment是一样的,就不再说了。同时,一个viewmodel多个Fragment或者activity可以共享。

假如viewmodel需要传参怎么办?有人会说直接这样:

 class TestViewModel(val tag: String) : ViewModel() {
 ​
     val homeLiveData: LiveData<String>
         get() = _mutableHomeLiveData
     private val _mutableHomeLiveData = MutableLiveData<String>()
 ​
     fun getHomeData() {
         //这里从网络或者本地获取数据
 ​
         _mutableHomeLiveData.value = "我获取到了数据"
     }
 ​
 }

但是你怎么去创建viewmodel实例呢?要知道我们获取viewmodel是这样的:

 private val mViewModel by lazy {ViewModelProvider(this)[TestViewModel::class.java]}

这样去创建viewmodel默认是无参数的,肯定会出错。那怎么办呢?

其实我们的ViewModelProvider的构造函数有三个:

第二参数是ViewModelProvider.Factory,是干什么的呢?用来实例化viewmodel的,默认是无参的,所以要传参我们肯定要自定义一个ViewModelProvider.Factory:

 class TestViewModelFactory(val tag: String) : ViewModelProvider.Factory {
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         val testViewModel = TestViewModel(tag)
         return testViewModel as T
     }
 }

创建一个有参数的Factory,实现ViewModelProvider.Factory这个接口然后创建ViewModel实例再把参数传进去就可以了。最后在activtiy获取viewmodel的时候把参数传进去:

 private val mViewModel by lazy {
     ViewModelProvider(
         this,
         TestViewModelFactory("我传了个参数")
     )[TestViewModel::class.java]
 }

这里我们new了一个我们自定义的Factory实例并把参数传了进去,这个参数你们可以自定义也可以从别的地方来,具体看你们的需求。

刚刚我们在上面提到了,如果我们的App是因为 内存不足 而被系统kill 掉,此时 ViewModel 也会被清除 。这时候我们可以:

  • 重写 onSaveInstanceState()onRestoreInstanceState();
  • 使用 SavedState,本质上其实还是 onSaveInstanceState()
  • 使用 SavedStateHandle ,本质上是依托于 SaveState 的实现;

上述的后两种都是随着 JetPack 逐步被推出,可以理解为是对原有的onSavexx的封装简化,从而使其变得更易用。

方法本质都是用onSaveInstanceState()我们就讲个方便的SavedStateHandle

首先导入依赖:

 implementation "androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1"

然后稍微修改一下ViewModel:

 class TestViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() {
 ​
     val homeLiveData: LiveData<String>
         get() = _mutableHomeLiveData
     private val _mutableHomeLiveData = MutableLiveData<String>()
 ​
     companion object {
         const val MY_KEY = "MY_KEY"
     }
 ​
     fun getHomeData() {
         //这里从网络或者本地获取数据
 ​
         _mutableHomeLiveData.value = "我传了个数据"
     }
 ​
     fun saveData() {
         savedStateHandle[MY_KEY] = "我保存了一个数据"
     }
 ​
     fun getData(): String? {
         return savedStateHandle[MY_KEY]
     }
 ​
 }

然后在Activity中修改一下viewmode的创建方式:

 private val mViewModel by lazy {ViewModelProvider(this,SavedStateViewModelFactory(application, this))[TestViewModel::class.java]}

就是传一个SavedStateViewModelFactory然后把application和当前activity传进去。之后就可以调方法保存数据获取数据了。

4.详解viewmodel

大概的用法讲完了,我们看看viewmodel是怎么从出生到死亡的。

ViewModelProvider

我们先从获取viewmodel的实例说起,也就是:

 private val mViewModel by lazy { ViewModelProvider(this)[TestViewModel::class.java] }

我们把当前的activity传进了ViewmodelProvider的构造函数,我们看一看

 /**
  * Creates `ViewModelProvider`. This will create `ViewModels`
  * and retain them in a store of the given `ViewModelStoreOwner`.
  *
  *
  * This method will use the
  * [default factory][HasDefaultViewModelProviderFactory.getDefaultViewModelProviderFactory]
  * if the owner implements [HasDefaultViewModelProviderFactory]. Otherwise, a
  * [NewInstanceFactory] will be used.
  */
 public constructor(
     owner: ViewModelStoreOwner
 ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))
 ​
 /**
  * Creates `ViewModelProvider`, which will create `ViewModels` via the given
  * `Factory` and retain them in a store of the given `ViewModelStoreOwner`.
  *
  * @param owner   a `ViewModelStoreOwner` whose [ViewModelStore] will be used to
  * retain `ViewModels`
  * @param factory a `Factory` which will be used to instantiate
  * new `ViewModels`
  */
 public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(
     owner.viewModelStore,
     factory,
     defaultCreationExtras(owner)
 )

其实就是调用了三个参数的构造方法,首先就是viewModelStoreOwner,那我们的activity肯定实现了这个接口,我们先不管,往后看。之后是构造默认的Factory:

 internal fun defaultFactory(owner: ViewModelStoreOwner): Factory =
     if (owner is HasDefaultViewModelProviderFactory)
         owner.defaultViewModelProviderFactory else instance

首先看是否实现了HasDefaultViewModelProviderFactory,否则返回instance,instance就是NewInstanceFactory:

 @JvmStatic
 public val instance: NewInstanceFactory
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     get() {
         if (sInstance == null) {
             sInstance = NewInstanceFactory()
         }
         return sInstance!!
     }

而NewInstanceFactory就是new 一个viewmodel的实例:

 public open class NewInstanceFactory : Factory {
     @Suppress("DocumentExceptions")
     override fun <T : ViewModel> create(modelClass: Class<T>): T {
         return try {
             modelClass.newInstance()
         } catch (e: InstantiationException) {
             throw RuntimeException("Cannot create an instance of $modelClass", e)
         } catch (e: IllegalAccessException) {
             throw RuntimeException("Cannot create an instance of $modelClass", e)
         }
     }

那我们看看实现了HasDefaultViewModelProviderFactory的情况,我们的MainActivity继承自AppCompatActivity,AppCompatActivity继承自FragmentActivity,FragmentActivity继承自ComponentActivity,而ComponentActivity实现了HasDefaultViewModelProviderFactory接口的:

 public class ComponentActivity extends androidx.core.app.ComponentActivity implements
         ContextAware,
         LifecycleOwner,
         ViewModelStoreOwner,
         HasDefaultViewModelProviderFactory,
         SavedStateRegistryOwner,
         OnBackPressedDispatcherOwner,
         ActivityResultRegistryOwner,
         ActivityResultCaller,
         OnConfigurationChangedProvider,
         OnTrimMemoryProvider,
         OnNewIntentProvider,
         OnMultiWindowModeChangedProvider,
         OnPictureInPictureModeChangedProvider,
         MenuHost {
             .........
     @NonNull
     @Override
     public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
         if (mDefaultFactory == null) {
             mDefaultFactory = new SavedStateViewModelFactory(
                     getApplication(),
                     this,
                     getIntent() != null ? getIntent().getExtras() : null);
         }
         return mDefaultFactory;
     }
             .........
 }

可以看到就是返回SavedStateViewModelFactory,那我们来看看它的create干了什么:

 override fun <T : ViewModel> create(modelClass: Class<T>, extras: CreationExtras): T {
     val key = extras[ViewModelProvider.NewInstanceFactory.VIEW_MODEL_KEY]
         ?: throw IllegalStateException(
             "VIEW_MODEL_KEY must always be provided by ViewModelProvider"
         )
 ​
     return if (extras[SAVED_STATE_REGISTRY_OWNER_KEY] != null &&
         extras[VIEW_MODEL_STORE_OWNER_KEY] != null) {
         val application = extras[ViewModelProvider.AndroidViewModelFactory.APPLICATION_KEY]
         val isAndroidViewModel = AndroidViewModel::class.java.isAssignableFrom(modelClass)
         val constructor: Constructor<T>? = if (isAndroidViewModel && application != null) {
             findMatchingConstructor(modelClass, ANDROID_VIEWMODEL_SIGNATURE)
         } else {
             findMatchingConstructor(modelClass, VIEWMODEL_SIGNATURE)
         }
         // doesn't need SavedStateHandle
         if (constructor == null) {
             return factory.create(modelClass, extras)
         }
         val viewModel = if (isAndroidViewModel && application != null) {
             newInstance(modelClass, constructor, application, extras.createSavedStateHandle())
         } else {
             newInstance(modelClass, constructor, extras.createSavedStateHandle())
         }
         viewModel
     } else {
         val viewModel = if (lifecycle != null) {
             create(key, modelClass)
         } else {
             throw IllegalStateException("SAVED_STATE_REGISTRY_OWNER_KEY and" +
                 "VIEW_MODEL_STORE_OWNER_KEY must be provided in the creation extras to" +
                 "successfully create a ViewModel.")
         }
         viewModel
     }
 }

其实就是先判断SAVED_STATE_REGISTRY_OWNER_KEY和VIEW_MODEL_STORE_OWNER_KEY存不存在,不存在就创建,方法大同小异,一般是存在的情况,我们来看看,先根据是不是AndroidViewModel去findMatchingConstructor,而findMatchingConstructor是:

 private val ANDROID_VIEWMODEL_SIGNATURE = listOf<Class<*>>(
     Application::class.java,
     SavedStateHandle::class.java
 )
 private val VIEWMODEL_SIGNATURE = listOf<Class<*>>(SavedStateHandle::class.java)
 ​
 // it is done instead of getConstructor(), because getConstructor() throws an exception
 // if there is no such constructor, which is expensive
 internal fun <T> findMatchingConstructor(
     modelClass: Class<T>,
     signature: List<Class<*>>
 ): Constructor<T>? {
     for (constructor in modelClass.constructors) {
         val parameterTypes = constructor.parameterTypes.toList()
         if (signature == parameterTypes) {
             @Suppress("UNCHECKED_CAST")
             return constructor as Constructor<T>
         }
         if (signature.size == parameterTypes.size && parameterTypes.containsAll(signature)) {
             throw UnsupportedOperationException(
                 "Class ${modelClass.simpleName} must have parameters in the proper " +
                     "order: $signature"
             )
         }
     }
     return null
 }

其实就是根据你自定义的ViewModel的构造参数去决定给你什么构造函数,你需要application就给你有application的构造函数,你需要saveStateHandle就给你saveStateHandle的构造函数。所以默认的Factory就是根据你是否需要application或者saveStateHandle去反射给你创建实例。

那好,现在构造函数我们搞清楚了,我们看看我们传进去的TestViewModel::class.java干了什么,那它肯定重写了操作符get,我们看看:

 @MainThread
 public open operator fun <T : ViewModel> get(modelClass: Class<T>): T {
     val canonicalName = modelClass.canonicalName
         ?: throw IllegalArgumentException("Local and anonymous classes can not be ViewModels")
     return get("$DEFAULT_KEY:$canonicalName", modelClass)
 }
 ​
 /**
  * Returns an existing ViewModel or creates a new one in the scope (usually, a fragment or
  * an activity), associated with this `ViewModelProvider`.
  *
  * The created ViewModel is associated with the given scope and will be retained
  * as long as the scope is alive (e.g. if it is an activity, until it is
  * finished or process is killed).
  *
  * @param key        The key to use to identify the ViewModel.
  * @param modelClass The class of the ViewModel to create an instance of it if it is not
  * present.
  * @return A ViewModel that is an instance of the given type `T`.
  */
 @Suppress("UNCHECKED_CAST")
 @MainThread
 public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
     val viewModel = store[key]
     if (modelClass.isInstance(viewModel)) {
         (factory as? OnRequeryFactory)?.onRequery(viewModel)
         return viewModel as T
     } else {
         @Suppress("ControlFlowWithEmptyBody")
         if (viewModel != null) {
             // TODO: log a warning.
         }
     }
     val extras = MutableCreationExtras(defaultCreationExtras)
     extras[VIEW_MODEL_KEY] = key
     // AGP has some desugaring issues associated with compileOnly dependencies so we need to
     // fall back to the other create method to keep from crashing.
     return try {
         factory.create(modelClass, extras)
     } catch (e: AbstractMethodError) {
         factory.create(modelClass)
     }.also { store.put(key, it) }
 }

就是调用了两个参数的get方法,第一个参数就是我们viewmodel的包名以此保证唯一性,第二个参数就是我们传进来的TestViewmodel。两个参数的get方法先从store里去获取viewmodel,这个sotre是ViewModelstore,是用来管理viewmodel的,等会说。然后判断你是否继承了抽象类viewmodel,如果存在就直接给你返回。否则,在最后调用factory的create方法去给你new 一个viewmodel的实例,然后存在viewmodelstore里面。这就是为什么我们直接去new ViewMdoel没用的原因,虽然不会报错,但是它就是一个普通的类,没啥用。

ViewModelStore

那viewmodelstore是个啥玩意儿呢?在上面我们知道,viewmodel的存取都是在viewmodelStore里面的,那viewmodel的生死消亡都跟viewmodelstore有关。我们刚刚分析ViewModelProvider的构造函数的时候只是分析了第二个参数factory,第一个参数我们还没分析我们看看:

 public constructor(
     owner: ViewModelStoreOwner
 ) : this(owner.viewModelStore, defaultFactory(owner), defaultCreationExtras(owner))

我们的activity肯定实现了ViewModelStoreOwner,分析它的继承关系可以发现,componentActivity实现了这个接口,我们看看它的getViewModelStore方法:

 @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.");
     }
     ensureViewModelStore();
     return mViewModelStore;
 }
 ​
 @SuppressWarnings("WeakerAccess") /* synthetic access */
 void ensureViewModelStore() {
     if (mViewModelStore == null) {
         NonConfigurationInstances nc =
                 (NonConfigurationInstances) getLastNonConfigurationInstance();
         if (nc != null) {
             // Restore the ViewModelStore from NonConfigurationInstances
             mViewModelStore = nc.viewModelStore;
         }
         if (mViewModelStore == null) {
             mViewModelStore = new ViewModelStore();
         }
     }
 }

先是尝试从NonConfigurationInstances中去获取viewmodelstore,获取不到就直接new ,那NonConfigurationInstances是干什么的呢?它是Android官方提供给因配置改变而要保存的数据的。那在什么时候保存的呢?在onRetainNonConfigurationInstance()中。而Activity重建的时候生命周期:onPause ----> onSaveInstanceState ---->onStop --->onRetainCustomNonConfigurationInstance ---> onDestroy ---> onCreate ---> onStart ----> onRestoreInstanceState ----> onResume onStop和onDeatory的时候会在onRetainCustomNonConfigurationInstance (后面简称onRCNC) 中保存的自定义非配置实例。他的前身是onRetainNonConfigurationInstance(简称onRNC),但是现在onRCNC已经被废弃,onRNC被final修饰不能被复写,官方更推荐我们用viewmodel去保存非配置实例,而在onRetainNonConfigurationInstance()中:

 @Override
 @Nullable
 @SuppressWarnings("deprecation")
 public final Object onRetainNonConfigurationInstance() {
     // Maintain backward compatibility.
     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;
     }
 ​
     NonConfigurationInstances nci = new NonConfigurationInstances();
     nci.custom = custom;
     nci.viewModelStore = viewModelStore;
     return nci;
 }

很简单,就是先看看有没有已经保存的NonConfigurationInstances,有就直接把里面的viewmodelstore拿出来再存在新的NonConfigurationInstances里面,否则就创建一个viewmodel,那viewmode和viewmodelStore的存取我们都清楚了,而viewmodelStore也只是个map来保存viewmodel的:

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

那他的clear方法在哪里调用的呢?也就是说viewmodel是在啥时候被清除的?肯定跟ComponentActivity的生命周期有关,研究他的生命周期你会发现,在初始化中有这么一段代码:

 public ComponentActivity() {
     
     ......
     getLifecycle().addObserver(new LifecycleEventObserver() {
             @Override
             public void onStateChanged(@NonNull LifecycleOwner source,
                     @NonNull Lifecycle.Event event) {
                 if (event == Lifecycle.Event.ON_DESTROY) {
                     // Clear out the available context
                     mContextAwareHelper.clearAvailableContext();
                     // And clear the ViewModelStore
                     if (!isChangingConfigurations()) {
                         getViewModelStore().clear();
                     }
                 }
             }
         });
     
     ........
 }

利用lifecycle的特性当activity即将detory的时候,调用isChangingConfigurations()去判断是否能因配置变更而导致的destory,如果是正常detroy的话就调用viewmodelstore.clear真正地清除viewmodel。

至此viewmodel的出生和消亡我们就分析结束了,在lifecycle的基础上他的实现还是很简单的。觉得写的不错的或者感兴趣的同学点点赞哟。