安卓 Volley+OkHttp3+Gson(Jackson)开源库的封装过程

来源:互联网 发布:c语言函数规则 编辑:程序博客网 时间:2024/06/05 06:57

前言

寒假学习了一下安卓的网络通信部分,扩展和封装了volley,还是挺有意思的,所以写一篇博客来记录一下整个历程吧。大家都知道,安卓网络通信有很多解决方案,比如HttpURLConnection,OkHttp,Android-async-http,Volley等,那为什么是Volley+OkHttp3+Gson(Jackson)?答案是这样的,用volley来进行网络通信,用Okhttp3来处理Volley的底层HTTP请求,然后用Gson或者Jackson来解析json数据,这样封装起来的库已经足够应付数据量小但通信频繁的网络操作了。下面会给出每个开源库的简介和地址(详细介绍和使用请看官网),接着就进行volley的简单扩展和封装,并且优化部分代码。

volley官方演讲配图 
volley官方演讲配图

简介

  • Volley 
    Google出品的一个简化网络任务的库,负责处理请求、加载、缓存、线程、异步等等操作,能处理JSON格式的数据,图片,缓存,纯文字,允许开发者实现一些自定制服务,适合进行数据量不大,但通信频繁的网络操作,使用时最好再进行简单的封装。 
    源码 
    非官方库

  • OKhttp3 
    Square出品的一个高效的HTTP客户端,android 4.4以后已替换掉HttpURLConnection作为默认的HTTP连接,OkHttp 3.x相对于2.x,在api和使用规范上有一些调整。 
    官网 
    源码 
    wiki

  • Gson 
    Google开发的用于转换Java对象和Json对象的java库 
    源码

  • Jackson 
    在处理json大文件时解析性能明显优于Gson,如果应用经常需要传输较大的json文件则使用Jackson,小文件则使用Gson。还有阿里的fastjson也有其优势,没用过,后面再说= = 
    Wiki


下载

Gradle

    compile 'com.mcxiaoke.volley:library:1.0.19'    compile 'com.squareup.okhttp3:okhttp:3.1.2'    compile 'com.squareup.okio:okio:1.6.0'    compile 'com.google.code.gson:gson:2.6.1'
  • 1
  • 2
  • 3
  • 4

简单使用

1、volley的使用一共三步骤,首先获取一个全局的请求队列对象,用来缓存所有的HTTP请求。

    RequestQueue mRequestQueue  = Volley.newRequestQueue(context);  
  • 1

2、然后新建一个请求,这里用JsonObjectRequest(JsonArrayRequest同理),(接口这里用mockaroo和Mocky在线生成一个)

    JsonObjectRequest jsonObjectRequest = new JsonObjectRequest("http://www.mocky.io/v2/56c9d8c9110000c62f4e0bb0", null,              new Response.Listener<JSONObject>() {                  @Override                  public void onResponse(JSONObject response) {                      Log.d("mTAG", response.toString());                  }              }, new Response.ErrorListener() {                  @Override                  public void onErrorResponse(VolleyError error) {                      Log.e("mTAG", error.getMessage(), error);                  }              });  
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

3、最后添加请求到队列中

    mRequestQueue.add(jsonObjectRequest);
  • 1

一个网络请求操作就这样方便简单,运行,可以看到log打印如下

{"last_name":"Ramos","id":1,"first_name":"Roger","gender":"Male","ip_address":"194.52.112.37","email":"rramos0@gizmodo.com"}
  • 1
  • 2

自定义GsonRequest解析json

为了将上面的json数据解析为Java对象,我们使用Gson库,而velloy没有支持Gson,所以我们仿照JsonObjectRequest自己定义一个GsonRequest

    public class GsonRequest<T> extends Request<T> {        private final Listener<T> mListener;        private Gson mGson;        private Class<T> mClass;        public GsonRequest(int method, String url, Class<T> clazz, Listener<T> listener,                           ErrorListener errorListener) {            super(method, url, errorListener);            mGson = new Gson();            mClass = clazz;            mListener = listener;        }        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 jsonString = new String(response.data,                        HttpHeaderParser.parseCharset(response.headers));                return Response.success(mGson.fromJson(jsonString, mClass),                        HttpHeaderParser.parseCacheHeaders(response));//用Gson解析返回Java对象            } catch (UnsupportedEncodingException e) {                return Response.error(new ParseError(e));            }        }        @Override        protected void deliverResponse(T response) {            mListener.onResponse(response);//回调T对象        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39

简单分析一下上面代码,我们覆盖了Request父类的方法,在parseNetworkResponse中使用了Gson解析得到的jsonString, 然后在deliverResponse中再回调此T对象。但是,parseNetworkResponse中Gson的解析只适用单个json对象,如果是json数组呢?所以我们还需要定义一个TypeToken来提供对复杂类型的支持。

还有一点,就是这个GsonRequest类只适合get请求,如果是post请求则会去其父类Request中寻找post参数Params,所以我们再覆盖一下父类的getParams()方法,并且让其支持在构造器中直接传入Params。 
具体看一下代码,修改如下

    public class GsonRequest<T> extends Request<T> {        private final Listener<T> mListener;        private static Gson mGson = new Gson();        private Class<T> mClass;        private Map<String, String> mParams;//post Params        private TypeToken<T> mTypeToken;        public GsonRequest(int method, Map<String, String> params, String url, Class<T> clazz, Listener<T> listener,                           ErrorListener errorListener) {            super(method, url, errorListener);            mClass = clazz;            mListener = listener;            mParams = params;        }        public GsonRequest(int method, Map<String, String> params, String url, TypeToken<T> typeToken, Listener<T> listener,                           ErrorListener errorListener) {            super(method, url, errorListener);            mTypeToken = typeToken;            mListener = listener;            mParams = params;        }        //get        public GsonRequest(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {            this(Method.GET, null, url, clazz, listener, errorListener);        }        public GsonRequest(String url, TypeToken<T> typeToken, Listener<T> listener, ErrorListener errorListener) {            this(Method.GET, null, url, typeToken, listener, errorListener);        }        @Override        protected Map<String, String> getParams() throws AuthFailureError {            return mParams;        }        @Override        protected Response<T> parseNetworkResponse(NetworkResponse response) {            try {                String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));                if (mTypeToken == null)                    return Response.success(mGson.fromJson(jsonString, mClass),                            HttpHeaderParser.parseCacheHeaders(response));//用Gson解析返回Java对象                else                    return (Response<T>) Response.success(mGson.fromJson(jsonString, mTypeToken.getType()),                            HttpHeaderParser.parseCacheHeaders(response));//通过构造TypeToken让Gson解析成自定义的对象类型            } catch (UnsupportedEncodingException e) {                return Response.error(new ParseError(e));            }        }        @Override        protected void deliverResponse(T response) {            mListener.onResponse(response);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63

定义好以后,我们就可以来new一个GsonRequest请求了。一步步来,先根据网络传输的json字段来定义一个实体类,重新看一下刚才运行打印出来的数据

{"last_name":"Ramos","id":1,"first_name":"Roger","gender":"Male","ip_address":"194.52.112.37","email":"rramos0@gizmodo.com"}
  • 1
  • 2

我们可以先取json数据中的first_name,last_name和gender作为Person类的属性

实体类Person

    public class Person {        private String gender;        private String first_name;           private String last_name;        public void setGender(String gender) {this.gender = gender;}            public String getGender() { return this.gender;}        public void setFirst_name(String first_name) {this.first_name = first_name;}        public String getFirst_name() {return this.first_name;}        public void setLast_name(String last_name) {this.last_name = last_name;}        public String getLast_name() {return this.last_name;}    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

然后新建一个GsonRequest,可以看到,onResponse回调方法直接返回了一个person对象,打印其数据验证一下

     GsonRequest<Person> gsonRequest = new GsonRequest<Person>(                    "http://www.mocky.io/v2/56c9d8c9110000c62f4e0bb0", Person.class,                    new Response.Listener<Person>() {                        @Override                        public void onResponse(Person person) {                            Log.d(TAG, "first_name: " + person.getFirst_name());                            Log.d(TAG, "last_name: " + person.getLast_name());                            Log.d(TAG, "gender: " + person.getGender());                            mTextview.setText("first_name: " + person.getFirst_name() + "\n"                                    + "last_name: " + person.getLast_name() + "\n" +                                    "gender: " + person.getGender());                        }                    }, new Response.ErrorListener() {                @Override                public void onErrorResponse(VolleyError error) {                    Log.e(TAG, error.getMessage(), error);                }            });     //添加请求到队列     mRequestQueue.add(gsonRequest);
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21

打印的结果当然是对的,我就不贴了。 
好,先休息一下~

嗯,接着说,如果应用经常要传输大文件,那么最好是使用Jackson库解析json,因为它比gson更快,JacksonRequest的定义也同样道理,贴上代码

    public class JacksonRequest<T> extends Request<T> {        private final Listener<T> mListener;        private static ObjectMapper objectMapper = new ObjectMapper();        private Class<T> mClass;        private TypeReference<T> mTypeReference;//提供解析复杂JSON数据支持        public JacksonRequest(int method, String url, Class<T> clazz, Listener<T> listener,                              ErrorListener errorListener) {            super(method, url, errorListener);            mClass = clazz;            mListener = listener;        }        public JacksonRequest(int method, String url, TypeReference<T> typeReference, Listener<T> listener,                              ErrorListener errorListener) {            super(method, url, errorListener);            mTypeReference = typeReference;            mListener = listener;        }        public JacksonRequest(String url, Class<T> clazz, Listener<T> listener, ErrorListener errorListener) {            this(Method.GET, url, clazz, listener, errorListener);        }        public JacksonRequest(String url, TypeReference<T> typeReference, Listener<T> listener,                              ErrorListener errorListener) {            super(Method.GET, url, errorListener);            mTypeReference = typeReference;            mListener = listener;        }        @Override        protected Response<T> parseNetworkResponse(NetworkResponse response) {            try {                String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers));                Log.v("mTAG", "json");                if (mTypeReference == null)//使用Jackson默认的方式解析到mClass类对象                    return (Response<T>) Response.success(                            objectMapper.readValue(jsonString, TypeFactory.rawClass(mClass)),                            HttpHeaderParser.parseCacheHeaders(response));                else//通过构造TypeReference让Jackson解析成自定义的对象类型                    return (Response<T>) Response.success(objectMapper.readValue(jsonString, mTypeReference),                            HttpHeaderParser.parseCacheHeaders(response));            } catch (Exception e) {                return Response.error(new ParseError(e));            }        }        @Override        protected void deliverResponse(T response) {            mListener.onResponse(response);        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59

因为项目中我使用的是Gson,所以没有把Jackson库一起导入,如果要使用的话当然是二选一了,而不是一起使用,不然项目apk文件该有多大啊


加载图片

volley还有加载网络图片的功能,我们可以new一个ImageRequest来获取一张网络的图片,不过它并没有做缓存处理,所以我们用ImageLoader(volley.toolbox.ImageLoader),volley内部实现了磁盘缓存,不过没有内存缓存,我们可以自己来定义。 
1.新建一个ImageLoader,设置ImageListener,然后在get方法中传入url,看代码吧

    ImageLoader imageLoader = new ImageLoader(mRequestQueue, new MyImageCache());    ImageLoader.ImageListener listener = ImageLoader.getImageListener(mImageview,                    R.mipmap.ic_default, R.mipmap.ic_error);            imageLoader.get("https://d262ilb51hltx0.cloudfront.net/max/800/1*dWGwx6UUjc0tocYzFNBLEw.jpeg",                    listener, 800, 800);
  • 1
  • 2
  • 3
  • 4
  • 5

2.为了实现图片的内存缓存,我们使用LruCache来实现,自定义一个MyImageCache类继承自ImageCache,然后其构造方法中new一个最大为8M的LruCache

    public class MyImageCache implements ImageLoader.ImageCache {        private LruCache<String, Bitmap> mCache;        public MyImageCache() {            int maxSize = 8 * 1024 * 1024;            mCache = new LruCache<String, Bitmap>(maxSize) {                @Override                protected int sizeOf(String key, Bitmap bitmap) {                   //getRowBytes()返回图片每行的字节数,乘以高度得到图片的size                    return bitmap.getRowBytes() * bitmap.getHeight();                }            };        }            @Override        public Bitmap getBitmap(String url) {            return mCache.get(url);        }        @Override        public void putBitmap(String url, Bitmap bitmap) {            mCache.put(url, bitmap);        }        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

添加OkHttp

我们已经实现了volley+Gson了,如果要使用OkHttp作为传输层,我们只需要在构建 Volley 的请求队列对象requestQueue时做一下改变,将OkHttp3Stack作为参数传进去。OkHttp3Stack具体的实现看一下链接 代码

       mRequestQueue = Volley.newRequestQueue(context, new OkHttp3Stack(new OkHttpClient()));
  • 1

二次封装

最后我们可以把volley的使用封装成一个VolleyManager,代码太长,见这里。 
或者也可以把volley的请求操作提取出来放到Application中,这样整个app就只用一个请求队列对象。

    public class App extends Application {        public static final String TAG = "App";        public RequestQueue mRequestQueue;//请求队列        private ImageLoader mImageLoader;        private static App mInstance;        @Override        public void onCreate() {            super.onCreate();            mInstance = this;        }        public static synchronized App getInstance() {            return mInstance;        }        public RequestQueue getRequestQueue() {            if (mRequestQueue == null) {                mRequestQueue = Volley.newRequestQueue(getApplicationContext());            }            return mRequestQueue;        }        public ImageLoader getImageLoader() {            getRequestQueue();            if (mImageLoader == null) {                mImageLoader = new ImageLoader(this.mRequestQueue,                        new MyImageCache());            }            return this.mImageLoader;        }        public <T> void addRequest(Request<T> req, String tag) {            req.setTag(tag);            getRequestQueue().add(req);        }        public <T> void addRequest(Request<T> req) {            req.setTag(TAG);            getRequestQueue().add(req);        }        public void cancelRequests(Object tag) {            if (mRequestQueue != null) {                mRequestQueue.cancelAll(tag);            }        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49

优化

1.上面加载图片MyImageCache类的图片缓存大小是固定的,改成这个可以实现动态地分配缓存。

    public class LruBitmapCache extends LruCache<String, Bitmap>            implements ImageCache {        public LruBitmapCache(int maxSize) {            super(maxSize);        }        public LruBitmapCache(Context ctx) {            this(getCacheSize(ctx));        }        @Override        protected int sizeOf(String key, Bitmap value) {            return value.getRowBytes() * value.getHeight();        }        @Override        public Bitmap getBitmap(String url) {            return get(url);        }        @Override        public void putBitmap(String url, Bitmap bitmap) {            put(url, bitmap);        }        // Returns a cache size equal to approximately three screens worth of images.        public static int getCacheSize(Context ctx) {            final DisplayMetrics displayMetrics = ctx.getResources().                    getDisplayMetrics();            final int screenWidth = displayMetrics.widthPixels;            final int screenHeight = displayMetrics.heightPixels;            // 4 bytes per pixel            final int screenBytes = screenWidth * screenHeight * 4;            return screenBytes * 3;        }    }2.在自定义的GsonRequest类里,我们可以通过在其构造器中添加 `setMyRetryPolicy()` 方法来实现请求超时时间的定制。    private void setMyRetryPolicy() {            setRetryPolicy(new DefaultRetryPolicy(30000,                    DefaultRetryPolicy.DEFAULT_MAX_RETRIES,                    DefaultRetryPolicy.DEFAULT_BACKOFF_MULT));
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

补充

忘了说jackson的导入了= =,为了避免重复入坑,补充一下Jackson的下载

    compile 'com.fasterxml.jackson.core:jackson-core:2.7.1'    compile 'com.fasterxml.jackson.core:jackson-annotations:2.7.1'    compile 'com.fasterxml.jackson.core:jackson-databind:2.7.1'
  • 1
  • 2
  • 3

记得还要添加一下packagingOptions,因为jackson-core和jackson-databind有重复的文件,重复加载会报错。

    android{        ...          packagingOptions {                exclude 'META-INF/NOTICE' // will not include NOTICE file                exclude 'META-INF/LICENSE' // will not include LICENSE file            }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

最后

如果这种解决方案还不满足,还有一种更为强大的,Retrofit+OkHttp,都是Square公司出品,然后图片加载再选择Square的Picasso(或者谷歌推荐的Glide、Facebook的Fresco)。而且,Retrofit还支持RxJava,可以使异步操作的代码更加简洁。这些搭配起来就是网络的神装了。不过Retrofit和RxJava我都没深入研究过,先打好基础再说,以后有时间再看看。


代码已经放上github了,本人新手,可能有不完善的地方,欢迎一起学习交流 
代码地址

原创粉丝点击