Android网络请求的封装方法

项目的代码中看到了这样一种的方法(就称之为“Gson泛型”吧)来封装网络请求,简单、方便、条理清晰,自己用后感觉非常nice,特此记录下来。
首先是网络请求需要注意的几个点:
第一:如何使用规范简洁的代码来实现网络架构?
第二:数据解析要用什么方法?
第三:网络异常如何统一处理?

1.定义网络封装类

/**

  • 网络请求封装

  • Created by kuaigeng01 on 2018/1/20.
    */
    public class ServerRequest {
    public static final int Error_from_net = 1;
    public static final int Error_from_server = 2;
    public static final int Error_from_parser = 3;

    /**

    • 发起网络请求,回调在ui线程
    • @param tagPrefix 请求前缀:用于设置请求的Tag,这样能根据tag取消请求
    • @param tClass 需要返回的数据类型
    • @param url 请求url
    • @param params 请求参数
    • @param callback 回调
    • @param <T> 数据类型
      */
      public static <T> void sendRequestCallbackInMainThread(String tagPrefix, Class<T> tClass,
      String url, Map<String, String> params, RequestCallback<T> callback) {
      sendRequest(tagPrefix, true, tClass, url, params, callback);
      }

/**

  • 发起网络请求,回调在非ui线程

  • @param tagPrefix 请求前缀:用于设置请求的Tag,这样能根据tag取消请求

  • @param tClass 需要返回的数据类型

  • @param url 请求url

  • @param params 请求参数

  • @param callback 回调

  • @param <T> 数据类型
    */
    public static <T> void sendRequestCallbackInBackgroundThread(String tagPrefix, Class<T> tClass,
    String url, Map<String, String> params, RequestCallback<T> callback) {
    sendRequest(tagPrefix, false, tClass, url, params, callback);
    }
    public static void cancelRequest(String tagPrefix, String url) {
    if (TextUtils.isEmpty(url)) {
    return;
    }
    String tag = createTag(tagPrefix, url);
    RequestQueue queue = VolleyHelper.getInstance().getRequestQueue();
    queue.cancelAll(tag);
    queue = VolleyHelper.getInstance().getAsyncRequestQueue();
    queue.cancelAll(tag);
    }

    private static <T> void sendRequest(String tagPrefix, boolean callbackInMainThread,
    final Class<T> tClass, final String url, Map<String, String> params,
    final RequestCallback<T> callback) {
    sendRequestImpl(tagPrefix, callbackInMainThread, url, params,
    new Response.Listener<String>() {
    @Override public void onResponse(String s) {
    ServerResult<T> result = ServerDataParser.simpleParseJsonToPojo(tClass, s);
    if (null != result) {
    if (result.getStatus() == 200) {
    if (null != callback) {
    callback.onSuccess(result.getData());
    }
    } else {
    if (null != callback) {

                                 callback.onFailure(Error_from_server, TextUtils.isEmpty(result.getMsg())?"":result.getMsg());
                             }
                         }
                     } else {
                         if (null != callback) {
                             callback.onFailure(Error_from_parser, "parse json failure");
                         }
                     }
                 }
             }, new Response.ErrorListener() {
                 @Override public void onErrorResponse(VolleyError volleyError) {
                     if (null != callback) {
                         callback.onFailure(Error_from_net, String.valueOf(volleyError));
                     }
                 }
             });
    

    }

    private static void sendRequestImpl(String tagPrefix, boolean callbackInMainThread, String url,
    Map<String, String> params, Response.Listener<String> listener,
    Response.ErrorListener errorListener) {

     if (TextUtils.isEmpty(url)) {
         throw new IllegalArgumentException("url can't be empty!!!");
     }
    
     if (TextUtils.isEmpty(tagPrefix)) {
         throw new IllegalArgumentException("tagPrefix can't be empty!!!");
     }
    
     if (null == params) {
         params = getCommonRequestParams();
     } else {
         params.putAll(getCommonRequestParams());
     }
    
     AcosStringRequest request = new AcosStringRequest(url, params, listener, errorListener);
    
     RequestQueue queue;
     if (callbackInMainThread) {
         queue = VolleyHelper.getInstance().getRequestQueue();
    

// queue = PluginAnswerLiveCooperation.getInstance().getRequestQueue();
} else {
// queue = PluginAnswerLiveCooperation.getInstance().getAsyncRequestQueue();
queue = VolleyHelper.getInstance().getAsyncRequestQueue();
}

    String tag = createTag(tagPrefix, url);
    request.setTag(tag);

    queue.cancelAll(tag);
    queue.add(request);
}

public interface RequestCallback<T extends Object> {
    void onSuccess(T data);

    void onFailure(int from, String errorMsg);
}

private static String createTag(String tagPrefix, String url) {
    return tagPrefix + "_" + StringUtils.calcMd5(url);//用于 同一个url请求,cancel掉掉旧掉
}

private static HashMap<String, String> getCommonRequestParams() {
    HashMap<String, String> map = new HashMap<>();

    User user = AnswerDataCenter.getInstance().getHostUser();
    String token = user == null ? "" : user.token;

    map.put("token", token);
    map.put("_appid", "10000");
    return map;
}

}

这个类做了这么几件事:
1)定义三种请求异常的类型,分别是网络异常、服务器异常、解析异常。
2)定义一个发起网络请求的方法,回调在UI线程(同时定义了一个回调在非UI线程的方法),各个参数的含义在代码中已经注释的很明白。重要的一点需要注意的是泛型的参数。
3)定义一个取消网络请求的方法,在发起请求的时候传入了一个tag设置到请求对象中,取消的时候也会用到那个tag。
4)例子中使用的是Volley网络请求库,在sendRequestImpl中设置Volley的请求参数。
5)定义RequestCallback接口,Volley回调使用,传入的参数为用户定义的实体类对象,定义为泛型的原因是为了实现Gson统一解析数据。
sendRequest方法中调用sendRequestImpl方法请求数据,在onResponse方法回调中处理返回的数据,这个时候就会调用服务端数据解析器ServerDataParser.simpleParseJsonToPojo(tClass,s),第一个参数是json字符串解析成的最终类。接下来看数据解析器里面是如何处理服务器返回的json字符串。
2.服务端数据解析器

public class ServerDataParser {

private static final TypeAdapter<Boolean> booleanAsIntAdapter = new TypeAdapter<Boolean>() {
    @Override
    public void write(JsonWriter out, Boolean value) throws IOException {
        if (value == null) {
            out.nullValue();
        } else {
            out.value(value);
        }
    }

    @Override
    public Boolean read(JsonReader in) throws IOException {
        JsonToken peek = in.peek();
        switch (peek) {
            case BOOLEAN:
                return in.nextBoolean();
            case NULL:
                in.nextNull();
                return null;
            case NUMBER:
                return in.nextInt() != 0;
            case STRING:

                String val = in.nextString();
                if (TextUtils.equals("1", val)) {
                    return true;
                }

                if (TextUtils.equals("0", val)) {
                    return false;
                }

                return Boolean.parseBoolean(val);
            default:
                throw new IllegalStateException("Expected BOOLEAN or NUMBER but was " + peek);
        }
    }
};

public static Gson createGson() {
    Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd hh:mm:ss").excludeFieldsWithoutExposeAnnotation().
            registerTypeAdapter(Boolean.class, booleanAsIntAdapter).
            registerTypeAdapter(boolean.class, booleanAsIntAdapter).create();

    return gson;
}

public static <T> ServerResult<T> simpleParseJsonToPojo(Class<T> tClass, String json) {
    if (TextUtils.isEmpty(json)) {
        return null;
    }

    Type type = new ParameterizedTypeImpl(ServerResult.class, new Class[]{tClass});

    try {

        Gson gson = createGson();
        ServerResult<T> parseResult = gson.fromJson(json, type);

        return parseResult;

    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}

}
数据解析器设置了Gson的一些参数,然后就调用Gson的泛型,得到传入实体类的Type对象,用来供gson.fromJson()使用。

3.Gson泛型
定义gson泛型类

public class ParameterizedTypeImpl implements ParameterizedType {
private final Class raw;
private final Type[] args;

public ParameterizedTypeImpl(Class raw, Type[] args) {
    this.raw = raw;
    this.args = args != null ? args : new Type[0];
}

@Override
public Type[] getActualTypeArguments() {
    return args;
}

@Override
public Type getRawType() {
    return raw;
}

@Override
public Type getOwnerType() {
    return null;
}

}
Gson泛型的作用,就是将传进来的实体类转换成gson能够识别的Type类型。
4.数据实体类
移动端请求接口得到的数据,大致都是统一格式的,例如下面这种:

public class ServerResult<T> {
@SerializedName("status")
@Expose
private int status;

@SerializedName("msg")
@Expose
private String msg;

@SerializedName("result")
@Expose
private T data;

public T getData() {
    return data;
}

public void setData(T data) {
    this.data = data;
}

public int getStatus() {
    return status;
}

public void setStatus(int status) {
    this.status = status;
}

public String getMsg() {
    return msg;
}

public void setMsg(String msg) {
    this.msg = msg;
}

}

这里是一个实体类的基类,之后所有的实体类都会继承它。
一个状态码字段status,一个错误信息字段msg,一个result对象。
这里的result对象定义为泛型,是为了编译之后的统一解析。
总结一下封装步骤:
1.创建ServerRequest网络请求类,调用sendRequest方法请求网络,tClass泛型类,继承自ServerResult。
2.网络请求成功,返回json字符串s,然后调用服务器端解析器ServerDataParser.simpleParseJsonToPojo(tClass,s);网络请求失败,则调用callback.onFailure(),传入失败类型和失败原因。
3.在simpleParseJsonToPojo方法中,调用
Type type=ParameterizedTypeImpl(ServerResult.class,new Class[]{tClass});
返回泛型的Type类型,然后调用
ServerResult<T> result=gson.fromJson(json,type);
最后得到Gson解析的实体对象。

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

推荐阅读更多精彩内容