安卓 RxJava 和 Retrofit

在本教程中,我们将在安卓应用程序中使用Retrofit实现RxJava调用。我们将创建一个应用程序,该应用程序使用Retrofit和RxJava填充RecEconerView。我们将使用加密货币API。

你会学到什么?

  • 使用RxJava进行Retrofit呼叫。
  • 使用RxJava进行多个Retrofit呼叫
  • 使用RxJava转换Retrofit POJO响应

我们将在Android应用程序中使用Java 8来释放lambda表达式。

概述

Retrofit是一个REST客户端,它使用OkHttp作为HttpClient和JSON解析器来解析响应。我们将使用gson作为JSON解析器。以下是如何创建Retrofit实例:

 1HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
 2        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
 3        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
 4
 5        Gson gson = new GsonBuilder()
 6                .setLenient()
 7                .create();
 8
 9        Retrofit retrofit = new Retrofit.Builder()
10                .baseUrl(BASE_URL)
11                .client(client)
12                .addConverterFactory(GsonConverterFactory.create(gson))
13                .build();

HttpLoggingInterceptor用于记录网络调用过程中的数据。RxJava是一个以流的形式用于异步和反应式编程的库。我们在RxJava中使用不同的线程。用于网络调用的后台线程和用于更新UI的主线程。RxJava中的调度器负责使用不同的线程执行操作。RxAndroid 是RxJava的扩展,它包含了要在Android环境中使用的Android线程。要在改造环境中使用RxJava,我们只需要做两个主要更改:

  • 在Retrofit Builder中添加RxJava。
  • 使用界面中的Observable类型,而不是Call

要执行多个调用或转换响应,我们使用[RxJava operators](/community/tutorials/rxjava-operators)。让我们通过下面的示例应用程序来看看它是如何完成的。

项目结构

android-rxjava-retrofit-project-structure在我们的build.gradle文件中添加了以下依赖项:

 1implementation 'com.android.support:cardview-v7:27.1.0'
 2    implementation 'com.android.support:design:27.1.0'
 3    implementation('com.squareup.retrofit2:retrofit:2.3.0')
 4            {
 5                exclude module: 'okhttp'
 6            }
 7
 8    implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
 9    implementation 'io.reactivex.rxjava2:rxjava:2.1.9'
10    implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
11    implementation 'io.reactivex.rxjava2:rxandroid:2.0.1'
12    implementation 'com.squareup.okhttp3:logging-interceptor:3.9.1'

代码

布局activity_main.xml的代码如下所示。

 1<?xml version="1.0" encoding="utf-8"?>
 2<android.support.constraint.ConstraintLayout xmlns:android="https://schemas.android.com/apk/res/android"
 3    xmlns:tools="https://schemas.android.com/tools"
 4    android:layout_width="match_parent"
 5    android:layout_height="match_parent"
 6    tools:context=".MainActivity">
 7
 8    <android.support.v7.widget.RecyclerView
 9        android:id="@+id/recyclerView"
10        android:layout_width="match_parent"
11        android:layout_height="match_parent" />
12
13</android.support.constraint.ConstraintLayout>

CryptoCurrencyService.java类的代码如下所示。

 1package com.journaldev.rxjavaretrofit;
 2
 3import com.journaldev.rxjavaretrofit.pojo.Crypto;
 4
 5import io.reactivex.Observable;
 6import retrofit2.http.GET;
 7import retrofit2.http.Path;
 8
 9public interface CryptocurrencyService {
10
11    String BASE_URL = "https://api.cryptonator.com/api/full/";
12
13    @GET("{coin}-usd")
14    Observable<Crypto> getCoinData(@Path("coin") String coin);
15}

@Path将我们指定的路径传递到花括号中。注意 :@Path参数名称必须与@Get中的匹配。POJO类Crypto.java如下:

 1package com.journaldev.rxjavaretrofit.pojo;
 2
 3import com.google.gson.annotations.SerializedName;
 4import java.util.List;
 5
 6public class Crypto {
 7
 8    @SerializedName("ticker")
 9    public Ticker ticker;
10    @SerializedName("timestamp")
11    public Integer timestamp;
12    @SerializedName("success")
13    public Boolean success;
14    @SerializedName("error")
15    public String error;
16
17    public class Market {
18
19        @SerializedName("market")
20        public String market;
21        @SerializedName("price")
22        public String price;
23        @SerializedName("volume")
24        public Float volume;
25
26        public String coinName;
27
28    }
29
30    public class Ticker {
31
32        @SerializedName("base")
33        public String base;
34        @SerializedName("target")
35        public String target;
36        @SerializedName("price")
37        public String price;
38        @SerializedName("volume")
39        public String volume;
40        @SerializedName("change")
41        public String change;
42        @SerializedName("markets")
43        public List<Market> markets = null;
44
45    }
46}

‘coinName’是我们设置的一个字段。使用RxJava的魔力,我们将在该字段上设置一个值来转换响应。

使用RxJava创建单次调用

1CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);
2
3Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");
4cryptoObservable.subscribeOn(Schedulers.io())
5.observeOn(AndroidSchedulers.mainThread())
6.map(result -> result.ticker)
7.subscribe(this::handleResults, this::handleError);

scribeOn()创建一个Scheduler线程,我们在该线程上执行网络调用。我们可以在其中传递以下任一调度器。

  • trampoline():在当前线程上运行任务。因此它将在线程上的当前任务完成后运行您的代码。对执行任务很有用。
  • newThread():创建并返回一个线程,该线程为每个工作单元创建一个新的线程。这是昂贵的,因为它每次创建一个单独的线程。
  • calculation():创建并返回一个用于计算工作的对象。这应该用于并行工作,因为线程池是绑定的。I/O操作不应该在这里进行。
  • io():创建并返回一个用于IO绑定工作的对象。它也像计算一样有界。这通常用于网络呼叫。

订阅打开()与观察打开()

  • SubscribeOn在下游和上游工作。它上面和下面的所有任务都将使用相同的线程。
  • ObserveOn仅在下游工作。
  • 连续的SubscribeOn方法不会更改线程。将只使用第一个订阅线程。
  • 连续的serveOn方法将更改线程。
  • 在serveOn()之后,放入一个SUBSCRIBE On()不会改变线程。因此,ObserveOn通常应该在订阅On之后。

androidSchedulers.mainThread()是RxAndroid的一部分,只用于观察主线程上的数据。订阅方法是触发改进调用并在方法handleResults中获取数据的方法,我们将很快看到这一点。

多个呼叫

我们使用RxJava操作符merge一个接一个地执行两个改造调用。

1Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc");
2
3Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth");
4
5Observable.merge(btcObservable, ethObservable)
6                .subscribeOn(Schedulers.computation())
7                .observeOn(AndroidSchedulers.mainThread())
8                .subscribe(this::handleResults, this::handleError);

转换响应

要转换POJO响应,我们可以执行以下操作:

 1Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")
 2                .map(result -> Observable.fromIterable(result.ticker.markets))
 3                .flatMap(x -> x).filter(y -> {
 4                    y.coinName = "btc";
 5                    return true;
 6                }).toList().toObservable();
 7
 8        Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")
 9                .map(result -> Observable.fromIterable(result.ticker.markets))
10                .flatMap(x -> x).filter(y -> {
11                    y.coinName = "eth";
12                    return true;
13                }).toList().toObservable();
14
15        Observable.merge(btcObservable, ethObservable)
16                .subscribeOn(Schedulers.computation())
17                .observeOn(AndroidSchedulers.mainThread())
18                .subscribe(this::handleResults, this::handleError);

我们使用Observable.FromIterable将地图结果转换为可见流。flatMap‘逐个对元素进行操作。从而将ArrayList转换为单个单数元素。在filter方法中,我们更改响应。toList()用于将flatMap的结果转换回列表。toObservable()`将它们包装为可观察的流。

MainActivity.Java

MainActivity.Java类的代码如下所示:

  1package com.journaldev.rxjavaretrofit;
  2
  3import android.support.v7.app.AppCompatActivity;
  4import android.os.Bundle;
  5import android.support.v7.widget.LinearLayoutManager;
  6import android.support.v7.widget.RecyclerView;
  7import android.util.Log;
  8import android.widget.Toast;
  9
 10import com.google.gson.Gson;
 11import com.google.gson.GsonBuilder;
 12import com.journaldev.rxjavaretrofit.pojo.Crypto;
 13
 14import java.util.List;
 15
 16import io.reactivex.Observable;
 17import io.reactivex.android.schedulers.AndroidSchedulers;
 18import io.reactivex.schedulers.Schedulers;
 19import okhttp3.OkHttpClient;
 20import okhttp3.logging.HttpLoggingInterceptor;
 21import retrofit2.Retrofit;
 22import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory;
 23import retrofit2.converter.gson.GsonConverterFactory;
 24
 25import static com.journaldev.rxjavaretrofit.CryptocurrencyService.BASE_URL;
 26
 27public class MainActivity extends AppCompatActivity {
 28
 29    RecyclerView recyclerView;
 30    Retrofit retrofit;
 31    RecyclerViewAdapter recyclerViewAdapter;
 32
 33    @Override
 34    protected void onCreate(Bundle savedInstanceState) {
 35        super.onCreate(savedInstanceState);
 36        setContentView(R.layout.activity_main);
 37
 38        recyclerView = findViewById(R.id.recyclerView);
 39        recyclerView.setLayoutManager(new LinearLayoutManager(this));
 40        recyclerViewAdapter = new RecyclerViewAdapter();
 41        recyclerView.setAdapter(recyclerViewAdapter);
 42
 43        HttpLoggingInterceptor interceptor = new HttpLoggingInterceptor();
 44        interceptor.setLevel(HttpLoggingInterceptor.Level.BODY);
 45        OkHttpClient client = new OkHttpClient.Builder().addInterceptor(interceptor).build();
 46
 47        Gson gson = new GsonBuilder()
 48                .setLenient()
 49                .create();
 50
 51        retrofit = new Retrofit.Builder()
 52                .baseUrl(BASE_URL)
 53                .client(client)
 54                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
 55                .addConverterFactory(GsonConverterFactory.create(gson))
 56                .build();
 57
 58        callEndpoints();
 59    }
 60
 61    private void callEndpoints() {
 62
 63        CryptocurrencyService cryptocurrencyService = retrofit.create(CryptocurrencyService.class);
 64
 65        //Single call
 66        /*Observable<Crypto> cryptoObservable = cryptocurrencyService.getCoinData("btc");
 67        cryptoObservable.subscribeOn(Schedulers.newThread()).observeOn(AndroidSchedulers.mainThread()).map(result -> result.ticker).subscribe(this::handleResults, this::handleError);*/
 68
 69        Observable<List<Crypto.Market>> btcObservable = cryptocurrencyService.getCoinData("btc")
 70                .map(result -> Observable.fromIterable(result.ticker.markets))
 71                .flatMap(x -> x).filter(y -> {
 72                    y.coinName = "btc";
 73                    return true;
 74                }).toList().toObservable();
 75
 76        Observable<List<Crypto.Market>> ethObservable = cryptocurrencyService.getCoinData("eth")
 77                .map(result -> Observable.fromIterable(result.ticker.markets))
 78                .flatMap(x -> x).filter(y -> {
 79                    y.coinName = "eth";
 80                    return true;
 81                }).toList().toObservable();
 82
 83        Observable.merge(btcObservable, ethObservable)
 84                .subscribeOn(Schedulers.computation())
 85                .observeOn(AndroidSchedulers.mainThread())
 86                .subscribe(this::handleResults, this::handleError);
 87
 88    }
 89
 90    private void handleResults(List<Crypto.Market> marketList) {
 91        if (marketList != null && marketList.size() != 0) {
 92            recyclerViewAdapter.setData(marketList);
 93
 94        } else {
 95            Toast.makeText(this, "NO RESULTS FOUND",
 96                    Toast.LENGTH_LONG).show();
 97        }
 98    }
 99
100    private void handleError(Throwable t) {
101
102        Toast.makeText(this, "ERROR IN FETCHING API RESPONSE. Try again",
103                Toast.LENGTH_LONG).show();
104    }
105
106}

handleResultshandleError是通过Java 8调用::来调用的。在handlResults中,我们将转换后的响应设置在Recle erViewAdapter上。如果响应有错误,则调用handleError()。Rececumerview_Item_Layout布局的代码如下所示。

 1<?xml version="1.0" encoding="utf-8"?>
 2<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
 3    xmlns:app="https://schemas.android.com/apk/res-auto"
 4    android:layout_width="match_parent"
 5    android:layout_height="wrap_content">
 6
 7    <android.support.v7.widget.CardView
 8        android:id="@+id/cardView"
 9        android:layout_width="match_parent"
10        android:layout_height="wrap_content"
11        android:layout_gravity="center"
12        android:layout_margin="16dp">
13
14        <android.support.constraint.ConstraintLayout
15            android:layout_width="match_parent"
16            android:layout_height="wrap_content"
17            android:padding="8dp">
18
19            <TextView
20                android:id="@+id/txtCoin"
21                android:layout_width="wrap_content"
22                android:layout_height="wrap_content"
23                android:layout_marginLeft="8dp"
24                android:layout_marginRight="8dp"
25                android:layout_marginTop="8dp"
26                android:textAllCaps="true"
27                android:textColor="@android:color/black"
28                app:layout_constraintHorizontal_bias="0.023"
29                app:layout_constraintLeft_toLeftOf="parent"
30                app:layout_constraintRight_toRightOf="parent"
31                app:layout_constraintTop_toTopOf="parent" />
32
33            <TextView
34                android:id="@+id/txtMarket"
35                android:layout_width="wrap_content"
36                android:layout_height="wrap_content"
37                android:layout_marginLeft="8dp"
38                android:layout_marginRight="8dp"
39                android:layout_marginTop="8dp"
40                app:layout_constraintHorizontal_bias="0.025"
41                app:layout_constraintLeft_toLeftOf="parent"
42                app:layout_constraintRight_toRightOf="parent"
43                app:layout_constraintTop_toBottomOf="@+id/txtCoin" />
44
45            <TextView
46                android:id="@+id/txtPrice"
47                android:layout_width="wrap_content"
48                android:layout_height="wrap_content"
49                android:layout_marginLeft="8dp"
50                android:layout_marginStart="8dp"
51                android:layout_marginTop="8dp"
52                app:layout_constraintHorizontal_bias="0.025"
53                app:layout_constraintLeft_toLeftOf="parent"
54                app:layout_constraintRight_toRightOf="parent"
55                app:layout_constraintTop_toBottomOf="@+id/txtMarket" />
56
57        </android.support.constraint.ConstraintLayout>
58    </android.support.v7.widget.CardView>
59</LinearLayout>

下面给出了RecillerViewAdapter.Java类的代码:

 1package com.journaldev.rxjavaretrofit;
 2
 3import android.graphics.Color;
 4import android.support.v4.content.ContextCompat;
 5import android.support.v7.widget.CardView;
 6import android.support.v7.widget.RecyclerView;
 7import android.view.LayoutInflater;
 8import android.view.View;
 9import android.view.ViewGroup;
10import android.widget.TextView;
11
12import com.journaldev.rxjavaretrofit.pojo.Crypto;
13
14import java.util.ArrayList;
15import java.util.List;
16
17public class RecyclerViewAdapter extends RecyclerView.Adapter<RecyclerViewAdapter.ViewHolder> {
18
19    private List<Crypto.Market> marketList;
20
21    public RecyclerViewAdapter() {
22        marketList = new ArrayList<>();
23    }
24
25    @Override
26    public RecyclerViewAdapter.ViewHolder onCreateViewHolder(ViewGroup parent,
27                                                             int viewType) {
28
29        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.recyclerview_item_layout, parent, false);
30
31        RecyclerViewAdapter.ViewHolder viewHolder = new RecyclerViewAdapter.ViewHolder(view);
32        return viewHolder;
33    }
34
35    @Override
36    public void onBindViewHolder(RecyclerViewAdapter.ViewHolder holder, int position) {
37        Crypto.Market market = marketList.get(position);
38        holder.txtCoin.setText(market.coinName);
39        holder.txtMarket.setText(market.market);
40        holder.txtPrice.setText("$" + String.format("%.2f", Double.parseDouble(market.price)));
41        if (market.coinName.equalsIgnoreCase("eth")) {
42            holder.cardView.setCardBackgroundColor(Color.GRAY);
43        } else {
44            holder.cardView.setCardBackgroundColor(Color.GREEN);
45        }
46    }
47
48    @Override
49    public int getItemCount() {
50        return marketList.size();
51    }
52
53    public void setData(List<Crypto.Market> data) {
54        this.marketList.addAll(data);
55        notifyDataSetChanged();
56    }
57
58    public class ViewHolder extends RecyclerView.ViewHolder {
59
60        public TextView txtCoin;
61        public TextView txtMarket;
62        public TextView txtPrice;
63        public CardView cardView;
64
65        public ViewHolder(View view) {
66            super(view);
67
68            txtCoin = view.findViewById(R.id.txtCoin);
69            txtMarket = view.findViewById(R.id.txtMarket);
70            txtPrice = view.findViewById(R.id.txtPrice);
71            cardView = view.findViewById(R.id.cardView);
72        }
73    }
74}

上述实际应用程序的输出如下所示:android-rxjava-retrofit-output上述输出合并了通过改造完成的比特币和以太市场价格的结果。本教程到此结束。您可以从下面的链接下载Android RxJavaRetrofit项目。

RxJavaRetrophit

Published At
Categories with 技术
Tagged with
comments powered by Disqus