🔥 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.
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.