Upload
kros-huang
View
3.432
Download
2
Embed Size (px)
Citation preview
RxJava 介紹與 Android 中的 RxJava
⿈黃千碩 (Kros)oSolve Ltd. / Wish8 Co,. Ltd.
Mobile App Developer
Outline• RxJava Introduction
• Observable
• Operators
• Subject
• Scheduler
• Android Lifecycle
• Testing
• Performance
What is RxJava?
• RxJava 是 Reactive X (Reactive Extensions) 對 Java VM 的實作
• 是⼀一個 Library,利⽤用資料流 (observable sequences) 來處理 「asynchronous 與 event-base 」類型的程式。
• 主要⾓角⾊色為:observable 與 observer 。
What is Reactive?
• 翻譯:「響應式」「反應式」開發
• 把資料 (data) 或是事件 (event) 變成「可觀察」(observer pattern) 的「資料流」。
• 並加上運算元 (operators) 來操作這些資料。
What is FRP ?
• FRP - Functional Reactive Programming.
• Reactive 是⺫⽬目的
• 為了能讓開發者不落⼊入如何處理(事件)資料的繁雜程式邏輯中,利⽤用 函數式 (Functional) 的⽅方法來處理資料流
• filter(), map(), flatMap(), …etc.
Why FRP?• Concurrency
• thread 的控管複雜
• Asynchronous Programming
• 為追求 60fps,許多事情我們會丟到背景處理
• Callback Hell
• 當 Callback 太多時,眼睛都花了。
– 林信良
“若開發者是以務實且不斷提升作為⾃自我期許,更⾼高階的抽象化作法將會是必修的課題”
Observable
• Observable 為發射資料的⼈人
Create Observable
• Observable.just()
• Observable.from()
• …etc.
Observable.just()• 把「資料」轉變成 Observable。
Observable.just("Hello World!")
Observer• Observer 為對這些資料有興趣的⼈人
• 透過 subscribe method 連結 observer 與 observable.
• Observer 透過 subscribe 來監聽⼀一個 Observable.
Subscribe• 連結 observable 與 observer
• 通常必須實作 subscribe 的 interface.
• onNext, onError, onComplete
public final Subscription subscribe(final Action1<? super T> onNext, final Action1<Throwable> onError, final Action0 onComplete) { /* ... */ }
>>>>>>>>>>>>>>>>>>> s:Hello World!
Observable.just("Hello World!").subscribe(new Action1<String>() { @Override public void call(String s) { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { } }, new Action0() { @Override public void call() { } });
>>>>>>>>>>>>>>>>>>> s:Hello World!
Observable.just("Hello World!").subscribe(new Action1<String>() { @Override public void call(String s) { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { } }, new Action0() { @Override public void call() { } });
Observable.just("Hello World!").subscribe(new Action1<String>() { @Override public void call(String s) { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); } }); 可以只實作感興趣的 callback
>>>>>>>>>>>>>>>>>>> s:Hello World!
Observable.just("Hello World!").subscribe(new Action1<String>() { @Override public void call(String s) { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); } }, new Action1<Throwable>() { @Override public void call(Throwable throwable) { } }, new Action0() { @Override public void call() { } });
Observable.just("Hello World!").subscribe(new Action1<String>() { @Override public void call(String s) { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); } });
Observable.just("Hello World!").subscribe(s -> { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); });
套⽤用 retrolambda,採⽤用 java8 lambda,讓程式碼更簡潔
Observable.from• 把「⼀一包資料」轉變成 Observable。⽽而這個
Observable 每次只發射資料中的單⼀一資料
Observable.from(listOfIntegers)
Observable.from
>>>>>>>>>>>>>>>>>>> integer:1 >>>>>>>>>>>>>>>>>>> integer:2 >>>>>>>>>>>>>>>>>>> integer:3 >>>>>>>>>>>>>>>>>>> integer:4 >>>>>>>>>>>>>>>>>>> integer:5 >>>>>>>>>>>>>>>>>>> integer:6 >>>>>>>>>>>>>>>>>>> integer:7
List<Integer> integers = new ArrayList<>(); integers.add(1); // ...integers.add(7); Observable.from(integers).subscribe(integer -> { System.out.println(">>>>>>>>>>>>>>>>>>> integer:" + integer); });
“Hot” and “Cold” Observable
• Observable 什麼時候會發射資料呢?
• Hot observable
• 當它⼀一建⽴立時就會發射資料
• Cold observable
• 當有 observer subscribe 時,才會發射資料
Operators• Creating Observables (ex: create, from, just, …)
• Transforming Observables (ex: map, flatMap, …)
• Filtering Observables
• Combining Observables
• Error Handling Operators
• Observable Utility Operators
• ……etc.
Observable.just("Hello World!").map(s -> s + " Android Taipei") .subscribe(s -> { System.out.println(">>>>>>>>>>>>>>>>>>> s:" + s); });
Observable.from(integers) .map(integer -> integer + 10) .subscribe(integer -> { System.out.println(">>>>>>>>>>>>>>>>>>> integer:" + integer); });
>>>>>>>>>>>>>>>>>>> s:Hello World! Android Taipei
>>>>>>>>>>>>>>>>>>> integer:11 >>>>>>>>>>>>>>>>>>> integer:12 >>>>>>>>>>>>>>>>>>> integer:13 >>>>>>>>>>>>>>>>>>> integer:14 >>>>>>>>>>>>>>>>>>> integer:15 >>>>>>>>>>>>>>>>>>> integer:16 >>>>>>>>>>>>>>>>>>> integer:17
對 "Hello World!" 加⼯工
對 list 中每個 element 加⼯工
http://data.taipei/
Callback 版本
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") void listToiletCallback(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset, Callback<ApiResponse> callback);
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") void listToiletCallback(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset, Callback<ApiResponse> callback);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet(); }
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") void listToiletCallback(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset, Callback<ApiResponse> callback);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet(); }
⺫⽬目的:找出距離我 5 km 以內的公廁,並按照遠近排序
private void fetchNearestToilet() { apiService.listToiletCallback(RID, SCOPE, 500, 0, new Callback<ApiResponse>() { @Override public void success(ApiResponse apiResponse, Response response) { List<Toilet> filtered = new ArrayList<>(); for (Toilet toilet : apiResponse.getResult().getToilets()) { if (lessThan5Km(toilet)) { filtered.add(toilet); } } Collections.sort(filtered, new Comparator<Toilet>() { @Override public int compare(Toilet lhs, Toilet rhs) { return compareDistance(lhs, rhs); } }); adapter.reset(filtered); progressBar.setVisibility(View.GONE); } @Override public void failure(RetrofitError error) { progressBar.setVisibility(View.GONE); ViewHelper.showError(getActivity(), error); } }); }
RxJava 版本
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe((toilets) -> { adapter.reset(toilets); }, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe((toilets) -> { adapter.reset(toilets); }, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable));
} Java 8 的 method reference
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable));
}
private Observable<List<Toilet>> fetchNearestToilet() { return apiService.listToilet(RID, SCOPE, 500, 0) .flatMap(response -> Observable.from(response.getResult().getToilets())) .filter(this::lessThan5Km) .toSortedList(this::compareDistance); }
// - 跟 Server 抓取公廁資料 @GET("/apiAccess") Observable<ApiResponse> listToilet(@Query("rid") String rid, @Query("scope") String scope, @Query("limit") int limit, @Query("offset") int offset);
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
Subject• 翻譯:主題
• A Subject is a sort of bridge or proxy that is available in some implementations of ReactiveX that acts both as an observer and as an Observable.
• Subject 可以是發送 event 的⼈人 (observable),也可以是註冊 event 的⼈人 (observer)。
• ⽤用途:Event Bus
Subject
• Subject 有很多種:
• AsyncSubject
• BehaviorSubject
• PublishSubject
• ReplaySubject
Publish Subject
• 會發送給每個 observers
• 只會接收到 subscribe 之後的 event
Subject
Activity 1
Subject
Activity 1
Subject
subject.subscribe();
Activity 1
Subject
startActivity();
subject.subscribe();
Activity 1 Activity 2
Subject
startActivity();
subject.subscribe();
Activity 1 Activity 2
Subject
startActivity();
// Do something… subject.onNext(Event); finish();
subject.subscribe();
Activity 1 Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Activity 1 Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Activity 1 Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Activity 1 Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Activity 1 (with Event)
Activity 1 (with Event) Activity 2
Subject
startActivity();
subject.subscribe();// Do something… subject.onNext(Event); finish();
Subject
subject.subscribe();
Activity 1 (with Event)
Subject
subject.subscribe();
Activity 1 (with Event)
Activity 2 (with Event)
Activity 3 (with Event)
subject.subscribe();
subject.subscribe();
可以很多⼈人註冊
Scheduler
• If you want to introduce multithreading into your cascade of Observable operators, you can do so by instructing those operators (or particular Observables) to operate on particular Schedulers.
• 可以利⽤用 Scheduler 來實作 thread 的切換。
Scheduler
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
Android Lifecycle• Activity 與 Fragment 都有各⾃自的 lifecycle.
• Activity, onCreate(), onResume(), onPause(), onDestory(), ..etc.
• Fragment, onCreate(), onCreateView(), onResume(), onPause(), onDestory(), ..etc
• 如果 Activity/Fragment 被 destroy 時,你的 async task 還沒做完怎麼辦?
Android Lifecycle
• 會導致 Memory leak 或是 NPE.
• Activity 與 Fragment 都有各⾃自的 lifecycle.
• Activity, onCreate(), onResume(), onPause(), onDestory(), ..etc.
• Fragment, onCreate(), onCreateView(), onResume(), onPause(), onDestory(), ..etc
• 如果 Activity/Fragment 被 destroy 時,你的 async task 還沒做完怎麼辦?
Android Lifecycle
• Import RxJava Android 版compile 'io.reactivex:rxandroid:0.25.0'
• 使⽤用 Android 相關的 observable 與 event. rx.android.lifecycle.LifecycleObservable rx.android.lifecycle.LifecycleEvent
@Overrideprotected void onStart() { super.onStart(); lifecycleSubject.onNext(LifecycleEvent.START); } @Overrideprotected void onResume() { super.onResume(); lifecycleSubject.onNext(LifecycleEvent.RESUME); } @Overrideprotected void onPause() { lifecycleSubject.onNext(LifecycleEvent.PAUSE); super.onPause(); } @Overrideprotected void onStop() { lifecycleSubject.onNext(LifecycleEvent.STOP); super.onStop(); } @Overrideprotected void onDestroy() { lifecycleSubject.onNext(LifecycleEvent.DESTROY); super.onDestroy(); }
private final BehaviorSubject<LifecycleEvent> lifecycleSubject = BehaviorSubject.create();
public class BaseActivity extends AppCompatActivity {
}
public class BaseActivity extends AppCompatActivity { /* reset code */
public Observable<LifecycleEvent> lifecycle() { return lifecycleSubject.asObservable(); }
protected <T> Observable<T> bind(Observable<T> observable) { return LifecycleObservable.bindActivityLifecycle(lifecycle(), observable.observeOn(AndroidSchedulers.mainThread())); }
/* reset code */ }
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); bind(fetchNearestToilet()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
bind() 的功能:當 fragment 被 destroyed 時,會⾃自動 unsubscribe 此 observable.
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); fetchNearestToilet() .observeOn(AndroidSchedulers.mainThread()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
@Overridepublic void onViewCreated(View view, Bundle savedInstanceState) { super.onViewCreated(view, savedInstanceState); progressBar.setVisibility(View.VISIBLE); bind(fetchNearestToilet()) .finallyDo(() -> progressBar.setVisibility(View.GONE)) .subscribe(adapter::reset, throwable -> ViewHelper.showError(getActivity(), throwable)); } }
bind() 的功能:當 fragment 被 destroyed 時,會⾃自動 unsubscribe 此 observable.
Testing
• 測試容易
• 簡易的⽅方法, toBlocking()
• 正規的⽅方法, TestSubscriber()
public class AccountDaemon {
public Observable<Account> login(final Account account) { return Observable.just(account).map(account1 -> { checkAccount(account); return accountService.login(account); }); }
private void checkAccount(Account account) throws IllegalArgumentException { if (TextUtils.isEmpty(account.getEmail()) || TextUtils.isEmpty(account.getPassword())) { throw new IllegalArgumentException("Email or password can not be empty."); } }
}
public class Account { private final String email; private final String password;
public static Account createLoginAccount(final String email, final String password) { return new Account(email, password); } // rest implementation… }
public void testLogin_empty_email() throws Exception { Account account = Account.createLoginAccount(null, "password"); try { accountDaemon.login(account).toBlocking().single(); fail("method should throw exception"); } catch (Throwable ex) { assertEquals("Email or password can not be empty.", ex.getLocalizedMessage()); } }
// Official way public void testLogin_using_test_subscriber() { TestSubscriber<Account> testSubscriber = new TestSubscriber<>(); Account account = Account.createLoginAccount("email", "password"); accountDaemon.login(account).subscribe(testSubscriber); Account expect = Account.createLoginAccount("email", "password"); testSubscriber.assertNoErrors(); testSubscriber.assertValue(expect); }
// Blocking way public void testLogin() throws Exception { Account account = Account.createLoginAccount("email", "password"); Account result = accountDaemon.login(account).toBlocking().single(); assertEquals("email", result.getEmail()); assertEquals("password", result.getPassword()); }
Performance
public void testPerformance_rx() { List<Integer> data = new ArrayList<>(); for (int i = 0; i < 100000; ++i) { data.add(i); } List<Integer> result = Observable.from(data) .filter(integer -> integer % 2 == 0).toList().toBlocking().first(); assertEquals(100000 / 2, result.size()); }
public void testPerformance_for_loop() { List<Integer> data = new ArrayList<>(); for (int i = 0; i < 100000; ++i) { data.add(i); } List<Integer> result = new ArrayList<>(); for (int i = 0, size = data.size(); i < size; i++) { if (i % 2 == 0) { result.add(i); } } assertEquals(100000 / 2, result.size()); }
不是每個語⾔言的 Rx 版本效能都很好,導⼊入時需注意
例如:ReactiveCocoa
RAC-ReactiveCocoa
- (void)testRACPerformance { NSArray *array = [self getTestArray]; [self measureBlock:^{ RACSequence *sequence = [array.rac_sequence filter:^BOOL(NSNumber *number) { return number.intValue % 2 == 0; }]; NSArray *results = sequence.array; XCTAssertEqualObjects(@(100000 / 2), @(results.count)); }]; }
- (void)testNativePerformance { NSArray *array = [self getTestArray]; [self measureBlock:^{ NSMutableArray *results = [NSMutableArray array]; for (int i = 0; i < array.count; ++i) { NSNumber *number = array[i]; if (number.intValue % 2 == 0) { [results addObject:number]; } } XCTAssertEqualObjects(@(100000 / 2), @(results.count)); }]; }
優缺點• 優點
• 程式碼清楚,簡潔
• 容易進⾏行 Asynchronous Programming
• 缺點
• 學習成本⾼高(map????, flatMap?????, amb???)
• ⼊入侵式的,所有 API 被迫改成 Observable<T>
public void fetchUserProfile() { // code} public void fetchFriends() { // code} public void fetchShippingInfo() { // code}
public Observable<Profile> fetchUserProfile() { // code} public Observable<List<Friend>> fetchFriends() { // code} public Observable<ShippingInfo> fetchShippingInfo() { // code}
Reference• ReactiveX
http://reactivex.io/
• FRP與函數式-林信良 http://www.ithome.com.tw/voice/91328
• RxJava Android Patternshttp://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx-observable-rxjava-android-patterns/
• Architecting Android…The evolutionhttp://fernandocejas.com/2015/07/18/architecting-android-the-evolution/
• Unit Testing RxJava Observableshttps://medium.com/ribot-labs/unit-testing-rxjava-6e9540d4a329
• Demo Projecthttps://github.com/ch8908/rxjava-demo