พบกับ RxJava: The Missing Reactive Programming Library for Android

เผยแพร่แล้ว: 2022-03-11

หากคุณเป็นนักพัฒนา Android คุณคงเคยได้ยินเกี่ยวกับ RxJava มาก่อนแล้ว เป็นหนึ่งในไลบรารี่ที่ได้รับการกล่าวถึงมากที่สุดสำหรับการเปิดใช้งาน Reactive Programming ในการพัฒนา Android มันถูกขนานนามว่าเป็นเฟรมเวิร์กไปสู่การลดความซับซ้อนของงานพร้อมกัน/อะซิงโครนัสที่มีอยู่ในการเขียนโปรแกรมมือถือ

แต่... RxJava คืออะไรและ "ทำให้ง่ายขึ้น" ได้อย่างไร?

การเขียนโปรแกรมเชิงโต้ตอบเชิงหน้าที่สำหรับ Android: บทนำสู่ RxJava

แก้ Android ของคุณจากเธรด Java มากเกินไปด้วย RxJava
ทวีต

แม้ว่าจะมีแหล่งข้อมูลออนไลน์มากมายที่อธิบายว่า RxJava คืออะไร แต่ในบทความนี้ เป้าหมายของฉันคือการให้ข้อมูลเบื้องต้นเกี่ยวกับ RxJava แก่คุณ และโดยเฉพาะเจาะจงถึงวิธีที่มันเข้ากับการพัฒนา Android นอกจากนี้ ฉันจะให้ตัวอย่างที่เป็นรูปธรรมและคำแนะนำเกี่ยวกับวิธีการรวมเข้ากับโครงการใหม่หรือโครงการที่มีอยู่

ทำไมต้องพิจารณา RxJava?

ที่แกนหลัก RxJava ช่วยลดความซับซ้อนของการพัฒนา เพราะมันยกระดับความเป็นนามธรรมรอบเธรด นั่นคือ ในฐานะนักพัฒนา คุณไม่ต้องกังวลมากเกินไปเกี่ยวกับรายละเอียดวิธีดำเนินการที่ควรเกิดขึ้นในเธรดต่างๆ สิ่งนี้น่าสนใจเป็นพิเศษเนื่องจากเธรดเป็นสิ่งที่ท้าทายในการดำเนินการให้ถูกต้อง และหากใช้งานไม่ถูกต้อง อาจทำให้เกิดจุดบกพร่องที่ยากที่สุดบางรายการในการดีบักและแก้ไข

จริงอยู่ที่ นี่ไม่ได้หมายความว่า RxJava สามารถกันกระสุนได้เมื่อพูดถึงการทำเกลียว และยังคงเป็นสิ่งสำคัญที่จะต้องเข้าใจว่าเกิดอะไรขึ้นเบื้องหลัง อย่างไรก็ตาม RxJava สามารถทำให้ชีวิตของคุณง่ายขึ้นได้อย่างแน่นอน

มาดูตัวอย่างกัน

การโทรผ่านเครือข่าย - RxJava กับ AsyncTask

สมมติว่าเราต้องการรับข้อมูลผ่านเครือข่ายและอัปเดต UI ตามผลลัพธ์ วิธีหนึ่งในการทำเช่นนี้คือ (1) สร้างคลาสย่อย AsyncTask ภายในใน Activity / Fragment ของเรา (2) ดำเนินการเครือข่ายในพื้นหลัง และ (3) นำผลลัพธ์ของการดำเนินการนั้นและอัปเดต UI ในเธรดหลัก .

 public class NetworkRequestTask extends AsyncTask<Void, Void, User> { private final int userId; public NetworkRequestTask(int userId) { this.userId = userId; } @Override protected User doInBackground(Void... params) { return networkService.getUser(userId); } @Override protected void onPostExecute(User user) { nameTextView.setText(user.getName()); // ...set other views } } private void onButtonClicked(Button button) { new NetworkRequestTask(123).execute() }

ดูเหมือนจะไม่เป็นอันตราย แนวทางนี้มีปัญหาและข้อจำกัดบางประการ กล่าวคือ หน่วยความจำ/การรั่วไหลของบริบทสามารถสร้างขึ้นได้อย่างง่ายดายเนื่องจาก NetworkRequestTask เป็นคลาสภายในและถือเป็นการอ้างอิงโดยนัยไปยังคลาสภายนอก แล้วถ้าเราต้องการโยงการดำเนินการที่ยาวนานอีกหลังจากการโทรผ่านเครือข่ายล่ะ? เราต้องซ้อน AsyncTask สองอันซึ่งสามารถลดความสามารถในการอ่านได้อย่างมาก

ในทางตรงกันข้าม วิธี RxJava ในการโทรผ่านเครือข่ายอาจมีลักษณะดังนี้:

 private Subscription subscription; private void onButtonClicked(Button button) { subscription = networkService.getObservableUser(123) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Action1<User>() { @Override public void call(User user) { nameTextView.setText(user.getName()); // ... set other views } }); } @Override protected void onDestroy() { if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } super.onDestroy(); }

โดยใช้วิธีการนี้ เราแก้ปัญหา (ของหน่วยความจำที่อาจรั่วไหลซึ่งเกิดจากเธรดที่ทำงานอยู่ซึ่งมีการอ้างอิงถึงบริบทภายนอก) โดยคงการอ้างอิงไปยังออบเจ็กต์ Subscription ที่ส่งคืน ออบเจ็กต์ Subscription นี้จะเชื่อมโยงกับ #onDestroy() ของอ็อบเจ็กต์ Activity / Fragment เพื่อรับประกันว่าการดำเนินการ Action1#call จะไม่ดำเนินการเมื่อต้องทำลาย Activity / Fragment

นอกจากนี้ โปรดสังเกตว่าประเภทส่งคืนของ #getObservableUser(...) (เช่น Observable<User> ) ถูกล่ามโซ่ด้วยการเรียกเพิ่มเติม ผ่าน API ของไหลนี้ เราสามารถแก้ปัญหาที่สองของการใช้ AsyncTask ได้ ซึ่งก็คือช่วยให้สามารถโทรผ่านเครือข่ายเพิ่มเติม/ดำเนินการลูกโซ่ยาวได้ ค่อนข้างเรียบร้อยใช่มั้ย

มาดำดิ่งลึกลงไปในแนวคิด RxJava กัน

ผู้สังเกตการณ์ ผู้สังเกตการณ์ และผู้ดำเนินการ - 3 O's ของ RxJava Core

ในโลกของ RxJava ทุกสิ่งสามารถสร้างโมเดลเป็นสตรีมได้ สตรีมจะปล่อยรายการเมื่อเวลาผ่านไป และแต่ละการปล่อยสามารถบริโภค/สังเกตได้

หากคุณลองคิดดู สตรีมไม่ใช่แนวคิดใหม่: เหตุการณ์การคลิกอาจเป็นสตรีม การอัปเดตตำแหน่งอาจเป็นสตรีม การแจ้งเตือนแบบพุชอาจเป็นสตรีม และอื่นๆ

ในโลกของ RxJava ทุกสิ่งสามารถสร้างโมเดลเป็นสตรีมได้

สิ่งที่เป็นนามธรรมของกระแสจะดำเนินการผ่านโครงสร้างหลัก 3 แบบที่ฉันชอบเรียกว่า "3 O's"; กล่าวคือ: O bservable, O bserver และ O perator The Observable ปล่อยรายการ (สตรีม); และผู้ สังเกตการณ์ ใช้รายการเหล่านั้น การปล่อยมลพิษจากวัตถุที่สังเกตได้สามารถปรับเปลี่ยน เปลี่ยนแปลง และจัดการเพิ่มเติมได้โดยการเรียก Operator แบบลูกโซ่

สังเกตได้

Observable เป็นนามธรรมของกระแสใน RxJava คล้ายกับ Iterator เมื่อได้รับลำดับ มันจะวนซ้ำและสร้างรายการเหล่านั้นในลักษณะที่เป็นระเบียบ ผู้บริโภคสามารถใช้รายการเหล่านั้นผ่านอินเทอร์เฟซเดียวกัน โดยไม่คำนึงถึงลำดับพื้นฐาน

สมมติว่าเราต้องการเรียงเลข 1, 2, 3 ตามลำดับ ในการทำเช่นนั้น เราสามารถใช้วิธี Observable<T>#create(OnSubscribe<T>)

 Observable<Integer> observable = Observable.create(new Observable.OnSubscribe<Integer>() { @Override public void call(Subscriber<? super Integer> subscriber) { subscriber.onNext(1); subscriber.onNext(2); subscriber.onNext(3); subscriber.onCompleted(); } });

การเรียก subscriber.onNext(Integer) จะปล่อยรายการในสตรีม และเมื่อสตรีมเสร็จสิ้น การเรียก subscriber.onCompleted() จะถูกเรียกใช้

วิธีการสร้าง Observable นี้ค่อนข้างละเอียด ด้วยเหตุผลนี้ มีวิธีอำนวยความสะดวกในการสร้างอินสแตนซ์ที่สังเกตได้ซึ่งควรเป็นที่ต้องการในเกือบทุกกรณี

วิธีที่ง่ายที่สุดในการสร้าง Observable คือการใช้ Observable#just(...) ตามที่ชื่อเมธอดแนะนำ มันก็แค่ปล่อยรายการที่คุณส่งผ่านเข้าไปเป็นอาร์กิวเมนต์ของเมธอด

 Observable.just(1, 2, 3); // 1, 2, 3 will be emitted, respectively

ผู้สังเกตการณ์

องค์ประกอบถัดไปของสตรีมที่สังเกตได้คือผู้สังเกตการณ์ (หรือผู้สังเกตการณ์) ที่สมัครรับข้อมูล ผู้สังเกตการณ์จะได้รับแจ้งเมื่อมีบางสิ่ง "น่าสนใจ" เกิดขึ้นในสตรีม ผู้สังเกตการณ์จะได้รับแจ้งผ่านเหตุการณ์ต่อไปนี้:

  • Observer#onNext(T) - เรียกเมื่อรายการถูกปล่อยออกจากสตรีม
  • Observable#onError(Throwable) - เรียกใช้เมื่อเกิดข้อผิดพลาดภายในสตรีม
  • Observable#onCompleted() - เรียกใช้เมื่อสตรีมเสร็จสิ้นการปล่อยไอเท็ม

หากต้องการสมัครรับข้อมูลสตรีม เพียงเรียก Observable<T>#subscribe(...) และส่งผ่านอินสแตนซ์ Observer

 Observable<Integer> observable = Observable.just(1, 2, 3); observable.subscribe(new Observer<Integer>() { @Override public void onCompleted() { Log.d("Test", "In onCompleted()"); } @Override public void onError(Throwable e) { Log.d("Test", "In onError()"); } @Override public void onNext(Integer integer) { Log.d("Test", "In onNext():" + integer); } });

รหัสด้านบนจะปล่อยสิ่งต่อไปนี้ใน Logcat:

 In onNext(): 1 In onNext(): 2 In onNext(): 3 In onNext(): 4 In onCompleted()

อาจมีบางกรณีที่เราไม่สนใจการปล่อยก๊าซเรือนกระจกที่สังเกตได้อีกต่อไป สิ่งนี้มีความเกี่ยวข้องอย่างยิ่งใน Android ตัวอย่างเช่น เมื่อต้องเรียกคืน Activity / Fragment ในหน่วยความจำ

หากต้องการหยุดดูรายการ เราเพียงแค่เรียก Subscription#unsubscribe() บนออบเจ็กต์ Subscription ที่ส่งคืน

 Subscription subscription = someInfiniteObservable.subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } }); // Call unsubscribe when appropriate subscription.unsubscribe();

ตามที่เห็นในข้อมูลโค้ดด้านบน เมื่อสมัคร Observable เราถือการอ้างอิงไปยังออบเจ็กต์ Subscription ที่ส่งคืน และเรียกใช้ subscription#unsubscribe() ในภายหลังเมื่อจำเป็น ใน Android เรียกใช้ได้ดีที่สุดใน Activity#onDestroy() หรือ Fragment#onDestroy()

โอเปอเรเตอร์

รายการที่ปล่อยออกมาโดย Observable สามารถเปลี่ยนแปลง แก้ไข และกรองผ่าน Operator ก่อนที่จะแจ้งออบเจ็กต์ Observer ที่สมัครรับข้อมูล การดำเนินการทั่วไปบางส่วนที่พบในการเขียนโปรแกรมเชิงฟังก์ชัน (เช่น แผนที่ ตัวกรอง ลด ฯลฯ) ยังสามารถนำไปใช้กับสตรีมที่สังเกตได้ ลองดูแผนที่เป็นตัวอย่าง:

 Observable.just(1, 2, 3, 4, 5).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { return integer * 3; } }).subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } });

ข้อมูลโค้ดด้านบนจะใช้การปล่อยแต่ละรายการจาก Observable และคูณด้วย 3 แต่ละครั้ง ทำให้เกิดสตรีม 3, 6, 9, 12, 15 ตามลำดับ โดยทั่วไปแล้ว การใช้ตัวดำเนินการจะส่งกลับค่า Observable อีกอันหนึ่ง ซึ่งเป็นผลลัพธ์ที่สะดวก เนื่องจากช่วยให้เราเชื่อมโยงการดำเนินการต่างๆ เข้าด้วยกันเพื่อให้ได้ผลลัพธ์ที่ต้องการ

จากสตรีมด้านบน สมมติว่าเราต้องการรับเฉพาะเลขคู่ ซึ่งสามารถทำได้โดยการผูกการทำงานของ ตัวกรอง

 Observable.just(1, 2, 3, 4, 5).map(new Func1<Integer, Integer>() { @Override public Integer call(Integer integer) { return integer * 3; } }).filter(new Func1<Integer, Boolean>() { @Override public Boolean call(Integer integer) { return integer % 2 == 0; } }).subscribe(new Observer<Integer>() { @Override public void onCompleted() { // ... } @Override public void onError(Throwable e) { // ... } @Override public void onNext(Integer integer) { // ... } });

มีโอเปอเรเตอร์มากมายในตัวชุดเครื่องมือ RxJava ที่ปรับเปลี่ยนสตรีมที่สังเกตได้ หากคุณสามารถคิดหาวิธีแก้ไขสตรีมได้ เป็นไปได้ว่าจะมี Operator สำหรับมัน ไม่เหมือนกับเอกสารทางเทคนิคส่วนใหญ่ การอ่านเอกสาร RxJava/ReactiveX ค่อนข้างง่ายและตรงประเด็น โอเปอเรเตอร์แต่ละตัวในเอกสารประกอบมาพร้อมกับการแสดงภาพว่าโอเปอเรเตอร์ส่งผลต่อสตรีมอย่างไร การสร้างภาพข้อมูลเหล่านี้เรียกว่า "แผนภาพหินอ่อน"

ต่อไปนี้คือวิธีที่ Operator สมมุติที่เรียกว่า flip สามารถจำลองผ่านไดอะแกรมหินอ่อน:

ตัวอย่างของการที่ Operator สมมุติที่เรียกว่า flip สามารถจำลองผ่านไดอะแกรมหินอ่อน

มัลติเธรดด้วย RxJava

การควบคุมเธรดภายในซึ่งการดำเนินการเกิดขึ้นในสาย Observable ทำได้โดยการระบุตัวจัดกำหนดการภายในที่ตัวดำเนินการควรเกิดขึ้น โดยพื้นฐานแล้ว คุณสามารถนึกถึงตัวจัดกำหนดการเป็นพูลเธรด ซึ่งเมื่อระบุ ตัวดำเนินการจะใช้และทำงาน ตามค่าเริ่มต้น หากไม่มีตัวจัดกำหนดการดังกล่าว สาย Observable จะทำงานบนเธรดเดียวกันกับที่เรียก Observable#subscribe(...) มิฉะนั้น ตัวกำหนดตารางเวลาสามารถระบุได้ผ่าน Observable#subscribeOn(Scheduler) และ/หรือ Observable#observeOn(Scheduler) ซึ่งการดำเนินการตามกำหนดเวลาจะเกิดขึ้นบนเธรดที่เลือกโดยผู้จัดกำหนดการ

ความแตกต่างที่สำคัญระหว่างสองวิธีคือ Observable#subscribeOn(Scheduler) สั่งให้ Observable ซอร์สตัวกำหนดตารางเวลาที่ควรรัน เชนจะยังคงทำงานบนเธรดจากตัวจัดกำหนดการที่ระบุใน Observable#subscribeOn(Scheduler) จนกว่าจะมีการเรียก Observable#observeOn(Scheduler) ด้วยตัวจัดกำหนดการอื่น เมื่อมีการเรียกดังกล่าว ผู้สังเกตการณ์ทั้งหมดจากที่นั่น (เช่น การดำเนินการที่ตามมาในสายโซ่) จะได้รับการแจ้งเตือนในชุดข้อความที่นำมาจากตัว observeOn ตารางเวลาการสังเกต

นี่คือไดอะแกรมหินอ่อนที่แสดงให้เห็นว่าเมธอดเหล่านี้ส่งผลต่อตำแหน่งที่รันการดำเนินการอย่างไร:

ไดอะแกรมหินอ่อนที่แสดงให้เห็นว่าเมธอดเหล่านี้ส่งผลต่อการดำเนินการที่ดำเนินการอย่างไร

ในบริบทของ Android หากการดำเนินการ UI จำเป็นต้องเกิดขึ้นอันเนื่องมาจากการใช้งานที่ยาวนาน เราต้องการให้การดำเนินการนั้นเกิดขึ้นบนเธรด UI เพื่อจุดประสงค์นี้ เราสามารถใช้ AndroidScheduler#mainThread() ซึ่งเป็นหนึ่งในตัวจัดกำหนดการที่มีให้ในไลบรารี RxAndroid

RxJava บน Android

ตอนนี้ เรามีข้อมูลพื้นฐานบางอย่างภายใต้เข็มขัดของเราแล้ว คุณอาจสงสัยว่า วิธีที่ดีที่สุดในการรวม RxJava ในแอปพลิเคชัน Android คืออะไร? อย่างที่คุณอาจจินตนาการได้ มีหลายกรณีการใช้งานสำหรับ RxJava แต่ในตัวอย่างนี้ ลองมาดูกรณีเฉพาะกรณีหนึ่งกัน: การใช้วัตถุที่สังเกตได้เป็นส่วนหนึ่งของเครือข่ายสแต็ก

ในตัวอย่างนี้ เราจะดูที่ Retrofit ซึ่งเป็นไคลเอ็นต์ HTTP แบบโอเพนซอร์สที่ Square สร้างขึ้นซึ่งมีการเชื่อมโยงในตัวกับ RxJava เพื่อโต้ตอบกับ API ของ GitHub โดยเฉพาะอย่างยิ่ง เราจะสร้างแอปง่ายๆ ที่แสดงที่เก็บที่ติดดาวทั้งหมดสำหรับผู้ใช้ที่ได้รับชื่อผู้ใช้ GitHub หากคุณต้องการก้าวไปข้างหน้า ซอร์สโค้ดมีให้ที่นี่

สร้างโปรเจ็กต์ Android ใหม่

  • เริ่มต้นด้วยการสร้างโปรเจ็กต์ Android ใหม่และตั้งชื่อ GitHubRxJava

สกรีนช็อต: สร้างโปรเจ็กต์ Android ใหม่

  • ในหน้าจอ อุปกรณ์ Android เป้าหมาย เลือก โทรศัพท์และแท็บเล็ต ไว้และตั้งค่าระดับ SDK ขั้นต่ำที่ 17 อย่าลังเลที่จะตั้งค่าเป็นระดับ API ที่ต่ำกว่า/สูงกว่า แต่สำหรับตัวอย่างนี้ API ระดับ 17 ก็เพียงพอแล้ว

สกรีนช็อต: กำหนดเป้าหมายหน้าจออุปกรณ์ Android

  • เลือก กิจกรรมที่ว่างเปล่า ในพรอมต์ถัดไป

สกรีนช็อต: เพิ่มกิจกรรมไปยังหน้าจอมือถือ

  • ในขั้นตอนสุดท้าย เก็บชื่อกิจกรรมเป็น MainActivity และสร้างไฟล์โครงร่าง activity_main

สกรีนช็อต: ปรับแต่งหน้าจอกิจกรรม

การจัดเตรียมโครงการ

รวม RxJava, RxAndroid และไลบรารี Retrofit ใน app/build.gradle โปรดทราบว่าการรวม RxAndroid โดยปริยายจะรวม RxJava ด้วย อย่างไรก็ตาม แนวทางปฏิบัติที่ดีที่สุดคือการรวมไลบรารีทั้งสองไว้อย่างชัดเจนเสมอ เนื่องจาก RxAndroid ไม่ได้มี RxJava เวอร์ชันล่าสุดเสมอไป การรวม RxJava เวอร์ชันล่าสุดไว้อย่างชัดเจนรับประกันการใช้งานเวอร์ชันล่าสุด

 dependencies { compile 'com.squareup.retrofit2:adapter-rxjava:2.1.0' compile 'com.squareup.retrofit2:converter-gson:2.1.0' compile 'com.squareup.retrofit2:retrofit:2.1.0' compile 'io.reactivex:rxandroid:1.2.0' compile 'io.reactivex:rxjava:1.1.8' // ...other dependencies }

สร้างออบเจ็กต์ข้อมูล

สร้างคลาสอ็อบเจ็กต์ข้อมูล GitHubRepo คลาสนี้สรุปที่เก็บใน GitHub (การตอบสนองของเครือข่ายมีข้อมูลมากกว่า แต่เราสนใจเพียงบางส่วนเท่านั้น)

 public class GitHubRepo { public final int id; public final String name; public final String htmlUrl; public final String description; public final String language; public final int stargazersCount; public GitHubRepo(int id, String name, String htmlUrl, String description, String language, int stargazersCount) { this.id = id; this.name = name; this.htmlUrl = htmlUrl; this.description = description; this.language = language; this.stargazersCount = stargazersCount; } }

ชุดติดตั้งเพิ่ม

  • สร้างอินเทอร์เฟซ GitHubService เราจะส่งต่ออินเทอร์เฟซนี้ไปยัง Retrofit และ Retrofit จะสร้างการใช้งาน GitHubService
 public interface GitHubService { @GET("users/{user}/starred") Observable<List<GitHubRepo>> getStarredRepositories(@Path("user") String userName); }
  • สร้างคลาส GitHubClient นี่จะเป็นวัตถุที่เราจะโต้ตอบด้วยเพื่อโทรผ่านเครือข่ายจากระดับ UI

    • เมื่อสร้างการใช้งาน GitHubService ผ่าน Retrofit เราจำเป็นต้องส่งผ่าน RxJavaCallAdapterFactory เป็นตัวแปลงการโทรเพื่อให้การโทรในเครือข่ายสามารถส่งคืนวัตถุที่สังเกตได้ (จำเป็นต้องส่งอะแดปเตอร์การโทรสำหรับการโทรเครือข่ายที่ส่งคืนผลลัพธ์อื่นที่ไม่ใช่ Call )

    • นอกจากนี้เรายังต้องส่งผ่าน GsonConverterFactory เพื่อให้เราสามารถใช้ Gson เป็นวิธีการจัดการวัตถุ JSON ไปยังวัตถุ Java

 public class GitHubClient { private static final String GITHUB_BASE_URL = "https://api.github.com/"; private static GitHubClient instance; private GitHubService gitHubService; private GitHubClient() { final Gson gson = new GsonBuilder().setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES).create(); final Retrofit retrofit = new Retrofit.Builder().baseUrl(GITHUB_BASE_URL) .addCallAdapterFactory(RxJavaCallAdapterFactory.create()) .addConverterFactory(GsonConverterFactory.create(gson)) .build(); gitHubService = retrofit.create(GitHubService.class); } public static GitHubClient getInstance() { if (instance == null) { instance = new GitHubClient(); } return instance; } public Observable<List<GitHubRepo>> getStarredRepos(@NonNull String userName) { return gitHubService.getStarredRepositories(userName); } }

เค้าโครงการตั้งค่า

ถัดไป สร้าง UI อย่างง่ายที่แสดง repos ที่ดึงมาโดยให้ชื่อผู้ใช้ GitHub ที่ป้อนเข้า สร้าง activity_home.xml - เลย์เอาต์สำหรับกิจกรรมของเรา - โดยมีลักษณะดังนี้:

 <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:andro android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical"> <ListView android: android:layout_width="match_parent" android:layout_height="0dp" android:layout_weight="1"/> <LinearLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="horizontal"> <EditText android: android:layout_width="0dp" android:layout_height="wrap_content" android:layout_weight="1" android:hint="@string/username"/> <Button android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:text="@string/search"/> </LinearLayout> </LinearLayout>

สร้าง item_github_repo.xml - เค้าโครงรายการ ListView สำหรับวัตถุที่เก็บ GitHub - ด้วยสิ่งต่อไปนี้:

 <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:andro xmlns:tools="http://schemas.android.com/tools" android:orientation="vertical" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="6dp"> <TextView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:textSize="24sp" android:text tools:text="Cropper"/> <TextView android: android:layout_width="match_parent" android:layout_height="wrap_content" android:lines="2" android:ellipsize="end" android:textSize="16sp" android:layout_below="@+id/text_repo_name" tools:text="Android widget for cropping and rotating an image."/> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_repo_description" android:layout_alignParentLeft="true" android:textColor="?attr/colorPrimary" android:textSize="14sp" android:text tools:text="Language: Java"/> <TextView android: android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_below="@+id/text_repo_description" android:layout_alignParentRight="true" android:textColor="?attr/colorAccent" android:textSize="14sp" android:text tools:text="Stars: 1953"/> </RelativeLayout>

กาวทุกอย่างเข้าด้วยกัน

สร้าง ListAdapter ที่รับผิดชอบการเชื่อมโยงวัตถุ GitHubRepo ลงในรายการ ListView กระบวนการนี้เกี่ยวข้องกับการ item_github_repo.xml ให้เป็น View หากไม่มี View ที่นำกลับมาใช้ใหม่ มิฉะนั้น View ที่นำกลับมาใช้ใหม่จะถูกนำมาใช้ซ้ำเพื่อป้องกันการพองวัตถุ View มากเกินไป

 public class GitHubRepoAdapter extends BaseAdapter { private List<GitHubRepo> gitHubRepos = new ArrayList<>(); @Override public int getCount() { return gitHubRepos.size(); } @Override public GitHubRepo getItem(int position) { if (position < 0 || position >= gitHubRepos.size()) { return null; } else { return gitHubRepos.get(position); } } @Override public long getItemId(int position) { return position; } @Override public View getView(int position, View convertView, ViewGroup parent) { final View view = (convertView != null ? convertView : createView(parent)); final GitHubRepoViewHolder viewHolder = (GitHubRepoViewHolder) view.getTag(); viewHolder.setGitHubRepo(getItem(position)); return view; } public void setGitHubRepos(@Nullable List<GitHubRepo> repos) { if (repos == null) { return; } gitHubRepos.clear(); gitHubRepos.addAll(repos); notifyDataSetChanged(); } private View createView(ViewGroup parent) { final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); final View view = inflater.inflate(R.layout.item_github_repo, parent, false); final GitHubRepoViewHolder viewHolder = new GitHubRepoViewHolder(view); view.setTag(viewHolder); return view; } private static class GitHubRepoViewHolder { private TextView textRepoName; private TextView textRepoDescription; private TextView textLanguage; private TextView textStars; public GitHubRepoViewHolder(View view) { textRepoName = (TextView) view.findViewById(R.id.text_repo_name); textRepoDescription = (TextView) view.findViewById(R.id.text_repo_description); textLanguage = (TextView) view.findViewById(R.id.text_language); textStars = (TextView) view.findViewById(R.id.text_stars); } public void setGitHubRepo(GitHubRepo gitHubRepo) { textRepoName.setText(gitHubRepo.name); textRepoDescription.setText(gitHubRepo.description); textLanguage.setText("Language: " + gitHubRepo.language); textStars.setText("Stars: " + gitHubRepo.stargazersCount); } } }

กาวทุกอย่างเข้าด้วยกันใน MainActivity โดยพื้นฐานแล้วนี่คือ Activity ที่แสดงเมื่อเราเปิดแอปครั้งแรก ในที่นี้ เราขอให้ผู้ใช้ป้อนชื่อผู้ใช้ GitHub และสุดท้าย แสดงที่เก็บที่ติดดาวทั้งหมดโดยใช้ชื่อผู้ใช้นั้น

 public class MainActivity extends AppCompatActivity { private static final String TAG = MainActivity.class.getSimpleName(); private GitHubRepoAdapter adapter = new GitHubRepoAdapter(); private Subscription subscription; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); final ListView listView = (ListView) findViewById(R.id.list_view_repos); listView.setAdapter(adapter); final EditText editTextUsername = (EditText) findViewById(R.id.edit_text_username); final Button buttonSearch = (Button) findViewById(R.id.button_search); buttonSearch.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { final String username = editTextUsername.getText().toString(); if (!TextUtils.isEmpty(username)) { getStarredRepos(username); } } }); } @Override protected void onDestroy() { if (subscription != null && !subscription.isUnsubscribed()) { subscription.unsubscribe(); } super.onDestroy(); } private void getStarredRepos(String username) { subscription = GitHubClient.getInstance() .getStarredRepos(username) .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .subscribe(new Observer<List<GitHubRepo>>() { @Override public void onCompleted() { Log.d(TAG, "In onCompleted()"); } @Override public void onError(Throwable e) { e.printStackTrace(); Log.d(TAG, "In onError()"); } @Override public void onNext(List<GitHubRepo> gitHubRepos) { Log.d(TAG, "In onNext()"); adapter.setGitHubRepos(gitHubRepos); } }); } }

เรียกใช้แอพ

การเรียกใช้แอปควรแสดงหน้าจอพร้อมช่องป้อนข้อมูลเพื่อป้อนชื่อผู้ใช้ GitHub การค้นหาควรแสดงรายการ repos ที่ติดดาวทั้งหมด

สกรีนช็อตของแอพที่แสดงรายการ repos ที่ติดดาวทั้งหมด

บทสรุป

ฉันหวังว่านี่จะเป็นการแนะนำที่เป็นประโยชน์สำหรับ RxJava และภาพรวมของความสามารถพื้นฐาน มีแนวคิดที่ทรงพลังมากมายใน RxJava และฉันขอให้คุณสำรวจโดยขุดลึกลงไปในวิกิ RxJava ที่มีเอกสารประกอบอย่างดี

อย่าลังเลที่จะทิ้งคำถามหรือความคิดเห็นในช่องแสดงความคิดเห็นด้านล่าง คุณสามารถติดตามฉันทาง Twitter ได้ที่ @arriolachris ซึ่งฉันทวีตเกี่ยวกับ RxJava และทุกสิ่งเกี่ยวกับ Android

หากคุณต้องการแหล่งข้อมูลการเรียนรู้ที่ครอบคลุมเกี่ยวกับ RxJava คุณสามารถดู ebook ที่ฉันเขียนร่วมกับ Angus Huang บน Leanpub ได้

ที่เกี่ยวข้อง: คุณสมบัติสิบ Kotlin เพื่อเพิ่มการพัฒนา Android