原文地址
題記:天貓App長大了,已經(jīng)長成了流量以千萬計規(guī)模的App,當下至少有10個團隊在直接維護天貓App翠肘。在App長大叁丧,團隊擴充的過程中解耦是一個永恒的話題,而界面解耦又是App架構(gòu)的重中之重刃滓。
統(tǒng)跳協(xié)議是天貓App統(tǒng)一跳轉(zhuǎn)協(xié)議,主要負責天貓App界面之間的串聯(lián)耸弄,也就是界面跳轉(zhuǎn)服務(wù)咧虎。Rewrite
引擎是與之配合的一套URL重寫引擎,可以通過配置實現(xiàn)重寫規(guī)則動態(tài)化计呈。
歷史上的今天
統(tǒng)跳協(xié)議的前身是一套叫做internal的協(xié)議砰诵,internal要重點解決的問題是在WebView和推送通知中如何跳轉(zhuǎn)到指定的界面,進一步在任何動態(tài)場景下如何跳轉(zhuǎn)到指定界面捌显。在這樣的思路下茁彭,internal中定義了多種協(xié)議格式,如:
tmall://tmallclient/?{"action":""}
internal:url=
link:url=
tmall://mobile.tmall.com/page/
幾乎每一種場景都有一種格式的協(xié)議與之對應(yīng)扶歪。在具體操作過程中這些協(xié)議都以URL表現(xiàn)出來理肺。不難看出,這套協(xié)議最大的問題在于協(xié)議格式異構(gòu)化嚴重善镰,且不符合W3C的URL標準妹萨。隨著App規(guī)模的擴大,場景日趨復雜炫欺,界面越來越多乎完,這套協(xié)議的弊端也日益顯露。
而在天貓App開始從百萬級沖擊千萬級的時候品洛,我們認識到一套格式統(tǒng)一树姨,符合標準,規(guī)則簡潔的協(xié)議非常必要桥状。這套協(xié)議的任務(wù)也絕不是解決固定場景跳轉(zhuǎn)帽揪,而是完全托管整個App的跳轉(zhuǎn)工作,從而實現(xiàn)全App界面解耦和跳轉(zhuǎn)動態(tài)化辅斟。因此台丛,我們重新設(shè)計了界面協(xié)議,形成了當下這套規(guī)范——統(tǒng)跳協(xié)議。配合統(tǒng)跳協(xié)議挽霉,為了解決更多細節(jié)問題和跨平臺問題,我們還設(shè)計了Rewrite引擎與之配合变汪。
統(tǒng)跳協(xié)議
統(tǒng)跳協(xié)議設(shè)計之初就保留了很強的可擴展性侠坎,為接下來更豐富的場景預留了能力。上文講到了統(tǒng)跳協(xié)議在界面跳轉(zhuǎn)中作用裙盾,而事實上界面跳轉(zhuǎn)僅僅是這套方案的一個典型場景实胸,一個最佳實踐。界面跳轉(zhuǎn)在統(tǒng)跳協(xié)議的框架中被認為成一個服務(wù)番官,而跳轉(zhuǎn)到哪一個界面則是由服務(wù)內(nèi)部實現(xiàn)決定的庐完。
注冊一個服務(wù)
服務(wù)通過聲明URL的方式注冊到統(tǒng)跳協(xié)議中,這個聲明發(fā)生在服務(wù)所屬模塊內(nèi)部的一個配置文件中徘熔,而這個配置文件被注冊到統(tǒng)跳協(xié)議里门躯。也就是說,整個App中的每一個模塊都要注冊一個配置文件到統(tǒng)跳協(xié)議酷师,統(tǒng)跳協(xié)議在初始化過程中會遍歷配置文件列表讶凉,逐一加載這些模塊配置,根據(jù)配置信息把一個一個的模塊服務(wù)注冊到協(xié)議中山孔。
統(tǒng)跳協(xié)議要求調(diào)用服務(wù)的URL必須是符合W3C URL標準的懂讯,服務(wù)注冊使用的URL只能包括host和path兩部分,其中host是必須的台颠,path則可選褐望。當統(tǒng)跳協(xié)議接收一個跳轉(zhuǎn)請求的URL后,先根據(jù)該URL的host和path兩部分作為條件查找已注冊的服務(wù)串前,再初始化對應(yīng)服務(wù)瘫里,把URL交給服務(wù)實例執(zhí)行后續(xù)操作。
如何實現(xiàn)服務(wù)
統(tǒng)跳協(xié)議聲明了一個服務(wù)接口酪呻,這個接口中只有一個方法减宣,服務(wù)必須由該接口實現(xiàn)而來。每一個服務(wù)可以通過實現(xiàn)接口中聲明的方法玩荠,使用參數(shù)中傳遞來的完整URL漆腌,參數(shù)列表和調(diào)用發(fā)起者指針,執(zhí)行具體業(yè)務(wù)邏輯阶冈。
例如分享服務(wù)闷尿,以iOS為例:實現(xiàn)了TMShareUrlHandler
服務(wù)。
@interface TMShareUrlHandler : NSObject<AliAppURLHandler>
@end
@implementation TMShareUrlHandler
#pragma mark - URL調(diào)用分享組件
- (id)handleUrl:(NSURL *)url withTarget:(id)target withParams:(id)params {
// 省略代碼詳情
return nil;
}
@end
在分享模塊的配置文件中聲明該服務(wù)的URL為sharekit.tm/doShare
女坑。
這份配置文件在分享模塊里:
分享模塊的配置文件sharekit_bundle.plist
也注冊到統(tǒng)跳協(xié)議中填具。
這份配置文件在統(tǒng)跳協(xié)議模塊里:
統(tǒng)跳協(xié)議如何處理界面跳轉(zhuǎn)
界面跳轉(zhuǎn)是統(tǒng)跳協(xié)議的初衷,也是統(tǒng)跳協(xié)議最重要的任務(wù)。因此在統(tǒng)跳協(xié)議服務(wù)注冊機制中劳景,為界面服務(wù)注冊做了更精細的定制開發(fā)誉简。
上文提到跳轉(zhuǎn)服務(wù)是一個單一服務(wù),而界面則成百上千盟广,所以在界面注冊和服務(wù)注冊中出現(xiàn)了沖突闷串。本著降低開發(fā)成本的原則,我們又希望把同一個模塊中界面注冊和服務(wù)注冊放在一起筋量。所以在統(tǒng)跳協(xié)議中做了如下訂制:
- 默認注冊跳轉(zhuǎn)服務(wù)
跳轉(zhuǎn)服務(wù)是默認存在的烹吵,在統(tǒng)跳協(xié)議初始化過程中這個服務(wù)就已經(jīng)初始化了。
- 給界面注冊提供特殊的標記
上文中可以看到在注冊分享服務(wù)的配置中object
字段是服務(wù)的類名桨武,若界面注冊也按照這個規(guī)則肋拔,那么界面的類就會被認為成一個服務(wù),在調(diào)用過程中必然會出現(xiàn)錯誤呀酸。因此我們約定凉蜂,界面注冊需要在類名前加#
標示。
如此一來七咧,在統(tǒng)跳協(xié)議初始化過程中跃惫,默認加載跳轉(zhuǎn)服務(wù)。當調(diào)用發(fā)生艾栋,解析URL查找到的對應(yīng)對象帶有#
爆存,則認為這是一個界面,則初始化這個對象蝗砾,但不對其調(diào)用處理URL的方法先较,而是托管給已注冊的跳轉(zhuǎn)服務(wù)。跳轉(zhuǎn)服務(wù)則根據(jù)URL和初始化的界面對象執(zhí)行跳轉(zhuǎn)服務(wù)悼粮。
Rewrite
Rewrite引擎的思路來源于Web容器中的Rewrite機制闲勺,主要解決天貓App中URL平臺展現(xiàn)一致性的問題。
天貓App中所有界面都是通過URL來標示的扣猫,然而標示Native界面的URL全部都建立在Native規(guī)范下菜循,無法和其他平臺對應(yīng)起來,而Rewrite引擎通過重寫URL來實現(xiàn)平臺一致性申尤。
例如:商品詳情頁面在PC Web的URL是https://detail.tmall.com/item.htm
癌幕,在Mobile Web則是https://detail.m.tmall.com/item.htm
,在Native聲明的是tmall://page.tm/itemDetail
昧穿。三者各不相同勺远。PC Web和Mobile Web可以通過判斷瀏覽器的UA識別環(huán)境,從而通過跳轉(zhuǎn)實現(xiàn)一致性时鸵,也就是說在手機瀏覽器訪問PC Web的URL胶逢,會通過一次302轉(zhuǎn)到Mobile Web的URL。而Native App的環(huán)境具有一定的特殊性,Native界面則無法通過類似302這樣的跳轉(zhuǎn)來實現(xiàn)無感知切換初坠,而Rewrite引擎就是來解決這個問題的和簸。首先,無論是Native還是Web某筐,在天貓App中他們兩兩之間的跳轉(zhuǎn)都被統(tǒng)跳協(xié)議托管比搭,而統(tǒng)跳協(xié)議在執(zhí)行跳轉(zhuǎn)操作之前會把原始URL放入Rewrite引擎中做一次Rewrite操作。這樣一來南誊,Rewrite引擎就根據(jù)配置規(guī)則,把原始URL轉(zhuǎn)換成適用于天貓App的目標URL蜜托,實現(xiàn)了URL表現(xiàn)平臺一致性抄囚。
原理
Rewrite引擎的原理非常簡單,模擬Web容器(Apache/Nginx等)的Rewrite配置橄务,根據(jù)配置把傳入的原始URL進行重寫幔托,返回重寫后的目標URL,交給統(tǒng)跳協(xié)議處理蜂挪。
配置是通過正則表達式描述的Rewrite規(guī)則列表重挑,這份列表通過貓客的配置中心實現(xiàn)動態(tài)更新。
Rewrite規(guī)則
- 每條Rewrite規(guī)則中有三個字段:模式串棠涮,轉(zhuǎn)換串和標記位
- 模式串:即正則表達式谬哀,用于匹配原始URL
- 轉(zhuǎn)換串:即需要被轉(zhuǎn)換成目標URL的描述
- 標記位:以西文逗號分隔的
標記位
,包括表示匹配則終止的l
严肪,需要進行店鋪域名查詢的s
等
- Rewrite規(guī)則按行整理史煎,并自上而下按順序逐行匹配
轉(zhuǎn)換模板中的保留字
-
變量
變量由變量標示符和變量名組合而成,如:
$0
驳糯,$#1
篇梭,query
,$#fragment
等酝枢。變量被用在轉(zhuǎn)換串中描述轉(zhuǎn)換后的目標URL中的值恬偷。- 變量標示符:
$
,$$
和$#
-
$
原變量的值 -
$$
對原變量做URL Encode
-
$#
自動識別編碼帘睦,對原變量做URL Decode
-
$$$
自動識別編碼袍患,對原變量做URL Decode
,再以UTF8
做URL Encode
-
- 變量名:數(shù)字(從
0
開始)官脓,枚舉(scheme
协怒,host
,port
卑笨,path
孕暇,query
,fragment
,shopid
)- 數(shù)字:
0 -
表示整個URL
妖滔,1~n -
表示正則中使用圓括號取出的參數(shù) - 枚舉:
scheme
隧哮,host
,port
座舍,path
沮翔,query
,fragment
表示標準URL中的相應(yīng)部分曲秉;shopid
表示對個性店鋪域名查詢后得到的shopid
- 數(shù)字:
- 變量標示符:
-
標記位
即上述規(guī)則中的標記位
Rewrite引擎查詢流程
- 取出規(guī)則列表中的首條規(guī)則
- 以模式串為模板對原始URL做匹配采蚀,并得到模式串定義的參數(shù)表
- 若匹配成功則繼續(xù)進行,否則進入下一條規(guī)則承二,從2開始進行下一輪匹配
- 查看該條規(guī)則是否包含
s
標記位榆鼠,若包含,則使用原始串做一次個性域名的查詢 - 使用1的結(jié)果和重寫串對原始URL進行重寫操作亥鸠,得到目標URL
- 查看該條規(guī)則是否包含
l
標記位:- 若包含妆够,則結(jié)束匹配,返回目標URL
- 若不包含负蚊,則把目標URL賦值給原始URL神妹,并進入下一條規(guī)則,從2開始下一輪匹配
- 直到最后一條規(guī)則結(jié)束家妆,返回目標URL
舉例
上述提到過商品詳情頁的例子鸵荠,在Rewrite配置中就體現(xiàn)為:
模式串 | 轉(zhuǎn)換串 | 標記位 |
---|---|---|
^(?:https?:)?\/\/detail(?:.m)?.tmall.com\/?item.htm\?(.*) |
tmall://page.tm/itemDetail?$1 |
l |
在這條規(guī)則的保護下,PC Web和Mobile Web下的商品詳情URL在天貓App中都會被攔截到Native商品詳情頁面揩徊,可以帶來最好的用戶體驗腰鬼。也就是說,在日常的運營工作中塑荒,不需要關(guān)注一個商品在某個平臺內(nèi)部需要以什么樣的URL來投放熄赡,只需要投放一個主要的URL格式。這個URL在天貓App內(nèi)部會被Rewrite引擎重寫為Native界面聲明的URL齿税,進行展示彼硫。