发送网络请求

APP的联网能力现在必不可少, 所以选择一个适合的网络框架至关重要. 我主要以以下几个方面考虑:

  • 使用简单简洁

  • 方便的自定义, 如加请求头

  • 方便的取消请求

  • 能方便的与GSON等序列化库结合

  • 完善的Log信息打印

Retrofit2

最后选择了Retrofit2, 具体介绍可以参考 用 Retrofit 2 简化 HTTP 请求. 基本用法可以参考 官方网站.

这里就不再赘述Retrofit2的优缺点, 主要谈谈引入Retrofit2后需要做的一些准备工作:

  1. 将Retrofit2的API应用至Model层

  2. 与GSON结合

  3. 自定义Log输出

  4. 处理一些通用的异常

  5. 取消请求

  6. 预处理请求结果

1. 将Retrofit2的API应用至Model层

Retrofit的特色是使用注解与接口来描述网络请求, 在多数情况下能很好的与Model层的接口结合起来, 而无需重写实现类.

public interface HomepageApi {
    @POST(Urls.GET_TYPES)
    Call<BaseData> getTypes();

    @FormUrlEncoded
    @POST(Urls.GET_HOT_PRODUCTS)
    Call<BaseData> getTopProducts(@Field("pageNo") int pageNo, 
                      @Field("pageSize") int pageSize);
}
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(Urls.BaseUrl)
    .build();

HomepageApi api = retrofit.create(HomepageApi.class);

然后通过api对象去调用getTypes或getTopProducts接口. 通过这种方式我们就能直接将描述请求的接口应当做一个Model来使用. 但是每次使用都去要生成一个Retrofit.Builder, 然后做一些配置, 额外增加了重复代码, 因此我们可以写一个工具类, 来专门生成Retrofit接口实例

在这里我使用了两个类:

  1. RetrofitFactory 持有一个Retrofit单例, 专门用于配置Retrofit, 比如baseUrl, converter, adapter之类.

  2. ApiFactory 单例, 持有一个HashMap对象, 用于缓存Retrofit生成的接口实例, 而无需重复生成.

/**
 * 用于获取配置好的retrofit对象, 通过设置{@link Configuration#enableLoggingNetworkParams()}来启用网络请求
 * 参数与相应结果.
 */
public class RetrofitFactory {
    private static Retrofit retrofit;
    private static String baseUrl;

    public static void setBaseUrl(String url) {
        baseUrl = url;
    }

    /**
     * 获取配置好的retrofit对象来生产Api对象
     */
    public static Retrofit getRetrofit() {
        if (retrofit == null) {
            if (baseUrl == null || baseUrl.length() <= 0)
                throw new IllegalStateException("请在调用getFactory之前先调用setBaseUrl");

            Retrofit.Builder builder = new Retrofit.Builder();

            builder.baseUrl(baseUrl)
                    .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) // 参考RxJava
                    .addConverterFactory(GsonConverterFactory.create()); // 参考与GSON的结合

            // 参考自定义Log输出
            if (Configuration.isShowNetworkParams()) {
                OkHttpClient client = new OkHttpClient.Builder().addInterceptor(
                        new HttpLoggingInterceptor()).build();

                builder.client(client);
            }

            retrofit = builder.build();
        }

        return retrofit;
    }
}
/**
 * 通过定义好的api接口以及Retrofit来生成具体的实例.
 */
public class ApiFactory {
    private static ApiFactory factory;
    private static HashMap<String, Object> serviceMap = new HashMap<>();

    public static ApiFactory getFactory() {
        if (factory == null) {
            synchronized (ApiFactory.class) {
                if (factory == null)
                    factory = new ApiFactory();
            }
        }
        return factory;
    }

    public <T> T create(Class<T> clz) {
        Object service = serviceMap.get(clz.getName());
        if (service == null) {
            service = RetrofitFactory.getRetrofit().create(clz);
            serviceMap.put(clz.getName(), service);
        }
        return (T) service;
    }
}
HomepageApi api = ApiFactory.getFactory().create(HomepageApi.class);

在使用前还需要调用

RetrofitFactory.setBaseUrl(Urls.ROOT);

2. 与GSON结合

Retrofit2可以很方便的通过Converter与GSON结合, 在RetrofitFactory类中已经做了绑定, 无需再处理.

builder.addConverterFactory(GsonConverterFactory.create());

我们只需要在定义接口的时候给定结果类型, Retrofit内部会调用GSON解析JSON数据, 并转换成相应的实体类.

Call<BaseData> getTypes();

3. 自定义Log输出

为何要有Log输出? 有了网络请求的Log我们就能很方便的追踪问题, 看是服务器返回的数据有误, 还是我们解析有误. Retrofit2内部使用的是okhttp3, 因此可以考虑使用okhttp的Interceptor来打印Log输出:

/**
 * OkHttp的{@link Interceptor}, 通过设置
 * {@link Configuration#enableLoggingNetworkParams()}打印网络请求参数与响应结果
 */
public class HttpLoggingInterceptor implements Interceptor {
    private static final String TAG = "HttpLogging";

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request request = chain.request(); // 获取请求
        long t1 = System.nanoTime();

        Buffer buffer = new Buffer();
        request.body().writeTo(buffer); //获取请求体

        Log.e(TAG, String.format("Sending request %s on %s%n%sRequest Params: %s",
                request.url(), chain.connection(), request.headers(), buffer.clone().readUtf8()));
        buffer.close();

        Response response = chain.proceed(request); //执行请求
        long t2 = System.nanoTime();

        BufferedSource source = response.body().source(); //获取请求结果
        source.request(Long.MAX_VALUE);
        buffer = source.buffer().clone(); //克隆返回结果, 因为buffer只能使用一次
        Log.e(TAG, String.format("Received response for %s in %.1fms%n%sResponse Json: %s",
                response.request().url(), (t2 - t1) / 1e6d, response.headers(),
                buffer.readUtf8()));

        return response;
    }
}

然后在RetrofitFactory中加入:

if (Configuration.isShowNetworkParams()) {
    OkHttpClient client = new OkHttpClient.Builder().addInterceptor(
            new HttpLoggingInterceptor()).build();

    builder.client(client);
}

以下是Log格式:

HttpLogging: Sending request http://xx.xx.xx.xx:xxxx/xxx/app/findAttractions.htm on null
         Request Params: city=%E6%AD%A6%E6%B1%89&pageNo=1&pageSize=10&latitude=xxx&longitude=xxx
HttpLogging: Received response for http://xx.xx.xx.xx:xxxx/xxx/app/findAttractions.htm in 181.2ms
         Server: Apache-Coyote/1.1
         Content-Type: text/html;charset=UTF-8
         Transfer-Encoding: chunked
         Date: Wed, 23 Mar 2016 06:50:48 GMT
         OkHttp-Sent-Millis: 1458715847034
         OkHttp-Received-Millis: 1458715847128
         Response Json: {"data":{"attractionsList":[{"distance":13110.6,"id":13,"image":"xx.xx.xx.xx:xxxx/xxx/upload/201603171607138491.jpg","latitude":30.559024,"longitude":114.30341,"name":"黄鹤楼","profile":null,"score":5,"voice":"xx.xx.xx.xx:xxxx/xxx/upload/201603171009165109.mp3"}]},"msg":"成功","page":{"pageCount":1,"pageNo":1,"pageSize":10,"totalCount":1},"status":1}

接下来的几部分都建议在有一定RxJava基础的情况下阅读. 参考RxJava.

Last updated