




版權(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)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025-2030淡奶油行業(yè)市場現(xiàn)狀供需分析及重點企業(yè)投資評估規(guī)劃分析研究報告
- 2025-2030潤膚露行業(yè)市場發(fā)展分析及投資前景研究報告
- 2025-2030消毒柜市場投資前景分析及供需格局研究研究報告
- 2025-2030汽車飛輪產(chǎn)業(yè)政府戰(zhàn)略管理與區(qū)域發(fā)展戰(zhàn)略研究咨詢報告
- 2025-2030汽車租賃行業(yè)市場深度分析及競爭格局與投資價值研究報告
- 2025-2030水泥攪拌車行業(yè)市場發(fā)展分析及投資前景研究報告
- 2025-2030氧化鈷納米粉行業(yè)市場現(xiàn)狀供需分析及重點企業(yè)投資評估規(guī)劃分析研究報告
- 2025-2030氣動減壓閥行業(yè)市場發(fā)展分析及發(fā)展趨勢與投資研究報告
- 2025-2030棉花纖維行業(yè)市場現(xiàn)狀供需分析及投資評估規(guī)劃分析研究報告
- 2025-2030果汁行業(yè)市場發(fā)展分析及發(fā)展前景與投資機(jī)會研究報告
- 慢性腎衰竭教學(xué)查房
- ZZ031 園林微景觀設(shè)計與制作賽項賽題-2023年全國職業(yè)院校技能大賽擬設(shè)賽項賽題完整版(10套)
- 得力DL-D82ES計算器使用說明書
- 吉林師范成人教育《大學(xué)英語2》期末考試復(fù)習(xí)題及參考答案
- 電力設(shè)計收費標(biāo)準(zhǔn)2018
- GB/T 27740-2011流延聚丙烯(CPP)薄膜
- GB/T 17214.4-2005工業(yè)過程測量和控制裝置的工作條件第4部分:腐蝕和侵蝕影響
- 顯微鏡檢驗報告
- 信息的提煉與概括
- 小學(xué)音樂應(yīng)道萍名師工作室工作實施方案
- 學(xué)習(xí)2022年建團(tuán)一百周年主題班會PPT
評論
0/150
提交評論