retrofit-helper concise encapsulation retrofit, elegant cancel request

Keywords: Retrofit Android JSON network

retrofit-helper

Retrofit is the Http request library that many android developers are using!He is responsible for the encapsulation of the network request interface. The underlying implementation is OkHttp. One of its features is that it contains extra annotations to simplify your code volume. CallAdapter.Factory and Converter.Factory can flexibly extend your request.We still need to encapsulate a layer to make it easier for us to use. The purpose of retrofit-helper is to simplify your request again.

1. Retrofit-helper extends those capabilities

2. Encapsulation Logic Resolution

2.1 RetrofitFactory globally manages retrofit instances

DEFAULT static variable manages retrofit objects commonly used by default, OTHERS manages retrofit for many other different configurations

/**
 * Creation time: 2018/4/3
 * Writer: Chegxin
 * Functional description: Manage global instances of R ofit
 */
public final class RetrofitFactory {
    /**
     * Cache retrofit collections of different configurations, such as different urls, converters, and so on
     */
    public static final Map<String, Retrofit> OTHERS = new ConcurrentHashMap<>(2);
    /**
     * Global etrofit objects
     */
    public static volatile Retrofit DEFAULT;
private RetrofitFactory() {
}

public static &lt;T&gt; T create(Class&lt;T&gt; service) {
    //Make sure retrofit is not empty or modified with multiple threads
    Retrofit retrofit = DEFAULT;
    Utils.checkState(retrofit != null, "DEFAULT == null");
    return retrofit.create(service);
}

/**
 * @param name Gets retrofit of the specified name in OTHERS
 */
public static &lt;T&gt; T create(String name, Class&lt;T&gt; service) {
    Utils.checkNotNull(name, "name == null");
    Retrofit retrofit = OTHERS.get(name);
    Utils.checkState(retrofit != null,
            String.format("retrofit named with '%s' was not found , have you put it in OTHERS ?", name));
    return retrofit.create(service);
}
}
2.2 Call2 interface inherits retrofit.Call overloaded enqueue (Callback<T> callback) method

Enqueue (@Nullable Object tag, Callback2<T> callback2) method incoming request tags this request, tag tags are needed to cancel the request

/**
 * Creation time: 2018/4/8
 * Writer: Chegxin
 * Function description: Add overloaded method {@link Call2#enqueue(Object, Callback2)} method
 */
public interface Call2<T> extends retrofit2.Call<T> {
    /**
     * @param tag       Requested tag, used to cancel the request use
     * @param callback2 Callback requested
     */
    void enqueue(@Nullable Object tag, Callback2<T> callback2);
@Override
Call2&lt;T&gt; clone();
}
2.3 Callback2 Unified Processing Callbacks

Request Start, Success Processing, Failure Processing, Successful Callback, Failure Callback and Request End are handled uniformly here. Each method can be overridden according to different business. For example, parseResponse method can be overridden to make different prompt descriptions according to improper HTTP codes or

Override parseThrowable method to handle various Throwable

@UiThread
public abstract class Callback2<T> {
public abstract void onStart(Call2&lt;T&gt; call2);

@NonNull
public Result&lt;T&gt; parseResponse(Call2&lt;T&gt; call2, Response&lt;T&gt; response) {
    T body = response.body();
    if (response.isSuccessful()) {
        if (body != null) {
            return Result.success(body);
        } else {
            return Result.error(new HttpError("No data at this time", response));
        }
    }

    final String msg;
    switch (response.code()) {
        case 400:
            msg = "Parameter error";
            break;
        case 401:
            msg = "Identity not authorized";
            break;
        case 403:
            msg = "No access";
            break;
        case 404:
            msg = "Address not found";
            break;
        default:
            msg = "Service Exception";
    }
    return Result.error(new HttpError(msg, response));
}

/**
 * Unified Resolution Throwable Object to HttpError Object.If it is HttpError,
 * Is an exception thrown in {@link retrofit2.Converter#convert(Object)}
 *
 * @param call2 call
 * @param t     Throwable
 * @return HttpError result
 */
@NonNull
public HttpError parseThrowable(Call2&lt;T&gt; call2, Throwable t) {
    if (t instanceof HttpError) {
        //Direct throw exception reception for convert function
        return (HttpError) t;
    } else if (t instanceof UnknownHostException) {
        return new HttpError("Network exception", t);
    } else if (t instanceof ConnectException) {
        return new HttpError("Network exception", t);
    } else if (t instanceof SocketException) {
        return new HttpError("Service Exception", t);
    } else if (t instanceof SocketTimeoutException) {
        return new HttpError("Response timeout", t);
    } else {
        return new HttpError("request was aborted", t);
    }
}

public abstract void onError(Call2&lt;T&gt; call2, HttpError error);

public abstract void onSuccess(Call2&lt;T&gt; call2, T response);


/**
 * @param t        Error message for request failure
 * @param canceled Has the request been cancelled
 */
public abstract void onCompleted(Call2&lt;T&gt; call2, @Nullable Throwable t, boolean canceled);
}
2.4 HttpError Unified Handling Exception Errors

Two member attributes of the HttpError class, msg, are body. MSG is the description of the error. body can store the specific information of the exception or the original json, etc. onError (Call2<T> Call2, HttpError error) callback method can do secondary processing based on the specific information of the body.

/**
 * Common error message. A common request is a failure. It only needs to pop up some error message, like{@link retrofit2.HttpException}
 * Created by chengxin on 2017/6/22.
 */
public final class HttpError extends RuntimeException {
    private static final long serialVersionUID = -134024482758434333L;
    /**
     * Error description information displayed on the front end
     */
    public String msg;
/**
 * &lt;p&gt;
 * Failed request to save failure information, for example:
 * &lt;li&gt;BusiModel: {code:xxx,msg:xxx} Business error information </li>
 * &lt;li&gt;original json:  Original json< /li>
 * &lt;li&gt;{@link retrofit2.Response}:Error Response Body - > Response<?> < /li>
 * &lt;li&gt;Throwable: Exception information thrown </li>
 * &lt;/p&gt;
 */
@Nullable
public final transient Object body;

public HttpError(String msg) {
    this(msg, null);
}

public HttpError(String msg, @Nullable Object body) {
    super(msg);
    if (body instanceof Throwable) {
        initCause((Throwable) body);
    }
    //FastPrintWriter#print(String str)
    this.msg = msg != null ? msg : "null";
    this.body = body;
}

/**
 * Be consistent with msg
 */
@Override
public String getMessage() {
    return msg;
}

@Override
public String toString() {
    return "HttpError {msg="
            + msg
            + ", body="
            + body
            + '}';
}
}
2.5 ExecutorCallAdapterFactory returns Call2 request adapter

Request adapter factory class that handles request interface methods returning to Call2

public final class ExecutorCallAdapterFactory extends CallAdapter.Factory {
public static final CallAdapter.Factory INSTANCE = new ExecutorCallAdapterFactory();

private ExecutorCallAdapterFactory() {
}

/**
 * Extract the raw class type from {@code type}. For example, the type representing
 * {@code List&lt;? extends Runnable&gt;} returns {@code List.class}.
 */
public static Class&lt;?&gt; getRawType(Type type) {
    return CallAdapter.Factory.getRawType(type);
}

@Override
public CallAdapter&lt;?, ?&gt; get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
    if (getRawType(returnType) != Call2.class) {
        return null;
    }
    if (!(returnType instanceof ParameterizedType)) {
        throw new IllegalArgumentException(
                "Call return type must be parameterized as Call2&lt;Foo&gt; or Call2&lt;? extends Foo&gt;");
    }
    final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);

    final Executor callbackExecutor = retrofit.callbackExecutor();
    if (callbackExecutor == null) throw new AssertionError();

    return new CallAdapter&lt;Object, Call&lt;?&gt;&gt;() {
        @Override
        public Type responseType() {
            return responseType;
        }

        @Override
        public Call&lt;Object&gt; adapt(Call&lt;Object&gt; call) {
            return new ExecutorCallbackCall2&lt;&gt;(callbackExecutor, call);
        }
    };
}
}
2.6 ExecutorCallbackCall2 inherits Call2 proxy OkHttpCall to handle UI callbacks

Decorator mode proxy all methods of OkHttpCall, and thread dispatch handles callback methods of Callback2 executed on the main thread

final class ExecutorCallbackCall2<T> implements Call2<T> {
    private final Executor callbackExecutor;
    private final Call<T> delegate;
/**
 * The executor used for {@link Callback} methods on a {@link Call}. This may be {@code null},
 * in which case callbacks should be made synchronously on the background thread.
 */
ExecutorCallbackCall2(Executor callbackExecutor, Call&lt;T&gt; delegate) {
    this.callbackExecutor = callbackExecutor;
    this.delegate = delegate;
}

@Override
public void enqueue(final Callback&lt;T&gt; callback) {
    throw new UnsupportedOperationException("please call enqueue(Object tag, Callback2&lt;T&gt; callback2)");
}

@Override
public void enqueue(@Nullable Object tag, final Callback2&lt;T&gt; callback2) {
    Utils.checkNotNull(callback2, "callback2==null");
    CallManager.getInstance().add(this, tag != null ? tag : "NO_TAG");
    callbackExecutor.execute(new Runnable() {
        @Override
        public void run() {
            if (!isCanceled()) {
                callback2.onStart(ExecutorCallbackCall2.this);
            }
        }
    });

    delegate.enqueue(new Callback&lt;T&gt;() {
        @Override
        public void onResponse(Call&lt;T&gt; call, final Response&lt;T&gt; response) {
            callbackExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    callResult(callback2, response, null);
                }
            });
        }

        @Override
        public void onFailure(Call&lt;T&gt; call, final Throwable t) {
            callbackExecutor.execute(new Runnable() {
                @Override
                public void run() {
                    callResult(callback2, null, t);
                }
            });
        }
    });
}

@UiThread
private void callResult(Callback2&lt;T&gt; callback2, @Nullable Response&lt;T&gt; response, @Nullable Throwable failureThrowable) {
    try {
        if (!isCanceled()) {
            //1. Get parsing results
            Result&lt;T&gt; result;
            if (response != null) {
                result = callback2.parseResponse(this, response);
                Utils.checkNotNull(result, "result==null");
            } else {
                Utils.checkNotNull(failureThrowable, "failureThrowable==null");
                HttpError error = callback2.parseThrowable(this, failureThrowable);
                result = Result.error(error);
            }
            //2. Callback success and failure
            if (result.isSuccess()) {
                callback2.onSuccess(this, result.body());
            } else {
                callback2.onError(this, result.error());
            }
        }
        callback2.onCompleted(this, failureThrowable, isCanceled());
    } finally {
        CallManager.getInstance().remove(this);
    }
}

@Override
public boolean isExecuted() {
    return delegate.isExecuted();
}

@Override
public Response&lt;T&gt; execute() throws IOException {
    return delegate.execute();
}

@Override
public void cancel() {
    delegate.cancel();
}

@Override
public boolean isCanceled() {
    return delegate.isCanceled();
}

@SuppressWarnings("CloneDoesntCallSuperClone") // Performing deep clone.
@Override
public Call2&lt;T&gt; clone() {
    return new ExecutorCallbackCall2&lt;&gt;(callbackExecutor, delegate.clone());
}

@Override
public Request request() {
    return delegate.request();
}
}
2.7 CallManager Unified Management Request, Cancel Request

Save all requests globally, add and delete requests, and cancel certain tag matching requests.CallManager.getInstance().cancel (yourTag) can be called in the destroy method of an Activity or Fragment

/**
 * Creation time: 2018/5/31
 * Writer: Chegxin
 * Function description: Manage Call request management globally, just like {@link okhttp3.Dispatcher}
 */
public final class CallManager implements ActionManager<Call<?>> {
    @GuardedBy("this")
    private final List<CallTag> callTags = new ArrayList<>(4);
    private volatile static CallManager instance;
private CallManager() {
}

public static CallManager getInstance() {
    if (instance == null) {
        synchronized (CallManager.class) {
            if (instance == null) {
                instance = new CallManager();
            }
        }
    }
    return instance;
}

@Override
public synchronized void add(Call&lt;?&gt; call, Object tag) {
    Utils.checkState(!contains(call), "Call&lt;?&gt;  " + call + " is already added.");
    callTags.add(new CallTag(call, tag));
}

/**
 * Remove when call ends
 *
 * @param call Retrofit Call
 */
@Override
public synchronized void remove(Call&lt;?&gt; call) {
    if (callTags.isEmpty())
        return;
    for (int index = 0; index &lt; callTags.size(); index++) {
        if (call == callTags.get(index).call) {
            //like okhttp3.Headers#removeAll(String name)
            //The remove(int index) method is superior to remove(Object o) and does not need to be traversed again
            callTags.remove(index);
            break;
        }
    }
}

/**
 * Cancel and remove the call for the corresponding tag to ensure that the call is no longer referenced after it is cancelled.
 * Double insurance with {@link #remove(Call)} method
 *
 * @param tag call Corresponding tag
 */
@Override
public synchronized void cancel(final @Nullable Object tag) {
    if (callTags.isEmpty())
        return;
    if (tag != null) {
        for (int index = 0; index &lt; callTags.size(); index++) {
            CallTag callTag = callTags.get(index);
            if (callTag.tag.equals(tag)) {
                callTag.call.cancel();
                callTags.remove(index);
                index--;
            }
        }
    } else {
        for (CallTag callTag : callTags) {
            callTag.call.cancel();
        }
        callTags.clear();
    }
}

@Override
public synchronized boolean contains(Call&lt;?&gt; call) {
    for (CallTag callTag : callTags) {
        if (call == callTag.call) {
            return true;
        }
    }
    return false;
}

/**
 * Save call and tag
 */
final static class CallTag {
    private final Call&lt;?&gt; call;
    private final Object tag;

    CallTag(Call&lt;?&gt; call, Object tag) {
        Utils.checkNotNull(call == null, "call==null");
        Utils.checkNotNull(tag == null, "tag==null");
        this.call = call;
        this.tag = tag;
    }
}
}
2.8 ProgressInterceptor interceptor listens for download and upload progress

Inherit okhttp3.Interceptor, pass ProgressListener into the construction method to monitor progress

/**
 * Creation time: 2018/8/2
 * Writer: Chegxin
 * Capability description: Upload or download progress monitor interceptor
 */
public class ProgressInterceptor implements Interceptor {
private final ProgressListener mProgressListener;

public ProgressInterceptor(ProgressListener progressListener) {
    Utils.checkNotNull(progressListener, "progressListener==null");
    this.mProgressListener = progressListener;
}

@Override
public Response intercept(Chain chain) throws IOException {
    Request request = chain.request();
    RequestBody requestBody = request.body();
    //Determine if there is an upload requirement
    if (requestBody != null &amp;&amp; requestBody.contentLength() &gt; 0) {
        Request.Builder builder = request.newBuilder();
        RequestBody newRequestBody = new ProgressRequestBody(requestBody, mProgressListener, request);
        request = builder.method(request.method(), newRequestBody).build();
    }

    Response response = chain.proceed(request);
    ResponseBody responseBody = response.body();
    if (responseBody != null &amp;&amp; responseBody.contentLength() &gt; 0) {
        Response.Builder builder = response.newBuilder();
        ResponseBody newResponseBody = new ProgressResponseBody(responseBody, mProgressListener, request);
        response = builder.body(newResponseBody).build();
    }
    return response;
}
}

2.9 HttpLoggingInterceptor allows you to specify the log level for a request individually

Add this interceptor when constructing the OkhttpClient, add comments to the requested service method

@Headers("LogLevel:NONE") or @Headers("LogLevel:BASIC") or @Headers("LogLevel:HEADERS") or @Headers("LogLevel:BODY")

@FormUrlEncoded
@Headers("LogLevel:HEADERS")
@POST("user/login")
Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);

3. Actual Warfare

3.1 Initialize the global Retrofit object
Retrofit retrofit = new Retrofit.Builder()
                .baseUrl("http://wanandroid.com/")
                .callFactory(new OkHttpClient.Builder()
                        .addNetworkInterceptor(httpLoggingInterceptor)
                        .build())
                //This adapter must be added to build processing callbacks
                .addCallAdapterFactory(ExecutorCallAdapterFactory.INSTANCE)
                //Add a custom json parser 
                .addConverterFactory(GsonConverterFactory.create())
                .build();
 RetrofitFactory.DEFAULT = retrofit;

 //You can add multiple, such as:
 RetrofitFactory.OTHERS.put("other",otherRetrofit);
3.2 Add Request Service Interface

The following are post requests for login

@FormUrlEncoded
@POST("user/login")
Call2<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
3.3 Add ILoadingView to start and end animations

Activity or Fragment can inherit the ILoadingView interface to start and end animations

public interface ILoadingView {
    /**
     * Show Loading
     */
    void showLoading();
/**
 * Hide Loading
 */
void hideLoading();
}
3.4 Add AnimCallback to handle animations

Override parseThrowable here to handle some unhandled exceptions in Callback2

public abstract class AnimCallback<T> extends Callback2<T> {
    private ILoadingView mLoadingView;
public AnimCallback(@Nullable ILoadingView loadingView) {
    this.mLoadingView = loadingView;
}

@Override
public void onStart(Call2&lt;T&gt; call2) {
    if (mLoadingView != null)
        mLoadingView.showLoading();
}

@Override
public void onCompleted(Call2&lt;T&gt; call2, @Nullable Throwable t, boolean canceled) {
    if (canceled)
        return;
    if (mLoadingView != null)
        mLoadingView.hideLoading();
}

@NonNull
@Override
public HttpError parseThrowable(Call2&lt;T&gt; call2, Throwable t) {
    HttpError filterError;
    if (t instanceof JsonSyntaxException) {
        filterError = new HttpError("Resolve Exception", t);
    } else {
        filterError = super.parseThrowable(call2, t);
    }
    return filterError;
}
}
3.5 Initiate Request
RetrofitFactory.create(ApiService.class)
        .getLogin("xxxxx", "123456")
        .enqueue(hashCode(), new AnimCallback<LoginInfo>(this) {
            @Override
            public void onError(Call2<LoginInfo> call2, HttpError error) {
                //Processing Failure
            }
        @Override
        public void onSuccess(Call2&lt;LoginInfo&gt; call2, LoginInfo response) {
           //Processing success such as saving login information
        }
    });
 //Cancel an open request in onDestor
   @Override
    protected void onDestroy() {
        super.onDestroy();
        //hashCode() guarantees uniqueness and cancels all requests made by the current page as long as
        // enqueue(tag, callback2) passes in the corresponding hashCode().
        CallManager.getInstance().cancel(hashCode());
    }

4. Notes

4.1 Building retrofit requires an ExecutorCallAdapterFactory instance, otherwise the service interface returned as Call2 cannot be handled
The callback functions of 4.2 Callback 2 are all executed on the main thread. If the Call2.cancel() method is called, no callback method will execute except onCompleted() method.

5. Download

implementation "com.xcheng:retrofit-helper:1.0.0"\

retrofit-helper GitHub address:
https://github.com/xchengDroid/retrofit-helper

Copyright 2019 xchengDroid

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

Last

For Android programmers, I've sorted out some information for you, including advanced UI, performance optimization, architect courses, NDK, ReactNative+Weex Wechat applets, Flutter and other advanced Android practice techniques. I hope to help you, save you time searching for information on the web, and also share dynamic information with you.Edge friends study together!

  • Outline of Android Frontier Technologies

  • Complete Systematic Advanced Architecture Video

Data collection: Tian Zan+Jia Qun for free Android IOC Architecture Design

Additive group Android IOC Architecture Design Receive and obtain prior Android Advanced Architecture data, source code, notes, videos.Advanced UI, performance optimization, architect courses, advanced Android practices for all aspects of ReactNative+Weex, and technical bulls in the group discuss and communicate to solve problems.

Posted by greeneel on Sat, 18 May 2019 19:25:36 -0700