Jetpack ViewBinding

Jetpck 才是真的豪華全家桶

引言

  • 通過(guò)視圖綁定功能专普,可以更輕松地編寫可與視圖交互的代碼。
  • 在模塊中啟用視圖綁定之后码耐,系統(tǒng)會(huì)為該模塊中的每個(gè) XML 布局文件生成一個(gè)綁定類追迟。
  • 綁定類的實(shí)例包含對(duì)在相應(yīng)布局中具有 ID 的所有視圖的直接引用。
  • 在大多數(shù)情況下骚腥,視圖綁定會(huì)替代 findViewById敦间。

整體預(yù)覽

Jetpack ViewBinding 概覽圖

1. 使用說(shuō)明

1.1 環(huán)境配置

1.1.1 版本要求

?ViewBinding 在 Android Studio 3.6 Canary 11 及更高版本中可用。

1.1.2 模塊啟用
//build.gradle
android {
        buildFeatures {
            viewBinding true
        }
    }

1.2 語(yǔ)法說(shuō)明

1.2.1 layout布局文件
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">
    <TextView
        android:id="@+id/title"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"/>
</LinearLayout>

?生成規(guī)則:將 XML 文件的名稱轉(zhuǎn)換為駝峰式大小寫束铭,并在末尾添加“Binding”一詞廓块。比如上面的文件是 item_rv.xml,那么會(huì)生成ItemRvBindiing的綁定類契沫。
?綁定內(nèi)容:生成對(duì)應(yīng)的綁定類均包含對(duì)根視圖以及具有 ID 的所有視圖的引用带猴。如果視圖沒(méi)有添加 ID,則不會(huì)生成對(duì)應(yīng)的綁定類引用懈万。
?根視圖:每個(gè)綁定類還包含一個(gè) getRoot() 方法浓利,用于為相應(yīng)布局文件的根視圖提供直接引用。比如上面的就是LinearLayout根視圖钞速。

1.2.2 layout文件忽略
<LinearLayout
            ...
            tools:viewBindingIgnore="true" >
        ...
    </LinearLayout>

?構(gòu)建優(yōu)化 & 內(nèi)存優(yōu)化:ViewBinding的開(kāi)啟會(huì)對(duì)所有的布局文件進(jìn)行綁定類生成贷掖,如果有些布局文件不需要綁定類生成, 則可以在根布局添加設(shè)置進(jìn)行關(guān)閉渴语。
?Apk瘦身優(yōu)化:在release版本苹威,可以添加 shrinkResources 將沒(méi)有用到的綁定類不打包在apk中。

1.3 場(chǎng)景舉例

1.3.1 Activity
class ViewBindingActivity : AppCompatActivity() {

    private lateinit var binding: ActivityViewBindingBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //傳統(tǒng)方式
//        setContentView(R.layout.activity_view_binding)

        //ViewBinding方式
        binding = ActivityViewBindingBinding.inflate(layoutInflater);
        val view = binding.root
        setContentView(view)

        //ViewBinding操作演示
        binding.rename.setOnClickListener {
            binding.name.text = binding.name.text.toString() + "-rename"
        }
    }
}

三步走

  • 調(diào)用生成的綁定類中包含的靜態(tài) inflate() 方法驾凶。此操作會(huì)創(chuàng)建該綁定類的實(shí)例以供 Activity 使用牙甫。
  • 通過(guò)調(diào)用 getRoot() 方法或使用 Kotlin 屬性語(yǔ)法獲取對(duì)根視圖的引用。
  • 將根視圖傳遞到 setContentView()调违,使其成為屏幕上的活動(dòng)視圖窟哺。
1.3.2 Fragment
class ViewBindingFragmentSample : Fragment() {
    private var _binding: FragmentViewBindingBinding? = null
    private val binding get() = _binding!!

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
    }

    override fun onCreateView(
        inflater: LayoutInflater, container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {
        //傳統(tǒng)方式
//        return inflater.inflate(R.layout.fragment_view_binding, container, false)

        //style 1
        _binding = FragmentViewBindingBinding.inflate(inflater, container, false)
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        //style 2(需要調(diào)用onCreateView中的傳統(tǒng)方式)
//        _binding = FragmentViewBindingBinding.bind(view)
    }

    override fun onDestroyView() {
        super.onDestroyView()
        _binding = null
    }
}

三步走

  • 調(diào)用生成的綁定類中包含的靜態(tài) inflate() 方法。此操作會(huì)創(chuàng)建該綁定類的實(shí)例以供 Fragment 使用技肩。
  • 通過(guò)調(diào)用 getRoot() 方法或使用 Kotlin 屬性語(yǔ)法獲取對(duì)根視圖的引用且轨。
  • onCreateView() 方法返回根視圖(也可以在onViewCreated()進(jìn)行bind()),使其成為屏幕上的活動(dòng)視圖虚婿。
1.3.3 ViewHolder
class RvAdapter(private val mData : List<String>) : RecyclerView.Adapter<RvAdapter.RvViewHolder>() {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RvViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        val binding = ItemRvTextBinding.inflate(inflater)
        return RvViewHolder(binding)
    }

    override fun getItemCount(): Int {
        return mData.size
    }

    override fun onBindViewHolder(holder: RvViewHolder, position: Int) {
        holder.getBinding().title.text = mData[position]
    }

    class RvViewHolder(private val binding: ItemRvTextBinding) : RecyclerView.ViewHolder(binding.root) {
        fun getBinding() : ItemRvTextBinding {
            return binding
        }
    }
}

三步走

  • 調(diào)用生成的綁定類中包含的靜態(tài) inflate() 方法旋奢。此操作會(huì)創(chuàng)建該綁定類的實(shí)例以供 ViewHolder 使用。
  • onCreateViewHolder創(chuàng)建 ViewHolder然痊,使其成為屏幕上的活動(dòng)視圖至朗。
  • onBindViewHolder() 進(jìn)行綁定類的屬性獲取更新。

1.4 ViewBinding類

1.4.1 文件說(shuō)明

1.4.1.1 綁定類路徑
JavaModel:app/build/generated/data_binding_base_class_source_out/buildTypes/out/packageName/databinding

綁定類生成路徑

Layout文件:app/build/intermediates/data_binding_layout_info_type_merge/buildTypes/out

Layout文件生成路徑

1.4.1.2 綁定類內(nèi)容
JavaModel

  • 作用1:根據(jù)layout文件生成視圖剧浸,并與父布局關(guān)聯(lián)(可能)锹引。
  • 作用2:根據(jù)生成的視圖矗钟,綁定子視圖組件的引用。
public final class ActivityViewBindingBinding implements ViewBinding {
  @NonNull
  private final LinearLayout rootView;   //根對(duì)象

  @NonNull
  public final LinearLayout detail;  //只要xml寫了ID嫌变,那么就會(huì)生成對(duì)應(yīng)的引用

  @NonNull
  public final TextView name;

  @NonNull
  public final Button rename;

  @NonNull
  public final RecyclerView rv;

  //私有構(gòu)造函數(shù)真仲,只能用在下面的 bind()方法中,符合最少知道原則
  private ActivityViewBindingBinding(@NonNull LinearLayout rootView, @NonNull LinearLayout detail,
      @NonNull TextView name, @NonNull Button rename, @NonNull RecyclerView rv) {
    this.rootView = rootView;
    this.detail = detail;
    this.name = name;
    this.rename = rename;
    this.rv = rv;
  }

  @Override
  @NonNull
  //根布局可獲取
  public LinearLayout getRoot() {
    return rootView;
  }

  @NonNull
  //inflate 重載方法
  public static ActivityViewBindingBinding inflate(@NonNull LayoutInflater inflater) {
    return inflate(inflater, null, false);
  }

  @NonNull
  //inflate 重載方法:生成視圖并綁定子視圖組件的引用
  public static ActivityViewBindingBinding inflate(@NonNull LayoutInflater inflater,
      @Nullable ViewGroup parent, boolean attachToParent) {
    //生成視圖
    View root = inflater.inflate(R.layout.activity_view_binding, parent, false);
    if (attachToParent) { //是否添加父布局初澎。tip:這個(gè)會(huì)影響這個(gè)layout文件的根視圖參數(shù)是否生效
      parent.addView(root);
    }
    return bind(root); //綁定子視圖組件的引用
  }

  @NonNull
  //綁定子視圖組件的引用
  public static ActivityViewBindingBinding bind(@NonNull View rootView) {
    // The body of this method is generated in a way you would not otherwise write.
    // This is done to optimize the compiled bytecode for size and performance.
    int id;
    missingId: {
      id = R.id.detail;
      //回歸原始秸应,依然通過(guò) findViewById 獲取視圖,并保存保存引用
      LinearLayout detail = rootView.findViewById(id);
      if (detail == null) {
        break missingId;
      }

      id = R.id.name;
      TextView name = rootView.findViewById(id);
      if (name == null) {
        break missingId;
      }

      id = R.id.rename;
      Button rename = rootView.findViewById(id);
      if (rename == null) {
        break missingId;
      }

      id = R.id.rv;
      RecyclerView rv = rootView.findViewById(id);
      if (rv == null) {
        break missingId;
      }

      //返回最終的綁定類
      return new ActivityViewBindingBinding((LinearLayout) rootView, detail, name, rename, rv);
    }
    //視圖解析錯(cuò)誤異常拋出
    String missingId = rootView.getResources().getResourceName(id);
    throw new NullPointerException("Missing required view with ID: ".concat(missingId));
  }
}

Layout文件

  • 作用:為了生成JavaModel對(duì)象
<?xml version="1.0" encoding="utf-8" standalone="yes"?>
<Layout directory="layout" filePath="app/src/main/res/layout/activity_view_binding.xml"
    //因?yàn)橹粏⒂昧薞iewBinding碑宴,所以isBindingData是false
    isBindingData="false" isMerge="false" layout="activity_view_binding"
    modulePackage="com.kejiyuanren.jetpack" rootNodeType="android.widget.LinearLayout">
    //標(biāo)記了Targets信息软啼,為了生成對(duì)應(yīng)的JavaModel
    <Targets>
        <Target tag="layout/activity_view_binding_0" view="LinearLayout">
            <Expressions />
            <location endLine="57" endOffset="14" startLine="1" startOffset="0" />
        </Target>
        <Target id="@+id/name" view="TextView">
            <Expressions />
            <location endLine="14" endOffset="36" startLine="9" startOffset="4" />
        </Target>
        <Target id="@+id/detail" view="LinearLayout">
            <Expressions />
            <location endLine="43" endOffset="18" startLine="22" startOffset="4" />
        </Target>
        <Target id="@+id/rename" view="Button">
            <Expressions />
            <location endLine="41" endOffset="35" startLine="36" startOffset="8" />
        </Target>
        <Target id="@+id/rv" view="androidx.recyclerview.widget.RecyclerView">
            <Expressions />
            <location endLine="55" endOffset="41" startLine="51" startOffset="4" />
        </Target>
    </Targets>
</Layout>
1.4.2 ViewBinding類 原理分析

?分析入口:從databinding-compiler進(jìn)行分析,因?yàn)樵诰幾g環(huán)節(jié)會(huì)被調(diào)用延柠。

分析入口

1.4.2.1 從何而來(lái)祸挪?

結(jié)論:利用res/layout中的xml文件,解析生成元素對(duì)象贞间,進(jìn)行緩存贿条。

(1)LayoutXmlProcessor -> processResources()

public boolean processResources(final ResourceInput input)
            throws ParserConfigurationException, SAXException, XPathExpressionException,
            IOException {
        ……
        //核心處理
        ProcessFileCallback callback = new ProcessFileCallback() {
        ……
        if (input.isIncremental()) {
            //增量編譯
            processIncrementalInputFiles(input, callback);
        } else {
            //全量編譯
            processAllInputFiles(input, callback);
        }
        ……
    }

(2)LayoutXmlProcessor -> processAllInputFiles()

private static void processAllInputFiles(ResourceInput input, ProcessFileCallback callback)
            throws IOException, XPathExpressionException, SAXException,
            ParserConfigurationException {
        ……
        for (File firstLevel : input.getRootInputFolder().listFiles()) {
            if (firstLevel.isDirectory()) {   //是否是路徑
                //文件夾是否為layout開(kāi)頭
                if (LAYOUT_FOLDER_FILTER.accept(firstLevel, firstLevel.getName())) {
                    //創(chuàng)建對(duì)應(yīng)文件夾
                    callback.processLayoutFolder(firstLevel);
                    //noinspection ConstantConditions
                    //遍歷文件夾中的xml文件
                    for (File xmlFile : firstLevel.listFiles(XML_FILE_FILTER)) {
                        //處理布局文件
                        callback.processLayoutFile(xmlFile);
                    }
                } else {
        ……
    }

(3)LayoutXmlProcessor -> processSingleFile()

public boolean processSingleFile(@NonNull RelativizableFile input, @NonNull File output)
            throws ParserConfigurationException, SAXException, XPathExpressionException,
            IOException {
        //解析xml文件
        final ResourceBundle.LayoutFileBundle bindingLayout = LayoutFileParser
                .parseXml(input, output, mResourceBundle.getAppPackage(), mOriginalFileLookup);
        if (bindingLayout != null && !bindingLayout.isEmpty()) {
            //解析出來(lái)的元素對(duì)象進(jìn)行緩存
            mResourceBundle.addLayoutBundle(bindingLayout, true);
            return true;
        }
        return false;
    }

(4)LayoutFileParser -> parseXml()

public static ResourceBundle.LayoutFileBundle parseXml(@NonNull final RelativizableFile input,
            @NonNull final File outputFile, @NonNull final String pkg,
            @NonNull final LayoutXmlProcessor.OriginalFileLookup originalFileLookup)
            throws ParserConfigurationException, IOException, SAXException,
            XPathExpressionException {
        ……
            //解析繼續(xù)
            return parseOriginalXml(
                RelativizableFile.fromAbsoluteFile(originalFile, input.getBaseDir()),
                pkg, encoding);
        ……
    }

(5)LayoutFileParser -> parseOriginalXml()

private static ResourceBundle.LayoutFileBundle parseOriginalXml(
            @NonNull final RelativizableFile originalFile, @NonNull final String pkg,
            @NonNull final String encoding)
            throws IOException {
        ……
            //文件解析(這個(gè)是databing用的,viewbinding的話在實(shí)現(xiàn)中直接return null)
            XMLParser.ElementContext data = getDataNode(root);
            XMLParser.ElementContext rootView = getViewNode(original, root);
           ……
            //生成元素對(duì)象
            ResourceBundle.LayoutFileBundle bundle =
                new ResourceBundle.LayoutFileBundle(
                    originalFile, xmlNoExtension, original.getParentFile().getName(), pkg,
                    isMerge);
            final String newTag = original.getParentFile().getName() + '/' + xmlNoExtension;
            //數(shù)據(jù)解析(這個(gè)是databing用的增热,viewbinding的話在實(shí)現(xiàn)中直接return null)
            parseData(original, data, bundle);
            parseExpressions(newTag, rootView, isMerge, bundle);
            return bundle;
        ……
    }

?
1.4.2.2 途徑哪里整以?

結(jié)論:利用上一節(jié)生成的元素對(duì)象緩存,解析生成中間件layout文件(build目錄下的xml文件)峻仇。

(1)LayoutXmlProcessor -> writeLayoutInfoFiles()

//
public void writeLayoutInfoFiles(File xmlOutDir, JavaFileWriter writer) throws JAXBException {
        //元素對(duì)象集合遍歷
        for (List<ResourceBundle.LayoutFileBundle> layouts : mResourceBundle.getLayoutBundles()
                .values()) {
            for (ResourceBundle.LayoutFileBundle layout : layouts) {
                //利用元素對(duì)象緩存生成layout文件
                writeXmlFile(writer, xmlOutDir, layout);
            }
        }
        ……
    }

(2)LayoutXmlProcessor -> writeXmlFile()

private void writeXmlFile(JavaFileWriter writer, File xmlOutDir,
            ResourceBundle.LayoutFileBundle layout)
            throws JAXBException {
        //生成文件名
        String filename = generateExportFileName(layout);
        //寫文件
        writer.writeToFile(new File(xmlOutDir, filename), layout.toXML());
    }

?
1.4.2.3 去往何處公黑?

結(jié)論:利用上一節(jié)生成的中間件layout文件,解析生成ViewBinding類摄咆。

(1)BaseDataBinder -> init()

init {
        input.filesToConsider
                .forEach {
                    it.inputStream().use {
                         // 將中間件layout中的xml文件 轉(zhuǎn)成 LayoutFileBundle
                        val bundle = ResourceBundle.LayoutFileBundle.fromXML(it)
                        // 緩存進(jìn) ResourceBundle
                        resourceBundle.addLayoutBundle(bundle, true)
                    }
        ……
    }

(2)BaseDataBinder -> generateAll()

fun generateAll(writer : JavaFileWriter) {
        ……
        //根據(jù)文件名進(jìn)行分組排序凡蚜,并進(jìn)行遍歷所有ResourceBundle
        resourceBundle.layoutFileBundlesInSource.groupBy { it.mFileName }.forEach {
            val layoutName = it.key
            val layoutModel = BaseLayoutModel(it.value)
            //BaseLayoutBinderWriter生成
            val binderWriter = BaseLayoutBinderWriter(layoutModel, libTypes)
            //BaseLayoutBinderWriter的解析處理:binderWriter.write(),并寫文件:writer.writeToFile()
            writer.writeToFile(binderWriter.write())
            ……
    }

(3)BaseLayoutBinderWriter -> write()

//解析數(shù)據(jù): createType()吭从,并生成JavaFile:javaFile()
fun write() = javaFile(binderTypeName.packageName(), createType()) {
        addFileComment("Generated by data binding compiler. Do not edit!")
    }

(4)BaseLayoutBinderWriter -> createType()

//解析所有朝蜘,細(xì)節(jié)就不跟了
private fun createType() = classSpec(binderTypeName) {
        superclass(viewDataBinding)
        addModifiers(Modifier.ABSTRACT, Modifier.PUBLIC)
        addFields(createBindingTargetFields())
        addFields(createVariableFields())
        addMethod(createConstructor())
        addMethods(createGettersAndSetters())
        addMethods(createStaticInflaters())
    }

?
原理分析總結(jié)

  • Step1:layout文件(xml資源文件) -> 解析 -> 元素緩存 。
  • Step2:元素緩存 -> 解析 -> layout文件(中間件) 涩金。
  • Step3:layout文件(中間件) -> 解析 -> ViewBinding類 谱醇。

2. 橫向?qū)Ρ?/h1>

2.1 findViewById

與使用 findViewById 相比,視圖綁定具有一些很顯著的優(yōu)點(diǎn):

  • Null 安全:由于視圖綁定會(huì)創(chuàng)建對(duì)視圖的直接引用鸭廷,因此不存在因視圖 ID 無(wú)效而引發(fā) Null 指針異常的風(fēng)險(xiǎn)枣抱。
  • 類型安全:每個(gè)綁定類中的字段均具有與它們?cè)?XML 文件中引用的視圖相匹配的類型熔吗。這意味著不存在發(fā)生類轉(zhuǎn)換異常的風(fēng)險(xiǎn)辆床。

這些差異意味著布局和代碼之間的不兼容將會(huì)導(dǎo)致構(gòu)建在編譯時(shí)(而非運(yùn)行時(shí))失敗。

2.2 DataBinding

視圖綁定和數(shù)據(jù)綁定均會(huì)生成可用于直接引用視圖的綁定類桅狠。但是讼载,視圖綁定旨在處理更簡(jiǎn)單的用例轿秧,與數(shù)據(jù)綁定相比,具有以下優(yōu)勢(shì)

  • 更快的編譯速度:視圖綁定不需要處理注釋咨堤,因此編譯時(shí)間更短菇篡。
  • 易于使用:視圖綁定不需要特別標(biāo)記的 XML 布局文件,因此在應(yīng)用中采用速度更快一喘。在模塊中啟用視圖綁定后驱还,它會(huì)自動(dòng)應(yīng)用于該模塊的所有布局。

反過(guò)來(lái)凸克,與數(shù)據(jù)綁定相比议蟆,視圖綁定也具有以下限制

  • 視圖綁定不支持布局變量或布局表達(dá)式,因此不能用于直接在 XML 布局文件中聲明動(dòng)態(tài)界面內(nèi)容萎战。
  • 視圖綁定不支持雙向數(shù)據(jù)綁定咐容。

推薦:在某些情況下,最好在項(xiàng)目中同時(shí)使用視圖綁定和數(shù)據(jù)綁定蚂维〈亮#可以在需要高級(jí)功能的布局中使用數(shù)據(jù)綁定,而在不需要高級(jí)功能的布局中使用視圖綁定虫啥。

3. 小結(jié)

?ViewBinding相比DataBinding更輕量級(jí)蔚约,不是每次殺雞都需要用牛刀。盡快把 findViewById 忘了吧涂籽!

?

小編的博客系列

Jetpack 全家桶
?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末炊琉,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子又活,更是在濱河造成了極大的恐慌苔咪,老刑警劉巖,帶你破解...
    沈念sama閱讀 207,113評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件柳骄,死亡現(xiàn)場(chǎng)離奇詭異团赏,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)耐薯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評(píng)論 2 381
  • 文/潘曉璐 我一進(jìn)店門舔清,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人曲初,你說(shuō)我怎么就攤上這事体谒。” “怎么了臼婆?”我有些...
    開(kāi)封第一講書人閱讀 153,340評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵抒痒,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我颁褂,道長(zhǎng)故响,這世上最難降的妖魔是什么傀广? 我笑而不...
    開(kāi)封第一講書人閱讀 55,449評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮彩届,結(jié)果婚禮上伪冰,老公的妹妹穿的比我還像新娘。我一直安慰自己樟蠕,他們只是感情好贮聂,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著寨辩,像睡著了一般寂汇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上捣染,一...
    開(kāi)封第一講書人閱讀 49,166評(píng)論 1 284
  • 那天骄瓣,我揣著相機(jī)與錄音,去河邊找鬼耍攘。 笑死榕栏,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蕾各。 我是一名探鬼主播扒磁,決...
    沈念sama閱讀 38,442評(píng)論 3 401
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼在讶,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼蛇捌!你這毒婦竟也來(lái)了磕诊?” 一聲冷哼從身側(cè)響起联予,我...
    開(kāi)封第一講書人閱讀 37,105評(píng)論 0 261
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎尿背,沒(méi)想到半個(gè)月后默赂,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體捻激,經(jīng)...
    沈念sama閱讀 43,601評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡钧排,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評(píng)論 2 325
  • 正文 我和宋清朗相戀三年敦腔,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片恨溜。...
    茶點(diǎn)故事閱讀 38,161評(píng)論 1 334
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡符衔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出糟袁,到底是詐尸還是另有隱情判族,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評(píng)論 4 323
  • 正文 年R本政府宣布项戴,位于F島的核電站形帮,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜沃缘,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評(píng)論 3 307
  • 文/蒙蒙 一躯枢、第九天 我趴在偏房一處隱蔽的房頂上張望则吟。 院中可真熱鬧槐臀,春花似錦、人聲如沸氓仲。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 30,352評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)敬扛。三九已至晰洒,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間啥箭,已是汗流浹背谍珊。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 31,584評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留急侥,地道東北人砌滞。 一個(gè)月前我還...
    沈念sama閱讀 45,618評(píng)論 2 355
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像坏怪,于是被迫代替她去往敵國(guó)和親贝润。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評(píng)論 2 344

推薦閱讀更多精彩內(nèi)容