Android中Rxjava实现三级缓存的两种方式

本文正如标题所说的用rxjava实现数据的三级缓存分别为内存,磁盘,网络,刚好最近在看Android源码设计模式解析与实战(受里面的ImageLoader的设计启发)。

我把代码放到了我的hot项目中,github地址

源码下载地址:Rxjava_jb51.rar

1.使用concat()和first()的操作符。

2.使用BehaviorSubject。

先说BehaviorSubject的实现方法,废话不多说直接上代码,

/**
 * Created by wukewei on 16/6/20.
 */
public class BehaviorSubjectFragment extends BaseFragment {

  public static BehaviorSubjectFragment newInstance() {
    BehaviorSubjectFragment fragment = new BehaviorSubjectFragment();
    return fragment;
  }

  String diskData = null;
  String networkData = "从服务器获取的数据";
  BehaviorSubject<String> cache;

  View mView;

  @Nullable
  @Override
  public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
    mView = inflater.inflate(R.layout.fragment_content, container, false);
    init();
    return mView;
  }

  private void init() {
    mView.findViewById(R.id.get).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        subscriptionData(new Observer<String>() {
          @Override
          public void onCompleted() {

          }

          @Override
          public void onError(Throwable e) {

          }

          @Override
          public void onNext(String s) {
            Log.d("onNext", s);
          }
        });
      }
    });

    mView.findViewById(R.id.memory).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        BehaviorSubjectFragment.this.cache = null;
      }
    });

    mView.findViewById(R.id.memory_disk).setOnClickListener(new View.OnClickListener() {
      @Override
      public void onClick(View v) {
        BehaviorSubjectFragment.this.cache = null;
        BehaviorSubjectFragment.this.diskData = null;
      }
    });

  }

  private void loadNewWork() {
    Observable<String> o = Observable.just(networkData)
        .doOnNext(new Action1<String>() {
          @Override
          public void call(String s) {
            BehaviorSubjectFragment.this.diskData = s;
            Log.d("写入磁盘", "写入磁盘");
          }
        });
    o.subscribe(new Action1<String>() {
      @Override
      public void call(String s) {
        cache.onNext(s);
      }
    }, new Action1<Throwable>() {
      @Override
      public void call(Throwable throwable) {

      }
    });
  }

  private Subscription subscriptionData(@NonNull Observer<String> observer) {
    if (cache == null) {
      cache = BehaviorSubject.create();
      Observable.create(new Observable.OnSubscribe<String>() {
        @Override
        public void call(Subscriber<? super String> subscriber) {
          String data = diskData;
          if (data == null) {
            Log.d("来自网络", "来自网络");
            loadNewWork();
          } else {
            Log.d("来自磁盘", "来自磁盘");
            subscriber.onNext(data);
          }
        }
      })
          .subscribeOn(Schedulers.io())
          .subscribe(cache);

    } else {
      Log.d("来自内存", "来自内存");
    }

    return cache.observeOn(AndroidSchedulers.mainThread()).subscribe(observer);
  }

}

其中最主要的是subscriptionData()这个方法,就是先判断 cache是否存在要是存在的话就返回内存中数据,再去判断磁盘数据是否存在,如果存在就返回,要是前面两种都不存在的时候,再去网络中获取数据。还有最重要的是当你从网络获取数据的时候要记得保存在内存中和保存在磁盘中,在磁盘获取数据的时候把它赋值给内存。

接下来就说说用concat()和first()的操作符来实现,这是我在看Android源码设计模式解析与实战,作者在第一章的时候就介绍ImageLoader的设计。

在内存中存储的方式LruCache来实现的,磁盘存储的方式就是序列化存储。

1.定义一个接口:

/**
 * Created by wukewei on 16/6/19.
 */
public interface ICache {
  <T> Observable<T> get(String key, Class<T> cls);

  <T> void put(String key, T t);
}

2.内存存储的实现

/**
 * Created by wukewei on 16/6/19.
 */
public class MemoryCache implements ICache{

  private LruCache<String, String> mCache;

  public MemoryCache() {
    final int maxMemory = (int) Runtime.getRuntime().maxMemory();
    final int cacheSize = maxMemory / 8;
    mCache = new LruCache<String, String>(cacheSize) {
      @Override
      protected int sizeOf(String key, String value) {
        try {
          return value.getBytes("UTF-8").length;
        } catch (UnsupportedEncodingException e) {
          e.printStackTrace();
          return value.getBytes().length;
        }
      }
    };
  }

  @Override
  public <T> Observable<T> get(final String key, final Class<T> cls) {
    return Observable.create(new Observable.OnSubscribe<T>() {
      @Override
      public void call(Subscriber<? super T> subscriber) {

        String result = mCache.get(key);

        if (subscriber.isUnsubscribed()) {
          return;
        }

        if (TextUtils.isEmpty(result)) {
          subscriber.onNext(null);
        } else {
          T t = new Gson().fromJson(result, cls);
          subscriber.onNext(t);
        }

        subscriber.onCompleted();
      }
    });
  }

  @Override
  public <T> void put(String key, T t) {
    if (null != t) {
      mCache.put(key, new Gson().toJson(t));
    }
  }

  public void clearMemory(String key) {
    mCache.remove(key);
  }
}

3.磁盘存储的实现

/**
 * Created by wukewei on 16/6/19.
 */
public class DiskCache implements ICache{

  private static final String NAME = ".db";
  public static long OTHER_CACHE_TIME = 10 * 60 * 1000;
  public static long WIFI_CACHE_TIME = 30 * 60 * 1000;
  File fileDir;
  public DiskCache() {
    fileDir = CacheLoader.getApplication().getCacheDir();
  }

  @Override
  public <T> Observable<T> get(final String key, final Class<T> cls) {
    return Observable.create(new Observable.OnSubscribe<T>() {
      @Override
      public void call(Subscriber<? super T> subscriber) {

        T t = (T) getDiskData1(key + NAME);

        if (subscriber.isUnsubscribed()) {
          return;
        }

        if (t == null) {
          subscriber.onNext(null);
        } else {
          subscriber.onNext(t);
        }

        subscriber.onCompleted();
      }
    })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread());

  }

  @Override
  public <T> void put(final String key, final T t) {
    Observable.create(new Observable.OnSubscribe<T>() {
      @Override
      public void call(Subscriber<? super T> subscriber) {

        boolean isSuccess = isSave(key + NAME, t);

        if (!subscriber.isUnsubscribed() && isSuccess) {

          subscriber.onNext(t);
          subscriber.onCompleted();
        }
      }
    })
        .subscribeOn(Schedulers.io())
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe();
  }

  /**
   * 保存数据
   */
  private <T> boolean isSave(String fileName, T t) {
    File file = new File(fileDir, fileName);

    ObjectOutputStream objectOut = null;
    boolean isSuccess = false;
    try {
      FileOutputStream out = new FileOutputStream(file);
          objectOut = new ObjectOutputStream(out);
      objectOut.writeObject(t);
      objectOut.flush();
      isSuccess=true;
    } catch (IOException e) {
      Log.e("写入缓存错误",e.getMessage());
    } catch (Exception e) {
      Log.e("写入缓存错误",e.getMessage());
    } finally {
      closeSilently(objectOut);
    }
    return isSuccess;
  }

  /**
   * 获取保存的数据
   */
  private Object getDiskData1(String fileName) {
    File file = new File(fileDir, fileName);

    if (isCacheDataFailure(file)) {
      return null;
    }

    if (!file.exists()) {
      return null;
    }
    Object o = null;
    ObjectInputStream read = null;
    try {
      read = new ObjectInputStream(new FileInputStream(file));
      o = read.readObject();
    } catch (StreamCorruptedException e) {
      Log.e("读取错误", e.getMessage());
    } catch (IOException e) {
      Log.e("读取错误", e.getMessage());
    } catch (ClassNotFoundException e) {
      Log.e("错误", e.getMessage());
    } finally {
      closeSilently(read);
    }
    return o;
  }

  private void closeSilently(Closeable closeable) {
    if (closeable != null) {
      try {
        closeable.close();
      } catch (Exception ignored) {
      }
    }
  }

  /**
   * 判断缓存是否已经失效
   */
  private boolean isCacheDataFailure(File dataFile) {
    if (!dataFile.exists()) {
      return false;
    }
    long existTime = System.currentTimeMillis() - dataFile.lastModified();
    boolean failure = false;
    if (NetWorkUtil.getNetworkType(CacheLoader.getApplication()) == NetWorkUtil.NETTYPE_WIFI) {
      failure = existTime > WIFI_CACHE_TIME ? true : false;
    } else {
      failure = existTime > OTHER_CACHE_TIME ? true : false;
    }

    return failure;
  }

  public void clearDisk(String key) {
    File file = new File(fileDir, key + NAME);
    if (file.exists()) file.delete();
  }
}

isCacheDataFailure()方式中就是判断当前的数据是否失效,我是根据当前的网络状况来分wifi状况和非wifi状况,wifi状态下数据过期时间比较短,其他状态过期时间比较长。

4.CacheLoader的设计

/
**
 * Created by wukewei on 16/6/19.
 */
public class CacheLoader {
  private static Application application;

  public static Application getApplication() {
    return application;
  }

  private ICache mMemoryCache, mDiskCache;

  private CacheLoader() {

    mMemoryCache = new MemoryCache();
    mDiskCache = new DiskCache();
  }
  private static CacheLoader loader;

  public static CacheLoader getInstance(Context context) {
    application = (Application) context.getApplicationContext();
    if (loader == null) {
      synchronized (CacheLoader.class) {
        if (loader == null) {
          loader = new CacheLoader();
        }
      }
    }
    return loader;
  }

  public <T> Observable<T> asDataObservable(String key, Class<T> cls, NetworkCache<T> networkCache) {

    Observable observable = Observable.concat(
        memory(key, cls),
        disk(key, cls),
        network(key, cls, networkCache))
        .first(new Func1<T, Boolean>() {
          @Override
          public Boolean call(T t) {
            return t != null;
          }
        });
    return observable;
  }

  private <T> Observable<T> memory(String key, Class<T> cls) {

    return mMemoryCache.get(key, cls).doOnNext(new Action1<T>() {
      @Override
      public void call(T t) {
        if (null != t) {
          Log.d("我是来自内存","我是来自内存");
        }
      }
    });
  }

  private <T> Observable<T> disk(final String key, Class<T> cls) {

    return mDiskCache.get(key, cls)
        .doOnNext(new Action1<T>() {
          @Override
          public void call(T t) {
            if (null != t) {
              Log.d("我是来自磁盘","我是来自磁盘");
              mMemoryCache.put(key, t);
            }
          }
        });
  }

  private <T> Observable<T> network(final String key, Class<T> cls
      , NetworkCache<T> networkCache) {

    return networkCache.get(key, cls)
        .doOnNext(new Action1<T>() {
          @Override
          public void call(T t) {
            if (null != t) {
              Log.d("我是来自网络","我是来自网络");
              mDiskCache.put(key, t);
              mMemoryCache.put(key, t);
            }
          }
        });
  }

  public void clearMemory(String key) {
    ((MemoryCache)mMemoryCache).clearMemory(key);
  }

  public void clearMemoryDisk(String key) {
    ((MemoryCache)mMemoryCache).clearMemory(key);
    ((DiskCache)mDiskCache).clearDisk(key);
  }
}

5.网络获取的NetworkCache:

/**
 * Created by wukewei on 16/6/19.
 */
public abstract class NetworkCache<T> {
  public abstract Observable<T> get(String key, final Class<T> cls);
}

6.接下来看怎么使用

/**
 * Created by wukewei on 16/5/30.
 */
public class ItemPresenter extends BasePresenter<ItemContract.View> implements ItemContract.Presenter {

  private static final String key = "new_list";
  protected int pn = 1;

  protected void replacePn() {
    pn = 1;
  }

  private boolean isRefresh() {
    return pn == 1;
  }

  private NetworkCache<ListPopular> networkCache;

  public ItemPresenter(Activity activity, ItemContract.View view) {
    super(activity, view);

  }

  @Override
  public void getListData(String type) {
    if (isRefresh()) mView.showLoading();
    networkCache = new NetworkCache<ListPopular>() {
      @Override
      public Observable<ListPopular> get(String key, Class<ListPopular> cls) {
        return mHotApi.getPopular(ItemPresenter.this.pn, Constants.PAGE_SIZE, type)
            .compose(SchedulersCompat.applyIoSchedulers())
            .compose(RxResultHelper.handleResult())
            .flatMap(populars -> {
              ListPopular popular = new ListPopular(populars);
              return Observable.just(popular);
            });
      }
    };

    Subscription subscription = CacheLoader.getInstance(mActivity)
        .asDataObservable(key + type + ItemPresenter.this.pn, ListPopular.class, networkCache)
        .map(listPopular -> listPopular.data)
        .subscribe(populars -> {
          mView.showContent();
          if (isRefresh()) {
            if (populars.size() == 0) mView.showNotdata();
            mView.addRefreshData(populars);
          } else {
            mView.addLoadMoreData(populars);
          }
        }, throwable -> {
          if (isRefresh())
          mView.showError(ErrorHanding.handleError(throwable));
          handleError(throwable);
        });

    addSubscrebe(subscription);

  }
}

一定要给个key,我是根据key来获取数据的,还要就是给个类型。

但是这个我设计的这个缓存还是不是很理想,接来下想要实现的就是在传入的时候类的class都不用给明,要是有好的实现的方式,欢迎告诉我。

(0)

相关推荐

  • 详解Android 图片的三级缓存及图片压缩

    为什么需要图片缓存 Android默认给每个应用只分配16M的内存,所以如果加载过多的图片,为了防止内存溢出,应该将图片缓存起来.图片的三级缓存分别是: 内存缓存 本地缓存 网络缓存 其中,内存缓存应优先加载,它速度最快:本地缓存次优先加载,它速度也快:网络缓存不应该优先加载,它走网络,速度慢且耗流量. 三级缓存的具体实现 网络缓存 根据图片的url去加载图片 在本地和内存中缓存 public class NetCacheUtils { private LocalCacheUtils mLoca

  • Android中图片的三级缓存机制

    我们不能每次加载图片的时候都让用户从网络上下载,这样不仅浪费流量又会影响用户体验,所以Android中引入了图片的缓存这一操作机制. 原理: 首先根据图片的网络地址在网络上下载图片,将图片先缓存到内存缓存中,缓存到强引用中 也就是LruCache中.如果强引用中空间不足,就会将较早存储的图片对象驱逐到软引用(softReference)中存储,然后将图片缓存到文件(内部存储外部存储)中:读取图片的时候,先读取内存缓存,判断强引用中是否存在图片,如果强引用中存在,则直接读取,如果强引用中不存在,则

  • Android使用缓存机制实现文件下载及异步请求图片加三级缓存

    首先给大家介绍Android使用缓存机制实现文件下载 在下载文件或者在线浏览文件时,或者为了保证文件下载的正确性,需要使用缓存机制,常使用SoftReference来实现. SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收.也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用.另外

  • Android实现图片异步请求加三级缓存

    使用xUtils等框架是很方便,但今天要用代码实现bitmapUtils 的功能,很简单, AsyncTask请求一张图片 ####AsyncTask #####AsyncTask是线程池+handler的封装 第一个泛型: 传参的参数类型类型(和doInBackground一致) 第二个泛型: #####更新进度的参数类型(和onProgressUpdate一致) 第三个泛型: 返回结果的参数类型(和onPostExecute一致, #####和doInBackground返回类型一致) 看A

  • android中图片的三级缓存cache策略(内存/文件/网络)

    1.简介 现在android应用中不可避免的要使用图片,有些图片是可以变化的,需要每次启动时从网络拉取,这种场景在有广告位的应用以及纯图片应用(比如百度美拍)中比较多. 现在有一个问题:假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响.当然,我想,向百度美拍这样的应用,必然也有其内部的图片缓存策略.总之,图片缓存是很重要而且是必须的. 2.图片缓存的原理 实现图片缓存也不难,需要有相

  • 详解Android中图片的三级缓存及实例

    详解Android中图片的三级缓存及实例 为什么要使用三级缓存 如今的 Android App 经常会需要网络交互,通过网络获取图片是再正常不过的事了 假如每次启动的时候都从网络拉取图片的话,势必会消耗很多流量.在当前的状况下,对于非wifi用户来说,流量还是很贵的,一个很耗流量的应用,其用户数量级肯定要受到影响 特别是,当我们想要重复浏览一些图片时,如果每一次浏览都需要通过网络获取,流量的浪费可想而知 所以提出三级缓存策略,通过网络.本地.内存三级缓存图片,来减少不必要的网络交互,避免浪费流量

  • Android 图片的三级缓存机制实例分析

    Android 图片的三级缓存机制实例分析 当我们获取图片的时候,如果不加以协调好图片的缓存,就会造成大流量,费流量应用,用户体验不好,影响后期发展.为此,我特地分享Android图片的三级缓存机制之从网络中获取图片,来优化应用,具体分三步进行: (1)从缓存中获取图片 (2)从本地的缓存目录中获取图片,并且获取到之后,放到缓存中 (3)从网络去下载图片,下载完成之后,保存到本地和放到缓存中 很好的协调这三层图片缓存就可以大幅度提升应用的性能和用户体验. 快速实现三级缓存的工具类ImageCac

  • Android图片三级缓存策略(网络、本地、内存缓存)

    一.简介 现在的Android应用程序中,不可避免的都会使用到图片,如果每次加载图片的时候都要从网络重新拉取,这样不但很耗费用户的流量,而且图片加载的也会很慢,用户体验很不好.所以一个应用的图片缓存策略是很重要的.通常情况下,Android应用程序中图片的缓存策略采用"内存-本地-网络"三级缓存策略,首先应用程序访问网络拉取图片,分别将加载的图片保存在本地SD卡中和内存中,当程序再一次需要加载图片的时候,先判断内存中是否有缓存,有则直接从内存中拉取,否则查看本地SD卡中是否有缓存,SD

  • Android中Rxjava实现三级缓存的两种方式

    本文正如标题所说的用rxjava实现数据的三级缓存分别为内存,磁盘,网络,刚好最近在看Android源码设计模式解析与实战(受里面的ImageLoader的设计启发). 我把代码放到了我的hot项目中,github地址 源码下载地址:Rxjava_jb51.rar 1.使用concat()和first()的操作符. 2.使用BehaviorSubject. 先说BehaviorSubject的实现方法,废话不多说直接上代码, /** * Created by wukewei on 16/6/20

  • Android中button实现onclicklistener事件的两种方式

    复制代码 代码如下: package com.demos; import android.app.Activity; import android.os.Bundle; import android.view.View; import android.view.View.OnClickListener; import android.widget.Button; public class GetScreenActivity extends Activity { private Button fi

  • Android 中Popwindow弹出菜单的两种方法实例

    Android 中Popwindow弹出菜单的两种方法实例 1.popWindow就是对话框的一种方式! 此文讲解的android中对话框的一种使用方式,它叫popWindow. 2.popWindow的特性 Android的对话框有两种:PopupWindow和AlertDialog.它们的不同点在于: AlertDialog的位置固定,而PopupWindow的位置可以随意. AlertDialog是非阻塞线程的,而PopupWindow是阻塞线程的. PopupWindow的位置按照有无偏

  • Android无需申请权限拨打电话的两种方式

    Android打电话有两种实现方法: 第一种方法,拨打电话跳转到拨号界面.源代码如下: Intent intent = new Intent(Intent.ACTION_DIAL); Uri data = Uri.parse("tel:" + "135xxxxxxxx"); intent.setData(data); startActivity(intent); 第二种方法,拨打电话直接进行拨打,但是有些第三方rom(例如:MIUI),不会直接进行拨打,而是要用户进

  • Android Studio进行APP图标更改的两种方式总结

    百度了许多相关资料,对两种修改app图标的方式进行总结: 第一种:(最简单的方法) 将你准备好的 图标放入res目录下的drawable,在AndroidManifest.xml文件中,找到android:icon以及android:roundIcon这两个属性,设置为你放入的图标文件. 如图,appicon就是我准备替换的文件.注意保存时,保存名称不能有大写字母与空格,否则编译时会报错,此外,查到的资料中图片格式建议保存为.png.不过自己试验过.jpg与.png都是可以正确替换图标的. 在这

  • MyBatis关闭一级缓存的两种方式(分注解和xml两种方式)

    目录 问题:为什么有缓存 什么场景下必须需要关闭一级缓存 关闭一级缓存方法(针对使用MyBatis场景) 第一种:xml形式(关闭所有一级缓存) 第二种:注解形式(可指定仅仅某个Mapper关闭注解) 第三种:sql动态拼接传入的随机数 问题:为什么有缓存 mybatis默认开启一级缓存 什么场景下必须需要关闭一级缓存 场景:执行2次相同sql,但是第一次查询sql结果会加工处理,比如解析铭文,或者反编译加密解密用户名/密码字符串等等,如果不关闭一级缓存,等第二次再查询相同sql时不会去数据库表

  • C语言中计算二叉树的宽度的两种方式

    C语言中计算二叉树的宽度的两种方式 二叉树作为一种很特殊的数据结构,功能上有很大的作用!今天就来看看怎么计算一个二叉树的最大的宽度吧. 采用递归方式 下面是代码内容: int GetMaxWidth(BinaryTree pointer){ int width[10];//加入这棵树的最大高度不超过10 int maxWidth=0; int floor=1; if(pointer){ if(floor==1){//如果访问的是根节点的话,第一层节点++; width[floor]++; flo

  • 详解python中字典的循环遍历的两种方式

    开发中经常会用到对于字典.列表等数据的循环遍历,但是python中对于字典的遍历对于很多初学者来讲非常陌生,今天就来讲一下python中字典的循环遍历的两种方式. 注意: python2和python3中,下面两种方法都是通用的. 1. 只对键的遍历 一个简单的for语句就能循环字典的所有键,就像处理序列一样: d = {'name1' : 'pythontab', 'name2' : '.', 'name3' : 'com'} for key in d: print (key, ' value

随机推荐