Anthony’s brief Book blog project code: CameloeAnthony/Ant

preface

After using Retrofit for a while, I finally covered the networking part here today. At present, there are many open source HTTP frameworks, such as Volley, Android Async HTTP, and OkHttp +Retrofit. I chose Retrofit for my own use, and here I will explain some encapsulation and use of Retrofit from basic to principle and then to examples. To further your understanding and mastery of Retrofit.

basis

Retrofit is a RESTFUL API request tool based on OkHttp. It is Square’s HTTP framework for Android and Java. Retrofit turns network requests into method invocations, which are simple and easy to use.

A type-safe HTTP client for Android and Java

If you don’t know anything about Retrofit, I suggest you check out the official documentation.

Retrofit is basically a three-step process. (1) Retrofit converts HTTP apis into Java interfaces, so first we provide an interface class, GitHubService.

public interface GitHubService {
  @GET("users/{user}/repos")
  Call> listRepos(@Path("user") String user);
}Copy the code

(2) The Retrofit class can generate a concrete implementation of the previously defined GitHubService interface.

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .build();

GitHubService service = retrofit.create(GitHubService.class);Copy the code

(3) We can then call the GitHubService method synchronously or asynchronously to access the network. That is, we can get data from the Call object: (You can use either enqueue, which is asynchronous, or execute, which is synchronous.)

Call> repos = service.listRepos("octocat");Copy the code

Through the above three steps, Retrofit certainly stands out for its annotation calls and elegant API-to-methods. Each method has an Http annotation, including five built-in annotations: GET, POST, PUT, DELETE, and HEAD. We also specify the corresponding relative address information on the annotation. For example, @get (“users/{user}/repos”)

Here originally wanted to translate all the content of the official website again, return a lot of inexpressible words. Then today happened to see guo Shen public number recommended an article Android network request library – Say Hello to Retrofit. It’s very detailed and easy to understand, so be sure to check this article out before you move on to the next chapter.

So our entire Retrofit process:

Retrofit retrofit = new Retrofit.Builder()
.baseUrl("https://api.github.com/")
.addConverterFactory(GsonConverterFactory.create())
//.addCallAdapterFactory(RxJavaCallAdapterFactory.create())
.build();

GitHubService service = retrofit.create(GitHubService.class);
Call> repos = service.listRepos("octocat");
repos.enqueue(new Callback>() {
    @Override
    public void onResponse(Call> call, Response> response) {

    }
    @Override
    public void onFailure(Call> call, Throwable t) {

    }
 });Copy the code

Retrofit principle analysis

Looking at the source code for Retrofit is a good way to understand how Retrofit works before further understanding and using it.

(1) The source code structure Retrofit contains an HTTP package full of Java annotations that define HTTP requests such as GET, POST, PUT, DELETE, Headers, Path, Query, etc. The remaining classes and interfaces in the Retrofit package are all Retrofit code, and very little code because Retrofit has relegated network requests to OkHttp.







(2) Overall process

Let’s go back to the example on the website.

New Retrofit.Builder()… Build () builds Retrofit, and you can see that the Builder pattern is used here.

In Android source code, the Builder mode is probably the AlerDialog. The Builder pattern is used to separate the construction of a complex object from its representation so that the same construction process can create different representations. Retrofit uses the Builder schema to support different transformations (that is, parsing data returned from HTTP into Java objects, mainly Xml, Gson, etc.) and returns (that is, converting a Call object into another object, such as RxJava). This is where you really build a complex object and decouple it from its components.

Here you can see Retrofit creation through the build method, which takes six parameters. The following code comments:

If (baseUrl == null) {throw new IllegalStateException("Base URL required."); } //2 callFactory creates a default OkHttpClient okHttp3.call.factory callFactory = this.callFactory; if (callFactory == null) { callFactory = new OkHttpClient(); } Executor callbackExecutor = this.callbackExecutor; If (callbackExecutor == null) {//3 callbackExecutor Android returns MainThreadExecutor callbackExecutor = platform.defaultCallbackExecutor(); } //4 adapterFactories (such as RxJavaCallAdapterFactory) convert the Call object to another type. List adapterFactories = new ArrayList<>(this.adapterFactories); adapterFactories.add(platform.defaultCallAdapterFactory(callbackExecutor)); BuiltInConverters are added by default to the collection of converterFactories (such as GsonConverterFactory for Gson converters) that request a response from the network. List converterFactories = new ArrayList<>(this.converterFactories); //6 private boolean validateEagerly; Return New Retrofit(callFactory, baseUrl, converterFactories, adapterFactories, callbackExecutor, validateEagerly); }}Copy the code

So we’ll see that when we create Retrofit access objects through Builder mode, we have to specify the base address URL. If you also need to support Gson transformation, we need to add. AddConverterFactory (GsonConverterFactory. The create ()), if you need to support Rxjava, You will need to add. AddCallAdapterFactory (RxJavaCallAdapterFactory. Create ()).

GitHubService = retrofit.create(githubservice.class); The create method creates an instance of the network request interface class GitHubService. It is using the listRepos method on this object that we complete Call> repos = service.listRepos(“octocat”); We get the data. Here is the source code for the create method:

public T create(final Class 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); } // To be compatible with Java8 platform, Android will not perform the if (platform. IsDefaultMethod (method)) {return platform. InvokeDefaultMethod (method, the service, the proxy, args);  } ServiceMethod serviceMethod = loadServiceMethod(method); OkHttpCall okHttpCall = new OkHttpCall<>(serviceMethod, args); return serviceMethod.callAdapter.adapt(okHttpCall); }}); }Copy the code

The Create method takes a Class object, the interface we wrote, that contains methods that request the network identified by annotations. Notice in the return statement section that the proxy. newProxyInstance method is called, which is important because the dynamic Proxy mode is used. For information about the dynamic proxy pattern, see this article: Java Dynamic Proxies at Common Technical Points. Proxy.newproxyinstance generates an instance A, the Proxy Class, from the Class object passed in. Whenever this Proxy class A executes A method, the invoke method of InvocationHandler(the third parameter in proxy.newProxyInstance) is called, and some operations (in this case parsing the annotation parameters of the method, etc.) can be performed in this method. This method actually executes the network request in the interface we wrote.

In a nutshell: dynamically proxy the Java interface into a response to a network request and hand it over to OkHttp for execution. And can be adapted to different Calladapters, can easily be used with RxJava.

See this article for more details on the source code. Android Retrofit source code parsing

Encapsulation and use

There were comments earlier that many open source libraries on the web are already perfectly packaged, so we don’t need to do extra packaging again. Open source libraries do a lot of things, but we still need to encapsulate our use for different business logic. For example, with the same image load, we can’t call some of Glide’s initialization operations every time. We can’t write a bunch of initialization code for every network request. The logical business operations are the same for each app, and can be wrapped up to make the code cleaner.

Here’s my encapsulation logic. It also provides access to Github my focus list and Baidu weather interface. Two real cases will be explained and used. The project code will be updated in CameloeAnthony/Ant:

(1) Github focus list




Take a look at the Github access page, where the GithubUser data is returned with just a few lines of code.

mSubscription = mDataManager.loadUserFollowingList("CameloeAnthony") .subscribe(new HttpSubscriber>() { @Override public  void onNext(List users) { ...... Github user data loading complete}});Copy the code

The loadUserFollowingList method is used to return an Observable> object via Rxjava’s Observable. The DataManager is a data entry, so we don’t put all data access in the DataManager. This approach was mentioned in the MVP model layer design article, and is similar to the Respository commonly used

Next look at the loadUserFollowingList method in the DataManager.

/** * load following list of github users * @return Observable> */ public Observable> loadUserFollowingList(String userName){ return mHttpHelper.getService(GithubApi.class) .loadUserFollowingList(userName) .subscribeOn(Schedulers.io())  .observeOn(AndroidSchedulers.mainThread()); }Copy the code

Githubapi.class is passed in and the loadUserFollowingList method of HttpHelper is called.

In this architecture, the Model layer is divided into two parts: a number of helpers classes and a DataManager. The number of helpers classes varies from project to project, but each has its own function. Such as: Interact with data by SharedPreferences PreferHelper, provide interact with the database through SqlBrite DatabaseHelper, DataManager according to different class Helpers for Rx and conversion operators, Provide meaningful data to the Presenter layer for the number of Observables types, And concurrently handle concurrent operations on data (group actions that will always happen together.). This layer also contains the actual Model classes that define the current data architecture. ——- From a brief analysis of the MODEL layer design in MVP




GithubApi interface, you can directly access api.github.com/users/Camel… Get the list data.

public interface GithubApi {
    String end_point = "https://api.github.com/";
    @GET("/users/{user}/following")
    Observable> loadUserFollowingList(@Path(value = "user") String user);
}Copy the code

GithubUser is the entity class of Github user information corresponding to Github API. The transformation of API and entity class can be quickly completed by going to www.jsonschema2pojo.org/ :

public class GithubUser { @SerializedName("login") private String login; @SerializedName("id") private Integer id; . public String getLogin() { return login; }...Copy the code

(2) Loading of weather information Load the weather information provided by Baidu API here




The first is the method of loading the page. A very simple Rxjava operation does the reading of the data

MSubscription = mDataManager. LoadWeatherData (" chengdu "). The subscribe (new HttpSubscriber () {@ Override public void onNext(WeatherData weatherData) { ....... } @override public void onError(Throwable e) {super.onerror (e); Toastutils.showtoast (" Failed to load weather info "); }});Copy the code

Then look at the loadWeatherData method provided by DataManager

    public Observable loadWeatherData(String location) {
        Map 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());
    }Copy the code

Again, the loadWeatherData method of HttpHelper is called. Follow the same Model layer access principles as above. All data is accessed first by the DataManager and then by the corresponding class, such as HttpHelper here.

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 loadWeatherData(@QueryMap Map params); }Copy the code

WeatherData is also an entity class written according to the Baidu weather API. This entity class is also a bit complicated. So I also put the API JSON into the input box through www.jsonschema2pojo.org/, and then write the name, and quickly complete the creation of the entity class.




So that’s the end of the two interface calls. But guest officer, you have to say, this is not right, you don’t see any traces of Retrofit. You’ve done all these API calls. Are you kidding me? Haha, keep reading.

Back to the original piece of code, creating RetroFIT is a three-step process. Let’s go back to the basics. Neither of the above API calls “uses Retrofit,” but both use the HttpHelper class. GithubApi and WeatherApi are passed to the getService method of the HttpHelper object, respectively, so here’s the trick. Look at the following code:

/** * Created by Anthony on 2016/7/8. * Class Note: * entrance class to access network with {@link Retrofit} * used only by{@link DataManager} is recommended * < /code >< p >< code> * Entry class for network access using Retrofit, It is recommended to use */ public class HttpHelper {private static Final int DEFAULT_TIMEOUT = 30 only in {@link DataManager}. 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; }}}Copy the code

Here the getService method will find out if there are any Class objects passed in the cache. If yes, use it; if no, create it. Here is called the createService (Class serviceClass) conducted OkHttpClient initialization, and add two LogInterceptor, CacheControlInterceptor respectively used for printing the request of the related information. The final method called is createService(Class serviceClass, OkHttpClient client) and we use reflection to reach the base address of the end_point field. Such as api.github.com/ and apis.baidu.com above. And then the code goes back

        Retrofit retrofit = new Retrofit.Builder()
                .baseUrl(end_point)
                .addConverterFactory(GsonConverterFactory.create())
                .addCallAdapterFactory(RxJavaCallAdapterFactory.create())
                .client(client)
                .build();

        return retrofit.create(serviceClass);Copy the code

As you can see, this is also the Builder creation of Retrofit and the create operation. I think you got here. AddConverterFactory (GsonConverterFactory. The create ()) completed the add Gson converter operation. RxJavaCallAdapterFactory. The create () finished RxJava results back support. That’s why we can support returning an Observable.

(4) Train of thought

All of the code here is done for us. So you can see that access to the network is much simpler. Here are the advantages and disadvantages of the summary, need to pay attention to in the development of use:

Advantages:

1. In the form of DataManager as the data entry, the low-level details are shielded to make the network access clearer.

2. Use www.jsonschema2pojo.org/ website to quickly complete the creation of entity class through API Json data

3 Supports Rxjava streaming operations, making Retrofit easier to use. Java provides Thread, Future,FutureTask, and CompletableFuture to solve this problem when performing asynchronous operations, but as the complexity of the task increases, the code becomes difficult to maintain. They also do not implement Rxjava’s chain-processing operations. Rxjava is more flexible than Rxjava, which can be called in chain and can operate on individual events as well as sequences.

4 Supports Gson converter and Retrofit, eliminating Gson fromJson operations. More convenient.

5 HttpHelper’s encapsulation of Retrofit eliminates the need to create Retrofit initializations and adds interceptors for log printing to facilitate viewing.

Disadvantages:

The base address in the Api interface class must be provided as “end_point”.

2 DataManager will become more and more bloated as the project grows as the only data entry point.

Dagger2 and ButterKnife were also introduced to make the code cleaner and easier to use.

Of course, this article does not go into the details of the official website and source code. Both can be viewed in the reference links above and below. See the code for more details

CameloeAnthony/Ant 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. Blog updates, hoping to provide guidance on your Android architecture

Refer to the article

RxJava Retrofit best practices combined with Retrofit “Android Technology Pool” Retrofit2 source code analysis and case notes Android Rxjava+ Retrofit +okHttp – the ultimate package