retrofit 封装,Lifecycle绑定Activty和Fragment生命周期,优雅的管理Http请求

retrofit-helper

Retrofit是很多android开发者都在使用的Http请求库!他负责网络请求接口的封装,底层实现是OkHttp,它的一个特点是包含了特别多注解,通过动态代理的方式使得开发者在使用访问网络的时候更加方便简单高效。

  • 1. Retrofit-helper扩展了那些功能

    描述 相关类和方法
    回调函数中直接处理请求结果,无需再次判断是否成功 Callback.onSuccess(Call<T> call, T response)
    请求开始和结束监听 Callback.onStart(Call<T> call)Callback.onCompleted(Call<T> call, @Nullable Throwable t)
    全局维护多个Retrofit实例 RetrofitFactory.DEFAULTRetrofitFactory.OTHERS
    统一解析异常信息 Callback.parseThrowable(Call<T> call, Throwable t)
    绑定Activity或者Fragment生命周期 LifeCall<T> bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event)
    拦截器监听下载和上传进度 ProgressInterceptorProgressListener
    单独指定某个请求的日志级别 HttpLoggingInterceptor
  • 2. 封装逻辑解析

    • 2.1 RetrofitFactory全局管理retrofit实例

      全局管理retrofit 实例,通用的做法有单利模式或者静态类成员属性的方式,并且在Application中初始化,在这里只是管理全局retrofit对象,故采用静态的成员属性即可满足需求

      DEFAULT 静态变量管理默认常用的的retrofit对象,OTHERS 管理其他多个不同配置的retrofit

      /**
       * 创建时间:2018/4/3
       * 编写人: chengxin
       * 功能描述:管理全局的Retrofit实例
       */
      public final class RetrofitFactory {
          /**
           * 缓存不同配置的retrofit集合,如不同的url ,converter等
           */
          public static final Map<String, Retrofit> OTHERS = new ConcurrentHashMap<>(2);
          /**
           * 全局的Retrofit对象
           */
          public static volatile Retrofit DEFAULT;
          /**
           * A {@code null} value is permitted
           */
          @Nullable
          public static volatile OnEventListener LISTENER;
      
          private RetrofitFactory() {
          }
      
          public static <T> T create(Class<T> service) {
              //确保多线程的情况下retrofit不为空或者被修改了
              Retrofit retrofit = DEFAULT;
              Utils.checkState(retrofit != null, "DEFAULT == null");
              return retrofit.create(service);
          }
      
          /**
           * @param name 获取 OTHERS 中指定名字的retrofit
           */
          public static <T> T create(String name, Class<T> 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 自定义Call,支持绑定生命周期。Call接口实现 enqueue(Callback<T> callback)方法 enqueue(Callback<T> callback) ,支持绑定Activity或者Fragment生命周期bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event)

      /**
       * 创建时间:2018/4/8
       * 编写人: chengxin
       * 功能描述:支持生命周期绑定的Call{@link retrofit2.Call}
       */
      public interface Call<T> extends Callable<T>, Cloneable {
      
          String TAG = Call.class.getSimpleName();
      
          boolean isExecuted();
      
          void cancel();
      
          boolean isCanceled();
      
          Call<T> clone();
      
          Request request();
      
          /**
           * 绑定生命周期
           *
           * @param provider LifecycleProvider
           * @param event    {@link Lifecycle.Event}
           * @return
           */
          LifeCall<T> bindToLifecycle(LifecycleProvider provider, Lifecycle.Event event);
      
         /**
           * default event is {@link Lifecycle.Event#ON_DESTROY}
           *
           * @param provider LifecycleProvider
           * @return LifeCall
           * @see Call#bindToLifecycle(LifecycleProvider, Lifecycle.Event)
           */
          LifeCall<T> bindUntilDestroy(LifecycleProvider provider);
      }
      
    • 2.3Callback 统一处理回调和异常

      retrofit 默认的callback只有支持成功和失败的回调,而一般我们的场景需要在请求开始和结束的地方做一些UI的展示处理,如显示加载动画等,故添加了onStartonCompleted 监听。parseThrowable方法可以处理各种请求过程中抛出的throwable,转换成统一的格式方便我们处理展示等

      
      /**
       * if {@link LifeCall#isDisposed()} return true, all methods will not call
       *
       * @param <T> Successful response body type.
       */
      @UiThread
      public interface Callback<T> {
          /**
           * @param call The {@code Call} that was started
           */
          void onStart(Call<T> call);
      
          /**
           * @param call The {@code Call} that has thrown exception
           * @param t    统一解析throwable对象转换为HttpError对象,如果throwable为{@link HttpError}
           *             <li>则为{@link retrofit2.Converter#convert(Object)}内抛出的异常</li>
           *             如果为{@link retrofit2.HttpException}
           *             <li>则为{@link Response#body()}为null的时候抛出的</li>
           */
          @NonNull
          HttpError parseThrowable(Call<T> call, Throwable t);
      
          /**
           * 过滤一次数据,如剔除List中的null等,默认可以返回t
           */
          @NonNull
          T transform(Call<T> call, T t);
      
          void onError(Call<T> call, HttpError error);
      
          void onSuccess(Call<T> call, T t);
      
          /**
           * @param t 请求失败的错误信息
           */
          void onCompleted(Call<T> call, @Nullable Throwable t);
      }
      
  • 2.4 HttpError 统一包装异常错误,由Callback parseThrowable 方法统一返回

    ​ HttpError类中有两个成员属性msg 被body,msg是保存错误的描述信息等,body可以保存异常的具体信息或者原始的json等,onError(Call<T> call, HttpError error)回调方法可以根据body的具体信息做二次处理。

    /**
     * 通用的错误信息,一般请求是失败只需要弹出一些错误信息即可,like{@link retrofit2.HttpException}
     * Created by chengxin on 2017/6/22.
     */
    public final class HttpError extends RuntimeException {
        private static final long serialVersionUID = -134024482758434333L;
        /**
         * 展示在前端的错误描述信息
         */
        public String msg;
    
        /**
         * <p>
         * 请求失败保存失败信息,for example:
         * <li>BusiModel: {code:xxx,msg:xxx} 业务错误信息</li>
         * <li>original json:  原始的json</li>
         * <li>{@link retrofit2.Response}:错误响应体->Response<?></li>
         * <li>Throwable: 抛出的异常信息</li>
         * </p>
         */
        @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;
        }
    
        /**
         * 保证和msg一致
         */
        @Override
        public String getMessage() {
            return msg;
        }
    
        @Override
        public String toString() {
            return "HttpError {msg="
                    + msg
                    + ", body="
                    + body
                    + '}';
        }
    }
    
  • 2.5 CallAdapterFactory返回Call请求适配器

    处理请求接口方法返回为Call的请求适配器工厂类

    public final class CallAdapterFactory extends CallAdapter.Factory {
        private static final String RETURN_TYPE = Call.class.getSimpleName();
    
        public static final CallAdapter.Factory INSTANCE = new CallAdapterFactory();
    
        private CallAdapterFactory() {
        }
    
        /**
         * Extract the raw class type from {@code type}. For example, the type representing
         * {@code List<? extends Runnable>} returns {@code List.class}.
         */
        public static Class<?> getRawType(Type type) {
            return CallAdapter.Factory.getRawType(type);
        }
    
        @Override
        public CallAdapter<?, ?> get(Type returnType, Annotation[] annotations, Retrofit retrofit) {
            if (getRawType(returnType) != Call.class) {
                return null;
            }
            if (!(returnType instanceof ParameterizedType)) {
                throw new IllegalArgumentException(
                        String.format("%s return type must be parameterized as %s<Foo> or %s<? extends Foo>", RETURN_TYPE, RETURN_TYPE, RETURN_TYPE));
            }
            final Type responseType = getParameterUpperBound(0, (ParameterizedType) returnType);
    
            return new CallAdapter<Object, Call<?>>() {
                @Override
                public Type responseType() {
                    return responseType;
                }
    
                @Override
                public Call<Object> adapt(retrofit2.Call<Object> call) {
                    return new RealCall<>(call);
                }
            };
        }
    }
    
  • 2.6 RealLifeCall 继承LifeCall实现Activity或Fragment生命周期绑定,自动管理生命周期

    负责生命周期的监听,在onChanged(@NonNull Lifecycle.Event event)方法中如果匹配到指定的event,标记为disposed 并取消请求,这时Callback中所有的回调函数将不会在执行,保证回调处的安全性。

    
    final class RealLifeCall<T> implements LifeCall<T> {
        private final Call<T> delegate;
        private final Lifecycle.Event event;
        private final LifecycleProvider provider;
        /**
         * LifeCall是否被释放了
         * like rxAndroid MainThreadDisposable or rxJava ObservableUnsubscribeOn, IoScheduler
         */
        private final AtomicBoolean once = new AtomicBoolean();
    
        RealLifeCall(Call<T> delegate, Lifecycle.Event event, LifecycleProvider provider) {
            this.delegate = delegate;
            this.event = event;
            this.provider = provider;
            provider.observe(this);
        }
    
        @Override
        public void enqueue(final Callback<T> callback) {
            Utils.checkNotNull(callback, "callback==null");
            delegate.enqueue(new Callback<T>() {
                @Override
                public void onStart(Call<T> call) {
                    if (!isDisposed()) {
                        callback.onStart(call);
                    }
                }
    
                @NonNull
                @Override
                public HttpError parseThrowable(Call<T> call, Throwable t) {
                    if (!isDisposed()) {
                        return callback.parseThrowable(call, t);
                    }
                    return new HttpError("Already disposed.", t);
                }
    
                @NonNull
                @Override
                public T transform(Call<T> call, T t) {
                    if (!isDisposed()) {
                        return callback.transform(call, t);
                    }
                    return t;
                }
    
                @Override
                public void onSuccess(Call<T> call, T t) {
                    if (!isDisposed()) {
                        callback.onSuccess(call, t);
                    }
                }
    
                @Override
                public void onError(Call<T> call, HttpError error) {
                    if (!isDisposed()) {
                        callback.onError(call, error);
                    }
                }
    
                @Override
                public void onCompleted(Call<T> call, @Nullable Throwable t) {
                    if (!isDisposed()) {
                        callback.onCompleted(call, t);
                        provider.removeObserver(RealLifeCall.this);
                    }
                }
            });
        }
    
        @NonNull
        @Override
        public T execute() throws Throwable {
            try {
                if (isDisposed()) {
                    throw new DisposedException("Already disposed.");
                }
                T body = delegate.execute();
                if (isDisposed()) {
                    throw new DisposedException("Already disposed.");
                }
                return body;
            } catch (Throwable t) {
                if (isDisposed() && !(t instanceof DisposedException)) {
                    throw new DisposedException("Already disposed.", t);
                }
                throw t;
            } finally {
                if (!isDisposed()) {
                    provider.removeObserver(this);
                }
            }
        }
    
        @Override
        public void onChanged(@NonNull Lifecycle.Event event) {
            if (this.event == event
                    || event == Lifecycle.Event.ON_DESTROY
                    //Activity和Fragment的生命周期是不会传入 {@code Lifecycle.Event.ON_ANY},
                    //可以手动调用此方法传入 {@code Lifecycle.Event.ON_ANY},用于区分是否为手动调  用
                    || event == Lifecycle.Event.ON_ANY) {
                if (once.compareAndSet(false, true)/*保证原子性*/) {
                    delegate.cancel();
                    RetrofitFactory.getOnEventListener().onDisposed(delegate, event);
                    provider.removeObserver(this);
                }
            }
        }
    
        @Override
        public boolean isDisposed() {
            return once.get();
        }
    }
    
  • 2.7 AndroidLifecycle观察者模式统一分发生命周期事件

    继承LifecycleObserver 接口监听当前的Activity或者Fragment的生命周期,分发生命周期事件到Observer的onChanged方法。这个类是线程安全的,保证多线程环境的正确性

    
    /**
     * 实现LifecycleObserver监听Activity和Fragment的生命周期
     * It is thread safe.
     *
     * @see android.database.Observable
     */
    public final class AndroidLifecycle implements LifecycleProvider, LifecycleObserver {
        private final Object mLock = new Object();
    
        @GuardedBy("mLock")
        private final ArrayList<Observer> mObservers = new ArrayList<>();
        /**
         * 缓存当前的Event事件
         */
        @GuardedBy("mLock")
        @Nullable
        private Lifecycle.Event mEvent;
    
        @MainThread
        public static LifecycleProvider createLifecycleProvider(LifecycleOwner owner) {
            return new AndroidLifecycle(owner);
        }
    
        private AndroidLifecycle(LifecycleOwner owner) {
            owner.getLifecycle().addObserver(this);
        }
    
        @OnLifecycleEvent(Lifecycle.Event.ON_ANY)
        void onEvent(LifecycleOwner owner, Lifecycle.Event event) {
            synchronized (mLock) {
                //保证线程的可见性
                mEvent = event;
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just march thru the list in the reverse order.
                for (int i = mObservers.size() - 1; i >= 0; i--) {
                    mObservers.get(i).onChanged(event);
                }
            }
            if (event == Lifecycle.Event.ON_DESTROY) {
                owner.getLifecycle().removeObserver(this);
            }
        }
    
        @Override
        public void observe(Observer observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized (mLock) {
                if (mObservers.contains(observer)) {
                    return;
                }
                mObservers.add(observer);
                RetrofitFactory.getOnEventListener().onObserverCountChanged(this, mObservers.size() - 1, mObservers.size());
                // since onChanged() is implemented by the app, it could do anything, including
                // removing itself from {@link mObservers} - and that could cause problems if
                // an iterator is used on the ArrayList {@link mObservers}.
                // to avoid such problems, just call onChanged() after {@code mObservers.add(observer)}
                if (mEvent != null) {
                    observer.onChanged(mEvent);
                }
            }
        }
    
        @Override
        public void removeObserver(Observer observer) {
            if (observer == null) {
                throw new IllegalArgumentException("The observer is null.");
            }
            synchronized (mLock) {
                int index = mObservers.indexOf(observer);
                if (index == -1) {
                    return;
                }
                mObservers.remove(index);
                RetrofitFactory.getOnEventListener().onObserverCountChanged(this, mObservers.size() + 1, mObservers.size());
            }
        }
    
        @Override
        public String toString() {
            return "AndroidLifecycle@" + Integer.toHexString(hashCode());
        }
    }
    
  • 2.8 ProgressInterceptor 拦截器监听下载和上传进度

    继承okhttp3.Interceptor ,构造方法中传入ProgressListener监听进度

    /**
     * 创建时间:2018/8/2
     * 编写人: chengxin
     * 功能描述:上传或下载进度监听拦截器
     */
    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();
            //判断是否有上传需求
            if (requestBody != null && requestBody.contentLength() > 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 && responseBody.contentLength() > 0) {
                Response.Builder builder = response.newBuilder();
                ResponseBody newResponseBody = new ProgressResponseBody(responseBody, mProgressListener, request);
                response = builder.body(newResponseBody).build();
            }
            return response;
        }
    }
    
  • 2.9 HttpLoggingInterceptor 可以单独指定某个请求的日志级别

    构造OkhttpClient时添加此拦截器,在请求的服务方法中添加注解

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

    @FormUrlEncoded
    @Headers("LogLevel:HEADERS")
    @POST("user/login")
    Call<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
    
  • 3.实战

    • 3.1 初始化全局Retrofit对象

      Retrofit retrofit = new Retrofit.Builder()
                      .baseUrl("http://wanandroid.com/")
                      .callFactory(new OkHttpClient.Builder()
                              .addNetworkInterceptor(httpLoggingInterceptor)
                              .build())
                      //必须添加此adapter 用于构建Call
                      .addCallAdapterFactory(CallAdapterFactory.INSTANCE)
                      //添加自定义json解析器 
                      .addConverterFactory(GsonConverterFactory.create())
                      .build();
       RetrofitFactory.DEFAULT = retrofit;
       
       //可以添加多个,如:
       RetrofitFactory.OTHERS.put("other",otherRetrofit);
       
      
    • 3.2 添加请求服务接口

      下面为登录的 post请求

      @FormUrlEncoded
      @POST("user/login")
      Call<LoginInfo> getLogin(@Field("username") String username, @Field("password") String password);
      
    • 3.3 发起请求

      是不是很简单,妈妈再也不用担心Activity销毁后资源回收导致的NullPointException等问题了

     
    
    public class MainActivity extends AppCompatActivity {
    
        LifecycleProvider provider = AndroidLifecycle.createLifecycleProvider(this);
    
        @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            final Context context = this;
    
            RetrofitFactory.create(ApiService.class)
                  .getLogin("loginName", "password")
                    //.bindUntilDestroy(provider) Activity销毁时取消请求
                  .bindToLifecycle(provider, Lifecycle.Event.ON_STOP)
                  .enqueue(new DefaultCallback<LoginInfo>() {
                      @Override
                        public void onStart(Call<LoginInfo> call) {
                          showLoading();
                        }
    
                        @Override
                        public void onError(Call<LoginInfo> call, HttpError error) {
                            Toast.makeText(context, error.msg, Toast.LENGTH_SHORT).show();
                        }
    
                        @Override
                        public void onSuccess(Call<LoginInfo> call, LoginInfo loginInfo) {
                            Toast.makeText(context, "登录成功!", Toast.LENGTH_SHORT).show();
                            //do...
                        }
    
                        @Override
                        public void onCompleted(Call<LoginInfo> call, @Nullable Throwable t){
                            hideLoading();
                        }
                    });
        }
    }
    
  • 4.注意事项

    • 4.1 构建retrofit是需要CallAdapterFactory实例,否则无法处理返回为Call的服务接口

    • 4.2 Callback的回调函数均在主线程执行,如果Call绑定了生命周期触发了cancel()方法

      UI回调方法均不会执行,如果要监听那些请求被取消了,可以设置RetrofitFactory.LISTENER属性,其为一个全局的监听器OnEventListener

  • 5.下载

    dependencies {
         implementation 'com.xcheng:retrofit-helper:1.5.5'
    }
    

​ github地址: retrofit-helper

License

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.
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,290评论 4 363
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,399评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,021评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,034评论 0 207
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,412评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,651评论 1 219
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,902评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,605评论 0 199
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,339评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,586评论 2 246
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,076评论 1 261
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,400评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,060评论 3 236
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,083评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,851评论 0 195
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,685评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,595评论 2 270

推荐阅读更多精彩内容