android-MVP架構(gòu)

MVP

簡(jiǎn)介

MVP是模型(Model)、視圖(View)碍岔、主持人(Presenter)的縮寫浴讯,分別代表項(xiàng)目中3個(gè)不同的模塊。如圖所示:


image
  • View 對(duì)應(yīng)于Activity蔼啦、Fragment榆纽,負(fù)責(zé)界面的繪制以及與用戶交互
  • Model 依然是業(yè)務(wù)邏輯和實(shí)體模型
  • Presenter 負(fù)責(zé)完成View于Model間的交互

設(shè)計(jì)前思考:

  • 首先在我們常用的MVC模式中,Activity承載了太多捏肢,做了不只是視圖層的事情掠河,而程序開發(fā)中最重要的 Context 一般也是在視圖層才擁有的,所以我們需要把Context保持在視圖中猛计。
  • MVP相對(duì)于MVC唠摹,MVP中是依賴Presenter這個(gè)接口任務(wù)調(diào)度器來實(shí)現(xiàn)任務(wù)調(diào)度,則視圖層中所有需要進(jìn)行數(shù)據(jù)交互的奉瘤,都需要將數(shù)據(jù)交給Presenter勾拉,而Presenter將調(diào)用Model來加載數(shù)據(jù)。
  • 在傳統(tǒng)的MVC中盗温,我常用 initView()藕赞、initData()、initEvent()卖局、doOther() 這幾個(gè)方法來實(shí)現(xiàn)數(shù)據(jù)流程加載斧蜕、界面交互實(shí)現(xiàn)。現(xiàn)在我們需要拆分出來砚偶,Activity從BaseActivity中實(shí)現(xiàn)批销。

經(jīng)過這樣的構(gòu)思洒闸,我們可以先實(shí)踐一下,我們讓View來實(shí)現(xiàn)Model的接口均芽,View來調(diào)用presenter丘逸,presenter利用面向接口編程的思想來調(diào)用接口實(shí)現(xiàn)對(duì)View的操作。實(shí)例如下:


import android.content.Context;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;

import com.acheng.achengutils.mvp.model.BaseViewController;
import com.acheng.achengutils.mvp.presenter.BasePresenter;


/**
 * Created by pc859107393 on 2016/6/28.
 */
public abstract class BaseActivity<T extends BasePresenter, M extends BaseViewController> extends AppCompatActivity {

    public String TAG;  //當(dāng)前Activity的標(biāo)記

    protected T mPresenter;     //主持人角色

    protected abstract T initPresenter();    //獲取到主持人


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        TAG = String.format("%s::%s", getPackageName(), getLocalClassName());


        mPresenter = initPresenter();    //初始化Presenter掀宋,提供主持人深纲,擁有主持人后才能提交界面數(shù)據(jù)給presenter

        setContentView(setLayoutId());

        initView();

        mPresenter.initData();

        initEvent();

        doOther();
    }

    protected void doOther() {

    }

    public Context getContext() {
        return this;
    }

    protected abstract void initEvent();


    protected abstract void initView();

    protected abstract int setLayoutId();

    @Override
    protected void onResume() {
        super.onResume();
        //如果presenter為空的時(shí)候,我們需要重新初始化presenter
        if (mPresenter == null) {
            mPresenter = initPresenter();
        }
    }

    @Override
    protected void onPause() {
        super.onPause();
    }

    @Override
    public void onBackPressed() {   //返回按鈕點(diǎn)擊事件
        //當(dāng)Activity中的 進(jìn)度對(duì)話框正在旋轉(zhuǎn)的時(shí)候(數(shù)據(jù)正在加載劲妙,網(wǎng)絡(luò)延遲高湃鹊,數(shù)據(jù)難以加載),關(guān)閉 進(jìn)度對(duì)話框 , 然后可以手動(dòng)執(zhí)行重新加載

        super.onBackPressed();
    }

    /**
     * 恢復(fù)界面后,我們需要判斷我們的presenter是不是存在,不存在則重置presenter
     *
     * @param savedInstanceState
     */
    @Override
    protected void onRestoreInstanceState(Bundle savedInstanceState) {
        super.onRestoreInstanceState(savedInstanceState);
        if (mPresenter == null)
            mPresenter = initPresenter();
    }

    /**
     * onDestroy中銷毀presenter
     */
    @Override
    protected void onDestroy() {
        super.onDestroy();
        mPresenter = null;
    }

}

既然我們的Activity已經(jīng)設(shè)定好了BaseActivity镣奋,我們需要接著完成BasePresenter涛舍,如下:

import com.acheng.achengutils.mvp.model.BaseViewController;

/**
 * Created by acheng on 2016/7/14.
 */
public abstract class BasePresenter<D extends BaseViewController> {


    public D model;

    /**
     * 在子類的構(gòu)造函數(shù)中,設(shè)定參數(shù)為model唆途,這時(shí)候可以presenter調(diào)用接口來實(shí)現(xiàn)對(duì)界面的操作富雅。
     */
    public BasePresenter(D model) {
        this.model = model;
    }

    public abstract void initData();


}

關(guān)于我這個(gè)Presenter的設(shè)計(jì),我想說的是我們需要將各層解耦肛搬,那么我的presenter就不應(yīng)該持有Android程序流轉(zhuǎn)的必然因子没佑,如Context、Bundle温赔、Intent蛤奢、View等,如果我們需要實(shí)現(xiàn)對(duì)界面的操作陶贼,必須通過調(diào)用我們?cè)O(shè)定好的Model來實(shí)現(xiàn)啤贩,關(guān)于BaseModel更加簡(jiǎn)單了,直接是一個(gè)空的接口文件拜秧,如下:


public interface BaseViewController {
    //這里面添加實(shí)現(xiàn)類需要實(shí)現(xiàn)的方法即可
}

設(shè)計(jì)后的思考

  • presenter作為主持人,應(yīng)該隨著視圖的關(guān)閉而關(guān)閉,所以我們需要在Activity和Fragment的關(guān)閉的時(shí)候,注銷相應(yīng)的presenter
  • 在應(yīng)用程序被銷毀的時(shí)候,我們重啟了程序,但是這時(shí)應(yīng)用的狀態(tài)如果不恢復(fù)到前面的狀態(tài)那么我們需要把對(duì)應(yīng)的presenter重建
  • 在應(yīng)用恢復(fù)后,如果想保持剛才的狀態(tài),那么我們需要在被銷毀前把視圖的狀態(tài)保存,并且恢復(fù)對(duì)應(yīng)的狀態(tài)

說了這么多痹屹,我們直接手底下見真章:


import android.Manifest;
import android.annotation.TargetApi;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Build;
import android.provider.Settings;
import android.support.v7.app.AlertDialog;
import android.view.View;
import android.widget.TextView;

import com.acheng.achengutils.mvp.view.BaseActivity;
import com.acheng.achengutils.utils.SPHelper;
import com.acheng.achengutils.widgets.AppUpdateDialog;
import com.acheng.achengutils.widgets.MustDoThingDailog;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import acheng1314.cn.a3dbuild.MyApplication;
import acheng1314.cn.a3dbuild.R;
import acheng1314.cn.a3dbuild.bean.LoginBean;
import acheng1314.cn.a3dbuild.view.activity.presenter.LoginActivityPresenter;
import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;
import acheng1314.cn.a3dbuild.widgets.MyProgressDialog;

/**
 * Created by pc859107393 on 2016/9/12 0012.
 */
public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController {

    private View mBt_login;
    private TextView mEt_username;  //用戶名
    private TextView mEt_password;  //密碼s


    final private int REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS = 124;
    private AppUpdateDialog appPermission;  //權(quán)限申請(qǐng)對(duì)話框
    private MyProgressDialog myProgressDialog;  //進(jìn)度對(duì)話框

    @Override
    protected LoginActivityPresenter initPresenter() {
        return new LoginActivityPresenter(this);    //實(shí)例化LoginActivity的Presenter
    }

    @Override
    protected void initEvent() {
        mBt_login.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                MyApplication.getInstance().outLog(TAG, "MDZZ");    //日志輸出
                //調(diào)用Presenter的登錄的網(wǎng)絡(luò)請(qǐng)求,將用戶名和密碼傳遞過去
                mPresenter.doLogin(mEt_username.getText().toString(), mEt_password.getText().toString()); 
                


            }
        });
    }

    @Override
    protected void initView() {
        MyApplication.getInstance().addActivity(this);  //將Activity加入堆棧管理
        mEt_username = (TextView) findViewById(R.id.mEt_username);
        mEt_password = (TextView) findViewById(R.id.mEt_password);
        mBt_login = findViewById(R.id.mBt_login);
    }

    @Override
    protected void doOther() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            List<String> permissionsNeeded = new ArrayList<String>();

            final List<String> permissionsList = new ArrayList<String>();
            if (!addPermission(permissionsList, Manifest.permission.WRITE_EXTERNAL_STORAGE))
                permissionsNeeded.add("手機(jī)存儲(chǔ)空間");
            if (!addPermission(permissionsList, Manifest.permission.READ_PHONE_STATE))
                permissionsNeeded.add("獲取手機(jī)狀態(tài)");
            if (!addPermission(permissionsList, Manifest.permission.CAMERA))
                permissionsNeeded.add("手機(jī)相機(jī)");
            if (!addPermission(permissionsList, Manifest.permission.ACCESS_COARSE_LOCATION))
                permissionsNeeded.add("手機(jī)位置");
//            if (!addPermission(permissionsList, Manifest.permission.WRITE_SETTINGS))
//                permissionsNeeded.add("手機(jī)設(shè)置");

            if (permissionsList.size() > 0) {
                if (permissionsNeeded.size() > 0) { //待申請(qǐng)的權(quán)限列表
                    // Need Rationale
                    String message = "你必須允許本APP使用:" + permissionsNeeded.get(0);
                    for (int i = 1; i < permissionsNeeded.size(); i++)
                        message = message + ", " + permissionsNeeded.get(i);
                    showMessageOKCancel(message,
                            new DialogInterface.OnClickListener() {
                                @TargetApi(Build.VERSION_CODES.M)
                                @Override
                                public void onClick(DialogInterface dialog, int which) {
                                    requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                                            REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
                                }
                            });
                    return;
                }
                requestPermissions(permissionsList.toArray(new String[permissionsList.size()]),
                        REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS);
            }
        }
        super.doOther();
    }

    private void showMessageOKCancel(String message, DialogInterface.OnClickListener okListener) {
        new AlertDialog.Builder(this)
                .setMessage(message)
                .setPositiveButton("允許", okListener)
                .setNegativeButton("拒絕", null)
                .create()
                .show();
    }

    private boolean addPermission(List<String> permissionsList, String permission) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            if (checkSelfPermission(permission) != PackageManager.PERMISSION_GRANTED) {
                permissionsList.add(permission);
                if (!shouldShowRequestPermissionRationale(permission))
                    return false;
            }
        }
        return true;
    }

    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        switch (requestCode) {
            case REQUEST_CODE_ASK_MULTIPLE_PERMISSIONS: {
                Map<String, Integer> perms = new HashMap<String, Integer>();
                // Initial
                perms.put(Manifest.permission.WRITE_EXTERNAL_STORAGE, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.READ_PHONE_STATE, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.CAMERA, PackageManager.PERMISSION_GRANTED);
                perms.put(Manifest.permission.ACCESS_COARSE_LOCATION, PackageManager.PERMISSION_GRANTED);
                // Fill with results
                for (int i = 0; i < permissions.length; i++)
                    perms.put(permissions[i], grantResults[i]);
                // Check for ACCESS_FINE_LOCATION
                if (perms.get(Manifest.permission.WRITE_EXTERNAL_STORAGE) == PackageManager.PERMISSION_GRANTED
                        && perms.get(Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED
                        && perms.get(Manifest.permission.CAMERA) == PackageManager.PERMISSION_GRANTED
                        && perms.get(Manifest.permission.ACCESS_COARSE_LOCATION) == PackageManager.PERMISSION_GRANTED) {
                    //經(jīng)過用戶授權(quán)枉氮,獲得所有權(quán)限
                    if (appPermission != null) {
                        appPermission = null;
                    }
                    // All Permissions Granted
                } else {    //未得到用戶授權(quán)
                    // Permission Denied
                    appPermission = new AppUpdateDialog(AppUpdateDialog.IMPORTANT, "一些權(quán)限未被允許志衍,請(qǐng)?jiān)谠O(shè)置中授權(quán)!", getContext(), new AppUpdateDialog.NeedDoThing() {
                        @Override
                        public void mustDoThing() {
                            Uri packageURI = Uri.parse("package:" + getPackageName());
                            Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, packageURI);
                            startActivity(intent);
                        }
                    });
                }
            }
            break;
            default:
                super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        }
    }

    @Override
    protected void onResume() {
        super.onResume();
        doOther();
    }

    @Override
    protected int setLayoutId() {
        return R.layout.activity_login;
    }

    @Override
    public void showDailog(String msg) {
        new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() {
            @Override
            public void mustDoThings() {

            }
        });
    }

    @Override
    public void showProgressD() {
        if (null == myProgressDialog)
            myProgressDialog = new MyProgressDialog("登陸", "正在登錄···", getContext());
        else
            myProgressDialog.show();
    }

    @Override
    public void disProgressD() {
        if (null != myProgressDialog)
            myProgressDialog.dismiss();
    }

    @Override
    public void openHome(LoginBean bean) {

        SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.username), mEt_username.getText().toString());
        SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.password), mEt_password.getText().toString());
        SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.userId), bean.getResult().getUserId());
        SPHelper.setString(getContext(), getContext().getString(R.string.user), getContext().getString(R.string.token), bean.getResult().getToken());

        startActivity(new Intent(getContext(), HomeActivity.class));

        finish();
    }
}

其實(shí)上面我們當(dāng)中可以看到我們前臺(tái)界面拿到用戶數(shù)據(jù)后,調(diào)用presenter的doLogin方法聊替,把用戶名和密碼傳遞過去楼肪,然后我們?cè)赑resenter中請(qǐng)求網(wǎng)絡(luò)然后再通過調(diào)用接口實(shí)現(xiàn)數(shù)據(jù)回傳。如下:

import com.acheng.achengutils.gsonutil.GsonUtils;
import com.acheng.achengutils.mvp.presenter.BasePresenter;
import com.acheng.achengutils.utils.CipherUtils;
import com.acheng.achengutils.utils.StringUtils;
import com.kymjs.rxvolley.RxVolley;
import com.kymjs.rxvolley.client.HttpCallback;
import com.kymjs.rxvolley.client.HttpParams;
import com.kymjs.rxvolley.http.VolleyError;

import acheng1314.cn.a3dbuild.MyApplication;
import acheng1314.cn.a3dbuild.bean.LoginBean;
import acheng1314.cn.a3dbuild.hostApi.MyApi;
import acheng1314.cn.a3dbuild.view.activity.viewcontroller.LoginActivityViewController;

/**
 * Created by pc859107393 on 2016/9/12 0012.
 */
public class LoginActivityPresenter extends BasePresenter<LoginActivityViewController> {
    /**
     * 在子類的構(gòu)造函數(shù)中惹悄,設(shè)定參數(shù)為model春叫,這時(shí)候可以presenter調(diào)用接口來實(shí)現(xiàn)對(duì)界面的操作。
     *
     * @param model
     */
    public LoginActivityPresenter(LoginActivityViewController model) {
        super(model);
    }

    @Override
    public void initData() {

    }

    public void doLogin(String name, String pwd) {
        //用戶名和密碼不能為空
        if (StringUtils.isEmpty(name) || StringUtils.isEmpty(pwd)) {
            model.showDailog("用戶名或密碼不能為空!"); //調(diào)用model的錯(cuò)誤提示對(duì)話框
            return;
        }

        //密碼MD5加密
        pwd = CipherUtils.small32md5(pwd);
        HttpParams params = new HttpParams();
        params.put("userName", name);
        params.put("passWord", pwd);
        RxVolley.post(MyApi.LoginApi, params, new HttpCallback() {
            @Override
            public void onSuccess(String t) {
                super.onSuccess(t);
                //數(shù)據(jù)不為空再進(jìn)行數(shù)據(jù)處理
                try {
                    if (null != t) {
                        MyApplication.getInstance().outLog("輸出", t);
                        LoginBean bean = new GsonUtils().toBean(t, LoginBean.class);
                        if (null != bean) {
                            if (bean.getCode() == 0) {
                                //請(qǐng)求成功
                                model.openHome(bean);
                            } else if (bean.getCode() == 1) {
                                model.showDailog("登錄失敗,帳戶不存在");
                            } else if (bean.getCode() == 2) {
                                model.showDailog("登錄失敗,密碼錯(cuò)誤");
                            } else {
                                model.showDailog("登錄失敗,其他未知錯(cuò)誤");
                            }
                        }
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                    model.showDailog("登錄失敗,其他未知錯(cuò)誤");
                }


            }

            @Override
            public void onFailure(VolleyError error) {
                super.onFailure(error);
                model.showDailog("登錄失敗,其他未知錯(cuò)誤");
            }

            @Override
            public void onFinish() {
                super.onFinish();
                model.disProgressD();   //model的關(guān)閉對(duì)話框的接口
            }

            @Override
            public void onPreStart() {
                super.onPreStart();
                model.showProgressD();  //model的進(jìn)度對(duì)話框
            }
        });


    }
}

我們上面可以看到我們現(xiàn)在只要把請(qǐng)求網(wǎng)絡(luò)的數(shù)據(jù)傳遞上去就可以完成單元測(cè)試了暂殖,這樣子我們就達(dá)到了我們數(shù)據(jù)流轉(zhuǎn)的單元測(cè)試的標(biāo)準(zhǔn)价匠。

既然我們都看到了Presenter對(duì)model的調(diào)用,那么我們直接貼上model再對(duì)比Activity就能明白了我們是怎么完成這個(gè)設(shè)計(jì)的央星。

public interface LoginActivityViewController extends BaseViewController {
    /**
     * 顯示信息提示對(duì)話框
     * @param msg   message
     */
    void showDailog(String msg);

    /**
     * 顯示進(jìn)度對(duì)話框
     */
    void showProgressD();

    /**
     * 關(guān)閉對(duì)話框
     */
    void disProgressD();

    /**
     * 登陸成功跳轉(zhuǎn)到其他界面
     * @param bean
     */
    void openHome(LoginBean bean);
}

我們看到這里,很多哥們可能又會(huì)不明白惫东,為什么我們能控制界面呢莉给?如下:

//我們?cè)诔绦蛑校琾resenter直接調(diào)用的model廉沮,但是model是被View實(shí)現(xiàn)了的颓遏。
public class LoginActivity extends BaseActivity<LoginActivityPresenter, LoginActivityViewController> implements LoginActivityViewController {
    @Override
    public void showDailog(String msg) {
        //實(shí)現(xiàn)了model的顯示對(duì)話框的方法
        new MustDoThingDailog("提示", msg, getContext(), new MustDoThingDailog.NeedDoThing() {
            @Override
            public void mustDoThings() {

            }
        });
    }

    @Override
    public void showProgressD() {
        //這是顯示進(jìn)度對(duì)話框的,實(shí)現(xiàn)了model的方法
    }

    @Override
    public void disProgressD() {
        //這是實(shí)現(xiàn)了moel的關(guān)閉進(jìn)度對(duì)話框的方法
    }

    @Override
    public void openHome(LoginBean bean) {

        //實(shí)現(xiàn)了model的打開其他頁面的方法
    }
}

所以我們的MVP執(zhí)行的步驟其實(shí)就是:用戶執(zhí)行操作 -> 調(diào)用presenter(完成獨(dú)立的數(shù)據(jù)處理) -> 調(diào)用model的方法控制界面 -> 展示給用戶


然后應(yīng)該又有哥們會(huì)問我滞时,為什么你的基類中會(huì)有<>這種括號(hào)括起來的東西叁幢,恩恩這個(gè)是泛型,主要是用來說明他們是哪一類的東西坪稽,通過泛型來解耦就可以在基類中整合更多的東西曼玩。具體的要我來說明的話,我只能說“就不V习佟J蚺小!”篙梢,我需要任性一回顷帖。關(guān)于MVP更好的介紹可以看下github的項(xiàng)目TheMvp,這個(gè)是我的偶像@張濤寫的喲渤滞。

總結(jié)

  • 在mvp架構(gòu)中,我們需要在基類中拿到每個(gè)界面對(duì)應(yīng)的presenter和model,則我們需要讓程序知道每個(gè)對(duì)應(yīng)的presenter和model.
  • 為了減少不必要的代碼開銷,我們需要把每個(gè)activity和Fragment的公共方法抽取出來,寫入基類中.
  • 在基類中,我們需要將具體的presenter和model解耦,則需要泛型進(jìn)行類型轉(zhuǎn)換來解除耦合.
  • 泛型解除耦合后,我們需要在每個(gè)具體的view中來持有presenter和實(shí)現(xiàn)model層的接口.并且通過每個(gè)view關(guān)聯(lián)的presenter調(diào)用model的某個(gè)方法來控制view.
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末贬墩,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子妄呕,更是在濱河造成了極大的恐慌陶舞,老刑警劉巖,帶你破解...
    沈念sama閱讀 219,539評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件绪励,死亡現(xiàn)場(chǎng)離奇詭異吊说,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)优炬,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評(píng)論 3 396
  • 文/潘曉璐 我一進(jìn)店門颁井,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人蠢护,你說我怎么就攤上這事雅宾。” “怎么了葵硕?”我有些...
    開封第一講書人閱讀 165,871評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵眉抬,是天一觀的道長(zhǎng)贯吓。 經(jīng)常有香客問我,道長(zhǎng)蜀变,這世上最難降的妖魔是什么悄谐? 我笑而不...
    開封第一講書人閱讀 58,963評(píng)論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮库北,結(jié)果婚禮上爬舰,老公的妹妹穿的比我還像新娘。我一直安慰自己寒瓦,他們只是感情好情屹,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,984評(píng)論 6 393
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著杂腰,像睡著了一般垃你。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上喂很,一...
    開封第一講書人閱讀 51,763評(píng)論 1 307
  • 那天惜颇,我揣著相機(jī)與錄音,去河邊找鬼少辣。 笑死官还,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的毒坛。 我是一名探鬼主播望伦,決...
    沈念sama閱讀 40,468評(píng)論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼煎殷!你這毒婦竟也來了屯伞?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,357評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤豪直,失蹤者是張志新(化名)和其女友劉穎劣摇,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體弓乙,經(jīng)...
    沈念sama閱讀 45,850評(píng)論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡末融,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,002評(píng)論 3 338
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了暇韧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片勾习。...
    茶點(diǎn)故事閱讀 40,144評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖懈玻,靈堂內(nèi)的尸體忽然破棺而出巧婶,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 35,823評(píng)論 5 346
  • 正文 年R本政府宣布艺栈,位于F島的核電站英岭,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏湿右。R本人自食惡果不足惜诅妹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,483評(píng)論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望毅人。 院中可真熱鬧吭狡,春花似錦、人聲如沸堰塌。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽场刑。三九已至,卻和暖如春蚪战,著一層夾襖步出監(jiān)牢的瞬間牵现,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評(píng)論 1 272
  • 我被黑心中介騙來泰國(guó)打工邀桑, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留瞎疼,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,415評(píng)論 3 373
  • 正文 我出身青樓壁畸,卻偏偏與公主長(zhǎng)得像贼急,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子捏萍,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,092評(píng)論 2 355

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