MVVM 架构演进(二) —— DataBinding 实现原理

前言

我们知道 DataBinding 在 MVVM 架构中起到了不可或缺的作用, 它是削弱 View 层与 ViewModel 之间耦合的重中之重, 那么它是如何做到当数据变更时便能够直接推送到 View 中的呢?

我们带着这个问题去探索一下 DataBinding 的工作流程.

一. ViewDataBinding 的创建

class MainFragment : Fragment() {

    ......
    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View {
        // 调用了 MainFragmentBinding.inflate 获取到了 MainFragmentBinding 对象
        val dataBinding: MainFragmentBinding = MainFragmentBinding.inflate(inflater, container, false)
        dataBinding.view = this
        dataBinding.viewmodel = ViewModelProviders.of(this).get(MainViewModel::class.java)
        return dataBinding.root
    }

}

ViewBinding 的实例化是通过生成类 MainFragmentBinding.inflate 完成的, 我们继续追踪

public abstract class MainFragmentBinding extends ViewDataBinding {
  
  @NonNull
  public static MainFragmentBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot) {
    // 1. 调用 DataBindingUtil.getDefaultComponent 获取了默认的 DataBindingComponent 对象, 为空
    // 调用了重载方法
    return inflate(inflater, root, attachToRoot, DataBindingUtil.getDefaultComponent());
  }

  @NonNull
  public static MainFragmentBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup root, boolean attachToRoot, @Nullable DataBindingComponent component) {
    // 将任务分发给 DataBindingUtil.inflate
    return DataBindingUtil.<MainFragmentBinding>inflate(
             inflater,
             com.sharry.demo.mvvm.R.layout.main_fragment, 
             root, 
             attachToRoot, 
             component
           );
  }
    
}

好的, 可以看到 MainFragmentBinding.inflate 会转交给 DataBindUtil 去执行 inflate 操作, 这里已经可以看到这个 ViewDataBinding 对应的 xml 布局文件了

接下来我们看看这个 DataBindUtil.inflate 做了什么操作

public class DataBindingUtil {
    
    public static <T extends ViewDataBinding> T inflate(
            @NonNull LayoutInflater inflater, int layoutId, @Nullable ViewGroup parent,
            boolean attachToParent, @Nullable DataBindingComponent bindingComponent) {
        // 判断是否作为子孩子填充到父容器
        final boolean useChildren = parent != null && attachToParent;
        // 判断插入起始位置
        final int startChildren = useChildren ? parent.getChildCount() : 0;
        // 1. 构造视图
        final View view = inflater.inflate(layoutId, parent, attachToParent);
        // 2. 执行视图的绑定
        if (useChildren) {
            // 与新插入的的视图进行绑定
            return bindToAddedViews(bindingComponent, parent, startChildren, layoutId);
        } else {
            // 与新创建的 View 进行绑定
            return bind(bindingComponent, view, layoutId);
        }
    }
    
    private static <T extends ViewDataBinding> T bindToAddedViews(DataBindingComponent component,
            ViewGroup parent, int startChildren, int layoutId) {
        // 记录当前父容器子 View 的数量
        final int endChildren = parent.getChildCount();
        // 计算新插入的数量(一般为 1, 但新插入的视图为 <merge></merge> 标签就不好说了)
        final int childrenAdded = endChildren - startChildren;
        if (childrenAdded == 1) {
            // 2.1 与单 view 进行绑定
            final View childView = parent.getChildAt(endChildren - 1);
            return bind(component, childView, layoutId);
        } else {
            // 2.2 与多 view 进行绑定
            final View[] children = new View[childrenAdded];
            for (int i = 0; i < childrenAdded; i++) {
                children[i] = parent.getChildAt(i + startChildren);
            }
            return bind(component, children, layoutId);
        }
    }
    
    private static DataBinderMapper sMapper = new DataBinderMapperImpl();
    
    // 执行多 view 的绑定
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View[] roots,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, roots, layoutId);
    }

    // 执行单 View 的绑定
    static <T extends ViewDataBinding> T bind(DataBindingComponent bindingComponent, View root,
            int layoutId) {
        return (T) sMapper.getDataBinder(bindingComponent, root, layoutId);
    }
    
}

好的, 可以看到 DataBindingUtil.inflate 的主要操作有两个

  • 实例化 xml 布局为 View
  • 对新的 view 构建执行绑定, 获取其对应的 ViewDataBinding 对象

好的, 接下来我们看看他是如何获取 ViewDataBinding 的

// 这个 DataBinderMapperImpl 是 AndroidX 依赖包中的类
public class DataBinderMapperImpl extends MergedDataBinderMapper {
  DataBinderMapperImpl() {
    // 这个 DataBinderMapperImpl 是编译时的生成类
    addMapper(new com.sharry.demo.mvvm.DataBinderMapperImpl());
  }
}

public class MergedDataBinderMapper extends DataBinderMapper {
    
    private List<DataBinderMapper> mMappers = new CopyOnWriteArrayList<>();

    @Override
    public ViewDataBinding getDataBinder(DataBindingComponent bindingComponent, View view,
            int layoutId) {
        // 在 DataBinderMapperImpl 中添加了一个 DataBinderMapperImpl 对象
        for(DataBinderMapper mapper : mMappers) {
            // 因此这里调用的是 DataBinderMapperImpl.getDataBinder
            ViewDataBinding result = mapper.getDataBinder(bindingComponent, view, layoutId);
            if (result != null) {
                return result;
            }
        }
        ......
        return null;
    }

}

public class DataBinderMapperImpl extends DataBinderMapper {

  private static final int LAYOUT_MAINFRAGMENT = 1;
  private static final SparseIntArray INTERNAL_LAYOUT_ID_LOOKUP = new SparseIntArray(1);

  static {
    // 静态代码块中, 添加了我们使用了 databinding 的布局
    INTERNAL_LAYOUT_ID_LOOKUP.put(com.sharry.demo.mvvm.R.layout.main_fragment, LAYOUT_MAINFRAGMENT);
  }

  @Override
  public ViewDataBinding getDataBinder(DataBindingComponent component, View view, int layoutId) {
    int localizedLayoutId = INTERNAL_LAYOUT_ID_LOOKUP.get(layoutId);
    if(localizedLayoutId > 0) {
      // 使用 DataBinding 的 xml 要求使用 <layout></layout> 标签
      // 因此 DataBinding 的 xml 解析器会为内部的 view 注入一个 TAG
      final Object tag = view.getTag();
      ......
      switch(localizedLayoutId) {
        case  LAYOUT_MAINFRAGMENT: {
          if ("layout/main_fragment_0".equals(tag)) {
            // 返回了一个 MainFragmentBindingImpl 对象, 这也是编译时生成的
            return new MainFragmentBindingImpl(component, view);
          }
          throw new IllegalArgumentException("The tag for main_fragment is invalid. Received: " + tag);
        }
      }
    }
    return null;
  }
  
}

好的, 可以看到最终我们获取到了 MainFragmentBindingImpl 这个对象, 这便是 ViewDataBinding 的实现类了, 它也是编译时生成的

流程回顾

image.png

这里我们还看没有看到 View 与 ViewModel 中的数据关联的过程, 接下来我们顺着 MainFragmentBindingImpl 的实例化继续探索

二. ViewDataBinding 的初始化

public class MainFragmentBindingImpl extends MainFragmentBinding implements com.sharry.demo.mvvm.generated.callback.OnClickListener.Listener {
    
    public MainFragmentBindingImpl(@Nullable androidx.databinding.DataBindingComponent bindingComponent, @NonNull View root) {
        this(bindingComponent, root, mapBindings(bindingComponent, root, 2, sIncludes, sViewsWithIds));
    }
    
    private MainFragmentBindingImpl(androidx.databinding.DataBindingComponent bindingComponent, View root, Object[] bindings) {
        // 将布局内的视图添加到父类中暂存
        super(bindingComponent, root, 1
            , (android.widget.Button) bindings[1]
            , (androidx.constraintlayout.widget.ConstraintLayout) bindings[0]
            );
        // 清空 tag
        this.btnCenter.setTag(null);
        this.main.setTag(null);
        // 将根视图标记为 databinding 的视图
        setRootTag(root);
        // 重置数据
        invalidateAll();
    }
    
}

好的, 可以看到 MainFragmentBindingImpl 中保存了我们 xml 的视图, 并且根据我们的设置创建了监听器

感觉距离 View 绑定 Observable 又近了一步, 定位到 invalidateAll 这个方法, 我们看看它做了什么

public class MainFragmentBindingImpl extends MainFragmentBinding implements com.sharry.demo.mvvm.generated.callback.OnClickListener.Listener {
    
    @Override
    public void invalidateAll() {
        synchronized(this) {
                mDirtyFlags = 0x8L;
        }
        requestRebind();
    } 
    
    private static final boolean USE_CHOREOGRAPHER = SDK_INT >= 16;
    
    protected void requestRebind() {
        // 初始化状态为 null
        if (mContainingBinding != null) {
            ......
        } else {
            ......
            if (USE_CHOREOGRAPHER) {
                // 投递到下一帧执行 callback, 最终还是会调用到 mRebindRunnable
                mChoreographer.postFrameCallback(mFrameCallback);
            } else {
                // 通过 Handler 直接 post 到消息队列
                mUIThreadHandler.post(mRebindRunnable);
            }
        }
    }
}

好的, 可以看到, invalidateAll 操作, 最终会回调 mRebindRunnable, 接下来我们它的实现

public abstract class ViewDataBinding extends BaseObservable {
    
    private final Runnable mRebindRunnable = new Runnable() {
        @Override
        public void run() {
            ......
            executePendingBindings();
        }
    };
   
    public void executePendingBindings() {
        // 判断当前 ViewDataBinding 是否持有其他的引用, 初始时是没有的
        if (mContainingBinding == null) {
            // 调用了 executeBindingsInternal 进行后续操作
            executeBindingsInternal();
        } else {
            // ......
        }
    }

    private void executeBindingsInternal() {
        // 若当前存在正在执行的任务, 则将本次操作投递到下一帧执行
        if (mIsExecutingPendingBindings) {
            requestRebind();
            return;
        }
        ......
        mIsExecutingPendingBindings = true;
        mRebindHalted = false;
        ......// 通知外界, 执行了 rebind 操作
        if (!mRebindHalted) {
            // 执行 Binding 操作
            executeBindings();
        }
        mIsExecutingPendingBindings = false;
    }
 
}

好的, 可以看到最终看到了它调用了 ViewDataBinding.executeBindings 执行 Binding 操作

三. View 与 ObservableFiled 的绑定

这个方法是交由子类重写的, 我们探究一下 MainFragmentBindingImpl 中是如何实现的

public class MainFragmentBindingImpl extends MainFragmentBinding implements com.sharry.demo.mvvm.generated.callback.OnClickListener.Listener {
    
    
    @Override
    protected void executeBindings() {
        ......
        // 获取 ViewModel, 因为这个方法是 post 到 MessageQueue 中执行的
        // 此时 mViewModel 我们已经通过 setViewModel 赋值了
        com.sharry.demo.mvvm.ui.main.MainViewModel viewmodel = mViewmodel;
        // 这个便是 ViewModel.中的 messageText 的引用
        androidx.databinding.ObservableField<java.lang.String> viewmodelMessageText = null;
        java.lang.String viewmodelMessageTextGet = null;

        if ((dirtyFlags & 0xdL) != 0) {
                if (viewmodel != null) {
                    // 1. 获取我们定义的 ObservableField 实例对象 
                    viewmodelMessageText = viewmodel.getMessageText();
                }
                // 2. 让 本地属性为 0 的控制与这个 viewmodelMessageText 相互注册
                updateRegistration(0, viewmodelMessageText);
                // 3. 获取 ObservableField 中的值
                if (viewmodelMessageText != null) {
                    viewmodelMessageTextGet = viewmodelMessageText.get();
                }
        }
        ......
    }
    
}

通过这段代码可以确定 View 与 Observable 的绑定操作是通过 updateRegistration 进行绑定的, 接下来我们看看具体的实现

public abstract class ViewDataBinding extends BaseObservable {
    
    private static final CreateWeakListener CREATE_PROPERTY_LISTENER = new CreateWeakListener() {
        @Override
        public WeakListener create(ViewDataBinding viewDataBinding, int localFieldId) {
            return new WeakPropertyListener(viewDataBinding, localFieldId).getListener();
        }
    };
    
    protected boolean updateRegistration(int localFieldId, Observable observable) {
        // 更新注册器
        return updateRegistration(localFieldId, observable, CREATE_PROPERTY_LISTENER);
    }
 
    private WeakListener[] mLocalFieldObservers;
 
    private boolean updateRegistration(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        ......
        // 从控件观察者表中获取 localFieldId 对应的观察者
        WeakListener listener = mLocalFieldObservers[localFieldId];
        // 首次进行注册操作, 故为 null
        if (listener == null) {
            //  调用 registerTo 进行后续操作
            registerTo(localFieldId, observable, listenerCreator);
            return true;
        }
        ......
        return true;
    }
    
    protected void registerTo(int localFieldId, Object observable,
            CreateWeakListener listenerCreator) {
        ......
        
        WeakListener listener = mLocalFieldObservers[localFieldId];
        if (listener == null) {
            // 1. 创建该控件对应的观察者, 即 WeakPropertyListener.getListener() 
            listener = listenerCreator.create(this, localFieldId);
            // 缓存到观察者表中
            mLocalFieldObservers[localFieldId] = listener;
            ......
        }
        // 2. 为 这个观察者 绑定要观察的对象, 即 ViewModel 中我们定义的 ObservableFiled 属性
        listener.setTarget(observable);
    }
    
}

好的, 可以看到这是一个典型的观察者设计模式

  • ViewDataBinding 为 View 创建了一个观察者对象, 即 WeakListener, 用于监听被观察者
  • 调用 WeakListener.setTarget 绑定要观察的对象, 即设置被观察者

接下来我们看看这个 WeakListener 的创建过程, 以及它与 ObservableFiled 的绑定

一) 创建观察者

public abstract class ViewDataBinding extends BaseObservable {
    
    private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
    
        final WeakListener<Observable> mListener;

        public WeakPropertyListener(ViewDataBinding binder, int localFieldId) {
            // 创建了 WeakListener
            mListener = new WeakListener<Observable>(binder, localFieldId, this);
        }
                
        @Override
        public WeakListener<Observable> getListener() {
            return mListener;
        }
        
        @Override
        public void addListener(Observable target) {
            // 调用了 Observable.addOnPropertyChangedCallback, 为被观察者订阅一个观察者
            target.addOnPropertyChangedCallback(this);
        }
    }
    
    private static class WeakListener<T> extends WeakReference<ViewDataBinding> {
        
        private final ObservableReference<T> mObservable;
        protected final int mLocalFieldId;
        private T mTarget;

        public WeakListener(ViewDataBinding binder, int localFieldId,
                ObservableReference<T> observable) {
            super(binder, sReferenceQueue);
            mLocalFieldId = localFieldId;
            // 即 WeakPropertyListener 对象
            mObservable = observable;
        }

        public void setTarget(T object) {
            ......
            // 获取要观察的对象
            mTarget = object;
            if (mTarget != null) {
                // 调用 WeakPropertyListener.addListener 
                mObservable.addListener(mTarget);
            }
        }
    }
}

好的, 可以观察者 WeakListener 是由 WeakPropertyListener 在构造函数中实例化的

  • WeakListener.setTarget 方法会回调 WeakPropertyListener.addListener 为被观察者 Observable 订阅观察者

这里可以看到虽然 ViewDataBinding 中维护的观察者数组 mLocalFieldObservers 是 WeakListener, 但真正的观察者为 WeakPropertyListener, 接下来我们看看 Observable 订阅观察者的过程

二) 观察者的订阅与通知

我们知道我们在 ViewModel 中定义的 Observable 为 ObservableFiled 类型, 其 addOnPropertyChangedCallback 实现为 BaseObservable

public class BaseObservable implements Observable {
    private transient PropertyChangeRegistry mCallbacks;

    public BaseObservable() {
    }

    @Override
    public void addOnPropertyChangedCallback(@NonNull OnPropertyChangedCallback callback) {
        synchronized (this) {
            if (mCallbacks == null) {
                mCallbacks = new PropertyChangeRegistry();
            }
        }
        // 很简单, mCallbacks 为订阅该被对象的观察者集合
        mCallbacks.add(callback);
    }
    
}

可以看到 addOnPropertyChangedCallback 中的操作非常简单, 其内部维护了一个 mCallbacks 对象, 维护了订阅了该对象的观察者

回顾

至此 DataBinding 框架便为我们成功的将 View 与 ViewModel 中的数据绑定了, 他们的关系如下

数据绑定的关系图

四. ObservableField 通知观察者数据更新

当被观察者 Observable 调用 set 方法更新数据时, 便会通知订阅其观察者们, 我们看看它的实现

public class ObservableField<T> extends BaseObservableField implements Serializable {

    private T mValue;

    public void set(T value) {
        if (value != mValue) {
            mValue = value;
            // 通知观察者数据变更了
            notifyChange();
        }
    }
    
}
public class BaseObservable implements Observable {
    
    public void notifyChange() {
        synchronized (this) {
            if (mCallbacks == null) {
                return;
            }
        }
        // 通知观察者数据变更
        mCallbacks.notifyCallbacks(this, 0, null);
    }
    
}

可以看到 notifyChange 将任务分发给了 PropertyChangeRegistry.notifyCallbacks 我们看看它的实现

public class CallbackRegistry<C, T, A> implements Cloneable {
 
    private List<C> mCallbacks = new ArrayList<C>();   
 
    public synchronized void notifyCallbacks(T sender, int arg, A arg2) {
        ......
        notifyRecurse(sender, arg, arg2);
        ......
    }
   
    private void notifyRecurse(T sender, int arg, A arg2) {
        ......
        notifyCallbacks(sender, arg, arg2, startCallbackIndex, callbackCount, 0);
    }
    
    private final NotifierCallback<C, T, A> mNotifier;
    
    private void notifyCallbacks(T sender, int arg, A arg2, final int startIndex,
            final int endIndex, final long bits) {
        long bitMask = 1;
        // 遍历订阅的观察者
        for (int i = startIndex; i < endIndex; i++) {
            if ((bits & bitMask) == 0) {
                // 通过 NotifierCallback 推送数据
                mNotifier.onNotifyCallback(mCallbacks.get(i), sender, arg, arg2);
            }
            bitMask <<= 1;
        }
    }

}

可看到推送数据的操作, 是由 NotifierCallback 完成的, 它是实例化是在 PropertyChangeRegistry 构造的时候进行的

public class PropertyChangeRegistry extends
        CallbackRegistry<Observable.OnPropertyChangedCallback, Observable, Void> {

    private static final CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void> NOTIFIER_CALLBACK = new CallbackRegistry.NotifierCallback<Observable.OnPropertyChangedCallback, Observable, Void>() {
        @Override
        public void onNotifyCallback(Observable.OnPropertyChangedCallback callback, Observable sender,
                int arg, Void notUsed) {
            // 调用了 WeakPropertyListener.onPropertyChanged 推送数据
            callback.onPropertyChanged(sender, arg);
        }
    };

    public PropertyChangeRegistry() {
        super(NOTIFIER_CALLBACK);
    }
            
}

可以看到 NotifierCallback 的实现类为 PropertyChangeRegistry.NOTIFIER_CALLBACK, 其内部调用了 WeakPropertyListener.onPropertyChanged 推送数据

public abstract class ViewDataBinding extends BaseObservable {
    
    private static class WeakPropertyListener extends Observable.OnPropertyChangedCallback
            implements ObservableReference<Observable> {
        
        @Override
        public void onPropertyChanged(Observable sender, int propertyId) {
            ViewDataBinding binder = mListener.getBinder();
            ......
            // 这里调用了 ViewDataBinding.handleFieldChange 对 View 进行赋值操作
            binder.handleFieldChange(mListener.mLocalFieldId, sender, propertyId);
        }
                    
    }
    
    private void handleFieldChange(int mLocalFieldId, Object object, int fieldId) {
        ......
        // 1. 调用了子类的 onFieldChange
        boolean result = onFieldChange(mLocalFieldId, object, fieldId);
        // 2. 返回 true, 调用 requestRebind, 这会重新执行 MainFragmentBindingImpl.executeBindings
        if (result) {
            requestRebind();
        }
    }
    
}

public class MainFragmentBindingImpl extends MainFragmentBinding implements com.sharry.demo.mvvm.generated.callback.OnClickListener.Listener {

    @Override
    protected boolean onFieldChange(int localFieldId, Object object, int fieldId) {
        switch (localFieldId) {
            case 0 :
                return onChangeViewmodelMessageText((androidx.databinding.ObservableField<java.lang.String>) object, fieldId);
        }
        return false;
    }
    
    private boolean onChangeViewmodelMessageText(androidx.databinding.ObservableField<java.lang.String> ViewmodelMessageText, int fieldId) {
        if (fieldId == BR._all) {
            synchronized(this) {
                    mDirtyFlags |= 0x1L;
            }
            // 1.1 返回 true, 说明需要数据重置
            return true;
        }
        return false;
    }
    
    @Override
    protected void executeBindings() {
        .....
        // 真正的为 View 赋值
        if ((dirtyFlags & 0xdL) != 0) {
            androidx.databinding.adapters.TextViewBindingAdapter.setText(this.btnCenter, viewmodelMessageTextGet);
        }
        ......
    }
    
}

好的, 可以看到观察者收到数据推送后, 最终会重新调用 MainFragmentBindingImpl.executeBindings 为控件更新数据, 至此一次数据推送就完成了

总结

通过上面的分析可知 DataBinding 的工作原理主要有如下三个重要的步骤

  • ViewDataBinding 的构建
    • 解析 xml 为 view 视图
    • 构建视图对应的 ViewDataBinding
  • ViewDataBinidng 处理视图与 ViewModel 中 Observable 的绑定
    • 为 view 创建其对应的 WeakListener
    • WeakListener 的实现由 WeakPropertyListener 实现
    • 将 WeakPropertyListener 当做观察者, 将其添加到 Observable 的订阅集合中
  • 当 Observable 的数据变更时, 便会推送给其订阅者集合
    • 调用 WeakPropertyListener.onPropertyChanged 处理数据变更
    • 通过 ID 找到 WeakPropertyListener 对应的 View
    • 通过 ViewDataBinding 的生成实现类的 executeBindings 更新指定 view 的数据

可以看到这是一个典型的观察者的应用场景, TextView 为观察者, Observable 为被观察者, 数据变更时便会推送给观察者更新数据, 当然这只是一个最基础的应用场景, 其他的使用场景可以类比推理

问题的思考

为什么 <layout> 标签, 会自动给 view 增加 tag

  • DataBinding 定义了 xml 解析器, 会给 view 增加 tag

为什么 要用 WeakListener 弱引用类型接口?

  • WeakListener 数组是被 ViewDataBinding 持有的, ViewDataBinding 在使用时会被 View 层持有
  • 当 WeakListener 被添加到数据源 ObservableFiled 中的订阅集合中时, 会被 ViewModel 中的数据源属性持有

我们知道 ViewModel 的移植性是非常强的, 因此若该 ViewModel 被另一个生命周期更长的 View 持有, 导致 ViewModel 的生命周期长于当前 View, 这会导致当前 View 无法释放而造成内存泄漏

WeakListener 使用弱引用, 当内存即将溢出时, 会无视弱引用将该对象回收, 相当于做了一层保护措施, 但这并不意味着我们在开发过程中可以肆意妄为, 代码的质量还是要保证的, 内存泄漏更是需要体验预判

DataBinding 设计上的优劣

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

推荐阅读更多精彩内容