使用Rx缓存网络数据

jopen 9年前

欢迎Follow我的GitHub, 关注我的CSDN.

RxJava是响应式编程, 在异步处理网络数据时, 使用广泛.
我们也可以使用一些Rx的特性, 优雅地缓存网络数据.

缓存模式: 读取数据库, 显示, 请求数据, 存储到数据库, 再更新页面.

缓存模式

使用Dagger2+Retrofit+Rx的标准组合, 我来讲解一下如何使用.

GitHub下载地址

动画


1. 框架

常规项目, 包含跳转缓存和非缓存页面, 为了模拟慢速环境, 延迟3秒加载数据.

public class MainActivity extends AppCompatActivity {        @Override      protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_main);      }        // 跳转无缓存      public void gotoNoCache(View view) {          startActivity(new Intent(this, NocacheActivity.class));      }        // 跳转有缓存      public void gotoCache(View view) {          startActivity(new Intent(this, CacheActivity.class));      }  }

2. 无缓存

依赖注入三个关键部分, Application/Component/Module.

public class RcApplication extends Application {      private ApiComponent mApiComponent;        @Override public void onCreate() {          super.onCreate();          mApiComponent = DaggerApiComponent.builder()                  .apiModule(new ApiModule(this)).build();      }        public ApiComponent getApiComponent() {          return mApiComponent;      }  }
@Singleton  @Component(modules = ApiModule.class)  public interface ApiComponent {      void inject(NocacheActivity activity);        void inject(CacheActivity activity);  }
@Module  public class ApiModule {      private Application mApplication;        public ApiModule(Application application) {          mApplication = application;      }        @Provides      @Singleton      public Application provideApplication() {          return mApplication;      }        @Provides      @Singleton GitHubClient provideGitHubClient() {          return new GitHubClient();      }        @Provides ObservableRepoDb provideObservableRepoDb() {          return new ObservableRepoDb(mApplication);      }  }

模块提供应用信息, GitHub的网络请求, 数据库.
@Singleton表示单例模式, 全部注入拥有一个实例.

页面, 使用RecyclerView显示列表信息, 在加载时显示ProgressBar.

/** * 无缓存Activity * <p> * Created by wangchenlong on 16/1/18. */  public class NocacheActivity extends Activity {        @Bind(R.id.nocache_rv_list) RecyclerView mRvList;      @Bind(R.id.nocache_pb_progress) ProgressBar mPbProgress;        @Inject Application mApplication;      @Inject GitHubClient mGitHubClient;        private ListAdapter mListAdapter;        @Override protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_nocache);          ButterKnife.bind(this);            ((RcApplication) getApplication()).getApiComponent().inject(this);            LinearLayoutManager layoutManager = new LinearLayoutManager(mApplication);          mRvList.setLayoutManager(layoutManager);            mListAdapter = new ListAdapter();          mRvList.setAdapter(mListAdapter);      }        @Override protected void onResume() {          super.onResume();            // 延迟3秒, 模拟效果          mGitHubClient.getRepos("SpikeKing")                  .delay(3, TimeUnit.SECONDS)                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribe(this::onSuccess, this::onError);            mPbProgress.setVisibility(View.VISIBLE);      }        private void onSuccess(ArrayList<Repo> repos) {          mListAdapter.setRepos(repos);          mPbProgress.setVisibility(View.INVISIBLE);      }        private void onError(Throwable throwable) {          mPbProgress.setVisibility(View.INVISIBLE);      }  }

通过观察可以发现, 长时间显示白屏会降低用户体验. 我来看看缓存模式.


3. 缓存

缓存模式: 读取数据库, 显示, 请求数据, 存储到数据库, 再更新页面.
推荐使用脚本生成数据库处理类, 使用方式参考, 自动生成DbHelper的脚本.

主页逻辑.

public class CacheActivity extends Activity {        @Bind(R.id.cache_rv_list) RecyclerView mRvList; // 列表      @Bind(R.id.cache_srl_swipe) SwipeRefreshLayout mSrlSwipe; // 刷新        @Inject Application mApplication;      @Inject ObservableRepoDb mRepoDb;      @Inject GitHubClient mGitHubClient;        private ListAdapter mListAdapter; // RecyclerView适配器        @Override protected void onCreate(Bundle savedInstanceState) {          super.onCreate(savedInstanceState);          setContentView(R.layout.activity_cache);          ButterKnife.bind(this);            // 注入类          ((RcApplication) getApplication()).getApiComponent().inject(this);            LinearLayoutManager layoutManager = new LinearLayoutManager(mApplication);          mRvList.setLayoutManager(layoutManager);            mListAdapter = new ListAdapter();          mRvList.setAdapter(mListAdapter);            mSrlSwipe.setOnRefreshListener(this::fetchUpdates);      }        @Override protected void onResume() {          super.onResume();          mRepoDb.getObservable()                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribe(this::setData);            fetchUpdates();          Toast.makeText(mApplication, "正在更新", Toast.LENGTH_SHORT).show();      }        // 设置数据, 更新完成会调用      private void setData(ArrayList<Repo> repos) {          mListAdapter.setRepos(repos);          Toast.makeText(mApplication, "更新完成", Toast.LENGTH_SHORT).show();      }        private void fetchUpdates() {          // 延迟3秒, 模拟效果          mGitHubClient.getRepos("SpikeKing")                  .delay(3, TimeUnit.SECONDS)                  .subscribeOn(Schedulers.io())                  .observeOn(AndroidSchedulers.mainThread())                  .subscribe(mRepoDb::insertRepoList, this::fetchError, this::fetchComplete);      }        private void fetchError(Throwable throwable) {          mSrlSwipe.setRefreshing(false);      }        private void fetchComplete() {          mSrlSwipe.setRefreshing(false);      }  }

数据库的观察者

/** * Redo的观察者 * <p> * Created by wangchenlong on 16/1/18. */  public class ObservableRepoDb {      private PublishSubject<ArrayList<Repo>> mPublishSubject; // 发表主题      private RepoDbHelper mDbHelper; // 数据库        public ObservableRepoDb(Context context) {          mDbHelper = new RepoDbHelper(context);          mPublishSubject = PublishSubject.create();      }        // 返回观察者      public Observable<ArrayList<Repo>> getObservable() {          Observable<ArrayList<Repo>> firstObservable = Observable.fromCallable(this::getRepoList);          return firstObservable.concatWith(mPublishSubject); // 连接发表主题      }        // 从数据库获得数据      private ArrayList<Repo> getRepoList() {          mDbHelper.openForRead();          ArrayList<Repo> repos = new ArrayList<>();          Cursor c = mDbHelper.getAllRepo();          if (!c.moveToFirst()) {              return repos; // 返回空          }            do {              // 添加数据              repos.add(new Repo(                      c.getString(RepoDbHelper.REPO_ID_COLUMN_POSITION),                      c.getString(RepoDbHelper.REPO_NAME_COLUMN_POSITION),                      c.getString(RepoDbHelper.REPO_DESCRIPTION_COLUMN_POSITION),                      new Repo.Owner(c.getString(RepoDbHelper.REPO_OWNER_COLUMN_POSITION), "", "", "")));          } while (c.moveToNext());          c.close();          mDbHelper.close();          return repos;      }        // 插入Repo列表      public void insertRepoList(ArrayList<Repo> repos) {          mDbHelper.open();          mDbHelper.removeAllRepo();          for (Repo repo : repos) {              mDbHelper.addRepo(                      repo.getId(),                      repo.getName(),                      repo.getDescription(),                      repo.getOwner().getLogin()              );          }          mDbHelper.close();          mPublishSubject.onNext(repos); // 会调用更新数据      }  }

这一部分是关键, 实现网络请求同步插入数据库和更新页面.
关联PublishSubject, 在插入数据完成后, 调用绑定观察者, 更新页面.
.concatWith(mPublishSubject)mPublishSubject.onNext(repos).


Rx在处理网络请求方面, 确实非常优雅, 值得喜欢完美的人使用.

OK, that’s all! Enjoy it.

来自: http://blog.csdn.net/caroline_wendy/article/details/50540272