Android开发实战:RxJava与Retrofit完美结合的网络请求处理指南

这是文章《Android的RxJava和Retrofit》的第1部分(共3部分)。

内容片段:在这个教程中,我们将在Android应用中使用RxJava来实现Retrofit调用。我们将创建一个使用Retrofit和RxJava来填充RecyclerView的应用程序。我们将使用一个加密货币API。

你将学到什么?

  • 使用RxJava进行Retrofit调用
  • 使用RxJava进行多个Retrofit调用
  • 使用RxJava转换Retrofit的POJO响应

我们的安卓应用将使用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中,Schedulers负责使用不同的线程执行操作。RxAndroid是RxJava的扩展,包含了用于在Android环境中使用的Android线程。在Retrofit环境中使用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.Olivia.rxjavaretrofit;

import com.Olivia.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.Olivia.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() 创建一个调度线程,用于进行网络请求。我们可以在其中传递以下任何调度器。

  • trampoline(): 在当前线程上运行任务。因此它会在当前线程上的任务完成后运行你的代码。对于排队操作很有用。
  • newThread(): 创建并返回一个为每个工作单元创建新线程的调度器。由于每次都创建单独的线程,这是很耗费资源的。
  • computation(): 创建并返回一个用于计算工作的调度器。由于线程池是受限制的,应该用于并行工作。不应该在这里执行I/O操作。
  • io(): 创建并返回一个用于I/O绑定工作的调度器。它像computation一样是受限制的。通常用于网络调用。

subscribeOn()和observeOn()的区别

  • subscribeOn 在上游和下游都起作用。它上方和下方的所有任务都将使用同一线程。
  • observeOn 仅在下游起作用。
  • 连续的 subscribeOn 方法不会改变线程。只有第一个 subscribeOn 线程会被使用。
  • 连续的 observeOn 方法会改变线程。
  • 在 observeOn() 之后放置 subscribeOn() 不会改变线程。因此 observeOn 通常应该放在 subscribeOn 之后。

AndroidSchedulers.mainThread() 是 RxAndroid 的一部分,用于仅在主线程上观察数据。subscribe 方法是触发 retrofit 调用并在 handleResults 方法中获取数据的方式,我们很快就会看到。

多个网络请求

我们使用 RxJava 操作符 merge 来依次执行两个 retrofit 调用。

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 将 map 结果转换为 Observable 流。flatMap 逐个操作元素。因此将 ArrayList 转换为单个元素。在 filter 方法中,我们改变了响应。toList() 用于将 flatMap 的结果转换回 List。toObservable() 将它们包装成 Observable 流。

主活动.java

这是文章《Android的RxJava和Retrofit》的第3部分(共3部分)。

以下给出了MainActivity.java类的代码:

package com.Olivia.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.Olivia.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.Olivia.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);

        //单一调用
        /*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, "未找到结果",
                    Toast.LENGTH_LONG).show();
        }
    }

    private void handleError(Throwable t) {

        Toast.makeText(this, "获取API响应时出错,请重试",
                Toast.LENGTH_LONG).show();
    }

}

在Java 8中使用::调用handleResults和handleError,handleResults中我们将转换后的响应设置在RecyclerViewAdapter上。如果响应中有错误,则调用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.Olivia.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.Olivia.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);
        }
    }
}

上述应用程序的输出如下:以上输出将通过Retrofit合并比特币和以太坊市场价格的结果。本教程到此结束。您可以通过下方链接下载Android RxJavaRetrofit项目。

RxJavaRetrofit 在中国的本地化翻译可能是:响应式Java和网络请求库

bannerAds