Share
Embed
Rxjava 介紹與 Android 中的 RxJava
2015/08/28 在 PicCollage 的 Android Taipei 的分享。
相關程式碼:
https://github.com/ch8908/rxjava-demo
Chien Shuo (Kros)
August 27, 2015
Transcript
71 slides
-
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 太多時,眼睛都花了。 -
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 >>>>>>>>>>>>>>>>>>> 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 加⼯工 -
// - 跟 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); @Override public 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); @Override public 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); } }); } -
// - 跟 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); @Override public 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); @Override public 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); @Override public 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); @Override public 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); @Override public 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 -
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 1 (with Event) Activity 2 Subject startActivity(); subject.subscribe(); //
Do something… subject.onNext(Event); finish(); -
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 @Override public 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 -
@Override protected void onStart() { super.onStart(); lifecycleSubject.onNext(LifecycleEvent.START); } @Override
protected void onResume() { super.onResume(); lifecycleSubject.onNext(LifecycleEvent.RESUME); } @Override protected void onPause() { lifecycleSubject.onNext(LifecycleEvent.PAUSE); super.onPause(); } @Override protected void onStop() { lifecycleSubject.onNext(LifecycleEvent.STOP); super.onStop(); } @Override protected 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 */ } -
@Override public 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)); } } @Override public 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. -
@Override public 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)); } } @Override public 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. -
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()); } -
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()); } -
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
Patterns http://stablekernel.com/blog/replace-asynctask-asynctaskloader-rx- observable-rxjava-android-patterns/ • Architecting Android…The evolution http://fernandocejas.com/2015/07/18/architecting-android-the-evolution/ • Unit Testing RxJava Observables https://medium.com/ribot-labs/unit-testing-rxjava-6e9540d4a329 • Demo Project https://github.com/ch8908/rxjava-demo