Jetpack之ViewModel

1. 介绍

ViewModel 类旨在以注重生命周期的方式存储和管理界面相关的数据。ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。

Android 框架可以管理界面控制器(如 Activity 和 Fragment)(记住这个术语,后面分析时会出现)的生命周期。

如果系统销毁或重新创建界面控制器,则存储在其中的任何瞬态界面相关数据都会丢失。例如,应用可能会在它的某个 Activity 中包含用户列表。为配置更改重新创建 Activity 后,新 Activity 必须重新提取用户列表。对于简单的数据,Activity 可以使用 onSaveInstanceState() 方法从 onCreate() 中的捆绑包恢复其数据,但此方法仅适合可以序列化再反序列化的少量数据,而不适合数量可能较大的数据,如用户列表或位图。

另一个问题是,界面控制器经常需要进行可能需要一些时间才能返回的异步调用。界面控制器需要管理这些调用,并确保系统在其销毁后清理这些调用以避免潜在的内存泄漏。此项管理需要大量的维护工作,并且在为配置更改重新创建对象的情况下,会造成资源的浪费,因为对象可能需要重新发出已经发出过的调用。

简单理解:

  • 页面销毁时会清除数据,重建时又会重新请求,而销毁的数据和清除的数据是一样的,这样会浪费资源
  • 页面在异步请求数据过程中被销毁,而异步操作没有被取消可能会导致内存泄漏和浪费资源,例如:用户进入列表页面,在列表网络数据没有返回时退出页面,这时如果不取消网络是会造成资源浪费
  • ViewModel 能感知 Activity或Fragment 的生命周期的改变,在 Activity或Fragment 销毁时执行一些数据清理工作(ViewModel 的实现类可以通过重写onCleared方法)。

2. 结论

  • Activity 持有一个ViewModelStore,其用到的ViewModel都存储到其中;Fragment持有FragmentManagerFragmentManager持有FragmentManagerViewModel,在FragmentManagerViewModel中维护了Fragment使用的ViewModelStoreActivityFragment都各自持有自己的ViewModelStore,而FragmentViewModelStore间接来自Activity
  • ViewModel能在销毁重建过程中保存数据,是因为在销毁时保存了ViewModelStore,在重建时重新获取了旧的ViewModelStore,再从ViewModelStore中获取ViewModel,进而ViewModel中持有的数据还在
  • 应用处于后台时,由于内存不足导致Activity被销毁,这时的ViewModel是无法恢复数据的,因为ViewModel是暂存再应用进程,应用进程被杀掉数据也就不存在了,onSaveInstanceState是可以的(见文末参考)

3. 使用

ViewModel使用非常简单

  1. 添加依赖,后面的源码分析也是基于此版本,不同版本源码可能不一样
    implementation 'androidx.core:core-ktx:1.6.0'
    implementation 'androidx.appcompat:appcompat:1.3.1'
    implementation 'androidx.lifecycle:lifecycle-viewmodel:2.4.0'
    implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.0'
  1. 业务ViewModel继承ViewModelAndroidViewModel
class MineViewModel:ViewModel(){
    
}
  1. ActivityFragment中初始化ViewModel,即可
class MainActivity : AppCompatActivity() {

    private lateinit var vm: MineViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        vm = ViewModelProvider(this, defaultViewModelProviderFactory)[MineViewModel::class.java]
    }
}

4. 分析

根据ViewModel的使用和原理,分以下几个步骤:

  • ViewModel的相关类和结构
  • ViewModel如何实现销毁重建时保存数据的
  • 同一Activity中的不同Fragment是如何通过ViewModel共享数据的

源码分析基于androidx.lifecycle:lifecycle-viewmodel:2.4.0ViewModelProvidersViewModelStores本质上是帮助类,官方已经标记为过时类,不再分析。

4.1 ViewModel的相关类和结构

viewmodel-package.png

重点关注源码包中上述图片中选中的5个类和接口。

ViewModel
ViewModel.png

ViewModel是一个抽象类,业务类需要继承它,而它内部的变量和方法非常少,boolean mCleared标记其是否已经销毁;void onCleared()方法可被子类覆写,在其销毁的时候来释放资源等。

AndroidViewModel
AndroidViewModel.png

AndroidViewModel继承自ViewModel,内部多了一个Application上下文。

ViewModelStore
ViewModelStore.png

ViewModelStore是用来存储ViewModel的,界面控制器(Activity、Fragment)中持有一个ViewModelStore界面控制器为什么不直接持有ViewModel而是通过ViewModelStore间接持有?ViewModelStore的结构中就可以看出,这样设计可以允许一个界面控制器(Activity、Fragment)持有多个ViewModel

在界面控制器(Activity、Fragment)销毁和重建时保存和恢复的对象就是这个类型。

这里注意下 ViewModelStore 的 get 和 put 方法的声明,访问权限都是包层级的,也就表示我们使用者是无法直接通过 ViewModelStore 通过 key 拿到对应的 ViewModel 的。

内部方法实现比较简单,点进去源码看一下,都是常规的存入(put)、取出(get)、循环遍历(clear)。

ViewModelStoreOwner
ViewModelStoreOwner.png

ViewModelStoreOwner是一个接口,它声明了一个 getViewModelStore 方法需要实现类实现。可以理解为ViewModelStore的持有者,一般是界面控制器(Activity、Fragment)实现此接口,职责是保留其拥有的ViewModelStore在配置更改期间不会被销毁,也可以看作保存和恢复的关键步骤。

ViewModelProvider

ViewModelProvider是对外提供ViewModel的一个帮助类,内部有两个成员变量store和factory。

store由外部传入,ViewModelProvider在get方法中会通过store向其中添加ViewModel;factory是一个工厂类,ViewModelProvider中提供了默认的几个工厂实现,作用是创建ViewModel。

在使用中可以发现ViewModelProvider是直接new出来,也就是说,在销毁和重建的时候ViewModelProvider是一个新的对象,那么一个新new出来的对象是如何持有旧的数据呢?【见4.2.2 结尾】

分析一下ViewModelProvider中比较重点的方法

// 含有两个私有的final的成员变量
public open class ViewModelProvider(private val store: ViewModelStore, private val factory: Factory) {

    public interface Factory {
        public fun <T : ViewModel> create(modelClass: Class<T>): T
    }
    ...
    // 单参数构造方法,会调用内部默认实现的一个工厂
    public constructor(owner: ViewModelStoreOwner) : this(owner.viewModelStore, defaultFactory(owner))
    
    // 接收 ViewModelStoreOwner 和 Factory 两个参数
    public constructor(owner: ViewModelStoreOwner, factory: Factory) : this(owner.viewModelStore,factory)
    
    // 对外暴露的获取ViewModel的类,一般外部都使用该方法
    @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")
        // "$DEFAULT_KEY:$canonicalName" 为构建的一个key,和目标ViewModel一一对应
        return get("$DEFAULT_KEY:$canonicalName", modelClass)
    }

    /**
    * 对外暴露的获取ViewModel的类,外部也可以调用该方法指定自定义的key
    * key:用来标识ViewModel的键。
    */
    @MainThread
    public open operator fun <T : ViewModel> get(key: String, modelClass: Class<T>): T {
        // 从ViewModelStore中获取缓存的ViewModel
        var 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.
            }
        }
        // 若不一致或为空,使用factory创建新的viewModel
        viewModel = if (factory is KeyedFactory) {
            factory.create(key, modelClass)
        } else {
            factory.create(modelClass)
        }
        // * 重点:这里会将创建的viewModel加入到界面控制器(Activity、Fragment)指持有的ViewModelStore中
        store.put(key, viewModel)
        return viewModel
    }
}

整理一下这5个类的关系


ViewModel5.png

4.2 ViewModel在源码中的结构(使用、实现)

前面介绍了ViewModel相关类的源码内容和类之间的关系,那么它们在源码中是如何使用的呢?为什么业务中仅使用一句 new ViewModelProvider 就能达到如此丝滑的效果呢?接下来分析一下在源码中是如果将这几个类串联起来使用ViewModel的。

界面控制器,包含 Activity 和 Fragment,而在它们的源码中对ViewModel功能的实现又有所不同

4.2.1 分析Activity

从使用的地方作为突破口查看源码vm = ViewModelProvider(this, defaultViewModelProviderFactory)[MineViewModel::class.java],这段代码是初始化MineViewModel,从前面类源码中我们了解到ViewModelProvider的第一个参数类型为ViewModelStoreOwner,所以我们的Activity肯定实现了ViewModelStoreOwner接口。

经过一系列简单查找,最终找到了 androidx.activity.ComponentActivity

// androidx.activity:activity:1.2.4,源码为该版本,不同版本源码可能不同
// implements 的接口中只截取来相关的接口,实际不止一个
public class ComponentActivity extends androidx.core.app.ComponentActivity implements ViewModelStoreOwner {
    // Activity 持有的 ViewModelStore,重建和恢复时就是保存的该对象
    private ViewModelStore mViewModelStore;
    // Activity 提供的默认工厂
    private ViewModelProvider.Factory mDefaultFactory;
    
    ...
    
    @Override
    public ViewModelStore getViewModelStore() {
        // 省略一个为空判断
        ...
        // 调用该方法,内部给mViewModelStore赋值
        ensureViewModelStore();
        return mViewModelStore;
    }

    // 该方法内部给mViewModelStore赋值,也是保存和恢复ViewModelStore的关键步骤
    void ensureViewModelStore() {
        if (mViewModelStore == null) {
            // ** 重点:从上一次的配置中读取mViewModelStore,会在[ViewModel如何实现销毁重建过程中保存数据]章节中讲解
            NonConfigurationInstances nc =
                    (NonConfigurationInstances) getLastNonConfigurationInstance();
            if (nc != null) {
                // Restore the ViewModelStore from NonConfigurationInstances
                mViewModelStore = nc.viewModelStore;
            }
            // 若上一次没有数据则新建
            if (mViewModelStore == null) {
                mViewModelStore = new ViewModelStore();
            }
        }
    }
    ...
    
    // 提供默认的工厂
    // ViewModelProvider(this, defaultViewModelProviderFactory) 中defaultViewModelProviderFactory即为该方法
    @Override
    public ViewModelProvider.Factory getDefaultViewModelProviderFactory() {
        // 内部实现省略
        ...
        return mDefaultFactory;
    }

}

至此,我们找到了Activity中持有的ViewModelStore对象及其来源,也知道销毁和重建过程中重点就在于保存这个对象。一定要记住保存的是mViewModelStore实例,mViewModelStore没有改变则数据就不会改变。为什么?重建过程中旧的Activity实例会销毁,创建新Activity实例,如果新Activity实例持有的ViewModelStore实例和旧Activity实例持有的ViewModelStore实例一致,那么ViewModelStore实例中的ViewModel中的数据就还存在,不需要再重新请求。用一张图简单说明一下:

ActivityToNew.jpg
4.2.2 分析Fragment

viewModel在Fragment中初始化,建议打开源码跟着看一遍,不然会有点晕

使用如下:

class MineFragment: Fragment() {
    private lateinit var vm: MineViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        vm = ViewModelProvider(this, defaultViewModelProviderFactory)[MineViewModel::class.java]
    }
}

和Activity一样,Fragment肯定也实现了ViewModelStoreOwner接口,查看源码:

// androidx.fragment:fragment:1.3.6
// 省略其他接口实现
public class Fragment implements ViewModelStoreOwner {
    FragmentManager mFragmentManager;

    @Override
    public ViewModelStore getViewModelStore() {
        // 省略判断
        ...
        return mFragmentManager.getViewModelStore(this);
    }        
}

这里又出现了一个mFragmentManager,(不重要)查找一下赋值的地方在哪里:

mFragmentManagerUse.png

这么多地方都在赋值,怎么找,打断点吧,把断点打到成员变量上(上图中有红色眼睛的地方),这样不管是哪里赋值或者取值都会走到断点,接下来以Debug模式启动,注意启动前记得将一个Fragment添加到Activity(继承自FragmentActivity)并正确显示,否则不会走断点(没有显示的Fragment怎么会走到断点呢,你说对不对),发现是在transaction.commit()的时候赋值的。

mFragmentManagerSetValue.png

重新回到mFragmentManager.getViewModelStore(this),查看FragmentManager源码:

public abstract class FragmentManager implements FragmentResultOwner {
    
    // * 这个很重要,是一个继承ViewModel的 final Class,目的也是要找到这个是在哪里赋值的
    private FragmentManagerViewModel mNonConfig;

    @NonNull
    ViewModelStore getViewModelStore(@NonNull Fragment f) {
        return mNonConfig.getViewModelStore(f);
    }
}

接下来看一下mNonConfig是在哪里赋值的,同样把断点打在成员变量上,并用Debug模式启动,查看调用栈信息:

ActivityStart.png
public abstract class FragmentManager implements FragmentResultOwner {
    void attachController(@NonNull FragmentHostCallback<?> host,
            @NonNull FragmentContainer container, @Nullable final Fragment parent) {
        ...
           
        // Get the FragmentManagerViewModel
        if (parent != null) {
            mNonConfig = parent.mFragmentManager.getChildNonConfig(parent);
        } else if (host instanceof ViewModelStoreOwner) {
            ViewModelStore viewModelStore = ((ViewModelStoreOwner) host).getViewModelStore();
            // 上图中的断点是这里,* 内部实现会把mNonConfig放到viewModelStore中,而viewModelStore来自Activity
            mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);
        } else {
            mNonConfig = new FragmentManagerViewModel(false);
        }
        ...
    }
}

从源码中分析attachController接收一个FragmentHostCallback<?> host,而FragmentManagerViewModel mNonConfig的创建和这个参数也至关重要,根据任务栈中的信息查找,可以看到FragmentHostCallback<?> host来自onContextAvailable:143, FragmentActivity$2 (androidx.fragment.app)

// androidx.fragment:fragment:1.3.6
public class FragmentActivity {
        // * 经过传递,传递到 FragmentManager.attachController方法中的FragmentHostCallback<?> host实际就是这里的 new HostCallbacks()
        final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
        private void init() {
        ...
        addOnContextAvailableListener(new OnContextAvailableListener() {
            @Override
            public void onContextAvailable(@NonNull Context context) {
                // 任务栈断点 onContextAvailable:143, FragmentActivity$2 (androidx.fragment.app) 就是这句话
                mFragments.attachHost(null /*parent*/);
                ...
            }
        });
        ...
    }
}

还记得要找什么吗?要找FragmentManager中FragmentManagerViewModel mNonConfig的赋值,经一系列的分析,其赋值mNonConfig = FragmentManagerViewModel.getInstance(viewModelStore);语句中的viewModelStore来自于new HostCallbacks(),它是FragmentActivity中的一个内部类:

public class FragmentActivity {

    // 省略多余接口实现
    class HostCallbacks extends FragmentHostCallback<FragmentActivity> implements ViewModelStoreOwner{
        @Override
        public ViewModelStore getViewModelStore() {
            // 到这里发现,Fragment中的ViewModel来自Activity
            return FragmentActivity.this.getViewModelStore();
        }
    }
}

经过以上分析,Fragmen中的ViewModelStore实际上是来自Activity:

Fragment中的getViewModelStore()方法返回值来自其成员变量FragmentManager mFragmentManager;

FragmentManager 中的getViewModelStore(Fragment f)来自其成员变量FragmentManagerViewModel mNonConfig;

mNonConfig在初始化时FragmentManagerViewModel.getInstance(viewModelStore)又会将自身保存到host的viewModelStore中;在FragmentManagerViewModel中又维护了一个存放Fragment的ViewModelStore的Mapprivate final HashMap<String, ViewModelStore> mViewModelStores = new HashMap<>();

host中的getViewModelStore()来自于FragmentActivity.this.getViewModelStore();

总结一下就是,Fragment中的ViewModel来自其自身的ViewModelStore实例,ViewModelStore实例存放在FragmentManagerViewModel的成员变量中,FragmentManagerViewModel实例又被存放在Activity的ViewModelStore中。

前面有一个问题【那么一个新new出来的对象(ViewModelProvider)是如何持有旧的数据呢?】,provider虽然是新对象,但是真正取到值的是从Activity的ViewModelStore中,Activity持有的ViewModelStore实例不变,ViewMode就不变,所以provider是否为新对象不影响ViewModel及其里面的数据

4.3 ViewModel如何实现销毁重建时保存数据的

前面的分析已经得到一个结论,界面控制器(Activity和Fragment)持有的ViewModelStore实例都来自Activity,所以在销毁过程中保存和恢复Activity的ViewModelStore实例即可达到目的。下面来分析一下如何保存和恢复。

既然是ViewModelStore实例在Activity中,那么就去分析Activity源码,看一下成员变量mViewModelStore使用的地方:

ActivityFiledUse.png

有3个方法用到mViewModelStore,其中getViewModelStore()ensureViewModelStore是在初始化的时候用到,那么看一下onRetainNonConfigurationInstance()方法,该方法内部将mViewModelStore经过一系列包装做为方法的返回值:

// androidx.activity:activity:1.2.4
// 省略继承类和实现接口
public class ComponentActivity{
    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;
        }

        NonConfigurationInstances nci = new NonConfigurationInstances();
        nci.custom = custom;
        nci.viewModelStore = viewModelStore;
        // 将mViewModelStore经过包装返回
        return nci;
    }
}

而在Activity的retainNonConfigurationInstances()方法中调用了上述方法:

// api 31 的源码
// 省略继承类和实现接口
public class Activity {
    NonConfigurationInstances retainNonConfigurationInstances() {
        Object activity = onRetainNonConfigurationInstance();
        // 省略其他代码
        ...
    }
}

而retainNonConfigurationInstances()在ActivityThread中有调用:

// api 31 的源码
public final class ActivityThread extends ClientTransactionHandler implements ActivityThreadInternal {
    // 该方法在旋转屏幕的时候会调用,可以打断点查看
    @Override
    public void handleRelaunchActivity(ActivityClientRecord tmp,
                                       PendingTransactionActions pendingActions) {
        // 省略其他代码
        handleRelaunchActivityInner(r, configChanges, tmp.pendingResults, tmp.pendingIntents,
                                    pendingActions, tmp.startsNotResumed, tmp.overrideConfig, "handleRelaunchActivity");
        // 省略其他代码
    }
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
                                             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
                                             PendingTransactionActions pendingActions, boolean startsNotResumed,
                                             Configuration overrideConfig, String reason) {
        // 省略其他代码
        // 这里面会将Activity的mViewModel放到 r.lastNonConfigurationInstances 中
        handleDestroyActivity(r, false, configChanges, true, reason);
        // 省略其他代码
        // 该方法会从 r 恢复mViewModel
        handleLaunchActivity(r, pendingActions, customIntent);
        // 省略其他代码
    }
    
    public void handleDestroyActivity(ActivityClientRecord r, boolean finishing, int configChanges,
                                      boolean getNonConfigInstance, String reason) {
        // 省略其他代码
        performDestroyActivity(r, finishing, configChanges, getNonConfigInstance, reason);
        // 省略其他代码
    }
    
    void performDestroyActivity(ActivityClientRecord r, boolean finishing,
                                int configChanges, boolean getNonConfigInstance, String reason) {
        // 省略其他代码
        // 调用 Activity.retainNonConfigurationInstances()
        r.lastNonConfigurationInstances = r.activity.retainNonConfigurationInstances();
        // 省略其他代码
    }
}

重点方法和调用都在上面截取,这样就找到Activity的mViewModel实例是在ActivityThread.handleRelaunchActivityInner中保存和恢复的,上面已经分析了报存的,接下来分析一下恢复,依旧是ActivityThread源码:

// api 31 的源码
public final class ActivityThread extends ClientTransactionHandler implements ActivityThreadInternal {
    private void handleRelaunchActivityInner(ActivityClientRecord r, int configChanges,
                                             List<ResultInfo> pendingResults, List<ReferrerIntent> pendingIntents,
                                             PendingTransactionActions pendingActions, boolean startsNotResumed,
                                             Configuration overrideConfig, String reason) {
        // 上面分析了Activity的mViewModel会放到 r.lastNonConfigurationInstances 中
        handleDestroyActivity(r, false, configChanges, true, reason);
        // 恢复的关键方法
        handleLaunchActivity(r, pendingActions, customIntent);
    }
    
    @Override
    public Activity handleLaunchActivity(ActivityClientRecord r,
                                         PendingTransactionActions pendingActions, Intent customIntent) {
        final Activity a = performLaunchActivity(r, customIntent);
    }
    
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        // 回调到这里,注意第12个参数r.lastNonConfigurationInstances,Activity的mViewModel实例在里面
        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,
                        r.assistToken, r.shareableActivityToken);
    }
}

去Activity中查看attach方法

// api 31 的源码
// 省略继承类和实现接口
public class Activity {
    NonConfigurationInstances mLastNonConfigurationInstances;
    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, IBinder assistToken,
                      IBinder shareableActivityToken) {
        // 把缓存的lastNonConfigurationInstances赋值给了成员变量mLastNonConfigurationInstances
        mLastNonConfigurationInstances = lastNonConfigurationInstances;
    }
    
    public Object getLastNonConfigurationInstance() {
        return mLastNonConfigurationInstances != null ? mLastNonConfigurationInstances.activity : null;
    }

}

在分析mViewModelStore在ComponentActivity的赋值的时候,有这么一个方法ensureViewModelStore()([4.2.1 分析Acivity章节有源码]),其内部就调用了getLastNonConfigurationInstance()方法取值赋值给mViewModelStore。

至此保存和恢复流程完成。重要的方法就是Activity的handleRelaunchActivityInner方法,内部处理了销毁和重建Activity的逻辑,ViewModel处理也在其中。

4.4 Fragment共享数据

通过分析知道,同一Activity的不同Fragment若需要共享数据,则保证ViewModel实例为同一个即可,ViewModel是放在ViewModelStore实例(通过ViewModerlStoreOwner提供)中,在初始化Fragment的ViewModel实例时,传入同一个ViewModerlStoreOwner即可,即:

vm = ViewModelProvider(this, defaultViewModelProviderFactory)[MineViewModel::class.java]
// 改为
vm = ViewModelProvider(requireActivity(), defaultViewModelProviderFactory)[MineViewModel::class.java]
// Fragment不同但requireActivity()都是其依赖的Activity同一实例,Activity内部提供的ViewModelStore也是成员变量,即可共享数据

参考:
ViewModel源码研究之聊聊onSaveInstanceState和onRetainNonConfigurationInstance的区别

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

推荐阅读更多精彩内容