react-native-update 热更新插件翻译

原文地址:https://github.com/remobile/react-native-update

React Native 更新 (remobile)

更新js版本和app版本

安装

npm install @remobile/react-native-update --save

安装 (Android)

...
include ':react-native-update'
project(':react-native-update').projectDir 
= new File(settingsDir, '../node_modules/@remobile/react-native-update/android')
  • android/app/build.gradle
...
dependencies {
    ...
    compile project(':react-native-update')
}
  • 注册模块 (在 MainActivity.java)
......
import com.remobile.update.RCTUpdateMgr;  // <--- import

......
public class MainApplication extends Application implements ReactApplication {
    private RCTUpdateMgr mUpdateMgr; // <------ add here

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        ......
        @Override
        protected String getJSBundleFile() {
            return mUpdateMgr.getBundleUrl();   // <------ change here
        }

        @Override
        protected List<ReactPackage> getPackages() {
            mUpdateMgr = new RCTUpdateMgr(MainActivity.activity);

            return Arrays.<ReactPackage>asList(
            ......
            mUpdateMgr.getReactPackage(),       // <------ add here
            ......
            );
        }
    };
    ......
}

用法

例子

UpdatePage.js

定义全局变量
'use strict';
import React, { Component } from 'react';
import {
  View,
  StyleSheet,
  Platform,
  Text,
  Image,
  TouchableHighlight
} from 'react-native';
import ReactNative form 'react-native'
import Update form '@remobile/react-native-update'

var
STATUS_GET_VERSION = 0, // 获得版本号状态
STATUS_HAS_VEW_VERSION = 1, // 有新版本状态
STATUS_HAS_NOT_VEW_VERSION = 2, // 没有新版本号状态
STATUS_DOWNLOAD_APK_PROGESS = 3, // 下载apk状态
STATUS_DOWNLOAD_JS_PROGESS = 4, // 下载js状态
STATUS_UNZIP_JS_PROGESS = 5, // 解压js状态
STATUS_GET_VERSION_ERROR = 6, // 获得版本异常状态
STATUS_DOWNKOAD_APK_ERROR = 7, // 下载apk异常状态
STATUS_DOWNKOAD_JS_ERROR = 8, // 下载js异常状态
STATUS_UNZIP_JS_ERROR = 9, // 解压js异常状态
STATUS_FAILED_INSTALL_ERROR = 10,  //失败安装异常状态
STATUS_UPDATE_END = 11; //更新结束状态

var
ERROR_NULL = 0,
ERROR_DOWNKOAD_APK = 1, // 异常下载apk
ERROR_DOWNKOAD_JS = 2, // 异常下载js
ERROR_FAILED_INSTALL = 3, // 失败安装
ERROR_UNZIP_JS = 4; // 解压js

var PROGRESS_WIDTH = sr.tw*0.7;
var {Button, ProgressBar} = COMPONENTS;
进度view
var ProgressInfo = React.createClass({
    render() {
        const { progress } = this.props;
        if (progress < 1000) {
            return (
                <View>
                    <Text>{this.props.title} [{progress}%]</Text>
                    <ProgressBar
                        fillStyle={{}}
                        backgroundStyle={{backgroundColor: '#cccccc', borderRadius: 2}}
                        style={{marginTop: 10, width:PROGRESS_WIDTH}}
                        progress={progress/100.0}
                        />
                    <View style={styles.progressText}>
                        <Text>0</Text>
                        <Text>100</Text>
                    </View>
                </View>
            );
        } else {
            let size = progress/1000/1024/1024;
            return (
                <View style={{flex: 1, alignItems: 'center'}}>
                    <Text>{this.props.title} [{size.toFixed(2)} M]</Text>
                </View>
            );
        }
    }
});
页面显示view
函数
module.exports = React.createClass({
    getInitialState() {
        const {options} = this.props;
        return {
            options,
            status: !options ? 
            STATUS_GET_VERSION : options.newVersion ? STATUS_HAS_VEW_VERSION : STATUS_HAS_NOT_VEW_VERSION,
            progress: 0,
        };
    },
    componentWillMount() {
        if (!this.state.options) {
            Update.checkVersion({
                versionUrl: app.route.ROUTE_VERSION_INFO_URL,
                iosAppId: CONSTANTS.IOS_APPID,
            }).then((options)=>{
                this.setState({options, status: !options ? 
                STATUS_GET_VERSION_ERROR : options.newVersion ?
                STATUS_HAS_VEW_VERSION : STATUS_HAS_NOT_VEW_VERSION});
            })
        }
    },
    onError(errCode) {
        if (errCode == ERROR_DOWNKOAD_APK) {
            this.setState({status: STATUS_DOWNKOAD_APK_ERROR});
        } else if (errCode == ERROR_DOWNKOAD_JS) {
            this.setState({status: STATUS_DOWNKOAD_JS_ERROR});
        } else if (errCode == ERROR_FAILED_INSTALL) {
            this.setState({status: STATUS_FAILED_INSTALL_ERROR});
        } else if (errCode == ERROR_UNZIP_JS) {
            this.setState({status: STATUS_UNZIP_JS_ERROR});
        }
    },
    doUpdate() {
        const {jsVersionCode, trackViewUrl} = this.state.options;
        if (jsVersionCode !== undefined) {
            Update.updateJS({
                jsVersionCode,
                jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
                onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
                onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
                onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
                onError:(errCode)=>{this.onError(errCode)},
            });
        } else {
            Update.updateApp({
                trackViewUrl,
                androidApkUrl:app.route.ROUTE_APK_URL,
                androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
                onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
                onError:(errCode)=>{this.onError(errCode)},
            });
        }
    },
页面的render
    render() {
        var components = {};
        const {
          currentVersion, 
          newVersion, 
          description
        } = this.state.options||{currentVersion:Update.getVersion()};

        components[STATUS_GET_VERSION] = (
            <Text style={styles.textInfo}>正在获取版本号</Text>
        );
        components[STATUS_HAS_NOT_VEW_VERSION] = (
            <Text style={styles.textInfo}>当前版本已经是最新版本</Text>
        );
        components[STATUS_GET_VERSION_ERROR] = (
            <Text style={styles.textInfo}>获取版本信息失败,请稍后再试</Text>
        );
        components[STATUS_DOWNKOAD_APK_ERROR] = (
            <Text style={styles.textInfo}>下载apk文件失败,请稍后再试</Text>
        );
        components[STATUS_DOWNKOAD_JS_ERROR] = (
            <Text style={styles.textInfo}>下载js bundle失败,请稍后再试</Text>
        );
        components[STATUS_UNZIP_JS_ERROR] = (
            <Text style={styles.textInfo}>解压js bundle失败,请稍后再试</Text>
        );
        components[STATUS_FAILED_INSTALL_ERROR] = (
            <Text style={styles.textInfo}>你放弃了安装</Text>
        );
        components[STATUS_HAS_VEW_VERSION] = (
            <View style={styles.textInfoContainer}>
                <Text style={styles.textInfo}>发现新版本{newVersion}</Text>
                <View style={styles.descriptionContainer}>
                    {
                        description && description.map((item, i)=>{
                            return (
                                <Text style={styles.textInfo} 
                                  key={i}>{(i+1)+'. '+item}
                                </Text>
                            )
                        })
                    }
                </View>
                <Button onPress={this.doUpdate} 
                    style={styles.button_layer} 
                    textStyle={styles.button_text}>立即更新
              </Button>
            </View>
        );
        components[STATUS_DOWNLOAD_APK_PROGESS] = (
            <ProgressInfo
                title="正在下载APK"
                progress={this.state.progress} />
        );
        components[STATUS_DOWNLOAD_JS_PROGESS] = (
            <ProgressInfo
                title="正在下载Bundle文件"
                progress={this.state.progress} />
        );
        components[STATUS_UNZIP_JS_PROGESS] = (
            <ProgressInfo
                title="正在解压Bundle文件"
                progress={this.state.progress} />
        );
        components[STATUS_UPDATE_END] = (
            <Text>正在重启...</Text>
        );
        return (
            ……
        );
    },
});
样式
var styles = StyleSheet.create({
    ……
});

UpdateInfoBox.js

全局变量
'use strict';
var React = require('react');
var ReactNative = require('react-native');
var {
    StyleSheet,
    View,
    Text,
    Image,
    TouchableOpacity,
} = ReactNative;

var Update = require('@remobile/react-native-update');

var
STATUS_HAS_VEW_VERSION = 0,
STATUS_DOWNLOAD_APK_PROGESS = 1,
STATUS_DOWNLOAD_JS_PROGESS = 2,
STATUS_UNZIP_JS_PROGESS = 3,
STATUS_DOWNKOAD_APK_ERROR = 4,
STATUS_DOWNKOAD_JS_ERROR = 5,
STATUS_UNZIP_JS_ERROR = 6,
STATUS_FAILED_INSTALL_ERROR = 7,
STATUS_UPDATE_END = 8;

var
ERROR_NULL = 0,
ERROR_DOWNKOAD_APK = 1,
ERROR_DOWNKOAD_JS = 2,
ERROR_FAILED_INSTALL = 3,
ERROR_UNZIP_JS = 4;

var PROGRESS_WIDTH = sr.tw*0.7;
var {Button, ProgressBar} = COMPONENTS;
进度view
var ProgressInfo = React.createClass({
    render() {
        const { progress } = this.props;
        if (progress < 1000) {
            return (
                <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                    <Text>{this.props.title} [{progress}%]</Text>
                    <ProgressBar
                        fillStyle={{}}
                        backgroundStyle={{backgroundColor: '#cccccc', borderRadius: 2}}
                        style={{marginTop: 10, width:PROGRESS_WIDTH}}
                        progress={progress/100.0}
                        />
                    <View style={styles.progressText}>
                        <Text>0</Text>
                        <Text>100</Text>
                    </View>
                </View>
            );
        } else {
            let size = progress/1000/1024/1024;
            return (
                <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                    <Text>{this.props.title} [ {size.toFixed(2)} M ]</Text>
                </View>
            );
        }
    }
});
页面view
函数
module.exports = React.createClass({
    getInitialState() {
        return {
            status:STATUS_HAS_VEW_VERSION,
            progress: 0,
        };
    },
    onError(errCode) {
        if (errCode == ERROR_DOWNKOAD_APK) {
            this.setState({status: STATUS_DOWNKOAD_APK_ERROR});
        } else if (errCode == ERROR_DOWNKOAD_JS) {
            this.setState({status: STATUS_DOWNKOAD_JS_ERROR});
        } else if (errCode == ERROR_FAILED_INSTALL) {
            this.setState({status: STATUS_FAILED_INSTALL_ERROR});
        } else if (errCode == ERROR_UNZIP_JS) {
            this.setState({status: STATUS_UNZIP_JS_ERROR});
        }
    },
    doUpdate() {
        const {jsVersionCode, trackViewUrl} = this.props.options;
        if (jsVersionCode !== undefined) {
            Update.updateJS({
                jsVersionCode,
                jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
                onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
                onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
                onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
                onError:(errCode)=>{this.onError(errCode)},
            });
        } else {
            Update.updateApp({
                trackViewUrl,
                androidApkUrl:app.route.ROUTE_APK_URL,
                androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
                onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
                onError:(errCode)=>{this.onError(errCode)},
            });
        }
    },
render
    render() {
        const components = {};
        const {newVersion, description} = this.props.options;
        components[STATUS_HAS_VEW_VERSION] = (
            <View style={styles.functionContainer}>
                <Text style={styles.title}>{`发现新版本(${newVersion})`}</Text>
                <Text style={styles.redLine}>
                </Text>
                <Text style={styles.content}>
                    {"更新内容:"}
                </Text>
                {
                    description.map((item, i)=>{
                        return (
                            <Text style={styles.contentItem} key={i}>{'- '+item}</Text>
                        )
                    })
                }
                <View style={styles.buttonViewStyle}>
                    <TouchableOpacity
                        onPress={app.closeModal}
                        style={styles.buttonStyleContainCannel}>
                        <Text style={styles.buttonStyleCannel}>以后再说</Text>
                    </TouchableOpacity>
                    <TouchableOpacity
                        onPress={this.doUpdate}
                        style={styles.buttonStyleContain}>
                        <Text style={styles.buttonStyle} >立即更新</Text>
                    </TouchableOpacity>
                </View>
            </View>
        );
        components[STATUS_DOWNKOAD_APK_ERROR] = (
            <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                <Text style={styles.textInfo}>下载apk文件失败,请在设置里重新更新</Text>
                <TouchableOpacity
                    onPress={app.closeModal}
                    style={styles.buttonStyleContainCannel}>
                    <Text style={styles.buttonStyleCannel}>我知道了</Text>
                </TouchableOpacity>
            </View>
        );
        components[STATUS_DOWNKOAD_JS_ERROR] = (
            <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                <Text style={styles.textInfo}>下载js bundle失败,请在设置里重新更新</Text>
                <TouchableOpacity
                    onPress={app.closeModal}
                    style={styles.buttonStyleContainCannel}>
                    <Text style={styles.buttonStyleCannel}>我知道了</Text>
                </TouchableOpacity>
            </View>
        );
        components[STATUS_UNZIP_JS_ERROR] = (
            <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                <Text style={styles.textInfo}>解压js bundle失败,请在设置里重新更新</Text>
                <TouchableOpacity
                    onPress={app.closeModal}
                    style={styles.buttonStyleContainCannel}>
                    <Text style={styles.buttonStyleCannel}>我知道了</Text>
                </TouchableOpacity>
            </View>
        );
        components[STATUS_FAILED_INSTALL_ERROR] = (
            <View style={[styles.functionContainer, {alignItems: 'center', paddingVertical: 30}]}>
                <Text style={styles.textInfo}>你放弃了安装</Text>
                <TouchableOpacity
                    onPress={app.closeModal}
                    style={styles.buttonStyleContainCannel}>
                    <Text style={styles.buttonStyleCannel}>我知道了</Text>
                </TouchableOpacity>
            </View>
        );
        components[STATUS_DOWNLOAD_APK_PROGESS] = (
            <ProgressInfo
                title="正在下载APK"
                progress={this.state.progress} />
        );
        components[STATUS_DOWNLOAD_JS_PROGESS] = (
            <ProgressInfo
                title="正在下载Bundle文件"
                progress={this.state.progress} />
        );
        components[STATUS_UNZIP_JS_PROGESS] = (
            <ProgressInfo
                title="正在解压Bundle文件"
                progress={this.state.progress} />
        );
        components[STATUS_UPDATE_END] = (
            <Text>即将重启...</Text>
        );
        return (
            <View style={styles.container}>
                {components[this.state.status]}
            </View>
        );
    },
});
样式

var styles = StyleSheet.create({
   ……
});

Update的方法

  • Update.getVersion - 获得当前版本
    • 返回值是 x.x.x
  • Update.checkVersion - 检查服务器版本
  • Update.updateApp - 更新apk或ios appstore
  • Update.updateJS - 更新js捆绑文件

检查版本并且显示更新对话框

const Update = require('@remobile/react-native-update');
const UpdateInfoBox = require('../modules/update/UpdateInfoBox');
Update.checkVersion({
    versionUrl: app.route.ROUTE_VERSION_INFO_URL,
    iosAppId: CONSTANTS.IOS_APPID,
}).then((options)=>{
    if (options && options.newVersion) {
        app.showModal(<UpdateInfoBox options={options} />, {backgroundColor:'rgba(0, 0, 0, 0.6)'})
    }
})

检查版本和显示更新页面

const Update = require('@remobile/react-native-update');
const UpdatePage = require('../modules/update/UpdatePage');

Update.checkVersion({
    versionUrl: app.route.ROUTE_VERSION_INFO_URL,
    iosAppId: CONSTANTS.IOS_APPID,
}).then((options)=>{
    app.navigator.push({
        title: '在线更新',
        component: UpdatePage,
        passProps: {options},
    });
})

选项

* versionUrl:服务器上的版本的url:
    * format: { "versionCode":1, "versionName":"1.0", "jsVersionCode":2, "description(描述)":"hello"}

   这个结构体将传递给needUpdateApp和updateUpdateJS,如果你设置它们, 所以你可以
自定义格式,但是versionCode和jsVersionCode必须保持一致

    * iosAppId:the appid on app Store

## Update App Or JS
```js
doUpdate() {
    const {jsVersionCode, trackViewUrl} = this.props.options;
    if (jsVersionCode !== undefined) {
        Update.updateJS({
            jsVersionCode,
            jsbundleUrl: app.isandroid?app.route.ROUTE_JS_ANDROID_URL:app.route.ROUTE_JS_IOS_URL,
            onDownloadJSProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_JS_PROGESS,progress})},
            onUnzipJSProgress:(progress)=>{this.setState({status: STATUS_UNZIP_JS_PROGESS,progress})},
            onUnzipJSEnd:()=>{this.setState({status: STATUS_UPDATE_END})},
            onError:(errCode)=>{this.onError(errCode)},
        });
    } else {
        Update.updateApp({
            trackViewUrl,
            androidApkUrl:app.route.ROUTE_APK_URL,
            androidApkDownloadDestPath:'/sdcard/yxjqd.apk',
            onDownloadAPKProgress:(progress)=>{this.setState({status: STATUS_DOWNLOAD_APK_PROGESS,progress})},
            onError:(errCode)=>{this.onError(errCode)},
        });
    }
},

选项

* jsbundleUrl:js捆绑网址
    * 当只有js代码更改或图像资源更改,我希望您发布jsbundle,称为次要版本
    * 包括www / index.android.bundle或www / index.ios.bundle
    * 包括android上的图像目录,ios上的资源目录
    * 在android上,image dir包含一些动态图像(这个版本添加的新图像)
    * 发布小版本你应该修改jsVersionCode
* androidApkUrl:apk的网址
    * 只有native代码改变了,你需要在Android上发布apk或者在ios上发布ipa,叫做Marjor版本
    * 发布Marjor版本,您应该增加versionCode并将jsVersionCode设置为0
* androidApkDownloadDestPath:apk文件下载路径,例如/ sdcard / download,确保它存在
* onDownloadAPKStart:function
    * 当apk开始下载时回调
* onDownloadAPKProgress:function
    * 当apk正在下载时回调
    * 将通过{total:xx,loaded:xx} 1参数
* onDownloadAPKEnd:function
    * 当apk下载结束时回调
* onDownloadJSStart:function
* onDownloadJSProgress:function
    * 当apk正在下载时回调,
    * 将通过{total:xx,loaded:xx} 1参数
* onDownloadJSEnd:function
* onUnzipJSStart:function
* onUnzipJSProgress:function
    * 解压时候回调,
    * 将通过{total:xx,loaded:xx} 1参数
* onUnzipJSEnd:function
* onError:function
    * 将通过errorCode
    * var ERROR_NULL = 0,
          ERROR_DOWNKOAD_APK = 1, 
          ERROR_DOWNKOAD_JS = 2, 
          ERROR_GET_VERSION = 3, 
          ERROR_UNZIP_JS = 4;

生成捆绑

#!/bin/bash

distpath=../../localServer/public/download

function genIOSBundle() {
    react-native bundle \
        --platform ios \
        --reset-cache \
        --verbose \
        --entry-file index.ios.js \
        --bundle-output ./tools/www/index.ios.bundle \
        --assets-dest ./tools/www/ \
        --dev false
}

function genAndroidBundle() {
    react-native bundle \
        --platform android \
        --reset-cache \
        --verbose \
        --entry-file index.android.js \
        --bundle-output ./tools/www/index.android.bundle \
        --assets-dest ./tools/www/ \
        --dev false
}

function zipWWW() {
    node -e "!function(){function i(e,r){var o=n.readdirSync(e);o.forEach(function(o){var s=e+'/'+o;n.statSync(s).isDirectory()?i(s,r+'/'+o):c.folder(r).file(o,n.readFileSync(s))})}function e(e,r,o){r=r||'',o=o||e+'.zip',i(e,r);var s=c.generate({base64:!1,compression:'DEFLATE'});n.writeFile(o,s,'binary',function(){console.log('success')})}var r=require('jszip'),n=require('fs'),c=new r,o=process.argv.splice(1);e.apply(null,o)}();"  www _www www.zip
}

function genMd5List() {
    cd ./tools
    # git co head ${distpath}/${1}_md5.json
    node -e "var o=process.argv;require('./getMd5List')(o[1],o[2])" ${1} ${distpath}
}

function zipFile() {
    zipWWW
    mv ./www.zip ${distpath}/js${1}.zip
    rm -fr www
    mv ./${1}_md5.json ${distpath}/${1}_md5.json
    echo "${distpath}"
}

function buildAndroid() {
    rm -fr www
    mkdir www
    cd ..
    genAndroidBundle
    genMd5List android
    zipFile android
}

function buildIos() {
    rm -fr www
    mkdir www
    cd ..
    genIOSBundle
    genMd5List ios
    zipFile ios
}

function main() {
    if [ "$1" = "android" ];then
        buildAndroid
    elif [ "$1" = "ios" ];then
        buildIos
    elif [ "$1" = "all" ];then
        buildAndroid
        buildIos
    else
        echo "Usage: ./genbundle ios|android|all"
    fi
}

main $@
  • 确保安装jszip在npm在全局(global),我们使用它的zip

服务器端 version.json

{
    "iosPassed": true,
    "iosJsVersionCode":0,
    "iosDescription":["修正bug", "添加新功能"],
    "androidPassed": {
        "baidu": false,
        "default": true
    },
    "versionName":"1.0",
    "versionCode": 1048576,
    "androidJsVersionCode":0,
    "androidDescription": ["修改bug", "添加新功能"]
}

看详细使用

工具在

useful tools

例子在 react-native-template

Update

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

推荐阅读更多精彩内容