java路由总线_网易考拉Android客户端路由总线设计
1.前言
$ e7 |??~% L) i7 @7 B& t3 T5 h* P/ e2 s
當前,Android路由框架已經有很多了,如雨后春筍般出現,大概是因為去年提出了Android組件化的概念。當一個產品的業務規模上升到一定程度,或者是跨團隊開發時,團隊/模塊間的合作問題就會暴露出來。如何保持團隊間業務的往來?如何互不影響或干涉對方的開發進度?如何調用業務方的功能?組件化給上述問題提供了一個答案。組件化所要解決的核心問題是解耦,路由正是為了解決模塊間的解耦而出現的。本文闡述了考拉Android端的路由設計方案,盡管與市面上的方案大同小異,但更多的傾向于與考拉業務進行一定程度的結合。
, o6 H, e5 F* L: J. w/ W1.1 傳統的頁面跳轉
' j* K' U4 f8 a- F+ Q9 g0 O頁面跳轉主要分為三種,App頁面間跳轉、H5跳轉回App頁面以及App跳轉至H5。
/ g3 z4 e% Z# r: I6 VApp頁面間跳轉
) x# t8 E& s9 D9 d9 AApp頁面間的跳轉,對于新手來說一般會在跳轉的頁面使用如下代碼:
1 V* {7 M. p2 {??_8 h; a, l4 q??hIntent intent = new Intent(this, MainActivity.class);intent.putExtra("dataKey", "dataValue");startActivity(intent);
對于有一定經驗的程序員,會在跳轉的類生成自己的跳轉方法:: w0 e+ \. J/ ^6 ]' m5 y* [4 w
public class OrderManagerActivity extends BaseActivity {? ? public static void launch(Context context, int startTab) {? ?? ???Intent i = new Intent(context, OrderManagerActivity.class);? ?? ???i.putExtra(INTENT_IN_INT_START_TAB, startTab);? ?? ???context.startActivity(i);? ? }}
無論使用哪種方式,本質都是生成一個Intent,然后再通過Context.startActivity(Intent)/Activity.startActivityForResult(Intent, int)實現頁面跳轉。這種方式的不足之處是當包含多個模塊,但模塊間沒有相互依賴時,這時候的跳轉會變得相當困難。如果已知其他模塊的類名以及對應的路徑,可以通過Intent.setComponent(Component)方法啟動其他模塊的頁面,但往往模塊的類名是有可能變化的,一旦業務方把模塊換個名字,這種隱藏的Bug對于開發的內心來說是崩潰的。另一方面,這種重復的模板代碼,每次至少寫兩行才能實現頁面跳轉,代碼存在冗余。5 P* E2 R2 s" F8 \
H5-App頁面跳轉; I" j: C" y3 @1 @
對于考拉這種電商應用,活動頁面具有時效性和即時性,這兩種特性在任何時候都需要得到保障。運營隨時有可能更改活動頁面,也有可能要求點擊某個鏈接就能跳轉到一個App頁面。傳統的做法是對WebViewClient.shouldOverrideUrlLoading(WebView, String)進行攔截,判斷url是否有對應的App頁面可以跳轉,然后取出url中的params封裝成一個Intent傳遞并啟動App頁面。0 k' w$ L, i) y; ]$ d??|7 \
感受一下在考拉App工程中曾經出現過的下面這段代碼:
: U! z4 o( V& j8 w9 u/ U; h% vpublic static Intent startActivityByUrl(Context context, String url, boolean fromWeb, boolean outer) {? ? if (StringUtils.isNotBlank(url) && url.startsWith(StringConstants.REDIRECT_URL)) {? ?? ?? ? try {? ?? ?? ?? ?String realUrl = Uri.parse(url).getQueryParameter("target");? ?? ?? ?? ?if (StringUtils.isNotBlank(realUrl)) {? ?? ?? ?? ?? ? url = URLDecoder.decode(realUrl, "UTF-8");? ?? ?? ?? ?}? ?? ???} catch (Exception e) {? ?? ?? ?? ?e.printStackTrace();? ?? ???}? ? }? ? Intent intent = null;? ? try {? ?? ???Uri uri = Uri.parse(url);? ?? ???String host = uri.getHost();? ?? ???List pathSegments = uri.getPathSegments();? ?? ???String path = uri.getPath();? ?? ???int segmentsLength = (pathSegments == null ? 0 : pathSegments.size());? ?? ???if (!host.contains(StringConstants.KAO_LA)) {? ?? ?? ?? ?return null;? ?? ???}? ?? ???if((StringUtils.isBlank(path))){? ?? ?? ?? ?do something...? ?? ?? ?? ?return intent;? ?? ???}? ?? ???if (segmentsLength == 2 && path.startsWith(StringConstants.JUMP_TO_GOODS_DETAIL)) {? ?? ?? ?? ?do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_SPRING_ACTIVITY_TAB)) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_SPRING_ACTIVITY_DETAIL) && segmentsLength == 3) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith(StringConstants.START_CART) && segmentsLength == 1) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_COUPON_DETAIL)? ?? ?? ?? ?? ? || (path.startsWith(StringConstants.JUMP_TO_COUPON) && segmentsLength == 2)) {? ?? ?? ?? ?do something...? ?? ???} else if (canOpenMainPage(host, uri.getPath())) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith(StringConstants.START_ORDER)) {? ?? ?? ?? ? if (!UserInfo.isLogin(context)) {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?} else {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?}? ?? ???} else if (path.startsWith(StringConstants.START_SAVE)) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_NEW_DISCOVERY)) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_NEW_DISCOVERY_2) && segmentsLength == 3) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith(StringConstants.START_BRAND_INTRODUCE)? ?? ?? ?? ?? ? || path.startsWith(StringConstants.START_BRAND_INTRODUCE2)) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith(StringConstants.START_BRAND_DETAIL) && segmentsLength == 2) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_ORDER_DETAIL)) {? ?? ?? ?? ???if (!UserInfo.isLogin(context) && outer) {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?} else {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?}? ?? ???} else if (path.startsWith("/cps/user/certify.html")) {? ?? ?? ?? ?? ?do something...? ?? ???} else if (path.startsWith(StringConstants.IDENTIFY)) {? ?? ?? ?? ? do something...? ?? ???} else if (path.startsWith("/album/share.html")) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith("/album/tag/share.html")) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith("/live/roomDetail.html")) {? ?? ?? ?? ?? ?do something...? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_ORDER_COMMENT)) {? ?? ?? ?? ? if (!UserInfo.isLogin(context) && outer) {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?} else {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?}? ?? ???} else if (openOrderDetail(url, path)) {? ?? ?? ?? ?if (!UserInfo.isLogin(context) && outer) {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?} else {? ?? ?? ?? ?? ? do something...? ?? ?? ?? ?}? ?? ???} else if (path.startsWith(StringConstants.JUMP_TO_SINGLE_COMMENT)) {? ?? ?? ?? ???do something...? ?? ???} else if (path.startsWith("/member/activity/vip_help.html")) {? ?? ?? ?? ?do something...? ?? ???} else if (path.startsWith("/goods/search.html")) {? ?? ?? ?? ?do something...? ?? ???} else if(path.startsWith("/afterSale/progress.html")){? ?? ?? ?? ???do something...? ?? ???} else if(path.startsWith("/afterSale/apply.html")){? ?? ?? ?? ???do something...? ?? ???} else if(path.startsWith("/order/track.html")) {? ?? ?? ?? ? do something...? ?? ???}? ? } catch (Exception e) {? ?? ???e.printStackTrace();? ? }? ? if (intent != null && !(context instanceof Activity)) {? ?? ???intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);? ? }? ? return intent;}
這段代碼整整260行,看到代碼時我的內心是崩潰的。這種做法的弊端在于:" I3 h9 k# j1 H$ R+ d
3 g1 J' |! g, s* ~9 Q??X
判斷不合理。上述代碼僅判斷了HOST是否包含StringConstants.KAO_LA,然后根據PATH區分跳轉到哪個頁面,PATH也只判斷了起始部分,當URL越來越多的時候很有可能造成誤判。
% J. [/ @! Y9 I
耦合性太強。已知攔截的所有頁面的引用都必須能夠拿到,否則無法跳轉;+ W# ^& [8 P/ O. r
代碼混亂。PATH非常多,從眾多的PATH中匹配多個已知的App頁面,想必要判斷匹配規則就要寫很多函數解決;% o1 K# h! S* C# ^8 z; a0 S( ~
攔截過程不透明。開發者很難在URL攔截的過程中加入自己的業務邏輯,如打點、啟動Activity前添加特定的Flag等;# B2 l. Z- C& ~3 Q% u, I# V8 t
沒有優先級概念,也無法降級處理。同一個URL,只要第一個匹配到App頁面,就只能打開這個頁面,無法通過調整優先級跳轉到別的頁面或者使用H5打開。
I; f% h, n0 IApp頁面-H5跳轉
% v4 i; Q2 B3 t8 h) |5 h& z這種情況不必多說,啟動一個WebViewActivity即可。) v- f* p: j0 W% A
1.2 頁面路由的意義& s??c3 z# {. n0 o* D6 l1 ~* r" Z$ S
路由最先被應用于網絡中,路由的定義是通過互聯的網絡把信息從源地址傳輸到目的地址的活動。頁面跳轉也是相當于從源頁面跳轉到目標頁面的過程,每個頁面可以定義為一個統一資源標識符(URI),在網絡當中能夠被別人訪問,也可以訪問已經被定義了的頁面。路由常見的使用場景有以下幾種:
' ~. ^/ b: G, M8 F# S8 o8 @: i3 Q2 l% [( k- I
App接收到一個通知,點擊通知打開App的某個頁面(OuterStartActivity)
3 V, y5 e- U0 P8 f+ ?
瀏覽器App中點擊某個鏈接打開App的某個頁面(OuterStartActivity)
/ J# _* O. [- ~2 P' b( R+ K
App的H5活動頁面打開一個鏈接,可能是H5跳轉,也可能是跳轉到某一個native頁面(WebViewActivity)
* {% v* ~3 q! q& g8 T# |2 m
打開頁面需要某些條件,先驗證完條件,再去打開那個頁面(需要登錄)
c# b- z??{) @4 ^* x. I
App內的跳轉,可以減少手動構建Intent的成本,同時可以統一攜帶部分參數到下一個頁面(打點)' S) c9 m2 D3 l! M& F9 i* S2 A. v
除此之外,使用路由可以避免上述弊端,能夠降低開發者頁面跳轉的成本。: z2 E3 e4 z. X$ l( c2.考拉路由總線
& X* J3 S( C8 D9 u, @9 a" Y- L) I
! d8 i2 t. ~, V( G9 Y- J2.1 路由框架
8 Z; l; n, I! v$ [" W/ k, P- l
( n6 |1 J; i6 C8 A??[* b8 X
考拉路由框架主要分為三個模塊:路由收集、路由初始化以及頁面路由。路由收集階段,定義了基于Activity類的注解,通過Android Processing Tool(以下簡稱“APT”)收集路由信息并生成路由表類;路由初始化階段,根據生成的路由表信息注入路由字典;頁面路由階段,則通過路由字典查找路由信息,并根據查找結果定制不同的路由策略略。. E1 B9 g# H" {6 n& e
9 {+ C* }' k& x
2.2 路由設計思路
總結
以上是生活随笔為你收集整理的java路由总线_网易考拉Android客户端路由总线设计的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 爱快3.5.4版本ipsec 一个smb
- 下一篇: 触发onblur事件alert死循环问题