Dagger Hilt 初探

介绍

Dagger Hilt (这名字起的溜...........)

官方描述其设计目的:

  • To simplify Dagger-related infrastructure for Android apps.
  • To create a standard set of components and scopes to ease setup, readability/understanding, and code sharing between apps.
  • To provide an easy way to provision different bindings to various build types (e.g. testing, debug, or release).

简单说就是Dagger Android的瘦身包,使依赖注入在Android开发中标准化、简单化。

集成

首先在项目级别的build.gradle文件中添加以下内容,这将使我们能够访问hilt gradle插件:

classpath 'com.google.dagger:hilt-android-gradle-plugin:2.28-alpha'

然后在应用程序级别的build.gradle文件并应用此插件:

apply plugin: 'kotlin-kapt'
apply plugin: 'dagger.hilt.android.plugin'

最后,在应用程序级别build.gradle文件中添加所需的hilt依赖项:

implementation "com.google.dagger:hilt-android:$hilt_version"
kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
implementation 'androidx.hilt:hilt-lifecycle-viewmodel:1.0.0-alpha01'
kapt 'androidx.hilt:hilt-compiler:1.0.0-alpha01'

这样Hilt就可以使用了。

Hilt Application

按照官方要求,首先需要在自定义的Application类中添加@HiltAndroidApp注解:

@HiltAndroidApp
class APP:Application()

这有什么作用?以下来自官方描述:

All apps using Hilt must contain an Application class annotated with @HiltAndroidApp. @HiltAndroidApp kicks off the code generation of the Hilt components and also generates a base class for your application that uses those generated components. Because the code generation needs access to all of your modules, the target that compiles your Application class also needs to have all of your Dagger modules in its transitive dependencies.

Just like other Hilt Android entry points, Applications are members injected as well. This means you can use injected fields in the Application after super.onCreate() has been called.

Daager2中,需要Application继承DaggerApplication,并且还需要创建Application的Module
这里只需要使用@HiltAndroidApp的注解就可以完成对Application的依赖注入,由Hilt gradle插件生成对应的文件

构建后我们看到在 app/build/generated/source/kapt/debug/目录下生成了一个Hilt_APP的抽象类:

/**
 * A generated base class to be extended by the @dagger.hilt.android.HiltAndroidApp annotated class. If using the Gradle plugin, this is swapped as the base class via bytecode transformation. */
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.ApplicationGenerator")
public abstract class Hilt_APP extends Application implements GeneratedComponentManager<Object> {
  private final ApplicationComponentManager componentManager = new ApplicationComponentManager(new ComponentSupplier() {
    @Override
    public Object get() {
      return DaggerAPP_HiltComponents_ApplicationC.builder()
          .applicationContextModule(new ApplicationContextModule(Hilt_APP.this))
          .build();
    }
  });

  protected final ApplicationComponentManager componentManager() {
    return componentManager;
  }

  @Override
  public final Object generatedComponent() {
    return componentManager().generatedComponent();
  }

  @CallSuper
  @Override
  public void onCreate() {
    // This is a known unsafe cast, but is safe in the only correct use case:
    // APP extends Hilt_APP
    ((APP_GeneratedInjector) generatedComponent()).injectAPP(UnsafeCasts.<APP>unsafeCast(this));
    super.onCreate();
  }
}

  • ApplicationComponentManager的声明
  • onCreate函数中注入Application类

看下里面涉及到的~

ApplicationComponentManager

public final class ApplicationComponentManager implements GeneratedComponentManager<Object> {
  private volatile Object component;
  private final Object componentLock = new Object();
  private final ComponentSupplier componentCreator;

  public ApplicationComponentManager(ComponentSupplier componentCreator) {
    this.componentCreator = componentCreator;
  }

  @Override
  public Object generatedComponent() {
    if (component == null) {
      synchronized (componentLock) {
        if (component == null) {
          component = componentCreator.get();
        }
      }
    }
    return component;
  }
}

主要用于管理应用程序中的Hilt Component的创建

  • 构造中创建ComponentSupplier实例。

  • generatedComponent():通过对象锁获取ComponentSupplier类中的Object,并负责在onCreate函数中将依赖项注入到我们的应用程序类中。

ComponentSupplier

提供component的接口

/**
 * Interface for supplying a component. This is separate from the Supplier interface so that
 * optimizers can strip this method (and therefore all the Dagger code) from the main dex even if a
 * Supplier is referenced in code kept in the main dex.
 */
public interface ComponentSupplier {
  Object get();
}

ApplicationC

代码比较长就不贴出来了,看下builder:

import dagger.hilt.android.internal.builders.ActivityComponentBuilder;
import dagger.hilt.android.internal.builders.ActivityRetainedComponentBuilder;
import dagger.hilt.android.internal.builders.FragmentComponentBuilder;
import dagger.hilt.android.internal.builders.ServiceComponentBuilder;
import dagger.hilt.android.internal.builders.ViewComponentBuilder;
import dagger.hilt.android.internal.builders.ViewWithFragmentComponentBuilder;

ComponentSupplier实现的内部,我们可以看到对ApplicationC(Application组件)类的引用。DaggerAPP_HiltComponents_ApplicationC是生成的应用程序组件,它充当Hilt在我们的应用程序中使用的(Activity,Fragment,Service,View等组件)的全局容器。

GeneratedInjector

@OriginatingElement(
    topLevelClass = APP.class
)
@GeneratedEntryPoint
@InstallIn(ApplicationComponent.class)
@Generated("dagger.hilt.android.processor.internal.androidentrypoint.InjectorEntryPointGenerator")
public interface APP_GeneratedInjector {
  void injectAPP(APP aPP);
}

接口类提供injectAPP方法为外部类提供了一个访问点,以触发应用程序Component的注入。

ApplicationContextModule

@Module
@InstallIn(ApplicationComponent.class)
public final class ApplicationContextModule {
  private final Context applicationContext;

  public ApplicationContextModule(Context applicationContext) {
    this.applicationContext = applicationContext;
  }

  @Provides
  @ApplicationContext
  Context provideContext() {
    return applicationContext;
  }

  @Provides
  Application provideApplication() {
    return (Application) applicationContext.getApplicationContext();
  }

}

主要是提供ApplicationContext,通过@InstalIIn注入到 ApplicationComponent便于后续使用

这里有个@ApplicationContext 这是个qualifers 限定符,Hilt还提供了一个@ActivityContext

例如:

class AnalyticsAdapter @Inject constructor(
    @ActivityContext private val context: Context,
    private val service: AnalyticsService
) { ... }
@Singleton
class NetWorkUtils @Inject constructor(@ApplicationContext private val context: Context) {
    fun isNetworkConnected(): Boolean {
        .....
    }
}

可以直接作为@Provides方法或@Inject构造的参数使用。

流程

image

Hilt Components

介绍

在之前Dagger-Android中,我们必须创建诸如ActivityScope,FragmentScope之类的范围注释,以管理对象的生命周期,

而这里只要使用@InstallIn的注解,就可以委托Hilt帮我们管理

组件的生存期:

组件 范围 创建 销毁
ApplicationComponent @Singleton Application#onCreate() Application#onDestroy()
ActivityRetainedComponent @ActivityRetainedScope Activity#onCreate()链接 Activity#onDestroy()链接
ActivityComponent @ActivityScoped Activity#onCreate() Activity#onDestroy()
FragmentComponent @FragmentScoped Fragment#onAttach() Fragment#onDestroy()
ViewComponent @ViewScoped View#super() View 被毁
ViewWithFragmentComponent @ViewScoped View#super() View 被毁
ServiceComponent @ServiceScoped Service#onCreate() Service#onDestroy()

@InstallIn模块中确定绑定范围时,绑定上的范围必须与component范围匹配。例如,@InstallIn(ActivityComponent.class)模块内的绑定只能用限制范围@ActivityScoped

例如我们需要在App中共享OkHttp的配置:

@Module
@InstallIn(ApplicationComponent::class)
class ApplicationModule {

    @Provides
    fun provideBaseUrl() = "...."

    @Provides
    @Singleton
    fun provideOkHttpClient() = if (BuildConfig.DEBUG) {
        val loggingInterceptor = HttpLoggingInterceptor()
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY)
        OkHttpClient.Builder()
            .addInterceptor(loggingInterceptor)
            .build()
    } else OkHttpClient
        .Builder()
        .build()


    @Provides
    @Singleton
    fun provideRetrofit(
        okHttpClient: OkHttpClient,
        BASE_URL: String
    ): Retrofit =
        Retrofit.Builder()
            .addConverterFactory(MoshiConverterFactory.create())
            .baseUrl(BASE_URL)
            .client(okHttpClient)
            .build()

    @Provides
    @Singleton
    fun provideApiService(retrofit: Retrofit): ApiService = retrofit.create(ApiService::class.java)

}

类似Dagger中:

@Module
class NetworkModule {
        // Hypothetical dependency on LoginRetrofitService
        @Provides
        fun provideLoginRetrofitService(
            okHttpClient: OkHttpClient
        ): LoginRetrofitService { ... }
}

@Component(modules = [NetworkModule::class])
interface ApplicationComponent {
        ...
}

这里通过@InstallIn(ApplicationComponent::class)Hilt帮我们管理ApplicationModule的生命周期

Module

module的使用基本和dagger中一样, 用来提供一些无法用构造@Inject的依赖, 比如接口, 第三方库类型, Builder模式构造的对象等.

  • @Module: 标记一个module, 可以是一个object.
  • @Provides: 标记方法, 提供返回值类型的依赖.这里就不需要手动添加到@Component(modules = ...)
  • @Binds: 标记抽象方法, 返回接口类型, 接口实现是方法的唯一参数.
interface AnalyticsService {
  fun analyticsMethods()
}

// Constructor-injected, because Hilt needs to know how to
// provide instances of AnalyticsServiceImpl, too.
class AnalyticsServiceImpl @Inject constructor(
  ...
) : AnalyticsService { ... }

@Module
@InstallIn(ActivityComponent::class)
abstract class AnalyticsModule {

  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

@Provides@Binds的区别:

按照官方说@Binds需要module是一个abstract class,@Provides需要module是一个object.而且@Binds需要在方法参数里面明确指明接口的实现类

但是@Provides这么用也是可以的。

Qualifier

如果要提供同一个接口的不同实现, 可以用不同的注解来标记. (类似于dagger中是@Named).

什么意思? 比如我们缓存接口有内存和磁盘两种实现:

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInMemory

@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class CacheInDisk

module中提供的时候用来标记相应的依赖:

@InstallIn(ApplicationComponent::class)
@Module
object CacheModule {

    @CacheInMemory
    @Singleton
    @Provides
    fun getCacheInMemory(memoryImpl: CacheSourceMemoryImpl): CacheSource = memoryImpl
    
    @CacheInDisk
    @Singleton
    @Provides
    fun getCacheInDisk(diskImpl: CacheSourceDiskImpl): CacheSource = diskImpl
}

Android types

@AndroidEntryPoint

Dagger2中,对Activity和Fragment的注入依赖的使用比较麻烦。

@Module
abstract class ActivityModule {

  @ActivityScope
  @ContributesAndroidInjector(modules = [MainActivityFragmentModule::class])
  internal abstract fun contributeMainActivity(): MainActivity

  @ActivityScope
  @ContributesAndroidInjector
  internal abstract fun contributeMovieDetailActivity(): MovieDetailActivity

  @ActivityScope
  @ContributesAndroidInjector
  internal abstract fun contributeTvDetailActivity(): TvDetailActivity

  @ActivityScope
  @ContributesAndroidInjector
  internal abstract fun contributePersonDetailActivity(): PersonDetailActivity
}

Hilt中就比较简单了,只需要@AndroidEntryPoint的注解。相当于上面的@ContributesAndroidInjector

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
    //
    private val mJokesViewModel: JokesViewModel by viewModels()
     private val mBinding: ActivityMainBinding by viewBinding {
        ActivityMainBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        mJokesViewModel.jokes.observe(this, Observer {
                ........
        }

    }
}

我们不需要编写AndroidInjection.inject(this)或扩展DaggerAppCompatActivity类。

这里官方给了限定范围:

  • Activity
  • Fragment
  • View
  • Service
  • BroadcastReceiver

Hilt currently only supports activities that extend ComponentActivity and fragments that extend androidx library Fragment, not the (now deprecated) Fragment in the Android platform.

Hilt目前不直接支持 content providers.

@EntryPoint

Hilt支持最常用的Android组件, 对于默认不支持的类型, 如果要做字段注入, 需要用@EntryPoint.

这里只是限制了字段注入的情况, 对于自定义类型我们一般习惯于用构造注入。

必须与@InstallIn搭配使用,将interface标记为入口点,这样就可以使用Hilt容器提供的依赖对象们.

如果要content provider使用Hilt

class ExampleContentProvider : ContentProvider() {

  @EntryPoint
  @InstallIn(ApplicationComponent::class)
  interface ExampleContentProviderEntryPoint {
    fun analyticsService(): AnalyticsService
  }

  ...
}

要访问@EntryPoint,使用静态方法 EntryPointAccessors

class ExampleContentProvider: ContentProvider() {
    ...

  override fun query(...): Cursor {
    val appContext = context?.applicationContext ?: throw IllegalStateException()
    val hiltEntryPoint =
      EntryPointAccessors.fromApplication(appContext, ExampleContentProviderEntryPoint::class.java)

    val analyticsService = hiltEntryPoint.analyticsService()
    ...
  }
}

appContext参数要与@InstallIn(ApplicationComponent::class)保持一致。

@EntryPoint除了解决上述字段注入的 问题,还有什么场景可以发挥用处?

生命周期匹配

我们利用FragmentFactoryActivityFragment之间用构造函数传递数据 :

class ContainerActivity : AppCompatActivity() {
    
    private var fragmentDataTest = FragmentDataTest()
    private val mBinding: ActivityContainerBinding by viewBinding {
        ActivityContainerBinding.inflate(layoutInflater)
    }
    override fun onCreate(savedInstanceState: Bundle?) {
        supportFragmentManager.fragmentFactory = EntryPointFragmentFactory(fragmentDataTest)
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        setSupportActionBar(mBinding.toolbar)
    }
}

看上述代码,在没有用Hilt之前fragmentFactory的设置应该是在 super.onCreate()之前,但是如果用Hilt就不能这么写了,因为在

之前Hilt Application中已经说过,Hilt是在super.onCreate()中进行依赖注入的,在Hilt_ContainerActivity类中:

  @CallSuper
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    inject();
    super.onCreate(savedInstanceState);
  }

<u>Injection happens in super.onCreate().</u>

所以,如果我们要在ContainerActivity中使用FragmentFactory就该在super.onCreate()之后,那么问题来了...我们知道FragmentFactory 负责在 Activity 和 parent Fragment 初始化 Fragment,应该在 super.onCreate() 之前关联 FragmentFactory 和 FragmentManager设置。如果在使用Hilt注入之后还是放在编译会报错:

UninitializedPropertyAccessException: lateinit property mFragmentFactory has not been initialized

所以我们将FragmentManager绑定FragmentFactory的动作放在super.onCreate()之后:

@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {

    @Inject lateinit var mFragmentFactory: EntryPointFragmentFactory

    private val mBinding: ActivityContainerBinding by viewBinding {
        ActivityContainerBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        supportFragmentManager.fragmentFactory = mFragmentFactory
        setContentView(mBinding.root)
        mFragmentFactory.fragmentDataTest.setData("xxx")
    }
}

这样就可以了,但是要注意了:

<u>如果上面的ContainerActivity被意外终止而触发重建的话是会报错的 :</u>

 java.lang.RuntimeException: Unable to start activity ComponentInfo{tt.reducto.daggerhiltsample/tt.reducto.daggerhiltsample.ui.entry.ContainerActivity}: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
 ...
Caused by: androidx.fragment.app.Fragment$InstantiationException: Unable to instantiate fragment tt.reducto.daggerhiltsample.ui.entry.EntryPointFragment: could not find Fragment constructor
  

可以定位 Hilt_ContainerActivity中的super.onCreate(savedInstanceState);这里就涉及到FragmentManager的状态保存与恢复

Fragment依附于Activity,而Fragment的状态保存与恢复机制也是由Activity的相关方法触发。Activity的方法onSaveInstanceState(Bundle outState)的参数outState是系统在状态需要保存时用来提供存放持久化状态的容器,当系统触发状态保存时,Activity下的Fragment的所有状态便通过mFragments的saveAllState方法保存在了 FRAGMENTS_TAG 键中,在Activity重建 的 时候通过mFragments.restoreAllState入口将状态恢复

在此期间Fragment的都会交由FragmentManager管理,包括我们需要注意的如何新建一个Fragment对象:

Fragment.instantiate(…)方法会根据所给的class name加载对应的Class类,调用clazz.newInstance()新建一个全新的Fragment对象:

public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
    ...
    Fragment f = (Fragment)clazz.newInstance();
    if (args != null) {
        args.setClassLoader(f.getClass().getClassLoader());
        f.mArguments = args;
    }
    ...
}

综上,Activity重建后无法利用FragmentFactory重新创建Fragment,所以官方FragmentFactory文档才有这么一句话:

  • Before the system restores the Fragment, if the Fragment is being recreated after a configuration change or the app’s process restart.

了解了FragmentManager绑定FragmentFactory的动作在super.onCreate()之前执行的必要性后,我们再来利用Hilt提供的特性解决声明周期不匹配的问题

@EntryPoint
@InstallIn(ActivityComponent::class)
interface ContainerActivityEntryPoint {
    fun getFragmentManager(): FragmentManager
    fun getFragmentFactory(): EntryPointFragmentFactory
}

我们利用@EntryPointContainerActivityEntryPoint对象中获取FragmentManager和EntryPointFragmentFactory的引用:

@AndroidEntryPoint
class ContainerActivity : AppCompatActivity() {

    private val mBinding: ActivityContainerBinding by viewBinding {
        ActivityContainerBinding.inflate(layoutInflater)
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        val entryPoint  = EntryPointAccessors.fromActivity(this,ContainerActivityEntryPoint::class.java)
        val mFragmentFactory = entryPoint.getFragmentFactory()
        entryPoint.getFragmentManager().fragmentFactory = mFragmentFactory
        super.onCreate(savedInstanceState)
        setContentView(mBinding.root)
        mFragmentFactory.fragmentDataTest.setData("xxxxxxxxxxx")
    }
        ......
}

从静态类EntryPointAccessors中获取定义的实例,有点SOLID中接口隔离原则的意思。

再次模拟Activity重建状态,一切正常。

ViewModel

之前Dagger注入ViewModel时比较麻烦,在构造函数中创建带有参数的ViewModel实例,每个ViewModel必须实现一个全局或每个ViewModelFactory,并实现ViewModelModule来绑定ViewModel

@Module
internal abstract class ViewModelModule {

  @Binds
  @IntoMap
  @ViewModelKey(MainActivityViewModel::class)
  internal abstract fun bindMainActivityViewModels(mainActivityViewModel: MainActivityViewModel): ViewModel
    ......
  }

Hilt更简单:

class JokesViewModel @ViewModelInject constructor(
    private val jokesRepository: JokesRepository,
    private val netWorkUtils: NetWorkUtils,
    @Assisted private val savedStateHandle: SavedStateHandle
) : ViewModel() {
        ........
}

使用@ViewModelInject即可,JokesRepositoryNetWorkUtils都是由Hilt注入的。

Hilt将在后台生成相应的工厂类和东西。

这里有个@Assisted需要注意下:

因为在这之前ViewModel中注入SavedStateHandle是比较麻烦的,由于@AssistedInject.Factory修饰接口再通过@AssistedInject注入ViewModel,最后还要通过@AssistedModule`中添加.....太太太麻烦了

看下Hilt@Assisted描述:

/**
 * Marks a parameter in a {@link androidx.hilt.lifecycle.ViewModelInject}-annotated constructor
 * or a {@link androidx.hilt.work.WorkerInject}-annotated constructor to be assisted
 * injected at runtime via a factory.
 */
// TODO(danysantiago): Remove and replace with dagger.assisted.
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Assisted {
}

意思是Worker通过@WorkerInject构造函数注入时要通过@Assisted修饰ContextWorkerParameters

类似:

class ExampleWorker @WorkerInject constructor(
  @Assisted appContext: Context,
  @Assisted workerParams: WorkerParameters,
  workerDependency: WorkerDependency
) : Worker(appContext, workerParams) { ... }

简单例子

利用Hilt写个超简单的请求列表的小例子:Github

image

总结

  • 不用手动创建Component.
  • 不用手动调用inject()进行字段注入.
  • 不用在Application中保存component.
  • 提供一些Scope管理他们的生命周期,只能在对应的范围内进行使用。
  • 提供了一些默认依赖, 比如Context.

以上就是Dagger Hilt简单上手,

目前Hilt还处于alpha状态,依赖kapt,等KSP成熟之后预计效率会有进一步提升。

当然koin玩起来更舒心。

参考

https://developer.android.com/training/dependency-injection/hilt-android#not-supported

https://developer.android.com/training/dependency-injection/hilt-android

http://joebirch.co/android/exploring-dagger-hilt-application-level-code-generation/

https://www.techyourchance.com/dagger-hilt-entry-point/

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