Android RxJava和Retrofit

时间:2020-02-23 14:29:14  来源:igfitidea点击:

在本教程中,我们将在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调用符::来调用handleResultshandleError
在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);
      }
  }
}