【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么_第1頁
【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么_第2頁
【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么_第3頁
【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么_第4頁
【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么_第5頁
已閱讀5頁,還剩17頁未讀 繼續(xù)免費閱讀

下載本文檔

版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進(jìn)行舉報或認(rèn)領(lǐng)

文檔簡介

【移動應(yīng)用開發(fā)技術(shù)】網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么

本篇內(nèi)容介紹了“網(wǎng)易考拉Android客戶端路由總線設(shè)計的方法是什么”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓在下帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!1.前言當(dāng)前,Android路由框架已經(jīng)有很多了,如雨后春筍般出現(xiàn),大概是因為去年提出了Android組件化的概念。當(dāng)一個產(chǎn)品的業(yè)務(wù)規(guī)模上升到一定程度,或者是跨團(tuán)隊開發(fā)時,團(tuán)隊/模塊間的合作問題就會暴露出來。如何保持團(tuán)隊間業(yè)務(wù)的往來?如何互不影響或干涉對方的開發(fā)進(jìn)度?如何調(diào)用業(yè)務(wù)方的功能?組件化給上述問題提供了一個答案。組件化所要解決的核心問題是解耦,路由正是為了解決模塊間的解耦而出現(xiàn)的。1.1傳統(tǒng)的頁面跳轉(zhuǎn)頁面跳轉(zhuǎn)主要分為三種,App頁面間跳轉(zhuǎn)、H5跳轉(zhuǎn)回App頁面以及App跳轉(zhuǎn)至H5。App頁面間跳轉(zhuǎn)App頁面間的跳轉(zhuǎn),對于新手來說一般會在跳轉(zhuǎn)的頁面使用如下代碼:Intent

intent

=

new

Intent(this,

MainActivity.class);

intent.putExtra("dataKey",

"dataValue");

startActivity(intent);https://upload-images.jianshu.io/upload_images/14847428-68300066a02a8a61.gif?imageMogr2/auto-orient/strip對于有一定經(jīng)驗的程序員,會在跳轉(zhuǎn)的類生成自己的跳轉(zhuǎn)方法: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);

}

}https://upload-images.jianshu.io/upload_images/14847428-ca20a468b28611c3.gif?imageMogr2/auto-orient/strip無論使用哪種方式,本質(zhì)都是生成一個Intent,然后再通過Context.startActivity(Intent)/Activity.startActivityForResult(Intent,int)實現(xiàn)頁面跳轉(zhuǎn)。這種方式的不足之處是當(dāng)包含多個模塊,但模塊間沒有相互依賴時,這時候的跳轉(zhuǎn)會變得相當(dāng)困難。如果已知其他模塊的類名以及對應(yīng)的路徑,可以通過Intent.setComponent(Component)方法啟動其他模塊的頁面,但往往模塊的類名是有可能變化的,一旦業(yè)務(wù)方把模塊換個名字,這種隱藏的Bug對于開發(fā)的內(nèi)心來說是崩潰的。另一方面,這種重復(fù)的模板代碼,每次至少寫兩行才能實現(xiàn)頁面跳轉(zhuǎn),代碼存在冗余。H5-App頁面跳轉(zhuǎn)對于考拉這種電商應(yīng)用,活動頁面具有時效性和即時性,這兩種特性在任何時候都需要得到保障。運營隨時有可能更改活動頁面,也有可能要求點擊某個鏈接就能跳轉(zhuǎn)到一個App頁面。傳統(tǒng)的做法是對WebViewClient.shouldOverrideUrlLoading(WebView,String)進(jìn)行攔截,判斷url是否有對應(yīng)的App頁面可以跳轉(zhuǎn),然后取出url中的params封裝成一個Intent傳遞并啟動App頁面。感受一下在考拉App工程中曾經(jīng)出現(xiàn)過的下面這段代碼:public

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<String>

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;

}https://upload-images.jianshu.io/upload_images/14847428-a879d737a68e0c02.gif?imageMogr2/auto-orient/strip這段代碼整整260行,看到代碼時我的內(nèi)心是崩潰的。這種做法的弊端在于:判斷不合理。上述代碼僅判斷了HOST是否包含StringConstants.KAO_LA,然后根據(jù)PATH區(qū)分跳轉(zhuǎn)到哪個頁面,PATH也只判斷了起始部分,當(dāng)URL越來越多的時候很有可能造成誤判。耦合性太強(qiáng)。已知攔截的所有頁面的引用都必須能夠拿到,否則無法跳轉(zhuǎn);代碼混亂。PATH非常多,從眾多的PATH中匹配多個已知的App頁面,想必要判斷匹配規(guī)則就要寫很多函數(shù)解決;攔截過程不透明。開發(fā)者很難在URL攔截的過程中加入自己的業(yè)務(wù)邏輯,如打點、啟動Activity前添加特定的Flag等;沒有優(yōu)先級概念,也無法降級處理。同一個URL,只要第一個匹配到App頁面,就只能打開這個頁面,無法通過調(diào)整優(yōu)先級跳轉(zhuǎn)到別的頁面或者使用H5打開。App頁面-H5跳轉(zhuǎn)這種情況不必多說,啟動一個WebViewActivity即可。1.2頁面路由的意義路由最先被應(yīng)用于網(wǎng)絡(luò)中,路由的定義是通過互聯(lián)的網(wǎng)絡(luò)把信息從源地址傳輸?shù)侥康牡刂返幕顒?。頁面跳轉(zhuǎn)也是相當(dāng)于從源頁面跳轉(zhuǎn)到目標(biāo)頁面的過程,每個頁面可以定義為一個統(tǒng)一資源標(biāo)識符(URI),在網(wǎng)絡(luò)當(dāng)中能夠被別人訪問,也可以訪問已經(jīng)被定義了的頁面。路由常見的使用場景有以下幾種:App接收到一個通知,點擊通知打開App的某個頁面(OuterStartActivity)瀏覽器App中點擊某個鏈接打開App的某個頁面(OuterStartActivity)App的H5活動頁面打開一個鏈接,可能是H5跳轉(zhuǎn),也可能是跳轉(zhuǎn)到某一個native頁面(WebViewActivity)打開頁面需要某些條件,先驗證完條件,再去打開那個頁面(需要登錄)App內(nèi)的跳轉(zhuǎn),可以減少手動構(gòu)建Intent的成本,同時可以統(tǒng)一攜帶部分參數(shù)到下一個頁面(打點)除此之外,使用路由可以避免上述弊端,能夠降低開發(fā)者頁面跳轉(zhuǎn)的成本。2.考拉路由總線2.1路由框架https://upload-images.jianshu.io/upload_images/14847428-5dab6053885ab1b8.gif?imageMogr2/auto-orient/strip考拉路由框架主要分為三個模塊:路由收集、路由初始化以及頁面路由。路由收集階段,定義了基于Activity類的注解,通過AndroidProcessingTool(以下簡稱“APT”)收集路由信息并生成路由表類;路由初始化階段,根據(jù)生成的路由表信息注入路由字典;頁面路由階段,則通過路由字典查找路由信息,并根據(jù)查找結(jié)果定制不同的路由策略略。2.2路由設(shè)計思路總的來說,考拉路由設(shè)計追求的是功能模塊的解耦,能夠?qū)崿F(xiàn)基本路由功能,以及開發(fā)者使用上足夠簡單??祭酚傻那皟蓚€階段對于路由使用者幾乎是無成本的,只需要在使用路由的頁面定義一個類注解@Router即可,頁面路由的使用也相當(dāng)簡單,后面會詳細(xì)介紹。功能設(shè)計路由在一定程度上和網(wǎng)絡(luò)請求是類似的,可以分為請求、處理以及響應(yīng)三個階段。這三個階段對使用者來說既可以是透明的,也可以在路由過程中進(jìn)行攔截處理??祭酚煽蚣苣壳爸С值墓δ苡校?.支持基本Activity的啟動,以及startActivityForResult回調(diào);?2.支持不同協(xié)議執(zhí)行不同跳轉(zhuǎn);(kaola://、http(s)://、native://等)?3.支持多個SCHEME/HOST/PATH跳轉(zhuǎn)至同一個頁面;((pre.).(.hk))?4.支持路由的正則匹配;?5.支持Activity啟動使用不同的Flag;?6.支持路由的優(yōu)先級配置;?7.支持對路由的動態(tài)攔截、監(jiān)聽以及降級;?以上功能保證了考拉業(yè)務(wù)模塊間的解耦,也能夠滿足目前產(chǎn)品和運營的需求。https://upload-images.jianshu.io/upload_images/14847428-e1b6cda0e1ec2abf.gif?imageMogr2/auto-orient/strip一個好的模塊或框架,需要事先設(shè)計好接口,預(yù)留足夠的權(quán)限供調(diào)用者支配,才能滿足各種各樣的需求??祭酚煽蚣茉谠O(shè)計過程中使用了常見的設(shè)計模式,如Builder模式、Factory模式、Wrapper模式等,并遵循了一些設(shè)計原則。(最近在看第二遍EffectiveJava,對以下原則深有體會,推薦看一下)針對接口編程,而不是針對實現(xiàn)編程這條規(guī)則排在最前面的原因是,針對接口編程,不管是對開發(fā)者還是對使用者,真的是百利而無一害。在路由版本迭代的過程中,底層對接口無論實現(xiàn)怎樣的修改,也不會影響到上層調(diào)用。對于業(yè)務(wù)來說,路由的使用是無感知的??祭酚煽蚣茉谠O(shè)計過程中并未完全遵循這條原則,下一個版本的迭代會盡量按照這條原則來實現(xiàn)。但在路由過程中的關(guān)鍵步驟都預(yù)留了接口,具體有:RouterRequestCallbackpublic

interface

RouterRequestCallback

{

void

onFound(RouterRequest

request,

RouterResponse

response);

boolean

onLost(RouterRequest

request);

}https://upload-images.jianshu.io/upload_images/14847428-42f8fbffe014b7ee.gif?imageMogr2/auto-orient/strip路由表中是否能夠匹配到路由信息的回調(diào),如果能夠匹配,則回調(diào)onFound(),如果不能夠匹配,則返回onLost()。onLost()的結(jié)果由開發(fā)來定義,如果返回的結(jié)果是true,則認(rèn)為開發(fā)者處理了這次路由不匹配的結(jié)果,最終返回RouterResult的結(jié)果是成功路由。RouterHandlerpublic

interface

RouterHandler

extends

RouterStarter

{

RouterResponse

findResponse(RouterRequest

request);

}https://upload-images.jianshu.io/upload_images/14847428-4e0c6a89a0d2dc22.gif?imageMogr2/auto-orient/strip路由處理與啟動接口,根據(jù)給定的路由請求,查找路由信息,根據(jù)路由響應(yīng)結(jié)果,分發(fā)給相應(yīng)的啟動器執(zhí)行后續(xù)頁面跳轉(zhuǎn)。這個接口的設(shè)計不太合理,功能上不完善,后續(xù)會重新設(shè)計這個接口,讓調(diào)用方有權(quán)限干預(yù)查找路由的過程。RouterResultCallbackpublic

interface

RouterResultCallback

{

boolean

beforeRoute(Context

context,

Intent

intent);

void

doRoute(Context

context,

Intent

intent,

Object

extra);

void

errorRoute(Context

context,

Intent

intent,

String

errorCode,

Object

extra);

}https://upload-images.jianshu.io/upload_images/14847428-1fbcc8f1efcb6dd3.gif?imageMogr2/auto-orient/strip匹配到路由信息后,真正執(zhí)行路由過程的回調(diào)。beforeRoute()這個方法是在真正路由之前的回調(diào),如果開發(fā)者返回true,則認(rèn)為這條路由信息已被調(diào)用者攔截,不會再回調(diào)后面的doRoute()以及執(zhí)行路由。在路由過程中發(fā)生的任何異常,都會回調(diào)errorRoute()方法,這時候路由中斷。ResponseInvokerpublic

interface

ResponseInvoker

{

void

invoke(Context

context,

Intent

intent,

Object...

args);

}https://upload-images.jianshu.io/upload_images/14847428-16d549270ff937ed.gif?imageMogr2/auto-orient/strip路由執(zhí)行者。如果開發(fā)需要執(zhí)行路由前進(jìn)行一些全局操作,例如添加額外的信息傳入到下一個Activity,則可以自己實現(xiàn)這個接口。路由框架提供默認(rèn)的實現(xiàn):ActivityInvoker。開發(fā)也可以繼承ActivityInvoker,重寫invoke()方法,先實現(xiàn)自己的業(yè)務(wù)邏輯,再調(diào)用super.invoke()方法。OnActivityResultListenerpublic

interface

OnActivityResultListener

{

void

onActivityResult(int

requestCode,

int

resultCode,

Intent

data);

}https://upload-images.jianshu.io/upload_images/14847428-2348661a07ef3102.gif?imageMogr2/auto-orient/strip特別強(qiáng)調(diào)一下這個Listener。本來這個回調(diào)的作用是方便調(diào)用者在執(zhí)行startActivityForResult的時候可以通過回調(diào)來告知結(jié)果,但由于不保留活動的限制,離開頁面以后這個監(jiān)聽器是無法被系統(tǒng)保存(saveInstanceState)的,因此不推薦在Activity/Fragment中使用回調(diào),而是在非Activity組件/模塊里使用,如View/Window/Dialog。這個過程已經(jīng)由core包里的CoreBaseActivity實現(xiàn),開發(fā)使用的時候,可以直接調(diào)用CoreBaseActivity.startActivityForResult(intent,requestCode,onActivityResultListener),也可以通過KaolaRouter.with(context).url(url).startForResult(requestCode,onActivityResultListener)調(diào)用。例如,要啟動訂單管理頁并回調(diào):KaolaRouter.with(context)

.url(url)

.data("orderId",

"replace

url

param

key.")

.startForResult(1,

new

OnActivityResultListener()

{

@Override

public

void

onActivityResult(int

requestCode,

int

resultCode,

Intent

data)

{

DebugLog.e(requestCode

+

"

"

+

resultCode

+

"

"

+

data.toString());

}

});https://upload-images.jianshu.io/upload_images/14847428-ee5661e8fd45924c.gif?imageMogr2/auto-orient/stripRouterResultpublic

interface

RouterResult

{

boolean

isSuccess();

RouterRequest

getRouterRequest();

RouterResponse

getRouterResponse();

}https://upload-images.jianshu.io/upload_images/14847428-f72c66b0523fc469.gif?imageMogr2/auto-orient/strip告知路由的結(jié)果,路由結(jié)果可以被干預(yù),例如RouterRequestCallback.onLost(),返回true的時候,路由也是成功的。這個接口不管路由的成功或失敗都會返回。不隨意暴露不必要的API“要區(qū)別設(shè)計良好的模塊與設(shè)計不好的模塊,最重要的因素在于,這個模塊對于外部的其他模塊而?言,是否隱藏其內(nèi)部數(shù)據(jù)和其他實現(xiàn)細(xì)節(jié)。設(shè)計良好的模塊會隱藏所有的實現(xiàn)細(xì)節(jié),把它的API與它的實現(xiàn)清晰地隔離開來。然后,模塊之間只通過它們的API進(jìn)行通信,一個模塊不需要知道其他模塊的內(nèi)部工作情況。這被稱為封裝(encapsulation)?!保ㄕ訣ffectiveJava,P58)舉個例子,考拉路由框架對路由調(diào)用的入?yún)⒆隽讼拗?,一旦入?yún)?,則不能再做修改,調(diào)用者無需知道路由框架對使用這些參數(shù)怎么實現(xiàn)調(diào)用者想要的功能。實現(xiàn)上,由RouterRequestWrapper繼承自RouterRequestBuilder,后者通過Builder模式給用戶構(gòu)造相關(guān)的參數(shù),前者通過Wrapper模式裝飾RouterRequestBuilder中的所有變量,并在RouterRequestWrapper類中提供所有參數(shù)的get函數(shù),供路由框架使用。單一職責(zé)無論是類還是方法,均需要遵循單一職責(zé)原則。一個類實現(xiàn)一個功能,一個方法做一件事。例如,KaolaRouterHandler是考拉路由的處理器,實現(xiàn)了RouterHandler接口,實現(xiàn)路由的查找與轉(zhuǎn)發(fā);RouterRequestBuilder用于收集路由請求所需參數(shù);RouterResponseFactory用于生成路由響應(yīng)的結(jié)果。提供默認(rèn)實現(xiàn)針對接口編程的好處是隨時可以替換實現(xiàn),考拉路由框架在路由過程中的所有監(jiān)聽、攔截以及路由過程都提供了默認(rèn)的實現(xiàn)。使用者即可以不關(guān)心底層的實現(xiàn)邏輯,也可以根據(jù)需要替換相關(guān)的實現(xiàn)。2.3考拉路由實現(xiàn)原理考拉路由框架基于注解收集路由信息,通過APT實現(xiàn)路由表的動態(tài)生成,類似于ButterKnife的做法,在運行時導(dǎo)入路由表信息,并通過正則表達(dá)式查找路由,根據(jù)路由結(jié)果實現(xiàn)最終的頁面跳轉(zhuǎn)。收集路由信息首先定義一個注解@Router,注解里包含了路由協(xié)議、路由主機(jī)、路由路徑以及路由優(yōu)先級。@Target(ElementType.TYPE)

@Retention(RetentionPolicy.CLASS)

public

@interface

Router

{

/**

*

URI協(xié)議,已經(jīng)提供默認(rèn)值,默認(rèn)實現(xiàn)了四種協(xié)議:https、http、kaola、native

*/

String

scheme()

default

"(https|http|kaola|native)://";

/**

*

URI主機(jī),已經(jīng)提供默認(rèn)值

*/

String

host()

default

"(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?";

/**

*

URI路徑,選填,如果使用默認(rèn)值,則只支持本地路由,不支持url攔截

*/

String

value()

default

"";

/**

*

路由優(yōu)先級,默認(rèn)為0。

*/

int

priority()

default

0;

}https://upload-images.jianshu.io/upload_images/14847428-df6889b0705ed197.gif?imageMogr2/auto-orient/strip對于需要使用路由的頁面,只需要在類的聲明處加上這個注解,標(biāo)明這個頁面對應(yīng)的路由路徑即可。例如:@Router("/app/myQuestion.html")

public

class

MyQuestionAndAnswerActivity

extends

BaseActivity

{

……

}https://upload-images.jianshu.io/upload_images/14847428-545b3b26ef2aedc2.gif?imageMogr2/auto-orient/strip那么通過APT生成的標(biāo)記這個頁面的url則是一個正則表達(dá)式:(https|http|kaola|native)://(pre\.)?(\w+\.)?kaola\.com(\.hk)?/app/myQuestion\.htmlhttps://upload-images.jianshu.io/upload_images/14847428-d4d07235a365fe58.gif?imageMogr2/auto-orient/strip路由表則是由多條這樣的正則表達(dá)式構(gòu)成。生成路由表路由表的生成需要使用APT工具以及Square公司開源的javapoet類庫,目的是根據(jù)我們定義的Router注解讓機(jī)器幫我們“寫代碼”,生成一個Map類型的路由表,其中key根據(jù)Router注解的信息生成對應(yīng)的正則表達(dá)式,value是這個注解對應(yīng)的類的信息集合。首先定義一個RouterProcessor,繼承自AbstractProcessor,public

class

RouterProcessor

extends

AbstractProcessor

{

@Override

public

synchronized

void

init(ProcessingEnvironment

processingEnv)

{

super.init(processingEnv);

//

初始化相關(guān)環(huán)境信息

mFiler

=

processingEnv.getFiler();

elementUtil

=

processingEnv.getElementUtils();

typeUtil

=

processingEnv.getTypeUtils();

Log.setLogger(processingEnv.getMessager());

}

@Override

public

Set<String>

getSupportedAnnotationTypes()

{

Set<String>

supportAnnotationTypes

=

new

HashSet<>();

//

獲取需要處理的注解類型,目前只處理Router注解

supportAnnotationTypes.add(Router.class.getCanonicalName());

return

supportAnnotationTypes;

}

@Override

public

boolean

process(Set<?

extends

TypeElement>

annotations,

RoundEnvironment

roundEnv)

{

//

收集與Router相關(guān)的所有類信息,解析并生成路由表

Set<?

extends

Element>

routeElements

=

roundEnv.getElementsAnnotatedWith(Router.class);

try

{

return

parseRoutes(routeElements);

}

catch

(Exception

e)

{

Log.e(e.getMessage(),

e);

return

false;

}

}

}https://upload-images.jianshu.io/upload_images/14847428-4d72720f5b64bd0e.gif?imageMogr2/auto-orient/strip上述的三個方法屬于AbstractProcessor的方法,publicabstractbooleanprocess(Setannotations,RoundEnvironmentroundEnv)是抽象方法,需要子類實現(xiàn)。private

boolean

parseRoutes(Set<?

extends

Element>

routeElements)

throws

IOException

{

if

(null

==

routeElements

||

routeElements.size()

==

0)

{

return

false;

}

//

獲取Activity類的類型,后面用于判斷是否是其子類

TypeElement

typeActivity

=

elementUtil.getTypeElement(ACTIVITY);

//

獲取路由Builder類的標(biāo)準(zhǔn)類名

ClassName

routeBuilderCn

=

ClassName.get(RouteBuilder.class);

//

構(gòu)建Map<String,

Route>集合

String

routerConstClassName

=

RouterProvider.ROUTER_CONST_NAME;

TypeSpec.Builder

typeSpec

=

TypeSpec.classBuilder(routerConstClassName).addJavadoc(WARNING_TIPS).addModifiers(PUBLIC);

/**

*

Map<String,

Route>

*/

ParameterizedTypeName

inputMapTypeName

=

ParameterizedTypeName.get(ClassName.get(Map.class),

ClassName.get(String.class),

ClassName.get(Route.class));

ParameterSpec

groupParamSpec

=

ParameterSpec.builder(inputMapTypeName,

ROUTER_MAP_NAME).build();

MethodSpec.Builder

loadIntoMethodOfGroupBuilder

=

MethodSpec.methodBuilder(METHOD_LOAD_INTO)

.addAnnotation(Override.class)

.addModifiers(PUBLIC)

.addParameter(groupParamSpec);

//

將路由信息放入Map<String,

Route>集合中

for

(Element

element

:

routeElements)

{

TypeMirror

tm

=

element.asType();

Router

route

=

element.getAnnotation(Router.class);

//

獲取當(dāng)前Activity的標(biāo)準(zhǔn)類名

if

(typeUtil.isSubtype(tm,

typeActivity.asType()))

{

ClassName

activityCn

=

ClassName.get((TypeElement)

element);

String

key

=

"key"

+

element.getSimpleName().toString();

String

routeString

=

RouteBuilder.assembleRouteUri(route.scheme(),

route.host(),

route.value());

if

(null

==

routeString)

{

//String

keyValue

=

RouteBuilder.generateUriFromClazz(Activity.class);

loadIntoMethodOfGroupBuilder.addStatement("String

$N=

$T.generateUriFromClazz($T.class)",

key,

routeBuilderCn,

activityCn);

}

else

{

//String

keyValue

=

"("

+

route.value()

+

")|("

+

RouteBuilder.generateUriFromClazz(Activity.class)

+

")";

loadIntoMethodOfGroupBuilder.addStatement(

"String

$N=$S

+

$S

+

$S+$T.generateUriFromClazz($T.class)+$S",

key,

"(",

routeString,

")|(",

routeBuilderCn,

activityCn,

")");

}

/**

*

routerMap.put(url,

RouteBuilder.build(String

url,

int

priority,

Class<?>

destination));

*/

loadIntoMethodOfGroupBuilder.addStatement("$N.put($N,

$T.build($N,

$N,

$T.class))",

ROUTER_MAP_NAME,

key,

routeBuilderCn,

key,

String.valueOf(route.priority()),

activityCn);

typeSpec.addField(generateRouteConsts(element));

}

}

//

Generate

RouterConst.java

JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY,

typeSpec.build()).build().writeTo(mFiler);

//

Generate

RouterGenerator

JavaFile.builder(RouterProvider.OUTPUT_DIRECTORY,

TypeSpec.classBuilder(RouterProvider.ROUTER_GENERATOR_NAME)

.addJavadoc(WARNING_TIPS)

.addSuperinterface(ClassName.get(RouterProvider.class))

.addModifiers(PUBLIC)

.addMethod(loadIntoMethodOfGroupBuilder.build())

.build()).build().writeTo(mFiler);

return

true;

}https://upload-images.jianshu.io/upload_images/14847428-ec963202ee71eddb.gif?imageMogr2/auto-orient/strip最終生成的路由表如下:/**

*

DO

NOT

EDIT

THIS

FILE!!!

IT

WAS

GENERATED

BY

KAOLA

PROCESSOR.

*/public

class

RouterGenerator

implements

RouterProvider

{

@Override

public

void

loadRouter(Map<String,

Route>

routerMap)

{

String

keyActivityDetailActivity="("

+

"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/activity/spring/\\w+"

+

")|("+RouteBuilder.generateUriFromClazz(ActivityDetailActivity.class)+")";

routerMap.put(keyActivityDetailActivity,

RouteBuilder.build(keyActivityDetailActivity,

0,

ActivityDetailActivity.class));

String

keyLabelDetailActivity="("

+

"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/album/tag/share\\.html"

+

")|("+RouteBuilder.generateUriFromClazz(LabelDetailActivity.class)+")";

routerMap.put(keyLabelDetailActivity,

RouteBuilder.build(keyLabelDetailActivity,

0,

LabelDetailActivity.class));

String

keyMyQuestionAndAnswerActivity="("

+

"(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html"

+

")|("+RouteBuilder.generateUriFromClazz(MyQuestionAndAnswerActivity.class)+")";

routerMap.put(keyMyQuestionAndAnswerActivity,

RouteBuilder.build(keyMyQuestionAndAnswerActivity,

0,

MyQuestionAndAnswerActivity.class));

……

}https://upload-images.jianshu.io/upload_images/14847428-72cbed52d962dd13.gif?imageMogr2/auto-orient/strip其中,RouteBuilder.generateUriFromClazz(Class)的實現(xiàn)如下,目的是生成一條默認(rèn)的與標(biāo)準(zhǔn)類名相關(guān)的native跳轉(zhuǎn)規(guī)則。public

static

final

String

SCHEME_NATIVE

=

"native://";public

static

String

generateUriFromClazz(Class<?>

destination)

{

String

rawUri

=

SCHEME_NATIVE

+

destination.getCanonicalName();

return

rawUri.replaceAll("\\.",

"\\\\.");

}https://upload-images.jianshu.io/upload_images/14847428-bc72ac7eca371790.gif?imageMogr2/auto-orient/strip可以看到,路由集合的key是一條正則表達(dá)式,包括了url攔截規(guī)則以及自定義的包含標(biāo)準(zhǔn)類名的native跳轉(zhuǎn)規(guī)則。例如,keyMyQuestionAndAnswerActivity最終生成的key是((https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html)|(native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)https://upload-images.jianshu.io/upload_images/14847428-97258abbcd29e25d.gif?imageMogr2/auto-orient/strip這樣,調(diào)用者不僅可以通過默認(rèn)的攔截規(guī)則(https|http|kaola|native)://(pre\\.)?(\\w+\\.)?kaola\\.com(\\.hk)?/app/myQuestion.html)https://upload-images.jianshu.io/upload_images/14847428-11f1866f0268ea20.gif?imageMogr2/auto-orient/strip跳轉(zhuǎn)到對應(yīng)的頁面,也可以通過(native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity)。https://upload-images.jianshu.io/upload_images/14847428-ac4253234254c19a.gif?imageMogr2/auto-orient/strip這樣的好處是模塊間的跳轉(zhuǎn)也可以使用,不需要依賴引用類。而native跳轉(zhuǎn)會專門生成一個類RouterConst來記錄,如下:/**

*

DO

NOT

EDIT

THIS

FILE!!!

IT

WAS

GENERATED

BY

KAOLA

PROCESSOR.

*/public

class

RouterConst

{

public

static

final

String

ROUTE_TO_ActivityDetailActivity

=

"native://com.kaola.modules.activity.ActivityDetailActivity";

public

static

final

String

ROUTE_TO_LabelDetailActivity

=

"native://com.kaola.modules.albums.label.LabelDetailActivity";

public

static

final

String

ROUTE_TO_MyQuestionAndAnswerActivity

=

"native://com.kaola.modules.answer.myAnswer.MyQuestionAndAnswerActivity";

public

static

final

String

ROUTE_TO_CertificatedNameActivity

=

"native://com.kaola.modules.auth.activity.CertificatedNameActivity";

public

static

final

String

ROUTE_TO_CPSCertificationActivity

=

"native://com.kaola.modules.auth.activity.CPSCertificationActivity";

public

static

final

String

ROUTE_TO_BrandDetailActivity

=

"native://com.kaola.modules.brands.branddetail.ui.BrandDetailActivity";

public

static

final

String

ROUTE_TO_CartContainerActivity

=

"native://com.kaola.modules.cart.CartContainerActivity";

public

static

final

String

ROUTE_TO_SingleCommentShowActivity

=

"native://ment.detail.SingleCommentShowActivity";

public

static

final

String

ROUTE_TO_CouponGoodsActivity

=

"native://com.kaola.modules.coupon.activity.CouponGoodsActivity";

public

static

final

String

ROUTE_TO_CustomerAssistantActivity

=

"native://com.kaola.modules.customer.CustomerAssistantActivity";

……

}https://upload-images.jianshu.io/upload_images/14847428-d1a092a002ea954b.gif?imageMogr2/auto-orient/strip初始化路由路由初始化在Application的過程中以同步的方式進(jìn)行。通過獲取RouterGenerator的類直接生成實例,并將路由信息保存在sRouterMap變量中。public

static

void

init()

{

try

{

sRouterMap

=

new

HashMap<>();

((RouterProvider)

(Class.forName(ROUTER_CLASS_NAME).getConstructor().newInstance())).loadRouter(sRouterMap);

}

catch

(Exception

e)

{

e.printStackTrace();

}

}https://upload-images.jianshu.io/upload_images/14847428-429eb0fa35171b9c.gif?imageMogr2/auto-orient/strip頁面路由給定一個url以及上下文環(huán)境,即可使用路由。調(diào)用方式如下:KaolaRouter.with(context).url(url).start();https://upload-images.jianshu.io/upload_images/14847428-bffa72b31dfae692.gif?imageMogr2/auto-orient/strip頁面路由分為路由請求生成,路由查找以及路由結(jié)果執(zhí)行這幾個步驟。路由請求目前較為簡單,僅是封裝了一個RouterRequest接口public

interface

RouterRequest

{

Uri

getUriRequest();

}https://upload-images.jianshu.io/upload_images/14847428-93daa5f71a6acf81.gif?imageMogr2/auto-orient/strip路由的查找過程相對復(fù)雜,除了遍歷路由初始化以后導(dǎo)入內(nèi)存的路由表,還需要判斷各種各樣的前置條件。具體的條件判斷代碼中有相關(guān)注釋。@Overridepublic

RouterResponse

findResponse(RouterRequest

request)

{

if

(null

==

sRouterMap)

{

return

null;

//throw

new

IllegalStateException(

//

String.format("Router

has

not

been

initialized,

please

call

%s.init()

first.",

//

KaolaRouter.class.getSimpleName()));

}

if

(mRouterRequestWrapper.getDestinationClass()

!=

null)

{

RouterResponse

response

=

RouterResponseFactory.buildRouterResponse(null,

mRouterRequestWrapper);

reportFoundRequestCallback(request,

response);

return

response;

}

Uri

uri

=

request.getUriRequest();

String

requestUrl

=

uri.toString();

if

(!TextUtils.isEmpty(requestUrl))

{

for

(Map.Entry<String,

Route>

entry

:

sRouterMap.entrySet())

{

if

(RouterUtils.matchUrl(requestUrl,

entry.getKey()))

{

Route

routerModel

=

entry.getValue();

if

(null

!=

routerModel)

{

RouterResponse

response

=

RouterResponseFactory.buildRouterResponse(routerModel,

mRouterRequestWrapper);

reportFoundRequestCallback(request,

response);

return

response;

}

}

}

}

return

null;

}@Overridepubli

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論