volley(1)

volley

[TOC]

volley是一个简单的异步http库,特点就是封装了请求,加载,缓存,线程,同步等过程和提供回调方法,使用者只需创建特定类型的Request和传入相应的url,便可在回调中获取到相应类型的对象。除此之外,volley还为图片的加载提供了多种方式,毕竟图片的大量加载在平时开发时需要用到。

优点是比较适合小而频繁的http请求,还结合了ImageLoader的优点。
缺点是不支持同步,不能post大数据,所以不适合用于上传大文件。

1 volley使用方法

学习一个框架最好的一个办法就是先从它的使用入手,知道了它如何使用了,然后再跟着它的使用步骤去看它的源码是怎样实现的。

这里分为三个部分介绍,一个是volley的基本使用方法,一个是将volley加载网络图片的方法单独提取出来介绍,一个是自定义Request的使用。

1.1 volley的基本使用

使用volley获取相应数据是需要3步:

  1. 创建RequestQueue对象
  2. 创建Request对象并在Listener的回调中处理回调对象
    一般会在回调方法中更新UI
  3. 将Request添加至RequeueQueue

注:HTTP的请求类型通常有两种,GET和POST,默认使用的是GET方法,之后在分析源码时可以看到,如果要发出POST请求的话,还应该在创建Request对象时传入Method参数并重写getParams(),在内部返回一个带有数据的Map对象

1.1.1 StringRequest

按照上面的步骤我们可以写出下面的StringRequest使用的步骤(其中访问的数据是在自己搭建的服务器):

//1. 创建RequestQueue对象
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

//2. 创建Request对象并在Listener中处理回调对象,在回调方法中更新UI
StringRequest stringRequest = new StringRequest(
    "http://10.132.25.104/JsonData/data1.php", 
    new Response.Listener<String>() {
    @Override
    public void onResponse(String response) {
        // TODO Auto-generated method stub
        textView.setText(response);
    }
}, new Response.ErrorListener() {

    @Override
    public void onErrorResponse(VolleyError error) {
        Log.e(TAG,error.getMessage());        
    }
}); 

//3. 将Request添加至RequestQueue
queue.add(stringRequest);

上面的Request的请求方式是GET,那如何将请求方式转为POST然后添加参数呢?答案就在于创建StringRequest的时候,传入Method.POST将请求方式转为POST,然后重写getParams()将请求参数添加进Map里然后返回,代码如下。

StringRequest stringRequest = new StringRequest(Request.Method.POST, url, listener, errorListener)
{
    @Override
    protected Map<String, String> getParams() throws AuthFailureError {
        Map<String, String> map = new HashMap<String,String>();
        map.put("name", "Shane");
        map.put("age","23");
        return map;
    }
};

1.1.2 JsonRequest

JsonRequest也是继承自Request类,因此使用方法跟StringRequest一样,不过JsonRequest是一个抽象类,JsonRequest有两个直接的子类JsonObjectRequest和JsonArrayRequest,这两个类的区别就是一个的回调方法是JSONObject,另一个是JSONArray。它们的格式分别是:
JSONObject:"{name:lee, age:24}"
JSONArray:"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]"

JsonObjectRequest用法(数据格式为"{name:lee, age:24}")

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

JsonObjectRequest jsonObjectRequest = new JsonObjectRequest(url, null,  
        new Response.Listener<JSONObject>() {  
            @Override  
            public void onResponse(JSONObject response) {  
                Log.d("TAG", response.toString());  

                textView.setText("name:"+response.getString("name")
                            ", age:"+response.getInt("age"));
            }  
        }, new Response.ErrorListener() {  
            @Override  
            public void onErrorResponse(VolleyError error) {  
                Log.e("TAG", error.getMessage(), error);  
            }  
        });  

queue.add(jsonObjectRequest);

JsonArrayRequest的用法(数据格式为"[{name:one, age:1},{name:two, age:2},{name:three, age:3}]")

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

JsonArrayRequest jsonArrayRequest = new JsonArrayRequest(
                url, new Listener<JSONArray>() {
            @Override
            public void onResponse(JSONArray response) {
                tv.setText("");
                for(int i = 0;i<response.length();i++)
                {
                    try {
                        JSONObject object = response.getJSONObject(i);
                        tv.append("name:"+object.getString("name"));
                        tv.append(", age:"+object.getInt("age")+"\n");
                    } catch (JSONException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
            }
        }, null);

queue.add(stringRequest);


1.2 volley加载网络图片

volley之中为加载图片提供了多种方法,能够实现与UIL一样出色的加载网络图片的功能,下面就来学习一下使用volley加载网络图片的方法。

1.2.1 ImageRequest

一看到Request就想到之前所学的StringRequest和JsonRequest,同样的,ImageRequest也是volley提供的异步加载图片的请求,使用方法也是和之前的一样。

  1. 创建RequestQueue对象
  2. 创建Request对象
  3. 将Request添加至RequeueQueue

不过与之前不同的是,ImageRequest的构造方法所需的参数比较特殊,由下面的代码可以看出,ImageRequest的构造函数接收六个参数,第一个参数就是图片的URL地址。第二个参数是图片请求成功的回调,这里我们把返回的Bitmap参数设置到ImageView中。第三第四个参数分别用于指定允许图片最大的宽度和高度,如果指定的网络图片的宽度或高度大于这里的最大值,则会对图片进行压缩,指定成0的话就表示不管图片有多大,都不会进行压缩。第五个参数用于指定图片的颜色属性,Bitmap.Config下的几个常量都可以在这里使用,其中ARGB_8888可以展示最好的颜色属性,每个图片像素占据4个字节的大小,而RGB_565则表示每个图片像素占据2个字节大小。第六个参数是图片请求失败的回调,这里我们当请求失败时在ImageView中显示一张默认图片。

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

ImageRequest imageRequest = new ImageRequest(
        "http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg", 
        new Response.Listener<Bitmap>() {
            @Override
            public void onResponse(Bitmap response) {
                // TODO Auto-generated method stub
                ivPic1.setImageBitmap(response);
            }
        },
        0,
        0,
        Config.RGB_565,
        new ErrorListener() {

            @Override
            public void onErrorResponse(VolleyError error) {
                // TODO Auto-generated method stub
                ivPic1.setImageResource(R.drawable.ic_launcher);
            }
        }); 

queue.add(imageRequest);

1.2.2 ImageLoader

使用ImageRequest已经十分方便了,因为用法和其他的Request的使用方法一样,因此十分容易记忆。但是ImageRequest就相当于每次调用都去访问网址请求数据,这对于大量加载网络图片来说这是不可取的。回忆一下UIL加载图片的流程,其中使用了缓存来提高加载图片的效率。
同样的,volley也提供了ImageLoader,它不仅可以对图片进行缓存,还可以过滤掉重复的链接来避免重复发送请求。
使用volley的ImageLoader加载图片分为以下几步:

  1. 创建RequestQueue对象
  2. 创建BitmapCache类实现ImageCache接口
    需要自己实现缓存(一般使用LruCache)
  3. 创建ImageLoader对象
  4. 通过ImageLoader获取ImageListener对象
    其中设置显示控件ImageView以及默认显示图片
  5. 通过ImageLoader.get()加载显示图片
    其中可设置图片大小限制
RequestQueue queue = Volley.newRequestQueue(getApplicationContext());

ImageLoader imageLoader = new ImageLoader(queue, new BitmapCache());

ImageListener listener = ImageLoader.getImageListener(imageView, R.drawable.defaultImage, R.drawable.errorImage);

imageLoader.get("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg",
        listener,200,200);

/**
 * 自定义缓存类实现ImageCache接口
 */
class BitmapCache implements ImageCache
{
    private LruCache<String, Bitmap> lruCache;
    
    public BitmapCache()
    {
        lruCache = new LruCache<String, Bitmap>(10 * 1024 * 1024)
                    {
                        @Override
                        protected int sizeOf(String key, Bitmap value) {
                            // TODO Auto-generated method stub
                            return value.getRowBytes() * value.getHeight();
                        }
                    };
    }
            
    @Override
    public Bitmap getBitmap(String url) {
        // TODO Auto-generated method stub
        return lruCache.get(url);
    }

    @Override
    public void putBitmap(String url, Bitmap bitmap) {
        // TODO Auto-generated method stub
        lruCache.put(url, bitmap);
    }
    
}

1.2.3 NetworkImageView

volley还提供了第三种加载网路图片的方法,自定义了继承于ImageView的NetworkImageView,通过setImageUrl可以直接从url中加载图片,当然这里面也需要RequestQueue和ImageLoader。
使用NetworkImageView需要以下几步(前面几步跟上面一节的一样,只是后面不需要Listener了):

  1. 创建RequestQueue对象
  2. 创建BitmapCache类实现ImageCache接口
    需要自己实现缓存(一般使用LruCache)
  3. 创建ImageLoader对象
  4. 在xml布局文件创建NetworkImageView控件
  5. 在代码中使用NetworkImageView的setImageUrl加载图片

注:在之前的加载网络图片的方法中,都可以自定义最终图片的大小从而压缩图片,NetworkImageView的压缩则是直接利用xml中的控件的宽高,在加载图片的时候它会自动获取自身的宽高,然后对比网络图片的宽高,再决定是否需要对图片进行压缩。在布局文件中把NetworkImageView的layout_width和layout_height都设置成wrap_content就可以了,这样NetworkImageView就会将该图片的原始大小展示出来,不会进行任何压缩。因此NetworkImageView的压缩过程是在内部自动化的,并不需要我们关心

前3步与前一节一样,所以省去了,只看NetworkImageView是怎么设置工作的。
在xml布局文件中:

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">
    
    <Button 
        android:id="@+id/btn1"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:text="Button"
        />
    
    <com.android.volley.toolbox.NetworkImageView
        android:id="@+id/network_image_view"
        android:layout_width="200dp"
        android:layout_height="200dp"
        android:layout_gravity="center_horizontal"
        />
</LinearLayout>    

在java代码中:

NetworkImageView networkImageView = (NetworkImageView) findViewById(R.id.network_image_view);
networkImageView.setImageUrl("http://mba.b2cedu.com/UploadFiles_8526/201202/20120208115239951.jpg", imageLoader);

1.3 自定义Request

上面介绍了volley中的一些基本网络请求使用方法,可以发现,volley主要是通过建立Request对象,然后将其添加进工作的RequestQueue之中。但是在volley之中仅仅提供了StringRequest、JsonRequest及其子类、还有针对图片的ImageRequest。而开发中还有许多其他类型的返回数据,比如说xml数据类型,此时就是展现volley的强大之处的时候了。volley是一个开源框架,因此我们可以仿照这StringRequest的实现来自定义XMLRequest,GsonRequest等Request子类,这样我们只需将该Request添加至RequestQueue当中便可以在回调中获取到相应的数据类型,而不用每次都进行封装。

1.3.1 StringRequest源码

要自定义Request,首先先查看volley自带的StringRequest是怎么实现的,然后我们便可以仿照着自定义Request类。以下是StringRequest的源码。

//StringRequest.java
public class StringRequest extends Request<String> {
    private final Listener<String> mListener;

    /**
     * Creates a new request with the given method.
     *
     * @param method the request {@link Method} to use
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(int method, String url, Listener<String> listener,
            ErrorListener errorListener) {
        super(method, url, errorListener);
        mListener = listener;
    }

    /**
     * Creates a new GET request.
     *
     * @param url URL to fetch the string at
     * @param listener Listener to receive the String response
     * @param errorListener Error listener, or null to ignore errors
     */
    public StringRequest(String url, Listener<String> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected void deliverResponse(String response) {
        mListener.onResponse(response);
    }

    @Override
    protected Response<String> parseNetworkResponse(NetworkResponse response) {
        String parsed;
        try {
            parsed = new String(response.data, HttpHeaderParser.parseCharset(response.headers));
        } catch (UnsupportedEncodingException e) {
            parsed = new String(response.data);
        }
        return Response.success(parsed, HttpHeaderParser.parseCacheHeaders(response));
    }
}

从上面的代码中可以看出实现一个Request的子类只需要关注3个地方,构造方法、deliverResponse和parseNetworkResponse。

  1. 其中构造方法中自定义了一个Response.Listener,用于主线程中的回调;
  2. deliverResponse是返回解析的结果,该方法运行在主线程当中,一般该方法内部利用Listener将解析数据回调出去;
  3. parseNetworkResponse是将网络返回的字节数组型的数据(response.data)解析成想要的类型,比如StringRequest就解析成String,JsonObjectRequest就解析成JSONObject,最后通过调用Response.success()来将解析结果重新包装成Response对象返回。

1.3.2 JsonObjectRequest源码

JsonObjectRequest跟StringRequest类似,不过在JsonObjectRequest的上面多了一层JsonRequest,原因是JsonArrayRequest和JsonObjectRequest有些相同的地方,比如deliverResponse(),因此JsonObjectRequest中就不用实现deleverResponse了。

public class JsonObjectRequest extends JsonRequest<JSONObject> {

    /**
     * Creates a new request.
     * @param method the HTTP method to use
     * @param url URL to fetch the JSON from
     * @param jsonRequest A {@link JSONObject} to post with the request. Null is allowed and
     *   indicates no parameters will be posted along with request.
     * @param listener Listener to receive the JSON response
     * @param errorListener Error listener, or null to ignore errors.
     */
    public JsonObjectRequest(int method, String url, JSONObject jsonRequest,
            Listener<JSONObject> listener, ErrorListener errorListener) {
        super(method, url, (jsonRequest == null) ? null : jsonRequest.toString(), listener,
                    errorListener);
    }

    /**
     * Constructor which defaults to <code>GET</code> if <code>jsonRequest</code> is
     * <code>null</code>, <code>POST</code> otherwise.
     *
     * @see #JsonObjectRequest(int, String, JSONObject, Listener, ErrorListener)
     */
    public JsonObjectRequest(String url, JSONObject jsonRequest, Listener<JSONObject> listener,
            ErrorListener errorListener) {
        this(jsonRequest == null ? Method.GET : Method.POST, url, jsonRequest,
                listener, errorListener);
    }

    @Override
    protected Response<JSONObject> parseNetworkResponse(NetworkResponse response) {
        try {
            String jsonString =
                new String(response.data, HttpHeaderParser.parseCharset(response.headers));
            return Response.success(new JSONObject(jsonString),
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        } catch (JSONException je) {
            return Response.error(new ParseError(je));
        }
    }
}

可以看出JsonObjectRequest的parseNetworkResponse与StringRequest不同的地方就是,其将String数据里面转换成了JSONObject数据。由此可以知道,自定义的Request子类其实就是在parseNetworkResponse里面将String数据转换成所需的数据格式,比如XML所需的数据格式为XmlPullParser,则只需将String数据转换成XMLPullParser即可。

1.3.3 XMLRequest

有前面的StringRequest和JsonObjectRequest可以知道,自定义一个Request只需要3步,最主要就是在parseNetworkResponse中则将String转换成XmlPullParser,这里就不多说了,直接看代码。

public class XMLRequest extends Request<XmlPullParser>{

    private final Listener<XmlPullParser> mListener;
    
    public XMLRequest(int method, String url, Listener<XmlPullParser> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
    }
    public XMLRequest(String url,Listener<XmlPullParser> listener, ErrorListener errorListener) {
        this(Method.GET, url, listener, errorListener);
    }

    @Override
    protected Response<XmlPullParser> parseNetworkResponse(
            NetworkResponse response) {
        
        try {
            String xmlString = new String(response.data, 
                    HttpHeaderParser.parseCharset(response.headers));
            
            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
            XmlPullParser xmlPullParser = factory.newPullParser();
            xmlPullParser.setInput(new StringReader(xmlString));
            
            return Response.success(xmlPullParser,
                    HttpHeaderParser.parseCacheHeaders(response));
            
        } catch (UnsupportedEncodingException e) {
            // TODO Auto-generated catch block
            return Response.error(new ParseError(e));
        }catch (XmlPullParserException e) {
            // TODO Auto-generated catch block
            return Response.error(new ParseError(e));
        } 
        
    }

    @Override
    protected void deliverResponse(XmlPullParser response) {
        mListener.onResponse(response);
    }

}

1.3.4 GsonRequest

再实现一个Request加深印象,这里采用GsonRequest。因为JsonObjectRequest和JsonArrayRequest还是有点麻烦,因此自行封装一个GsonRequest。

public class GsonRequest<T> extends Request<T> {

    private final Listener<T> mListener;
    private Class<T> mClazz;
    private Gson mGson;
    
    public GsonRequest(int method, String url,Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {
        super(method, url, errorListener);
        this.mListener = listener;
        this.mClazz = clazz;
        mGson = new Gson();
        
    }

    public GsonRequest(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {
        this(Method.GET, url, clazz, listener, errorListener);
    }
    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        
        try {
            String parsed = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            T result = mGson.fromJson(parsed, mClazz);
            
            return Response.success(result, 
                    HttpHeaderParser.parseCacheHeaders(response));
        } catch (UnsupportedEncodingException e) {
            return Response.error(new ParseError(e));
        }
    }
    @Override
    protected void deliverResponse(T response) {
        mListener.onResponse(response);
    }
}

下面展示实际的用法:
首先看一下信息类User

public class User {

    private UserInfo userInfo;

    public UserInfo getUserInfo() {
        return userInfo;
    }

    public void setUserInfo(UserInfo userInfo) {
        this.userInfo = userInfo;
    }
    

    
}

public class UserInfo
{
    private String name;
    private int age;
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getAge() {
        return age;
    }
    public void setAge(int age) {
        this.age = age;
    }
    
}

在代码中使用GsonRequest,可以发现,GsonRequest中只传入了User而并没有传入UserInfo,这里Gson已经把User里面的类做了封装了,因此可以直接获取到UserInfo类。

RequestQueue queue = Volley.newRequestQueue(getApplicationContext());
GsonRequest<User> gsonRequest = new GsonRequest<User>(url,
                User.class,
                new Response.Listener()
                {
                    @Override
                    public void onResponse(User response)
                    {
                        UserInfo userInfo = response.getUserInfo();
                        textView.setText("name:"+userInfo.getName()
                            + ", age:"+userInfo.getAge());
                    }
                },new Response.ErrorListener()
                {
                    @Override
                    public void onErrorResponse(VolleyError error)
                    {
                         Log.e("TAG", error.getMessage(), error); 
                    }
                });
queue.add(gsonRequest);

从url获取到的数据格式如下:

{
    "userInfo":
    {
        "name":"lee",
        "age":23
    }       
}

输出格式如下:

name:23, age:23

1.3.5 JacksonRequest

由于Gson解析Json数据的性能不如Jackson,因此再来自定义一个JacksonRequest,这里需要注意的是,Jackson在解析Json数据的时候,对于字符串类型一定要有双引号引起来,不然会报错,比如{"name":"lee","age":19}

public class JacksonRequest<T> extends Request<T> {

    private final Listener<T> listener;
    private Class<T> mClazz;
    private ObjectMapper mapper;
    
    public JacksonRequest(int method, String url,Class<T> clazz, Listener<T> l, ErrorListener listener) {
        super(method, url, listener);
        this.listener = l;
        this.mClazz = clazz;
        mapper = new ObjectMapper();
    }

    public JacksonRequest(String url,Class<T> clazz, Listener<T> l, ErrorListener listener) {
        this(Method.GET,url,clazz, l, listener);
        
    }

    @Override
    protected Response<T> parseNetworkResponse(NetworkResponse response) {
        
        try {
            String parsed = new String(response.data,
                    HttpHeaderParser.parseCharset(response.headers));
            
            T result = mapper.readValue(parsed, mClazz);
            
            return Response.success(result, 
                    HttpHeaderParser.parseCacheHeaders(response));
            
        } catch (Exception e) {
            
            return Response.error(new ParseError(e));
        }
    }

    @Override
    protected void deliverResponse(T response) {
        // TODO Auto-generated method stub
        listener.onResponse(response);
    }
}

最后再总结一下自定义Request的用法,主要分为3步:

  1. 构造带有Response.Listener的构造方法
  2. 重写deliverResponse()方法,一般在该方法里面调用mListener.onResponse(result)将解析结果回调出去
  3. 重写parseNetworkResponse()方法,该方法是实现不同Request子类的关键,因为在该方法中,需要将获取到的String类型的数据转换成特定的类型,比如JsonObjectRequest需要JSONObject类型数据,XMLRequest需要XmlPullParser类型数据等等。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容