Retrofit + GSON处理JSON模板

动机

  • 首先封装了Retrofit的一些操作,也就是回调的时候不必去为每个javabean的每个操作去写一个Callback,所有的bean公用一个Callback
  • 使用了MVP的设计思想,加上公用的Callback,使得每个bean的Presenter层只需要极少的方法,就可以实现原来的功能。
  • 然后封装GSON,定义了公用的Holder容器,存储从服务器直接返回的JSON数据,然后分发给每个bean类。

当然上面所有的前提都是一个:服务器返回的JSON数据是按照一定的格式的。

API返回的数据格式:

由于服务器是自己写的所以格式可以自己定啦 ,所以本文所说的就是这样的一个格式,所有的解析都会用一个模板,把所有的公共代码向父类重构,大大减少的代码的数量,使得子类更加简单,这就是我的目的。

{
    "Msg": "OK",
    "Code": 100,
    "Result":[] //这里是一个数组
}

或者

{
    "Msg": "Error",
    "Code": 111,
}

然后前面一个是请求成功的结果,后面一个是请求失败的结果,由于这是一个模板,所以Result是对应多个对象(POJO).

下面是一个请求成功返回的例子:

{
  "Msg": "OK",
  "Code": 100,
  "Result": [
    {
      "createdAt": "2016-03-23 01:17:36",
      "times": 0,
      "tagId": 1,
      "id": 1,
      "title": "[Java GC]Algorithm For GC",
      "type": "原创",
      "tagName": "Java",
      "content": "Content"
    }
  ]
}

Retrofit的使用

对于Retrofit的基本使用这里就不再赘述。

首先我们定义请求的模板:

public interface BlogAPI {

    @GET("blog/queryById")
    Call<ResponseBody> queryById(@Query("id") int id);

    @GET("blog/queryByTitle")
    Call<ResponseBody> queryByTitle(@Query("title") String title);

    @GET("blog/queryByTag")
    Call<ResponseBody> queryByTag(@Query("tag") String tag);

    @GET("blog/queryByTime")
    Call<ResponseBody> queryByTime(@Query("time") String time);

    @GET("blog/queryByType")
    Call<ResponseBody> queryByType(@Query("type") String type);

    //Request to BlogController#index() in server
    @GET("blog")
    Call<ResponseBody> queryAll(@Query("pageNum") int pageNum);
}

所有的Call<>里面的泛型都是ResponseBody,那么什么是ResponseBody呢??为什么要用这个呢??

A one-shot stream from the origin server to the client application with the raw bytes of the response body. Each response body is supported by an active connection to the webserver. This imposes both obligations and limits on the client application.

从服务器返回的一次性的字节流,是从服务返回的原始数据,这意味着客户端必须做一些解析工作。同时这个流在使用之后必须关闭,就像InputStream一样。基于上述原,我们选择了这个(其实也可以在Call里面直接放上javabean,然后每个javabean里面有CodeMsg属性,但是我觉得这样不合理,违反了类的单一职责的原则)

下面定义一个Manager用来处理Retrofit

/**
 * Created by WQH on 2016/4/16  19:13.
 */
public class RemoteManager {

    public static final String DOMAIN = "http://wangqihang.cn:8080/Blog/";
    public static final int OK = 100; // Request OK.
    public static final int PARSE = 109; // Some error in parse JSON.
    public static final int NO_MORE = 107; // No More Data from server.
    public static final int SYNTAX = 110; // JSONNull
    private Retrofit retrofit;

    // Singleton
    private static class ClassHolder {
        private static RemoteManager INSTANCE = new RemoteManager();
    }

    private RemoteManager() {
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor();
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient client =
                new OkHttpClient
                        .Builder()
                        .readTimeout(10, TimeUnit.SECONDS)
                        .build();

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

    public static <T> T create(final Class<T> service) {
        return ClassHolder.INSTANCE.retrofit.create(service);
    }
}

那么怎么请求呢?(这里给出一个简单的使用,等下还会具体说)

Call<ResponseBody> call = mBlogAPI.queryAll(pageNum);
call.enqueue(new BlogCallback(mLoadView)); // 这句等哈会说明的。。。

MVP+Retrofit

本文采用了MVP怎么和Retrofit结合,所以向网络上请求数据是在Presenter里面写的,而得到数据后会回调到View里面。所以我们先定义一个View接口:

//DataType 就是一个javabean类哦
public interface LoadView<DataType> {
    /**
     * Null only occurs in POST action.means success.
     */
    @Nullable
    void onSuccess(List<DataType> data);

    void onFail(int errorCode, String errorMsg);
}

然后再定义一个Presenter基类,持有一个LoadView的引用,子类就可以回调里面的方法啦。

public abstract class LoadPresenter<DataType> {
    /**
     * The default LoadView,the LoadView will not change.
     */
    protected LoadView<DataType> mLoadView;

    public LoadPresenter(LoadView<DataType> mLoadView) {
        this.mLoadView = mLoadView;
        initAPI();
    }

    public LoadPresenter() {
        initAPI();
    }

    /**
     * Init the RemoteManager in vary subclass
     */
    protected abstract void initAPI();
}

那么,LoadPresenter的子类有哪些方法从网络请求数据呢?这时候可以定义一个接口(比如Blog的查询,有根据id查询的,也用根据标题查询的):

/**
 * Created by WQH on 2016/4/12  23:44.
 * <p>
 * Load Presenter for download data from server.
 * Other class CAN implements it or implements their own Presenter.
 */
public interface DownLoadPresenter<DataType> {

    //Load all data paginate.
    void loadAll(int pageNum, LoadView<DataType> mLoadView);

    //Load data by id
    void loadById(int id, LoadView<DataType> mLoadView);
}   

所以对于加载Blog的具体实现类就可以下面这样写:需要注意的是,为什么不直接在LoadPresenter里面写方法呢?这里把LoadPresenterDownLoadPresenter分离开来,也就是运用了设计模式中桥接模式(Bridge)这样无疑是极好的,因为每个实体都有不同的方法从网络上加载数据,而初始化API都是一样的,所以就可以所有的类都去继承LoadPresenter而实现不同的接口。实现了复用。

看一下怎么实现桥接模式的?

public class BlogDownLoadPresenterImpl extends LoadPresenter<Blog> implements DownLoadPresenter<Blog> {
    BlogAPI mBlogAPI;

    @Override
    protected void initAPI() {
        mBlogAPI = RemoteManager.create(BlogAPI.class);
    }

    @Override
    public void loadAll(int pageNum, LoadView<Blog> mLoadView) {
        Call<ResponseBody> call = mBlogAPI.queryAll(pageNum);
        doQuery(call, mLoadView);
    }

    @Override
    public void loadById(int id, LoadView<Blog> mLoadView) {
        Call<ResponseBody> call = mBlogAPI.queryById(id);
        doQuery(call, mLoadView);
    }

    private void doQuery(Call<ResponseBody> call, LoadView<Blog> mLoadView) {
        call.enqueue(new BlogCallback(mLoadView));
    }

    // The inner class that do the callback work after fetch data from server
    // MUST specify the type of the generics.(like this:Blog)
    // Why? GSON parse Json must know the type at RUNTIME
    class BlogCallback extends DefaultCallback<Blog> {

        public BlogCallback(LoadView<Blog> mLoadView) {
            super(mLoadView);
        }

        @Override
        protected void onParseResult(String result) {
            mLoadView.onSuccess(CollectionUtil.asList(Json.fromJson(result, Blog[].class)));
        }
    }
}

这里主要在解释一哈DefaultCallback

public abstract class DefaultCallback<DataType> implements Callback<ResponseBody> {

    private static final String TAG = "DefaultCallback";
    protected LoadView<DataType> mLoadView;

    public DefaultCallback(LoadView<DataType> mLoadView) {
        this.mLoadView = mLoadView;
    }

    @Override
    public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
        if (response.isSuccessful()) {
            try {
                //Attention: response.body() will return null in second calls.
                String jsonStr = response.body().string();
                Holder holder = Json.fromJson(jsonStr, Holder.class);
                if (holder.Code == RemoteManager.OK) {
                    onParseResult(holder.Result.toString());
                } else {
                    mLoadView.onFail(holder.Code, holder.Msg);
                }
            } catch (Exception e) {
                e.printStackTrace();
                // Can‘t Find Object.
                mLoadView.onFail(RemoteManager.SYNTAX, "At " + TAG + "#onResponse-> Can‘t Find Object.Because JSONNull");
            }
        } else {
            mLoadView.onFail(RemoteManager.PARSE, "At " + TAG + "#onResponse-> " + response.errorBody().toString());
        }
    }

    /**
     * Example in subclass:
     * <code>
     * mLoadView.onSuccess(Arrays.asList(Json.fromJson(result, Blog[].class)));
     * </code>
     *
     * @param result a JSON string associated with <code>Holder.Result</code>,So the subclass MUST parse the JSON string.
     */
    protected abstract void onParseResult(String result);

    @Override
    public void onFailure(Call<ResponseBody> call, Throwable t) {
        mLoadView.onFail(RemoteManager.PARSE, "At " + TAG + "#onFailure-> " + t.toString());
    }
}

首先可以看到是实现了retrofit2.Callback接口,然后持有LoadView的实例,所以这个类就是真正的回调方法。

// 当网络响应成功的时候就会被调用
public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response);
// 对应的,失败时调用
public void onFailure(Call<ResponseBody> call, Throwable t);

上面这几个方法就是retrofit2.Callback的方法啦,注意参数Call<ResponseBody>是我们定义API的返回值,在这里真正的使用到了。

下面进入onResponse的内部进行分析:

String jsonStr = response.body().string();

得到返回的JSON数据,注意这是一个流,所以在下一次再调用这个方法的jsonStr就为空了,这个问题当时纠结了好久才发现。

Holder holder = Json.fromJson(jsonStr, Holder.class);

Json是一个工具类,封装了GSON的一些方法:(GSON的知识等哈再说)

public class Json {
    private static Gson gson;

    private static Gson getGson() {
        if (gson == null) {
            synchronized (Json.class) {
                if (gson == null) {
                    gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
                }
            }
        }
        return gson;
    }

    public static String toJson(Object src) {
        return getGson().toJson(src);
    }

    public static <T> T fromJson(String json, Class<T> classOfT) throws JsonSyntaxException {
        return getGson().fromJson(json, classOfT);
    }
}

Holder是什么呢?

/**
 * Created by WQH on 2016/5/8  12:53.
 *
 * An Holder that hold the objects fetch from server.
 * <p>
 * <Code>   the code that the server return, all case are in @see{wqh.blog.mvp.model.net.RemoteManager}.
 * <Msg>    the message that the server return, the message is an description of the code.
 * <Result> the entity that the server return,and the type od the field is <code>JsonArray</code> because the JSON data the server are all in Array though the number of data is 1
 */
public class Holder {

    @SerializedName(value = "Code")
    public int Code;
    @SerializedName(value = "Msg")
    public String Msg;
    @SerializedName(value = "Result")
    public JsonElement Result;
}

所有从网上取下来的JSON数据首先被转化为Holder这个POJO,然后才会在具体的变化其他的实体类(因为服务器返回的JSON是这种格式,所以这样就把所有实体类的请求全部放在这里处理,实体类只去处理他自己的属性)

@SerializedName(value = "Code")表示反序列化的时候key=Code(等哈具体说明)

回到DefaultCallback onResponse的方法里面:

if (holder.Code == RemoteManager.OK) {
        onParseResult(holder.Result.toString());
} else {
        mLoadView.onFail(holder.Code, holder.Msg);
       }

定义了一个抽象方法

/**
     * Example in subclass:
     * <code>
     * mLoadView.onSuccess(Arrays.asList(Json.fromJson(result, Blog[].class)));
     * </code>
     *
     * @param result a JSON string associated with <code>Holder.Result</code>,So the subclass MUST parse the JSON string.
     */
    protected abstract void onParseResult(String result);

分发解析的JSON数据,用来让子类处理真正的JSON数据.

到这里MVP的一些知识都基本介绍了,下面看看GSON怎么工作的

GSON

首先看看Google的基本使用

这里不再说明:GSON ANNOTATIONS EXAMPLE

注意到在Holder里面的Result属性是JsonElement,那么其实除了这一JSON类还有其他的,下面首先来看一哈:

- JsonElement 任何一个JSON数据都是JsonElment

- JsonPrimitive such as a string or integer

- JsonObject a collection of JsonElements indexed by thier name (of type String). This is similar to a Map<String, JsonElement>

- JsonArray a collection of JsonElements. Note that the array elements can be any of the four types and mixed types are supported.在JSON数据里面就是[]扩起来的部分。

- JsonNull a null value

这些所有的类都是继承自JsonElement.

原来我把Result的类型设置JsonArray,但是当服务器返回一个错误的CodeMsg的时候,就没有Result这一项,就会产生一个JSONSYNTAXException错误。所以设置为JsonElement就是一个正确的决定。

@SerializedName(value = "Code")也就表面反序列化的时候就把找到JSON数据里面的”Code”键,然后转化为HolderCode字段。子类只要去处理onParseResult就可以了.

然后我看看另一种GSON的使用(并没有在我的项目里面使用):

public class JsonManager {
    private final GsonBuilder gsonBuilder = new GsonBuilder();

    private static class ClassHolder {
        private static JsonManager INSTANCE = new JsonManager();
    }

    public static JsonManager instance() {
        return ClassHolder.INSTANCE;
    }

    public void addDeserializer(Class<?> clazz, JsonDeserializer jsonDeserializer) {
        gsonBuilder.registerTypeAdapter(clazz, jsonDeserializer);
    }

    public Gson create() {
        return gsonBuilder.create();
    }
}

处理JSON的工具类,注意gsonBuilder.registerTypeAdapter(clazz, jsonDeserializer)第一个参数是要解析的Javabean类,而第二个参数是该bean的反序列器。

所以bean类不懂,然后实现下面几个Deserializer

public class HolderDeserializer implements JsonDeserializer<Holder> {
    @Override
    public Holder deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        final JsonObject jsonObject = jsonElement.getAsJsonObject();
        final int Code = jsonObject.get("Code").getAsInt();
        final String Msg = jsonObject.get("Msg").getAsString();
        final JsonElement Result = jsonObject.get("Result");
        Holder mHolder = new Holder();
        mHolder.Code = Code;
        mHolder.Msg = Msg;
        mHolder.Result = Result;
        return mHolder;
    }
}

public class CommentDesrializer implements JsonDeserializer<Comment> {
    @Override
    public Comment deserialize(JsonElement jsonElement, Type type, JsonDeserializationContext jsonDeserializationContext) throws JsonParseException {
        final JsonObject jsonObject = jsonElement.getAsJsonObject();
        final int id = jsonObject.get("id").getAsInt();
        final String content = jsonObject.get("content").getAsString();
        final int createdBy = jsonObject.get("createdBy").getAsInt();
        final int belongTo = jsonObject.get("belongTo").getAsInt();
        final String createdAt = jsonObject.get("createdAt").getAsString();
        Comment aComment = new Comment();
        aComment.id = id;
        aComment.content = content;
        aComment.createdBy = createdBy;
        aComment.belongTo = belongTo;
        aComment.createdAt = createdAt;
        return aComment;
    }
}

在Main里面:

public class Main {
    public static void main(String args[]) {
        String jsonStr = "{\n" +
                "  \"Msg\": \"OK\",\n" +
                "  \"Code\": 100\n" +
                "}";
        JsonManager.instance().addDeserializer(Holder.class, new HolderDeserializer());
        JsonManager.instance().addDeserializer(Comment.class, new CommentDesrializer());
        final Gson gson = JsonManager.instance().create();

        Holder aHolder = gson.fromJson(jsonStr, Holder.class);
        if (aHolder.Result == null)
        {
            System.out.println("Error");
            return;
        }
        Comment[] comments = gson.fromJson(aHolder.Result.toString(), Comment[].class);
        for (Comment comment : comments) {
            System.out.println(comment);
        }
    }
}

就可以了,,就这么简单…我是想首先把Holder提取出来,然后和上面一样,判断Code,如果是OK,就分发给子类。也是和上面一样的道理,只不过这里没有使用@SerializedName()

邮箱:[email protected]

欢迎一起讨论

时间: 2024-11-01 10:12:13

Retrofit + GSON处理JSON模板的相关文章

android之GSON解析JSON

Gson 是 Google 提供的用来在 Java 对象和 JSON 数据之间进行映射的 Java 类库. 比如: <pre name="code" class="java">public class Order { public String id; public String OrderName; @Override public String toString() { return "id:"+id+",OrderNa

我的Android进阶之旅------&gt;解决Jackson、Gson解析Json数据时,Json数据中的Key为Java关键字时解析为null的问题

1.问题描述 首先,需要解析的Json数据类似于下面的格式: { ret: 0, msg: "normal return.", news: [ { id: "NEW2016062800875700", from: "腾讯新闻客户端", qqnews_download_url: "http://inews.qq.com/m?refer=openapi_for_xiaotiancai", articletype: "1&

Retrofit Gson解析空字符串的问题

在实际开发项目中,服务器经常会用空字符串 "" 作为返回结果表示空值 ,但这在Gson当中就会遇到问题,如果这项数据的类型不是字符串,Gson解析就会报错 我们希望程序可以自动将空字符串解析为对应类型的空值,比如整型就解析为0,List型就解析为一个Empty List 这个问题可以说是我用Retrofit+Gson以来最大的一个坑,以至于我在研究时差不多都要把源码看完了 提一件离奇的事是,Gson在用整型解析空字符串时,报的居然是"Inavalid double"

使用谷歌Gson实现Json串和Java Bean互转

/** * 使用谷歌Gson实现Json串和Java Bean互转 */ public class JsonHelper { public static String toJson(Object src){ return new Gson().toJson(src); } public static <T> T fromJson(String json, Class<T> clazz) throws Exception { return new Gson().fromJson(js

使用Gson解析Json数组遇到的泛型类型擦除问题解决方法

谷歌Gson转换Json串有如下方法: public Object fromJson(String json, Type typeOfT); 可以使用它进行数组解析.如下,使用此方法解析Json串为类型MyBean的List数组,方法可用. List<MyBean> lst = new Gson().fromJson(data, new TypeToken<List<MyBean>>(){}.getType()); 但如果把MyBean改为泛型T,封装此方法为如下: L

Android中使用Gson解析JSON数据的两种方法

Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率;本文将介绍两种方法解析JSON数据,需要的朋友可以参考下 Json是一种类似于XML的通用数据交换格式,具有比XML更高的传输效率. 从结构上看,所有的数据(data)最终都可以分解成三种类型: 第一种类型是标量(scalar),也就是一个单独的字符串(string)或数字(numbers),比如"北京"这个单独的词. 第二种类型是序列(sequence),也就是若干个相关的数据按照一定顺序并列在一起,又叫做数组

使用Gson解析Json

1.Json介绍 JSON的全称是"JavaScript Object Notation",即JavaScript对象表示法,它是一种基于文本,独立于语言的轻量级数据交换格式.XML也是一种数据交换格式.两者的区别:因为XML虽然可以作为跨平台的数据交换格式,但是在JS中处理XML非常不方便,同时XML标记比数据多,增加了交换产生的流量,而JSON没有附加的任何标记,在JS中可作为对象处理,所以我们倾向于选择JSON来交换数据. 2.Json的两种结构 JSON有两种表示结构,对象和数

Android--------使用gson解析json文件

##使用gson解析json文件 **json的格式有两种:** **1. {}类型,及数据用{}包含:** **2. []类型,即数据用[]包含:** 下面用个例子,简单的介绍gson如何解析json,仅使用~ 先发两个json 内容 1.最外层是{} {             "resp": "ok",         "result": {             "date": "2013-4-19 16:

Android用Gson解析JSON字符串

在volley框架中有一个 protected Response<Result<T>> parseNetworkResponse(NetworkResponse response){}函数.从服务器上或者在缓存中获取的JSON字符串在这个函数进行解析. String jsonString = new String(response.data, HttpHeaderParser.parseCharset(response.headers, HTTP.UTF_8)) Result<