React Native性能、升級、特定平臺(tái)代碼


title: React Native 學(xué)習(xí)筆記--進(jìn)階(五)--性能、升級版本、特定平臺(tái)代碼
tags: React Native
categories: React Native
description:


React Native 進(jìn)階(五)--性能、升級版本、特定平臺(tái)代碼

性能

使用React Native替代基于WebView的框架來開發(fā)App的一個(gè)強(qiáng)有力的理由,就是為了使App可以達(dá)到每秒60幀(足夠流暢),并且能有類似原生App的外觀和手感。但是,還是有一些地方有所欠缺,以及在某些場合React Native還不能夠替你決定如何進(jìn)行優(yōu)化,因此人工的干預(yù)依然是必要的。

關(guān)于“幀”你所需要知道的

視頻中逼真的動(dòng)態(tài)效果其實(shí)是一種幻覺,這種幻覺是由一組靜態(tài)的圖片以一個(gè)穩(wěn)定的速度快速變化所產(chǎn)生的。我們把這組圖片中的每一張圖片叫做一幀,而每秒鐘顯示的幀數(shù)直接的影響了視頻(或者說用戶界面)的流暢度和真實(shí)感。iOS設(shè)備提供了每秒60的幀率,這就留給了開發(fā)者和UI系統(tǒng)大約16.67ms來完成生成一張靜態(tài)圖片(幀)所需要的所有工作。如果在這分派的16.67ms之內(nèi)沒有能夠完成這些工作,就會(huì)引發(fā)‘丟幀’的后果,使界面表現(xiàn)的不夠流暢。

調(diào)出你應(yīng)用的開發(fā)菜單,打開Show FPS Monitor. 你會(huì)注意到有兩個(gè)不同的幀率(JS和UI):

JavaScript 幀率

對大多數(shù)React Native應(yīng)用來說,業(yè)務(wù)邏輯是運(yùn)行在JavaScript線程上的。這是React應(yīng)用所在的線程,也是發(fā)生API調(diào)用,以及處理觸摸事件等操作的線程。更新數(shù)據(jù)到原生支持的視圖是批量進(jìn)行的,并且在事件循環(huán)每進(jìn)行一次的時(shí)候被發(fā)送到原生端,這一步通常會(huì)在一幀時(shí)間結(jié)束之前處理完(一切順利的話)。如果JavaScript線程有一幀沒有及時(shí)響應(yīng),就被認(rèn)為發(fā)生了一次丟幀。 例如:你在一個(gè)復(fù)雜應(yīng)用的根組件上調(diào)用了this.setState,從而導(dǎo)致一次開銷很大的子組件樹的重繪,可想而知,這可能會(huì)花費(fèi)200ms也就是整整12幀的丟失。此時(shí),任何由JavaScript控制的動(dòng)畫都會(huì)卡住。只要卡頓超過100ms,用戶就會(huì)明顯的感覺到。

這種情況經(jīng)常發(fā)生在Navigator的切換過程中:當(dāng)你push一個(gè)新的路由時(shí),JavaScript需要繪制新場景所需的所有組件,以發(fā)送正確的命令給原生端去創(chuàng)建視圖。由于切換是由JavaScript線程所控制,因此經(jīng)常會(huì)占用若干幀的時(shí)間,引起一些卡頓。有的時(shí)候,組件會(huì)在componentDidMount函數(shù)中做一些額外的事情,這甚至可能會(huì)導(dǎo)致頁面切換過程中多達(dá)一秒的卡頓。

另一個(gè)例子是觸摸事件的響應(yīng):如果你正在JavaScript線程處理一個(gè)跨越多個(gè)幀的工作,你可能會(huì)注意到TouchableOpacity的響應(yīng)被延遲了。這是因?yàn)镴avaScript線程太忙了,不能夠處理主線程發(fā)送過來的原始觸摸事件。結(jié)果TouchableOpacity就不能及時(shí)響應(yīng)這些事件并命令主線程的頁面去調(diào)整透明度了。

主線程 (也即UI線程) 幀率

很多人會(huì)注意到,NavigatorIOS的性能要比Navigator好的多。原因就是它的切換動(dòng)畫是完全在主線程上執(zhí)行的,因此不會(huì)被JavaScript線程上的掉幀所影響。

同樣,當(dāng)JavaScript線程卡住的時(shí)候,你仍然可以歡快的上下滾動(dòng)ScrollView,因?yàn)镾crollView運(yùn)行在主線程之上(盡管滾動(dòng)事件會(huì)被分發(fā)到JS線程,但是接收這些事件對于滾動(dòng)這個(gè)動(dòng)作來說并不必要)。

性能問題的常見原因

console.log語句

在運(yùn)行打好了離線包的應(yīng)用時(shí),控制臺(tái)打印語句可能會(huì)極大地拖累JavaScript線程。注意有些第三方調(diào)試庫也可能包含控制臺(tái)打印語句,比如redux-logger,所以在發(fā)布應(yīng)用前請務(wù)必仔細(xì)檢查,確保全部移除。

有個(gè)babel插件可以幫你移除所有的console.*調(diào)用。首先需要使用npm install babel-plugin-transform-remove-console --save來安裝,然后在項(xiàng)目根目錄下編輯(或者是新建)一個(gè)名為·.babelrc`的文件,在其中加入:

{
  "env": {
    "production": {
      "plugins": ["transform-remove-console"]
    }
  }
}

這樣在打包發(fā)布時(shí),所有的控制臺(tái)語句就會(huì)被自動(dòng)移除,而在調(diào)試時(shí)它們?nèi)匀粫?huì)被正常調(diào)用。

開發(fā)模式 (dev=true)

JavaScript線程的性能在開發(fā)模式下是很糟糕的。這是不可避免的,因?yàn)橛性S多工作需要在運(yùn)行的時(shí)候去做,譬如使你獲得良好的警告和錯(cuò)誤信息,又比如驗(yàn)證屬性類型(propTypes)以及產(chǎn)生各種其他的警告。

緩慢的導(dǎo)航器(Navigator)切換

Navigator的動(dòng)畫是由JavaScript線程所控制的。想象一下“從右邊推入”這個(gè)場景的切換:每一幀中,新的場景從右向左移動(dòng),從屏幕右邊緣開始,最終移動(dòng)到x軸偏移為0的屏幕位置。切換過程中的每一幀,JavaScript線程都需要發(fā)送一個(gè)新的x軸偏移量給主線程。如果JavaScript線程卡住了,它就無法處理這項(xiàng)事情,因而這一幀就無法更新,動(dòng)畫就被卡住了。

長遠(yuǎn)的解決方法,其中一部分是要允許基于JavaScript的動(dòng)畫從主線程分離。同樣是上面的例子,我們可以在切換動(dòng)畫開始的時(shí)候計(jì)算出一個(gè)列表,其中包含所有的新的場景需要的x軸偏移量,然后一次發(fā)送到主線程以某種優(yōu)化的方式執(zhí)行。由于JavaScript線程已經(jīng)從更新x軸偏移量給主線程這個(gè)職責(zé)中解脫了出來,因此JavaScript線程中的掉幀就不是什么大問題了 —— 用戶將基本上不會(huì)意識到這個(gè)問題,因?yàn)橛脩舻淖⒁饬?huì)被流暢的切換動(dòng)作所吸引。

不幸的是,這個(gè)方案還沒有被實(shí)現(xiàn)。所以當(dāng)前的解決方案是,在動(dòng)畫的進(jìn)行過程中,利用InteractionManager來選擇性的渲染新場景所需的最小限度的內(nèi)容。

InteractionManager.runAfterInteractions的參數(shù)中包含一個(gè)回調(diào),這個(gè)回調(diào)會(huì)在navigator切換動(dòng)畫結(jié)束的時(shí)候被觸發(fā)(每個(gè)來自于Animated接口的動(dòng)畫都會(huì)通知InteractionManager)。

你的場景組件看上去應(yīng)該是這樣的:

class ExpensiveScene extends React.Component {
  constructor(props, context) {
    super(props, context);
    this.state = {renderPlaceholderOnly: true};
  }

  componentDidMount() {
    InteractionManager.runAfterInteractions(() => {
      this.setState({renderPlaceholderOnly: false});
    });
  }

  render() {
    if (this.state.renderPlaceholderOnly) {
      return this._renderPlaceholderView();
    }

    return (
      <View>
        <Text>Your full view goes here</Text>
      </View>
    );
  }

  _renderPlaceholderView() {
    return (
      <View>
        <Text>Loading...</Text>
      </View>
    );
  }
};

你不必被限制在僅僅是做一些loading指示的渲染,你也可以繪制部分的頁面內(nèi)容 —— 例如:當(dāng)你加載Facebook應(yīng)用的時(shí)候,你會(huì)看見一個(gè)灰色方形的消息流的占位符,是將來用來顯示文字的地方。如果你正在場景中繪制地圖,那么最好在場景切換完成之前,顯示一個(gè)灰色的占位頁面或者是一個(gè)轉(zhuǎn)動(dòng)的動(dòng)畫,因?yàn)榍袚Q過程的確會(huì)導(dǎo)致主線程的掉幀。

ListView初始化渲染太慢以及列表過長時(shí)滾動(dòng)性能太差

這是一個(gè)頻繁出現(xiàn)的問題。因?yàn)閕OS配備了UITableView,通過重用底層的UIViews實(shí)現(xiàn)了非常高性能的體驗(yàn)。用React Native實(shí)現(xiàn)相同效果的工作仍正在進(jìn)行中,但是在此之前,我們有一些可用的方法來稍加改進(jìn)性能以滿足我們的需求。

initialListSize

這個(gè)屬性定義了在首次渲染中繪制的行數(shù)。如果我們關(guān)注于快速的顯示出頁面,可以設(shè)置initialListSize為1,然后我們會(huì)發(fā)現(xiàn)其他行在接下來的幀中被快速繪制到屏幕上。而每幀所顯示的行數(shù)由pageSize所決定。

pageSize

在初始渲染也就是initialListSize被使用之后,ListView將利用pageSize來決定每一幀所渲染的行數(shù)。默認(rèn)值為1 —— 但是如果你的頁面很小,而且渲染的開銷不大的話,你會(huì)希望這個(gè)值更大一些。稍加調(diào)整,你會(huì)發(fā)現(xiàn)它所起到的作用。

scrollRenderAheadDistance

“在將要進(jìn)入屏幕某些區(qū)域中先渲染行,距離按像素計(jì)算”

如果我們有一個(gè)2000個(gè)元素的列表,并且立刻全部渲染出來的話,無論是內(nèi)存還是計(jì)算資源都會(huì)顯得很匱乏。還很可能導(dǎo)致非常可怕的阻塞。因此scrollRenderAheadDistance允許我們來指定一個(gè)超過視野范圍之外所需要渲染的行數(shù)。

removeClippedSubviews

“當(dāng)這一選項(xiàng)設(shè)置為true的時(shí)候,超出屏幕的子視圖(同時(shí)overflow值為hidden)會(huì)從它們原生的父視圖中移除。這個(gè)屬性可以在列表很長的時(shí)候提高滾動(dòng)的性能。默認(rèn)為true。(0.14版本前默認(rèn)為false)”

這是一個(gè)應(yīng)用在長列表上極其重要的優(yōu)化。Android上,overflow值總是hidden的,所以你不必?fù)?dān)心沒有設(shè)置它。而在iOS上,你需要確保在行容器上設(shè)置了overflow: hidden。

我的組件渲染太慢,我不需要立即顯示全部

這在初次瀏覽ListView時(shí)很常見,適當(dāng)?shù)氖褂盟谦@得穩(wěn)定性能的關(guān)鍵。就像之前所提到的,它可以提供一些手段在不同幀中來分開渲染頁面,稍加改進(jìn)就可以滿足你的需求。此外要記住的是,ListView也可以橫向滾動(dòng)。

在重繪一個(gè)幾乎沒有什么變化的頁面時(shí),JS幀率嚴(yán)重降低

如果你正在使用一個(gè)ListView,你必須提供一個(gè)rowHasChanged函數(shù),它通過快速的算出某一行是否需要重繪,來減少很多不必要的工作。如果你使用了不可變的數(shù)據(jù)結(jié)構(gòu),這項(xiàng)工作就只需檢查其引用是否相等。

同樣的,你可以實(shí)現(xiàn)shouldComponentUpdate函數(shù)來指明在什么樣的確切條件下,你希望這個(gè)組件得到重繪。如果你編寫的是純粹的組件(返回值完全由props和state所決定),你可以利用PureRenderMixin來為你做這個(gè)工作。再強(qiáng)調(diào)一次,不可變的數(shù)據(jù)結(jié)構(gòu)在提速方面非常有用 —— 當(dāng)你不得不對一個(gè)長列表對象做一個(gè)深度的比較,它會(huì)使重繪你的整個(gè)組件更加快速,而且代碼量更少。

由于在JavaScript線程中同時(shí)做很多事情,導(dǎo)致JS線程掉幀

“導(dǎo)航切換極慢”是該問題的常見表現(xiàn)。在其他情形下,這種問題也可能會(huì)出現(xiàn)。使用InteractionManager是一個(gè)好的方法,但是如果在動(dòng)畫中,為了用戶體驗(yàn)的開銷而延遲其他工作并不太能接受,那么你可以考慮一下使用LayoutAnimation。

Animated的接口一般會(huì)在JavaScript線程中計(jì)算出所需要的每一個(gè)關(guān)鍵幀,而LayoutAnimation則利用了Core Animation,使動(dòng)畫不會(huì)被JS線程和主線程的掉幀所影響。
注意:LayoutAnimation只工作在“一次性”的動(dòng)畫上("靜態(tài)"動(dòng)畫) -- 如果動(dòng)畫可能會(huì)被中途取消,你還是需要使用Animated。

在屏幕上移動(dòng)視圖(滾動(dòng),切換,旋轉(zhuǎn))時(shí),UI線程掉幀

當(dāng)具有透明背景的文本位于一張圖片上時(shí),或者在每幀重繪視圖時(shí)需要用到透明合成的任何其他情況下,這種現(xiàn)象尤為明顯。設(shè)置shouldRasterizeIOS或者renderToHardwareTextureAndroid屬性可以顯著改善這一現(xiàn)象。 注意不要過度使用該特性,否則你的內(nèi)存使用量將會(huì)飛漲。在使用時(shí),要評估你的性能和內(nèi)存使用情況。如果你沒有需要移動(dòng)這個(gè)視圖的需求,請關(guān)閉這一屬性。

使用動(dòng)畫改變圖片的尺寸時(shí),UI線程掉幀

在iOS上,每次調(diào)整Image組件的寬度或者高度,都需要重新裁剪和縮放原始圖片。這個(gè)操作開銷會(huì)非常大,尤其是大的圖片。比起直接修改尺寸,更好的方案是使用transform: [{scale}]的樣式屬性來改變尺寸。比如當(dāng)你點(diǎn)擊一個(gè)圖片,要將它放大到全屏的時(shí)候,就可以使用這個(gè)屬性。

Touchable系列組件不能很好的響應(yīng)

有些時(shí)候,如果我們有一項(xiàng)操作與點(diǎn)擊事件所帶來的透明度改變或者高亮效果發(fā)生在同一幀中,那么有可能在onPress函數(shù)結(jié)束之前我們都看不到這些效果。比如在onPress執(zhí)行了一個(gè)setState的操作,這個(gè)操作需要大量計(jì)算工作并且導(dǎo)致了掉幀。對此的一個(gè)解決方案是將onPress處理函數(shù)中的操作封裝到requestAnimationFrame中:

handleOnPress() {
  // 謹(jǐn)記在使用requestAnimationFrame、setTimeout以及setInterval時(shí)
  // 要使用TimerMixin(其作用是在組件unmount時(shí),清除所有定時(shí)器)
  this.requestAnimationFrame(() => {
    this.doExpensiveAction();
  });
}

分析

你可以利用內(nèi)置的分析器來同時(shí)獲取JavaScript線程和主線程中代碼執(zhí)行情況的詳細(xì)信息。

升級

時(shí)刻將React Native更新到最新的版本,可以獲得更多API、視圖、開發(fā)者工具以及其他一些好東西(官方開發(fā)任務(wù)繁重,人手緊缺,幾乎不會(huì)對舊版本提供維護(hù)支持,所以即便更新可能帶來一些兼容上的變更,但建議開發(fā)者還是盡一切可能第一時(shí)間更新)。由于一個(gè)完整的React Native項(xiàng)目是由Android項(xiàng)目、iOS項(xiàng)目和JavaScript項(xiàng)目組成的,且都打包在一個(gè)npm包中,所以升級可能會(huì)有一些麻煩。以下是目前所需的升級步驟:

更新react-native的node依賴包

打開項(xiàng)目目錄下的package.json文件,然后在dependencies模塊下找到react-native,將當(dāng)前版本號改到最新(或指定)版本號,如:

{
  "name": "reactnativedemo",
  "version": "1.0.0",
  "description": "",
  "main": "index.android.js",
  "scripts": {
      "start": "node node_modules/react-native/local-cli/cli.js start"
  },
  "author": "",
  "license": "ISC",
  "dependencies": {
      "react": "^15.4.1",
      "react-native": "^0.38.0"
  }
}

react-native的npm包的最新版本可以去這里查看,或使用npm info react-native命令查看。

項(xiàng)目的根目錄執(zhí)行:

npm install

安裝最新的React Native版本,成功后可能會(huì)出現(xiàn)如下類似警告:

npm WARN react-native@0.38.0 requires a peer of react@15.4.1 but none was installed.

根據(jù)警告執(zhí)行:

npm install –save react@15.4.1

更新最新的React且項(xiàng)目下package.json 的 dependencies下的react版本會(huì)被修改為 15.4.1

升級項(xiàng)目模板文件

新版本的npm包通常還會(huì)包含一些動(dòng)態(tài)生成的文件,這些文件是在運(yùn)行react-native init創(chuàng)建新項(xiàng)目時(shí)生成的,比如iOS和Android的項(xiàng)目文件。為了使老項(xiàng)目的項(xiàng)目文件也能得到更新(不重新init),你需要在命令行中運(yùn)行:

react-native upgrade

這一命令會(huì)檢查最新的項(xiàng)目模板,然后進(jìn)行如下操作:

  • 如果是新添加的文件,則直接創(chuàng)建。
  • 如果文件和當(dāng)前版本的文件相同,則跳過。
  • 如果文件和當(dāng)前版本的文件不同,則會(huì)提示你一些選項(xiàng):查看兩者的不同,選擇保留你的版本或是用新的模板覆蓋。你可以按下h鍵來查看所有可以使用的命令。

注意:如果你有修改原生代碼,那么在使用upgrade升級前,先備份,再覆蓋。覆蓋完成后,使用比對工具找出差異,將你之前修改的代碼逐步搬運(yùn)到新文件中。

手動(dòng)升級

有時(shí)候React Native的項(xiàng)目結(jié)構(gòu)改動(dòng)較大,此時(shí)還需要手動(dòng)做一些修改,例如從0.13到0.14版本,或是0.28到0.29版本。所以在升級時(shí)請先閱讀一下更新日志,以確定是否需要做一些額外的手動(dòng)修改。

查看版本是否升級成功

執(zhí)行:

react-native -v

通過如上命令來看最新的版本,檢測是否升級成功!

特定平臺(tái)代碼

在制作跨平臺(tái)的App時(shí),多半會(huì)碰到針對不同平臺(tái)編寫不同代碼的需求。最直接的方案就是把組件放置到不同的文件夾下:

/common/components/
/android/components/
/ios/components/

另一個(gè)選擇是根據(jù)平臺(tái)不同在組件的文件命名上加以區(qū)分,如下:

BigButtonIOS.js
BigButtonAndroid.js

但除此以外React Native還提供了另外兩種簡單區(qū)分平臺(tái)的方案:

特定平臺(tái)擴(kuò)展名

React Native會(huì)檢測某個(gè)文件是否具有.ios.或是.android.的擴(kuò)展名,然后根據(jù)當(dāng)前運(yùn)行的平臺(tái)加載正確對應(yīng)的文件。

假設(shè)你的項(xiàng)目中有如下兩個(gè)文件:

BigButton.ios.js
BigButton.android.js

這樣命名組件后你就可以在其他組件中直接引用,而無需關(guān)心當(dāng)前運(yùn)行的平臺(tái)是哪個(gè)。

import BigButton from './components/BigButton';

React Native會(huì)根據(jù)運(yùn)行平臺(tái)的不同引入正確對應(yīng)的組件。

平臺(tái)模塊

React Native提供了一個(gè)檢測當(dāng)前運(yùn)行平臺(tái)的模塊。如果組件只有一小部分代碼需要依據(jù)平臺(tái)定制,那么這個(gè)模塊就可以派上用場。

import { Platform, StyleSheet } from 'react-native';

var styles = StyleSheet.create({
  height: (Platform.OS === 'ios') ? 200 : 100,
});

Platform.OS在iOS上會(huì)返回ios,而在Android設(shè)備或模擬器上則會(huì)返回android。

還有個(gè)實(shí)用的方法是Platform.select(),它可以以Platform.OS為key,從傳入的對象中返回對應(yīng)平臺(tái)的值,見下面的示例:

import { Platform, StyleSheet } from 'react-native';

const styles = StyleSheet.create({
  container: {
    flex: 1,
    ...Platform.select({
      ios: {
        backgroundColor: 'red',
      },
      android: {
        backgroundColor: 'blue',
      },
    }),
  },
});

上面的代碼會(huì)根據(jù)平臺(tái)的不同返回不同的container樣式——iOS上背景色為紅色,而android為藍(lán)色。

這一方法可以接受任何合法類型的參數(shù),因此你也可以直接用它針對不同平臺(tái)返回不同的組件,像下面這樣:

const Component = Platform.select({
  ios: () => require('ComponentIOS'),
  android: () => require('ComponentAndroid'),
})();
<Component />;

檢測Android版本

在Android上,平臺(tái)模塊還可以用來檢測當(dāng)前所運(yùn)行的Android平臺(tái)的版本:

import { Platform } from 'react-native';

if(Platform.Version === 21){
  console.log('Running on Lollipop!');
}

React Native學(xué)習(xí)筆記--進(jìn)階(一)--嵌入到Android原生應(yīng)用中、組件的生命周期、顏色、圖片、觸摸事件
React Native學(xué)習(xí)筆記--進(jìn)階(二)--動(dòng)畫
React Native學(xué)習(xí)筆記--進(jìn)階(三)--定時(shí)器、直接操作(setNativeProps)、調(diào)試
React Native學(xué)習(xí)筆記--進(jìn)階(四)--導(dǎo)航器
React Native學(xué)習(xí)筆記--進(jìn)階(五)--性能、升級、特定平臺(tái)代碼

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 42,976評論 1 369
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 47,958評論 2 373

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