Android RxJava和Retrofit
在本教程中,我们将在Android应用中使用RxJava实现Retrofit调用。
我们将创建一个使用Retrofit和RxJava填充RecyclerView的应用程序。
我们将使用CryptoCurrency API。
我们将在Android应用程序中使用Java 8释放lambda表达式。
说明
Retrofit是一个REST客户端,它使用OkHttp作为HttpClient和JSON解析器来解析响应。
其中我们将gson用作JSON解析器。
这是创建Retrofit实例的方式:
HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); Gson gson = new GsonBuilder() .setLenient() .create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create(gson)) .build();
HttpLoggingInterceptor用于在网络调用期间记录数据。
RxJava是一个用于以流形式进行异步和反应式编程的库。
我们在RxJava中使用不同的线程。
网络调用的后台线程和用于更新UI的主线程。
RxJava中的调度程序负责使用不同的线程执行操作。
RxAndroid是RxJava的扩展,它包含要在Android环境中使用的Android线程。
要在改造环境中使用RxJava,我们只需做两个主要更改:
在Retrofit Builder中添加RxJava。
在界面中使用
Observable
类型而不是Call
要进行多个调用或者转换响应,我们使用RxJava运算符。
让我们通过下面的示例应用程序来了解它是如何完成的。
项目结构
在我们的build.gradle文件中添加以下依赖项:
implementation 'com.android.support:cardview-v7:27.1.0' implementation 'com.android.support:design:27.1.0' implementation('com.squareup.retrofit2:retrofit:2.3.0') { exclude module: 'okhttp' } implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.1.9' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'io.reactivex.rxjava2:rxandroid:2.0.1' implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'
代码
下面给出了布局activity_main.xml的代码。
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:tools="https://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context=".MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/recyclerView" android:layout_width="match_parent" android:layout_height="match_parent" </android.support.constraint.ConstraintLayout>
下面给出了CryptocurrencyService.java
类的代码。
package com.theitroad.rxjavaretrofit; import com.theitroad.rxjavaretrofit.pojo.Crypto; import io.reactivex.Observable; import retrofit2.http.GET; import retrofit2.http.Path; public interface CryptocurrencyService { String BASE_URL = "https://api.cryptonator.com/api/full/"; @GET("{coin}-usd") Observable<Crypto> getCoinData(@Path("coin") String coin); }
@Path将我们指定的路径传递到花括号中。
注意:@Path参数名称必须与@GET中的名称匹配。
POJO类Crypto.java
给出如下:
package com.theitroad.rxjavaretrofit.pojo; import com.google.gson.annotations.SerializedName; import java.util.List; public class Crypto { @SerializedName("ticker") public Ticker ticker; @SerializedName("timestamp") public Integer timestamp; @SerializedName("success") public Boolean success; @SerializedName("error") public String error; public class Market { @SerializedName("market") public String market; @SerializedName("price") public String price; @SerializedName("volume") public Float volume; public String coinName; } public class Ticker { @SerializedName("base") public String base; @SerializedName("target") public String target; @SerializedName("price") public String price; @SerializedName("volume") public String volume; @SerializedName("change") public String change; @SerializedName("markets") public List<Market> markets = null; } }
" coinName"是我们设置的字段。
借助RxJava的魔力,我们将在该字段上设置一个值以转换响应。
使用RxJava创建单个呼叫
CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class); Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc"); cryptoObservable.subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .map(result -> result.ticker) .subscribe(this::handleResults, this::handleError);
subscribeOn()创建一个Scheduler线程,通过它我们进行网络调用。
我们可以其中传递以下任何调度程序。
trampoline():这将在当前线程上运行任务。
因此,它将在线程上的当前任务完成后运行您的代码。
对排队操作很有用。newThread():创建并返回一个Scheduler,该Scheduler为每个工作单元创建一个新的Thread。
这是昂贵的,因为它每次都会创建一个单独的线程。Calculation():创建并返回一个计划程序,用于计算工作。
由于线程池已绑定,因此应将其用于并行工作。
I/O操作不应该在这里完成。io():创建并返回用于IO绑定工作的Scheduler。
同样,它的边界像计算一样。
通常,它用于网络呼叫。
subscriptionOn()与observeOn()
subscriptionOn在下游和上游工作。
它上面和下面的所有任务都将使用同一线程。watchOn仅在下游工作。
连续的subscriptionOn方法不会更改线程。
仅使用第一个subscriptionOn线程。连续的watchOn方法将更改线程。
经过observeOn()之后,放置subscribeOn()不会更改线程。
因此,observeOn通常应在subscribeOn之后。
AndroidSchedulers.mainThread()是RxAndroid的一部分,仅用于观察主线程上的数据。
订阅方法是触发改造调用并在" handleResults"方法中获取数据的方法,我们稍后会看到。
多个通话
我们使用RxJava运算符merge
一个接一个地进行两个改造。
Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc"); Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth"); Observable.merge(btcObservable, ethObservable) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::handleResults, this::handleError);
转变回应
要转换POJO响应,我们可以执行以下操作:
Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "btc"; return true; }).toList().toObservable(); Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "eth"; return true; }).toList().toObservable(); Observable.merge(btcObservable, ethObservable) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::handleResults, this::handleError);
我们使用Observable.fromIterable
将地图结果转换为Observable流。flatMap
一一对应于元素。
因此将ArrayList转换为单个奇异元素。
在filter
方法中,我们更改响应。
toList()用于将flatMap的结果转换回List。
toObservable()将它们包装为Observable流。
MainActivity.java
MainActivity.java类的代码如下:
package com.theitroad.rxjavaretrofit; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.util.Log; import android.widget.Toast; import com.google.gson.Gson; import com.google.gson.GsonBuilder; import com.theitroad.rxjavaretrofit.pojo.Crypto; import java.util.List; import io.reactivex.Observable; import io.reactivex.android.schedulers.AndroidSchedulers; import io.reactivex.schedulers.Schedulers; import okhttp3.OkHttpClient; import okhttp3.logging.HttpLoggingInterceptor; import retrofit2.Retrofit; import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory; import retrofit2.converter.gson.GsonConverterFactory; import static com.theitroad.rxjavaretrofit.CryptocurrencyService.BASE_URL; public class MainActivity extends AppCompatActivity { RecyclerView recyclerView; Retrofit retrofit; RecyclerViewAdapter recyclerViewAdapter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView = findViewById(R.id.recyclerView); recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerViewAdapter = new RecyclerViewAdapter(); recyclerView.setAdapter(recyclerViewAdapter); HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor(); interceptor.setLevel(HttpLoggingInterceptor.Level.BODY); OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build(); Gson gson = new GsonBuilder() .setLenient() .create(); retrofit = new Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); callEndpoints(); } private void callEndpoints() { CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class); //Single call /*Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc"); cryptoObservable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).map(result -> result.ticker).subscribe(this::handleResults, this::handleError);*/ Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "btc"; return true; }).toList().toObservable(); Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth") .map(result -> Observable.fromIterable(result.ticker.markets)) .flatMap(x -> x).filter(y -> { y.coinName = "eth"; return true; }).toList().toObservable(); Observable.merge(btcObservable, ethObservable) .subscribeOn(Schedulers.computation()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(this::handleResults, this::handleError); } private void handleResults(List<Crypto.Market> marketList) { if (marketList != null && marketList.size() != 0) { recyclerViewAdapter.setData(marketList); } else { Toast.makeText(this, "NO RESULTS FOUND", Toast.LENGTH_LONG).show(); } } private void handleError(Throwable t) { Toast.makeText(this, "ERROR IN FETCHING API RESPONSE. Try again", Toast.LENGTH_LONG).show(); } }
使用Java 8调用符::
来调用handleResults
和handleError
。
在handlResults中,我们在ReyclerViewAdapter上设置了转换后的响应。
如果响应有错误,则调用handleError()。
下面给出了recyclerview_item_layout布局的代码。
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="https://schemas.android.com/apk/res/android" xmlns:app="https://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content"> <android.support.v7.widget.CardView android:id="@+id/cardView" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="center" android:layout_margin="16dp"> <android.support.constraint.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:padding="8dp"> <TextView android:id="@+id/txtCoin" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" android:textAllCaps="true" android:textColor="@android:color/black" app:layout_constraintHorizontal_bias="0.023" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toTopOf="parent" <TextView android:id="@+id/txtMarket" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginRight="8dp" android:layout_marginTop="8dp" app:layout_constraintHorizontal_bias="0.025" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/txtCoin" <TextView android:id="@+id/txtPrice" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginLeft="8dp" android:layout_marginStart="8dp" android:layout_marginTop="8dp" app:layout_constraintHorizontal_bias="0.025" app:layout_constraintLeft_toLeftOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintTop_toBottomOf="@+id/txtMarket" </android.support.constraint.ConstraintLayout> </android.support.v7.widget.CardView> </LinearLayout>
下面给出了RecyclerViewAdapter.java类的代码:
package com.theitroad.rxjavaretrofit; import android.graphics.Color; import android.support.v4.content.ContextCompat; import android.support.v7.widget.CardView; import android.support.v7.widget.RecyclerView; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; import android.widget.TextView; import com.theitroad.rxjavaretrofit.pojo.Crypto; import java.util.ArrayList; import java.util.List; public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> { private List<Crypto.Market> marketList; public RecyclerViewAdapter() { marketList = new ArrayList<>(); } @Override public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_layout, parent, false); RecyclerViewAdapter.ViewHolder viewHolder = new RecyclerViewAdapter.ViewHolder(view); return viewHolder; } @Override public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) { Crypto.Market market = marketList.get(position); holder.txtCoin.setText(market.coinName); holder.txtMarket.setText(market.market); holder.txtPrice.setText("$" + String.format("%.2f", Double.parseDouble(market.price))); if (market.coinName.equalsIgnoreCase("eth")) { holder.cardView.setCardBackgroundColor(Color.GRAY); } else { holder.cardView.setCardBackgroundColor(Color.GREEN); } } @Override public int getItemCount() { return marketList.size(); } public void setData(List<Crypto.Market> data) { this.marketList.addAll(data); notifyDataSetChanged(); } public class ViewHolder extends RecyclerView.ViewHolder { public TextView txtCoin; public TextView txtMarket; public TextView txtPrice; public CardView cardView; public ViewHolder(View view) { super(view); txtCoin = view.findViewById(R.id.txtCoin); txtMarket = view.findViewById(R.id.txtMarket); txtPrice = view.findViewById(R.id.txtPrice); cardView = view.findViewById(R.id.cardView); } } }