安卓開發進階之RxJava在實際項目中使用--第二篇

關於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 hostname
io.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後,數據被緩存了,再點擊緩存按鈕就去取緩存沒有去網絡請求,所以不報錯。嗯,就是這樣。

修改後的運行效果
這裏寫圖片描述

源碼下載地址

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章