對於客戶端來說,發版本身就屬於一種很高成本的行為。然而一個初創的app,會有各式各樣的問題,而在初期也不會像大型app一般有一套成熟的處理異常機制。而這往往會造成許多問題,那麼問題來了,如何在有限的開發資源下,做到客戶端的動態化。並且實現降級、ABTest等等一系列的行為呢?
What we want?
如前文提到,當版本迭代時,首當其衝的就是版本的問題。作為團隊的開發,我曾經花了很多時間在思考一個問題。如何能讓我們的架構模型變得優雅,變得像橡皮泥一般能隨著業務的發展迅速變形,特別是在一個創業公司,迫切需要一個強大的架構體系能支撐業務的快速發展。
那麼事情就變得很簡單了,在維持業務發展速度不變的情況下,儘可能減少發版的頻率,以技術的方式在不發版的情況下滿足“偽發版”。
當然,還有其他的一些想法,會在後文提到:
- ABTest
- 灰度
- 降級策略
- 動態呼叫本地服務
What to do?
有了想法知道,思路就比較清晰了,會針對性的對目的進行處理。
“偽發版”
對於app來說,偽發版的方案已經在業內比較成熟了。我們可以用weex/react native(以同一JS程式碼,在H5、iOS、Android同時實現同一頁面)的方式來實現在不發版的情況下實現頁面級別的更新。
我們試想一下,雖然weex實現了頁面的動態更新。但是卻沒法很優雅地處理新頁面與老頁面的跳轉關係。打個比方,一個native頁面A已經在程式碼裡寫死點選按鈕跳轉頁面B,此時就算用weex/rn寫了一個新頁面,也是很難跳轉的(當然如果兩個頁面都是weex/rn寫的,就不會存在這樣的問題,但是在大多數應用場景裡,還是會存在部分weex/rn,部分native的頁面)。
即 A -> B 是一個既定的關係。為了達到動態化,必然需要對整個app的導航進行統一處理。
這就是URLRoute要做的事情!
正如其名,URLRoute會有一個路由規則。
URLRoute
而動態化的祕訣就是將無法變動的邏輯程式碼維護成文字。即 程式碼 -> 文字 。
文字沒有編譯、簽名等等發版需要的步驟,它的更新完全是可以由檔案的更新完成,而這恰恰是不需要發版的。
拿iOS為例,我實現了一個URLRoute的庫,其中的處理流程可以由下圖所示:
其中URL與對應的class的對映是由一個檔案維護的,如圖所示:
通過維護一個dictionary(map)維護URL與本地頁面或服務的關係。
ABTest && 灰度
任何業務在發展的階段都不可避免會產生分歧,此時需要如果業務架構支援對不同的人群命中不同粒度的產品,並且給出資料以展示哪一種結果更好,這對於思考產品的人有更多的選擇餘地。
而通過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頁面等等。
How to do?
我們可以在要實現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。