?本文意在快速集成并掌握阿里Android技術(shù)團(tuán)隊(duì)開(kāi)源的一款路由框架。這款路由框架可以為我們的應(yīng)用開(kāi)發(fā)提供更好更豐富的跳轉(zhuǎn)方案谒出。比如支持解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)痹籍,并自動(dòng)注入?yún)?shù)到目標(biāo)頁(yè)面中氓仲;支持添加多個(gè)攔截器朗恳,自定義攔截順序(滿足攔截器設(shè)置的條件才允許跳轉(zhuǎn),所以這一特性對(duì)于某些問(wèn)題又提供了新的解決思路)蔚叨。
????本文示例代碼基于ARouter框架最新1.3版本進(jìn)行編寫(xiě)床蜘。介于篇幅的原因?qū)⑵浞殖蓛善谝黄饕榻B該框架的配置以及基本使用蔑水;第二篇主要的內(nèi)容是通過(guò)現(xiàn)象去研究路由框架的源碼邢锯。
前言:
? ? 首先借用阿里云棲社區(qū)的一段話:我們所使用的原生路由方案一般是通過(guò)顯式intent和隱式intent兩種方式實(shí)現(xiàn)的(這里主要是指跳轉(zhuǎn)Activity or Fragment)。在顯式intent的情況下搀别,因?yàn)闀?huì)存在直接的類(lèi)依賴的問(wèn)題丹擎,導(dǎo)致耦合非常嚴(yán)重;而在隱式intent情況下领曼,則會(huì)出現(xiàn)規(guī)則集中式管理鸥鹉,導(dǎo)致協(xié)作變得非常困難。一般而言配置規(guī)則都是在Manifest中的庶骄,這就導(dǎo)致了擴(kuò)展性較差毁渗。除此之外,使用原生的路由方案會(huì)出現(xiàn)跳轉(zhuǎn)過(guò)程無(wú)法控制的問(wèn)題单刁,因?yàn)橐坏┦褂昧薙tartActivity()就無(wú)法插手其中任何環(huán)節(jié)了灸异,只能交給系統(tǒng)管理府适,這就導(dǎo)致了在跳轉(zhuǎn)失敗的情況下無(wú)法降級(jí),而是會(huì)直接拋出運(yùn)營(yíng)級(jí)的異常肺樟。這時(shí)候如果考慮使用自定義的路由組件就可以解決以上問(wèn)題檐春,比如通過(guò)URL索引就可以解決類(lèi)依賴的問(wèn)題;通過(guò)分布式管理頁(yè)面配置可以解決隱式intent中集中式管理Path的問(wèn)題么伯;自己實(shí)現(xiàn)整個(gè)路由過(guò)程也可以擁有良好的擴(kuò)展性疟暖,還可以通過(guò)AOP的方式解決跳轉(zhuǎn)過(guò)程無(wú)法控制的問(wèn)題,與此同時(shí)也能夠提供非常靈活的降級(jí)方式田柔。
添加依賴:
添加依賴
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName :project.getName() ]
} }
compile'com.alibaba:arouter-api:1.3.1'
annotationProcessor'com.alibaba:arouter-compiler:1.1.4'
初始化
官方建議我們?cè)贏pplication里面進(jìn)行ARouter初始化俐巴,于是乎就有了以下代碼:
初始化
然后別忘記了在清單文件里面配置自定義的Application和我們的Activity。
項(xiàng)目依賴導(dǎo)入和初始化就已經(jīng)完成了硬爆,下面就開(kāi)始正式的功能使用以及簡(jiǎn)單的封裝欣舵。
開(kāi)始使用:
1)首先:在Activity/Fragment類(lèi)上面寫(xiě)上 Route path 注解。
? ? ? 注意:這里的路徑需要注意的是至少需要有兩級(jí)缀磕,/xx/xx
2)然后:在Activity/Fragment類(lèi)里面進(jìn)入Arouter 注入缘圈,也就是:ARouter.getInstance().inject(this);
3)接著:目標(biāo)的Activity類(lèi)上面需要聲明Route path 注解,以此對(duì)應(yīng)(跳轉(zhuǎn)如果不對(duì)應(yīng)路徑袜蚕,框架會(huì)Toast說(shuō)路徑不匹配)
上述說(shuō)明的簡(jiǎn)單使用如下圖:
簡(jiǎn)單使用
理論上來(lái)說(shuō)糟把,如果只是進(jìn)行簡(jiǎn)單的跳轉(zhuǎn)頁(yè)面,
ARouter.getInstance().build(“目標(biāo)界面對(duì)應(yīng)的路徑”).navigation(); 就這樣一行代碼即可完成跳轉(zhuǎn)界面廷没。
好了糊饱,看到這里我們就會(huì)發(fā)現(xiàn)垂寥,路徑的標(biāo)簽如果多了就不是很好管理颠黎,(所以更好的選擇是寫(xiě)一個(gè)類(lèi),在這個(gè)類(lèi)里面統(tǒng)一管理和維護(hù)路徑標(biāo)簽滞项,不僅利于維護(hù)也方便后期拓展狭归,看到路徑就一目了然,哇~這個(gè)路徑對(duì)應(yīng)的是登錄界面文判,這個(gè)路徑對(duì)應(yīng)的是詳情界面)过椎;其次,每個(gè)頁(yè)面的注入戏仓,也就是ARouter.getInstance().inject(this);這句代碼出現(xiàn)的幾率會(huì)寫(xiě)的很多疚宇,(而且一般的常規(guī)邏輯是有注入就有解綁或者釋放資源)所以我們應(yīng)該簡(jiǎn)單封裝起來(lái)提高效率
簡(jiǎn)單封裝
首先是路徑管理:
路徑統(tǒng)一管理
然后是注入封裝:
這里多提一嘴,優(yōu)秀的第三方框架如果一般有注入或者綁定的API赏殃,那與之對(duì)應(yīng)的一般就會(huì)有釋放或者解綁資源的API敷待。(這樣做的本質(zhì)是優(yōu)化內(nèi)存)其中,? ARouter.getInstance().destroy( ) ; 這個(gè)API一目了然仁热,就是釋放資源的API榜揖。下面就是開(kāi)始注入和釋放資源的封裝:
注入封裝
筆者在Activity的基類(lèi)里面通過(guò)生命周期進(jìn)行了注入和解綁,但是項(xiàng)目運(yùn)行后發(fā)現(xiàn)了一個(gè)問(wèn)題,就是如果在onDestroy()里面調(diào)用了?ARouter.getInstance().destroy( ) ; 在進(jìn)入目標(biāo)Activity之后举哟,然后按back鍵返回原界面的時(shí)候思劳,APP會(huì)報(bào)錯(cuò)崩潰,下面是崩潰日志:
????仔細(xì)一看妨猩,初始化有問(wèn)題潜叛?在前面我們說(shuō)到在自定義Application里面已經(jīng)初始化了ARouter,且在清單文件里面配置了自定義的Application壶硅,但是依舊提示沒(méi)有初始化钠导,這就納了個(gè)悶?然后想了想森瘪,可能是?ARouter.getInstance().destroy( )牡属;這行代碼的使用位置可能用錯(cuò)了。然后既然是在Application里面進(jìn)行的初始化扼睬,那么就可以將這行釋放資源的代碼逮栅,寫(xiě)在Application生命周期的onTerminate( )里面,果不其然窗宇,項(xiàng)目運(yùn)行后就沒(méi)什么問(wèn)題了措伐。當(dāng)然,這是我自己的思路军俊,有更好的意見(jiàn)和想法請(qǐng)?jiān)谠u(píng)論區(qū)指出侥加,謝謝。
初始化
封裝完畢了路徑標(biāo)識(shí)以及注入釋放等基本功能粪躬,我們回到ARouter的基本使用:
簡(jiǎn)單頁(yè)面跳轉(zhuǎn)
如果只是簡(jiǎn)單的頁(yè)面跳轉(zhuǎn)担败,一行代碼即可完成,如下圖
簡(jiǎn)單跳轉(zhuǎn) - 1
其中镰官,build里面是頁(yè)面的標(biāo)簽路徑提前,對(duì)應(yīng)的就是目標(biāo)Activity的這里,也就是類(lèi)注釋標(biāo)簽路徑要一致:
Ps:不要忘了在清單文件里面配置Activity泳唠。
帶參數(shù)的界面跳轉(zhuǎn)
????帶參數(shù)的跳轉(zhuǎn)是很常見(jiàn)的功能狈网,Android可以通過(guò)Bundle去傳遞參數(shù),如果使用ARouter框架笨腥,它傳遞參數(shù)通過(guò)以下去操作:
????ARouter傳遞對(duì)象的時(shí)候拓哺,首先該對(duì)象需要Parcelable或者Serializable序列化,可能Parcelable這個(gè)序列化大家覺(jué)得手寫(xiě)起來(lái)比較麻煩脖母,但是Android Studio已經(jīng)有一些插件幫我們自動(dòng)生成Parcelable序列化了(因?yàn)锳ndroid用Parcelable序列化優(yōu)勢(shì)會(huì)更加明顯一些)
????字符串士鸥、char、int等基本數(shù)據(jù)類(lèi)型當(dāng)然都是可以傳遞的
????當(dāng)然镶奉,它也可以直接傳Bundle础淤、數(shù)組崭放、列表等很多對(duì)象,傳遞類(lèi)型如下圖
傳遞類(lèi)型
攜帶參數(shù)的界面跳轉(zhuǎn)鸽凶,簡(jiǎn)單使用如下圖
跳轉(zhuǎn)傳遞參數(shù)
其中币砂,第一個(gè)參數(shù)代表的是參數(shù)的key,第二個(gè)參數(shù)對(duì)應(yīng)的是我們要傳遞的屬性值,也就是value
那么目標(biāo)界面如何獲取傳遞過(guò)來(lái)的值玻侥?
這個(gè)時(shí)候决摧,我們需要在目標(biāo)界面,使用Autowired注解凑兰,
Autowired 注解
這樣就可以獲取到傳遞過(guò)來(lái)的值了
獲取屬性值
值得注意的是掌桩,只有當(dāng) @Autowired(name = "test"),也就是key標(biāo)簽一致的情況下姑食,才可以獲取到對(duì)象的值波岛,如果不寫(xiě)標(biāo)簽名,結(jié)果會(huì)為null音半,
Autowired 注解
所以為了規(guī)避每一個(gè)可能會(huì)遇到的風(fēng)險(xiǎn)则拷,建議在@Autowired里面 都寫(xiě)上與之對(duì)應(yīng)具體的key名。
界面跳轉(zhuǎn)動(dòng)畫(huà)
直接調(diào)用withTransition曹鸠,里面?zhèn)魅雰蓚€(gè)動(dòng)畫(huà)即可(R.anim.xxx)
跳轉(zhuǎn)動(dòng)畫(huà)
使用URI進(jìn)行跳轉(zhuǎn)
ARouter框架也可以使用URI進(jìn)行匹配跳轉(zhuǎn)煌茬,代碼也很少,只需匹配路徑一致即可完成跳轉(zhuǎn):
URI - 匹配跳轉(zhuǎn)
Fragment跳轉(zhuǎn)
Fragment的跳轉(zhuǎn)也可以參照Activity跳轉(zhuǎn)彻桃,第一步依舊是先寫(xiě)上類(lèi)注釋?zhuān)缓笫菑?qiáng)轉(zhuǎn)坛善,代碼如下
Fragment跳轉(zhuǎn)
進(jìn)階用法之?dāng)r截器:
????攔截器是ARouter這一款框架的亮點(diǎn)。說(shuō)起攔截器這個(gè)概念邻眷,可能印象更加深刻的是OkHttp的攔截器眠屎,OkHttp的攔截器主要是用來(lái)攔截請(qǐng)求體(比如添加請(qǐng)求Cookie)和攔截響應(yīng)體(判斷Token是否過(guò)期),在真正的請(qǐng)求和響應(yīng)前做一些判斷和修改然后在去進(jìn)行操作耗溜,大抵這就是攔截器的簡(jiǎn)單概念组力。那么,ARouter框架的攔截器是怎么實(shí)現(xiàn)的抖拴?
????ARouter的攔截器,是通過(guò)實(shí)現(xiàn)?IInterceptor接口腥椒,重寫(xiě)init()和process()方法去完成攔截器內(nèi)部操作的阿宅。
????首先我們定義兩個(gè)攔截器:
攔截器 - 1
攔截器 - 2
????首先,定義ARouter攔截器必須要使用Interceptor類(lèi)注解笼蛛。注解里面的 priority(也就是紅色框) 這個(gè)是聲明攔截器的優(yōu)先級(jí)洒放、里面的屬性值是int類(lèi)型。既然是定義優(yōu)先級(jí)滨砍,我們這里定義2個(gè)攔截器來(lái)測(cè)試看看優(yōu)先級(jí)是如何區(qū)分誰(shuí)先誰(shuí)后的往湿??jī)蓚€(gè)攔截器寫(xiě)完之后妖异,運(yùn)行下項(xiàng)目看下效果:
攔截器初始化先后順序
????結(jié)論 1:根據(jù)實(shí)驗(yàn)得知,使用Interceptor類(lèi)注解的priority數(shù)值越小领追,越先執(zhí)行他膳,優(yōu)先級(jí)越高。(四大組件中的廣播绒窑,優(yōu)先級(jí)的取值是 -1000到1000棕孙,數(shù)值越大優(yōu)先級(jí)越高)
????那么,還有一種情況些膨,如果兩個(gè)攔截器定義的優(yōu)先級(jí)都是一樣的蟀俊,那么誰(shuí)的優(yōu)先級(jí)會(huì)高?是根據(jù)類(lèi)的字符串長(zhǎng)度來(lái)判斷嘛還是別的條件來(lái)判斷的订雾?肢预?
????首先,將上面的攔截器的優(yōu)先級(jí)改成一樣(都改成1)洼哎,項(xiàng)目編譯試試误甚,結(jié)果發(fā)現(xiàn)項(xiàng)目就會(huì)直接報(bào)錯(cuò)!
同優(yōu)先級(jí)問(wèn)題
????看下具體的錯(cuò)誤原因:
相同優(yōu)先級(jí)錯(cuò)誤原因
????翻譯過(guò)來(lái)就是他們使用了相同的優(yōu)先級(jí)谱净,所以:
????結(jié)論 2:如果兩個(gè)攔截器的優(yōu)先級(jí)一樣窑邦,項(xiàng)目編譯就會(huì)報(bào)錯(cuò)。所以壕探,不同攔截器定義的優(yōu)先級(jí)屬性值不能相同
????我們到這兩個(gè)攔截器里面加一點(diǎn)篩選條件的代碼:
攔截器 - 3
????將這段代碼加進(jìn)去之后冈钦,重新運(yùn)行App,打印日志結(jié)果如下:
攔截器 - 執(zhí)行流程
????為了方便看清運(yùn)行的日志李请,我用三種顏色的箭頭去對(duì)應(yīng)瞧筛。首先是兩個(gè)攔截器的初始化,然后导盅,調(diào)用了NavigationCallback這個(gè)回調(diào)函數(shù)里面的onFound()较幌,然后執(zhí)行了攔截器里面的process()方法;當(dāng)攔截器的process()方法執(zhí)行完畢以后白翻,最終回調(diào)了NavigationCallback里面的onArrival()方法乍炉。攔截器的工作流程大抵就是這樣。那么滤馍,NavigationCallback這個(gè)又是什么岛琼?實(shí)際上,NavigationCallback這個(gè)簡(jiǎn)單理解就是ARouter在路由跳轉(zhuǎn)的過(guò)程中巢株,我們可以監(jiān)聽(tīng)路由的一個(gè)具體過(guò)程槐瑞。它一共有四個(gè)方法:
NavigationCallback
那么,這個(gè)回調(diào)里面的 Postcard 又是什么意思阁苞?點(diǎn)進(jìn)去源碼看看困檩,類(lèi)注釋寫(xiě)的一目了然:
Postcard類(lèi)注釋
紅色框翻譯過(guò)來(lái)的類(lèi)注釋就是:一個(gè)包含路線圖的容器祠挫。
既然是路線圖的容器,那肯定有些API會(huì)獲取到相應(yīng)的信息悼沿,
Group簡(jiǎn)單使用
????通過(guò)Postcard可以獲取到路徑的組以及全路徑等舔,那么,路徑的組(Group)又是什么显沈?是這樣软瞎,一般來(lái)說(shuō),ARouter在編譯期框架掃描了所有的注冊(cè)頁(yè)面/字段/攔截器等拉讯,那么很明顯運(yùn)行期不可能一股腦全部加載進(jìn)來(lái)涤浇,這樣就太不和諧了。所以就使用分組來(lái)管理魔慷,我們的類(lèi)標(biāo)簽里面的注釋?zhuān)瑢?duì)于group默認(rèn)是 “ ”(空字符串)如下圖:
????在 Group簡(jiǎn)單使用 這張圖上面只锭,根據(jù)日志,打印了分組的信息院尔,可以發(fā)現(xiàn)Group的值默認(rèn)就是第一個(gè) / ?/(兩個(gè)分隔符) 之間的內(nèi)容蜻展。
????那么,我們也可以自定義分組邀摆,來(lái)進(jìn)行界面跳轉(zhuǎn)纵顾,所以ARouter又提供了一種解決方案:
自定義分組 實(shí)現(xiàn)跳轉(zhuǎn)界面
如果使用自定義分組來(lái)跳轉(zhuǎn)界面,只需要在源代碼改動(dòng)以下三個(gè)位置:
1:類(lèi)注解新增 group栋盹,賦值我們自定義的組名施逾,(依舊統(tǒng)一寫(xiě)在一個(gè)類(lèi)里面這樣便于管理)
自定義分組跳轉(zhuǎn)界面 - 1
2:在build方法里面(這是一個(gè)方法重載),添加我們的與之對(duì)應(yīng)的組名
自定義分組跳轉(zhuǎn) - 2
3:在被跳轉(zhuǎn)的Activity里面的類(lèi)注釋?zhuān)由贤瑯拥慕M名
自定義分組跳轉(zhuǎn) - 3
通過(guò)上面三個(gè)步驟即可完成 自定義分組 來(lái)完成界面跳轉(zhuǎn)
自定義分組顯示結(jié)果
通過(guò)日志顯示例获,這里的組名已經(jīng)被我們更改成自定義分組且成功完成了跳轉(zhuǎn)汉额。
ARouter如何實(shí)現(xiàn)類(lèi)似startActivityForResult()?
這種應(yīng)用場(chǎng)景也是很常見(jiàn)的榨汤,那ARouter該如何實(shí)現(xiàn)蠕搜?
第一步:為了方便看效果,我們?cè)诘谝粋€(gè)Activity設(shè)置requestCode 為123收壕,
onActivityResult
第二步:需要在跳轉(zhuǎn)的navigation方法(這是一個(gè)方法重載)里面的第二個(gè)參數(shù)妓灌,設(shè)置我們定義的requestCode,(通過(guò)匹配requestCode 來(lái)實(shí)現(xiàn)該功能)
第三步:在第二個(gè)界面的setResult方法里面啼器,寫(xiě)上對(duì)應(yīng)的resultCode旬渠,這里就不展示Intent數(shù)據(jù)了
綜合上面三個(gè)步驟,項(xiàng)目編譯運(yùn)行端壳,跳轉(zhuǎn)到第二個(gè)界面然后返回上一個(gè)界面,日志成功打忧鼓ⅰ: