RxJava開發精要3 - 向響應式世界問好

向響應式世界問好

在上一章中,我們對觀察者模式有個理論上的快速概述。我們也看了從頭開始、從列表、或者從已經存在的函數來創建Observables。在本章中,我們將用我們學到的來創建我們第一個響應式Android應用程序。首先,我們需要搭建環境,導入需要的庫和有用的庫。然后我們將創建一個簡單的應用程序,在不同的flavors中包含幾個用RxJava填充的RecycleView items。

啟動引擎

我們將使用IntelliJ IDEA/Android Studio來創建這個工程,因此你會對截圖看起來比較熟悉。

讓我們開始創建一個新的Android工程。你可以創建你自己的工程或者用本書中提供的導入。選擇你自己喜歡的創建方式這取決于你。

如果你想用Android Studio創建一個新的工程,通常你可以參考官方文檔:http://developer.android.com/intl/zh-cn/training/basics/firstapp/creating-project.html

依賴

很明顯,我們將使用Gradle來管理我們的依賴列表。我們的build.gradble文件看起來像這樣: 正如你看到的我們引入了RxAndroid。RxAndroid是RxJava的增強版,尤其是針對Android設計的。

RxAndroid

RxAndroid是RxJava家族的一部分。它基于RxJava1.0.x,在普通的RxJava基礎上添加了幾個有用的類。大多數情況下,它為Android添加了特殊的調度器。我們將在第七章Schedulers-Defeating the Android MainThread Issue再討論它。

工具

出于實用,我們引入了Lombok 和 Butter Knife。這兩個可以幫助我們在Android應用程序中少寫許多模板類代碼。

Lombok

Lombok使用注解的方式為你生成許多代碼。我們將使用它老生成getter/settertoString()equals()hashCode()。它借助于Gradle依賴和一個Android Studio插件。

Butter Knife

Butter Knife使用注解的方式來幫助我們免去寫findViewById()和設置點擊監聽的痛苦。至于Lombok,我們可以通過導入依賴和安裝Android Studio插件來獲得更好的體驗。

Retrolambda

最后,我們導入Retrolambda,是因為我們開發的Android是基于Java 1.6,然后我們可以借助它來實現Java 8 Lambda函數從而減少許多模板代碼。

我們的第一個Observable

在我們的第一個列子里,我們將檢索安裝的應用列表并填充RecycleView的item來展示它們。我們也設想一個下拉刷新的功能和一個進度條來告知用戶當前任務正在執行。

首先,我們創建Observable。我們需要一個函數來檢索安裝的應用程序列表并把它提供給我們的觀察者。我們一個接一個的發射這些應用程序數據,將它們分組到一個單獨的列表中,以此來展示響應式方法的靈活性。

private Observable<AppInfo> getApps(){
    return Observable.create(subscriber -> {
        List<AppInfoRich> apps = new ArrayList<AppInfoRich>();

        final Intent mainIntent = new Intent(Intent.ACTION_MAIN,null);
        mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);

        List<ResolveInfo> infos = getActivity().getPackageManager().queryIntentActivities(mainIntent, 0);

        for(ResolveInfo info : infos){
            apps.add(new AppInfoRich(getActivity(),info));
        }

        for (AppInfoRich appInfo:apps) {
            Bitmap icon = Utils.drawableToBitmap(appInfo.getIcon());
            String name = appInfo.getName();
            String iconPath = mFilesDir + "/" + name;
            Utils.storeBitmap(App.instance, icon,name);

            if (subscriber.isUnsubscribed()){
                return;
            }
            subscriber.onNext(new AppInfo(name,iconPath,appInfo.getLastUpdateTime()));                
        }
        if (!subscriber.isUnsubscribed()){
            subscriber.onCompleted();
        }
    });
}

AppInfo對象如下:

@Data
@Accessors(prefix = "m")
public class AppInfo implements Comparable<Object> {

    long mLastUpdateTime;
    String mName;
    String mIcon;

    public AppInfo(String nName, long lastUpdateTime, String icon) {
        mName = nName;
        mIcon = icon;
        mLastUpdateTime = lastUpdateTime;
    }

    @Override
    public int compareTo(Object another) {
        AppInfo f = (AppInfo)another;
        return getName().compareTo(f.getName());
    }
}

需要重點注意的是在發射新的數據或者完成序列之前要檢測觀察者的訂閱情況。這樣的話代碼會更高效,因為如果沒有觀察者等待時我們就不生成沒有必要的數據項。

此時,我們可以訂閱Observable并觀察它。訂閱一個Observable意味著當我們需要的數據進來時我們必須提供對應的操作來執行它。

當前的場景是什么?我們展示一個進度條來等待數據。當數據到來時,我們需要隱藏掉進度條,填充list,最終展示列表。現在,我們知道當一切都準備好了該做什么。那么錯誤的場景呢?對于錯誤這種情況,我們僅僅是用Toast展示一個錯誤的信息。

使用Butter Knife,我們得到list和下拉刷新組件的引用:

@InjetcView(R.id.fragment_first_example_list)
RecyclerView mRecycleView;

@InjectView(R.id.fragment_first_example_swipe_container)
SwipeRefreshLayout mSwipeRefreshLayout;

我們使用Android 5的標準組件:RecyclerView和SwipeRefreshLayout。截屏展示了我們這個簡單App的list Fragment的layout文件:

我們使用一個下拉刷新方法,因此列表數據可以來自初始化加載,或由用戶觸發的一個刷新動作。針對這兩個場景,我們用同樣的行為,因此我們把我們的觀察者放在一個易被復用的函數里面。下面是我們的觀察者,定義了成功、失敗、完成要做的事情:

private void refreshTheList() {
    getApps().toSortedList()
            .subscribe(new Observable<List<AppInfo>>() {

                @Override
                public void onCompleted() {
                    Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }

                @Override
                public void onNext(List<AppInfo> appInfos) {
                    mRecyclerView.setVisibility(View.VISIBLE);
                    mAdapter.addApplications(appInfos);
                    mSwipeRefreshLayout.setRefreshing(false);
                }
            });
}

定義一個函數使我們能夠用同樣一個block來處理兩種場景成為了可能。當fragment加載時我們只需調用refreshTheList()方法并設置refreshTheList()方法作為用戶下拉這一行為所觸發的方法。

mSwipeRefreshLayout.setOnRefreshListener(this::refreshTheList);

我們第一個例子現在完成了,運行跑一下。

從列表創建一個Observable

在這個例子中,我們將引入from()函數。使用這個特殊的“創建”函數,我們可以從一個列表中創建一個Observable。Observable將發射出列表中的每一個元素,我們可以通過訂閱它們來對這些發出的元素做出響應。

為了實現和第一個例子同樣的結果,我們在每一個onNext()函數更新我們的適配器,添加元素并通知插入。

我們將復用和第一個例子同樣的結構。主要的不同的是我們不再檢索已安裝的應用列表。列表由外部實體提供:

mApps = ApplicationsList.getInstance().getList();

獲得列表后,我們僅需將它響應化并填充RecyclerView的item:

private void loadList(List<AppInfo> apps) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.from(apps)
            .subscribe(new Observable<AppInfo>() {

                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }

                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo); 
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}

正如你看到的,我們將已安裝的應用程序列表作為參數傳進from()函數,然后我們訂閱生成的Observable。觀察者和我們第一個例子中的觀察者十分相像。一個主要的不同是我們在onCompleted()函數中停掉進度條是因為我們一個一個的發射元素;第一個例子中的Observable發射的是整個list,因此在onNext()函數中停掉進度條的做法是安全的。

再多幾個例子

在這一節中,我們將基于RxJava的just(),repeat(),defer(),range(),interval(),和timer()方法展示一些例子。

just()

假如我們只有3個獨立的AppInfo對象并且我們想把他們轉化為Observable并填充到RecyclerView的item中:

List<AppInfo> apps = ApplicationsList.getInstance().getList();

AppInfo appOne = apps.get(0);

AppInfo appTwo = apps.get(10);

AppInfo appThree = apps.get(24);

loadApps(appOne,appTwo,appThree);

我們可以像我們之前的例子那樣檢索列表并提取出這三個元素。然后我們將他們傳到這個loadApps()函數里面:

private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.just(appOne,appTwo,appThree)
            .subscribe(new Observable<AppInfo>() {

                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }

                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo); 
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}

正如你看到的,代碼和之前的例子很像。這種方法讓我們有機會來考慮一下代碼的復用。

你可以將一個函數作為參數傳給just()方法,你將會得到一個已存在代碼的原始Observable版本。在一個新的響應式架構的基礎上遷移已存在的代碼,這個方法可能是一個有用的開始點。

repeat()

假如你想對一個Observable重復發射三次數據。例如,我們用just()例子中的Observable:

private void loadApps(AppInfo appOne,AppInfo appTwo,AppInfo appThree) {
    mRecyclerView.setVisibility(View.VISIBLE);
    Observable.just(appOne,appTwo,appThree)
            .repeat(3)
            .subscribe(new Observable<AppInfo>() {

                @Override
                public void onCompleted() {
                    mSwipeRefreshLayout.setRefreshing(false);
                    Toast.makeText(getActivity(), "Here is the list!", Toast.LENGTH_LONG).show();
                }

                @Override
                public void onError(Throwable e) {
                    Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
                    mSwipeRefreshLayout.setRefreshing(false);
                }

                @Override
                public void onNext(AppInfo appInfo) {
                    mAddedApps.add(appInfo); 
                    mAdapter.addApplication(mAddedApps.size() - 1,appInfo);
                }
            });
}

正如你看到的,我們在just()創建Observable后追加了repeat(3),它將會創建9個元素的序列,每一個都單獨發射。

defer()

有這樣一個場景,你想在這聲明一個Observable但是你又想推遲這個Observable的創建直到觀察者訂閱時。看下面的getInt()函數:

private Observable<Integer> getInt(){
    return Observable.create(subscriber -> {
        if(subscriber.isUnsubscribed()){
            return;
        }
        App.L.debug("GETINT");
        subscriber.onNext(42);
        subscriber.onCompleted();
    });
}

這比較簡單,并且它沒有做太多事情,但是它正好為我們服務。現在,我們可以創建一個新的Observable并且應用defer():

Observable<Integer> deferred = Observable.defer(this::getInt);

這次,deferred存在,但是getInt() create()方法還沒有調用:logcat日志也沒有“GETINT”打印出來:

deferred.subscribe(number -> {
    App.L.debug(String.valueOf(number));
});

但是一旦我們訂閱了,create()方法就會被調用并且我們也可以在logcat日志中得到下賣弄兩個:GETINT和42。

range()

你需要從一個指定的數字X開始發射N個數字嗎?你可以用range:

Observable.range(10,3)
    .subscribe(new Observable<Integer>() {

        @Override
        public void onCompleted() {
            Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNext(Integer number) {
            Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show();
        }
    });

range()函數用兩個數字作為參數:第一個是起始點,第二個是我們想發射數字的個數。

interval()

interval()函數在你需要創建一個輪詢程序時非常好用。

Subscription stopMePlease = Observable.interval(3,TimeUnit.SECONDS)
    .subscribe(new Observable<Integer>() {

        @Override
        public void onCompleted() {
            Toast.makeText(getActivity(), "Yeaaah!", Toast.LENGTH_LONG).show();
        }

        @Override
        public void onError(Throwable e) {
            Toast.makeText(getActivity(), "Something went wrong!", Toast.LENGTH_SHORT).show();
        }

        @Override
        public void onNext(Integer number) {
            Toast.makeText(getActivity(), "I say " + number, Toast.LENGTH_SHORT).show();
        }
    });

interval()函數的兩個參數:一個指定兩次發射的時間間隔,另一個是用到的時間單位。

timer()

如果你需要一個一段時間之后才發射的Observable,你可以像下面的例子使用timer()

Observable.timer(3,TimeUnit.SECONDS)
    .subscribe(new Observable<Long>() {

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Long number) {
            Log.d("RXJAVA", "I say " + number);
        }
    });

它將3秒后發射0,然后就完成了。讓我們使用timer()的第三個參數,就像下面的例子:

Observable.timer(3,3,TimeUnit.SECONDS)
    .subscribe(new Observable<Long>() {

        @Override
        public void onCompleted() {

        }

        @Override
        public void onError(Throwable e) {

        }

        @Override
        public void onNext(Long number) {
            Log.d("RXJAVA", "I say " + number);
        }
    });

用這個代碼,你可以創建一個以初始值來延遲(上一個例子是3秒)執行的interval()版本,然后每隔N秒就發射一個新的數字(前面的例子是3秒)。

總結

在本章中,我們創建了第一個由RxJava強化的Android應用程序。我們從頭、從已有的列表、從已有的函數來創建Observable。我們也學習了如何創建重復發射的Observables,間隔發射的Observables以及延遲發射的Observables。

在下一章中,我們將掌握過濾操作,能夠從我們接收到的序列中創建我們需要的序列。



25选5玩法中奖