使用Rx缓存网络数据

欢迎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.

时间: 2024-10-08 17:47:53

使用Rx缓存网络数据的相关文章

分布式缓存系统 Memcached 状态机之网络数据读取与解析

整个状态机的基本流程如下图所示,后续分析将按该流程来进行. 接上节分解,主线程将接收的连接socket分发给了某工作线程,然后工作线程从任务队列中取出该连接socket的CQ_ITEM,开始处理该连接的所有业务逻辑.这个过程也就是上图中的第一个状态conn_listening. 而工作线程首先进入的状态就是conn_new_cmd,即为这个新的连接做一些准备工作,如清理该连接conn结构的读缓冲区等. 准备状态conn_new_cmd具体分析如下: {  <span style="font

iOS开发网络数据之AFNetworking使用

iOS开发网络数据之AFNetworking使用 如何选择AFNetworking版本 首先得下载AFNetworking库文件,下载时得首先弄清楚,你将要开发的软件兼容的最低版本是多少.AFNetworking 2.0或者之后的版本需要xcode5.0版本并且只能为IOS6或更高的手机系统上运行,如果开发MAC程序,那么2.0版本只能在MAC OS X 10.8或者更高的版本上运行. AFNetworking 2.0的下载地址https://github.com/AFNetworking/AF

网络数据包分析 网卡Offload

http://blog.nsfocus.net/network-packets-analysis-nic-offload/ 对于网络安全来说,网络传输数据包的捕获和分析是个基础工作,绿盟科技研究员在日常工作中,经常会捕获到一些大小远大于MTU值的数据包,经过分析这些大包的特性,发现和网卡的offload特性有关,本文对网卡Offload技术做简要描述. 文章目录 网络分片技术 网卡offload机制 发送模式 接收模式 网卡offload模式的设置 Linux windows 网卡Offload

Python黑客编程基础3网络数据监听和过滤

Python黑客编程3网络数据监听和过滤 课程的实验环境如下: •      操作系统:kali Linux 2.0 •      编程工具:Wing IDE •      Python版本:2.7.9 •      涉及到的主要python模块:pypcap,dpkt,scapy,scapy-http 涉及到的几个python网络抓包和分析的模块,dpkt和scapy在kali linux 2.0 中默认已经被安装,如果你的系统中没有需要手动安装一下,下面是软件包安装的简单说明. 在kali下

Linux内核中网络数据包的接收-第一部分 概念和框架

与网络数据包的发送不同,网络收包是异步的的,因为你不确定谁会在什么时候突然发一个网络包给你,因此这个网络收包逻辑其实包含两件事:1.数据包到来后的通知2.收到通知并从数据包中获取数据这两件事发生在协议栈的两端,即网卡/协议栈边界以及协议栈/应用边界:网卡/协议栈边界:网卡通知数据包到来,中断协议栈收包:协议栈栈/应用边界:协议栈将数据包填充socket队列,通知应用程序有数据可读,应用程序负责接收数据.本文就来介绍一下关于这两个边界的这两件事是怎么一个细节,关乎网卡中断,NAPI,网卡poll,

网络数据备份系统结构的几种类型分析

目前最常见的网络数据备份系统结构按其架构不同可以分为四种:基于网络附加存储(DAS-Base)结构,基于局域网(LAN-Base)结构,基于 SAN 结构的 LAN-Free 和Server-Free结构. 网络数据备份系统结构之DAS-Base 结构 基于网络附加存储系统的网络数据备份系统结构是最简单的一种数据保护方案,在大多数情况下,这种备份大多是采用服务器上自带的磁带机或备份硬盘,而备份操作往往也是通过手工操作的方式进行的.如图1所示,红色虚线表示数据流,下同.它适合下面的应用环境: 图1

Android公共库——图片缓存 网络缓存 下拉及底部更多ListView 公共类

Android公共库--图片缓存 网络缓存 下拉及底部更多ListView 公共类 转载自http://www.trinea.cn/android/android-common-lib/ 介绍总结的一些android公共库,包含缓存(图片缓存.预取缓存.网络缓存).公共View(下拉及底部加载更多ListView.底部加载更多ScrollView.滑动一页Gallery).及Android常用工具类(网络.下载.shell.文件.json等等). TrineaAndroidCommon已开源,地

缓存网络请求的结果

显然在某些情况下我们很希望减少移动设备和网络的交互次数,这就需要使用iOS的内存缓存了.代码基本上没有什么需要解释的地方,注意不要乱缓存,注意根据需要清理缓存即可. 1 //构建请求 2 NSURL *url = [NSURL URLWithString:@"http://218.241.17.106/webService/configService.asmx/GetNewCarInfo?CarID=1"]; 3 NSURLCache *urlCache = [NSURLCache s

NSURLSession访问网络数据

1.NSMutableURLRequest的设置 //创建NSMutableURLRequest对象 NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url]; //设置请求类型 [request setHTTPMethod:@"POST"]; //设置超时时间 [request setTimeoutInterval:60]; //设置缓存策略 [request setCachePolicy:NSUR