新手也能看得懂的 Android MVP 講解

前言

作為菜鳥一只,學(xué)習(xí)的新知識都要記下來歧强,以便日后復(fù)習(xí)鲤看。

本文側(cè)重點在于介紹 Android MVP 的優(yōu)劣朝卒,通過 Google 官方的to-do-mvp 系列項目了解官方是如何使用 MVP 的博烂,并通過自己動手寫一個小小的 MVP-demo 來加深對該模式的理解香椎。

不廢話了,下面進入正文禽篱。

MVC

談到 MVP畜伐,就不能不提它的“前身”- MVC,但為了更好的了解躺率,我們還需要向上追溯到 三層架構(gòu)

  1. 界面層:與用戶交互的界面
  2. 業(yè)務(wù)邏輯層:界面層和數(shù)據(jù)訪問層的橋梁玛界,實現(xiàn)業(yè)務(wù)邏輯。
  3. 數(shù)據(jù)訪問層:和數(shù)據(jù)庫打交道悼吱,類似DAO慎框。

而 MVC 實際上更多只涉及前兩層,目的在于解除業(yè)務(wù)邏輯和視圖之間的耦合后添”靠荩可以說不只是 Android ,甚至在整個軟件開發(fā)中都是使用最廣的系統(tǒng)架構(gòu)之一遇西。

MVC 將整個結(jié)構(gòu)分為三個組件--Model馅精、View、Controller

  1. Model(模型):Model 是應(yīng)用程序的數(shù)據(jù)源粱檀,同時包括對業(yè)務(wù)邏輯的封裝洲敢。它接受 Controller 的請求并完成相應(yīng)的業(yè)務(wù)處理,并將處理后的數(shù)據(jù)通過 View 顯示給用戶茄蚯。數(shù)據(jù)源可以是Web压彭、本地數(shù)據(jù)庫(sqlite)等。
  2. View(視圖):該組件直接與用戶交互渗常,并負(fù)責(zé)用戶如何查看我們的應(yīng)用程序壮不。View 可以直接與 Model 進行交互,在MVC中凳谦,XML(也可以說是 Activity )被視為視圖。
  3. Controller(控制器):這是MVC模式的重要部分衡未,Controller是操作尸执、編輯、使用 Model 并通過 View 顯示給用戶的組件缓醋。Controller 負(fù)責(zé)收集所有數(shù)據(jù)如失,在Model 和 View 之間充當(dāng)中間人。Activity/Fragment 被認(rèn)為是Android 的 Controller 送粱。
MVC

一句話概括 MVC 的工作機理就是:當(dāng) User 觸發(fā)事件時褪贵,View 發(fā)送指令到Controller,之后 Controller 通知 Model 更新數(shù)據(jù),之后將結(jié)果顯示到 View 中脆丁。

過程很理想是吧世舰?但是在 Android 卻并不怎么令人滿意,我們來看看在 Android 中是個什么情況槽卫。

首先 布局.xml 毫無疑問是 View 吧跟压,然后一些 java bean 之類的就是 Model,而 Controller 則是 Activity/Fragment 咯歼培,但是理想很豐滿震蒋,現(xiàn)實很骨感,作為 View 而言躲庄,xml 顯然是不能勝任的查剖,它只能展示最基礎(chǔ)的靜態(tài)界面,比如當(dāng)我們動態(tài)隱藏顯示一個界面時候噪窘,我們必須在 Activity/Fragment 中去實現(xiàn)笋庄,這也就導(dǎo)致了 Activity/Fragment 既是 Controller 但是又承擔(dān)了一部分 View 的工作。

可以說 Android 中的 MVC 只做到了 M-V效览,因為所有一切都和 Activity 緊密相連无切。

MVC 在 Android 中的表現(xiàn)大致如下:

上述結(jié)果就是,Activity 中的代碼輕輕松松上千行丐枉。如果只是我們自己寫哆键,自己維護的話,上千行似乎并非不能接受瘦锹。但是一但需要你去看別人的上千行代碼籍嘹,想想就很難受。(更別說需要研讀 Android 破萬行的源碼了弯院。辱士。)

為了解決這個重大問題,我們需要將 Activity 承擔(dān)的工作拆分听绳,Activity 只控制 View颂碘,另外新建一個 Controller ,以此避免 Activity 越來越大椅挣,難以維護头岔。

于是就衍生出了 MVP。

MVP

MVP 作為 MVC 的衍生鼠证,將 Controller 和 View 從 Activity 中分割開開筒捺。對于 Android 來說边败,MVP 的 Model 和 MVC 中的 Model 是一樣的,而 Activity/Fragment 不再是 Controller,而是純粹的 View,所有關(guān)于用戶事件的處理都通過 Presenter。

1*1P4n9JkHChEUVr5umQx4Zw

我們可以看到,最明顯的差別就是 Model 和 View 不再相連,取而代之的是 Presenter 在二者之間充當(dāng)橋梁载城,分別與 Model 和 View 雙向通信。

工作流程大致為:

  1. View 接受用戶的交互請求
  2. View 將事件傳遞到 Presenter
  3. Presenter 操作 Model 進行數(shù)據(jù)處理
  4. Model 處理完成后戚宦,通知 Presenter 處理已完成
  5. Presenter 根據(jù)處理后的數(shù)據(jù)更新 View 的顯示

至于 Presenter 如何與 Model个曙、View 交互的,還記得設(shè)計模式中提到的 面向接口編程 的思想么受楼?沒錯垦搬,這里我們也是采取接口的形式,比如 Activity/Fragment 實現(xiàn)已經(jīng)定義好的接口艳汽,在對應(yīng)的 Presenter 中通過接口調(diào)用方法猴贰。

下面我們就看一看 Google 為我們提供的 MVP 示例中是如何編程的。

如果讀者對 面向接口編程 的思想不了解河狐,那么建議先去Google 一下米绕,有基本的了解之后,再繼續(xù)閱讀馋艺,不然只能是徒增痛苦栅干。。捐祠。碱鳞。

Google todo-mvp 項目介紹

Google 在 GayHub 上推出了一個項目 Android Architecture Blueprints,用來展示 Android 使用各種各樣的 MVP 架構(gòu)踱蛀,雖然Google 表示其中的示例只是用來做參考窿给,并不是要做標(biāo)準(zhǔn),但是作為 Google 腦殘粉率拒,相信 Google 出品崩泡,必屬精品

項目中設(shè)計多種架構(gòu)猬膨,但作為菜雞一枚角撞,還是只從最基礎(chǔ)的 todo-mvp 入手,分析如何實現(xiàn) MVP 架構(gòu)勃痴。

總體結(jié)構(gòu)

這里寫圖片描述

以 StatisticsContract 為例(其他類似):

這里寫圖片描述

基類

//在 Fragment 的 onResume()中調(diào)用方法谒所,作用是 presenter 開始獲取數(shù)據(jù)并調(diào)用 view 中方法改變界面顯示
public interface BasePresenter {
    void start();
}

//在 presenter 實現(xiàn)類中調(diào)用方法,作用是將 presenter 實例傳入 view 中
public interface BaseView<T> {
    void setPresenter(T presenter);
}

“契約類”-XXXContract

public interface StatisticsContract {

    interface View extends BaseView<Presenter> {

        void setProgressIndicator(boolean active);

        void showStatistics(int numberOfIncompleteTasks, int numberOfCompletedTasks);

        void showLoadingStatisticsError();

        boolean isActive();
    }

    interface Presenter extends BasePresenter {

    }
}

Google 似乎很喜歡這種寫法召耘,編寫一個契約類來管理 View 和 Presenter 的所有接口百炬,這種方式使得我們很清楚的知道這二者有哪些功能褐隆,方便維護污它。

Activity 的作用

在子模塊中扮演著 模塊管理者 的角色,負(fù)責(zé)創(chuàng)建 Presenter 實例以及 創(chuàng)建 View(Fragment),并將二者聯(lián)系起來:

@Override
protected void onCreate(Bundle savedInstanceState) {
    //衫贬。德澈。。固惯。
    StatisticsFragment statisticsFragment = (StatisticsFragment) getSupportFragmentManager()
        .findFragmentById(R.id.contentFrame);
    if (statisticsFragment == null) {
        statisticsFragment = StatisticsFragment.newInstance();
        ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),statisticsFragment,         R.id.contentFrame);
    }

    new StatisticsPresenter(
        Injection.provideTasksRepository(getApplicationContext()), statisticsFragment);
    //梆造。。葬毫。镇辉。
}

在創(chuàng)建 StatisticsPresenter 時,我們傳入了 statisticsFragment贴捡,再看看 StatisticsPresenter 的構(gòu)造函數(shù):

public StatisticsPresenter(@NonNull TasksRepository tasksRepository,
                           @NonNull StatisticsContract.View statisticsView) {
    mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
    mStatisticsView = checkNotNull(statisticsView, "StatisticsView cannot be null!");

    mStatisticsView.setPresenter(this);
}

也就是說 這時 StatisticsPresenter 獲取到了 statisticsFragment 的引用忽肛,且其實現(xiàn)了 view 接口,那么就可以調(diào)用 view 的方法了烂斋。

View <--> Presenter

分析一下 View 如何與 Presenter 雙向通信屹逛,上源碼:

public class StatisticsFragment extends Fragment implements StatisticsContract.View {

    private TextView mStatisticsTV;

    private StatisticsContract.Presenter mPresenter;

    public static StatisticsFragment newInstance() {
        return new StatisticsFragment();
    }

    @Override
    public void setPresenter(@NonNull StatisticsContract.Presenter presenter) {
        mPresenter = checkNotNull(presenter);
    }
    
    @Override
    public void onResume() {
        super.onResume();
        mPresenter.start();
    }
    
    //。汛骂。罕模。。
}

可以看到帘瞭,F(xiàn)ragment 作為 View 淑掌,同時在 setPresenter 方法中得到 Presenter 實例(結(jié)合 Presenter 的構(gòu)造方法),從而可以調(diào)用 Presenter 中的方法图张。

而上面我們也提到過在 presenter 的構(gòu)造方法中獲取到了 fragment 也就是view 的引用锋拖。于是 二者就可以互相通信了。

Model <--> Presenter

分析完 View-Presenter祸轮,再來看看 Model 如何與 Presenter 雙向交互兽埃,源碼:

public class StatisticsPresenter implements StatisticsContract.Presenter {

    private final TasksRepository mTasksRepository;

    public StatisticsPresenter(@NonNull TasksRepository tasksRepository,
                               @NonNull StatisticsContract.View statisticsView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mStatisticsView = checkNotNull(statisticsView, "StatisticsView cannot be null!");

        mStatisticsView.setPresenter(this);
    }

    @Override
    public void start() {
        loadStatistics();
    }

    private void loadStatistics() {
        mStatisticsView.setProgressIndicator(true);

        // The network request might be handled in a different thread so make sure Espresso knows
        // that the app is busy until the response is handled.
        EspressoIdlingResource.increment(); // App is busy until further notice

        mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                int activeTasks = 0;
                int completedTasks = 0;

                //.....
                mStatisticsView.setProgressIndicator(false);

                mStatisticsView.showStatistics(activeTasks, completedTasks);
            }

            @Override
            public void onDataNotAvailable() {
                // The view may not be able to handle UI updates anymore
                if (!mStatisticsView.isActive()) {
                    return;
                }
                mStatisticsView.showLoadingStatisticsError();
            }
        });
    }
}

上述代碼中的 TasksRepository 即為 Model,仍然是在構(gòu)造函數(shù)中獲取到其引用适袜,同時在 loadStatistics() 方法中調(diào)用 mTasksRepository.getTasks()方法柄错,體現(xiàn)的 presenter 調(diào)用 model 的方法;同時 getTasks() 方法中傳入了 TasksDataSource.LoadTasksCallback() 參數(shù)苦酱,該接口定義如下:

public interface TasksDataSource {

    interface LoadTasksCallback {

        void onTasksLoaded(List<Task> tasks);

        void onDataNotAvailable();
    }
    //售貌。。疫萤。颂跨。

而在 TasksRepository(Model)中 getTasks() 方法定義如下:

public class TasksRepository implements TasksDataSource {
@Override
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        checkNotNull(callback);

        // Respond immediately with cache if available and not dirty
        if (mCachedTasks != null && !mCacheIsDirty) {
            callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            return;
        }

        if (mCacheIsDirty) {
            // If the cache is dirty we need to fetch new data from the network.
            getTasksFromRemoteDataSource(callback);
        } else {
            // Query the local storage if available. If not, query the network.
            mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                @Override
                public void onTasksLoaded(List<Task> tasks) {
                    refreshCache(tasks);
                    callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                }

                @Override
                public void onDataNotAvailable() {
                    getTasksFromRemoteDataSource(callback);
                }
            });
        }
    }
    //。扯饶。恒削。池颈。。
}

也就是說钓丰,在getTasks()方法中躯砰,TasksRepository 處理完數(shù)據(jù)之后,回調(diào)了 StatisticsPresenter 中實現(xiàn)的方法携丁,也就體現(xiàn)了 Model -> Presenter琢歇。

小結(jié)

到這里,關(guān)于 todo-mvp 的架構(gòu)就分析的差不多了梦鉴,總體上看李茫,MVP 的使用使得整個結(jié)構(gòu)十分的清晰,畢竟我這樣的菜鳥都能去分析源碼了肥橙,雖然代碼量略微增多涌矢,但是每個模塊的界限很清晰,責(zé)任單一快骗,高度的解耦娜庇,使得維護起來很輕松。

動手?jǐn)]一個 Demo

上面我們分析了Google的源碼方篮,但看懂畢竟只是看懂名秀,距離我們深入理解還差得遠(yuǎn),下面就動手實踐一下藕溅,擼一個 MVP 的簡易 Demo匕得。

Demo 地址見文章結(jié)尾。

先放上最終的效果:


這里寫圖片描述

接下來是 Demo 的結(jié)構(gòu)圖:

這里寫圖片描述

下面我們就來一點點實現(xiàn)巾表。

Model

首先肯定需要有一個實體類 User汁掠,然后對于這個 Demo,業(yè)務(wù)邏輯只有一個:登錄集币,那么我們也就至少有一個 login() 方法來實現(xiàn)登錄業(yè)務(wù)考阱。同時,我還需要能告知 presenter 我是否登陸成功了鞠苟,那么我就需要寫一個 回調(diào)接口 供 presenter 來實現(xiàn)乞榨。

User.java

package com.bit.whdalive.demomvp.bean;

public class User {

    private String username;
    private String password;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

IUserModel.java

package com.bit.whdalive.demomvp.mvp;

public interface IUserModel {

    void login(String username,String password,OnLoginListener listener);
    
    //回調(diào)接口,我放到IUserModel中当娱,實際上也可以單獨抽離出來吃既,或者放到 presenter 的接口中都是可以的,畢竟這個demo功能太單一了
    public interface OnLoginListener{

        void loginSuccess();

        void loginFailed();
    }
}

IUserModelImpl.java

package com.bit.whdalive.demomvp.mvp;

public class UserModelImpl implements IUserModel{

    private IUserLoginPresenter mIUserLoginPresenter;

    public UserModelImpl(IUserLoginPresenter IUserLoginPresenter) {
        mIUserLoginPresenter = IUserLoginPresenter;
    }

    @Override
    public void login(final String username, final String password,final IUserModel.OnLoginListener listener) {
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if("whdalive".equals(username)&&"123...".equals(password)){
                    User user = new User();
                    user.setUsername(username);
                    user.setPassword(password);
                    listener.loginSuccess();
                }else{
                    listener.loginFailed();
                }
            }
        }.start();
    }
}

依舊是 面向接口編程 的思想跨细,將 Model 層的方法抽離成一個接口鹦倚,日后彼此交互也都是通過傳遞接口類型的引用,避免強耦合冀惭。

View

我們考慮一下 View 中應(yīng)該有哪些功能震叙,首先效果圖中有兩個按鈕愤诱,login 和 clear。

想要實現(xiàn) login 捐友,就需要能夠提供我們輸入的文本,對應(yīng)如下方法:

String getUserName();

String getPassword();

實現(xiàn) clear溃槐,那么意味著 View 需要有清除 輸入文本 的功能匣砖,也就是需要如下兩個方法:

void clearUserName();

void clearPassword();

同時,我們看到登錄時有個 Progressbar 來提示登錄過程(畢竟實際上這是個耗時的過程)昏滴,那么就需要能夠顯示和隱藏它:

void showLoading();

void hideLoading();

最后猴鲫,無論我們登錄成功與否,都需要有個提示顯示我們是否登錄成功了:

void toMainActivity();

void showFailedError();

綜上谣殊,完整的接口定義為:

IUserLoginView

package com.bit.whdalive.demomvp.mvp;

public interface IUserLoginView {

    String getUserName();

    String getPassword();

    void clearUserName();

    void clearPassword();

    void showLoading();

    void hideLoading();

    void toMainActivity();

    void showFailedError();

}

接下來就是寫它的實現(xiàn)類了(實際上就是個純碎的Activity)

UserLoginActivity

package com.bit.whdalive.demomvp.mvp;

public class UserLoginActivity extends AppCompatActivity implements IUserLoginView {

    private EditText mEdtUsername,mEdtPwd;
    private Button mBtnLogin,mBtnClear;
    private ProgressBar mPbLoading;

    private IUserLoginPresenter mIUserLoginPresenter;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        initViews();
    }
    private void initViews(){
        mIUserLoginPresenter = new UserLoginPresenterImpl(this);

        mEdtUsername = findViewById(R.id.input_account);
        mEdtPwd = findViewById(R.id.input_password);

        mBtnClear = findViewById(R.id.btn_clear);
        mBtnLogin = findViewById(R.id.btn_login);

        mPbLoading = findViewById(R.id.pb_loading);

        mBtnLogin.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mIUserLoginPresenter.doLogin();
            }
        });

        mBtnClear.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mIUserLoginPresenter.clear();
            }
        });

    }

    @Override
    public String getUserName() {
        return mEdtUsername.getText().toString();
    }

    @Override
    public String getPassword() {
        return mEdtPwd.getText().toString();
    }

    @Override
    public void clearUserName() {
        mEdtUsername.setText("");
    }

    @Override
    public void clearPassword() {
        mEdtPwd.setText("");
    }

    @Override
    public void showLoading() {
        mPbLoading.setVisibility(View.VISIBLE);
    }

    @Override
    public void hideLoading() {
        mPbLoading.setVisibility(View.GONE);
}

    @Override
    public void toMainActivity() {
        Toast.makeText(this,"login success, to MainActivity",Toast.LENGTH_SHORT).show();

    }

    @Override
    public void showFailedError() {
        Toast.makeText(this,"Login failed",Toast.LENGTH_SHORT).show();
    }

}

對于 View 而言拂共,因為其只和 用戶的交互 打交道,因此我們只需要考慮好 哪些操作需要改動界面顯示姻几?哪些操作需要什么反饋宜狐? 并以此來編寫對應(yīng)方法并抽象成接口,而一旦寫好了接口蛇捌,那么實現(xiàn)類就是手到擒來的事情了抚恒。

Presenter

Presenter 作為 Model 和 View 的橋梁,需要能夠調(diào)用 Model 的方法络拌,來執(zhí)行具體的業(yè)務(wù)方法俭驮;同時需要調(diào)用 View 的方法,來更新界面春贸。

在這個 Demo 中混萝,就只有兩個功能可言:doLogin 和 clear。

對于 doLogin 實際就是調(diào)用了 Model 中的 login方法萍恕,clear 則是調(diào)用了 View 中的 clearUserName() 和 clearPassword() 來清除文本逸嘀。

IUserLoginPresenter.java

package com.bit.whdalive.demomvp.mvp;

public interface IUserLoginPresenter {
    
    void doLogin();
    
    void clear();

}

UserLoginPresenterImpl.java

package com.bit.whdalive.demomvp.mvp;

import android.os.Handler;

public class UserLoginPresenterImpl implements IUserLoginPresenter,IUserModel.OnLoginListener {

    private IUserLoginView mIUserLoginView;
    private IUserModel mIUserModel;

    private Handler mHandler = new Handler();

    public UserLoginPresenterImpl(IUserLoginView IUserLoginView) {
        mIUserLoginView = IUserLoginView;
        mIUserModel = new UserModelImpl(this);
    }

    @Override
    public void doLogin() {
        String username = mIUserLoginView.getUserName();
        String password = mIUserLoginView.getPassword();
        mIUserLoginView.showLoading();
        mIUserModel.login(username,password,this);
    }

    @Override
    public void loginSuccess() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mIUserLoginView.hideLoading();
                mIUserLoginView.toMainActivity();
            }
        });
    }

    @Override
    public void loginFailed() {
        mHandler.post(new Runnable() {
            @Override
            public void run() {
                mIUserLoginView.hideLoading();
                mIUserLoginView.showFailedError();
            }
        });

    }

    @Override
    public void clear() {
        mIUserLoginView.clearUserName();
        mIUserLoginView.clearPassword();
    }
}

上述代碼中,由于 Model 需要通知 Presenter 是否登陸成功允粤,因此 presenter 實現(xiàn)了 IUserModel.OnLoginListener 接口厘熟。

同時由于Presenter 分別和 Model、View 雙向通信维哈,因此 Presenter 持有后兩者的引用绳姨,而 Model和View彼此不持有對方的引用,都只有 Presenter 的引用阔挠。

契約類寫法

當(dāng)然如果讀者偏愛于 契約類Contracts 的寫法飘庄,問題也不大:

LoginContract.java

package com.bit.whdalive.demomvp.mvp_contracts;

import com.bit.whdalive.demomvp.bean.User;

public interface LoginContract {

    public interface View {

        String getUserName();

        String getPassword();

        void clearUserName();

        void clearPassword();

        void showLoading();

        void hideLoading();

        void toMainActivity();

        void showFailedError();
    }

    public interface Presenter {
        
        void login();

        void clear();
    }
}

之后,在實現(xiàn)類中對實現(xiàn)接口的名字從 IUserLoginPresenter/IUserLoginView 改為 LoginContract.Presenter/LoginContract.View 即可购撼。

小結(jié)

最后以 Login 登錄功能捋順一下該demo的執(zhí)行流程:

  1. 用戶輸入賬號密碼
  2. 點擊Login按鈕跪削,View 將該事件傳遞給 Presenter
  3. Presenter 接收到 login 請求谴仙,從 View 中提取 賬號密碼文本,并一并交給 Model 執(zhí)行具體的 login 操作(也就是調(diào)用 Model 的login()方法)
  4. Model 執(zhí)行 login 操作后碾盐,通知 Presenter 是否登錄成功
  5. Presenter 接收到 Model 的反饋晃跺,通知 View 更新頁面
  6. View 根據(jù) Presenter 的指令,更改當(dāng)前頁面毫玖。

上面我們就通過一步步拆解掀虎,逐步講解如何手?jǐn)]一個簡易的Demo,讀者也可以自己找一些簡單的場景加以練習(xí)付枫。

附上 Demo 地址:

GitHub-whdalive:Demo-MVP

最后烹玉,愿本文對大家有所幫助。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阐滩,一起剝皮案震驚了整個濱河市二打,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌掂榔,老刑警劉巖继效,帶你破解...
    沈念sama閱讀 221,635評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異装获,居然都是意外死亡莲趣,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,543評論 3 399
  • 文/潘曉璐 我一進店門饱溢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喧伞,“玉大人,你說我怎么就攤上這事绩郎∨琐辏” “怎么了?”我有些...
    開封第一講書人閱讀 168,083評論 0 360
  • 文/不壞的土叔 我叫張陵肋杖,是天一觀的道長溉仑。 經(jīng)常有香客問我,道長状植,這世上最難降的妖魔是什么浊竟? 我笑而不...
    開封第一講書人閱讀 59,640評論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮津畸,結(jié)果婚禮上振定,老公的妹妹穿的比我還像新娘。我一直安慰自己肉拓,他們只是感情好后频,可當(dāng)我...
    茶點故事閱讀 68,640評論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般卑惜。 火紅的嫁衣襯著肌膚如雪膏执。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,262評論 1 308
  • 那天露久,我揣著相機與錄音更米,去河邊找鬼。 笑死毫痕,一個胖子當(dāng)著我的面吹牛征峦,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播镇草,決...
    沈念sama閱讀 40,833評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瘤旨!你這毒婦竟也來了梯啤?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,736評論 0 276
  • 序言:老撾萬榮一對情侶失蹤存哲,失蹤者是張志新(化名)和其女友劉穎因宇,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體祟偷,經(jīng)...
    沈念sama閱讀 46,280評論 1 319
  • 正文 獨居荒郊野嶺守林人離奇死亡察滑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,369評論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了修肠。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片贺辰。...
    茶點故事閱讀 40,503評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖嵌施,靈堂內(nèi)的尸體忽然破棺而出饲化,到底是詐尸還是另有隱情,我是刑警寧澤吗伤,帶...
    沈念sama閱讀 36,185評論 5 350
  • 正文 年R本政府宣布吃靠,位于F島的核電站,受9級特大地震影響足淆,放射性物質(zhì)發(fā)生泄漏巢块。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,870評論 3 333
  • 文/蒙蒙 一巧号、第九天 我趴在偏房一處隱蔽的房頂上張望族奢。 院中可真熱鬧,春花似錦丹鸿、人聲如沸歹鱼。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,340評論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弥姻。三九已至南片,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庭敦,已是汗流浹背疼进。 一陣腳步聲響...
    開封第一講書人閱讀 33,460評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留秧廉,地道東北人伞广。 一個月前我還...
    沈念sama閱讀 48,909評論 3 376
  • 正文 我出身青樓,卻偏偏與公主長得像疼电,于是被迫代替她去往敵國和親嚼锄。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,512評論 2 359

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