In this paper, the source: Tamic article/www.jianshu.com/p/b1979c256…

Rxjava +Rterofit several skills to master





Write the picture description here

RxJava introduction and details please go to the famous “RxJava details”, here to continue the previous series of introduction to some easily overlooked techniques.

Retrofit+RxJava Combination series read:

  • Retrofit 2.0

    Super practice, perfect support Https transmission
  • Retrofit2.0

    Perfect synchronization Cookie implementation login free

  • Retrofit 2.0 super Practices (III), easy to upload files/images

  • -Retrofit 2.0 Superpowers (iv), complete download of large file breakpoints

  • Super useful RetrofitClient tool class based on Retrofit2.0 package

  • Play IOC, teach you to implement custom Retrofit framework freehand


unsubscribe

Normally we can simply unsubscribe after a view dies without RxJava executing it

subscription.unsubscribe(a)observable.unsubscribeOn(Schedulers.io());Copy the code

This can be called in the activity onDestroy() or Fragment onDestroyView()

There are also scenarios where network data is requested by rxJava and the network is returned to save the data and update the UI. In this case, the view is dead, which will surely lead to rxJava error and App flash back. In this case, we can determine whether the previous activity/ View is empty and whether it has been shown. If neither exists, you don’t need to update the UI. Just save the data.

Subscribe to the problem

To avoid blocking the UI, we need to delay the execution of the subscription.

Subscribe now;

         observable
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(action);Copy the code

Subscribe to delay

observable.delay(2, TimeUnit.SECONDS)
               .subscribeOn(Schedulers.io())
               .observeOn(AndroidSchedulers.mainThread())
               .subscribe(action);Copy the code

Basic ApiService

We usually write interfaces with the following definition, adding an API must write a method

public interface MyApi {

@GET("app.php")
Observable<SouguBean> getSougu(@Query("name") String name);

@GET("/getWeather")
Observable<ResponseBody> getWeather(@QueryMap Map<String, String> maps);
}Copy the code

In many cases, every new interface requires an API. Is there a good way to replace this situation?

@GET()
<T> Observable<ResponseBody> get(
        @Url String url,
        @QueryMap Map<String, T> maps);Copy the code

We can define a generic getApi, pass in the URL dynamically, return the Modle as ResponseBody, and define the actual parameters as generic, and change the URL, the server return type, and the number of parameters. This is a perfect way to change the URL, the server return type, and the number of parameters. Because Retrofit explicitly states that interfaces must be given specific types, slow down!

The upper layer for general assembly can look like this:

   public <T> T get(String url, Map<String, T> maps, BaseSubscriber<ResponseBody> subscriber) {
return (T) apiManager.get(url, maps)
.compose(schedulersTransformer)
.compose(handleErrTransformer())
.subscribe(subscriber);
}Copy the code

Look not to understand? Don’t understand it is not strange, the source can go to the end of the article download research, here is just a list. This is a good way to migrate from HttpClent to Retrofit with interface adaptation issues.

Based on the Subscriber

Most of the time, we need to use RxJava to open multiple Observables to read the network, which makes it difficult for us to deal with different Subscriber, so we process the return of Subscriber to the network and judge whether there is a network. You can even build an abstract BaseSubscribe class that shows loading progress on demand and so on, dealing only with start() and onCompleted (), and only with onError () and onNext () for upper-level processing

 /** * BaseSubscriber * Created by Tamic on 2016-7-15. */
   public abstract class BaseSubscriber<T> extends Subscriber<T> {

       private BaseActivity context;

       public BaseSubscriber(BaseActivity context) {
           this.context = context;
       }

       @Override
       public void onStart() {
           super.onStart();

           if (!NetworkUtil.isNetworkAvailable(context)) {

               Toast.makeText(context, "The current network is unavailable. Please check the network condition.".Toast.LENGTH_SHORT).show();
              // ** ** ** *
               onCompleted();

               return;
           }
           // Displays a progress bar
           showLoadingProgress();
       }

       @Override
       public void onCompleted() {
          // Close the waiting progress barcloseLoadingProgress(); }}Copy the code

In this way, we only care about success and failure when we call the upper layer, and we don’t need to care about the network

``` observable.. subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new BaseSubscriber<ResponseBody>(MainActivity.this) {

                @Override
                public void onError(Throwable e) {
                   Toast.makeText(MainActivity.this, e.getMessage(), Toast.LENGTH_LONG).show();
                }

                @Override
                public void onNext(ResponseBody responseBody) {
                    Toast.makeText(MainActivity.this, responseBody.toString(), Toast.LENGTH_LONG).show(); }}); ;Copy the code

If you want to handle errors uniformly, you can also handle onError() in BaseSubscriber and then overlay the callback on top of it, depending on your project, as you can see below

If the successful result is processed, you can add the ResonseBody to the generic <Response<T>>. The Response usually contains Code, MSg, and Data. Here you can distribute the business according to the Code

You can also define the Response base class if you are having trouble with the current return judgment

 /** * Created by Tamic on 2016-06-06. */

public class BaseResponse<T> {

  private int code;
  private String msg;
  private T data;

 public int getCode(a) {
     return code;
 }

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

public String getMsg(a) {
    return msg;
}

public void setMsg(String msg) {
    this.msg = msg;
}

public T getData(a) {
    return data;
}

public void setData(T data) {
    this.data = data;
}

public boolean isOk(a) {
    return code == 0;
}Copy the code

}

This way we only need to judge the status code in onNext()

                       @Override
                        public void onNext(BaseResponse<IpResult> responseBody) {

                            if (responseBody.isOk()) {
                           // This ok is not HTTP access OK,Copy the code

// Is the success code agreed with the server, some people do not like to add this filter, some people like to add business to the business callback, if not the success code will not go error callback, also do not go success callback, straight to the business callback

                                IpResult ip = responseBody.getData();
                                Toast.makeText(MainActivity.this, ip.toString(), Toast.LENGTH_LONG).show();}}Copy the code

Error result problem

Func1 of RXJva is used for packaging conversion of the original Throwable

We have converted the original Throwable into a custom ResponeThrowable;

private static class HttpResponseFunc"T"implements Func1"Throwable.Observable"T""{
    @Override public Observable<T> call(Throwable t) {
        returnObservable.error(ExceptionHandle.handleException(t)); }}Copy the code

ResponeThrowable

public static class ResponeThrowable extends Exception {
    public int code;
    public String message;

    public ResponeThrowable(Throwable throwable, int code) {
        super(throwable);
        this.code = code; }}Copy the code

We add Func1 to Observable once we have handled the forcible transform:

So you can associate your custom Func1 with an error handler class using the onErrorResumeNext provided by Observable:

  ((Observable) observable).onErrorResumeNext(new HttpResponseFunc<T>());Copy the code

This is probably a bit confusing for you, but you need to know about RxJava’s escapes and the operators Observable.Transformer and Func1

In this way, the error status returned by the server is processed by ourselves, and then a little translation can be achieved in a language that users can understand

For this class, I refer to the case of Yiye Benzhou, and I have made improvements again:

ExceptionHandle Error processing driver

public class ExceptionHandle {

 private static final int UNAUTHORIZED = 401;
 private static final int FORBIDDEN = 403;
 private static final int NOT_FOUND = 404;
 private static final int REQUEST_TIMEOUT = 408;
 private static final int INTERNAL_SERVER_ERROR = 500;
 private static final int BAD_GATEWAY = 502;
 private static final int SERVICE_UNAVAILABLE = 503;
 private static final int GATEWAY_TIMEOUT = 504;

 public static ResponeThrowable handleException(Throwable e) {
    ResponeThrowable ex;
    if (e instanceof HttpException) {
        HttpException httpException = (HttpException) e;
        ex = new ResponeThrowable(e, ERROR.HTTP_ERROR);
        switch (httpException.code()) {
            case UNAUTHORIZED:
            case FORBIDDEN:
            case NOT_FOUND:
            case REQUEST_TIMEOUT:
            case GATEWAY_TIMEOUT:
            case INTERNAL_SERVER_ERROR:
            case BAD_GATEWAY:
            case SERVICE_UNAVAILABLE:
            default:
                ex.message = "Network error";
                break;
        }
        return ex;
    } else if (e instanceof ServerException) {
        ServerException resultException = (ServerException) e;
        ex = new ResponeThrowable(resultException, resultException.code);
        ex.message = resultException.message;
        return ex;
    } else if (e instanceof JsonParseException
            || e instanceof JSONException
            || e instanceof ParseException) {
        ex = new ResponeThrowable(e, ERROR.PARSE_ERROR);
        ex.message = "Parsing error";
        return ex;
    } else if (e instanceof ConnectException) {
        ex = new ResponeThrowable(e, ERROR.NETWORD_ERROR);
        ex.message = "Connection failed";
        return ex;
    } else if (e instanceof javax.net.ssl.SSLHandshakeException) {
        ex = new ResponeThrowable(e, ERROR.SSL_ERROR);
        ex.message = "Certificate verification failed";
        return ex;
    }
    else {
        ex = new ResponeThrowable(e, ERROR.UNKNOWN);
        ex.message = "Unknown error";
        returnex; }}/** * the convention is abnormal */
class ERROR {
    /** * Unknown error */
    public static final int UNKNOWN = 1000;
    /** * parsing error */
    public static final int PARSE_ERROR = 1001;
    /** * Network error */
    public static final int NETWORD_ERROR = 1002;
    /**
     * 协议出错
     */
    public static final int HTTP_ERROR = 1003;

    /** * Certificate error */
    public static final int SSL_ERROR = 1005;
}

public static class ResponeThrowable extends Exception {
    public int code;
    public String message;

    public ResponeThrowable(Throwable throwable, int code) {
        super(throwable);
        this.code = code; }}public class ServerException extends RuntimeException {
    public int code;
    publicString message; }}Copy the code

You can then handle the exception pull in BaseSubscriber<T>

public abstract class BaseSubscriber<T> extends Subscriber<T> {

private Context context;


public BaseSubscriber(Context context) {
    this.context = context;
}

@Override
public void onError(Throwable e) {
    Log.e("Tamic", e.getMessage());
    // todo error somthing

    if(e instanceof ExceptionHandle.ResponeThrowable){
        onError((ExceptionHandle.ResponeThrowable)e);
    } else {
        onError(new ExceptionHandle.ResponeThrowable(e, ExceptionHandle.ERROR.UNKNOWN)); }}}Copy the code

The final upper-level call looks like this:

 RetrofitClient.getInstance(MainActivity.this).createBaseApi().getData(new   BaseSubscriber<IpResult>(MainActivity.this) {

                @Override
                public void onError(ResponeThrowable e) {
                   // Handle post-translation exceptions.
                    Log.e("Tamic", e.code + ""+ e.message);
                    Toast.makeText(MainActivity.this, e.message, Toast.LENGTH_LONG).show();

                }

                @Override
                public void onNext(IpResult responseBody) {
                    Toast.makeText(MainActivity.this, responseBody.toString(), Toast.LENGTH_LONG).show(); }},"21.22.11.33");Copy the code

If you want to override BaseSubscriber’s onStat () and onCompleted() classes, you can override BaseSubscriber’s onStat () and onCompleted() classes. BaseSubscriber only handles public processing. Or the business returns the format check, the specific success of the resolution of his subclass (implementation class) to do.

Note: If you do not want to add business distribution to the error callback, you can also do this: for example, some people like to add business processing to the business callback, if the background return business code is not successful code, do not want to go through the error callback, do not want to go through the success callback, want to go straight business callback.

It can be handled like this:

In onNext(), call back a custom abstract onBusiness(code, masg) that subclasses it to implement

                       @Override
                        public void onNext(BaseResponse<IpResult> responseBody) {

                            if(! responseBody.isOk()) {// Business distribution
                                 onBusiness(responseBody.getCode, responseBody.getMsg)
                            } else {
                                  // Successful callback
                                  onNext(responseBody.getData())
                            }

                        }Copy the code

The cache problem

Common cache: Sometimes you need to add caching capabilities when there is no network, so Retrofit added a base interceptor to handle caching

/** * BaseInterceptor * Created by Tamic on 2016-7-15. */
public class BaseInterceptor implements Interceptor{
    private Map<String.String> headers;
    private Context context;
    public BaseInterceptor(Map<String.String> headers, Context context) {
        this.headers = headers;
        this.context = context;
    }

    @Override
    public Response intercept(Chain chain) throws IOException {

        Request.Builder builder = chain.request()
                .newBuilder(a); builder.cacheControl(CacheControl.FORCE_CACHE).url(chain.request().url()) .build();if(! NetworkUtil.isNetworkAvailable(context)) { ((Activity)context).runOnUiThread(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(context, "No network at present!", Toast.LENGTH_SHORT).show(); }}); }if(headers ! =null && headers.size() > 0) {
            Set<String> keys = headers.keySet();
            for (String headerKey : keys) {
                builder.addHeader(headerKey, headers.get(headerKey)).build(); }}if (NetworkUtil.isNetworkAvailable(context)) {
            int maxAge = 60; // read from cache for 60 s
            builder
                    .removeHeader("Pragma")
                    .addHeader("Cache-Control"."public, max-age=" + maxAge)
                    .build();
        } else {
            int maxStale = 60 * 60 * 24 * 14; // tolerate 2-weeks stale
            builder
                    .removeHeader("Pragma")
                    .addHeader("Cache-Control"."public, only-if-cached, max-stale=" + maxStale)
                    .build();
        }
        returnchain.proceed(builder.build()); }}Copy the code

OkHttpClient joins the interceptor

       okHttpClient = new OkHttpClient.Builder()
             .addInterceptor(new BaseInterceptor(headers))
            .addInterceptor(new   CaheInterceptor(context))
            .addNetworkInterceptor(new CaheInterceptor(context))
            .build();Copy the code

Retrofit to join okhttpClient

retrofit = new Retrofit.Builder().client(okHttpClient)
                .baseUrl(url)
                .build(a);Copy the code

Separate cache:

If you don’t want to add a public cache and want to cache an API separately using Headers, you can do this:

@Headers("Cache-Control : public, max-age = 3600")
@GET("service/getIpInfo.php")
Observable<BaseResponse<IpResult>> getData(@Query("ip") String ip);Copy the code

It should be noted that the following two sentences must also be added:

             .addInterceptor(new   CaheInterceptor(context))
             .addNetworkInterceptor(new CaheInterceptor(context))Copy the code

Cache path and default size

If you want to change the cache force of okHTTP, you can set the cache path as follows

 Cache cache = new Cache(httpCacheDirectory, 10 * 1024 * 1024);Copy the code

The first parameter is the path and the second maximum cache size

                      okHttpClient = new OkHttpClient.Builder().cache(cache)
                           .build(a);Copy the code

In this case, the custom Cache policy is added

Custom cache

HTTP 504 Unsatisfiable Request (only-if-cached) ¶ If you don’t want to use okHTTP to create a cache, you will need to load the Request from the server.

In addition to modifying request. cacheControl to implement caching, you can also customize a Cahe policy to implement local hard caching.

Build CaheManager, using Url corresponding Json implementation, this class is very simple, you can implement your own, time policy can self-add extension in BaseSubscriber network judgment, load cache data return no problem;

 @Override
 public void onStart() {
    super.onStart(a); Toast.makeText(context."http is start", Toast.LENGTH_SHORT).show(a);// todo some common as show loadding and check netWork is NetworkAvailable
    // if NetworkAvailable no ! must to call onCompleted
    if(! NetworkUtil.isNetworkAvailable(context)) {
        Toast.makeText(context."No network", Toast.LENGTH_SHORT).show(a);if (isNeedCahe) {
            Toast.makeText(context."No network, intelligent read cache!", Toast.LENGTH_SHORT).show(a); IpResult ipResult =new Gson().fromJson(CaheManager.getjson(url), IpResult.class); onNext((T) ipResult); } onCompleted(); }}Copy the code

conclusion

Through this collation, RxJava and Retrofit, all pits are directly added to the line, following the introduction last time, the author’s new framework development novate is nearing the end, it is estimated that we can meet you this month, please continue to pay attention to!

Read the Retrofit 2.0 series

  • Retrofit 2.0(I) super practice, perfect support for Https transfer

  • Retrofit2.0 (ii) Perfect synchronization Cookie implementation login free

  • Retrofit 2.0 super Practices (III), easy to upload files/images

  • Retrofit 2.0 Superpowers (iv), complete download of large file breakpoints

  • Super useful RetrofitClient tool class based on Retrofit2.0 package

  • Play IOC, teach you to implement custom Retrofit framework freehand

  • Rxjava and Retrofit need to master several practical skills, caching issues and unified handling of network-free issues

  • Novate: another perfect enhancement to Retrofit2.0! (9)




Wechat official account

Tamic: www.jianshu.com/users/3bbb1…


The source code:Github.com/Tamicer/Nov…

This article has been put on record in copyright press, please visit copyright press for reprinting. 66790509