安卓开发进阶之RxJava在实际项目中使用--第二篇

来源:互联网 发布:淘宝vr眼镜效果怎么样 编辑:程序博客网 时间:2024/06/08 04:12

关于RxJava原理分析,请参考仍物线写的文章—-给 Android 开发者的 RxJava 详解。本文不对原理作过多的分析,从最快上手的角度,让开发者使用起来,当我们有实践经验后回过头来看原理分析会更清晰。
本系列共有三篇文章,分别关于Rxjava的基础使用(最快,最实用),Retrofit使用(Github上star达22k+,安卓领域排名第一),最后是RxCache缓存(大部分app都支持离线查看功能)。

安卓开发进阶之RxJava在实际项目中使用–第一篇

这是第二篇,网络请求框架Retrofit的使用,包括封装,日志打印,添加头部信息,Get和Post。本来想把RxCache作为第三篇文章,但是想想没必要,就作为第二篇的一部分,所以这个系列暂时就只有这两篇文章。

首先添加引用

        compile 'io.reactivex.rxjava2:rxandroid:2.0.1'    compile 'com.squareup.retrofit2:retrofit:2.2.0'compile 'com.squareup.retrofit2:converter-gson:2.0.0-beta4'    compile 'com.jakewharton.retrofit:retrofit2-rxjava2-adapter:1.0.0'    compile 'com.squareup.okhttp3:logging-interceptor:3.6.0'

第三个将服务器返回的数据用Gson转换,第五个是日志拦截器,用来打印日志。
因为Retrofit是基于Okhttp的,所以先获取OkHttp,

private   OkHttpClient getClient() {        HttpLoggingInterceptor loggingInterceptor=new HttpLoggingInterceptor(new HttpLoggingInterceptor.Logger() {            @Override            public void log(String message) {                Log.d("resHttp",message);//打印服务器返回的内容            }        });        loggingInterceptor.setLevel(HttpLoggingInterceptor.Level.BODY);//BODY表示显示服务器返回的内容体,还有其他级别比如Header        OkHttpClient httpClient = new OkHttpClient.Builder().addInterceptor(new Interceptor() {            @Override            public Response intercept(Chain chain) throws IOException {                Request request = chain.request().newBuilder().cacheControl(CacheControl.FORCE_NETWORK)                        .addHeader("X-Requested-With", "XMLHttpRequest")//添加头部信息,比如session//                        .addHeader("Cookie", "JSESSIONID="+ ZxlVar.mySession)                        .build();                return chain.proceed(request);}})                .addInterceptor(loggingInterceptor).build();        return httpClient;    }

然后通过单例模式获取Retrofit对象,其中传入上面的httpClient

public  Retrofit getRetrofit(String hostUrl)    {        if (mRetrofit==null)        mRetrofit=new Retrofit.Builder().baseUrl(hostUrl).client(getClient())        .addConverterFactory(GsonConverterFactory.create())//使用Gson来解析数据,这个可以自定义,但是一般不需要                     .addCallAdapterFactory(RxJava2CallAdapterFactory.create()).build();        return mRetrofit;    }

使用之前,先新建类用来存放所有的调用接口,并指明调用方式(Get/Post),声明参数类型及个数,以及要解析的格式,比如

@GET("/jt/getApkVersion.htm")    Observable<ApkUpdateEntity> getApkVersson(@Query("apkCode") String apkCode);

其对应的完整URL为:

http://app.xxx.com00/jt/getApkVersion.htm?apkCode=OperationTeminal

Post请求格式如下:

@POST("/jt/icCardManager/uploadIcCardRecord")    Observable<CardUpResEntity> upLoadCardData(@Query("terminalCode") String terminalCode            , @Query("icCardData") String data);

其对应的完整URL为:

http://app.xxx.com00/jt/icCardManager/uploadIcCardRecord?terminalCode=xxx&icCardData=xxx

服务器返回数据后,解析成ApkUpdateEntity格式,然后就可以调用里面的内容。涉及到私密性,这里使用网上公用接口
https://api.github.com/
不需要传参数,调用代码如下

MyApiProvider myApiProvider= MyApplication.getInstance().getRetrofit("https://api.github.com/")//一定以  /  结尾                .create(MyApiProvider.class);        myApiProvider.getApkVersson().subscribeOn(Schedulers.io())//指定在子线程进行耗时处理                .observeOn(AndroidSchedulers.mainThread())//指定在UI线程更新UI        .subscribe(new Consumer<TestEntity>() {//指定按照TestEntity解析            @Override            public void accept(TestEntity testEntity) throws Exception {//TestEntity,这里是解析后的结果                //处理返回的结果                Toast.makeText(MainActivity.this,testEntity.getAuthorizations_url(),Toast.LENGTH_LONG).show();            }        });

接口声明为:

@GET("/")    Observable<TestEntity> getApkVersson();

如果将@GET(“/”) 换成@GET(“”)会报错
Missing either @GET URL or @Url parameter.
运行效果
这里写图片描述
至此就可以从服务器拿到相应的数据并做处理了。我们再看另外一个重要的话题–缓存。缓存至少有三个好处,一是减少网络请求次数,以降低对服务器的压力,二是缩短网络请求时间,从本地取数据肯定比从服务器取数据要快,三是网络情况差甚至断网后还可以查看数据,你现在还能看到断网了不能使用的app吗,很少了。既然缓存这么重要,那么一般是怎么做的呢?现在一般使用二级缓存,即内存缓存和硬盘缓存。内存没有数据,就去硬盘取,还没有,就从服务器取数据。注意:内存缓存会造成堆内存泄露,所有一级缓存通常要严格控制缓存的大小,一般控制在系统内存的1/4。这两种缓存方式分别对应ASimpleCache和DiskLruCache。大家可以去网上搜索,实现起来也不复杂,但是刚上手的人可能也要花不少时间。这都不重要,我要说的是,有了Retrofit支持的RxCache,前面那些东西都不要了,对,Retrofit已经帮我们实现了二级缓存,不用费老大的劲去自己实现,效果还不一定有人家的好。下面进入主题使用RxCache+Retrofit+RxJava实现二级缓存。
RxCache地址

先添加依赖

    compile "com.github.VictorAlbertos.RxCache:runtime:1.8.0-2.x"    compile 'com.github.VictorAlbertos.Jolyglot:gson:0.0.3'

然后获取RxCache对象

public CacheProvider getCacheProvider()    {        if (mCacheProvider==null) {            mCacheProvider = new RxCache.Builder().persistence(MyRetrofitUtil.getCacheDir(getApplicationContext()), new GsonSpeaker()).using(CacheProvider.class);        }        return mCacheProvider;    }

其中CacheProvider类用于存放需要缓存的接口,

public interface CacheProvider {    @Expirable(false)//false表示内存不足系统回收时永远不回收    @LifeCache(duration = 60,timeUnit = TimeUnit.MINUTES)//60分钟有效时间    Observable<TestEntity> getApkVersson(Observable<TestEntity> obs);}

注意CacheProvider中的接口名getApkVersson要与MyApiProvider 中的一致。
调用:

MyApiProvider iGetData= MyApplication.getInstance().getRetrofit("https://api.github.com/").create(MyApiProvider.class);        CacheProvider cache=MyApplication.getInstance().getCacheProvider();//获取CacheProvider对象        Observable<TestEntity> observable;        observable= cache.getApkVersson( iGetData.getApkVersson());//调用接口(带缓存功能)        observable.subscribeOn(Schedulers.io()).observeOn(AndroidSchedulers.mainThread())                .subscribe(new Consumer<TestEntity>() {            @Override            public void accept(@NonNull TestEntity testEntity) throws Exception {                Toast.makeText(MainActivity.this,"RxCache:"+testEntity.getAuthorizations_url(),Toast.LENGTH_LONG).show();            }        });

运行效果
这里写图片描述
从演示中可以看到,关闭网络后依然可以拿到缓存数据展示。至此缓存功能已经实现,还有两个参数需要说明一下,

Observable<TestEntity> getApkVersson2(Observable<TestEntity> obs,EvictProvider evictProvider, DynamicKey dynamicKey);//EvictProvider表示是否强制刷新,下拉刷新时就需要强制刷新,DynamicKey表示要缓存第几页数据,当不传此参数时默认缓存第一页

传值方式为 new EvictProvider(true/false),new DynamicKey(page)
还有一点就是为什么使用Gson来解析,因为有GsonFormate工具。使用非常方便, 开发必备,还没使用的赶紧去上手。操作效果如下:
这里写图片描述

当然,上面介绍的内容不复杂,但很实用,还有很多额外的东西没有介绍,我之所以没有介绍,是因为我项目中没有用到,项目中用到的知识点都介绍了。我想,如果我不加区分的把所有东西都介绍一遍,不仅文章会显得又臭又长,还让人抓不住重点,那样做又与官方的API文档有什么分别呢?这也是很多人写博客的一个通病,其实我也有,但我会有意识的避免。

过程中遇到的问题1:在没有网的情况下点击第一个按钮会报错并且崩溃

resHttp: <-- HTTP FAILED: java.net.UnknownHostException: Unable to resolve host "api.github.com": No address associated with hostnameio.reactivex.exceptions.OnErrorNotImplementedException: Unable to resolve host "api.github.com": No address associated with hostname```

而且配置文件已经配置网络权限<uses-permission android:name="android.permission.INTERNET" />,上网搜了一下也没解决,不是说没有配置权限,就是说模拟器有问题,重启模拟器,还有的说是服务器有问题,还是只能靠自己。仔细看错误内容,其中OnErrorNotImplementedException提示说该异常主要是指OnError方法没有实现,OnError方法是哪里的呢。依稀记得重写观察者Observer时要重写OnNext(),OnComplete(),OnError()等方法,对,就是这个OnError()。实现它就不报错了。

MyApiProvider myApiProvider= MyApplication.getInstance().getRetrofit("https://api.github.com/")//一定以  /  结尾                .create(MyApiProvider.class);        myApiProvider.getApkVersson().subscribeOn(Schedulers.io())//指定在子线程进行耗时处理                .observeOn(AndroidSchedulers.mainThread())//指定在UI线程更新UI        .subscribe(new Consumer<TestEntity>() {//指定按照TestEntity解析            @Override            public void accept(TestEntity testEntity) throws Exception {//TestEntity,这里是解析后的结果                //处理返回的结果                Toast.makeText(MainActivity.this,testEntity.getAuthorizations_url(),Toast.LENGTH_LONG).show();            }        });

修改后的代码为

MyApiProvider myApiProvider= MyApplication.getInstance().getRetrofit("http://api.github.com/")//一定以  /  结尾                .create(MyApiProvider.class);        myApiProvider.getApkVersson().subscribeOn(Schedulers.io())//指定在子线程进行耗时处理                .observeOn(AndroidSchedulers.mainThread())//指定在UI线程更新UI        .subscribe(new Observer<TestEntity>() {//new Observer()得到观察者,并订阅被观察者,调用时顺序相反            @Override            public void onSubscribe(Disposable d) {            }            @Override            public void onNext(TestEntity testEntity) {                Toast.makeText(MainActivity.this,"NoCache:"+testEntity.getAuthorizations_url(),Toast.LENGTH_LONG).show();            }            @Override            public void onError(Throwable e) {                Toast.makeText(MainActivity.this,"NoCacheError:"+e.toString(),Toast.LENGTH_LONG).show();            }            @Override            public void onComplete() {            }        });

也就是将new Consumer<TestEntity>() 后面的内容替换掉,不能偷懒只重写OnNext()方法,如果要考虑异常的话,还有重写OnError()方法。

过程中遇到的问题2:在没有网的情况下,第一次启动app,先点击第二个按钮会报错并且崩溃,如果点击了第一个按钮,再点击第二个按钮则没问题。
其实这与问题1是同一个问题但是表现不一样,没有缓存时点击缓存按钮则去网络请求,此时没有网,就出现问题1的场景,而先点击按钮1后,数据被缓存了,再点击缓存按钮就去取缓存没有去网络请求,所以不报错。嗯,就是这样。

修改后的运行效果
这里写图片描述

源码下载地址

原创粉丝点击