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