今天,移动应用程序使用 Web 应用程序编程接口 (api) 与远程服务器交换数据已经很常见了。
无论是使用gmail API检查新电子邮件、使用Ticketmaster API搜索本地事件,还是利用用户的Spotify历史推荐新音乐,API(通常是rest API)都提供了对大量信息的访问,您可以在您的应用程序中使用。
与远程服务器通信已成为现代移动开发的重要组成部分,以至于有 无数库致力于帮助您进行网络调用,但 Retrofit 是 android 使用最广泛的网络库之一。
在本文中,我将向您展示如何使用 Retrofit 和免费的Nomics Cryptocurrency & Bitcoin API从远程 API 检索数据。
检索到一些数据后,您会想好好利用它!在 Android 中,这通常涉及将数据转发到 Android 最重要的主 UI 线程,准备在应用程序的 UI 中显示,或者以某种方式转换数据,例如过滤或组合它。方便的是,您可以使用 Rxjava 库完成所有这些事情,因此我们将在项目中同时使用 Retrofit 和 RxJava。
在本文结束时,您将创建一个应用程序,该应用程序使用 Retrofit 从远程 API 检索数据,将该数据转换为 RxJava 的Observable
格式,然后使用选择的 RxJava和RxAndroid 运算符和调度程序来操作该数据。
我们将建造什么
那里不乏 API,但为了让事情变得简单,我们将使用免费的Nomics 加密货币和比特币 API来检索最后记录交易的加密货币列表及其市场价格。然后,我将使用 arecyclerview
在我们的应用程序中显示此信息。
要发出改造请求,我们需要创建以下内容:
改造类。 在这里您将创建一个 Retrofit 实例,添加一个转换器(我们将使用 Gson),然后指定您的应用应用于其所有 HTTP 请求的基本 URL。
一个数据类。 此类表示您应用的每个 API 调用的响应。
一个 API 接口。 这是您描述您想要提出的每个改造请求的地方。
在本教程结束时,您将拥有一个如下所示的项目:
为什么我应该使用 RxJava 进行 API 调用?
RxJava 是react iveX 库的开源、JVM 兼容实现,旨在帮助您以响应式编程风格处理异步数据流,而无需编写大量回调。
在你的 Android 项目中使用 RxJava 有很多好处,但其中一些最重要的包括:
简化的代码。RxJava 让您描述您想要实现的目标,而不是为您的应用程序编写一个指令列表来完成。这种方法通常会产生更简洁、更易于维护且不易出错的代码。
一致性。RxJava 可能是一个以数据为中心的库,但它对“数据”的定义非常广泛,包括变量、缓存、属性,甚至是点击和滑动等用户输入事件。由于 RxJava 将几乎所有内容都视为数据,因此它提供了一个一致的工作流程,您可以在许多不同的ios场景中跨应用程序使用。
多线程变得容易。现代移动应用程序必须能够执行多任务处理,但作为单线程环境,Android 并不是天生的多任务处理程序!Android 确实提供了一些用于创建额外线程的内置工具,但这些解决方案都不是特别容易使用的,而且它们会很快导致复杂的代码容易受到内存泄漏等性能问题的影响。通过将 RxJava 添加到您的项目中,您可以访问
subscribeOn
和observeOn
操作符,这使得创建和管理其他线程变得更加容易。复杂的数据转换。RxJava 拥有大量的操作符集合,您可以使用它们来修改、过滤、合并和转换您的 RxJava Observables 发出的数据。将运算符应用于 Observable 通常会返回另一个 Observable,因此如果您无法为您的项目找到完美的运算符,那么您可以继续将运算符应用于 Observable,直到获得您想要的结果。
有关在您的 Android 项目中使用 RxJava 的好处的更多信息,请查看我们的RxJava 2 入门文章。
使用 API 密钥进行身份验证
许多 API 要求您在 HTTP 请求中包含唯一的 API 密钥。这些密钥允许 API 背后的公司或个人跟踪与您的项目相关的请求,这对于强制执行使用限制或 API 的创建者只是想了解其 API 的使用方式非常方便。
在查询 Nomics 加密货币和比特币 API 时,您需要将唯一的 API 密钥合并到您的请求中,所以现在让我们生成这个密钥:
在您的网络浏览器中,前往Nomics 网站。
找到Get Free API Key按钮,然后单击它。
检查条款和条件,如果您愿意继续,请填写表格。
选择完成订单。片刻之后,您应该会收到一封包含您唯一 API 密钥的电子邮件。
创建您的应用程序:添加依赖项
使用您选择的设置创建一个新的 Android 应用程序,但在出现提示时,选择包括 kotlin 支持。
接下来,打开您的build.gradle 文件并添加我们将在整个项目中使用的所有库。除了 Retrofit 和 RxJava 2.0 之外,我们还需要以下内容:
1.RecyclerView
检索到我们的数据后,我们将其显示在一个列表中,使用RecyclerView
.
implementation 'com.android.support:recyclerview-v7:26.1.0'
2.Gson转换器
大多数情况下,服务器的响应将被映射为与语言无关的格式,例如 JSON,因此您的应用程序需要对 JSON 数据进行序列化和反序列化。开箱即用,Retrofit 只能将 HTTP 主体反序列化为 OkHttp 的ResponseBody
类型,但您可以通过将转换外包给独立转换器来支持其他类型。
有很多可用的转换器,但在本教程中,我将使用 Gson,它将 JSON 转换为等效的 Java 对象。
implementation 'com.squareup.retrofit2:converter-gson:2.3.0'
3. RxJava 适配器
要创建能够返回 RxJava 类型的接口方法,我们需要使用 RxJava 的 Retrofit 适配器。
implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0'
4. RxAndroid 库
RxAndroid 库为 RxJava 提供了一些特定于 Android 的绑定,尤其是AndroidSchedulers.mainThread
.
在 Android 中,您只能从主 UI 线程更新应用的 UI。如果您的用户要查看任何加密货币数据,那么您需要在某个时候切换到主 UI 线程。
observeOn
通过结合使用 RxJava 的操作符和 RxAndroid 的调度程序,您可以快速轻松地安排代码在 Android 的主 UI 线程上运行AndroidSchedulers.mainThread
:
.observeOn(AndroidSchedulers.mainThread())
要在您的项目中使用 RxAndroid,您需要以下项目依赖项:
implementation 'io.reactivex.rxjava2:rxandroid:2.0.2'
...但不是 RxKotlin
此时,您可能想知道为什么我们使用 Kotlin 进行编码,但使用 RxJava,因为有一个专用的 RxKotlin 库可用。
由于 Kotlin 与 Java 100% 互操作,因此大多数 Java 库都与 Kotlin 兼容,RxJava 也不例外!你可以在你的项目中使用 RxJava和RxKotlin,但为了让事情变得简单,我将在整个教程中坚持使用 RxJava。如果您有兴趣了解有关 RxKotlin 的更多信息,那么您会在我关于 使用 RxJava 和 RxKotlin 进行 Kotlin 反应式编程的文章中找到大量信息。
添加项目依赖项
添加所有这些项目依赖项后,您的build.gradle文件应如下所示:
dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation"org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version" implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support:recyclerview-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' implementation 'com.squareup.retrofit2:retrofit:2.3.0' implementation 'com.squareup.retrofit2:converter-gson:2.3.0' implementation 'com.squareup.retrofit2:adapter-rxjava2:2.3.0' implementation 'io.reactivex.rxjava2:rxjava:2.1.9' implementation 'io.reactivex.rxjava2:rxandroid:2.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
请求访问 Internet
由于我们的应用程序将与远程服务器通信,因此它需要访问 Internet 的权限。打开项目的Manifest并添加以下内容:
<?xml version="1.0" encoding="utf-8"?> https://schemas.android.com/apk/res/android" package="com.jessicathornsby.myapplication"> <uses-permission android:name="android.permission.INTERNET"/>
请注意,由于android.permission.INTERNET
被归类为安全权限,我们不需要在运行时请求它。
定义您的 HTTP 端点
接下来,我们需要创建一个接口来描述我们的 HTTP 请求:
单击确定。
每个请求都需要包含至少一个 HTTP 注释,指示应该如何处理该请求。Retrofit 支持所有标准请求类型的注释,但我们只会使用@GET
注释,因为我们正在从 服务器检索数据。
我们还需要描述端点,即我们从中检索数据的 URL。在我们的应用程序中,这是https://api.nomics.com/v1/markets/prices,它由一个基本 URL ( https://api.nomics.com/v1/ ) 和一个相对 URL (prices) 组成。我们将在项目的其他地方定义基本 URL,所以现在我们只需要担心相对 URL。
如果所讨论的 API 不需要 API 密钥,那么声明基本 URL 和相对 URL 就足够了,但是由于 Nomics API确实需要密钥,因此我们需要将此信息合并到我们的请求中。
此过程在 API 之间可能有所不同,因此您应始终参考您的 API 文档以获取更多信息。特别要注意标记为Authentication的任何部分,因为您通常会在此处找到与 API 密钥相关的说明。Nomics API 文档就是一个很好的例子,因为它有一个专门的身份验证部分,描述了您应该如何以以下格式合并您的密钥:
https://api.nomics.com/v1/prices?key=your-api-key-goes-here
结合@GET
注释、相对 URL 和您的 API 密钥,我们可以得到以下信息:
@GET("prices?key=your-api-key-goes-here")
我将使用 Kotlin 的函数获取API 数据。getData()
由于我们希望这个函数发出 RxJava Observables,我们需要将端点定义为有效的 RxJava 类型:
fun getData() : Observable<List<RetroCrypto>>
完成的界面如下:
import io.reactivex.Observable import retrofit2.http.GET interface GetData { //Describe the request type and the relative URL// @GET("prices?key=YOUR-API-KEY-HERE") fun getData() : Observable<List<RetroCrypto>> }
确保YOUR-API-KEY-HERE
用自己的密钥替换!
使用 Kotlin 的数据类创建模型
接下来,我们需要创建一个数据类来表示对每个 API 调用的响应:
从 Android Studio 工具栏中 选择文件>新建> Kotlin 文件/类。
将此类命名为 RetroCrypto,然后单击“确定” 。
打开你的新RetroCrypto类。
如果您以前使用 Java 执行过 Retrofit 调用,那么这是 Kotlin 比等效的 Java 更简洁的一个领域,这要归功于数据类。
在 Kotlin 中,数据类是专门为保存某些数据而设计的类。Kotlin 编译器会自动实现所需hashCode()
的equals()
和方法,因此您可以在一行代码 toString()
中创建数据模型类。
打开新的RetroCrypto文件并添加以下内容:
data class RetroCrypto(val currency : String, val price : String)
构建改造实例
要发送网络请求,我们需要使用Retrofit.Builder
该类来创建一个 Retrofit 实例,我们将在其中调用我们的端点并检索加密货币信息。
private fun loadData() { val requestInterface = Retrofit.Builder()
一旦我们构建了我们的 Retrofit 对象,我们需要:
设置基本 URL。在调用
build()
on 方法之前Retrofit.Builder
,您至少需要定义基本 URL。设置默认转换器,使用
addConverterFactory()
方法。设置 Gson 转换器后,它会自动为您翻译 JSON 响应。应用适配器。Retrofit 附带一个用于执行
Call
实例的默认适配器,但您可以在构建 Retrofit 实例时通过提供该适配器的一个实例来应用一个或多个附加适配器。要将 RxJava 与 Retrofit 一起使用,我们需要添加RxJava2CallAdapterFactory
调用适配器,使用.addCallAdapterFactory
.
这给了我们以下信息:
private fun loadData() { //Build a Retrofit object// val requestInterface = Retrofit.Builder() .baseUrl(BASE_URL) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Get a usable Retrofit object by calling .build()// .build().create(GetData::class.java)
默认情况下,Observable 在声明订阅的线程上发出其数据,在 Android 中通常是主 UI 线程。但是,为了获得最佳结果,您的应用程序应该只在 UI 线程上执行与 UI 相关的工作,因此我们将使用 RxJavasubscribeOn
和一个随附Scheduler
的线程来创建一个可以执行 Observable 的替代线程:
.subscribeOn(Schedulers.io())
接下来,我们将使用observeOn
操作符,加上 RxAndroid 的AndroidSchedulers.mainThread
调度程序将 Observable 的通知发送到 Android 的主 UI 线程,准备在我们的应用程序的 UI 中显示检索到的数据。
有关在 Kotlin 中订阅 Observables 的更详细信息,请查看我在 Kotlin Reactive Programming With RxJava 和 RxKotlin上的帖子。
在下面的代码片段中,我还使用该handleResponse()
方法来处理我们将收到的数据,在成功的网络操作之后:
myCompositeDisposable?.add(requestInterface.getData() .observeOn(AndroidSchedulers.mainThread()) .subscribeOn(Schedulers.io()) .subscribe(this::handleResponse)) }
上面的代码返回一个Disposable
——如果你以前有 RxJava 1.0 的经验,那么一次性用品本质上是 RxJava 2.0 的订阅版本。
完成一次性/订阅后,必须将其处理掉以避免内存泄漏。我们可以通过将我们所有的一次性用品添加到一个CompositeDisposable
可以容纳多个一次性用品的容器中来简化这个清理过程。在上面的代码中,我们正在初始化 RxJava 的CompositeDisposable
,然后CompositeDisposable
使用该add()
方法将每个一次性添加到 。
myCompositeDisposable?.add(requestInterface.getData()
然后,我们只需要清除CompositeDisposable
我们项目的onDestroy()
方法:
override fun onDestroy() { super.onDestroy() myCompositeDisposable?.clear() } }
这是完成的课程:
import android.support.v7.app.AppCompatActivity import android.os.Bundle import io.reactivex.disposables.CompositeDisposable import android.support.v7.widget.LinearLayoutManager import android.support.v7.widget.RecyclerView import android.widget.toast import retrofit2.converter.gson.GsonConverterFactory import retrofit2.adapter.rxjava2.RxJava2CallAdapterFactory import retrofit2.Retrofit import io.reactivex.schedulers.Schedulers import io.reactivex.android.schedulers.AndroidSchedulers import kotlinx.android.synthetic.main.activity_main.* class MainActivity : AppCompatActivity(), MyAdapter.Listener { private var myAdapter: MyAdapter? = null private var myCompositeDisposable: CompositeDisposable? = null private var myRetroCryptoArrayList: ArrayList<RetroCrypto>? = null private val BASE_URL = "https://api.nomics.com/v1/" override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) myCompositeDisposable = CompositeDisposable() initRecyclerView() loadData() } //Initialise the RecyclerView// private fun initRecyclerView() { //Use a layout manager to position your items to look like a standard ListView// val layoutManager : RecyclerView.LayoutManager = LinearLayoutManager(this) cryptocurrency_list.layoutManager = layoutManager } //Implement loadData// private fun loadData() { //Define the Retrofit request// val requestInterface = Retrofit.Builder() //Set the API’s base URL// .baseUrl(BASE_URL) //Specify the converter factory to use for serialization and deserialization// .addConverterFactory(GsonConverterFactory.create()) //Add a call adapter factory to support RxJava return types// .addCallAdapterFactory(RxJava2CallAdapterFactory.create()) //Build the Retrofit instance// .build().create(GetData::class.java) //Add all RxJava disposables to a CompositeDisposable// myCompositeDisposable?.add(requestInterface.getData() //Send the Observable’s notifications to the main UI thread// .observeOn(AndroidSchedulers.mainThread()) //Subscribe to the Observer away from the main UI thread// .subscribeOn(Schedulers.io()) .subscribe(this::handleResponse)) } private fun handleResponse(cryptoList: List<RetroCrypto>) { myRetroCryptoArrayList = ArrayList(cryptoList) myAdapter = MyAdapter(myRetroCryptoArrayList!!, this) //Set the adapter// cryptocurrency_list.adapter = myAdapter } override fun onItemClick(retroCrypto: RetroCrypto) { //If the user clicks on an item, then display a Toast// Toast.makeText(this, "You clicked: ${retroCrypto.currency}", Toast.LENGTH_LONG).show() } override fun onDestroy() { super.onDestroy() //Clear all your disposables// myCompositeDisposable?.clear() } }
显示加密货币 API 数据
此时,我们的应用程序可以成功地从 Nomics API 检索数据,因此我们的最终任务是通过 RecyclerView 向用户显示这些数据。
要实现 RecyclerView,我们需要以下内容:
一个
RecyclerView
小部件。RecyclerView
可用于显示其每个项目 的自定义 XML 布局。一个适配器,它将数据绑定到您的
RecyclerView
.
创建可滚动列表
让我们首先在我们的文件中添加一个RecyclerView
小部件:activity_main.xml
<?xml version="1.0" encoding="utf-8"?> http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.jessicathornsby.myapplication.MainActivity"> <android.support.v7.widget.RecyclerView android:id="@+id/cryptocurrency_list" android:layout_width="match_parent" android:layout_height="match_parent" app:layout_constraintTop_toTopOf="parent" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintRight_toRightOf="parent" app:layout_constraintLeft_toLeftOf="parent" /> </android.support.constraint.ConstraintLayout>
现在我们有了RecyclerView
小部件,我们需要为其中的每一行定义布局RecyclerView
。我将创建一个由两个TextView
s 组成的简单布局,我将在其中显示从 Nomics API 检索到的每种加密货币的名称和价格:
Control -单击项目的res/layout文件夹。
选择新建>布局资源文件。
将此文件命名为 row_layout,然后单击确定。
打开row_layout.xml
文件,添加以下内容:
<?xml version="1.0" encoding="utf-8"?> <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginBottom="2dp"> <TextView android:id="@+id/text_name" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="26sp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <TextView android:id="@+id/text_price" android:layout_width="match_parent" android:layout_height="wrap_content" android:textColor="@android:color/white" android:textSize="20sp" android:layout_marginTop="10dp" android:layout_marginBottom="10dp" android:layout_marginStart="10dp" android:layout_marginEnd="10dp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toBottomOf="@+id/text_name" /> </android.support.constraint.ConstraintLayout>
我们的 RecyclerView 现在将为列表中的每个项目使用此布局。
使用 Android 适配器绑定数据
适配器是负责将每个项目绑定View
到RecyclerView
.
要将底层数据模型连接到应用程序的 UI,您需要扩展RecyclerView.Adapter
,然后实现以下内容:
onCreateViewHolder()
. 这是我们指定R.layout.row_layout
RecyclerView 中每个项目应使用的布局 () 的地方,并使用LayoutInflater
.onBindViewHolder()
. 由 RecyclerView 调用,在指定位置显示数据。getItemCount()
. 返回数据集中存在的项目数。
使用 时Views
,通常可以使用 Kotlin Android 扩展来简化代码。这些扩展允许您直接访问布局Views
,方法是将它们作为“合成”属性导入到您的类中。例如,您可以row_layout
使用以下命令导入对文件中所有视图的引用:
import kotlinx.android.synthetic.main.row_layout.view.*
此时,您可以仅使用他们的 ID 访问所有row_layout
's Views
——而且findViewById()
看不到!例如,您可以text_name
使用以下命令更新小部件:
itemView.text_name.text = retroCrypto.currency
在这个类中,我还将数据作为 传递ArrayList
,以及Listener
用于处理用户输入事件的 。最后,为了让我们的应用看起来更有趣,我将定义一种Array
颜色,然后将它们用作我的RecyclerView
项目的背景。
创建一个名为的新 Kotlin 类MyAdapter
,然后添加以下内容:
import android.graphics.Color import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.support.v7.widget.RecyclerView import kotlinx.android.synthetic.main.row_layout.view.* //Pass the ArrayList and a listener, and add a variable to hold your data// class MyAdapter (private val cryptoList : ArrayList<RetroCrypto>, private val listener : //Extend RecyclerView.Adapter// Listener) : RecyclerView.Adapter<MyAdapter.ViewHolder>() { interface Listener { fun onItemClick(retroCrypto : RetroCrypto) } //Define an array of colours// private val colors : Array<String> = arrayOf("#7E57C2", "#42A5F5", "#26C6DA", "#66BB6A", "#FFEE58", "#FF7043" , "#EC407A" , "#d32f2f") //Bind the ViewHolder// override fun onBindViewHolder(holder: ViewHolder, position: Int) { //Pass the position where each item should be displayed// holder.bind(cryptoList[position], listener, colors, position) } //Check how many items you have to display// override fun getItemCount(): Int = cryptoList.count() override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.row_layout, parent, false) return ViewHolder(view) } //Create a ViewHolder class for your RecyclerView items// class ViewHolder(view : View) : RecyclerView.ViewHolder(view) { //Assign values from the data model, to their corresponding Views// fun bind(retroCrypto: RetroCrypto, listener: Listener, colors : Array<String>, position: Int) { //Listen for user input events// itemView.setOnClickListener{ listener.onItemClick(retroCrypto) } itemView.setbackgroundColor(Color.parseColor(colors[position % 8])) itemView.text_name.text = retroCrypto.currency itemView.text_price.text = retroCrypto.price } } }
测试你的改造和 RxJava 2.0 应用程序
现在是时候测试您的应用程序了!确保您有有效的 Internet 连接,然后在 Android 智能手机或平板电脑或 AVD(Android 虚拟设备)上安装您的应用程序。应用启动后,Retrofit 将从 Nomics API 检索所有可用数据,然后将其显示为RecyclerView
.
您还可以从我们的 GitHub 存储库下载已完成的项目。
结论
在本文中,我向您展示了如何使用 Retrofit 从 API 中检索数据,将数据转换为 RxJava 2.0 的 Observable 格式,然后以RecyclerView
.
- 为什么我应该使用 RxJava 进行 API 调用?
- 1.RecyclerView
- 2.Gson转换器
- 3. RxJava 适配器
- 4. RxAndroid 库
- ...但不是 RxKotlin
- 添加项目依赖项
- 请求访问 Internet
- 创建可滚动列表
- 使用 Android 适配器绑定数据