RxJava用法

核心思想:

RxJava學習和使用最重要的是掌握它的核心思想,它在 github 主頁的介紹是:

"a library for composing asynchronous and event-based programs using observable sequences for the Java VM"

我理解是:一個運行在 Java 虛擬機上的可序列觀察組合、異步和基于事件的庫芥玉。所以能推導 Rx 定義為:序列觀察組合異步事件姨拥。
我們能得到幾個關鍵詞:

  • 序列:對順序敏感(事件發(fā)送)
  • 觀察:響應式編程
  • 組合:可處理多個事件
  • 異步:可調度線程
  • 事件:可以理解為起點發(fā)送的事件,也可以理解為每個中間環(huán)節(jié)的操作

根據(jù)使用經(jīng)驗來總結其核心思想:在起點注冊觀察后分發(fā)一個事件,流式操作中間步驟克伊,終點消費事件解除觀察捏浊。

因為它的操作符很多懂衩,遍歷學習操作符性價比很小,我們根據(jù)其核心思想金踪,以輸出的方式來學習 RxJava浊洞。

普通應用:

業(yè)務要求1:有一個圖片url地址,下載圖片并且將圖片顯示在 ImageView胡岔。

思路:事件為下載并且展示圖片到 ImageView法希,事件起點為分發(fā)(放置)一個 url 給下游中間步驟1(傳送帶),下游中間步驟1拿到的為String而步驟二設置圖片顯示需要的數(shù)據(jù)為 Bitmap靶瘸,所以此步驟需要 Map 操作符來將url轉換為 Bitmap并且應該 為異步操作苫亦,然后將 Bitmap分發(fā)給下游中間步驟 2尖淘,步驟 2 將 Bitmap更新到 ImageView,消費掉事件著觉,事件結束村生。

流程圖為:


image-20200527164516864.png

上代碼:

    // 打印logcat日志的標簽
    String TAG = DownloadActivity.class.getSimpleName();

  // 網(wǎng)絡圖片的鏈接地址
   String PATH = "http://pic1.win4000.com/wallpaper/c/53cdd1f7c1f21.jpg";

  // 彈出加載框
  ProgressDialog progressDialog;

Observable.just(PATH)  // 內部會分發(fā)  PATH Stirng // TODO 第二步 
  // TODO 第二步      
                .map(new Function<String, Bitmap>() {
            @Override
            public Bitmap apply(String s) throws Exception {
                URL url = new URL(PATH);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(5000);
                int responseCode = httpURLConnection.getResponseCode(); // 才開始 request
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = httpURLConnection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    return bitmap;
                }
                return null;
            }
        })
        // 日志記錄
        .map(new Function<Bitmap, Bitmap>() {
            @Override
            public Bitmap apply(Bitmap bitmap) throws Exception {
                Log.d(TAG, "apply: 是這個時候下載了圖片啊:" + System.currentTimeMillis());
                return bitmap;
            }
        })
        .subscribeOn(Schedulers.io())     // 給上面代碼分配異步線程
        .observeOn(AndroidSchedulers.mainThread()) // 切換主線程
        // 訂閱 起點 和 終點 訂閱起來
        .subscribe(
                // 終點
                new Observer<Bitmap>() {
                    // 訂閱開始
                    @Override
                    public void onSubscribe(Disposable d) {
                        // 預備 開始 要分發(fā) 創(chuàng)建并展示 loading
                        // TODO 第一步
                        progressDialog = new ProgressDialog(DownloadActivity.this);
                        progressDialog.setTitle("download run");
                        progressDialog.show();
                    }

                    // TODO 第四步
                    // 消費事件
                    @Override
                    public void onNext(Bitmap bitmap) {
                        image.setImageBitmap(bitmap);
                    }

                    // 錯誤事件
                    @Override
                    public void onError(Throwable e) {
                    }
                    // TODO 第五步
                    // 完成事件
                    @Override
                    public void onComplete() {
                        if (progressDialog != null)
                            progressDialog.dismiss();
                    }
        });

實現(xiàn)完上邊需求穿香,產(chǎn)品小姐姐說需要一個加水印的功能袍榆,沒問題宾尚,我們就在上邊代碼基礎上來做改造蝠咆。

思路:在上述步驟1九巡,拿到 Bitmap 后然后我們將步驟2 改為為 Bitmap 添加水印勤篮,然后步驟3 來完成圖片的展示和事件的消費逼龟。

修改后的流程圖為:


image-20200527172449607.png

上代碼:

// 打印logcat日志的標簽
    String TAG = DownloadActivity.class.getSimpleName();

  // 網(wǎng)絡圖片的鏈接地址
   String PATH = "http://pic1.win4000.com/wallpaper/c/53cdd1f7c1f21.jpg";

  // 彈出加載框
  ProgressDialog progressDialog;

Observable.just(PATH)  // 內部會分發(fā)  PATH Stirng // TODO 第二步 
  // TODO 第二步      
                .map(new Function<String, Bitmap>() {
            @Override
            public Bitmap apply(String s) throws Exception {
                URL url = new URL(PATH);
                HttpURLConnection httpURLConnection = (HttpURLConnection) url.openConnection();
                httpURLConnection.setConnectTimeout(5000);
                int responseCode = httpURLConnection.getResponseCode(); // 才開始 request
                if (responseCode == HttpURLConnection.HTTP_OK) {
                    InputStream inputStream = httpURLConnection.getInputStream();
                    Bitmap bitmap = BitmapFactory.decodeStream(inputStream);
                    return bitmap;
                }
                return null;
            }
        })
            // 此處為新增水印操作代碼
        .map(new Function<Bitmap, Bitmap>() {
                  @Override
                  public Bitmap apply(Bitmap bitmap) throws Exception {
                      Paint paint = new Paint();
                      paint.setTextSize(88);
                      paint.setColor(Color.RED);
                      return drawTextToBitmap(bitmap, "產(chǎn)品要得水印",paint, 88 , 88);
                  }
              })
  
        // 日志記錄
        .map(new Function<Bitmap, Bitmap>() {
            @Override
            public Bitmap apply(Bitmap bitmap) throws Exception {
                Log.d(TAG, "apply: 是這個時候下載了圖片啊:" + System.currentTimeMillis());
                return bitmap;
            }
        })
        .subscribeOn(Schedulers.io())     // 給上面代碼分配異步線程
        .observeOn(AndroidSchedulers.mainThread()) // 切換主線程
        // 訂閱 起點 和 終點 訂閱起來
        .subscribe(
                // 終點
                new Observer<Bitmap>() {
                    // 訂閱開始
                    @Override
                    public void onSubscribe(Disposable d) {
                        // 預備 開始 要分發(fā) 創(chuàng)建并展示 loading
                        // TODO 第一步
                        progressDialog = new ProgressDialog(DownloadActivity.this);
                        progressDialog.setTitle("download run");
                        progressDialog.show();
                    }

                    // TODO 第四步
                    // 消費事件
                    @Override
                    public void onNext(Bitmap bitmap) {
                        image.setImageBitmap(bitmap);
                    }

                    // 錯誤事件
                    @Override
                    public void onError(Throwable e) {
                    }
                    // TODO 第五步
                    // 完成事件
                    @Override
                    public void onComplete() {
                        if (progressDialog != null)
                            progressDialog.dismiss();
                    }
        });


// 圖片上繪制文字 加水印
    private final Bitmap drawTextToBitmap(Bitmap bitmap, String text, Paint paint, int paddingLeft, int paddingTop) {
        Bitmap.Config bitmapConfig = bitmap.getConfig();

        paint.setDither(true); // 獲取跟清晰的圖像采樣
        paint.setFilterBitmap(true);// 過濾一些
        if (bitmapConfig == null) {
            bitmapConfig = Bitmap.Config.ARGB_8888;
        }
        bitmap = bitmap.copy(bitmapConfig, true);
        Canvas canvas = new Canvas(bitmap);

        canvas.drawText(text, paddingLeft, paddingTop, paint);
        return bitmap;
    }

防抖:

防抖的意思就是說規(guī)定時間內接收到多次事件只觸發(fā)一次亿胸。放置惡意轟炸典徘,或者重復觸發(fā)問題蟀苛。

業(yè)務需求:如下圖,兩秒內項目按鈕點擊只完成一次獲取所有項目分類逮诲,并且根據(jù)項目 id 獲取所有項目下的所有列表數(shù)據(jù)帜平。

image-20200527174315867.png

一般思路:利用 RxBinding 處理防抖動,然后步驟1梅鹦,網(wǎng)絡獲取所有項目數(shù)據(jù)裆甩,數(shù)據(jù)類型為[ProjectBean,ProjectBean,ProjectBean,ProjectBean,ProjectBean...],步驟 2,根據(jù)步驟 1 獲取的ProjectBeanList 遍歷齐唆,分別根據(jù)每個 ProjectBean id 去獲取項目列表數(shù)據(jù)嗤栓。步驟 3,分別獲取每個項目的列表數(shù)據(jù) [ProjectItem,ProjectItem,ProjectItem,ProjectItem,ProjectItem..]去更新 UI消費掉該事件箍邮。

流程圖如下:

image-20200527180256022.png

上代碼:(此處為偽代碼實現(xiàn))

// 注意:(項目分類)查詢的id茉帅,通過此id再去查詢(項目列表數(shù)據(jù))
        // 對那個控件防抖動?
        Button bt_anti_shake = findViewById(R.id.bt_anti_shake);
        RxView.clicks(bt_anti_shake)
                .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒鐘之內 響應你一次
                .subscribe(new Consumer<Object>() {
                    @Override
                    public void accept(Object o) throws Exception {
                        api.getProject() // 查詢主數(shù)據(jù)
                        .compose(RxUtils.rxud()) //線程切換封裝
                        .subscribe(new Consumer<ProjectBean>() {
                            @Override
                            public void accept(ProjectBean projectBean) throws Exception {
                                for (ProjectBean.DataBean dataBean : projectBean.getData()) { // 10
                                    // 查詢item數(shù)據(jù)
                                    api.getProjectItem(1, dataBean.getId())
                                    .compose(RxUtils.rxud())
                                    .subscribe(new Consumer<ProjectItem>() {
                                        @Override
                                        public void accept(ProjectItem projectItem) throws Exception {
                                            Log.d(TAG, "accept: " + projectItem); // 可以UI操作
                                        }
                                    });
                                }
                            }
                        });
                    }
                });

網(wǎng)絡嵌套:

上邊防抖代碼可讀性很差锭弊,why堪澎?因為上邊代碼有兩次嵌套縮進,第一次為實現(xiàn)防抖動廷蓉,回調縮進了一次全封,項目列表數(shù)據(jù)獲取也在主數(shù)據(jù)獲取的回調嵌套中。

解決迷之嵌套問題桃犬,我們需要了解 RxJava 的另一個操作符:

flatmap:可以將一個觀察集合數(shù)據(jù)的觀察者展開為多個觀察一條數(shù)據(jù)的觀察者刹悴。這也是他和 map 的最主要區(qū)別,當集合中只有一條數(shù)據(jù)時攒暇,它的作用將和 map 一樣土匀。回顧上邊普通應用的需求實現(xiàn)都是從頭到尾一個觀察者形用,觀察著一條數(shù)據(jù)向下游流動就轧。

改造版代碼:(同樣為偽代碼)

// 對那個控件防抖動证杭?
        Button bt_anti_shake = findViewById(R.id.bt_anti_shake);

        RxView.clicks(bt_anti_shake)
                .throttleFirst(2000, TimeUnit.MILLISECONDS) // 2秒鐘之內 響應你一次
                // 我只給下面 切換 異步
                .observeOn(Schedulers.io())
                .flatMap(new Function<Object, ObservableSource<ProjectBean>>() {
                    @Override
                    public ObservableSource<ProjectBean> apply(Object o) throws Exception {
                        return api.getProject(); // 主數(shù)據(jù)
                    }
                })
                .flatMap(new Function<ProjectBean, ObservableSource<ProjectBean.DataBean>>() {
                    @Override
                    public ObservableSource<ProjectBean.DataBean> apply(ProjectBean projectBean) throws Exception {
                        return Observable.fromIterable(projectBean.getData()); // 注冊多個觀察者
                    }
                })
                .flatMap(new Function<ProjectBean.DataBean, ObservableSource<ProjectItem>>() {
                    @Override
                    public ObservableSource<ProjectItem> apply(ProjectBean.DataBean dataBean) throws Exception {
                        return api.getProjectItem(1, dataBean.getId());
                    }
                })
                .observeOn(AndroidSchedulers.mainThread()) // 給下面切換 主線程
                .subscribe(new Consumer<ProjectItem>() {
                    @Override
                    public void accept(ProjectItem projectItem) throws Exception {
                        // 如果我要更新UI  會報錯2  不會報錯1
                        Log.d(TAG, "accept: " + projectItem);
                    }
                });

連續(xù)操作:

上邊需求發(fā)現(xiàn)沒?有一個共同規(guī)律妒御,就是都是一個事件開始解愤,然后事件結束。如果實現(xiàn)一個時間開始乎莉,一個時間結束送讲,然后再次開啟一個事件,然后再次結束第二個事件怎么辦惋啃?是和上述代碼并列哼鬓,再次創(chuàng)建一個觀察者?NO边灭!看下邊操作符异希。

doNoNext:可以用于,多個連續(xù)事件绒瘦,執(zhí)行過程中称簿,事件都需要按順序消費掉前邊事件然后繼續(xù)執(zhí)行后邊事件。

業(yè)務需求:用戶注冊完成后更新UI 跳轉登錄頁面并且自動登錄后跳轉首頁椭坚。

實現(xiàn)思路:用戶點擊 reigstBtn獲取注冊結果予跌,拿到注冊結果 UI 線程去跳轉登錄頁面,然后工作線程去根據(jù) reigstResponse 去請求登錄數(shù)據(jù)善茎,拿到登錄信息去跳轉首頁,消費掉這個事件频轿。注意:此時消費掉的事件并非最初事件垂涯。

業(yè)務流程比較簡單,直接代碼實現(xiàn):(偽代碼)

/**
         * 一行代碼 實現(xiàn)需求
         * 需求:
         *   還有彈出加載
         *  * 1.請求服務器注冊操作
         *  * 2.注冊完成之后航邢,更新注冊UI
         *  * 3.馬上去登錄服務器操作
         *  * 4.登錄完成之后耕赘,更新登錄的UI
         */
        MyRetrofit.createRetrofit().create(IReqeustNetwor.class)
                .registerAction(new RegisterRequest()) // todo 1.請求服務器注冊操作   // todo 2
                .subscribeOn(Schedulers.io()) // 給上面 異步
                .observeOn(AndroidSchedulers.mainThread()) // 給下面分配主線程
                .doOnNext(new Consumer<RegisterResponse>() { // todo 3
                    @Override
                    public void accept(RegisterResponse registerResponse) throws Exception {
                        // todo 2.注冊完成之后,更新注冊UI
                    }
                })
                // todo 3.馬上去登錄服務器操作
                .observeOn(Schedulers.io()) // 給下面分配了異步線程
                .flatMap(new Function<RegisterResponse, ObservableSource<LoginResponse>>() { // todo 4
                    @Override
                    public ObservableSource<LoginResponse> apply(RegisterResponse registerResponse) throws Exception {
                        Observable<LoginResponse> loginResponseObservable = MyRetrofit.createRetrofit().create(IReqeustNetwor.class)
                                .loginAction(new LoginReqeust());
                        return loginResponseObservable;
                    }
                })
                .observeOn(AndroidSchedulers.mainThread()) // 給下面 執(zhí)行主線程
                .subscribe(new Observer<LoginResponse>() {
                    // 一定是主線程膳殷,為什么操骡,因為 subscribe 馬上調用onSubscribe
                    @Override
                    public void onSubscribe(Disposable d) {
                        // TODO 1
                        progressDialog = new ProgressDialog(RequestActivity.this);
                        progressDialog.show();
                        // UI 操作
                        disposable = d;
                    }

                    @Override
                    public void onNext(LoginResponse loginResponse) { // todo 5
                        // TODO 4.登錄完成之后,更新登錄的UI
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    // todo 6
                    @Override
                    public void onComplete() {
                        // over
                        if (progressDialog != null) {
                            progressDialog.dismiss();
                        }
                    }
                });

????

并行操作:

??????????????????????????再有一個例子赚窃,右下邊需求:

image-20200605104151377.png
Map<String, String> map = CZController.paramHelp.getBasicParam();
        map.put("catalogId", catalogId);
        map.put("courseId", courseId);


        Observable<CZResult<String>> obLiveData =
                ApiWrapper.getService().getLiveRoomV2(map)
                        .subscribeOn(Schedulers.io());
        Observable<CZResult<String>> obUserInfo =
                ApiWrapper.getService().getLiveEmployeeInfo(map)
                        .subscribeOn(Schedulers.io());

        Observable.merge(obLiveData, obUserInfo)
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new CZSubscriberD<CZResult<String>>() {
                    @Override
                    public void onCZNext(String json) {
                      
                    }
                });
}

遞歸:

還是一個需求來看册招,掃描某個路徑下的所有文件,找出格式為 .doc 的文件并輸出路徑勒极。

直接看代碼:

/**
* 獲取某文件夾下所有 doc 格式的文件路徑
*/
private void getFiles(File file) {
    if(!file.isDirectory()) return;

        Observable.just(file)
                .observeOn(Schedulers.io())
                .map(new Function<File, List<File>>() {
                    @Override
                    public List<File> apply(File file) throws Exception {
                        return Arrays.asList(file.listFiles());
                    }
                })
                .flatMap(new Function<List<File>, ObservableSource<File>>() {
                    @Override
                    public ObservableSource<File> apply(List<File> files) throws Exception {
                        return Observable.fromIterable(files);
                    }
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Consumer<File>() {
                    @Override
                    public void accept(File file) throws Exception {
                        //TODO 判斷 file 類型是掰,如果是文件夾則遞歸,如果是需要的格式則繼續(xù)辱匿,否則結束
                        if (file.isDirectory()) {
                            getFiles(file);
                        } else if (file.getName().endsWith(".doc")) {
                            Log.d("Tag1", "path:" + file.getPath());
                        } else {
                            Log.d("Tag1", "其他類型文件");
                        }
                    }
                });
    }

哈哈哈键痛,咋樣炫彩,如果你真拿上邊代碼去應用,就會發(fā)現(xiàn)一個問題絮短,卡的一筆江兢。

為什么呢,flatmap 就會創(chuàng)建 n 個 Observable丁频,然后外層遞歸更可怕 n 的 n 次方個Observable,所以遞歸中最好都不要有對象的創(chuàng)建杉允,哈哈哈。

看一下下邊代碼:

/**
 * Created by droidwolf on 2017/11/14.
 * https://my.oschina.net/droidwolf
 * 轉載請注明
 */
public class FileTreeWalker implements Iterable<File> {
    private ArrayDeque<File> mDirectories =null;
    private ArrayDeque<File> mFiles = null;
 
    public FileTreeWalker walk(File path) {
        if (mDirectories != null && !mDirectories.isEmpty()) {
            mDirectories.clear();
        }
        if (mFiles != null && !mFiles.isEmpty()) {
            mFiles.clear();
        }
        walkDir(path);
        return this;
    }
 
    private void walkDir(File path) {
        if (path == null || !path.exists() || !path.isDirectory()) {
            return;
        }
        final File[] files = path.listFiles();
        if (files == null || files.length == 0) {
            return;
        }
        if(mDirectories==null) mDirectories = new ArrayDeque<File>(256);
        if(mFiles==null) mFiles = new ArrayDeque<File>(512);
 
        for (File f : files) {
            if (f.isDirectory()) {
                mDirectories.push(f);
            } else {
                mFiles.addLast(f);
            }
        }
    }
 
    @Override
    public Iterator<File> iterator() {
        return mIterator;
    }
    private final Iterator<File> mIterator=new Iterator<File>() {
        @Override
        public boolean hasNext() {
            return (mFiles!=null &&!mFiles.isEmpty()) || (mDirectories!=null&& !mDirectories.isEmpty());
        }
        @Override
        public File next() {
            if (mFiles != null && !mFiles.isEmpty()) {
                final File f = mFiles.pollFirst();
 
                return f;
            } else if (mDirectories != null && !mDirectories.isEmpty()) {
                final File dir = mDirectories.pop();
 
                walkDir(dir);
                return dir;
            }
            return null;
        }
    };
}



// rxjava調用FileTreeWalker 
     Observable.fromIterable(new FileTreeWalker().walk(path))
                .subscribeOn(Schedulers.io())
                .filter(file -> {
                    //限府。夺颤。。
                    return false;
                })
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(fileNext -> {
                    //胁勺。世澜。。
                });
//當然也可以在java8中使用FileTreeWalker

StreamSupport.stream(Spliterators.spliteratorUnknownSize(new FileTreeWalker().walk(new File("C:\\")).iterator(),Spliterator.NONNULL),true )
.forEach(file->{
    if(file.isDirectory()) {
        System.out.println(file.getName());
    }else {
        System.out.println("\t"+file.getName());
    }
});

小結

帶著本文第一節(jié)對“核心思想”的理解署穗,來思考一下這幾個業(yè)務實現(xiàn)寥裂。發(fā)現(xiàn)每個業(yè)務都重點關注事件的開始和結束也就是事件的傳入數(shù)據(jù)類型和最終消費事件需要的數(shù)據(jù)類型,中間步驟的所有轉換都有操作符去支持我們案疲。

上邊圖片下載問題可以舉個相似栗子:小明肚子餓了封恰,想吃東西利用核心思想來實現(xiàn)一下:

image-20200527190542686.png

此事件完整實現(xiàn)需要先確定事件開始(大腦餓了信號類型),和事件結束(食物類型)中間具體需要步驟都可以有操作符來實現(xiàn)褐啡。

網(wǎng)絡嵌套需求舉個相似栗子:小明餓了诺舔,想吃 10 種東西:

image-20200527192110882.png

連續(xù)操作需求舉一個相似栗子:小明餓了,吃完飯然后去上學备畦。

image-20200527193408847.png

關注開始與結束

操作符匯總:

image-20200605185317819.png
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末低飒,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子懂盐,更是在濱河造成了極大的恐慌褥赊,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,968評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件莉恼,死亡現(xiàn)場離奇詭異拌喉,居然都是意外死亡,警方通過查閱死者的電腦和手機俐银,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,601評論 2 382
  • 文/潘曉璐 我一進店門尿背,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人悉患,你說我怎么就攤上這事残家。” “怎么了售躁?”我有些...
    開封第一講書人閱讀 153,220評論 0 344
  • 文/不壞的土叔 我叫張陵坞淮,是天一觀的道長茴晋。 經(jīng)常有香客問我,道長回窘,這世上最難降的妖魔是什么诺擅? 我笑而不...
    開封第一講書人閱讀 55,416評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮啡直,結果婚禮上烁涌,老公的妹妹穿的比我還像新娘。我一直安慰自己酒觅,他們只是感情好撮执,可當我...
    茶點故事閱讀 64,425評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著舷丹,像睡著了一般抒钱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上颜凯,一...
    開封第一講書人閱讀 49,144評論 1 285
  • 那天谋币,我揣著相機與錄音,去河邊找鬼症概。 笑死蕾额,一個胖子當著我的面吹牛,可吹牛的內容都是我干的彼城。 我是一名探鬼主播诅蝶,決...
    沈念sama閱讀 38,432評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼募壕!你這毒婦竟也來了秤涩?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,088評論 0 261
  • 序言:老撾萬榮一對情侶失蹤司抱,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后黎烈,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體习柠,經(jīng)...
    沈念sama閱讀 43,586評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,028評論 2 325
  • 正文 我和宋清朗相戀三年照棋,在試婚紗的時候發(fā)現(xiàn)自己被綠了资溃。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,137評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡烈炭,死狀恐怖溶锭,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情符隙,我是刑警寧澤趴捅,帶...
    沈念sama閱讀 33,783評論 4 324
  • 正文 年R本政府宣布垫毙,位于F島的核電站,受9級特大地震影響拱绑,放射性物質發(fā)生泄漏综芥。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,343評論 3 307
  • 文/蒙蒙 一猎拨、第九天 我趴在偏房一處隱蔽的房頂上張望膀藐。 院中可真熱鬧,春花似錦红省、人聲如沸额各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,333評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽虾啦。三九已至,卻和暖如春蚜枢,著一層夾襖步出監(jiān)牢的瞬間缸逃,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,559評論 1 262
  • 我被黑心中介騙來泰國打工厂抽, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留需频,地道東北人。 一個月前我還...
    沈念sama閱讀 45,595評論 2 355
  • 正文 我出身青樓筷凤,卻偏偏與公主長得像昭殉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子藐守,可洞房花燭夜當晚...
    茶點故事閱讀 42,901評論 2 345