仿RXJava功能--Android自制流處理框架思路及實現(xiàn)

前言及準備

背景

最近重溫了RxJava的機制,且正在學習結構化思維。故打算以結構化思維的方式,從零開始自己編寫一個與RxJava功能接近的Android框架練手。

技術準備

在閱讀該文章之前,我們至少應該掌握Java語法基礎、Android開發(fā)基礎、泛型、觀察者模式、Rxjava概要。
本文主要用于鍛煉思維,暫不引入注解、反射、APT等框架常見優(yōu)化技術,僅以純Java代碼方式設計。

思路及實現(xiàn)

本節(jié)我們將一步一步開設設計與實現(xiàn)我們自己的架構。我們需要從RxJava解決了什么、設計并實現(xiàn)自己的框架、復盤與改進三大方面來一步一步完成我們的框架。

RxJava解決了什么

在我們的代碼中,經(jīng)常會有如下形式的代碼結構(偽代碼):

optionA();
optionB();
optionC();
...
optionN();

這種一寫到底的代碼結構,大概是我們初學編程時候最簡單了形式了吧,如果所有代碼都能一直這樣寫下去就好了。

然而,由于業(yè)務場景的需要,我們按部就班的順序執(zhí)行代碼并不能解決所有問題。比如一個按鈕的點擊需要點擊事件觸發(fā)、一個網(wǎng)絡的請求需要等待數(shù)據(jù)···
所以,我們又產(chǎn)生了如下的代碼結構(偽代碼):

//option可能是一次點擊事件,也可能是一次線程切換,或者一次網(wǎng)絡請求回調。
optionA(new CallBack(){
    @Override
    doSomething(){
        ···
        optionB(new CallBack(){
                @Override
                doSomething(){
                    ···
                    optionC(new CallBack(){
                            @Override
                            doSomething(){
                                ...
                            }
                    });
                    ···
                }
        });
        ···
    }
});

上述代碼可以解決順序結構無法解決的問題,但缺點是代碼冗雜,耦合性高
冗雜:我們的直觀感受,就是這類代碼嵌套了好多層,導致結構復雜。如果在其內(nèi)部填充大量代碼的話,看起來將更加冗雜。
耦合高:上述代碼中,如果我們想去掉optionB操作,使optionA操作之后直接進行OptionC,那么我們無法簡單的調整,而是需要將OptionC及其內(nèi)部所有代碼移植到OptionA的回調下。這需要考慮對整個結構的影響,代碼復雜的話會很麻煩。

那么有什么方法可以使上述代碼變得簡潔、耦合低呢?有,那就是RxJava。
RxJava可以輸入一個事件流,經(jīng)過中間的各步驟轉化后,輸出另外一個事件流。而中間的轉化過程,我們也可以靈活調整。且RxJava不會導致層級過多。

設計并實現(xiàn)自己的框架

我們設計框架時只參考RxJava的作用,而不參考RxJava的邏輯。故命名、細節(jié)會與RxJava完全不同

首先,我們分析多層嵌套的回調代碼,不難發(fā)現(xiàn),其代碼結構可以分解為一個一個的獨立單元(后續(xù)都稱其為“單元”):

option(new CallBack(){
      doSomething{
        ···
      }
});

這個單元既可以作為其外層回調中的內(nèi)容,又可以作為其它內(nèi)容的外層回調。只不過它被回調與回調功能,都是通過增加代碼層級的方式來進行的,這也是產(chǎn)生問題的根源。

//option作為其外層回調中的內(nèi)容的場景
optionA(new CallBackA(){
    @Override
    doSomething(){
        ···
        option(new CallBack(){
                @Override
                doSomething(){
                }
        });
        ···
    }
});

//option作為其它內(nèi)容的外層回調的場景
option(new CallBack(){
    @Override
    doSomething(){
        ···
        optionA(new CallBackA(){
                @Override
                doSomething(){
                }
        });
        ···
    }
});

那么,我們只需要一個組件(后續(xù)都將稱其為“組件”),使其可以接管各個單元的回調與被回調方式,并且可以自由組裝每個單元,那么就可以完美解決這個問題了。

到現(xiàn)在,我們的編碼目標已經(jīng)很明確了,創(chuàng)建一個類,使其可以成為一個“組件”:

  • 接管單元回調: 暴露一個方法“doSomething”給用戶進行單元的裝載。回調單元執(zhí)行完成后可觸發(fā)下一組件的回調。
  • 支持組件組裝:支持綁定下一個組件的實例,使其成為鏈式結構。用于在自己的單元執(zhí)行完成后觸發(fā)下一個組件的回調
    偽代碼如下:
//Assembly意為組件,表示這是一個組件。
class Assembly{
    private Assembly assembly;//裝載下一個單元的組件。

    void setAssembly(Assembly a){
        assembly = a;//用于裝配下一個組件
    }

    abstract doSomething();  //自己的回調方法,暴露給用戶。用于使用戶裝載單元進來,并使用戶自行調用下一個組件的回調
}


//使用方式

//1、創(chuàng)建組件并裝載單元
Assembly a = new Assembly(){
    @Override
    doSomething(){
        //一個單元
        option(new CallBack(){
              ...
              assembly.doSomething();//自己的代碼處理完成后,執(zhí)行下一個組件的回調方法
        })
    }
};

Assembly b = new Assembly(){
    @Override
    doSomething(){
        //一個單元
        ...
        assembly.doSomething();//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};

Assembly c = new Assembly(){
    @Override
    doSomething(){
        //一個單元
        ...  
        assembly.doSomething();//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};
...
//組裝組件
a.setAssembly(b);
b.setAssembly(c);
...
//觸發(fā)事件流
a.doSomething();

可以看到,上述的代碼結構,已經(jīng)可以使我們的回調變得不再無限加深嵌套,并且可以自由組裝,降低了耦合性。

那我們的框架就此完成了嗎?答案是否定的。
我們可以回到我們最初的場景,我們?yōu)槭裁葱枰帉懚啻吻短谆卣{的代碼?究其原因有兩種:

  • 事件流前后順序限制(如線程切換)
  • 數(shù)據(jù)流前后順序限制(如網(wǎng)絡請求等)

我們上述的框架,是針對事件流的。而由于我們改變了代碼嵌套的形式,所以下層單元無法拿到上層單元的數(shù)據(jù)了。這是我們引入的新問題。

那么,我們需要基于上述組件新增如下功能:

  • 支持傳遞上層單元給到的數(shù)據(jù)(用于用戶進行操作轉化為其它數(shù)據(jù))
  • 支持傳出下層單元所需的數(shù)據(jù)(數(shù)據(jù)為單元邏輯轉化后的數(shù)據(jù))

我們暫時將數(shù)據(jù)格式統(tǒng)一視為Object類型,那么框架的偽代碼改動點為:

class Assembly{
    ...
    abstract doSomething(Object o);  //新增了形參“Object o”。即可實現(xiàn)數(shù)據(jù)的傳輸
}

//使用方式
//創(chuàng)建組件并裝載單元
Assembly a = new Assembly(){
    @Override
    doSomething(Object a){
        //一個單元
        option(new CallBack(){
              a parsedTo b
              assembly.doSomething(b);//自己的代碼處理完成后,執(zhí)行下一個組件的回調方法
        })
    }
};

Assembly b = new Assembly(){
    @Override
    doSomething(Object a){
        //一個單元
        a parsedTo b
        assembly.doSomething(b);//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};

Assembly c = new Assembly(){
    @Override
    doSomething(Object a){
        //一個單元
        a parsedTo b
        assembly.doSomething(b);//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};
...

//組裝組件
a.setAssembly(b);
b.setAssembly(c);
...
//觸發(fā)事件流(帶源數(shù)據(jù))
a.doSomething(objectInstance);//objectInstance:原始數(shù)據(jù)

我們的功能基本已經(jīng)完成,接下來我們使用泛型代替Object,提高代碼可讀性,減少Object強轉為各種數(shù)據(jù)類型的操作,并且使用鏈式調用來裝載各個單元,簡化代碼:

//Assembly意為組件,表示這是一個組件。
//泛型中的數(shù)據(jù)類型代表數(shù)據(jù)是由A類型轉化為B類型。即A為源數(shù)據(jù)類型,B為轉化后的數(shù)據(jù)類型
class Assembly<A,B>{  
    private Assembly<B,?> assembly;//裝載下一個單元的組件實例。其源數(shù)據(jù)是當前單元轉化后的類型B。

    Assembly<B,?> setAssembly(Assembly<B,?> a){
        assembly = a//用于裝配下一個組件
        return assembly;
    }
    
    abstract doSomething(A a);  //用于裝載自己的單元,自己的單元需要轉化前的數(shù)據(jù)A。
}


//使用方式
//創(chuàng)建組件并裝載單元
Assembly<String,Integer> a = new Assembly<String,Integer>(){
    @Override
    doSomething(String a){
        //一個單元
        option(new CallBack(){
              ...
              a parseto b  //b被轉化為Integer類型
              assembly.doSomething(b);//自己的代碼處理完成后,執(zhí)行下一個組件的回調方法
        })
    }
};

Assembly<Integer,Integer> b = new Assembly<Integer,Integer>(){
    @Override
    doSomething(Integer a){
        //一個單元
        ...
        a parseTo b  //a與b都是Integer類型
        assembly.doSomething();//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};

Assembly<Integer,Float> c = new Assembly<Integer,Float>(){
    @Override
    doSomething(Integer a){
        //一個單元
        ...  
        a parseTo b  //b被轉化為Float類型
        assembly.doSomething();//自己的代碼處理完成后,執(zhí)行下一個組件的回調代碼
    }
};
...

//組裝單元
a
  .setAssembly(b)
  .setAssembly(c);
...
//觸發(fā)
a.doSomething(str);//輸入String類型數(shù)據(jù)

上述代碼傳入的源數(shù)據(jù)經(jīng)歷了String > Integer > Integer >String的轉化。
至此,我們嵌套回調所帶來的所有問題得到了解決。

復盤與改進

總結:我們構建自己的框架時,首先確認了需要解決的問題,然后進行問題的解決,接著思考問題解決后是否引入了新的問題,最后進行查漏補缺,完善框架。使一個框架在解決痛點的同時,不引入新的痛點
自夸:本文在寫作過程中,緊抓核心思想,圍繞著解決問題的主流程,使得框架設計達到了極簡,在使用了最少的代碼的前提下,表達了完整的框架思想。
缺點:該框架目前還有很多不完善的地方。比如沒有實現(xiàn)背壓、泛型使用復雜等。還有很大完善控件,目前僅供學習使用。

關于背壓問題:RxJava的一個重要優(yōu)勢。而我們要做到背壓,其核心思想是數(shù)據(jù)流不直接調用,在下一個組件完成任務處理后,通知上層組件。這樣就可以由上層組件控制當前未處理完成的任務數(shù)量,然后就山高憑魚躍,通過各種機制實現(xiàn)自己的背壓(比如仿線程池的機制)。
關于泛型問題:目前泛型存在的問題為:鏈式連續(xù)調用裝配組件的方法,會導致第三個組件的裝配方法所需入?yún)锳ssembly<?,?>。從而導致傳入的類型不匹配,編譯器報錯。目前需要單獨創(chuàng)建組件為一個變量規(guī)避類型校驗。優(yōu)化方法其實有很多,比如不使用泛型,而是使用注解加APT生成有固定數(shù)據(jù)類型的類,然后調用該類。
當然,我們也可以內(nèi)部封裝多種已裝載常用單元的組件來當工具類(比如線程切換組件)等。還有更多新功能需要根據(jù)實際情況不斷的迭代維護。

后記

寫這一篇文章之前,我自己著手寫過一個框架雛形。但是寫作過程中,又找到了諸多改進點。所以說學無止境,多學多練。思維也是在一次次的優(yōu)化中升華的,當一件事情練習了一定的次數(shù),那么總會找到自己的方法論,使自己變得更強,共勉。

此篇文章用于學習交流,個人經(jīng)驗僅供參考,有錯誤歡迎指正

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容