真实案例出发,再谈retrofit封装
<h2><strong>前言</strong></h2> <p>在使用了一段时间的Retrofit之后,今天终于在这里讲解到了网络的部分。目前开源的HTTP 框架有很多,Volley,Android Async Http,以及OkHttp +Retrofit等。而我在自己的使用中选择了Retrofit,这里就从基础到原理,再到实例的方式,讲解我对Retrofit做出的一些封装和使用。来让你进一步的了解和掌握Retrofit的使用。</p> <h2><strong>基础</strong></h2> <p>Retrofit一个基于OkHttp的RESTFUL API请求工具。它是 Square 推出的 HTTP 框架,主要用于 Android 和 Java。Retrofit 将网络请求变成方法的调用,使用起来非常简洁方便。</p> <p>A type-safe HTTP client for Android and Java</p> <p>如果你还对Retrofit不了解,那么我建议你去 <a href="/misc/goto?guid=4958837204152834453" rel="nofollow,noindex">官方文档</a> 了解一下。</p> <p>Retrofit使用大体分为三个步骤</p> <p>(1)Retrofit将HTTP API 转化成了Java接口的形式,所以首先我们会提供一个接口类GitHubService 。</p> <pre> <code class="language-java">public interface GitHubService { @GET("users/{user}/repos") Call<List<Repo>> listRepos(@Path("user") String user); }</code></pre> <p>(2)Retrofit类可以针对之前定义的GitHubService 接口生成一个具体实现。</p> <pre> <code class="language-java">Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .build(); GitHubService service = retrofit.create(GitHubService.class);</code></pre> <p>(3)然后就可以对GitHubService 的方法进行同步或者异步的调用来进行网络的访问,也就是说可以通过call对象获得数据:(可以使用enqueue 或者 execute来执行发起请求,enqueue是是异步执行,而 execute是同步执行。)</p> <pre> <code class="language-java">Call<List<Repo>> repos = service.listRepos("octocat");</code></pre> <p>通过上面三个步骤,我们会发现Retrofit给人眼前一亮的当然是它的注解调用和优雅的API转化为方法。每一个方法都会对应着一个Http的注解,总共有GET, POST, PUT, DELETE,HEAD五个内嵌的注解。我们也会在注解上指定相应的相对地址信息。比如上方的 @GET("users/{user}/repos")</p> <p>这里本来想将官网所有内容翻译一遍的,返现很多词不达意 。然后今天又凑巧看到了郭神公众号推荐的一篇文章 Android网络请求库 - Say hello to retrofit .对官网对的内容讲解得非常的详细易懂,继续阅读下面章节之前,一定要去看看这篇文章。</p> <p>于是我们完整的Retrofit使用流程:</p> <pre> <code class="language-java">Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()) //.addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .build(); GitHubService service = retrofit.create(GitHubService.class); Call<List<Repo>> repos = service.listRepos("octocat"); repos.enqueue(new Callback<List<Repo>>() { @Override public void onResponse(Call<List<Repo>> call, Response<List<Repo>> response) { } @Override public void onFailure(Call<List<Repo>> call, Throwable t) { } });</code></pre> <h2><strong>Retrofit原理解析</strong></h2> <p>在进一步了解和使用Retrofit之前,不妨先来了解Retrofit的原理,看看Retrofit的源码是了解原理的一个有效途径。</p> <p>(1) 源码结构</p> <p>Retrofit包含一个http包,里面全部是定义HTTP请求的Java注解,比如GET、POST、PUT、DELETE、Headers、Path、Query等等。</p> <p>余下的retrofit包中几个类和接口就是全部retrofit的代码了,代码很少因为retrofit把网络请求这部分功能全部交给了OkHttp了。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/fbcd3d0c129b29590aab8a9862bccd23.png"></p> <p style="text-align:center"><img src="https://simg.open-open.com/show/383b06a45cd0e48e21ca310ab76b5c82.png"></p> <p>(2) 整体流程</p> <p>继续回到官网的例子。</p> <p>首先关注的是我们通过 new Retrofit.Builder()...build() 进行Retrofit的构建,可以了解的是这里使用的是 Builder 模式。</p> <p>在Android源码中,经常用到Builder模式的可能就是AlerDialog 了。Builder模式用于将一个复杂的对象的构建和它的表示分离,使得同样的构建过程可以创建不同的表示。这里Retrofit使用Builder模式支持了支持不同的转换(就是将HTTP返回的数据解析成Java对象,主要有Xml、Gson等)和返回(主要作用就是将Call对象转换成另一个对象,比如RxJava)。这里也就真正的达到了构建复杂对象和它的部件进行解耦。</p> <p>这里通过build方法来了解Retrofit创建,需要6个参数。如下方代码注解:</p> <pre> <code class="language-java">public Retrofit build() { //1 baseUrl 基地址 if (baseUrl == null) { throw new IllegalStateException("Base URL required."); } //2 callFactory 默认创建一个 OkHttpClient okhttp3.Call.Factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; if (callbackExecutor == null) { //3 callbackExecutor Android 中返回的是 MainThreadExecutor callbackExecutor = platform.defaultCallbackExecutor(); } //4 adapterFactories(比如RxJavaCallAdapterFactory 用于将Call返回支持Rxjava) 把Call对象转换成其它类型 List<CallAdapter.Factory> adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); //5 converterFactories(例如GsonConverterFactory 用于Gson转换) 请求网络得到的response的转换器的集合 默认会加入 BuiltInConverters , List<Converter.Factory> converterFactories = new ArrayList<>(this.converterFactories); //6 private boolean validateEagerly; validateEagerly 是否需要立即解析接口中的方法 return new Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); } }</code></pre> <p>所以我们会看到我们通过Builder模式创建Retrofit访问对象都必须指定基地址url。如果还需要支持Gson转换,我们就需要添加 .addConverterFactory(GsonConverterFactory.create()) ,如果需要支持Rxjava,那么还需要添加 .addCallAdapterFactory (RxJavaCallAdapterFactory.create()) 。</p> <p>接着我们通过 GitHubService service = retrofit.create(GitHubService.class); create方法创建网络请求接口类GitHubService 的实例。我们正是使用该对象的listRepos方法完成了 Call<List<Repo>> repos = service.listRepos("octocat"); 获取到了数据。下面看看create方法的源码:</p> <pre> <code class="language-java">public <T> T create(final Class<T> service) { Utils.validateServiceInterface(service); if (validateEagerly) { eagerlyValidateMethods(service); } return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class<?>[] { service }, new InvocationHandler() { private final Platform platform = Platform.get(); @Override public Object invoke(Object proxy, Method method, Object... args) throws Throwable { // If the method is a method from Object then defer to normal invocation. if (method.getDeclaringClass() == Object.class) { return method.invoke(this, args); } //为了兼容 Java8 平台,Android 中不会执行 if (platform.isDefaultMethod(method)) { return platform.invokeDefaultMethod(method, service, proxy, args); } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); } }); }</code></pre> <p>create方法接受一个 Class 对象,也就是我们编写的接口,里面含有通过注解标识的请求网络的方法。注意 return</p> <p>语句部分,这里调用了 Proxy.newProxyInstance方法,这个很重要,因为用了 <strong>动态代理模式</strong> 。关于动态代理模式,可以参考这篇文章: <a href="/misc/goto?guid=4959720672163494623" rel="nofollow,noindex">公共技术点之 Java 动态代理</a> 。简单的描述就是,Proxy.newProxyInstance根据传进来的 Class 对象生成了一个实例 A,也就是代理类。每当这个代理类 A 执行某个方法时,总是会调用 InvocationHandler(Proxy.newProxyInstance中的第三个参数) 的invoke方法,在这个方法中可以执行一些操作(这里是解析方法的注解参数等),通过这个方法真正的执行我们编写的接口中的网络请求。</p> <p>也就是概括一句话:通过动态代理的方式把 Java 接口中的解析为响应的网络请求,然后交给 OkHttp 去执行。并且可以适配不同的 CallAdapter</p> <p>可以方便与 RxJava 结合使用。</p> <h2><strong>封装和使用</strong></h2> <p>之前有网友评论,说网络上的很多开源库已经封装的很完美了 ,我们就不需要再次做出多余的封装了 。这个观点实在是不敢苟同,开源库固然已经做了很多事情,但是我们还是要根据不同的业务逻辑封装自己的使用呢 。比如同样的图片加载,我们不可能每次都要调用Glide的一些初始化操作。同样的网络请求,我们也不可能每次都写一大堆初始化代码 。每个app的逻辑业务操作都是相同的,当然可以封装起来,让代码更加清爽。</p> <p>下面讲讲我这里的封装逻辑。并提供Github我的关注列表以及百度天气接口的访问,两个真实案例进行讲解使用,项目代码将会在 CameloeAnthony / <strong> Ant </strong> 中提供更新:</p> <p>(1) Github 关注列表</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/f3dacd05b646f17377a6d2f1c82b4389.png"></p> <p>来看看Github访问页面,这里只需要下面几行代码就完成了GithubUser 数据的返回。</p> <pre> <code class="language-java">mSubscription = mDataManager.loadUserFollowingList("CameloeAnthony") .subscribe(new HttpSubscriber<List<GithubUser>>() { @Override public void onNext(List<GithubUser> users) { ......Github用户数据加载完成 } });</code></pre> <p>这里使用 loadUserFollowingList 方法通过Rxjava的Observable返回 Observable<List<GithubUser>> 对象,DataManager是一个数据的入口,我们不将所有的数据访问放在DataManager中。这种创建方式在之前的文章 <a href="/misc/goto?guid=4959674442599349744" rel="nofollow,noindex">浅析MVP中model层设计</a> 中有过提及,类似于通常使用的 Respository</p> <p>接下来看看DataManager中的 loadUserFollowingList 方法。</p> <pre> <code class="language-java">/** * load following list of github users * @return Observable<List<GithubUser>> */ public Observable<List<GithubUser>> loadUserFollowingList(String userName){ return mHttpHelper.getService(GithubApi.class) .loadUserFollowingList(userName) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); }</code></pre> <p>这里传入了接口GithubApi.class 然后调用了HttpHelper的 loadUserFollowingList 方法。</p> <p>在此架构中,Model层被划分为两个部分:许多helpers类和一个 DataManager</p> <p>.helpers类的数量在不同的工程中不尽相同,但是每个都有自己的功能。比如:通过SharedPreferences与数据进行交互的PreferHelper,通过SqlBrite提供与数据库交互的DatabaseHelper,DataManager结合并且转化不同的Helpers类为Rx操作符,向Presenter层提供Observables类型的数(provide meaningful data to the Presenter),并且同时处理数据的并发操作(group actions that will always happen together.)。这一层也包含实际的model类,用于定义当前数据架构。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2a260a4e347fdba61022f024dde9b2c3.png"></p> <p>GithubApi 接口,你可以直接访问</p> <p>https://api.github.com/users/CameloeAnthony/following 获取到这些列表数据。</p> <pre> <code class="language-java">public interface GithubApi { String end_point = "https://api.github.com/"; @GET("/users/{user}/following") Observable<List<GithubUser>> loadUserFollowingList(@Path(value = "user") String user); }</code></pre> <p>GithubUser 是与Github API对应的Github用户信息的实体类,API 和实体类的转化可以去网站 http://www.jsonschema2pojo.org/ 快捷完成:</p> <pre> <code class="language-java">public class GithubUser { @SerializedName("login") private String login; @SerializedName("id") private Integer id; ...... public String getLogin() { return login; } ......</code></pre> <p>(2) 天气信息的加载</p> <p>这里加载百度API提供的天气信息</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/3209f40674ddfcd1f2f14f5831bc96e6.png"></p> <p>首先还是加载的页面的方法,非常简单的Rxjava操作完成了数据的读取</p> <pre> <code class="language-java">mSubscription = mDataManager.loadWeatherData(“成都”).subscribe(new HttpSubscriber<WeatherData>() { @Override public void onNext(WeatherData weatherData) { .......天气数据加载完成 } @Override public void onError(Throwable e) { super.onError(e); toastUtils.showToast("加载天气信息失败"); } });</code></pre> <p>接着看看DataManager提供的方法 loadWeatherData</p> <pre> <code class="language-java">public Observable<WeatherData> loadWeatherData(String location) { Map<String, String> params = new HashMap<>(); params.put("location", location); params.put("language", "zh-Hans"); params.put("unit", "c"); params.put("start", "0"); params.put("days", "3"); return mHttpHelper.getService(WeatherApi.class) .loadWeatherData(params) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()); }</code></pre> <p>这里同样是调用了HttpHelper的loadWeatherData方法。遵从的是上面一样的Model层访问原则。所有数据都是先访问DataManager再访问相应的类,比如这里的HttpHelper。</p> <pre> <code class="language-java">public interface WeatherApi { String end_point = “http://apis.baidu.com/”; //example , remember to add a apikey to your header // "http://apis.baidu.com/thinkpage/weather_api/suggestion?location=beijing&language=zh-Hans&unit=c&start=0&days=3"; @Headers("apikey: 87f4cacc3ffe1f1025ebf1ea415ff112") @GET("/thinkpage/weather_api/suggestion") Observable<WeatherData> loadWeatherData(@QueryMap Map<String,String> params); }</code></pre> <p>WeatherData同样是根据百度天气API编写的实体类 。这个实体类也是有点复杂。所以同样是通过 <a href="/misc/goto?guid=4958832210121601500" rel="nofollow,noindex">http://www.jsonschema2pojo.org/</a> 把API json放到输入框,然后写好名字,快速的完成了实体类的创建。</p> <p style="text-align:center"><img src="https://simg.open-open.com/show/2f93453630890af86a46f7075c3a4642.png"></p> <p>所以这里就讲解完了两个接口的调用 。但是客官,你肯定要说 ,这不对呀 ,你这Retrofit踪迹都没看到。你就完成了各种API调用,你逗我呢吧?哈哈,接着往下看。</p> <p>(3)retrofit封装</p> <p>这里还是要回到最初的那一段代码,retrofit的创建分为三个步骤。回到基础部分再看一下吧。虽然上面的两次API调用都没有“使用Retrofit”,但是都是使用了HttpHelper类。将 GithubApi 和 WeatherApi 分别传递到了HttpHelper对象的 getService 方法中,所以猫腻就在这里 。看下面的代码:</p> <pre> <code class="language-java">/** * Created by Anthony on 2016/7/8. * Class Note: * entrance class to access network with {@link Retrofit} * used only by{@link DataManager} is recommended * <p> * 使用retrofit进行网络访问的入口类,推荐只在{@link DataManager}中使用 */ public class HttpHelper { private static final int DEFAULT_TIMEOUT = 30; private HashMap<String, Object> mServiceMap; private Context mContext; // private Gson gson = new GsonBuilder().setLenient().create(); @Inject public HttpHelper(@ApplicationContext Context context) { //Map used to store RetrofitService mServiceMap = new HashMap<>(); this.mContext = context; } @SuppressWarnings("unchecked") public <S> S getService(Class<S> serviceClass) { if (mServiceMap.containsKey(serviceClass.getName())) { return (S) mServiceMap.get(serviceClass.getName()); } else { Object obj = createService(serviceClass); mServiceMap.put(serviceClass.getName(), obj); return (S) obj; } } @SuppressWarnings("unchecked") public <S> S getService(Class<S> serviceClass, OkHttpClient client) { if (mServiceMap.containsKey(serviceClass.getName())) { return (S) mServiceMap.get(serviceClass.getName()); } else { Object obj = createService(serviceClass, client); mServiceMap.put(serviceClass.getName(), obj); return (S) obj; } } private <S> S createService(Class<S> serviceClass) { //custom OkHttp OkHttpClient.Builder httpClient = new OkHttpClient.Builder(); //time our httpClient.connectTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); httpClient.writeTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); httpClient.readTimeout(DEFAULT_TIMEOUT, TimeUnit.SECONDS); //cache File httpCacheDirectory = new File(mContext.getCacheDir(), "OkHttpCache"); httpClient.cache(new Cache(httpCacheDirectory, 10 * 1024 * 1024)); //Interceptor httpClient.addNetworkInterceptor(new LogInterceptor()); httpClient.addInterceptor(new CacheControlInterceptor()); return createService(serviceClass, httpClient.build()); } private <S> S createService(Class<S> serviceClass, OkHttpClient client) { String end_point = ""; try { Field field1 = serviceClass.getField("end_point"); end_point = (String) field1.get(serviceClass); } catch (NoSuchFieldException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.getMessage(); e.printStackTrace(); } Retrofit retrofit = new Retrofit.Builder() .baseUrl(end_point) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(client) .build(); return retrofit.create(serviceClass); } private class LogInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); long t1 = System.nanoTime(); Timber.i("HttpHelper" + String.format("Sending request %s on %s%n%s", request.url(), chain.connection(), request.headers())); Response response = chain.proceed(request); long t2 = System.nanoTime(); Timber.i("HttpHelper" + String.format("Received response for %s in %.1fms%n%s", response.request().url(), (t2 - t1) / 1e6d, response.headers())); return response; // log Response Body // if(BuildConfig.DEBUG) { // String responseBody = response.body().string(); // Log.v("HttpHelper", String.format("Received response for %s in %.1fms%n%s%n%s", // response.request().url(), (t2 - t1) / 1e6d, response.headers(), responseBody)); // return response.newBuilder() // .body(ResponseBody.create(response.body().contentType(), responseBody)) // .build(); // } else { // Log.v("HttpHelper", String.format("Received response for %s in %.1fms%n%s", // response.request().url(), (t2 - t1) / 1e6d, response.headers())); // return response; // } } } private class CacheControlInterceptor implements Interceptor { @Override public Response intercept(Chain chain) throws IOException { Request request = chain.request(); if (!AppUtils.isNetworkConnected(mContext)) { request = request.newBuilder() .cacheControl(CacheControl.FORCE_CACHE) .build(); } Response response = chain.proceed(request); if (AppUtils.isNetworkConnected(mContext)) { int maxAge = 60 * 60; // read from cache for 1 minute response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, max-age=" + maxAge) .build(); } else { int maxStale = 60 * 60 * 24 * 28; // tolerate 4-weeks stale response.newBuilder() .removeHeader("Pragma") .header("Cache-Control", "public, only-if-cached, max-stale=" + maxStale) .build(); } return response; } } }</code></pre> <p>这里getService方法将会获取缓存中的是否有传递进来的Class对象。有则使用,没有则创建。</p> <p>这里则调用了 createService(Class<S> serviceClass) 进行了OkHttpClient的初始化操作,并添加了两个LogInterceptor,CacheControlInterceptor 分别用于打印相关的请求信息。最终调用的方法是 createService(Class<S> serviceClass, OkHttpClient client) 我们通过反射达到了end_point字段的基地址。</p> <p>然后代码也就回到了</p> <pre> <code class="language-java">Retrofit retrofit = new Retrofit.Builder() .baseUrl(end_point) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .client(client) .build(); return retrofit.create(serviceClass);</code></pre> <p>可以看到了这里同样是进行了Retrofit的Builder创建以及create操作。到这里我想你就 .addConverterFactory(GsonConverterFactory.create()) 完成了添加Gson的转换器的操作。 RxJavaCallAdapterFactory.create() 完成了RxJava结果返回的支持。所以我们才可以支持了返回Observable的对象。</p> <p>(4) 思路梳理</p> <p>这里所有的代码也就完成了我们的操作。所以能够看到网络的访问变得更加简洁。这里对优点和缺点进行总结,需要在开发使用中注意:</p> <p>优点:</p> <p>1 通过DataManager作为数据入口的形式,屏蔽底层细节,让网络的访问更加清晰。</p> <p>2 使用 快速的通过API Json数据完成了实体类的创建</p> <p>3 支持Rxjava的流式操作,是Retrofit的使用更加得心应手。当我们执行异步操作的时候,java提供了Thread, Future,FutureTask, CompletableFuture 去解决这个问题,但是随着任务的复杂程度的增加,代码也变得难于维护,他们也不能实现Rxjava一样的链式处理操作。Rxjava相比具有更高的灵活性,可以链式调用,可以对单个事件以及序列进行操作。</p> <p>4 支持Gson转换器和Retrofit的配合,省去了Gson fromJson的操作。更加便捷。</p> <p>5 HttpHelper对Retrofit的封装省去了Retrofit初始化的创建,并且添加了拦截器进行日志打印方便查看。</p> <p>缺点:</p> <p>1 Api接口类中的基地址,必须按照”end_point”的形式提供。</p> <p>2 DataManager随着项目的增大作为唯一的数据入口将会变得越来越臃肿。</p> <p>当然本项目也引入了Dagger2和ButterKnife,让代码更加的整洁易用。</p> <p>当然本篇文章没有对官网和源码细节进行进一步解析。都可以在上面和下方提供的参考链接中进行查看。</p> <p>example project make your architecting of android apps quicker and smoother ,long-term maintenance by me,blog updating at the same time.Used in real project in my development 由作者长期维护的架构以及示例代码,用于本人的各种真实项目。博客更新,希望对你的安卓架构提供指导性的意义</p> <h2><strong>参考文章</strong></h2> <p><a href="/misc/goto?guid=4959671481386446029" rel="nofollow,noindex">Retrofit官网</a></p> <p><a href="/misc/goto?guid=4959720672338624936" rel="nofollow,noindex">Android网络请求库 - Say hello to retrofit</a></p> <p><a href="/misc/goto?guid=4959720672419461902" rel="nofollow,noindex">RxJava 与 Retrofit 结合的最佳实践</a> <a href="/misc/goto?guid=4959720672506730171" rel="nofollow,noindex">「Android技术汇」Retrofit2 源码解析和案例说明</a> <a href="/misc/goto?guid=4959720672581723948" rel="nofollow,noindex">Android Retrofit源码解析</a> <a href="/misc/goto?guid=4959720672667195106" rel="nofollow,noindex"> <strong>Rxjava+ReTrofit+okHttp深入浅出-终极封装</strong> </a></p> <p> </p> <p>来自:http://blog.csdn.net/u014315849/article/details/52809011</p> <p> </p>
本文由用户 nk2670 自行上传分享,仅供网友学习交流。所有权归原作者,若您的权利被侵害,请联系管理员。
转载本站原创文章,请注明出处,并保留原始链接、图片水印。
本站是一个以用户分享为主的开源技术平台,欢迎各类分享!