《一个Android工程的从零开始》阶段总结与修改6-BaseNetActivity

先扯两句

前几篇博客把之前的BaseActivity做了拆解,分成了BaseActivity与BaseLayoutActivity,而BaseNetActivity确实也在之前做了一定的去耦合处理,虽然没有发对应的博客,但是如果有关注我github的朋友或许会发现,那就是封装了一个NetUtils类,将原本Header的封装,Retrofit的调用封装全都拆解了出来,只对应GET、POST请求暴露了两个方法。不过当前的封装实际上,还是针对的mvc框架。毕竟网络请求还是没有脱离BaseNetActivity与BaseNetFragment,无法达到进一步的去耦合,彻底摆脱Activity与Fragment的限制。于是查了一下MVP框架与MVVM框架。当然,我这种懒汉性格,不会去看什么分析文档了,那么正式的纯文字,看两段我就睡给他看。所以就找了些demo类的:

  1. JesseBraveManAndroid MVP架构搭建
  2. 江南一点雨玩转Android之MVVM开发模式实战,炫酷的DataBinding!
  3. SoloHoAndroid,DataBinding的官方双向绑定

大家按照上面的博客直接敲代码就能实现最基本的操作,余下的部分就是适配自己的底层环境以及业务逻辑,这些内容我也会后续跟进学习,只是当前《一个Android工程的从零开始》还是比较基础的部分,甚至大学生之间拿来简单搭建个毕业设计应该都可以,个人感觉说明的还算详细了(除了博客更新慢点。。。)。所以关于MVP和MVVM框架暂时就不集成进来了,当前如果大家使用过程中,真的遇到有需要从adapter或者是自定义View中进行网络请求的,可以参考BaseNetActivity和BaseNetFragment自行封装一个BaseNetAdapter或者BaseNetView。再简单点,参考BaseNetActivity和BaseNetFragment直接给NetUtils传递参数即可(其实个人感觉MVP的BasePresenter与我的NetUtils也差不多)。
好了,闲言少叙,老规矩还是先上我的Git,然后开始正文吧。 MyBaseApplication (https://github.com/BanShouWeng/MyBaseApplication)

正文

其实这部分,对于之前看过我博客的朋友来说,其实没有什么新意,就像上面扯的一样,只是把大多数公共部分提出来而已,先说明一下我这里的网络框架是使用的Retrofit2,大家如果使用其他网络框架的话,就辛苦自行替换一下喽。首先把在初级阶段不那么常用的部分拿到前面,那就是header:

private Map<String, String> headerParams;

/**
 * 初始化请求头,具体情况根据需求设置
 */
public void initHeader() {
    headerParams = new HashMap<>();
    // 传参方式为json
    headerParams.put("Content-Type", "application/json");
}

这就是传说中的header了,是不是超简单!!!好吧,我承认还需要调用,不过就是传个参数喽,关于Retrofit Service的封装,大家可以看一下我之前的博客,
《一个Android工程的从零开始》-8、base(七) Retrofit的封装,如果不想看,直接去Git找源码就好,链接你知道的,就在上面。

如果后台需要其他的header参数,对应添加就好,都是键值对的方式,至于有没有更复杂的header方式,我猜是有,不过反正至今没遇到,那就管他去死。

当然,这里呢当前这条header的意思是要传的数据类型是json(只针对POST请求,GET不受影响),需要说明一点的是,其实这里设置json,服务器收到的请求还不是json而是key—value形式(忘记了默认格式是不是formdata了),具体如何传json,大家可以看一下我的这篇博客——《一个Android工程的从零开始》阶段总结与修改2-Retrofit 上传JSON及尾址特殊字符转译问题,当然,也有过一个朋友,使用我的框架,去调取了“聚合数据”的接口,结果POST请求无法正常获取数据,最后查证,是因为“聚合数据”的那个接口POST请求不支持JSON格式,只能还原到原本的key-value格式,所以大家在使用框架的时候一定要具体问题具体分析,尤其是请求方式,最好能与后台约定好,不然麻烦也算不上,但至少要多很多操作。

好了跑题够远,该收回来了,上面的部分就是header的设置,对我而言,一般都会跟后台约束好header字段,然后统一传参,对方用不上可以不取。不过万一遇到一些严谨的公司,就要有好几套不同的header,也禁止传无用的数据,那就需要提供一个自定义header的入口,也比较好实现,相信能进入这种严谨公司的人完成这部分调整也不在话下,我就不班门弄斧,直接进行下一部分了。

/**
 * 初始化数据
 *
 * @param action 当前请求的尾址
 */
private Retrofit initBaseData(final String action) {
    // 监听请求条件
    OkHttpClient.Builder builder = new OkHttpClient.Builder();
    builder.connectTimeout(5, TimeUnit.SECONDS);
    builder.addInterceptor(new Interceptor() {
        @Override
        public okhttp3.Response intercept(Chain chain) throws IOException {
            Request request = chain.request();
            Logger.i("zzz", "request====" + action);
            Logger.i("zzz", "request====" + request.headers().toString());
            Logger.i("zzz", "request====" + request.toString());
            okhttp3.Response proceed = chain.proceed(request);
            Logger.i("zzz", "proceed====" + proceed.headers().toString());
            return proceed;
        }
    });

    Retrofit.Builder builder1 = new Retrofit.Builder()
            .client(builder.build())                                    // 配置监听请求
            .addConverterFactory(GsonConverterFactory.create())         // 请求结果转换(当前为GSON)
            .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 请求接受工具(当前为RxJava2)
    builder1.baseUrl(BuildConfig.BASE_URL + action.substring(0, action.lastIndexOf("/") + 1));

    return builder1.build();
}

这里主要是Retrofit的一些配置,通过注释,大家也能了解到具体都是做什么功能的,还是不加以赘述了。只是说一点,那就是OkHttpClient的部分,注释是“监听请求条件”,也就是将我们向平台发送的数据做了一下回显,这样开发过程中,一旦出现什么错误,也方便查找。说的猥琐点,至少出问题了能做个判断,自己没错的时候也方便甩锅不是!

但是项目正式上线的时候,一定要把这个部分干掉,我是因为release版和debug版通过自己封装的Logger做了处理,大家如果使用系统的Log,一定要想着这部分,不然打开Android Studio运行一下APP,我们请求的数据就都暴露在对方眼前了。再加上后面返回数据的输出,那我们的项目就完完全全成透明的了。虽然说即便如此,他人使用代理一样可以抓包,但是毕竟相对于前者来说,会玩代理的还是少数,老板来问,我们也只能说非战之过了。

/**
 * Get请求
 *
 * @param action   请求接口的尾址
 * @param params   索要传递的参数
 * @param observer 求情观察者
 */
public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
    RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
    if (params == null) {
        params = new HashMap<>();
    }

    if (null == headerParams){
        headerParams = new HashMap<>();
    }

    Logger.i("zzz", "request====" + new JSONObject(params));

    getService.getResult(action.substring(action.lastIndexOf("/") + 1), headerParams, params)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}

/**
 * Post请求
 *
 * @param action   请求接口的尾址
 * @param observer 求情观察者
 */
public void post(final String action, String json, Observer<ResponseBody> observer) {
    RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
    RequestBody requestBody =
            RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
                    json);

    Logger.i("zzz", "request====" + json);

    jsonService.postResult(action.substring(action.lastIndexOf("/") + 1), requestBody)
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(observer);
}

上面两个方法分别是对GET的封装以及对POST(参数格式JSON)的封装,毕竟只是个demo,所以这部分我GET中使用的是传header的形式,而POST中使用的是不需要传header的形式,大家在使用的时候,针对应业务需求统一一下即可。以上也就是NetUtils中的全部内容,完整代码如下:

public class NetUtils {
    private Map<String, String> headerParams;

    /**
     * 初始化请求头,具体情况根据需求设置
     */
    public void initHeader() {
        headerParams = new HashMap<>();
        // 传参方式为json
        headerParams.put("Content-Type", "application/json");
    }

    /**
     * 初始化数据
     *
     * @param action 当前请求的尾址
     */
    private Retrofit initBaseData(final String action) {
        // 监听请求条件
        OkHttpClient.Builder builder = new OkHttpClient.Builder();
        builder.connectTimeout(5, TimeUnit.SECONDS);
        builder.addInterceptor(new Interceptor() {
            @Override
            public okhttp3.Response intercept(Chain chain) throws IOException {
                Request request = chain.request();
                Logger.i("zzz", "request====" + action);
                Logger.i("zzz", "request====" + request.headers().toString());
                Logger.i("zzz", "request====" + request.toString());
                okhttp3.Response proceed = chain.proceed(request);
                Logger.i("zzz", "proceed====" + proceed.headers().toString());
                return proceed;
            }
        });

        Retrofit.Builder builder1 = new Retrofit.Builder()
                .client(builder.build())                                    // 配置监听请求
                .addConverterFactory(GsonConverterFactory.create())         // 请求结果转换(当前为GSON)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create()); // 请求接受工具(当前为RxJava2)
        builder1.baseUrl(BuildConfig.BASE_URL + action.substring(0, action.lastIndexOf("/") + 1));

        return builder1.build();
    }

    /**
     * Get请求
     *
     * @param action   请求接口的尾址
     * @param params   索要传递的参数
     * @param observer 求情观察者
     */
    public void get(final String action, Map<String, String> params, Observer<ResponseBody> observer) {
        RetrofitGetService getService = initBaseData(action).create(RetrofitGetService.class);
        if (params == null) {
            params = new HashMap<>();
        }

        if (null == headerParams){
            headerParams = new HashMap<>();
        }

        Logger.i("zzz", "request====" + new JSONObject(params));

        getService.getResult(action.substring(action.lastIndexOf("/") + 1), headerParams, params)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }

    /**
     * Post请求
     *
     * @param action   请求接口的尾址
     * @param observer 求情观察者
     */
    public void post(final String action, String json, Observer<ResponseBody> observer) {
        RetrofitPostJsonService jsonService = initBaseData(action).create(RetrofitPostJsonService.class);
        RequestBody requestBody =
                RequestBody.create(MediaType.parse("application/json; charset=utf-8"),
                        json);

        Logger.i("zzz", "request====" + json);

        jsonService.postResult(action.substring(action.lastIndexOf("/") + 1), requestBody)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(observer);
    }
}

而这部分搞定之后,下面就要回到我们的BaseNetActivity中了,其实BaseNetActivity也是相对简单的,因为我们已经有了NetUtils中的get方法以及post方法,其他的只需要你对应业务做一下处理即可。所以第一步是什么呢?恭喜你,答对了,就是在BaseNetActivity中创建NetUtils对象!

/**
 * 加载提示框
 */
private CustomProgressDialog customProgressDialog;

private NetUtils netUtils;
protected Map<String, String> params;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加载中。。。");
    netUtils = new NetUtils();
    netUtils.initHeader();
}

这里调用了initHeader方法,不是必须的,还是一切看业务逻辑,还记得小时候考试那个抄别人卷子,最后连对方名字都抄上的人吗?没错,那就是我。除此之外,这里还创建了一个进度加载提示框,相关内容大家还是到源码中寻找答案吧。至于这里使用的params,我使用的是Map<String, String>,而大多数情况下使用的还是Map<String, Object>,大家也可以替换一下。

创建后当然就要开始使用了:

/**
 * Get请求
 *
 * @param action     请求接口的尾址
 * @param clazz      要转换的Bean类型(需继承BaseBean)
 * @param showDialog 显示加载进度条
 */
protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
    if (!isNetworkAvailable()) {
        toast("网络异常,请检查网络是否连接");
        error(action, new Exception("网络异常,请检查网络是否连接"));
        return;
    }
    if (showDialog) {
        showLoadDialog();
    }
    if (params == null) {
        params = new HashMap<>();
    }
    netUtils.get(action, params, new MyObserver<>(action, clazz));
    params = null;
}

private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {

    private Class<T> clazz;
    private String action;

    MyObserver(String action, Class<T> clazz) {
        this.clazz = clazz;
        this.action = action;
    }

    @Override
    public void onSubscribe(@NonNull Disposable d) {

    }

    @Override
    public void onNext(@NonNull ResponseBody responseBody) {
        hideLoadDialog();
        try {
            String responseString = responseBody.string();
            Logger.i("responseString", action + "********** responseString get  " + responseString);
            success(action, (T) new Gson().fromJson(responseString, clazz));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void onError(@NonNull Throwable e) {
        Logger.i("responseString", "responseString get  " + e.toString());
        error(action, e);
    }

    @Override
    public void onComplete() {
        params = null;
    }
}

这里是以封装get请求与为例,传入的参数分别为网络请求尾址、参数接收类、以及是否显示提示框。这三类个人认为是最基础的部分了,大家使用过程中可以根据业务调整参数,当然,这部分也是在封装接口,所以调整的时候,最好尽可能兼容多个请求,而不要一个接口对应一个方法,那样的就不如直接请求NetUtilsL ,BaseNetActivity这一层的封装就没有用了。

而下面封装的MyObserver完全是为了结果统一获取,而不需要每封装一个方法,就定义一次结果取值的部分,那样过于繁琐。当然,这样自然不可能就完全兼顾所有请求,所以特殊情况,我们可以提供一个标志位,去做对应处理:

/**
 * Get请求
 *
 * @param action     请求接口的尾址
 * @param showDialog 显示加载进度条
 */
protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
    if (!isNetworkAvailable()) {
        toast("网络异常,请检查网络是否连接");
        error(action, new Exception("网络异常,请检查网络是否连接"));
        return;
    }
    if (showDialog) {
        showLoadDialog();
    }
    if (params == null) {
        params = new HashMap<>();
    }
    netUtils.get(action, params, new MyObserver<>(action, 1));
    params = null;
}

private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {
    private Class<T> clazz;
    private String action;
    /**
     * 返回结果状态:0、正常Bean;1、Bitmap
     */
    private int resultStatus = 0;
    MyObserver(String action, Class<T> clazz) {
        this.clazz = clazz;
        this.action = action;
    }
    MyObserver(String action, int resultStatus) {
        this.action = action;
        this.resultStatus = resultStatus;
    }
    @Override
    public void onSubscribe(@NonNull Disposable d) {
    }
    @Override
    public void onNext(@NonNull ResponseBody responseBody) {
        hideLoadDialog();
        try {
            switch (resultStatus) {
                case 0:
                    String responseString = responseBody.string();
                    Logger.i("responseString", action + "********** responseString get  " + responseString);
                    success(action, (T) new Gson().fromJson(responseString, clazz));
                    break;
                case 1:
                    success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
                    Logger.i("responseString", action + "********** 图片获取成功 ");
                    break;
                default:
                    break;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    @Override
    public void onError(@NonNull Throwable e) {
        Logger.i("responseString", "responseString get  " + e.toString());
        error(action, e);
    }
    @Override
    public void onComplete() {
        params = null;
    }
}

都说需求(规则)是死的,人是活的,可有的时候活人改活规则,真容易把其他活人逼死。。。之所以有这个感慨完全是因为上面这个封装,如今开发APP,图片加载的部分肯定是绕不过去的,什么头像啊、产品样例啊、朋友圈晒娃晒狗晒美食的查看啊,我写过最奇葩的是一个自定义日记本封面尺寸是 350dp*100dp,WTF(这个感叹来自于截图)!!!不过一般而言都是获取的图片Url,然后使用各种图片加载控价加载即可。可一直这样人怎么能进步啊,这不就需要获取图片字节流了吗?

设计之初,想过直接取responseBody.string(),没有值再去取字节流,就在为自己的机智鼓掌时,发现这根本行不通,因为Observer的onNext中,responseBody.string()连续调用连词,第二次都无法取得数据。无奈之下,就只有创建一个标志位resultStatus,判断究竟是获取的String还是Bitmap。当然,如果同样适用这个框架的情况下,遇到其他奇葩需求,也可以继续扩展。

需要说明的是,一般而言,我们的请求还是获取文本数据居多,所以这里success的抽象方法还是返回的“String action, BaseBean baseBean”,而返回bitmap的没有做抽象处理,需要使用的时候重写一下就好。BaseNetAcrivity完整代码如下,:

public abstract class BaseNetActivity extends BaseLayoutActivity {

    /**
     * 加载提示框
     */
    private CustomProgressDialog customProgressDialog;

    private NetUtils netUtils;

    protected Map<String, String> params;


    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        customProgressDialog = new CustomProgressDialog(activity, R.style.progress_dialog_loading, "玩命加载中。。。");
        netUtils = new NetUtils();
        netUtils.initHeader();
    }

    protected void refreshHeader() {
        netUtils.initHeader();
    }

    /**
     * Get请求
     *
     * @param action     请求接口的尾址
     * @param clazz      要转换的Bean类型(需继承BaseBean)
     * @param showDialog 显示加载进度条
     */
    protected <T extends BaseBean> void get(final String action, Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("网络异常,请检查网络是否连接");
            error(action, new Exception("网络异常,请检查网络是否连接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.get(action, params, new MyObserver<>(action, clazz));
        params = null;
    }

    /**
     * Get请求
     *
     * @param action     请求接口的尾址
     * @param showDialog 显示加载进度条
     */
    protected <T extends BaseBean> void getImage(final String action, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("网络异常,请检查网络是否连接");
            error(action, new Exception("网络异常,请检查网络是否连接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.get(action, params, new MyObserver<>(action, 1));
        params = null;
    }

    /**
     * Post请求
     *
     * @param action     请求接口的尾址
     * @param clazz      要转换的Bean类型(需继承BaseBean)
     * @param showDialog 显示加载进度条
     */
    protected <T extends BaseBean> void post(final String action, Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("网络异常,请检查网络是否连接");
            error(action, new Exception("网络异常,请检查网络是否连接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        if (params == null) {
            params = new HashMap<>();
        }
        netUtils.post(action, String.valueOf(new JSONObject(params)), new MyObserver<>(action, clazz));
        params = null;
    }

    /**
     * Post请求
     *
     * @param action     请求接口的尾址
     * @param clazz      要转换的Bean类型(需继承BaseBean)
     * @param showDialog 显示加载进度条
     */
    protected <T extends BaseBean> void post(final String action, String json, final Class<T> clazz, boolean showDialog) {
        if (!isNetworkAvailable()) {
            toast("网络异常,请检查网络是否连接");
            error(action, new Exception("网络异常,请检查网络是否连接"));
            return;
        }
        if (showDialog) {
            showLoadDialog();
        }
        netUtils.post(action, json, new MyObserver<>(action, clazz));
    }

    /**
     * 访问成功回调抽象方法
     *
     * @param action   网络访问尾址
     * @param baseBean 返回的数据Bean
     */
    protected abstract void success(String action, BaseBean baseBean);

    /**
     * 访问成功回调方法
     *
     * @param action 网络访问尾址
     * @param bitmap 获取的Bitmap
     */
    protected void success(String action, Bitmap bitmap) {
    }

    protected abstract void error(String action, Throwable e);

    /**
     * 显示加载提示框
     */
    private void showLoadDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                customProgressDialog.show();
            }
        });
    }

    /**
     * 隐藏加载提示框
     */
    private void hideLoadDialog() {
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                if (customProgressDialog != null && customProgressDialog.isShowing()) {
                    customProgressDialog.dismiss();
                }
            }
        });
    }

    private class MyObserver<T extends BaseBean> implements Observer<ResponseBody> {

        private Class<T> clazz;
        private String action;
        /**
         * 返回结果状态:0、正常Bean;1、Bitmap
         */
        private int resultStatus = 0;

        MyObserver(String action, Class<T> clazz) {
            this.clazz = clazz;
            this.action = action;
        }

        MyObserver(String action, int resultStatus) {
            this.action = action;
            this.resultStatus = resultStatus;
        }

        @Override
        public void onSubscribe(@NonNull Disposable d) {

        }

        @Override
        public void onNext(@NonNull ResponseBody responseBody) {
            hideLoadDialog();
            try {
                switch (resultStatus) {
                    case 0:
                        String responseString = responseBody.string();
                        Logger.i("responseString", action + "********** responseString get  " + responseString);
                        success(action, (T) new Gson().fromJson(responseString, clazz));
                        break;

                    case 1:
                        success(action, BitmapFactory.decodeStream(responseBody.byteStream()));
                        Logger.i("responseString", action + "********** 图片获取成功 ");
                        break;

                    default:
                        break;
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        @Override
        public void onError(@NonNull Throwable e) {
            Logger.i("responseString", "responseString get  " + e.toString());
            error(action, e);
        }

        @Override
        public void onComplete() {
            params = null;
        }
    }
}

附录

《一个Android工程的从零开始》- 目录

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

推荐阅读更多精彩内容