不知不覺,從八月二十七號的第一篇教程 RxJava2 實戰知識梳理(1) - 後臺執行耗時操做,實時通知 UI 更新 到今天恰好兩個星期,這一系列教程的目的主要是但願經過一些實際的案例,讓你們對於RxJava
中的一些操做符能有比較直觀的認識。java
今天這篇文章,是昨天晚上花了幾個小時,對項目中用到的MVP + RxJava + Retrofit
的整個架構作了一個簡化,抽離出其中最核心的部分編寫的讀取 Gank 中拉取新聞資訊的例子。git
該例子的源碼能夠經過 RxSample 的第十五章獲取,下面咱們先介紹一個整個例子的框架: github
Model
對應上圖中的NewsRepository
,它負責爲Presenter
層提供數據,由於數據有可能來自緩存或者網絡,所以在NewsRepository
中包含了兩個數據源,也就是LocalNewsSource
和RemoteNewsSource
:數據庫
public class NewsRepository {
private LocalNewsSource mLocalNewsSource;
private RemoteNewsSource mRemoteNewsSource;
private NewsRepository() {
mLocalNewsSource = new LocalNewsSource();
mRemoteNewsSource = new RemoteNewsSource();
}
public static NewsRepository getInstance() {
return Holder.INSTANCE;
}
private static class Holder {
private static NewsRepository INSTANCE = new NewsRepository();
}
public Observable<NewsEntity> getNetNews(String category) {
return mRemoteNewsSource.getNews(category).doOnNext(new Consumer<NewsEntity>() {
@Override
public void accept(NewsEntity newsEntity) throws Exception {
mLocalNewsSource.saveNews(newsEntity);
}
});
}
public Observable<NewsEntity> getCacheNews(String category) {
return mLocalNewsSource.getNews(category);
}
}
複製代碼
LocalNewsSource
經過數據庫的方式實現了資訊的緩存,該數據庫的表包含兩個字段,即資訊的分類,和資訊的具體數據。api
public class LocalNewsSource {
private static final String[] QUERY_PROJECTION = new String[] { NewsContract.NewsTable.COLUMN_NAME_DATA };
private static final String QUERY_SELECTION = NewsContract.NewsTable.COLUMN_NAME_CATEGORY + "= ?";
private NewsDBHelper mNewsDBHelper;
private SQLiteDatabase mSQLiteDatabase;
public LocalNewsSource() {
mNewsDBHelper = new NewsDBHelper(Utils.getAppContext());
mSQLiteDatabase = mNewsDBHelper.getWritableDatabase();
}
public Observable<NewsEntity> getNews(String category) {
return Observable.just(category).flatMap(new Function<String, ObservableSource<NewsEntity>>() {
@Override
public ObservableSource<NewsEntity> apply(String category) throws Exception {
NewsEntity newsEntity = new NewsEntity();
Cursor cursor = mSQLiteDatabase.query(NewsContract.NewsTable.TABLE_NAME, QUERY_PROJECTION, QUERY_SELECTION, new String[] { category }, null, null, null);
if (cursor != null && cursor.moveToNext()) {
String data = cursor.getString(cursor.getColumnIndex(NewsContract.NewsTable.COLUMN_NAME_DATA));
newsEntity = JSON.parseObject(data, NewsEntity.class);
}
if (cursor != null) {
cursor.close();
}
return Observable.just(newsEntity);
}
});
}
public void saveNews(NewsEntity newsEntity) {
Observable.just(newsEntity).observeOn(Schedulers.io()).subscribe(new Consumer<NewsEntity>() {
@Override
public void accept(NewsEntity newsEntity) throws Exception {
if (newsEntity.getResults() != null && newsEntity.getResults().size() > 0) {
String cache = JSON.toJSONString(newsEntity);
ContentValues values = new ContentValues();
values.put(NewsContract.NewsTable.COLUMN_NAME_CATEGORY, "Android");
values.put(NewsContract.NewsTable.COLUMN_NAME_DATA, cache);
mSQLiteDatabase.insert(NewsContract.NewsTable.TABLE_NAME, null, values);
}
}
});
}
}
複製代碼
RemoteNewsSource
則負責從網絡上拉取資訊信息,這裏用到了Retrofit
來實現,有須要瞭解的同窗能夠參考前面的這篇文章 RxJava2 實戰知識梳理(4) - 結合 Retrofit 請求新聞資訊。緩存
public class RemoteNewsSource {
private NewsApi mNewsApi;
public RemoteNewsSource() {
mNewsApi = new Retrofit.Builder()
.baseUrl("http://gank.io")
.addConverterFactory(GsonConverterFactory.create())
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.build().create(NewsApi.class);
}
public Observable<NewsEntity> getNews(String category) {
return mNewsApi.getNews(category, 10, 1);
}
}
複製代碼
首先,咱們須要對於View
和Presenter
之間的交互定義接口,這些接口咱們寫在NewsMvpContract
中:網絡
public class NewsMvpContract {
public static final int REFRESH_AUTO = 0;
public static final int REFRESH_CACHE = 1;
@IntDef ({REFRESH_AUTO, REFRESH_CACHE})
public @interface RefreshType {}
public interface View {
void onRefreshFinished(@RefreshType int refreshType, List<NewsBean> newsEntity);
void showTips(String message);
}
public interface Presenter {
void refresh(@RefreshType int refreshType);
void destroy();
}
}
複製代碼
View
層定義了兩個接口,它們的含義分別爲:架構
onRefreshFinished
:刷新資訊列表。showTips
:在發生錯誤時給予用戶提示。Presenter
層則負責在View
層發起請求以後,調用Model
層獲取數據,而後回調View
層的接口進行界面的更新,所以它提供了兩個接口:app
refresh
:用於View
層發起刷新操做。destroy
:在View
層銷燬時,取消訂閱。這裏,咱們實現Activity
做爲View
層的實現,它在有新的資訊列表回來時,通知RecyclerView
進行刷新,而須要提示時則經過SnackBar
進行提示,同時它還持有一個NewsPresenter
的實例,並在銷燬時調用它的destroy
方法取消訂閱。框架
public class NewsMvpActivity extends AppCompatActivity implements NewsMvpContract.View {
private CoordinatorLayout mRootLayout;
private RecyclerView mRecyclerView;
private NewsMvpAdapter mRecyclerAdapter;
private List<NewsBean> mNewsBeans = new ArrayList<>();
private NewsMvpContract.Presenter mPresenter;
private LinearLayoutManager mLayoutMgr;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_news_mvp);
initView();
dispatchRefresh(NewsMvpContract.REFRESH_CACHE);
}
private void initView() {
mRootLayout = (CoordinatorLayout) findViewById(R.id.cl_root);
mRecyclerView = (RecyclerView) findViewById(R.id.rv_news);
mRecyclerAdapter = new NewsMvpAdapter();
mRecyclerAdapter.setNewsResult(mNewsBeans);
mLayoutMgr = new LinearLayoutManager(this);
mRecyclerView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
mRecyclerView.setLayoutManager(mLayoutMgr);
mRecyclerView.setAdapter(mRecyclerAdapter);
mPresenter = new NewsPresenter(this);
}
@Override
protected void onResume() {
super.onResume();
dispatchRefresh(NewsMvpContract.REFRESH_AUTO);
}
private void dispatchRefresh(@NewsMvpContract.RefreshType int refreshType) {
mPresenter.refresh(refreshType);
}
@Override
public void onRefreshFinished(@NewsMvpContract.RefreshType int refreshType, List<NewsBean> newsBeans) {
mNewsBeans.clear();
mNewsBeans.addAll(newsBeans);
mRecyclerAdapter.notifyDataSetChanged();
}
@Override
public void showTips(String message) {
Snackbar.make(mRootLayout, message, Snackbar.LENGTH_SHORT).show();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.destroy();
}
}
複製代碼
Presenter
層是比較複雜的地方,當View
層調用refresh
接口時,它須要根據刷新的類型,請求Model
層不一樣的接口進行處理,而且在數據返回時,將其和當前的數據進行合併,再通知View
層進行刷新。
public class NewsPresenter implements NewsMvpContract.Presenter {
private static final long AUTO_REFRESH_TIME = 1000 * 60 * 10;
private CompositeDisposable mCompositeDisposable;
private NewsMvpContract.View mView;
private List<NewsBean> mNewsBeans;
private long mLastNetUpdateTime;
public NewsPresenter(NewsMvpContract.View view) {
mView = view;
mCompositeDisposable = new CompositeDisposable();
mNewsBeans = new ArrayList<>();
}
@Override
public void refresh(@RefreshType int refreshType) {
if (refreshType == NewsMvpContract.REFRESH_CACHE) {
NewsRepository.getInstance()
.getCacheNews("Android")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RefreshObserver(refreshType));
} else {
if (System.currentTimeMillis() - mLastNetUpdateTime > AUTO_REFRESH_TIME) { //自動刷新的間隔時間爲十分鐘。
NewsRepository.getInstance()
.getNetNews("Android")
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new RefreshObserver(refreshType));
}
}
}
@Override
public void destroy() {
mCompositeDisposable.clear();
mView = null;
}
private void updateNewsBeans(@NewsMvpContract.RefreshType int refreshType, NewsEntity newsEntity) {
List<NewsBean> filter = new ArrayList<>();
for (NewsResultEntity resultEntity : newsEntity.getResults()) { //對資訊進行去重,須要重寫NewsBean的對應方法。
NewsBean newsBean = entityToBean(resultEntity);
if (!mNewsBeans.contains(newsBean)) {
filter.add(newsBean);
}
}
if (refreshType == NewsMvpContract.REFRESH_CACHE && mNewsBeans.size() == 0) { //只有當前沒有數據時,才使用緩存。
mNewsBeans = filter;
} else if (refreshType == NewsMvpContract.REFRESH_AUTO) { //自動刷新的數據放在頭部。
mNewsBeans.addAll(0, filter);
mLastNetUpdateTime = System.currentTimeMillis();
}
}
private NewsBean entityToBean(NewsResultEntity resultEntity) {
String title = resultEntity.getDesc();
NewsBean bean = new NewsBean();
bean.setTitle(title);
return bean;
}
private class RefreshObserver extends DisposableObserver<NewsEntity> {
private @NewsMvpContract.RefreshType int mRefreshType;
RefreshObserver(@NewsMvpContract.RefreshType int refreshType) {
mRefreshType = refreshType;
}
@Override
public void onNext(NewsEntity newsEntity) {
updateNewsBeans(mRefreshType, newsEntity);
mView.onRefreshFinished(mRefreshType, mNewsBeans);
}
@Override
public void onError(Throwable throwable) {
mView.showTips("刷新錯誤");
}
@Override
public void onComplete() {}
}
}
複製代碼
這裏咱們之因此要定義一個刷新類型的目的在於,在實際項目中,咱們有如下幾種刷新方式:
10
分鐘進入一次界面後自動獲取。這裏爲了演示方便咱們只實現了前兩種方式,你們之後在處理時,也能夠借鑑相應的方式。
下面,咱們演示一下,先在聯網狀態下拉取資訊,再在斷網狀況下從新進入,讀取緩存的狀況:
這篇文章原理的東西很少,由於相信你們對於MVP
也已經很熟悉,主要是經過一個簡單的案例演示一個如何在
MVP
架構當中使用
RxJava
,有部分沒有貼出來的代碼你們能夠查看
RxSample 中的第十五章。