JetPack

Jetpack 知识点

1.1 JetPack是什么?

官方定义如下:

Jetpack 是一个由多个库组成的套件,可帮助开发者遵循最佳做法,减少样板代码并编写可在各种 Android 版本和设备中一致运行的代码,让开发者精力集中编写重要的代码。

JetPack更多是一种概念和态度,它是谷歌开发的非Android Framework SDK自带、但同时是Android开发必备的/推荐的SDK/开发规范合集。相当于Google把自己的Android生态重新整理了一番,确立了Android未来的开发大方向。

使用Jetpack有如下好处:

1.2  Jetpack分类

      Android Jetpack 组件覆盖以下 4 个方面:架构(Architecture)、基础(Foundation)、行为(Behavior) 、界面(UI)。

真正的精华主要是Architecture,全称是Android Architecture Component(AAC), 即Android架构组件。

其包括比较成功的Lifecycle、LiveData、ViewModel,同时也是我们使用MVVM模式的最好框架工具,可以组合使用,也可以单独使用。

二、Lifecycle

Lifecycle,顾名思义,是用于帮助开发者管理Activity和Fragment 的生命周期,它是LiveData和ViewModel的基础。下面就先介绍为何及如何使用Lifecycle。

2.1 Lifecycle之前

官方文档有个例子 来说明使用Lifecycle之前是如何生命周期管理的:

假设我们有一个在屏幕上显示设备位置的 Activity。常见的实现可能如下所示:

虽然此示例看起来没问题,但在真实的应用中,最终会有太多管理界面和其他组件的调用,以响应生命周期的当前状态。管理多个组件会在生命周期方法(如 onStart() 和 onStop())中放置大量的代码,这使得它们难以维护。

此外,无法保证组件会在 Activity 或 Fragment 停止之前启动myLocationListener。在我们需要执行长时间运行的操作(如 onStart() 中的某种配置检查)时尤其如此。在这种情况下,myLocationListener的onStop() 方法会在 onStart() 之前调用,这使得组件留存的时间比所需的时间要长,从而导致内次泄漏。如下:

即2个问题点:

Lifecycle库 则可以 以弹性和隔离的方式解决这些问题。

2.2 Lifecycle的使用

Lifecycle是一个库,也包含Lifecycle这样一个类,Lifecycle类 用于存储有关组件(如 Activity 或 Fragment)的生命周期状态的信息,并允许其他对象观察此状态。

2.2.1 引入依赖

1、非androidX项目 引入:

添加这一句代码就依赖了如下的库:

2、androidX项目 引入:

如果项目已经依赖了AndroidX:

那么我们就可以使用Lifecycle库了,因为appcompat依赖了androidx.fragment,而androidx.fragment下依赖了ViewModel和 LiveData,LiveData内部又依赖了Lifecycle。

如果想要单独引入依赖,则如下:

在项目根目录的build.gradle添加 google() 代码库,然后app的build.gradle引入依赖,官方给出的依赖如下:

看着有很多,实际上如果只使用Lifecycle,只需要引入lifecycle-runtime即可。但通常都是和 ViewModel、 LiveData 配套使用的,所以lifecycle-viewmodel、lifecycle-livedata 一般也会引入。

另外,lifecycle-process是给整个app进程提供一个lifecycle,会面也会提到。

2.2.2 使用方法

Lifecycle的使用很简单:

2.2.2.1 基本使用

在Activity(或Fragment)中 一般用法如下:

Activity(或Fragment)是生命周期的拥有者,通过getLifecycle()方法获取到生命周期Lifecycle对象,Lifecycle对象使用addObserver方法 给自己添加观察者,即MyObserver对象。当Lifecycle的生命周期发生变化时,MyObserver就可以感知到。

MyObserver是如何使用生命周期的呢?看下MyObserver的实现:

首先MyObserver实现了接口LifecycleObserver,LifecycleObserver用于标记一个类是生命周期观察者。 然后在connectListener()、disconnectListener()上 分别都加了@OnLifecycleEvent注解,且value分别是Lifecycle.Event.ON_RESUME、Lifecycle.Event.ON_PAUSE,这个效果就是:connectListener()会在ON_RESUME时执行,disconnectListener()会在ON_PAUSE时执行。

我们打开LifecycleTestActivity 然后退出,日志打印如下:

可见MyObserver的方法 确实是在对应关注的生命周期触发时调用。 当然注解中的value你也写成其它 你关注的任何一个生命周期,例如Lifecycle.Event.ON_DESTROY。

2.2.2.2 MVP架构中的使用

如果是 在MVP架构中,那么就可以把presenter作为观察者:

这里是让Presenter实现LifecycleObserver接口,同样在方法上注解要触发的生命周期,最后在Activity中作为观察者添加到Lifecycle中。

这样做好处是啥呢? 当Activity生命周期发生变化时,MyPresenter就可以感知并执行方法,不需要在MainActivity的多个生命周期方法中调用MyPresenter的方法了。

—— 上面提到的第一个问题点就解决了。

另外,注意到 getDataOnStart()中耗时校验回调后,对当前生命周期状态进行了检查:至少处于STARTED状态才会继续执行start()方法,也就是保证了Activity停止后不会走start()方法;

—— 上面提到的第二个问题点也解决了。

2.2.3 自定义LifecycleOwner

在Activity中调用getLifecycle()能获取到Lifecycle实例,那getLifecycle()是哪里定义的方法呢 ?是接口LifecycleOwner,顾明来思义,生命周期拥有者:

Support Library 26.1.0及以上、AndroidX的 Fragment 和 Activity 已实现 LifecycleOwner 接口,所以我们在Activity中可以直接使用getLifecycle()。

如果有一个自定义类并希望使其成为LifecycleOwner,可以使用LifecycleRegistry类,它是Lifecycle的实现类,但需要将事件转发到该类:

MyActivity实现LifecycleOwner,getLifecycle()返回lifecycleRegistry实例。lifecycleRegistry实例则是在onCreate创建,并且在各个生命周期内调用markState()方法完成生命周期事件的传递。这就完成了LifecycleOwner的自定义,也即MyActivity变成了LifecycleOwner,然后就可以和 实现了LifecycleObserver的组件配合使用了。

补充一点,观察者的方法可以接受一个参数LifecycleOwner,就可以用来获取当前状态、或者继续添加观察者。 若注解的是ON_ANY还可以接收Event,用于区分是哪个事件。如下:

2.3 Application生命周期 ProcessLifecycleOwner

之前对App进入前后台的判断是通过registerActivityLifecycleCallbacks(callback)方法,然后在callback中利用一个全局变量做计数,在onActivityStarted()中计数加1,在onActivityStopped方法中计数减1,从而判断前后台切换。

而使用ProcessLifecycleOwner可以直接获取应用前后台切换状态。(记得先引入lifecycle-process依赖)

使用方式和Activity中类似,只不过要使用ProcessLifecycleOwner.get()获取ProcessLifecycleOwner,代码如下:

看到确实很简单,和前面Activity的Lifecycle用法几乎一样,而我们使用ProcessLifecycleOwner就显得很优雅了。 生命周期分发逻辑已在注释里说明。

2.4 源码分析

Lifecycle的使用很简单,接下来就是对Lifecycle原理和源码的解析了。

我们可以先猜下原理:LifecycleOwner(如Activity)在生命周期状态改变时(也就是生命周期方法执行时),遍历观察者,获取每个观察者的方法上的注解,如果注解是@OnLifecycleEvent且value是和生命周期状态一致,那么就执行这个方法。 这个猜测合理吧?下面你来看看。

2.4.1 Lifecycle类

先来瞅瞅Lifecycle:

Lifecycle 使用两种主要枚举跟踪其关联组件的生命周期状态:

Event触发的时机:

这保证了LifecycleOwner是在这个状态内。

官网有个图很清晰:

2.4.2 Activity对LifecycleOwner的实现

前面提到Activity实现了LifecycleOwner,所以才能直接使用getLifecycle(),具体是在androidx.activity.ComponentActivity中:

这里忽略了一些其他代码,我们只看Lifecycle相关。

看到ComponentActivity实现了接口LifecycleOwner,并在getLifecycle()返回了LifecycleRegistry实例。前面提到LifecycleRegistry是Lifecycle具体实现。

然后在onSaveInstanceState()中设置mLifecycleRegistry的状态为State.CREATED,然后怎么没有了?其他生命周期方法内咋没处理?what?和猜测的不一样啊。  别急,在onCreate()中有这么一行:ReportFragment.

,这个就是关键所在。

2.4.3 生命周期事件分发——ReportFragment

首先injectIfNeededIn()内进行了版本区分:在API 29及以上 直接使用activity的registerActivityLifecycleCallbacks 直接注册了生命周期回调,然后给当前activity添加了ReportFragment,注意这个fragment是没有布局的。

然后, 无论LifecycleCallbacks、还是fragment的生命周期方法 最后都走到了 dispatch(Activity activity, Lifecycle.Event event)方法,其内部使用LifecycleRegistry的handleLifecycleEvent方法处理事件。

而ReportFragment的作用就是获取生命周期而已,因为fragment生命周期是依附Activity的。好处就是把这部分逻辑抽离出来,实现activity的无侵入。如果你对图片加载库Glide比较熟,就会知道它也是使用透明Fragment获取生命周期的。

2.4.4 生命周期事件处理——LifecycleRegistry

到这里,生命中周期事件的处理有转移到了 LifecycleRegistry 中:

逻辑很清晰:使用getStateAfter()获取event发生之后的将要处于的状态(看前面那张图很好理解),moveToState()是移动到新状态,最后使用sync()把生命周期状态同步给所有观察者。

注意到sync()中有个while循环,很显然是在遍历观察者。并且很显然观察者是存放在mObserverMap中的,而mObserverMap对观察者的添加 很显然 就是 Activity中使用getLifecycle().addObserver()这里:

用observer创建带状态的观察者ObserverWithState,observer作为key、ObserverWithState作为value,存到mObserverMap。 接着做了安全判断,最后把新的观察者的状态 连续地 同步到最新状态mState,意思就是:虽然可能添加的晚,但会把之前的事件一个个分发给你,即粘性。

回到刚刚sync()的while循环,看看如何处理分发事件:

循环条件是!isSynced(),若最老的和最新的观察者的状态一致,且都是ower的当前状态,说明已经同步完了。

没有同步完就进入循环体:

接着ObserverWithState类型的observer就获取到了事件,即observer.dispatchEvent(lifecycleOwner, event),下面来看看它是如何让加了对应注解的方法执行的。

2.4.5 事件回调后 方法执行

我们继续看下 ObserverWithState:

mState的作用是:新的事件触发后 遍历通知所有观察者时,判断是否已经通知这个观察者了,即防止重复通知。

mLifecycleObserver是使用Lifecycling.getCallback(observer)获取的GenericLifecycleObserver实例。GenericLifecycleObserver是接口,继承自LifecycleObserver:

也就说,LifecycleEventObserver 给 LifecycleObserver 增加了感知生命周期状态变化的能力。

看看Lifecycling.getCallback(observer):

方法内有很多对observer进行类型判断的代码,我们这里关注的是ComponentActivity,所以LifecycleEventObserver的实现类就是ReflectiveGenericLifecycleObserver了:

它的onStateChanged()方法内部使用CallbackInfo的invokeCallbacks方法,这里应该就是执行观察者的方法了。

ClassesInfoCache内部用Map存了 所有观察者的回调信息,CallbackInfo是当前观察者的回调信息。

先看下CallbackInfo实例的创建,ClassesInfoCache.sInstance.getInfo(mWrapped.getClass()):

整体思路还是很清晰的,继续看CallbackInfo的invokeCallbacks方法:

很好理解,执行对应event的方法、执行注解了ON_ANY的方法。其中mEventToHandlers是在创建CallbackInfo时由遍历mHandlerToEvent来获取,存放了每个Event对应的多个方法。

最后看看handlers.get(i).invokeCallback,即MethodReference中:

根据不同参数类型,执行对应方法。

到这里,整个流程就完整了。实际看了这么一大圈,基本思路和我们的猜想是一致的。

这里借Android Jetpack架构组件(三)一文带你了解Lifecycle(原理篇)的图总结下:

三、LiveData介绍

3.1 LiveData介绍

3.1.1 作用

LiveData是Jetpack AAC的重要组件,同时也有一个同名抽象类。

LiveData,原意是 活着的数据。 数据还能有生命? 先来看下官方的定义:

LiveData 是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity/Fragment)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。

拆解开来:

也就是说,LiveData使得 数据的更新 能以观察者模式 被observer感知,且此感知只发生在 LifecycleOwner的活跃生命周期状态。

3.1.2 特点

使用 LiveData 具有以下优势:

3.2LiveData的使用

下面介绍LiveData的使用,掌握使用方法也可以更好理解上面的内容。

3.2.1基本使用

gradle依赖在上一篇中已经介绍了。下面来看基本用法:

举个例子:

注意到 LiveData实例mLiveData的创建是使用MutableLiveData,它是LiveData的实现类,且指定了源数据的类型为String。然后创建了接口Observer的实例,实现其onChanged()方法,用于接收源数据的变化。observer和Activity一起作为参数调用mLiveData的observe()方法,表示observer开始观察mLiveData。然后Activity的所有生命周期方法中都调用了mLiveData的setValue()方法。  结果日志打印如下:

另外,除了使用observe()方法添加观察者,也可以使用observeForever(Observer) 方法来注册未关联 LifecycleOwner的观察者。在这种情况下,观察者会被视为始终处于活跃状态。

3.2.2 扩展使用

扩展包括两点:

官方的例子如下:

为了观察股票价格变动,继承LiveData自定义了StockLiveData,且为单例模式,只能通过get(String symbol)方法获取实例。 并且重写了onActive()、onInactive(),并加入了 开始观察股价更新、移除股价更新观察 的逻辑。

也就是说,只有当 存在活跃的观察者(LifecycleOwner)时 才会连接到 股价更新服务 监听股价变化。使用如下:

由于StockLiveData是单实例模式,那么多个LifycycleOwner(Activity、Fragment)间就可以共享数据了。

3.2.3 高级用法

如果希望在将 LiveData 对象分派给观察者之前对存储在其中的值进行更改,或者需要根据另一个实例的值返回不同的 LiveData 实例,可以使用LiveData中提供的Transformations类。

3.2.3.1 数据修改 - Transformations.map

使用很简单:原本的liveData1 没有添加观察者,而是使用Transformations.map()方法 对liveData1的数据进行的修改 生成了新的liveDataMap,liveDataMap添加观察者,最后liveData1设置数据 。

此例子把 Integer类型的liveData1 修改为String类型的liveDataMap。结果如下:

3.2.3.2 数据切换 - Transformations.switchMap

如果想要根据某个值 切换观察不同LiveData数据,则可以使用Transformations.switchMap()方法。

liveData3、liveData4是两个数据源,有一个判断条件来决定 取哪一个数据 ,这个条件就是liveDataSwitch,如果值为true则取liveData3,false则取liveData4。 Transformations.switchMap()就用于实现这一逻辑,返回值liveDataSwitchMap添加观察者就可以了。  结果如下:

(Transformations对LivaData这两个用法和Rxjava简直一毛一样)

3.2.3.3 观察多个数据 - MediatorLiveData

MediatorLiveData 是 LiveData 的子类,允许合并多个 LiveData 源。只要任何原始的 LiveData 源对象发生更改,就会触发 MediatorLiveData 对象的观察者。

例如,如果界面中有可以从本地数据库或网络更新的 LiveData 对象,则可以向 MediatorLiveData 对象添加以下源:

Activity 只需观察 MediatorLiveData 对象即可从这两个源接收更新。 结果如下:

(Transformations也是对MediatorLiveData的使用。)

LiveData的使用就讲完了,下面开始源码分析。

3.3 源码分析

前面提到 LiveData几个特点,能感知生命周期状态变化、不用手动解除观察等等,这些是如何做到的呢?

3.3.1 添加观察者

LiveData原理是观察者模式,下面就先从LiveData.observe()方法看起:

首先是判断LifecycleOwner是DESTROYED状态,就直接忽略,不能添加。接着使用LifecycleOwner、observer 组装成LifecycleBoundObserver包装实例wrapper,使用putIfAbsent方法observer-wrapper作为key-value添加到观察者列表mObservers中。(putIfAbsent意思是只有列表中没有这个observer时才会添加。)

然后对添加的结果进行判断,如果mObservers中已经存在此observer key,但value中的owner不是传进来的owner,就会报错“不能添加同一个observer却是不同LifecycleOwner”。如果是相同的owner,就直接returne。

最后用LifecycleOwner的Lifecycle添加observer的封装wrapper。

另外,再看observeForever方法:

和observe()类似,只不过 会认为观察者一直是活跃状态,且不会自动移除观察者。

3.3.2 事件回调

LiveData添加了观察者LifecycleBoundObserver,接着看如何进行回调的:

LifecycleBoundObserver是LiveData的内部类,是对原始Observer的包装,把LifecycleOwner和Observer绑定在一起。当LifecycleOwner处于活跃状态,就称 LifecycleBoundObserver是活跃的观察者。

它实现自接口LifecycleEventObserver,实现了onStateChanged方法。上一篇Lifecycle中提到onStateChanged是生命周期状态变化的回调。

在LifecycleOwner生命周期状态变化时 判断如果是DESTROYED状态,则移除观察者。LiveData自动移除观察者特点就来源于此。 如果不是DESTROYED状态,将调用父类ObserverWrapper的activeStateChanged()方法处理 这个生命周期状态变化,shouldBeActive()的值作为参数,至少是STARTED状态为true,即活跃状态为true。

ObserverWrapper也是LiveData的内部类。mActive是ObserverWrapper的属性,表示此观察者是否活跃。如果活跃状态 未发生变化时,不会处理。

LiveData.this.mActiveCount == 0 是指 LiveData 的活跃观察者数量。活跃的观察者数量 由0变为1、由1变为0 会分别调用LiveData的 onActive()、onInactive()方法。这就是前面提到的

的回调方法。

最后观察者变为活跃,就使用LiveData的dispatchingValue(observerWrapper)进行数据分发:

如果当前正在分发,则分发无效;observerWrapper不为空,就使用considerNotify()通知真正的观察者,observerWrapper为空 则遍历通知所有的观察者。 observerWrapper啥时候为空呢?这里先留个疑问。 继续看considerNotify()方法:

先进行状态检查:观察者是非活跃就return;若当前observer对应的owner非活跃,就会再调用activeStateChanged方法,并传入false,其内部会再次判断。最后回调真正的mObserver的onChanged方法,值是LivaData的变量mData。

到这里回调逻辑也通了。

3.3.3 数据更新

LivaData数据更新可以使用setValue(value)、postValue(value),区别在于postValue(value)用于 子线程:

postValue方法把Runable对象mPostValueRunnable抛到主线程,其run方法中还是使用的setValue(),继续看:

setValue()把value赋值给mData,然后调用dispatchingValue(null),参数是null,对应前面提到的observerWrapper为空的场景,即 遍历所有观察者 进行分发回调。

到这里观察者模式完整的实现逻辑就梳理清晰了:LivaData通过observe()添加 与LifecycleOwner绑定的观察者;观察者变为活跃时回调最新的数据;使用setValue()、postValue()更新数据时会通知回调所有的观察者。

3.3.4 Transformations原理

最后来看下Transformations的map原理,如何实现数据修改的。switchMap类似的。

new了一个MediatorLiveData实例,然后将 传入的livaData、new的Observer实例作为参数 调用addSource方法:

MediatorLiveData是LiveData的子类,用来观察其他的LiveData并在其OnChanged回调时 做出响应。传入的livaData、Observer 包装成Source实例,添加到列表mSources中。

如果MediatorLiveData有活跃观察者,就调用plug():

Source是MediatorLiveData的内部类,是对源LiveData的包装。plug()中让源LiveData调用observeForever方法添加永远观察者-自己。  这里为啥使用observeForever方法呢,这是因为源LiveData在外部使用时不会调用observer方法添加观察者,这里永远观察是为了在源LiveData数据变化时及时回调到 mObserver.onChanged(v)方法,也就是Transformations map方法中的nChanged方法。  而在e.plug()前是有判断 MediatorLiveData 确认有活跃观察者的。

最后map方法中的nChanged方法中有调用MediatorLiveData实例的setValue(mapFunction.apply(x)); 并返回实例。而mapFunction.apply()就是map方法传入的修改逻辑Function实例。

最后类关系图:

四、ViewModel介绍

4.1、ViewModel介绍

ViewModel是Jetpack AAC的重要组件,同时也有一个同名抽象类。

ViewModel,意为 视图模型,即 为界面准备数据的模型。简单理解就是,ViewModel为UI层提供数据。 官方文档定义如下:

ViewModel 以注重生命周期的方式存储和管理界面相关的数据。(作用)

ViewModel 类让数据可在发生屏幕旋转等配置更改后继续留存。(特点)

到这里,你可能还是不清楚ViewModel到底是干啥的,别急,往下看。

4.1.1 出场背景

在详细介绍ViewModel前,先来看下背景和问题点。

这时候ViewModel就闪亮出场了——ViewModel用于代替MVP中的Presenter,为UI层准备数据,用于解决上面两个问题。

4.1.2 特点

具体地,相比于Presenter,ViewModel有以下特点:

4.1.2.1 生命周期长于Activity

ViewModel最重要的特点是 生命周期长于Activity。来看下官网的一张图:

看到在因屏幕旋转而重新创建Activity后,ViewModel对象依然会保留。 只有Activity真正Finish的时ViewModel才会被清除。

也就是说,因系统配置变更Activity销毁重建,ViewModel对象会保留并关联到新的Activity。而Activity的正常销毁(系统不会重建Activity)时,ViewModel对象是会清除的。

那么很自然的,因系统配置变更Activity销毁重建,ViewModel内部存储的数据 就可供重新创建的Activity实例使用了。这就解决了第一个问题。

4.1.2.2 不持有UI层引用

我们知道,在MVP的Presenter中需要持有IView接口来回调结果给界面。

而ViewModel是不需要持有UI层引用的,那结果怎么给到UI层呢?答案就是使用上一篇中介绍的基于观察者模式的LiveData。 并且,ViewModel也不能持有UI层引用,因为ViewModel的生命周期更长。

所以,ViewModel不需要也不能 持有UI层引用,那么就避免了可能的内存泄漏,同时实现了解耦。这就解决了第二个问题。

4.2、ViewModel使用

4.2.1 基本使用

了解了ViewModel作用解特点,下面来看看如何结合LivaData使用的。(gradle依赖在第一篇中已经介绍过了。)

步骤:

举个例子,如果您需要在Activity中显示用户信息,那么需要将获取用户信息的操作分放到ViewModel中,代码如下:

UserViewModel继承ViewModel,然后逻辑很简单:假装网络请求 2s后 返回用户信息,其中userLiveData用于抛出用户信息,loadingLiveData用于控制进度条显示。

再看UI层:

页面有个按钮用于点击获取用户信息,有个TextView展示用户信息。 在onCreate()中先 创建ViewModelProvider实例,传入的参数是ViewModelStoreOwner,Activity和Fragment都是其实现。然后通过ViewModelProvider的get方法 获取ViewModel实例,然后就是 观察ViewModel中的LiveData。

运行后,点击按钮 会弹出进度条,2s后展示用户信息。接着旋转手机,我们发现用户信息依然存在。来看下效果:

旋转手机后确实是重建了Activity的,日志打印如下:

总结下:

4.2.2 Fragment间数据共享

Activity 中的多个Fragment需要相互通信是一种很常见的情况。假设有一个ListFragment,用户从列表中选择一项,会有另一个DetailFragment显示选定项的详情内容。在之前 你可能会定义接口或者使用EventBus来实现数据的传递共享。

现在就可以使用 ViewModel 来实现。这两个 Fragment 可以使用其 Activity 范围共享 ViewModel 来处理此类通信,如以下示例代码所示:

代码很简单,ListFragment中在点击Item时更新ViewModel的LiveData数据,然后DetailFragment监听这个LiveData数据即可。

要注意的是,这两个 Fragment 通过ViewModelProvider获取ViewModel时 传入的都是它们宿主Activity。这样,当这两个 Fragment 各自获取 ViewModelProvider 时,它们会收到相同的 SharedViewModel 实例(其范围限定为该 Activity)。

此方法具有以下 优势:

最后来看下效果:

4.3、源码分析

经过前面的介绍,我们知道ViewModel的核心点 就是 因配置更新而界面(Activity/Fragment)重建后,ViewModel实例依然存在,这个如何实现的呢? 这就是我们源码分析的重点了。

在获取ViewModel实例时,我们并不是直接new的,而是使用ViewModelProvider来获取,猜测关键点应该就在这里了。

4.3.1 ViewModel的存储和获取

先来看下ViewModel类:

ViewModel类 是抽象类,内部没有啥逻辑,有个clear()方法会在ViewModel将被清除时调用。

然后ViewModel实例的获取是通过ViewModelProvider类,见名知意,即ViewModel提供者,来看下它的构造方法:

例子中我们使用的是只需传ViewModelStoreOwner的构造方法,最后走到两个参数ViewModelStore、factory的构造方法。继续见名知意:ViewModelStoreOwner——ViewModel存储器拥有者;ViewModelStore——ViewModel存储器,用来存ViewModel的地方;Factory——创建ViewModel实例的工厂。

ViewModelStoreOwner是个接口:

实现类有Activity/Fragment,也就是说 Activity/Fragment 都是 ViewModel存储器的拥有者,具体是怎样实现 获取ViewModelStore的呢?

先不急,我们先看 ViewModelStore 如何存储ViewModel、以及ViewModel实例如何获取的。

ViewModelStore代码很简单,viewModel作为Value存储在HashMap中。

再来看下创建ViewModel实例的工厂Factory,也就是NewInstanceFactory:

很简单,就是通过传入的class 反射获取ViewModel实例。

回到例子中,我们使用

来获取UserViewModel实例,那么来看下get()方法:

逻辑很清晰,先尝试从ViewModelStore获取ViewModel实例,key是"androidx.lifecycle.ViewModelProvider.DefaultKey:xxx.SharedViewModel",如果没有获取到,就使用Factory创建,然后存入ViewModelStore。

到这里,我们知道了 ViewModel如何存储、实例如何获取的,但开头说的分析重点:“因配置更新而界面重建后,ViewModel实例依然存在”,这个还没分析到。

4.3.2 ViewModelStore的存储和获取

回到上面的疑问,看看 Activity/Fragment 是怎样实现 获取ViewModelStore的,先来看ComponentActivity中对ViewModelStoreOwner的实现:

这里就是重点了。先尝试 从NonConfigurationInstance从获取 ViewModelStore实例,如果NonConfigurationInstance不存在,就new一个mViewModelStore。 并且还注意到,在onRetainNonConfigurationInstance()方法中 会把mViewModelStore赋值给NonConfigurationInstances:

onRetainNonConfigurationInstance()方法很重要:在Activity因配置改变 而正要销毁时,且新Activity会立即创建,那么系统就会调用此方法。 也就说,配置改变时 系统把viewModelStore存在了NonConfigurationInstances中。

NonConfigurationInstances是个啥呢?

ComponentActivity静态内部类,依然见名知意,非配置实例,即 与系统配置 无关的 实例。所以屏幕旋转等的配置改变 不会影响到这个实例? 继续看这个猜想是否正确。

我们看下getLastNonConfigurationInstance():

方法是在Acticity.java中,它返回的是Acticity.java中的NonConfigurationInstances的属性activity,也就是onRetainNonConfigurationInstance()方法返回的实例。(注意上面那个是ComponentActivity中的NonConfigurationInstances,是两个类)

来继续看mLastNonConfigurationInstances是哪来的,通过寻找调用找到在attach()方法中:

mLastNonConfigurationInstances是在Activity的attach方法中赋值。 在《Activity的启动过程详解》中我们分析过,attach方法是为Activity关联上下文环境,是在Activity 启动的核心流程——ActivityThread的performLaunchActivity方法中调用,这里的lastNonConfigurationInstances是存在 ActivityClientRecord中的一个组件信息。

ActivityClientRecord是存在ActivityThread的mActivities中:

那么,ActivityThread 中的 ActivityClientRecord 是不受 activity 重建的影响,那么ActivityClientRecord中lastNonConfigurationInstances也不受影响,那么其中的Object activity也不受影响,那么ComponentActivity中的NonConfigurationInstances的viewModelStore不受影响,那么viewModel也就不受影响了。

那么,到这里 核心问题 “配置更改重建后ViewModel依然存在” 的原理就分析完了。

4.4、对比onSaveInstanceState()

系统提供了onSaveInstanceState()用于让开发者保存一些数据,以方便界面销毁重建时恢复数据。那么和 使用ViewModel恢复数据 有哪些区别呢?

4.4.1 使用场景

在我很久之前一篇文章《Activity生命周期》中有提到:

onSaveInstanceState调用时机:

当某个activity变得“容易”被系统销毁时,该activity的onSaveInstanceState就会被执行,除非该activity是被用户主动销毁的,例如当用户按BACK键的时候。 注意上面的双引号,何为“容易”?言下之意就是该activity还没有被销毁,而仅仅是一种可能性。

这种可能性有哪些?有这么几种情况:

1、当用户按下HOME键时。 这是显而易见的,系统不知道你按下HOME后要运行多少其他的程序,自然也不知道activity A是否会被销毁,故系统会调用onSaveInstanceState,让用户有机会保存某些非永久性的数据。以下几种情况的分析都遵循该原则 。

2、长按HOME键,选择运行其他的程序时。

3、按下电源按键(关闭屏幕显示)时。

4、从activity A中启动一个新的activity时。

5、屏幕方向切换时,例如从竖屏切换到横屏时。 在屏幕切换之前,系统会销毁activity A,在屏幕切换之后系统又会自动地创建activity A,所以onSaveInstanceState一定会被执行。

总而言之,onSaveInstanceState的调用遵循一个重要原则,即当系统“未经你许可”时销毁了你的activity,则onSaveInstanceState会被系统调用,这是系统的责任,因为它必须要提供一个机会让你保存你的数据(当然你不保存那就随便你了)。

而使用ViewModel恢复数据 则 只有在 因配置更改界面销毁重建 的情况。

4.4.2 存储方式

ViewModel是存在内存中,读写速度快,而通过onSaveInstanceState是在 序列化到磁盘中。

4.4.3 存储数据的限制

ViewModel,可以存复杂数据,大小限制就是App的可用内存。而 onSaveInstanceState只能存可序列化和反序列化的对象,且大小有限制(一般Bundle限制大小1M)。


五、MVVM

5.1、开发架构 是什么?

我们先来理解开发架构的本质是什么,维基百科对软件架构的描述如下:

软件架构是一个系统的草图。软件架构描述的对象是直接构成系统的抽象组件。各个组件之间的连接则明确和相对细致地描述组件之间的通讯。在实现阶段,这些抽象组件被细化为实际的组件,比如具体某个类或者对象。在面向对象领域中,组件之间的连接通常用接口来实现。

拆分开来就是三条:

为啥要做开发架构设计呢?

架构模式最终都是 服务于开发者。如果代码职责和逻辑混乱,维护成本就会相应地上升。

宏观上来说,开发架构是一种思想,每个领域都有一些成熟的架构模式,选择适合自己项目即可。

5.2 、Android开发中的架构

具体到Android开发中,开发架构就是描述 视图层、逻辑层、数据层 三者之间的关系和实施:

正常的开发流程中,开始写代码之前 都会有架构设计这一过程。这就需要你选择使用何种架构模式了。

我的Android开发之路完整地经过了 MVC、MVP、MVVM,相信很多开发者和我一样都是这样一个过程,先来回顾下三者。

5.2.1 MVC

MVC,Model-View-Controller,职责分类如下:

View层 接收到用户操作事件,通知到 Controller 进行对应的逻辑处理,然后通知 Model去获取/更新数据,Model 再把新的数据 通知到 View 更新界面。这就是一个完整 MVC 的数据流向。

但在Android中,因为xml布局能力很弱,View的很多操作是在Activity/Fragment中的,而业务逻辑同样也是写在Activity/Fragment中。

所以,MVC 的问题点 如下:

5.2.2 MVP

MVP,Model-View-Presenter,职责分类如下:

MVP解决了MVC的问题:1.View责任明确,逻辑不再写在Activity中,而是在Presenter中;2.Model不再持有View。

View层 接收到用户操作事件,通知到Presenter,Presenter进行逻辑处理,然后通知Model更新数据,Model 把更新的数据给到Presenter,Presenter再通知到 View 更新界面。

MVP的实现思路:

MVP 本质是面向接口编程,实现了依赖倒置原则。MVP解决了View层责任不明的问题,但并没有解决代码耦合的问题,View和Presenter之间相互持有。

所以 MVP 有问题点 如下:

5.2.3 MVVM

MVVM,Model-View-ViewModel,职责分类如下:

注意,MVVM这里的ViewModel就是一个名称,可以理解为MVP中的Presenter。不等同于上一篇中的 ViewModel组件 ,Jetpack ViewModel组件是 对 MVVM的ViewModel 的具体实施方案。

MVVM 的本质是 数据驱动,把解耦做的更彻底,viewModel不持有view 。

View 产生事件,使用 ViewModel进行逻辑处理后,通知Model更新数据,Model把更新的数据给ViewModel,ViewModel自动通知View更新界面,而不是主动调用View的方法。

MVVM在Android开发中是如何实现的呢?接着看~

到这里你会发现,所谓的架构模式本质上理解很简单。比如MVP,甚至你都可以忽略这个名字,理解成 在更高的层面上 面向接口编程,实现了 依赖倒置 原则,就是这么简单。

5.3 MVVM 的实现 - Jetpack MVVM

前面提到,架构模式选择适合自己项目的即可。话虽如此,但Google官方推荐的架构模式 是适合大多数情况,是非常值得我们学习和实践的。

好了,下面我们就来详细介绍 Jetpack MVVM 架构。

5.3.1 Jetpack MVVM 理解

Jetpack MVVM 是 MVVM 模式在 Android 开发中的一个具体实现,是 Android中 Google 官方提供并推荐的 MVVM实现方式。

不仅通过数据驱动完成彻底解耦,还兼顾了 Android 页面开发中其他不可预期的错误,例如Lifecycle 能在妥善处理 页面生命周期 避免view空指针问题,ViewModel使得UI发生重建时 无需重新向后台请求数据,节省了开销,让视图重建时更快展示数据。

首先,请查看下图,该图显示了所有模块应如何彼此交互:

各模块对应MVVM架构:

View层 包含了我们平时写的Activity/Fragment/布局文件等与界面相关的东西。

ViewModel层 用于持有和UI元素相关的数据,以保证这些数据在屏幕旋转时不会丢失,并且还要提供接口给View层调用以及和仓库层进行通信。

仓库层 要做的主要工作是判断调用方请求的数据应该是从本地数据源中获取还是从网络数据源中获取,并将获取到的数据返回给调用方。本地数据源可以使用数据库、SharedPreferences等持久化技术来实现,而网络数据源则通常使用Retrofit访问服务器提供的Webservice接口来实现。

另外,图中所有的箭头都是单向的,例如View层指向了ViewModel层,表示View层会持有ViewModel层的引用,但是反过来ViewModel层却不能持有View层的引用。除此之外,引用也不能跨层持有,比如View层不能持有仓库层的引用,谨记每一层的组件都只能与它相邻层的组件进行交互。

这种设计打造了一致且愉快的用户体验。无论用户上次使用应用是在几分钟前还是几天之前,现在回到应用时都会立即看到应用在本地保留的数据。如果此数据已过期,则应用的Repository将开始在后台更新数据。

5.3.2 实施

我们来举个完整的例子 - 在页面中显示用户信息列表,来说明 Jetpack MVVM 的具体实施。

5.3.2.1 构建界面

首先创建一个列表页面 UserListActivity,并且知道页面所需要的数据是,用户信息列表。

那么 用户信息列表 如何获取呢?根据上面的架构图,就是ViewModel了,所以我们创建 UserListViewModel 继承自 ViewModel,并且把 用户信息列表 以 LiveData呈现。

LiveData 是一种可观察的数据存储器。应用中的其他组件可以使用此存储器监控对象的更改,而无需在它们之间创建明确且严格的依赖路径。LiveData 组件还遵循应用组件(如 Activity、Fragment 和 Service)的生命周期状态,并包括清理逻辑以防止对象泄漏和过多的内存消耗。

将 UserListViewModel 中的字段类型更改为 MutableLiveData<List>。现在,更新数据时,系统会通知 UserListActivity。此外,由于此 LiveData 字段具有生命周期感知能力,因此当不再需要引用时,会自动清理它们。

另外,注意到暴露的获取LiveData的方法 返回的是LiveData类型,即不可变的,而不是MutableLiveData,好处是避免数据在外部被更改。(参见LivaData篇文章)

现在,我们修改 UserListActivity 以观察数据并更新界面:

每次更新用户列表信息数据时,系统都会调用 onChanged() 回调并刷新界面,而不需要 ViewModel主动调用View层方法刷新,这就是 数据驱动 了 —— 数据的更改 驱动 View 自动刷新。

因为LiveData具有生命周期感知能力,这意味着,除非 Activity 处于活跃状态,否则它不会调用 onChanged() 回调。当调用 Activity 的 onDestroy() 方法时,LiveData 还会自动移除观察者。

另外,我们也没有添加任何逻辑来处理配置更改(例如,用户旋转设备的屏幕)。UserListViewModel 会在配置更改后自动恢复,所以一旦创建新的 Activity,它就会接收相同的 ViewModel 实例,并且会立即使用当前的数据调用回调。鉴于 ViewModel 对象应该比它们更新的相应 View 对象存在的时间更长,因此 ViewModel 实现中不得包含对 View 对象的直接引用,包括Context。

5.3.2.2 获取数据

现在,我们已使用 LiveData 将 UserListViewModel 连接到UserListActivity,那么如何获取用户个人信息列表数据呢?

实现 ViewModel 的第一个想法可能是 使用Retrofit/Okhttp调用接口 来获取数据,然后将该数据设置给 LiveData 对象。这种设计行得通,但如果采用这种设计,随着应用的扩大,应用会变得越来越难以维护。这样会使 UserListViewModel 类承担太多的责任,这就违背了单一职责原则。

ViewModel 会将数据获取过程委派给一个新的模块,即Repository。

Repository模块会处理数据操作。它们会提供一个干净的 API,以便应用内其余部分也可以轻松获取该数据。数据更新时,它们知道从何处获取数据以及进行哪些 API 调用。您可以将Repository视为不同数据源(如持久性模型、网络服务和缓存)之间的媒介。

虽然Repository模块看起来不必要,但它起着一项重要的作用:它会从应用的其余部分中提取数据源。现在,UserListViewModel 是不知道数据来源的,因此我们可以为ViewModel提供从几个不同的数据源获取数据。

5.3.2.3 连接 ViewModel 与存储区

我们在UserListViewModel 提供一个方法,用户Activity获取用户信息。此方法就是调用Repository来执行,并且吧数据设置到LiveData。

在Activity中,就要获取UserListViewModel实例,获取用户信息:

5.3.2.4 缓存数据

现在UserRepository 有个问题是,它从后端获取数据后,不会将缓存该数据。因此,如果用户在离开页面后再返回,则应用必须重新获取数据,即使数据未发生更改也是如此。这就浪费了宝贵的网络资源,迫使用户等待新的查询完成。 所以,我们向 UserRepository 添加了一个新的数据源,本地缓存。缓存实现 可以是 数据库、SharedPreferences等持久化技术。(具体实现就不再写了)

到这里,Jetpack MVVM 就介绍完了。

实际上只要前面介绍的 Lifecycle、LivaData、ViewModel 熟练掌握的话,这里是十分好理解的。

5.3.3 注意点

5.3.4 MVP改造MVVM

了解了Jetpack MVVM的实现,再来改造 MVP 是很简单的了。

步骤如下:

这样就已经成为了MVVM。当然也要检查下 原MVP的 Model层的实现,是否满足上面的要求。

5.4 总结

本篇介绍了 架构模式的含义,回顾和比较了Android中的架构模式MVC、MVP、MVVM,最好在 Jetpack架构组件 基础上 介绍了 MVVM 的详细实现方法、注意点,以及MVP的改造。

整篇下来,基本很简单容易理解的。 例子是很简单的,所以在实际开发中 需要深入理解 MVVM 数据驱动的本质,和MVP的区别。

有人可能会有疑惑:怎么完全没有提 DataBinding、双向绑定?

实际上,这也是我之前的疑惑。 没有提 是因为:

六、重新认知 DataBinding

Jetpack MVVM 架构模式,到这里已经基本满足标准化开发了。但 Jetpack 架构组件 除了 Lifecycle、LivaData、ViewModel,还有:

WorkManager,用于管理后台工作的任务,即使应用退出或重启时。

Paging,分页库,按需加载部分数据。

Startup,用于App启动速度优化的库,但只适用于库开发者, 郭霖这篇有详细介绍。DataStore,用于替换SharedPreferences,目前还处于Alpha阶段。

DataBinding,将布局中的界面组件直接绑定到数据源,提供双向绑定,及高级绑定适配能力。

ViewBinding,用于替代findViewById,而DataBinding也包含ViewBinding的能力。

Room,实现本地存储 数据库管理,支持LiveData。目前,就学习使用的必要性和库的功能性 来说,WorkManager、Paging、Startup都是非必须的,DataStore还未正式发布,ViewBinding的能力也包含在DataBinding中。Room,实际 功能和性能 同GreenDAO类似,有个好处是支持LivaData,但已使用GreenDao的项目,也不必切换为Room了。

DataBinding是比较有争议的一个库,这也是本篇的重点,相信会带你 重新认识 被误解的 DataBinding。

6.1 重新认知 DataBinding

DataBinding的使用方法,参考官方文档就可以,介绍地很详细了,这里就不再搬运。(另外还找到一个慕课网的视频很不错:入门篇高级篇

6.1.1 DataBinding 的本质

应该不少人和我以前一样,对 DataBinding 的认知就是 在xml中写逻辑:

看到 xml 中 使用三元表达式 来计算view需要的值,然后就认为:“ DataBinding 不好用!在xml中写表达式逻辑,出错了debug不了啊,逻辑写在xml里面的话 xml 就承担了 Presenter/ViewModel 的职责 变得混乱了啊。”

如果是把逻辑写在xml中,确实如是:xml中是不能调试的、职责上确实是混乱了。

但,这就是 DataBinding 的本质了吗?

6.1.1.1 DataBinding 以前

在 DataBinding 出现以前,想要改变视图 就要引用该视图:

而要引用该视图就要先判空,textView 和 viewModel 都不能为空。textView为啥要判空呢?一种情况是 R.id.sample_text是定义在在其他页面中;一种情况是存在控件存在差异的 横、竖 两种布局,如横屏存在此 textView 控件,而竖屏没有,那么就需要对其做判空处理。

App内页面和控件数量繁多,一个控件可能会多处调用,这就会有出现空指针的可能,那如何完全避免呢?

6.1.1.2 数据绑定

DataBinding,含义是 数据绑定,即  布局中的控件 与  可观察的数据 进行绑定。

布局中这个TextView是实实在在 存在的,就不需要判空了。而user是否为空 DataBinding也会自动处理:在表达式 @{user.name} 中,如果 user 为 Null,则为 user.name 分配默认值 null。

并且,当该 user.name 被 set 新值时,被绑定了该数据的控件即可获得通知和刷新。换言之,在使用 DataBinding 后,唯一的改变是,你无需手动调用视图来 set 新状态,你只需 set 数据本身。

所以,DataBinding 并非是 将 UI 逻辑搬到 XML 中写 导致而难以调试 ,只负责绑定数据, UI 控件 与 其需要的 终态数据 进行绑定。 终态数据是指 UI 控件 直接需要的数据(UI数据),string值、int值等,而不是一段逻辑(不然就叫 LogicBinding了 ,虽然DataBinding支持逻辑表达式)。

明确了 DataBinding 的 职责边界后 应该知道了:原本的逻辑代码 该怎么写还是怎么写,只不过不再需要 textView.setText(user.name),而是直接 user.setName()。

所以 DataBinding 的本质就是 终态数据 与 UI控件 的绑定,具有以下优势:

无需多处调用控件,原本调用的地方只需要set数据即可1的延伸,无需手动判空1的延伸,完全不用写模板代码 findViewById并且,引入DataBinding后,原本的 UI 逻辑无需改动,只需设置终态数据上篇提到过 Jetpack MVVM 架构本质是数据驱动,这就是说,控件的状态及数据是 被分离到 ViewModel 中管理,并且 ViewModel 这一层只需负责状态数据本身的变化,至于该数据在布局中是 被哪些视图绑定、有没有视图来绑定、以及怎么绑定,ViewModel 是不用关心的。

那控件是如何做到被通知且更新状态的呢?

DataBinding 是通过 观察者模式 来管理控件刷新状态。当状态数据变化时,只需手动地完成 setValue,这将通知 DataBinding 去刷新 该数据 绑定的控件。

而,文章开头提到的把逻辑放入xml中的写法,是不建议的。数据值应 直接反映UI控件需要的结果,而不是作为逻辑条件放在 xml 中。所以,DataBinding 不负责 UI 逻辑,逻辑原本在哪里写,现在还是在哪里写,只不过,原本调用控件实例去刷新状态的方式,现在改成了数据驱动。 这就是DataBinding 的核心目标。

6.1.2 例子 - 绑定列表数据

来举个例子进行说明:在页面中展示用户信息(User)列表,同时还有两个按钮用于添加、移除用户:

我们知道,RecyclerView的所展示的列表数据, 是通过Adapter 对每一项数据 分别进行设置的,也就是说User是绑定到 Item的xml中:

我们看下,在Activity中是如何处理的:

RecyclerView的初始化、调用ViewModel对数据的获取,这些处理及逻辑 和之前一毛一样,不同点在于 Item数据的展示:

在UserItemViewHolder中,不用去findViewById了,而是直接DataBindingUtil.bind(view),ViewHolder只要Hold住 binding就可以了,之前是Hold住所有的view。在UserListAdapter中,设置数据是,也只是使用 binding 去 setUser(user)即可。

6.2 自定义属性 - BindingAdapter

DataBinding 还有个强大功能:能为控件提供自定义属性的 BindingAdapter!

不懂?我们来看个例子。

其中的 app:imageUrl 、app:placeHolder 分别与 user.avatar、@drawable/dog 绑定了。 但我们知道ImageView本身是没有这两个属性的,并且我们也并不是 继承 ImageView 的自定义View,那为啥可以这样使用呢? 再来看:

在随便某个类中添加 public static 方法(方法名随意),增加注解@BindingAdapter,并且注明对应的"app:imageUrl", "app:placeHolder",然后方法参数是 控件类型 及 这两个属性对应 值。 然后在方法中写逻辑即可,这里就是使用Glide加载用户头像,其中placeHolder是占位图。

这样就完成了 图片的加载了!

使用确实相当简洁,相当于 直接自定义属性。你可以自定义 任何你想要的属性。

通常我们可以用 @BindingAdapter 方式,在模块 内部 来做一些公用逻辑。例如这个图片加载,@BindingAdapter注解的方法 只要写一次,那么 所有用到 ImageView 加载图片的地方 xml中都可以 直接使用属性 app:imageUrl 、app:placeHolder  直接绑定数据 。

6.3 结合 LiveData

DataBinding 还有个妙处在于: 可以结合 LiveData 使用。

原本我们使用DataBinding,在xml中定义的variable数据 ,必须要继承BaseObservable 或者使用 ObservableField,还要添加 注解 @Bindable、调用notifyPropertyChanged(BR.name)。这是为了 user.setName(name) 字段发生变化时 通知 对应绑定View 也进行刷新。

而 我们 上一篇 中 MVVM 是使用  LiveData,实现数据驱动的,它包裹的 User 是没有继承BaseObservable的,  要继承嘛? 不用!

LiveData 的出现,就可以代替 ObservableField ,并且 还自动具备 生命周期管理。

不用侵入式的修改数据实体类了,直接使用LiveData,同样支持DataBinding的数据绑定!

DataBinding 结合 LiveData 使用步骤很简单:

要使用LiveData对象作为数据绑定来源,需要设置LifecycleOwnerxml中 定义变量 ViewModel, 并使用 ViewModel 中的 LiveData 绑定对应控件binding设置变量ViewModel

这样就ok了,你会发现 我们不需要在 Activity中 拿到LivaData 去 observe(owner,observer)了,DataBinding 自动生成的代码,会帮我们去做这操作,所以需要设置LifecycleOwner。

也就是说,在上一篇中介绍的 Jetpack MVVM 中,如果要使用 DataBinding 的话,也是很简单的。

6.4、Jetpack MVVM 补充说明

讲完DataBinding,所有的 Jetpack 架构组件 的重点内容 就全部讲完了。

这里对 Jetpack AAC 及 MVVM ,做一些 补充 和 说明:

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

推荐阅读更多精彩内容