As the title says in this article, using rxjava to implement the third-level cache of data is memory, disk, and network. I happened to be watching Android source code design pattern analysis and actual combat (inspired by the design of ImageLoader inside).
I put the code into my hot project,github address
Source code download address:Rxjava_jb51.rar
1. Use concat() and first() operators.
2. Use BehaviorSubject.
Let’s talk about the implementation method of BehaviorSubject first, and just add the code without saying much nonsense.
/** * 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 = "Data fetched from the server"; BehaviorSubject<String> cache; View mView; @Nullable @Override public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { mView = (.fragment_content, container, false); init(); return mView; } private void init() { ().setOnClickListener(new () { @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) { ("onNext", s); } }); } }); ().setOnClickListener(new () { @Override public void onClick(View v) { = null; } }); (.memory_disk).setOnClickListener(new () { @Override public void onClick(View v) { = null; = null; } }); } private void loadNewWork() { Observable<String> o = (networkData) .doOnNext(new Action1<String>() { @Override public void call(String s) { = s; ("Write to disk", "Write to disk"); } }); (new Action1<String>() { @Override public void call(String s) { (s); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { } }); } private Subscription subscriptionData(@NonNull Observer<String> observer) { if (cache == null) { cache = (); (new <String>() { @Override public void call(Subscriber<? super String> subscriber) { String data = diskData; if (data == null) { ("From the Internet", "From the Internet"); loadNewWork(); } else { ("From Disk", "From Disk"); (data); } } }) .subscribeOn(()) .subscribe(cache); } else { ("From Memory", "From Memory"); } return (()).subscribe(observer); } }
The most important one is the subscriptionData() method, which is to first determine whether the cache exists or not. If it exists, return the data in memory, then judge whether the disk data exists. If it exists, return it. If the previous two do not exist, then go to the network to obtain the data. The most important thing is that when you get data from the network, remember to save it in memory and on disk, and assign it to memory when the data is obtained by the disk.
Next, let’s talk about using the operators of concat() and first() to implement it. This is when I was watching Android source code design pattern analysis and actual combat. The author introduced the design of ImageLoader in the first chapter.
The storage method in memory is implemented by LruCache, and the disk storage method is serialized storage.
1. Define an interface:
/** * 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. Implementation of memory storage
/** * Created by wukewei on 16/6/19. */ public class MemoryCache implements ICache{ private LruCache<String, String> mCache; public MemoryCache() { final int maxMemory = (int) ().maxMemory(); final int cacheSize = maxMemory / 8; mCache = new LruCache<String, String>(cacheSize) { @Override protected int sizeOf(String key, String value) { try { return ("UTF-8").length; } catch (UnsupportedEncodingException e) { (); return ().length; } } }; } @Override public <T> Observable<T> get(final String key, final Class<T> cls) { return (new <T>() { @Override public void call(Subscriber<? super T> subscriber) { String result = (key); if (()) { return; } if ((result)) { (null); } else { T t = new Gson().fromJson(result, cls); (t); } (); } }); } @Override public <T> void put(String key, T t) { if (null != t) { (key, new Gson().toJson(t)); } } public void clearMemory(String key) { (key); } }
3. Implementation of disk storage
/** * 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 = ().getCacheDir(); } @Override public <T> Observable<T> get(final String key, final Class<T> cls) { return (new <T>() { @Override public void call(Subscriber<? super T> subscriber) { T t = (T) getDiskData1(key + NAME); if (()) { return; } if (t == null) { (null); } else { (t); } (); } }) .subscribeOn(()) .observeOn(()); } @Override public <T> void put(final String key, final T t) { (new <T>() { @Override public void call(Subscriber<? super T> subscriber) { boolean isSuccess = isSave(key + NAME, t); if (!() && isSuccess) { (t); (); } } }) .subscribeOn(()) .observeOn(()) .subscribe(); } /** * Save data */ 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); (t); (); isSuccess=true; } catch (IOException e) { ("Write cache error",()); } catch (Exception e) { ("Write cache error",()); } finally { closeSilently(objectOut); } return isSuccess; } /** * Get saved data */ private Object getDiskData1(String fileName) { File file = new File(fileDir, fileName); if (isCacheDataFailure(file)) { return null; } if (!()) { return null; } Object o = null; ObjectInputStream read = null; try { read = new ObjectInputStream(new FileInputStream(file)); o = (); } catch (StreamCorruptedException e) { ("Read Error", ()); } catch (IOException e) { ("Read Error", ()); } catch (ClassNotFoundException e) { ("mistake", ()); } finally { closeSilently(read); } return o; } private void closeSilently(Closeable closeable) { if (closeable != null) { try { (); } catch (Exception ignored) { } } } /** * Determine whether the cache has expired */ private boolean isCacheDataFailure(File dataFile) { if (!()) { return false; } long existTime = () - (); boolean failure = false; if ((()) == 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 (()) (); } }
The isCacheDataFailure() method is to determine whether the current data is invalid. I divide the wifi status and non-wifi status based on the current network status. The data expiration time in the wifi state is relatively short, while other states expiration time is relatively long.
Design
/ ** * 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) (); if (loader == null) { synchronized () { if (loader == null) { loader = new CacheLoader(); } } } return loader; } public <T> Observable<T> asDataObservable(String key, Class<T> cls, NetworkCache<T> networkCache) { Observable observable = ( 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 (key, cls).doOnNext(new Action1<T>() { @Override public void call(T t) { if (null != t) { ("I'm from memory","I'm from memory"); } } }); } private <T> Observable<T> disk(final String key, Class<T> cls) { return (key, cls) .doOnNext(new Action1<T>() { @Override public void call(T t) { if (null != t) { ("I'm from disk","I'm from disk"); (key, t); } } }); } private <T> Observable<T> network(final String key, Class<T> cls , NetworkCache<T> networkCache) { return (key, cls) .doOnNext(new Action1<T>() { @Override public void call(T t) { if (null != t) { ("I'm from the Internet","I'm from the Internet"); (key, t); (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 obtained by the network:
/** * Created by wukewei on 16/6/19. */ public abstract class NetworkCache<T> { public abstract Observable<T> get(String key, final Class<T> cls); }
6. Let’s see how to use it
/** * Created by wukewei on 16/5/30. */ public class ItemPresenter extends BasePresenter<> implements { 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, view) { super(activity, view); } @Override public void getListData(String type) { if (isRefresh()) (); networkCache = new NetworkCache<ListPopular>() { @Override public Observable<ListPopular> get(String key, Class<ListPopular> cls) { return (, Constants.PAGE_SIZE, type) .compose(()) .compose(()) .flatMap(populars -> { ListPopular popular = new ListPopular(populars); return (popular); }); } }; Subscription subscription = (mActivity) .asDataObservable(key + type + , , networkCache) .map(listPopular -> ) .subscribe(populars -> { (); if (isRefresh()) { if (() == 0) (); (populars); } else { (populars); } }, throwable -> { if (isRefresh()) ((throwable)); handleError(throwable); }); addSubscrebe(subscription); } }
You must give a key. I get the data based on the key, and I also need to give a type.
However, the cache I designed is still not very ideal. What I want to implement is that when passing in, the class class does not need to be clearly explained. If there is a good way to implement it, please let me know.