[Dry goods] Android realizes perfect packaging of Rxjava2+Retrofit

Learning is like rowing upstream.

I learned the basic usage of Rxjava and Retrofit last year, but I haven't used it in actual projects. To start a new project this year, decisively introduced RxJava and Retrofit in the new project. This article will introduce the author's encapsulation of Retrofit in the project.
Let's take a look at how to use Retrofit after packaging.

RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("请求成功,妹子个数为" + response.size());
                    }
                });

That's right, it's such a simple chain call, which can display loading animation, and also adds Retrofit life cycle management.
Before you start, you need to add the dependent libraries used in the Gradle file in the module project.

    compile "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version"
    compile "com.squareup.retrofit2:retrofit:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-scalars:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:converter-gson:$rootProject.ext.retrofit2Version"
    compile "com.squareup.retrofit2:adapter-rxjava2:$rootProject.ext.retrofit2Version"
    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'
    compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
    compile 'com.squareup.okhttp3:logging-interceptor:3.4.1'
    compile "com.trello.rxlifecycle2:rxlifecycle:$rootProject.ext.rxlifecycle"
    //compile "com.trello.rxlifecycle2:rxlifecycle-android:$rootProject.ext.rxlifecycle"
    compile "com.trello.rxlifecycle2:rxlifecycle-components:$rootProject.ext.rxlifecycle"

In order to facilitate the modification of the dependent library version, we use "io.reactivex.rxjava2:rxjava:$rootProject.ext.rxjava2Version" to add dependencies, so we need to add the following content to the build.gradle file of the project:

ext {
    supportLibVersion = '25.1.0'
    butterknifeVersion = '8.5.1'
    rxjava2Version = '2.0.8'
    retrofit2Version = '2.2.0'
    rxlifecycle='2.1.0'
    gsonVersion = '2.8.0'
}

The following sections will provide a detailed analysis of this package:

  • The base class BasicResponse of server response data
  • Build the tool class IdeaApi to initialize Retrofit
  • Obtain real response data through GsonConverterFactory
  • Encapsulate DefaultObserver to handle server response
  • Processing loading
  • Manage the Retrofit life cycle
  • How to use encapsulation
  • summary

1. The base class BasicResponse of server response data.

Assume that the Json data format returned by the server is as follows:

{
 "code": 200,
 "message": "成功",
 "content": {
    ...
    }
}

Construct our BasicResponse based on the Json data format (the content of the fields in the BasicResponse needs to be determined based on the data returned by your own server). code show as below:

public class BasicResponse<T> {

    private int code;
    private String message;
    private T content;
    ...此处省去get、set方法。

2. Construct IdeaApi to initialize Retrofit.

This class obtains an instance of ApiService through RetrofitUtils. code show as below:

public class IdeaApi {
    public static <T> T getApiService(Class<T> cls,String baseUrl) {
        Retrofit retrofit = RetrofitUtils .getRetrofitBuilder(baseUrl).build();
        return retrofit.create(cls);
    }
}

RetrofitUtils is used to build Retrofit.Builder and configure OkHttp in the following aspects:

  1. Set a log interceptor to intercept the json data returned by the server. Retrofit directly converts the request to json data into entity classes, but sometimes we need to view json data. Retrofit does not provide the function of directly obtaining json data. Therefore, we need to customize a log interceptor to intercept json data and enter it into the console.

  2. Set the Http request header. Add a request header interceptor to OkHttp and configure request header information. You can also uniformly add request header data to the interface. For example, the user name and password (or token) are uniformly added to the request header. The user name, password (or token) data will be carried in the request header of each subsequent interface, which avoids adding separately for each interface.

  3. Configure the cache for OkHttp. The same can be achieved with the interceptor for caching. This includes controlling the maximum life value of the cache and controlling the expiration time of the cache.

  4. If https is adopted, we can also handle certificate verification and server verification here.

  5. Add GsonConverterFactory for Retrofit. This is a more important link, which will be explained in detail later.

The RetrofitUtils code is as follows:

public class RetrofitUtils {
    public static OkHttpClient.Builder getOkHttpClientBuilder() {

        HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {
            @Override
            public void log(String message) {
                try {
                    LogUtils.e("OKHttp-----", URLDecoder.decode(message, "utf-8"));
                } catch (UnsupportedEncodingException e) {
                    e.printStackTrace();
                    LogUtils.e("OKHttp-----", message);
                }
            }
        });
        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);

        File cacheFile = new File(Utils.getContext().getCacheDir(), "cache");
        Cache cache = new Cache(cacheFile, 1024 * 1024 * 100); //100Mb

        return new OkHttpClient.Builder()
                .readTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .connectTimeout(Constants.DEFAULT_TIMEOUT, TimeUnit.MILLISECONDS)
                .addInterceptor(loggingInterceptor)
                .addInterceptor(new HttpHeaderInterceptor())
                .addNetworkInterceptor(new HttpCacheInterceptor())
               // .sslSocketFactory(SslContextFactory.getSSLSocketFactoryForTwoWay())  // https认证 如果要使用https且为自定义证书 可以去掉这两行注释,并自行配制证书。
               // .hostnameVerifier(new SafeHostnameVerifier())
                .cache(cache);
    }

    public static Retrofit.Builder getRetrofitBuilder(String baseUrl) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").serializeNulls().create();
        OkHttpClient okHttpClient = RetrofitUtils.getOkHttpClientBuilder().build();
        return new Retrofit.Builder()
                .client(okHttpClient)
                .addConverterFactory(GsonConverterFactory.create(gson))
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .baseUrl(baseUrl);
    }
}

3. Obtain real response data through GsonConverterFactory

In the first section, we constructed the server response data BasicResponse. BasicResponse consists of three fields: code, message, and content. The code is the error code returned by the server. We will agree with the server in advance the code value for success. For example, 200 means the request is successful. However, various errors are unavoidable in the process of requesting server data. For example, when the user logs in, the password is wrong, and the request parameter is wrong. At this time, the server will return the corresponding error code according to the error situation. Generally speaking, we only care about the content data when the code is 200 when it succeeds. When the code is not 200, we only need to give the corresponding Toast prompt. In fact, what we are useful to us is only the content data when the code is 200. Therefore, we can consider filtering out the code and message, and only return the content of the content in the callback when the request is successful.

In this case, we need to customize GsonConverterFactory to achieve. We can directly copy the three related classes of GsonConverterFactory from Retrofit's source code to make modifications.

The final part is to modify the convert method in GsonResponseBodyConverter. In this method, get the server response data and judge whether the code is 200. If it is, get the content and return, if it is not, you can throw a corresponding custom exception here. Then the abnormal situation is handled uniformly in Observer. The GsonResponseBodyConverter code is as follows:

final class GsonResponseBodyConverter<T> implements Converter<ResponseBody, Object> {

    private final TypeAdapter<T> adapter;

    GsonResponseBodyConverter(TypeAdapter<T> adapter) {
        this.adapter = adapter;
    }

    @Override
    public Object convert(ResponseBody value) throws IOException {
        try {
            BasicResponse response = (BasicResponse) adapter.fromJson(value.charStream());
            if (response.getCode()==200) {
            return response.getResults();
            } else {
                // 特定 API 的错误,在相应的 DefaultObserver 的 onError 的方法中进行处理
                throw new ServerResponseException(response.getCode(), response.getMessage());
            }
        } finally {
            value.close();
        }
        return null;
    }
}

4. Build DefaultObserver to handle server response.

In the previous section, we talked about some situations that may occur when requesting the server, such as incorrect passwords and parameter errors. The server returned us the corresponding error code, and we threw the corresponding custom exception based on the error code. In addition, some abnormal situations may occur when we initiate a network request. For example, there is no network, the request timed out, or the server returned data but a data parsing exception occurred during parsing. We will also deal with such situations in a unified manner. Then we need to customize a DefaultObserver class to inherit Observer and override the corresponding method.
The two most important methods in this class are onNext and onError.

1. When the server returns data successfully, it will call back to the onNext method. Therefore, we can define an abstract method onSuccess(T response) in DefaultObserver and override the onSuccess method when calling the network.

2. If any exception occurs during the request to the server, it will be called back to the onError method. Including the exceptions thrown by ourselves in the previous section will be called back to onError. So our main task is to deal with onError. In onError, we can give the corresponding Toast prompt according to the exception information.

The code of the DefaultObserver class is as follows:

public abstract class DefaultObserver<T> implements Observer<T> {
    @Override
    public void onSubscribe(Disposable d) {

    }

    @Override
    public void onNext(T response) {
        onSuccess(response);
        onFinish();
    }

    @Override
    public void onError(Throwable e) {
        LogUtils.e("Retrofit", e.getMessage());
        if (e instanceof HttpException) {     //   HTTP错误
            onException(ExceptionReason.BAD_NETWORK);
        } else if (e instanceof ConnectException
                || e instanceof UnknownHostException) {   //   连接错误
            onException(ExceptionReason.CONNECT_ERROR);
        } else if (e instanceof InterruptedIOException) {   //  连接超时
            onException(ExceptionReason.CONNECT_TIMEOUT);
        } else if (e instanceof JsonParseException
                || e instanceof JSONException
                || e instanceof ParseException) {   //  解析错误
            onException(ExceptionReason.PARSE_ERROR);
        }else if(e instanceof ServerResponseException){
            onFail(e.getMessage());
        } else {
            onException(ExceptionReason.UNKNOWN_ERROR);
        }
        onFinish();
    }

    @Override
    public void onComplete() {
    }

    /**
     * 请求成功
     *
     * @param response 服务器返回的数据
     */
    abstract public void onSuccess(T response);

    /**
     * 服务器返回数据,但响应码不为200
     *
     */
    public void onFail(String message) {
        ToastUtils.show(message);
    }

    public void onFinish(){}

    /**
     * 请求异常
     *
     * @param reason
     */
    public void onException(ExceptionReason reason) {
        switch (reason) {
            case CONNECT_ERROR:
                ToastUtils.show(R.string.connect_error, Toast.LENGTH_SHORT);
                break;

            case CONNECT_TIMEOUT:
                ToastUtils.show(R.string.connect_timeout, Toast.LENGTH_SHORT);
                break;

            case BAD_NETWORK:
                ToastUtils.show(R.string.bad_network, Toast.LENGTH_SHORT);
                break;

            case PARSE_ERROR:
                ToastUtils.show(R.string.parse_error, Toast.LENGTH_SHORT);
                break;

            case UNKNOWN_ERROR:
            default:
                ToastUtils.show(R.string.unknown_error, Toast.LENGTH_SHORT);
                break;
        }
    }

    /**
     * 请求网络失败原因
     */
    public enum ExceptionReason {
        /**
         * 解析数据失败
         */
        PARSE_ERROR,
        /**
         * 网络问题
         */
        BAD_NETWORK,
        /**
         * 连接错误
         */
        CONNECT_ERROR,
        /**
         * 连接超时
         */
        CONNECT_TIMEOUT,
        /**
         * 未知错误
         */
        UNKNOWN_ERROR,
    }
}

Five. Processing loading

Regarding Loading, we can use RxJava's compose operator to do a very elegant treatment. First define a ProgressUtils tool class, and then use RxJava's ObservableTransformer to make a transformation to handle Loading. To display Loading, just add it .compose(ProgressUtils.< T >applyProgressBar(this)).

The ProgressUtils code is as follows:

public class ProgressUtils {
    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity, String msg) {
        final WeakReference<Activity> activityWeakReference = new WeakReference<>(activity);
        final DialogUtils dialogUtils = new DialogUtils();
        dialogUtils.showProgress(activityWeakReference.get());
        return new ObservableTransformer<T, T>() {
            @Override
            public ObservableSource<T> apply(Observable<T> upstream) {
                return upstream.doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {

                    }
                }).doOnTerminate(new Action() {
                    @Override
                    public void run() throws Exception {
                        Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }
                    }
                }).doOnSubscribe(new Consumer<Disposable>() {
                    @Override
                    public void accept(Disposable disposable) throws Exception {
                        /*Activity context;
                        if ((context = activityWeakReference.get()) != null
                                && !context.isFinishing()) {
                            dialogUtils.dismissProgress();
                        }*/
                    }
                });
            }
        };
    }

    public static <T> ObservableTransformer<T, T> applyProgressBar(
            @NonNull final Activity activity) {
        return applyProgressBar(activity, "");
    }
}

So far, the secondary packaging of RxJava and Retrofit has been basically completed. But we cannot ignore a very important point, which is the life cycle of network requests. We will explain in detail in the next section.

Six, manage the Retrofit life cycle

When the activity is destroyed, the network request should also be terminated. Otherwise, it may cause a memory leak. Will seriously affect the performance of the App! Therefore, the management of Retrofit's life cycle is also an important point. Here we use RxLifecycle to manage the life cycle of Retrofit. The use process is as follows:

1. Add dependencies in gradel as follows:

compile 'com.trello.rxlifecycle2:rxlifecycle:2.1.0'
compile 'com.trello.rxlifecycle2:rxlifecycle-components:2.1.0'

2. Let our BaseActivity inherit RxAppCompatActivity.
The specific code is as follows:

public abstract class BaseActivity extends RxAppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(getLayoutId());
        init(savedInstanceState);
    }
    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    protected abstract @LayoutRes int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);
}

Similarly, the BaseFragment of our project inherits RxFragment (note the use of RxFragment under the inherited V4 package), as follows:

public abstract class BaseFragment extends RxFragment {

    public View rootView;
    public LayoutInflater inflater;

    @Nullable
    @Override
    public final View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
        super.onCreateView(inflater, container, savedInstanceState);
        this.inflater = inflater;
        if (rootView == null) {
            rootView = inflater.inflate(this.getLayoutId(), container, false);
            init(savedInstanceState);
        }
        ViewGroup parent = (ViewGroup) rootView.getParent();
        if (parent != null) {
            parent.removeView(rootView);
        }
        return rootView;
    }

    protected abstract int getLayoutId();

    protected abstract void init(Bundle savedInstanceState);

    protected void showToast(String msg) {
        ToastUtils.show(msg);
    }

    @Override
    public void onResume() {
        super.onResume();
    }

    @Override
    public void onPause() {
        super.onPause();
    }

    @Override
    public void onDestroyView() {
        super.onDestroyView();
    }
}

3. Use the compose operator to manage the Retrofit life cycle:

myObservable
            .compose(bindToLifecycle())
            .subscribe();

或者

myObservable
    .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
    .subscribe();

For detailed usage of RxLifecycle, please refer to RxLifecycle official website

7. How to use encapsulation

The previous sections explained how RxJava performs secondary encapsulation, and the code of the encapsulation part can be placed in the Library module of our project. So how should we use it in the app module after encapsulation?

1. Define an interface to store the API of our project

public interface IdeaApiService {
    /**
     * 此接口服务器响应数据BasicResponse的泛型T应该是List<MeiZi>
     * 即BasicResponse<List<MeiZi>>
     * @return BasicResponse<List<MeiZi>>
     */
    @Headers("Cache-Control: public, max-age=10")//设置缓存 缓存时间为100s
    @GET("福利/10/1")
    Observable<List<MeiZi>> getMezi();

    /**
     * 登录 接口为假接口 并不能返回数据
     * @return
     */
    @POST("login.do")
    Observable<LoginResponse> login(@Body LoginRequest request);

    /**
     * 刷新token 接口为假接口 并不能返回数据
     * @return
     */
    @POST("refresh_token.do")
    Observable<RefreshTokenResponseBean> refreshToken(@Body RefreshTokenRequest request);

    @Multipart
    @POST("upload/uploadFile.do")
    Observable<BasicResponse> uploadFiles(@Part List<MultipartBody.Part> partList);
}

2. Define a RetrofitHelper class to obtain an instance of IdeaApiService through IdeaApi.

public class RetrofitHelper {
    private static IdeaApiService mIdeaApiService;

    public static IdeaApiService getApiService(){
        return mIdeaApiService;
    }
    static {
       mIdeaApiService= IdeaApi.getApiService(IdeaApiService.class, Constants.API_SERVER_URL);
    }
}

3. Initiate a network request in Activity or Fragment

    /**
     * Get请求
     * @param view
     */
    public void getData(View view) {
        RetrofitHelper.getApiService()
                .getMezi()
                .compose(this.<List<MeiZi>>bindToLifecycle())
                .compose(ProgressUtils.<List<MeiZi>>applyProgressBar(this))
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new DefaultObserver<List<MeiZi>>() {
                    @Override
                    public void onSuccess(List<MeiZi> response) {
                        showToast("请求成功,妹子个数为" + response.size());
                    }
                });
    }

8. Summary

This article mainly explains the secondary packaging of Rxjava and Retrofit. The above content is also optimized by the author after a long time of modification with reference to various materials. However, given my limited ability, impropriety cannot be avoided. Please also forgive me. In addition, when submitting to Guoshen's public account, the article may have many inelegant handling, such as the processing of response data and the processing of Loading. After the submission was pushed, I received a lot of suggestions from friends, so the author also referred to your opinions and made optimizations. Thank you all. Finally, if you have any questions, please leave a comment in the article.

Complete architect growth path, study notes GitHub address: my study notes

Guess you like

Origin blog.csdn.net/Androiddddd/article/details/112496488