Rxjava實(shí)現(xiàn)三級緩存的兩種方式

本文正如標(biāo)題所說的用rxjava實(shí)現(xiàn)數(shù)據(jù)的三級緩存分別為內(nèi)存病袄,磁盤,網(wǎng)絡(luò)观话,剛好最近在看Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)(受里面的ImageLoader的設(shè)計(jì)啟發(fā))炒嘲。
我把代碼放到了我的hot項(xiàng)目中蹬铺,Hot是關(guān)于微信頭條分享的app
github地址
1.使用concat()和first()的操作符尝哆。
2.使用BehaviorSubject。
先說BehaviorSubject的實(shí)現(xiàn)方法丛塌,廢話不多說直接上代碼较解,

/**
 * 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 = "從服務(wù)器獲取的數(shù)據(jù)";
    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("來自網(wǎng)絡(luò)", "來自網(wǎng)絡(luò)");
                        loadNewWork();
                    } else {
                        Log.d("來自磁盤", "來自磁盤");
                        subscriber.onNext(data);
                    }
                }
            })
                    .subscribeOn(Schedulers.io())
                    .subscribe(cache);

        } else {
            Log.d("來自內(nèi)存", "來自內(nèi)存");
        }

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


}

其中最主要的是subscriptionData()這個方法畜疾,就是先判斷 cache是否存在要是存在的話就返回內(nèi)存中數(shù)據(jù),再去判斷磁盤數(shù)據(jù)是否存在印衔,如果存在就返回啡捶,要是前面兩種都不存在的時候,再去網(wǎng)絡(luò)中獲取數(shù)據(jù)奸焙。還有最重要的是當(dāng)你從網(wǎng)絡(luò)獲取數(shù)據(jù)的時候要記得保存在內(nèi)存中和保存在磁盤中瞎暑,在磁盤獲取數(shù)據(jù)的時候把它賦值給內(nèi)存。
接下來就說說用concat()和first()的操作符來實(shí)現(xiàn)与帆,這是我在看Android源碼設(shè)計(jì)模式解析與實(shí)戰(zhàn)了赌,作者在第一章的時候就介紹ImageLoader的設(shè)計(jì)。
在內(nèi)存中存儲的方式LruCache來實(shí)現(xiàn)的玄糟,磁盤存儲的方式就是序列化存儲勿她。
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.內(nèi)存存儲的實(shí)現(xiàn)

/**
 * 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.磁盤存儲的實(shí)現(xiàn)

/**
* 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();
   }

   /**
    * 保存數(shù)據(jù)
    */
   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;
   }

   /**
    * 獲取保存的數(shù)據(jù)
    */
   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) {
           }
       }
   }



   /**
    * 判斷緩存是否已經(jīng)失效
    */
   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()方式中就是判斷當(dāng)前的數(shù)據(jù)是否失效,我是根據(jù)當(dāng)前的網(wǎng)絡(luò)狀況來分wifi狀況和非wifi狀況阵翎,wifi狀態(tài)下數(shù)據(jù)過期時間比較短逢并,其他狀態(tài)過期時間比較長。
4.CacheLoader的設(shè)計(jì)

**
 * 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("我是來自內(nèi)存","我是來自內(nèi)存");
                }
            }
        });
    }

    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("我是來自網(wǎng)絡(luò)","我是來自網(wǎng)絡(luò)");
                            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.網(wǎng)絡(luò)獲取的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郭卫,我是根據(jù)key來獲取數(shù)據(jù)的砍聊,還要就是給個類型。
但是這個我設(shè)計(jì)的這個緩存還是不是很理想贰军,接來下想要實(shí)現(xiàn)的就是在傳入的時候類的class都不用給明玻蝌,要是有好的實(shí)現(xiàn)的方式,歡迎告訴我词疼。再次給上我的github地址俯树。謝謝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市寒跳,隨后出現(xiàn)的幾起案子聘萨,更是在濱河造成了極大的恐慌竹椒,老刑警劉巖童太,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異胸完,居然都是意外死亡书释,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門赊窥,熙熙樓的掌柜王于貴愁眉苦臉地迎上來爆惧,“玉大人,你說我怎么就攤上這事锨能〕对伲” “怎么了芍耘?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵,是天一觀的道長熄阻。 經(jīng)常有香客問我斋竞,道長,這世上最難降的妖魔是什么秃殉? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任坝初,我火速辦了婚禮,結(jié)果婚禮上钾军,老公的妹妹穿的比我還像新娘鳄袍。我一直安慰自己,他們只是感情好吏恭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布拗小。 她就那樣靜靜地躺著,像睡著了一般樱哼。 火紅的嫁衣襯著肌膚如雪十籍。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天唇礁,我揣著相機(jī)與錄音勾栗,去河邊找鬼。 笑死盏筐,一個胖子當(dāng)著我的面吹牛围俘,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播琢融,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼界牡,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了漾抬?” 一聲冷哼從身側(cè)響起宿亡,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎纳令,沒想到半個月后挽荠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡平绩,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年圈匆,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片捏雌。...
    茶點(diǎn)故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡跃赚,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出性湿,到底是詐尸還是另有隱情纬傲,我是刑警寧澤满败,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布,位于F島的核電站叹括,受9級特大地震影響葫录,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜领猾,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一米同、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧摔竿,春花似錦面粮、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至袁翁,卻和暖如春柴底,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背粱胜。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工柄驻, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人焙压。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓鸿脓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親涯曲。 傳聞我的和親對象是個殘疾皇子野哭,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評論 2 345

推薦閱讀更多精彩內(nèi)容