Reservoir 实现的简易缓存


title: Reservoir 实现的简易缓存
category: Android开发
feature_image: "https://ssevening.github.io/assets/android.png"
image: "https://ssevening.github.io/assets/android.png"


Reservoir 实现的简易缓存的业务使用场景 和 Retrofit网络框架评测

一、Reservoir 实现的简易缓存

  • 先贴地址:GitHub

使用场景

  • App开发中,希望缓存一些数据对象,比如某些产品数据,在服务端获取后,直接从缓存读取,但又不希望这个缓存太大。如果自己写的话,就要维护缓存大小,缓存数量等内容。
  • 其实图片模块的LRU DiskCache,就是本开源组件的实现,只是这里不是用在图片上,而是数据对象。

实现原理:

  • LRU缓存,JAVA对象转换为JSON字符串写入。详见 put方法:
    public static void put(final String key, final Object object) throws IOException {
        failIfNotInitialised();
        String json = sGson.toJson(object);
        cache.put(key, json);
    }
  • 异步存入实现,直接用AsyncTask了,好吧。这样也可以。
private static class PutTask extends AsyncTask {
        private final String key;
        private Exception e;
        private final ReservoirPutCallback callback;
        final Object object;

        private PutTask(String key, Object object, ReservoirPutCallback callback) {
            this.key = key;
            this.callback = callback;
            this.object = object;
            this.e = null;
        }

        @Override
        protected Void doInBackground(Void... params) {

            try {
                String json = sGson.toJson(object);
                cache.put(key, json);
            } catch (Exception e) {
                this.e = e;
            }
            return null;
        }

        @Override
        protected void onPostExecute(Void aVoid) {
            if (callback != null) {
                if (e == null) {
                    callback.onSuccess();
                } else {
                    callback.onFailure(e);
                }
            }
        }

    }
  • 我们再来看看读取:从Cache中取出来后,再通过Json转换为对象。如果未获取到数据,会有空指针抛出
    public static  T get(final String key, final Class classOfT) throws IOException {
        failIfNotInitialised();
        String json = cache.getString(key).getString();
        T value = sGson.fromJson(json, classOfT);
        if (value == null)
            throw new NullPointerException();
        return value;
    }
  • 我们再看异步读取:还是AsyncTask,这里可以针对性的去优化,增加线程池。
    /**
     * AsyncTask to perform get operation in a background thread.
     */
    private static class GetTask extends AsyncTask {
        private final String key;
        private final ReservoirGetCallback callback;
        private final Class classOfT;
        private final Type typeOfT;
        private Exception e;

        private GetTask(String key, Class classOfT, ReservoirGetCallback callback) {
            this.key = key;
            this.callback = callback;
            this.classOfT = classOfT;
            this.typeOfT = null;
            this.e = null;
        }

        private GetTask(String key, Type typeOfT, ReservoirGetCallback callback) {
            this.key = key;
            this.callback = callback;
            this.classOfT = null;
            this.typeOfT = typeOfT;
            this.e = null;
        }

        @Override
        protected T doInBackground(Void... params) {
            try {
                String json = cache.getString(key).getString();
                T value;
                if (classOfT != null) {
                    value = sGson.fromJson(json, classOfT);
                } else {
                    value = sGson.fromJson(json, typeOfT);
                }
                if (value == null) {
                    throw new NullPointerException();
                }
                return value;
            } catch (Exception e) {
                this.e = e;
                return null;
            }
        }

        @Override
        protected void onPostExecute(T object) {
            if (callback != null) {
                if (e == null) {
                    callback.onSuccess(object);
                } else {
                    callback.onFailure(e);
                }
            }
        }

    }

和SP的区别

  • SP是持久化的,App不删除,不卸载,就一直存在,但Reservoir的缓存,达到初始化容量就会清除掉,近期未使用的数据。
  • 另外一种缓存的实现思路:基于时间的DB,存取数据时,把过期时间同步存入,然后获取时,比较时间有效性,决定是否用缓存的值。

二、客户端架构一些参考文章

  • 有赞的:https://youzanmobile.github.io/2017/04/14/youzan-app-modularization/
  • 大众点评:http://jiajixin.cn/2016/09/28/android_modularization/

三、Retrofit网络框架评测

参考的文档:点击直达

使用方式我就不多介绍了,大家可以看 官网 的介绍。反正就是定义接口,然后就可以直接调用了。

Retrofit retrofit = new Retrofit.Builder()
    .baseUrl("https://api.github.com")
    .addConverterFactory(GsonConverterFactory.create())
    .build();

GitHubService service = retrofit.create(GitHubService.class);
// 这里就可以直接发送网络请求获取数据了
service.getUserData();

先来说一下优点

  • 易于开发,接入简单,开发成本低。
  • 支持动态参数,动态路径,比如api中有动态的参数的情况。可以在路径中替换。
  • RxJAVA无缝支持,rx火了,他也跟着火了。
  • JSON to POJO 是在IO线程中执行的。而volley是在主线程中完成的。这点性能上要好一些。

下面来爆菊:

  • 上面代码中,必须添加 baseUrl,这就是他的致命问题,比如我们默认访问 hangzhou.api.com 但当杭州出现问题的时候,我们要切换到 shanghai.api.com,这种情况下,baseUrl就显得有点尴尬了。当然,你说我们可以解析API,然后动态构建Retrofit,那下面我再举例子。
  • 对于那种API提供是多方提供的,如果有一个模块的展示数据源,今天是 hot.api.com/product.json 明天是 cold.api.com/p.json 这种情况,那也就费了。
  • 或者还有一种解决方案:就是定义一个通用的Service,即如下代码的情况。但这就一点也不符合Retrofit的设计了。
// parse url get base url and url path: http://api.com/aaa.htm
String baseUrl = "http://api.com";
String path = "aaa.htm";
Retrofit retrofit = new Retrofit.Builder()
    .baseUrl(baseUrl)
    .addConverterFactory(GsonConverterFactory.create())
    .build();
    
Service的代码实现如下:
    @GET("{path}")
    public Observable getApiData(@Path("path") String path);    

调用的时候,如下面方法的调用
String json = service.getApiData(path);
// 下面再把json解析成pojo.好吧。我只能帮你到这里了。


所以综上所述,对于集中式管理API的App来说,使用Retrofit是个不错的选择,特别是结合RXJAVA,但当App越来越大的时候,特别是涉及到容灾,域名切换,甚至多机房的情况下,使用起来就费了。

  • 再说一下实现原理:先说入口:GitHubService service = retrofit.create(GitHubService.class); Create方法为入口。
public  T create(final Class service) {
    // 传递过来的类进行校验,看看不是接口类,是不是没有接口定义之类的。
    Utils.validateServiceInterface(service);
    if (validateEagerly) {
      eagerlyValidateMethods(service);
    }
    // 这里就是动态代理了
    return (T) Proxy.newProxyInstance(service.getClassLoader(), new Class[] { service },
        new InvocationHandler() {
          // 看看是什么操作平台,如Android JAVA8之类的,根据平台最终实现的方案不同。我们这里直接跳到最终的 loadMethodHandler方法
          private final Platform platform = Platform.get();

          @Override public Object invoke(Object proxy, Method method, Object... args)
              throws Throwable {
            // If the method is a method from Object then defer to normal invocation.
            if (method.getDeclaringClass() == Object.class) {
              return method.invoke(this, args);
            }
            if (platform.isDefaultMethod(method)) {
              return platform.invokeDefaultMethod(method, service, proxy, args);
            }
            return loadMethodHandler(method).invoke(args);
          }
        });
  }

下面的方法,我们再看MethodHandler.create(this, method)的方法,这里主要就是先从缓存中取一下没有这个实例,如果有,直接用,如果没有,就创建,所以第二次调用会快,第一次调用会慢一丢丢。上面的代码中,最后就是搞出一个MethodHandler来后,再调用相应的invoke方法。

  MethodHandler loadMethodHandler(Method method) {
    MethodHandler handler;
    synchronized (methodHandlerCache) {
      // 这里是用来缓存MethodHandler的。
      handler = methodHandlerCache.get(method);
      if (handler == null) {
        handler = MethodHandler.create(this, method);
        methodHandlerCache.put(method, handler);
      }
    }
    return handler;
  }

下面的代码中,封装了请求工程,以前数据返回的转换适配器。我们直接找MothodHandler.invoke方法的实现类去。

  static MethodHandler create(Retrofit retrofit, Method method) {
    CallAdapter callAdapter = createCallAdapter(method, retrofit);
    Type responseType = callAdapter.responseType();
    if (responseType == Response.class || responseType == okhttp3.Response.class) {
      throw Utils.methodError(method, "'"
          + Types.getRawType(responseType).getName()
          + "' is not a valid response body type. Did you mean ResponseBody?");
    }
    Converter responseConverter =
        createResponseConverter(method, retrofit, responseType);
    RequestFactory requestFactory = RequestFactoryParser.parse(method, responseType, retrofit);
    return new MethodHandler(retrofit.callFactory(), requestFactory, callAdapter,
        responseConverter);
  }

下面的代码就是invoke的实现类,把所有builder过程中的参数都带了进去。发送网络请求的真实实现马上要来了。继续看:callAdapter.adapt的实现类

  Object invoke(Object... args) {
    return callAdapter.adapt(
        new OkHttpCall<>(callFactory, requestFactory, args, responseConverter));
  }

我们看到,执行的是RxJAVA的代码,这里的核心是被观察者的生成。我们下一步去看:new CallOnSubscribe的实现。


    @Override public  Observable adapt(Call call) {
      return Observable.create(new CallOnSubscribe<>(call)) //
          .flatMap(new Func1, Observable>() {
            @Override public Observable call(Response response) {
              if (response.isSuccess()) {
                return Observable.just(response.body());
              }
              return Observable.error(new HttpException(response));
            }
          });
    }
  }

然后就有下下面的代码, 在call方法中,call.execute() 最终发送了网络请求,看一下他的具体实现。在一个代码段。

  static final class CallOnSubscribe implements Observable.OnSubscribe> {
    private final Call originalCall;

    CallOnSubscribe(Call originalCall) {
      this.originalCall = originalCall;
    }

    @Override public void call(final Subscriber> subscriber) {
      // Since Call is a one-shot type, clone it for each new subscriber.
      final Call call = originalCall.clone();

      // Attempt to cancel the call if it is still in-flight on unsubscription.
      subscriber.add(Subscriptions.create(new Action0() {
        @Override public void call() {
          call.cancel();
        }
      }));

      try {
        Response response = call.execute();
        if (!subscriber.isUnsubscribed()) {
          subscriber.onNext(response);
        }
      } catch (Throwable t) {
        Exceptions.throwIfFatal(t);
        if (!subscriber.isUnsubscribed()) {
          subscriber.onError(t);
        }
        return;
      }

      if (!subscriber.isUnsubscribed()) {
        subscriber.onCompleted();
      }
    }
  }

实现类okhttp3的代码,发送了网络请求,然后并且解析返回,然后转换为我们要返回的POJO对象,至此就完成了一次网络交互。


  @Override public Response execute() throws IOException {
    okhttp3.Call call;

    synchronized (this) {
      if (executed) throw new IllegalStateException("Already executed.");
      executed = true;

      if (creationFailure != null) {
        if (creationFailure instanceof IOException) {
          throw (IOException) creationFailure;
        } else {
          throw (RuntimeException) creationFailure;
        }
      }

      call = rawCall;
      if (call == null) {
        try {
          call = rawCall = createRawCall();
        } catch (IOException | RuntimeException e) {
          creationFailure = e;
          throw e;
        }
      }
    }

    if (canceled) {
      call.cancel();
    }

    return parseResponse(call.execute());
  }

欢迎关注作者微信公众号,及时获得作者更新:

微信公众号
微信公众号

另外还建立了小密圈:圈主 和 嘉宾 都就职于 阿里巴巴 的顶尖开发者,开发的app被Google 编辑推荐,对性能,架构,图片,MD设计都有研究和深入,欢迎大家加入,提升自己,一起进步,互相帮助交流!

微信公众号
微信公众号

你可能感兴趣的:(Reservoir 实现的简易缓存)