retrofit 学习笔记

ps: 我写这篇这是很晚了,也是怪我自己啊,一直以来拖拖拉拉的就是改不了,retrofit 看着好几次了,看完有些日子就记不清了,也没记录下来,忘了都没地方找去。所以今天好好理一理 retrofit ,注解的使用还是很有必要记记的,谁也不敢打包票自己不忘是不,尤其是我,我对于这可是有深刻的印象啊,16年那会完全不写博客,看了好多东西,看完就忘,结果发现16年的收获惨不忍睹啊。

另外说一点,写博客真是对知识点的理解和记忆是大大的加深啊,所以大家总是说好记性不如烂笔头呀

retrofit 集成,简单使用


  1. 添加依赖
    // retrofit 依赖
    compile 'com.squareup.retrofit2:retrofit:2.4.0'
    compile 'com.squareup.retrofit2:converter-gson:2.4.0'
    compile 'com.squareup.retrofit2:adapter-rxjava2:2.4.0'
  1. 编写远程接口
    retrofit 使用直接注解动态生成远程请求 API ,这让我们专注于接口声明,而不用去管理实现,既简单又明了
public interface BlueService {
    @GET("book/search")
    Call<ResponseBody> getSearchBooks(@Query("q") String name,
                                      @Query("tag") String tag, @Query("start") int start,
                                      @Query("count") int count);
}
  1. 原生简单请求
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .build();

        BlueService blueService = retrofit.create(BlueService.class);
        blueService.getSearchBooks("小王子", "", 0, 3)
                .enqueue(new Callback<ResponseBody>() {
                    @Override
                    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                        try {
                            String message = response.body().string();

                            Log.d("AAA", "onResponse: " + message);
                        } catch (IOException e) {
                            e.printStackTrace();
                        }

                    }

                    @Override
                    public void onFailure(Call<ResponseBody> call, Throwable t) {
                        Log.d("AAA", "onFailure: 联网失败");
                    }
                });

注意啊:

  • baseUrl 跟路径必须以 “ / ” 结尾,现在强制要求了,不写会报错
  • response.body().string() 可以获取原始的 response 数据(字符串),我们使用的 .string() 而不是 .toString() , toString 出来的 class 名字。同时返回参数数据类型我们必须使用 ResponseBody 这个才行,这是 okhttp3 里面的,这样才能拿到原始响应体
  • 这个写法一般是获取 json 转 bean 或是测试通不通用的,正常我们不这么用
  1. 添加 json 数据变换
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .build();

        BlueService blueService = retrofit.create(BlueService.class);
        blueService.getSearchBooks("小王子", "", 0, 3)
                .enqueue(new Callback<BookResponse>() {
                    @Override
                    public void onResponse(Call<BookResponse> call, Response<BookResponse> response) {
                        try {
                            BookResponse bookResponse = response.body();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                    }

                    @Override
                    public void onFailure(Call<BookResponse> call, Throwable t) {
                        Log.d("AAA", "onFailure: 联网失败");
                    }
                });

这个我们在 retrofit.build 时添加了类型转换器,就可以让 retrofit 自动帮我们转换 json 了,然后远程接口,callback 我们都可以直接使用具体的数据类型了。我们还是 try 一下保险

  1. 添加 rxjava 支持,变换请求结果为 observable
public interface BlueService {
    @GET("book/search")
    Observable<BookResponse> getSearchBooks(@Query("q") String name,
                                            @Query("tag") String tag, @Query("start") int start,
                                            @Query("count") int count);
}
        String baseUrl = "https://api.douban.com/v2/";

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

        BlueService blueService = retrofit.create(BlueService.class);

        blueService.getSearchBooks("小王子", "", 0, 3)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<BookResponse>() {

                    Disposable disposable;

                    @Override
                    public void onSubscribe(Disposable d) {
                        disposable = d;
                    }

                    @Override
                    public void onNext(BookResponse bookResponse) {
                        Log.d("AAA", "onNext: " + bookResponse.getBooks().size());
                    }

                    @Override
                    public void onError(Throwable e) {

                    }

                    @Override
                    public void onComplete() {
                        disposable.dispose();
                    }
                });
    }

要注意 rxjava2 的支持和1不同,rxjava2 的支持请安上面的依赖添加

中间插一段 http 协议基础


我也是个搬运工,http 协议我摘一点作用明显的,大家看看,然后觉得不怎么懂就去专门看下,我在下面会附上地址的

HTTP 的请求报文分为三个部分 请求行、请求头和请求体,格式如图:


1724103-c43900117e983241.png
  1. 请求行

请求行(Request Line)分为三个部分:请求方法、请求地址和协议及版本,HTTP/1.1 定义的请求方法有8种:

  • GET、POST、PUT、DELETE、PATCH、HEAD、OPTIONS、TRACE,最常的两种GET和POST
  • RestFul 规范的接口的话会用到 GET、POST、DELETE、PUT,分别对应数据库的增删改查操作:
    • PUT -> 增
    • DELETE -> 删
    • POST -> 改
    • GET -> 查

在了解请求地址之前,先了解一下URL的构成:


1724103-95c263da5671d6fa.png

所以我们在 retrofit 的 baseUrl 添加跟地址的行为就是添加 http 协议和主机地址

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(baseUrl)

剩下的 http 基础内容还有很多,我就不复制了,大家看这里:你应该知道的HTTP基础知识,一定要看哦,要不后面你会晕,有地方搞不明白的

  1. 请求头
    其中有一点我们一定要弄清楚的是 http 请求体类型,也就是我们会看到的 Content-Type
  • Content-Type:application/json
    表示这是提交的 json
  • Content-Type:application/x-www-form-urlencoded
    表单提交
  • Content-Type:multipart/form-data; boundary={boundary}
    文件提交,也叫模拟表单

重头戏来啦,注解详解


整篇文章的重点就在这啦,前面的都是浮云,初次使用的同学可能觉得有些繁琐,有些不好理解,但是对于有点水平的同学们来说就不叫事了不是,所x以注解这个才是我们需要重点学习的,看了上面的 http 协议的部分知识点,我们再去研究下 retrofit 的注解就不是那么挠头了

1. 请求类型

请求类型是请求头中的一个内容,写在 header 里,字段是:Content-Type,是描述我们给服务器发送的数据采用什么格式发送,下面放个例子:

interface APIStore {
   @Headers({"Content-Type: application/json","Accept: application/json"})
   @POST ("vdyweb/ws/rest/Login")
   Call<ResponseBody>getMessage(@Body RequestBody info);   
}

大家看到在请求头中添加了 Content-Type 字段的数据,这里总结下有几种请求方式啊,这点很重要的,写不对你的远程通讯就会不同,或是服务器拿不到数据哦。retrofit 已经为我们封装好了常用的请求方式,这里结合的说一下:

  • 什么都不写
    什么都不写表示传递参数没有规范,发送过去的是二进制流,传值你得用 @Body 注解,服务器接受到的是二进制流,然后可以转成字符串按 json 解析数据,不过一般我么不推荐这么写。
  • json 提交
    json 的话我司现在就是用这种方法,适合做 post 万能请求接口,不过在 RestFul 规范的今天, 不在推荐这么写了,我们也得让后台兄弟舒服不是。所以我们在 retrofit 找不到这个注解,就是因为不推荐这种写法了, 下面是自己写的方式,这里只是接口声明,详细在后面
interface APIStore {
   @Headers({"Content-Type: application/json","Accept: application/json"})
   @POST ("vdyweb/ws/rest/Login")
   Call<ResponseBody>getMessage(@Body  Book book);  
}

@Body 的数据对象,Retrofit2 会自动转成 Gson 字符串发送

  • get 方式
    get 方式很特殊,大家都知道 get 请求方式很特殊,把一切都拼接在 url 地址中,所以没那么复杂, Content-Type 不用设置
  • 表单提交
    表单提交,数据都以 key—value 的形式存在请求体中,服务器获取参数也是用 key—value 的形式去拿数据,作为最常使用的提交方式,retrofit 已经给我们封装好了,专门有一个字段
    @FormUrlEncoded
    Observable<BookResponse> getSearchBooks(@Query("q") String name);

自己写的话就是

    @Headers("Content-Type:application/x-www-form-urlencoded")
    Observable<BookResponse> getBooks();
  • 模拟表单
    在表单提交的基础上可以添加文件数据,retrofit 也给我们封装好了
    @Multipart
    Observable<BookResponse> getBooks();

自己写的话就是

    @Headers("Content-Type:multipart/form-data; boundary={boundary}")
    Observable<BookResponse> getBooks();

boundary 部分可写可不些,我看有人不写 boundary 也没事,boundary=xxxxxxx,xxxxxx规定了请求体中的内容分隔符

  • 文档相关资料


    retrofit3.png

2. 请求方法

请求方法上面 http 基础应说过了,这个不难理解,需要注意的是 restFul 规范下的部分:
* PUT -> 增
* DELETE -> 删
* POST -> 改
* GET -> 查
这里要提一下 http 这个请求方法,HTTP注解则可以代替任意一个青谷去方法,有3个属性:method,path,hasBody,下面是例子:

public interface BlogService {
    /**
     * method 表示请的方法,不区分大小写
     * path表示路径
     * hasBody表示是否有请求体
     */
    @HTTP(method = "get", path = "blog/{id}", hasBody = false)
    Call<ResponseBody> getFirstBlog(@Path("id") int id);
} 

3. 数据接收类型

在 http 协议中可以指定接受服务器返回数据的类型,这个一般我们不用自己去设置,retrofit 默认就是 json 格式的,但是自己也可以设,使用的是上面可以看到的 Accept 这个字段

@Headers({"Content-Type: application/json","Accept: application/json"})

写在 head 请求头了,看到这大家可以发现,这些设置除了 get/post 之外,所有的请求设置都是写在请求头里的,请求头就是干这事的,类似于我们常用的 config ,包裹配置参数和一些通用参数。
retrofit 还提供了 @Streaming 这个注解,我们接收的数据不再是 json 的了,而是一个二进制输入流。

    @Streaming

4. 传参相关参数

retrofit4.png

retrofit 用来在不同请求方法中传递参数的注解都在上面了,单说没意思,结合 get / post 和请求不同的数据来说最简单明了,容易懂。

5. get 传参详解

    @GET("xxxUrl")
    Observable<User> getUserInfo(
            @Part("xxxUrl") String userUrl,
            @Query("userid") int id,
            @QueryMap Map<String, String> options,
            @Query("list") List<String> options2);

可以看到 get 可以接受4种传参注解:

  • @Part -1
    用来替换具体的 utl 路径的,一般我们都是直接就写 url 地址了,这里通过 @Part 提供一种动态传入 url 地址方式,@Part 注解是所有请求方法通用的。
  • @Part -2
    上面的 @Part 写法是整体替换具体的 URL 请求,其实要是后台同学的 restFul API 写的规范的话,我们请求,操作数据的 URL 也是可以做到动态的,不是费的一个接口写一个固定的地址给我们。这时 @Part 可以用来实现动态替换 URL 中参数的事,使用 {“xxx”} 来标记 URL 中可变的部分,下面就是一个例子:
    @GET("book/{id}")
    Observable<User> getBook(@Part("id") String id);
  • @Query
    传单个参数,Query("userid") 里面的字符串是 key,后面指定 value 的类型
  • @QueryMap
    可以接受多个参数,所有参数直接写在 map 里
  • Query("list")
    get 方式支持直接传入一个集合,可以看到其实和我们单个传参一样,只不过区别是接收的数据是单个对象,还是一个集合对象

6. post 传参详解

    @POST("xxxUrl")
    Observable<User> getUserInfo(
            @Field("userid") int id,
            @FieldMap Map<String, String> options,
            @Field("list") List<String> options2);

post 传参其实和 get 差不多,却别不多,一个是 query ,一个是 field

  • @Field
    传单个参数,和 @Query 一样
  • @FieldMap
    传多个数据
  • @Field("list")
    同样支持直接传递集合数据类型
  • @POST
    @POST 除了get 都能用,用来传递一个数据对象,服务器按 stream 流的方式接收数据
    @POST("name")
    Observable<User> getName(@Body Book book);

7. 上传文件

上传文件一直是个有问题的地方,不管是 Xutils,okhttp,retrofit 都有不是很明确的地方,这里就能看出网络基础是多么重要,有时候你写完之后,服务器接收不到参数,或是问你发的数据用的什么类型,我怎么接受,你都回答不上来,这个很尴尬不是,我是遇到过,还好后台的兄弟没打我脸,哈哈哈哈.......

不看我写的,看看这些文章也是可以的:

有一点我们要说,表单提交的时候参数的编码问题 enctype属性。enctype:规定了form表单在发送到服务器时候编码方式,他有如下的几个值:

  • application/x-www-form-urlencoded
    默认值,不写就是这个,retrofit 的表单默认也是这个,不能用来传文件,也就是二进制数据
  • multipart/form-data
    所有的数据以二进制流发送
  • application/otcet-stream
    很奇怪,我查了查是八进制.........这个恕我真的不知道为啥有二进制不用,去用八进制
  • text/plain
    纯文本格式

在带有 file 的表单提交中:

  • file 只能用流的形式上传
    • 可以用:multipart/form-data / application/otcet-stream 这2种流上传,差别估计是后台接受数据的 API 不同。
  • 文本参数可以用流的方式上传,也可以用文本格式上传
    • 可以使用 multipart/form-data 这样后台用流接数据,getParame 拿不到数据的。
    • 最好用 text/plain 文本格式传,这样后台获取数据和原来的方式相同。

好了看过这些,我们心里总算是对这些弯弯绕的东西有些初步了解了,再去和后台沟通也大概知道怎么说了,查资料也知道去找什么东东了

    @Multipart
    @POST("upload")
    Call<ResponseBody> uploadMultipleFiles(
            @Part("description") RequestBody description,
            @PartMap Map<String, RequestBody> options
            @Part MultipartBody.Part file1,
            @Part MultipartBody.Part file2);

这是一个带其他参数的文件上传 API ,大家注意看注解如何使用,这个是固定的,大家背下来就行。期中 @Part("description") 里面的字符串是普通文本参数的 key

我们来看看重点的 java 代码:

  • 创建文本参数
    注意这里我用的是 text/plain 发送文本参数,用 multipart/form-data 二进制的方式也可以的
RequestBody requestBody = RequestBody.create(MediaType.parse("text/plain"), "文本参数");
  • 创建 file 参数
        File imageFile = new File("一张图片");
        RequestBody requestBody = RequestBody.create(MediaType.parse("multipart/form-data"), imageFile);
        MultipartBody.Part data = MultipartBody.Part.createFormData("file", imageFile.getName(), requestBody);
  • 文件数据传入 map 集合中的写法
    可能我们的接口是这样的
    @POST("NewsServlet")
    @Multipart
    Call<ResponseBody> testFileUpload3(@PartMap Map<String, RequestBody> map);

所有参数都放在这个集合中,集合参数的放置如下,注意 file 类型的参数的 key 要包含 file 的name 进去,需要自己拼接字符串的

        NewsService newsService = createRetrofit().create(NewsService.class);
         Map<String, RequestBody> fileUpload3Args = new HashMap<>();

        MediaType textType = MediaType.parse("text/plain");
        RequestBody name = RequestBody.create(textType, "txy");
        RequestBody age = RequestBody.create(textType, "18");

        fileUpload3Args.put("name", name);
        fileUpload3Args.put("age", age);

        //构建要上传的文件
        File file = new File(Environment.getExternalStorageDirectory(), "paoche1.jpg");
        RequestBody requestFile =
                RequestBody.create(MediaType.parse("application/otcet-stream"), file);

        fileUpload3Args.put("fileUploader\"; filename=\"paoche3.jpg",requestFile);

        Call<ResponseBody> answers = newsService.testFileUpload3(fileUpload3Args)

上传文件基本就是这些了,这块容易忘,容易混,记下来非常有必要的。

8. 上传图片

上传图片看这篇也行,总结的也很好,比我全面一点:

图片也是文件,但是为啥要单独拿出来说呢,因为图片有自己单独的数据格式 : image/png

RequestBody photoRequestBody = RequestBody.create(MediaType.parse("image/png"), imageFile);
MultipartBody.Part photo = MultipartBody.Part.createFormData("上传的key", "文件名.png", photoRequestBody);

9. 添加请求头参数

请求头参数我们可以在网络请求的 API 接口里写,也可以给 okhttp 添加一个拦截器进来,在请求构建完成,发送前的那一刻拦截,然后添加请求头数据,这样适合添加动态可变参数

  • @Headers 添加静态请求头参数
public interface BlueService {
    @Headers("Cache-Control: max-age=640000")
    @Headers({
        "Accept: application/vnd.yourapi.v1.full+json",
        "User-Agent: Your-App-Name"
    })
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(@Query("q") String name, 
            @Query("tag") String tag, @Query("start") int start, 
            @Query("count") int count);
}

注意添加一条和多条的区别

  • @Header 添加动态请求头参数
public interface BlueService {
    @GET("book/search")
    Call<BookSearchResponse> getSearchBooks(
    @Header("Content-Range") String contentRange, 
    @Query("q") String name, @Query("tag") String tag, 
    @Query("start") int start, @Query("count") int count);
}
  • 添加拦截器动态添加请求头参数
public class HeadInterceptor implements Interceptor {
    @Override
    public Response intercept(Chain chain) throws IOException {
        Request original = chain.request();
        Request request = original.newBuilder()
            .header("User-Agent", "Your-App-Name")
            .header("Accept", "application/vnd.yourapi.v1.full+json")
            .method(original.method(), original.body())
            .build();
        return chain.proceed(request);
    }
}
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(new HeadInterceptor ())
                .build();

        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

添加 header 参数 Request 提供了两个方法:

  • header(key, value)
    如果有重名的将会覆盖
  • ddHeader(key, value)
    允许相同 key 值的 header 存在

10. 为某个请求设置完整的URL

有个别的请求的 URL 不用用的我们基础的 baseurl 的,怎么办,我们可以使用 @Url 注解来忽略 baseurl 的

@GET
public Call<ResponseBody> profilePicture(@Url String url);

Retrofit retrofit = Retrofit.Builder()  
    .baseUrl("https://your.api.url/");
    .build();

BlueService service = retrofit.create(BlueService.class);  
service.profilePicture("https://s3.amazon.com/profile-picture/path");

json 上传的再次说明


json 上传数据不推荐,但是有时候我们真的需要,这里多记录一下找到的东西。

json 除了我们直接 @Body Book book 直接写具体的数据类型的做法,我们也可以使用 RequestBody 来写

public interface PostRoute {  
   @Headers({"Content-Type: application/json","Accept: application/json"})
    @POST("api/FlyRoute/Add")  
   Call<FlyRouteBean> postFlyRoute(@Body RequestBody route)RequestBody  
}  
// 先 json 一个字符串数据出来
Book book= new Book ();  
Gson gson=new Gson();  
String route = gson.toJson(book);

// 我们生成一个 RequestBody 请求对象出来
PostRoute postRoute=retrofit.create(PostRoute.class);  
RequestBody body=RequestBody.create(okhttp3.MediaType.parse("application/json; charset=utf-8"),route);  
Call<Book> call=postRoute.postFlyRoute(body);  

添加日志管理


这个是 okhttp 的部分,但是呢我们不打算专门写一个 okhttp 的入了,在 retrofit 中我们已经充分使用了 okhttp 了,索性就一起写了

retrifit 又开源的日志拦截器,要不我们就自己写拦截器打印日志,不过肯定大的不如直接诶个开源的全啊。

添加依赖

compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'

使用

        // 创建日志拦截器对象出来
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        //包含header、body数据
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        // 添加日志拦截器到 okhttp 对象中
        OkHttpClient client = new OkHttpClient.Builder()
                .addInterceptor(loggingInterceptor)
                .build();

        // 把 okhttp 对象添加到 retrofit 对象中
        Retrofit retrofit = new Retrofit.Builder()
                .client(client)
                .baseUrl(baseUrl)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .build();

打印样式图:


436713-a7e0267212e9f809.png

注意里面 --> 、<-- 箭头的方向:

  • --> 表示我们请求的内容,包括请求头参数,请求信息等,这里我们是最简单的 get 请求所以表现不出来
  • <-- 是我们接受到的内容,包括接受数据形式
  • interceptor 有几个级别,我们用 body 就行,body 会代印包括头在内的所有信息

facebook 网络调试器 Stetho


我还看到有一位朋友提到 facebook 开源了 Stetho 网络监测工具,试了试我没成功,有兴趣的朋友请看:使用OkHttp高效开发调试

其他内容


对于 https 证书有两种策略:

参考资料


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

推荐阅读更多精彩内容