Android okhttp + retro fit + rxjava + hit implements the network request framework

Posted by WhiteHawksan on Wed, 17 Nov 2021 12:30:21 +0100

ūüĒ• introduce

This paper implements a network request box through okhttp + retrofit + rxjava + hit.

ūüí• Final code

        iWanAndroidService.register(map)
                .compose(ResponseTransformer.obtain())
                .subscribe(registerData -> {
                    //Request succeeded
                }, new ErrorConsumer() {
                    @Override
                    protected void error(ApiException e) {
                        //request was aborted
                    }
                });

Isn't it particularly easy.

ūüí• Project structure

ūüĒ• OkHttp

ūüí• What is OkHttp

OkHttp is a default efficient HTTP client:

  • HTTP/2 support allows all requests to the same host to share a socket.

  • Connection pooling reduces request latency if HTTP/2 is not available.

  • Transparent GZIP reduces download size.

  • Response caching completely avoids repeated requests on the network.

  • After a network problem occurs, OkHttp will remain unchanged and automatically recover from the problem.

Official documents

Simple use + source code interpretation

OkHttp will not be introduced separately.

Defects:

  • The interface configuration of network request is cumbersome, especially when it is necessary to configure complex request body, request header and parameters;

  • The data analysis process requires the user to manually get the response body for analysis, which cannot be reused;

  • Unable to adapt automatic thread switching.

  • In case of our existence, nested network requests will fall into a "callback trap".

ūüĒ• Retrofit

ūüí• What is Retrofit

Retrofit is based on OkHttp, and the network request is actually completed by OkHttp, while retrofit is mainly responsible for the encapsulation of the interface.

Retrofit not only has the efficient features of OkHttp, but also has the following advantages:

  • Support RESTful API design style.

  • Configure requests through annotations: including request methods, request parameters, request headers, return values, etc.

  • It can be combined with a variety of converters to automatically parse and serialize the obtained data: it supports Gson, Jackson, Protobuff, etc. Provides support for RxJava.

  • The request speed is fast, and the use is very convenient and flexible.

Note: Retrofit does not have the function of network request, so you need to set the distributor interceptor in OkHttpClient.

ūüí• Retrofit annotation

Official documents The usage of various annotations is also provided.

ūüĒ• OkHttp+Retrofit instance

ūüí• Add dependency

dependencies {
¬†¬†¬†¬†implementation¬†'com.squareup.retrofit2:retrofit:2.8.1'¬†//  Necessary dependency, retrofit
¬†¬†¬†¬†implementation¬†'com.squareup.retrofit2:converter-gson:2.8.1'¬†//  Parse json characters if necessary
    implementation 'com.squareup.okhttp3:logging-interceptor:4.9.0' //Print logs for unnecessary dependencies

}

ūüí• Define request interface

public interface IWanAndroidService {
    String BASE_URL = "https://www.wanandroid.com/";
    @GET("banner/json")
    Call<ResponseData<List<HomeBanner>>> homeBanner();

    @POST("user/register")
    @FormUrlEncoded
    Call<ResponseData<RegisterData>> register(@FieldMap Map<String,String> map);

}

ūüí• Set OkHttp+Retrofit

public class NetworkManager {
    private static volatile NetworkManager instances;
    private static volatile OkHttpClient okHttpClient;
    private static volatile Retrofit retrofit;

    public static NetworkManager getInstance() {
        if (instances == null) {
            synchronized (NetworkManager.class) {
                if (instances == null) {
                    instances = new NetworkManager();
                }
            }

        }
        return instances;
    }

    private static int TIME_OUT = 30; //30 seconds timeout disconnect

    private OkHttpClient initClient() {
        if (okHttpClient == null) {
            synchronized (NetworkManager.class) {
                if (okHttpClient == null) {
                    //Request log printing
                    HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
                        try {
                            MLog.e(URLDecoder.decode(message, "utf-8"));
                        } catch (UnsupportedEncodingException e) {
                            e.printStackTrace();
                            MLog.e(message);
                        }
                    });
                    loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
                    //Note 1: create OkHttpClient
                    okHttpClient = new OkHttpClient.Builder()
                            .sslSocketFactory(new NetworkSSL(TrustManager.trustAllCert), TrustManager.trustAllCert)
                            .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .addInterceptor(loggingInterceptor)
                            .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
                            .build();
                }
            }
        }
        return okHttpClient;

    }

    public Retrofit initRetrofit() {
        if (retrofit == null) {
            synchronized (NetworkManager.class) {
                if (retrofit == null) {
                    //Note 2: create Retrofit
                    retrofit = new Retrofit.Builder()
                            .client(initClient())//Optional filling
                            .baseUrl(IWanAndroidService.BASE_URL)//Required
                            .addConverterFactory(GsonConverterFactory.create())//Optional (data converter, parsing)
                            .build();
                }
            }
        }
        return retrofit;
    }
}
  • Note 1: create OkHttpClient object and build an instance of network type. Generally, all network requests will use the same singleton object. (if OkHttpClient uses the default, it can not be set)

  • Note 2: create a Retrofit object and build a carrier object of network request. There are many initialization contents during build, such as setting OkHttpClient, setting the requested url, adding data converter, etc.

ūüí• Network request

            //GET
            //Note 1: get iwanadroidservice object dynamically
            IWanAndroidService service = NetworkManager.getInstance().initRetrofit().create(IWanAndroidService.class);
            //Note 2: network request
            service.homeBanner().enqueue(new Callback<ResponseData<List<HomeBanner>>>() {
                @Override
                public void onResponse(Call<ResponseData<List<HomeBanner>>> call, Response<ResponseData<List<HomeBanner>>> response) {
                    if (response.body().getData() != null) {
                        MLog.e(response.body().getData().get(0).toString());
                        binding.loginTvContent.setText(response.body().getData().get(0).toString());
                    }
                }
                @Override
                public void onFailure(Call<ResponseData<List<HomeBanner>>> call, Throwable t) {
                    MLog.e(t.getMessage());
                }
            });
            
            //POST
            Map<String, String> map = new HashMap<>();
            map.put("username", account);
            map.put("password", passsword);
            map.put("repassword", passsword);
            NetworkManager.getInstance().initRetrofit().create(IWanAndroidService.class)
                    .register(map).enqueue(new Callback<ResponseData<RegisterData>>() {
                @Override
                public void onResponse(Call<ResponseData<RegisterData>> call, Response<ResponseData<RegisterData>> response) {
                    if (response.body().getData() != null) {
                        MLog.e(response.body().getData().toString());
                        binding.loginTvContent.setText(response.body().getData().toString());
                    }
                }

                @Override
                public void onFailure(Call<ResponseData<RegisterData>> call, Throwable t) {
                    MLog.e(t.getMessage());
                }
            });

The essence of Retrofit: complete the setting of dynamic agent for unified configuration of network requests.

ūüí• design sketch

ūüĒ• Rxjava

RxJava uses chained calls in observer mode and builder mode.

Observer mode:

After the Observable is subscribed by the Observer, the Observable will notify the corresponding Observer when sending a message, and an Observable can be subscribed by multiple observers.

Chain call: call the corresponding method to process the original object and return to the original object, so as to achieve Chain call.

participant:

  • Observable: the observed, that is, the sender of the message

  • Observer: observer, the receiver of the message

  • Subscriber: another representation of subscriber and observer

  • Scheduler: scheduler for thread switching

RxJava is of course excellent and powerful, with the following advantages:

  • It has the characteristics of responsive programming.

  • It is asynchronous, does not need to create threads manually, and has the ability of thread switching.

  • Support chain call to ensure the simplicity of the code.

  • Various operators are very powerful to meet various business needs.

  • Simplifies exception handling.

RxJava application scenario: network request, database read / write, file read / write, scheduled task and other time-consuming operations that need to be completed asynchronously can use RxJava.

ūüí• Add dependency (New)

¬†¬†¬†¬†implementation¬†"io.reactivex.rxjava2:rxjava:2.2.6"¬†//  Necessary rxjava dependencies
¬†¬†¬†¬†implementation¬†"io.reactivex.rxjava2:rxandroid:2.1.0"¬†//  Necessary rxandrroid dependency, which is required when cutting threads
    ...
¬†¬†¬†¬†implementation¬†'com.squareup.retrofit2:adapter-rxjava2:2.5.0'¬†//  Necessary dependencies, which must be used in combination with rxjava

ūüí• Modify request interface

public interface IWanAndroidService {
    String BASE_URL = "https://www.wanandroid.com/";
    //OkHttp+Retrofit
    //OkHttp+Retrofit+RxJava
    @GET("banner/json")
    Observable<ResponseData<List<HomeBanner>>> homeBanner();

    @POST("user/register")
    @FormUrlEncoded
    Observable<ResponseData<RegisterData>> register(@FieldMap Map<String,String> map);

}

ūüí• Set OkHttp+Retrofit+RxJava

    public Retrofit initRetrofitRxJava() {
        if (retrofit == null) {
            synchronized (NetworkManager.class) {
                if (retrofit == null) {
                    retrofit = new Retrofit.Builder()
                            .client(initClient())//Optional filling
                            .baseUrl(IWanAndroidService.BASE_URL)//Required
                            .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//New network request adapter
                            .addConverterFactory(GsonConverterFactory.create())//Optional (data converter, parsing)
                            .build();
                }
            }
        }
        return retrofit;
    }

ūüí• Make a network request

        NetworkManager.getInstance().initRetrofitRxJava()
                .create(IWanAndroidService.class)
                .homeBanner()
                .subscribeOn(Schedulers.io())//Switch to IO thread
                .observeOn(AndroidSchedulers.mainThread())//Switch to main thread
¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†¬†//  Add subscription
                .subscribe(listResponseData -> {
                    //Request succeeded
                    if (listResponseData != null) {
                        MLog.e(listResponseData.getData().get(0).toString());
                        binding.loginTvContent.setText(listResponseData.getData().get(0).toString());
                    }
                }, throwable -> {
                    //request was aborted
                    MLog.e(throwable.getMessage());
                });

ūüí• design sketch

ūüí• Further encapsulation

As the request is too cumbersome, we'll try to encapsulate it further.

ūüĆÄ Unified exception handling (custom ApiException)

public class ApiException extends Exception {
    //unknown error
    public static final int UNKNOWN = 1000;
    //Parsing error
    public static final int PARSE_ERROR = 1001;
    //Network error / connection error
    public static final int NETWORK_ERROR = 1002;
    private int code;
    private String displayMessage;

    public ApiException(int code, String displayMessage) {
        this.code = code;
        this.displayMessage = displayMessage;
    }

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getDisplayMessage() {
        return displayMessage;
    }

    public void setDisplayMessage(String displayMessage) {
        this.displayMessage = displayMessage;
    }

    public static ApiException handleException(Throwable e) {
        ApiException ex;
        if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {
            //Parsing error
            ex = new ApiException(PARSE_ERROR, e.getMessage());
            return ex;
        } else if (e instanceof ConnectException) {
            //network error
            ex = new ApiException(NETWORK_ERROR, e.getMessage());
            return ex;
        } else if (e instanceof UnknownHostException || e instanceof SocketTimeoutException) {
            //Connection error
            ex = new ApiException(NETWORK_ERROR, e.getMessage());
            return ex;
        } else {
            //unknown error
            ex = new ApiException(UNKNOWN, e.getMessage());
            return ex;
        }
    }
}

ūüĆÄ Unified exception handling (implement the consumer < throwable > interface)

public abstract class ErrorConsumer implements Consumer<Throwable> {
    @Override
    public void accept(@NotNull Throwable throwable) throws Exception {
        //Handle exceptions
        ApiException exception;
        if (throwable instanceof ApiException) {
            exception = (ApiException) throwable;
        } else {
            exception = ApiException.handleException(throwable);
        }
        //Call the error method
        error(exception);
    }
    //You can implement the error method when using.
    protected abstract void error(ApiException e);
}

ūüĆÄ Response conversion processing

public class ResponseTransformer<T> implements ObservableTransformer<ResponseData<T>, T> {
    public ResponseTransformer() {
    }
    public static <R> ResponseTransformer<R> obtain(){
        return new ResponseTransformer<>();
    }

    @NotNull
    @Override
    public ObservableSource<T> apply(@NotNull Observable<ResponseData<T>> upstream) {
        return upstream.onErrorResumeNext(new Function<Throwable, ObservableSource<? extends ResponseData<T>>>() {
            @Override
            public ObservableSource<? extends ResponseData<T>> apply(@NotNull Throwable throwable) throws Exception {
                return Observable.error(ApiException.handleException(throwable));
            }
        }).flatMap(new Function<ResponseData<T>, ObservableSource<T>>() {
            @Override
            public ObservableSource<T> apply(@NotNull ResponseData<T> responseData) throws Exception {
                //The request is successful. Start processing your logic
                if (0==responseData.getErrorCode()) {
                    if (null!=responseData.getData()) {
                        return Observable.just(responseData.getData());
                    } else {
                        //It is possible that the returned data result is ull. If Null is passed directly, an exception will be generated.
                        //Use reflection to create a data instance without content.
                        return Observable.just(responseData.getData());
                    }
                }
                //Request exception
                return Observable.error(new ApiException(responseData.getErrorCode(), responseData.getErrorMsg()));
            }
        }).subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread());
    }
}

ūüĆÄ Use after packaging

Get the data.

ūüĒ• Hilt(Jetpack member)

Use Hilt for dependency injection on Android. Built on Dagger, Hilt provides a standard way to incorporate Dagger dependency injection into Android applications.

Official documents are the most deadly

ūüí• Add dependency (New)

  implementation 'com.google.dagger:hilt-android:2.40.1'
  annotationProcessor 'com.google.dagger:hilt-compiler:2.40.1'

ūüí• Hilt Gradle plugin

ūüĆÄ build.gradle(Project)

buildscript {
    repositories {
        google()
        mavenCentral()
    }
    dependencies {
        classpath 'com.google.dagger:hilt-android-gradle-plugin:2.40.1'
    }
}

ūüĆÄ build.gradle(Module)

apply plugin: 'dagger.hilt.android.plugin'

ūüĆÄ Hilt Application

All applications using Hilt must contain an Application class annotated with @ HiltAndroidApp.

Create Application

import android.app.Application;

import dagger.hilt.android.HiltAndroidApp;

@HiltAndroidApp
public class App extends Application {
}

Set AndroidManifest

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.scc.wanandroid">
    <uses-permission android:name="android.permission.INTERNET"/>
    <application
        android:name=".App"
        ...>
    </application>

</manifest>

After the preparatory work is completed, we begin to use Hilt to build the network framework

ūüí• Set okhttp + retro fit + rxjava + hilt

ūüĆÄ Create NetworkModule to initialize

@InstallIn(SingletonComponent.class)
@Module
public class NetworkModule {
    private static int TIME_OUT = 30; //30 seconds timeout disconnect
    @Provides
    @Singleton
    OkHttpClient providesOkHttpClient(){
        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                try {
                    MLog.e("--network--", URLDecoder.decode(message, "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    MLog.e("--network--", message);
                }
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
        return new OkHttpClient.Builder()
                .sslSocketFactory(new NetworkSSL(TrustManager.trustAllCert), TrustManager.trustAllCert)
                .connectTimeout(TIME_OUT, TimeUnit.SECONDS)
                .addInterceptor(loggingInterceptor)
                .readTimeout(TIME_OUT, TimeUnit.SECONDS)
                .writeTimeout(TIME_OUT, TimeUnit.SECONDS)
                .build();
    }
    @Singleton
    @Provides
    Retrofit providesRetrofit(OkHttpClient okHttpClient){
        return new Retrofit.Builder()
                .client(okHttpClient)
                .baseUrl(IWanAndroidService.BASE_URL)
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .build();
    }
    @Singleton
    @Provides
    IWanAndroidService providesWanAndroidService(Retrofit retrofit){
        return retrofit.create(IWanAndroidService.class);
    }
}

ūüĆÄ use

@AndroidEntryPoint
public class LoginActivity extends AppCompatActivity {
    ActivityLoginBinding binding;
    @Inject
    IWanAndroidService iWanAndroidService;
    //Retrofit+RxJava+Hilt
        iWanAndroidService.homeBanner()
                .compose(ResponseTransformer.obtain())
                .subscribe(homeBanners -> {
                    //Request succeeded
                    if (homeBanners != null) {
                        MLog.e(homeBanners.get(0).toString());
                        binding.loginTvContent.setText(homeBanners.get(0).toString());
                    }
                }, new ErrorConsumer() {
                    @Override
                    protected void error(ApiException e) {
                        //request was aborted
                        MLog.e(e.getMessage()+e.getCode());
                    }
                });
}

ūüĆÄ design sketch

ūüĒ• Summary

The project can be used after getting it. Of course, there are places that can be optimized.

  • Processing when the obtained data is null.

  • Hide the implementation details of network requests.

  • Further refine according to the actual situation, such as API sub module creation, etc.

ūüí• Project portal

ūüĒ• Link summary

ūüĆÄ OkHttp Github

ūüĆÄ Retrofit Github

ūüĆÄ RxJava Github

ūüĆÄ Hilt official documents are the most deadly

ūüĒ• thank

ūüĆÄ Thanks for the API provided by Hongshen wanandroid

ūüĆÄ Detailed explanation of Android retro + rxjava

ūüĆÄ Encapsulating Retrofit2+RxJava2 network request framework

Topics: Android Retrofit rxjava