對於客戶端來說,發版本身就屬於一種很高成本的行為。然而一個初創的app,會有各式各樣的問題,而在初期也不會像大型app一般有一套成熟的處理異常機制。而這往往會造成許多問題,那麼問題來了,如何在有限的開發資源下,做到客戶端的動態化。並且實現降級、ABTest等等一系列的行為呢?
如前文提到,當版本迭代時,首當其沖的就是版本的問題。作為團隊的開發,我曾經花了很多時間在思考一個問題。如何能讓我們的架構模型變得優雅,變得像橡皮泥一般能隨著業務的發展迅速變形,特別是在一個創業公司,迫切需要一個強大的架構體系能支撐業務的快速發展。
那麼事情就變得很簡單了,在維持業務發展速度不變的情況下,盡可能減少發版的頻率,以技術的方式在不發版的情況下滿足“偽發版”。
當然,還有其他的一些想法,會在後文提到:
ABTest
灰度
降級策略
動態調用本地服務
有了想法知道,思路就比較清晰了,會針對性的對目的進行處理。
對於app來說,偽發版的方案已經在業內比較成熟了。我們可以用weex/react native(以同一JS代碼,在H5、iOS、Android同時實現同一頁面)的方式來實現在不發版的情況下實現頁面級別的更新。
我們試想一下,雖然weex實現了頁面的動態更新。但是卻沒法很優雅地處理新頁面與老頁面的跳轉關系。打個比方,一個native頁面A已經在代碼裡寫死點擊按鈕跳轉頁面B,此時就算用weex/rn寫了一個新頁面,也是很難跳轉的(當然如果兩個頁面都是weex/rn寫的,就不會存在這樣的問題,但是在大多數應用場景裡,還是會存在部分weex/rn,部分native的頁面)。
即 A -> B 是一個既定的關系。為了達到動態化,必然需要對整個app的導航進行統一處理。
這就是URLRoute要做的事情!
正如其名,URLRoute會有一個路由規則。
而動態化的秘訣就是將無法變動的邏輯代碼維護成文本。即 代碼 -> 文本 。
文本沒有編譯、簽名等等發版需要的步驟,它的更新完全是可以由文件的更新完成,而這恰恰是不需要發版的。
拿iOS為例,我實現了一個URLRoute的庫,其中的處理流程可以由下圖所示:
其中URL與對應的class的映射是由一個文件維護的,如圖所示:
通過維護一個dictionary(map)維護URL與本地頁面或服務的關系。
任何業務在發展的階段都不可避免會產生分歧,此時需要如果業務架構支持對不同的人群命中不同粒度的產品,並且給出數據以展示哪一種結果更好,這對於思考產品的人有更多的選擇余地。
而通過URLRoute,我們完全可以針對不同的人群派發不同的映射文件達到ABTest的目的。
甚至在某一個版本上可以做到灰度,10%->20%->...->100%。
試想一下,如果以上線的項目產生了重大BUG怎麼辦。對於iOS來說,可以通過Runtime的方式達到patch。但是對於Android來說,現有的三種方案:classloader,更改jar包,更改apk。後兩者更不用說,我們並不是一個apk。而前者在現如今的動態方案中逐漸被拋棄。其實都不適合我們,這些其實都需要一套完整的解決方案,並不僅僅是導入庫就行,還要考慮與server的交互等。
那麼如何做到一個方式,能以最簡單的方式做到在產生BUG時,以最小的成本並且在兩端以同樣的策略去做到修復或者降級呢?
答案是我們可以通過更改映射文件,將原先有問題的URL對應的value替換掉,即A->B變成A->C(C可以是一個error頁面,也可以是一個weex/rn頁面)。當然你會問如果本地沒有實現C怎麼辦?沒關系,我們刪掉那條URL記錄,當URLRoute攔截不到URL時,會以webview的形式打開,這樣就降級成H5了。
URLRoute不僅僅處理了頁面之間的跳轉邏輯,也可以處理service。這裡的service可以理解為任何native的行為,比如登錄、強制更新、發網絡請求等等。
我們可以事先注冊好對應的module,通過URL調用對應的service。
這是很重要的,比如我們產生了重大BUG,我們就可以強制登出用戶,甚至強制所有頁面都跳到error頁面等等。
我們可以在要實現URLRoute的class,在plist文件裡聲明,key為對應的host + path,value為對應的class name。
// 默認調用方式 OpenURL(@"http://m.kuailejim.com/home?id=552131"); // 如果需要傳URL不能表達的參數(Object參數) OpenURLWithParams(@"http://m.kuailejim.com/home?id=552131", paramsDictionary);
這裡的scheme為http,是因為我在URLRoute裡做了處理,當這裡的key(m.kuailejim.com/home)沒有被攔截到即在plist文件中並沒有找到對應的class來處理時,我會判斷scheme是否為http/https,如果是,則會跳到H5以實現降級。
我們知道,URL傳參對於object類型是不友好的,於是需要實現一個能帶dictionary(map)的方法,並且在URLRoute內部實現基於key/value形式參數的解析。
對應的class需要實現下述方法,這樣URLRoute通過runtime動態創建對應的class,並且調用class已經實現的代理方法達到處理的效果:
- (id)initWithUrlRequest:(URLRouteRequest *)request;
這裡還需要注意,在URLRoute裡要hook對應的viewdidappear方法來拿到當前的view controller,才能做到push和model的操作。如果是model操作還需實現一個navigation controller(因為有可能會在新的頁面裡push)
AOP統一處理,例如想在跳轉時對所有url記錄埋點等功能,這個很好理解,只要在URLRoute裡在實現攔截前統一做處理就行。
這只是客戶端動態化的第一步,baby steps to big dreams。