RxJava是用于将反应式编程引入Android平台的最受欢迎的库之一,在这个由三部分组成的系列文章中,我向您展示了如何在自己的Android项目中开始使用该库。
在创建适用于Android的RxJava 2入门中 ,我们研究了RxJava是什么以及它为Android开发人员所提供的东西,然后创建了一个Hello World应用程序,该应用程序演示了RxJava的三个核心组件:一个Observable
,一个Observer
和一个订阅。
在RxJava 2中的反应式编程运算符教程中,我们研究了如何使用运算符执行复杂的数据转换,以及如何结合使用Operator
和Scheduler
以最终使Android上的多线程体验轻松自如。
我们还谈到了RxAndroid,这是一个专门设计用来帮助您在Android项目中使用RxJava的库,但是在RxAndroid中还有很多值得探索的地方。 因此,在本文中,我将只专注于RxAndroid系列库。
与RxJava一样,RxAndroid在其版本2中进行了大修。 RxAndroid团队决定对库进行模块化,将其大部分功能移至专用的RxAndroid附加模块中。
在本文中,我将向您展示如何设置和使用一些最流行且功能最强大的RxAndroid模块-包括一个库,该库可以使您能够处理侦听器,处理程序和TextWatchers
,从而成为过去任何 Android UI事件均作为Observable
。
而且由于订阅不完整导致的内存泄漏是在Android应用中使用RxJava的最大缺点,因此我还将向您展示如何使用可以为您处理订阅过程的RxAndroid模块。 到本文结尾,您将知道如何在任何Activity
或Fragment
使用RxJava,而不会冒遇到任何与RxJava相关的内存泄漏的风险。
创建更多反应式Android UI
对UI事件(例如轻击,滑动和文本输入)的反应是开发几乎所有Android应用程序的基础部分,但是处理Android UI事件并不是特别简单。
通常,您将结合使用侦听器,处理程序, TextWatchers
和可能的其他组件来对UI事件做出反应,具体取决于要创建的UI的类型。 这些组件中的每一个都需要您编写大量的样板代码,更糟糕的是,实现这些不同组件的方式并不一致。 例如,您可以通过实现OnClickListener
处理OnClick
事件:
Button button = (Button)findViewById(R.id.button);
button.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//Perform some work//
}
});
但这与实现TextWatcher的方式完全不同:
final EditText name = (EditText) v.findViewById(R.id.name);
//Create a TextWatcher and specify that this TextWatcher should be called whenever the EditText’s content changes//
name.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
//Perform some work//
}
@Override
public void afterTextChanged(Editable s) {
}
});
缺乏一致性可能会给您的代码增加很多复杂性。 而且,如果您拥有依赖于其他UI组件输出的UI组件,那么请准备好使事情变得更加复杂! 甚至是一个简单的用例(例如要求用户在EditText
键入其名称,以便您可以个性化随后出现的TextViews
中出现的文本),都需要嵌套的回调,这是众所周知很难实现和维护的。 (有些人将嵌套的回调称为“回调地狱”。)
显然,处理UI事件的标准化方法可以极大地简化您的代码,RxBinding是一个通过提供使您能够将任何Android View
事件转换为Observable
绑定而做到这一点的库。
一旦将view事件转换为Observable
,它将以数据流的形式发出其UI事件,您可以订阅该数据流的方式与订阅任何其他Observable
方式完全相同。
既然我们已经看到了如何使用Android的标准OnClickListener
捕获click事件,那么让我们看一下如何使用RxBinding获得相同的结果:
import com.jakewharton.rxbinding.view.RxView;
...
Button button = (Button) findViewById(R.id.button);
RxView.clicks(button)
.subscribe(aVoid -> {
//Perform some work here//
});
这种方法不仅更加简洁,而且是一种标准实现,可以应用于整个应用中发生的所有UI事件。 例如,捕获文本输入的方式与捕获单击事件的方式相同:
RxTextView.textChanges(editText)
.subscribe(charSequence -> {
//Perform some work here//
});
具有RxBinding的示例应用
因此,您可以确切地看到RxBinding如何简化您的应用程序与UI相关的代码,让我们创建一个演示这些绑定的一些应用程序。 我还打算到包括View
这依赖于另一个输出View
,演示如何RxBinding简化了创建UI组件之间的关系。
这个程序将包括:
- 轻按时显示
Toast
Button
。 - 用于检测文本更改的
EditText
。 - 一个
TextView
,它更新以显示EditText
的内容。
项目设置
使用您选择的设置创建一个Android Studio项目,然后打开您的模块级build.gradle文件,并将最新版本的RxBinding库添加为项目依赖项。 为了尽量减少样板代码,我还将使用lambda,因此我更新了build.gradle文件以支持此Java 8功能:
apply plugin: 'com.android.application'
android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "com.jessicathornsby.myapplication"
minSdkVersion 23
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//Enable the Jack toolchain//
jackOptions {
enabled true
}
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
//Set sourceCompatibility and targetCompatibility to JavaVersion.VERSION_1_8//
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
//Add the core RxBinding library//
compile 'com.jakewharton.rxbinding:rxbinding:0.4.0'
compile 'com.android.support:appcompat-v7:25.3.0'
//Don’t forget to add the RxJava and RxAndroid dependencies//
compile 'io.reactivex.rxjava2:rxandroid:2.0.1'
compile 'io.reactivex.rxjava2:rxjava:2.0.5'
testCompile 'junit:junit:4.12'
}
}
当您使用多个RxJava库时,有可能在编译时遇到在APK META-INF / DEPENDENCIES错误消息中复制的重复文件 。 如果确实遇到此错误,那么解决方法是通过将以下内容添加到模块级build.gradle文件中来抑制这些重复文件:
android {
packagingOptions {
//Use “exclude” to point at the specific file (or files) that Android Studio is complaining about//
exclude 'META-INF/rxjava.properties'
}
创建主要活动布局
同步您的Gradle文件,然后创建一个由Button
, EditText
和TextView
组成的布局:
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
tools:context=".MainActivity" >
<Button
android:text="Button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:id="@+id/button" />
<EditText
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:inputType="textPersonName"
android:text="Type here"
android:ems="10"
android:id="@+id/editText" />
<TextView
android:text="TextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/textView" />
</LinearLayout>
编写事件绑定
现在,让我们看看如何使用这些RxBinding捕获应用程序需要响应的各种UI事件。 首先,声明您的导入并定义MainActivity
类。
package com.jessicathornsby.myapplication;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
//Import the view.RxView class, so you can use RxView.clicks//
import com.jakewharton.rxbinding.view.RxView;
//Import widget.RxTextView so you can use RxTextView.textChanges//
import com.jakewharton.rxbinding.widget.RxTextView;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button button = (Button) findViewById(R.id.button);
TextView textView = (TextView) findViewById(R.id.textView);
EditText editText = (EditText) findViewById(R.id.editText);
//Code for the bindings goes here//
//...//
}
}
现在,您可以开始添加绑定以响应UI事件。 RxView.clicks
方法用于绑定单击事件。 创建绑定以在单击按钮时显示吐司:
RxView.clicks(button)
.subscribe(aVoid -> {
Toast.makeText(MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT).show();
});
接下来,使用RxTextView.textChanges()
方法通过使用EditText
的内容更新TextView
来对文本更改事件做出反应。
RxTextView.textChanges(editText)
.subscribe(charSequence -> {
textView.setText(charSequence);
});
运行应用程序时,最终将显示如下屏幕。
将项目安装在物理Android智能手机或平板电脑或兼容的AVD上,然后花一些时间与各种UI元素进行交互。 您的应用应该正常响应点击事件和文本输入-所有这些都不会出现侦听器,TextWatcher或回调!
RxBinding支持库视图
虽然核心RxBinding库为构成标准Android平台的所有UI元素提供了绑定,但也有RxBinding兄弟模块为Android的各种支持库中包含的视图提供了绑定。
如果您已在项目中添加了一个或多个支持库,则通常也需要添加相应的RxBinding模块。
这些兄弟模块遵循简单的命名约定,可以轻松识别相应的Android支持库:每个兄弟模块仅获取支持库的名称,并将com.android
替换为com.jakewharton.rxbinding2:rxbinding
。
-
compile com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
-
compile 'com.jakewharton.rxbinding2:rxbinding-support-v4:2.0.0'
-
compile 'com.jakewharton.rxbinding2:rxbinding-appcompat-v7:2.0.0'
-
compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
-
compile 'com.jakewharton.rxbinding2:rxbinding-recyclerview-v7:2.0.0'
-
compile 'com.jakewharton.rxbinding2:rxbinding-leanback-v17:2.0.0'
如果您在Android项目中使用Kotlin ,那么每个RxBinding模块都可以使用Kotlin版本。 要访问Kotlin版本,只需将-kotlin
附加到要使用的库的名称即可,因此:
compile 'com.jakewharton.rxbinding2:rxbinding-design:2.0.0'
成为:
compile 'com.jakewharton.rxbinding2:rxbinding-design-kotlin:2.0.0'
将View
事件转换为Observable
,所有这些事件都会作为数据流发出。 正如我们已经看到的,您可以订阅这些流,然后执行触发此特定UI事件所需的任何任务,例如显示Toast
或更新TextView
。 但是,您也可以将RxJava庞大的运算符集合中的任何一个应用于此可观察的流,甚至可以将多个运算符链接在一起以对UI事件执行复杂的转换。
在一篇文章中讨论的操作员太多了(无论如何,官方文档都列出了所有操作员 ),但是在处理Android UI事件时,有一些操作员会特别有用。
debounce()
运算符
首先,如果您担心不耐烦的用户可能会反复轻按UI元素,从而可能使您的应用程序感到困惑,则可以使用debounce()
运算符过滤掉快速连续发出的UI事件。
在下面的示例中,我指定仅当自上次单击事件以来至少存在500毫秒的间隔时,此按钮才应对OnClick
事件作出反应:
RxView.clicks(button)
.debounce(500, TimeUnit.MILLISECONDS)
.subscribe(aVoid -> {
Toast.makeText(MainActivity.this, "RxView.clicks", Toast.LENGTH_SHORT).show();
});
publish()
运算符
您还可以使用publish()
运算符将多个侦听器附加到同一视图,这在Android中通常很难实现。
publish()
运算符将标准Observable
转换为可连接的 observable。 尽管常规可观察对象在第一个观察者订阅后立即开始发出项目,但是可连接可观察对象将不会发出任何东西,直到您通过应用connect()
运算符明确指示它为止。 这为您提供了一个机会窗口,可以在其中订阅多个观察者,而在第一次订阅发生后,可观察的对象就不会开始发出项目。
创建所有订阅后,只需应用connect()
运算符,observable将开始向其所有分配的观察者发送数据。
避免破坏应用程序的内存泄漏
正如我们在本系列文章中所看到的那样,RxJava可以成为创建更具反应性,交互式Android应用程序的强大工具,其代码要比通常单独使用Java获得相同结果所需的代码少得多。 但是,在Android应用程序中使用RxJava的一个主要缺点是-订阅不完整可能导致内存泄漏。
当Android系统尝试破坏包含正在运行的Observable
的Activity
时,会发生这些内存泄漏。 由于可观察对象正在运行,因此其观察者将仍然持有对该活动的引用,因此系统将无法垃圾收集该活动。
由于Android每次设备配置更改时都会销毁并重新创建Activity
,因此您的应用可能会在用户每次在纵向和横向模式之间切换以及每次打开和关闭设备键盘时都创建一个重复的Activity
。
这些活动将在后台徘徊,可能永远不会收集垃圾。 由于“活动”是大对象,因此可能很快导致严重的内存管理问题,尤其是由于Android智能手机和平板电脑开始时内存有限。 大量内存泄漏和有限内存的组合会Swift导致内存不足错误。
RxJava内存泄漏可能会对应用程序的性能造成严重破坏,但是有一个RxAndroid库允许您在应用程序中使用RxJava,而不必担心内存泄漏。
Trello开发的RxLifecycle库提供了生命周期处理API,您可以使用这些API将Observable
的生命周期限制为Activity
或Fragment
的生命周期。 建立此连接后,RxLifecycle将终止可观察对象的序列,以响应该可观察对象的分配活动或片段中发生的生命周期事件。 这意味着您可以创建一个可观察的对象,该对象在活动或片段被销毁时会自动终止。
请注意,我们正在谈论终止序列,而不是取消订阅。 尽管经常在管理订阅/取消订阅过程的上下文中谈论RxLifecycle,但从技术上讲,它并不取消订阅观察者。 而是,RxLifecycle库通过发出onComplete()
或onError()
方法来终止可观察序列。 取消订阅后,观察者将停止从其可观察对象接收通知,即使该可观察对象仍在发出项目。 如果您特别需要退订行为,那么您就需要实现自己。
使用RxLifecycle
要在您的Android项目中使用RxLifecycle,请打开您的模块级build.gradle文件,并添加最新版本的RxLifeycle库以及RxLifecycle Android库:
dependencies {
...
...
compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1'
compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.0.1'
然后,在要使用库的生命周期处理API的Activity
或Fragment
中,扩展RxActivity
, RxAppCompatActivity
或RxFragment
,并添加相应的import语句,例如:
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity;
...
public class MainActivity extends RxAppCompatActivity {
在将Observable
绑定到Activity
或Fragment
的生命周期时,可以指定Observable
终止的生命周期事件,也可以让RxLifecycle库决定何时终止Observable序列。
默认情况下,RxLifecycle将在与该订阅发生的事件互补的生命周期事件中终止一个可观察对象,因此,如果您在Activity的onCreate()
方法期间订阅了一个Observable,则RxLifecycle将在该Activity的onDestroy()
方法期间终止该可观察序列。 。 如果您在Fragment
的onAttach()
方法期间进行订阅,则RxLifecycle将在onDetach()
方法中终止此序列。
您可以使用RxLifecycleAndroid.bindActivity
将此决定留给RxLifecycleAndroid.bindActivity
:
Observable<Integer> myObservable = Observable.range(0, 25);
...
@Override
public void onResume() {
super.onResume();
myObservable
.compose(RxLifecycleAndroid.bindActivity(lifecycle))
.subscribe();
}
或者,你可以指定生命周期事件,其中RxLifecycle应终止Observable
序列,使用RxLifecycle.bindUntilEvent
。
在这里,我指定可观察序列应在onDestroy()
终止:
@Override
public void onResume() {
super.onResume();
myObservable
.compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY))
.subscribe();
}
使用Android棉花糖权限
我们要看的最后一个库是RxPermissions,它旨在帮助您将RxJava与Android 6.0中引入的新权限模型一起使用。 此库还允许您在Activity.onRequestPermissionsResult()
发出许可权请求并在同一位置处理许可权结果,而不是在一处请求许可权,然后分别处理许可权结果。
首先将RxPermissions库添加到build.gradle文件中:
compile 'com.tbruyelle.rxpermissions2:rxpermissions:aaa@qq.com'
然后,创建一个RxPermissions实例:
RxPermissions rxPermissions = new RxPermissions(this);
然后,您可以使用以下公式开始通过RxPermissions库发出权限请求:
rxPermissions.request(Manifest.permission.READ_CONTACTS)
.subscribe(granted -> {
if (granted) {
// The permission has been granted//
} else {
// The permission has been denied//
}
});
当您发出您的权限要求是至关重要的,因为总有一个机会,主办Activity
可能被破坏,然后重建,而权限对话屏幕上,通常是由于配置变化,如用户纵向和横向模式之间移动。 如果发生这种情况,则可能不会重新创建您的订阅,这意味着您将不会订阅可观察到的RxPermissions,也不会收到用户对许可请求对话框的响应。 为确保您的应用程序收到用户的响应,请始终在初始化阶段(例如Activity.onCreate()
, Activity.onResume()
或View.onFinishInflate()
期间调用您的请求。
功能需要多个权限并不少见。 例如,发送SMS消息通常要求您的应用具有SEND_SMS
和READ_CONTACTS
权限。 RxPermissions库提供了一种发出多个权限请求的简洁方法,然后将用户的响应组合为一个false
(可以拒绝一个或多个权限)或true
(可以授予所有权限)响应,然后您可以做出相应的反应。
RxPermissions.getInstance(this)
.request(Manifest.permission.SEND_SMS,
Manifest.permission.READ_CONTACTS)
.subscribe(granted -> {
if (granted) {
// All permissions were granted//
} else {
//One or more permissions was denied//
}
});
通常,您通常希望触发权限请求以响应UI事件,例如用户点击菜单项或按钮,因此RxPermissions和RxBiding是两个可以很好地协同工作的库。
将UI事件处理为可观察到的事件,并通过RxPermissions发出许可请求,使您仅需执行几行代码即可完成大量工作:
RxView.clicks(findViewById(R.id.enableBluetooth))
.compose(RxPermissions.getInstance(this).ensure(Manifest.permission.BLUETOOTH_ADMIN))
.subscribe(granted -> {
// The ‘enableBluetooth’ button has been clicked//
});
结论
阅读本文之后,您将对如何从Android应用程序中削减很多样板代码有一些想法-使用RxJava处理应用程序的所有UI事件,并通过RxPermissions发出权限请求。 我们还研究了如何在任何Android Activity
或Fragment
使用RxJava,而不必担心由于订阅不完整而导致的内存泄漏。
我们已经探索了本系列中一些最受欢迎和最有用的RxJava和RxAndroid库,但是如果您渴望看到RxJava还可以为Android开发人员提供什么,请查看其他许多RxAndroid库。 您可以在GitHub上找到其他RxAndroid库的完整列表 。