Android APP中的RxJava 2:RxBinding和RxLifecycle
Chinese (Traditional) (中文(繁體)) translation by zheban (you can also view the original English article)
RxJava是將回應式程式設計引入到Android平臺的最受歡迎的庫之一,在這個系列三篇文章中,我已經展示了如何在自己的Android專案中使用該庫。
在 Get Started With RxJava 2 for Android這篇文章中,我們先看了什麼是RxJava以及它為Android開發人員提供了什麼,然後再創建一個Hello World APP,演示了RxJava的三個核心元件:一個Observable
,一個Observer
和一個訂閱。
在Reactive Programming Operators in RxJava 2這篇文章中,我們研究了如何使用運算子執行複雜的資料轉換,以及如何組合Operators
和Schedulers
以最終在Android上實現多執行緒體驗。
我們還介紹了RxAndroid,這是一個專門用於幫助你在Android項目中使用RxJava的庫,但RxAndroid還有許多要探索的地方。所以,在這篇文章中,我將專注於RxAndroid系列庫。
與RxJava很相似,RxAndroid在其版本2中進行了大規模的修改。RxAndroid團隊決定對庫進行模組化,將其大部分功能轉移到專用的RxAndroid附加模組中。
在本文中,將展示如何設置和使用一些最受歡迎和功能強大的RxAndroid模組,包括處理listeners, handlers和TextWatchers
的庫,以及讓你能夠將任何Android UI事件轉換為Observable
的東西。
由於不完整的訂閱引起的記憶體洩漏是Android APP中使用RxJava的最大缺點,所以我將展示如何使用處理訂閱過程的RxAndroid模組。在本文結尾,你將瞭解如何在任何Activity
或Fragment
中使用RxJava而避免任何RxJava相關的記憶體洩漏的風險。
創建更多的回應式Android UI
對UI事件(如點擊,滑動和文本輸入)的回應幾乎是安卓APP開發的基本部分,但是處理Android UI事件並不是特別直截了當。
你通常會使用listeners, handlers, TextWatchers
和其他元件等的組合來回應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
的文本內容,而TextViews需要嵌套回檔,這是非常難以實現和維護的。 (有人將嵌套回檔稱為“回檔地獄”)
顯然,處理UI事件的標準化方法有可能大大簡化代碼,而RxBinding就是這樣的庫,它通過提供綁定,使你能夠將任何Android View
事件轉換為一個Observable
。
一旦你將view事件轉換為Observable
,它將發射資料流程形式的UI事件,你就可以訂閱這個資料流程,這與你訂閱其他Observable
的方式一樣。
由於我們已經瞭解了如何使用Android標準捕獲點擊事件OnClickListener
,我們來看看如何使用RxBinding實現相同的結果:
import com.jakewharton.rxbinding.view.RxView; ... Button button = (Button) findViewById(R.id.button); RxView.clicks(button) .subscribe(aVoid -> { //Perform some work here// });
這種方法不僅更簡潔,而且是一種標準的實現方式,你可以將其應用於整個APP中發生的所有UI事件。例如,捕獲文本輸入與捕獲點擊事件相模式一樣:
RxTextView.textChanges(editText) .subscribe(charSequence -> { //Perform some work here// });
使用RxBinding的示例APP
你可以看到RxBinding如何簡化APP的UI相關代碼,讓我們創建一個演示綁定操作的APP。我要添加一個View
,它取決於另一個的View
的輸出,這樣來演示RxBinding如何簡化UI元件之間創建關係。
這個APP將包括:
- 當點擊
Button
時顯示Toast
。 - 一個檢測文本變化的
EditText
。 - 一個
TextView
即時顯示EditText
的內容。
專案設置
創建一個Android Studio項目,然後打開module級的build.gradle文件,添加最新版本的RxBinding庫的依賴關係。為了使用lambdas運算式,更新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庫時,可能會在編譯時遇到Duplicate files copied in APK META-INF/DEPENDENCIES這個錯誤。如果遇到此錯誤,解決方法是通過將以下內容添加到module級的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' }
創建 MainActivity的佈局
同步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來捕獲APP需要回應的各種UI事件。對於初學者,聲明imports並定義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(); });
接下來,當EditText
內容變化後,使用RxTextView.textChanges()
方法來更新TextView
的內容。
RxTextView.textChanges(editText) .subscribe(charSequence -> { textView.setText(charSequence); });
運行結果如下.



將APP安裝到物理Android智慧手機或平板電腦或相容的AVD上,然後與各種UI元素進行交互。APP應該按照正常的點擊事件和文本輸入做出回應,所有這些都不需要listener,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'
如果使用的是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
事件轉換為Observabl
e後,所有這些事件都將作為資料流程被發射出去。正如你所見,你可以訂閱這些流,然後執行任何需要此特定UI事件觸發的任務,例如顯示Toast
或更新TextView
。 然而,也可以將RxJava的大量運算子應用到這個observable的流中,甚至將多個運算子連結在一起,對UI事件進行複雜的轉換。
操作符太多以至於在一篇文章中討論不完(官方文檔列出了所有的操作符),但是在處理Android UI事件時,有一些操作符特別有用。
debounce()
操作符
首先,如果你擔心不耐煩的用戶可能會重複點擊UI而導致可能會弄迷糊你的APP,那麼你可以使用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()
運算子讓多個listeners監聽同一個view,這在傳統Android開發中很難實現。
publish()
運算子將標準Observable
轉換為可連接的Observable。只要第一個觀察者訂閱常規的Observable,它就開始發射資料流程,但是通過connect()
運算子連接後,Observable將不會發出任何內容,除非收到明確的指示。 這是一個機會,訂閱多個觀察者,一旦第一次訂閱發生,不必立即開始發射資料。
創建完所有訂閱後,只需應用connect()
運算子,observable就會開始向所有分配的觀察者發送資料。
避免APP記憶體洩漏
正如我們在本系列中所看到的,RxJava可以創建更具回應性的、互動式的Android APP,實現相同結果,其代碼比使用Java更少。然而,在Android APP中使用RxJava存在一個最大的缺點,即不完整的訂閱會導致記憶體洩漏。
當Android系統嘗試銷毀包含正在運行的Observable的Activit
y時,會發生記憶體洩漏。由於observable
正在運行,其觀察者仍然會持有對該Activity的引用,因此系統將無法對此Activity進行垃圾回收。
由於Android的Activity
在每次設備配置更改時都會被破壞並被重建,因此APP可能會在使用者在縱向和橫向螢幕切換時以及每次打開和關閉設備的鍵盤時創建重複的Activity
。
這些Activities將在後臺掛起,可能永遠不會被回收。由於Activities是大的物件,這可能會導致嚴重的記憶體管理問題,尤其是Android智慧手機和平板電腦的記憶體限制。 大量記憶體洩漏和有限記憶體的組合可能會導致Out Of Memory 錯誤。
RxJava記憶體洩漏可能會對APP的性能造成破壞,但是現在有一個RxAndroid庫允許你在APP中使用RxJava而無需擔心記憶體洩漏。
由Trello開發的RxLifecycle庫提供了生命週期處理API,你可以使用它們來限制Activity
或Fragment
中的Observable
。一旦建立了此連接,RxLifecycle將終止observable的序列以回應該observable所分配的Activity或Fragment中的生命週期事件。 這意味著你可以創建一個observable,當Activity或Fragment被銷毀時,它將自動終止。
請注意,我所的說是終止序列,而不是取消訂閱。雖然在管理訂閱/取消訂閱過程的上下文中經常談到RxLifecycle,但在技術上它不取消訂閱觀察者 相反,RxLifecycle庫通過發出onComplete()
或者onError()
方法終止observable序列。 當你取消訂閱時,observer停止接收來自其可觀察的通知,即使該observable仍在發射資料。如果你特別要求取消訂閱行為,那麼你需要實現自己。
使用RxLifecycle
要在Android專案中使用RxLifecycle,請打開module級的build.gradle文件,並添加最新版本的RxLifeycle庫以及RxLifecycle Android庫:
dependencies { ... ... compile 'com.trello.rxlifecycle2:rxlifecycle:2.0.1' compile 'com.trello.rxlifecycle2:rxlifecycle-android:2.0.1'
然後,在你想使用該庫的Activity
或Fragmen
t中,要麼繼承RxActivity
,RxAppCompatActivity
或RxFragment
,並添加相應的import語句,例如:
import com.trello.rxlifecycle2.components.support.RxAppCompatActivity; ... public class MainActivity extends RxAppCompatActivity {
當涉及綁定Observable
到Activity
或Fragment
的生命週期時,你要麼指定observable應終止的生命週期事件,要麼讓RxLifecycle庫決定何時終止observable 序列。
預設情況下,RxLifecycle將在輔助生命週期事件中終止observable,所以如果你在Activity的onCreate()
方法期間訂閱了observable,則RxLifecycle將在該Activity的onDestroy()
方法終止observable序列。 如果你在Fragment
的onAttach()
方法中訂閱,那麼RxLifecycle將在onDetach()
方法中終止在該序列。
使用RxLifecycleAndroid.bindActivity
進行設置:
Observable<Integer> myObservable = Observable.range(0, 25); ... @Override public void onResume() { super.onResume(); myObservable .compose(RxLifecycleAndroid.bindActivity(lifecycle)) .subscribe(); }
或者,你可以指定lifecycle事件,在該事件中RxLifecycle應該使用RxLifecycle.bindUntilEvent
終止Observable
序列。
在這裡,我指定在onDestroy()
方法中終止observable序列:
@Override public void onResume() { super.onResume(); myObservable .compose(RxLifecycle.bindUntilEvent(lifecycle, ActivityEvent.DESTROY)) .subscribe(); }
Android Marshmallow許可權
我們將要看的最後一個庫是RxPermissions,它旨在幫助你使用RxJava與Android 6.0中引入的新許可權模型。該庫還允許你發出許可權請求並處理相同位置的許可權結果,而不是在一個地方請求許可權,然後在Activity.onRequestPermissionsResult()
中分別處理其結果。
首先將RxPermissions庫添加到build.gradle文件中:
compile 'com.tbruyelle.rxpermissions2:rxpermissions:0.9.3@aar'
然後創建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 observable並且不會收到用戶對許可權請求對話方塊的回應。為了保證你APP收到用戶的回應,總是在初始化階段申請許可權,比如在Activity.onCreate()
, Activity.onResume()
或View.onFinishInflate()
中。
APP功能需要多個許可權並不罕見。例如,發送短信通常需要有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事件作為observable,並使用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 APP中刪除大量樣板代碼 - 使用RxJava處理所有應用程式的UI事件,並通過RxPermissions申請許可權。我們還研究了如何在Activity
或者Fragment
中使用RxJava而不用擔心不完整訂閱引起的記憶體洩漏。
我們已經在本系列中探討了一些最受歡迎和有用的RxJava和RxAndroid庫,但如果你還想看看RxJava可以為Android開發人員提供其他庫,請查看其他RxAndroid庫。也可以在GitHub上找到 comprehensive list of additional RxAndroid libraries。
與此同時,請查看我們在Envato Tuts +上的一些其他Android開發文章!