ARouter
幫助Android App進(jìn)行組件化改造的框架亩钟,支持模塊間路由驹针、通信逞刷、解耦
支持功能:
- 支持直接解析標(biāo)準(zhǔn)URL進(jìn)行跳轉(zhuǎn)纠俭,并自動(dòng)注入?yún)?shù)到目標(biāo)頁面中
- 支持多模塊工程使用
- 支持添加多個(gè)攔截器冰木,自定義攔截順序
- 支持依賴注入穷劈,可單獨(dú)作為依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關(guān)系按組分類、多級(jí)管理踊沸,按需初始化
- 用戶指定全局降級(jí)與局部降級(jí)策略
- 頁面歇终、攔截器、服務(wù)等組件均自動(dòng)注冊(cè)到框架
- 多種方式配置跳轉(zhuǎn)動(dòng)畫
- 獲取Fragment
- 完全支持Kotlin以及混編
- 第三方App加固(使用arouter-register實(shí)現(xiàn)自動(dòng)注冊(cè))
- 生成路由文檔
- IDE插件便捷關(guān)聯(lián)路徑和目標(biāo)類
應(yīng)用
- 從外部URL映射到內(nèi)部頁面逼龟,參數(shù)傳遞和解析
- 跨模塊頁面跳轉(zhuǎn)评凝,模塊解耦
- 攔截跳轉(zhuǎn)過程,處理登陸腺律、埋點(diǎn)
- 跨模塊API調(diào)用奕短,控制反轉(zhuǎn)來組件解耦
依賴(在app、子module等需要路由跳轉(zhuǎn)的模塊添加):
plugins {
// module如下疾渣,如果是app(id 'com.android.application')
id 'com.android.library'
// kotlin 擴(kuò)展
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
// kotlin模塊(和javamodule模塊不一樣注意)
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
dependencies{
implementation 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
}
初始化:在Application中進(jìn)行初始化
class BaseApplication :Application(){
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG){
ARouter.openDebug()
ARouter.openLog()
}
ARouter.init(this)
}
}
一個(gè)包含多個(gè)module項(xiàng)目篡诽,名為user的module中存在一個(gè)UserHomeActivity,路由路徑:/account/userHome榴捡。從其他module跳轉(zhuǎn)該頁面杈女,指定path來跳轉(zhuǎn)。
// 主app里的
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_home)
}
}
// module里的
@Route(path = "/library1/test")
class Library1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_library1)
tvTest.setOnClickListener{
// 跨模塊跳轉(zhuǎn)
ARouter.getInstance().build("/account/userHome").navigation()
}
}
}
編譯階段生成輔助代碼來實(shí)現(xiàn)path跳轉(zhuǎn)吊圾。需要拿到Activity的Class對(duì)象才行达椰。編譯階段,ARouter根據(jù)我們?cè)O(shè)定的路由跳轉(zhuǎn)規(guī)則來自動(dòng)生成映射文件项乒,包含path和ActivityClass之間的對(duì)應(yīng)關(guān)系啰劲。
UserHomeActivity編譯階段自動(dòng)生成輔助java文件ARouter$$Group$$account類中就將path和ActivityClass作為鍵值對(duì)保存到了Map中,ARouter依靠此進(jìn)行跳轉(zhuǎn)檀何。
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
}
}
這類自動(dòng)生成的文件包名路徑都是com.alibaba.android.arouter.routes蝇裤,類名前綴特定規(guī)則。雖然ARouter$$Group$$account類實(shí)現(xiàn)了對(duì)應(yīng)關(guān)系保存到Map中频鉴,loadInto方法還是需要有ARouter運(yùn)行時(shí)來調(diào)用栓辜,ARouter就需要拿到ARouter$$Group$$account這個(gè)類才行,ARouter通過掃描com.alibaba.android.arouter.routes這個(gè)包名路徑來獲取所有輔助文件垛孔。
1藕甩、程序員自己維護(hù)特定path和特定目標(biāo)類之間對(duì)應(yīng)關(guān)系,ARouter只要求開發(fā)者使用包含path的@Route 注解修飾目標(biāo)類
2周荐、ARouter在編譯階段通過注解處理器自動(dòng)生成path和特定的目標(biāo)類之間的對(duì)應(yīng)關(guān)系狭莱,path作為key僵娃,將目標(biāo)類Class對(duì)象為value之一存到Map中
3、在運(yùn)行階段腋妙,通過path來發(fā)起請(qǐng)求默怨,ARouter根據(jù)path從map中取值,拿到目標(biāo)類
ARouter類使用單例模式辉阶,暴露外部調(diào)用的API先壕。實(shí)現(xiàn)邏輯交給_ARouter來完成。
_ARouter類包私有權(quán)限谆甜,使用了單例垃僚,通過init(Application)-->LogisticsCenter.init(mContext, executor)
LogisticsCenter實(shí)現(xiàn)掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件的邏輯。在進(jìn)行初始化的時(shí)候规辱,加載到當(dāng)前項(xiàng)目一共包含所有g(shù)roup谆棺,以及每個(gè)group對(duì)應(yīng)的路由信息表。
1罕袋、如果開啟debug模式或通過本地SP緩存判斷出app的版本前后發(fā)生變化改淑,需要重新獲取路由信息,否則從使用之前緩存到SP中的數(shù)據(jù)浴讯。
2朵夏、獲取全局路由信息是耗時(shí)操作,所以ARouter通過將全局路由信息緩存到SP中來實(shí)現(xiàn)榆纽。開發(fā)階段可能隨時(shí)添加路由表仰猖,每次發(fā)布新的版本正常都會(huì)加大應(yīng)用版本號(hào),所以ARouter只開啟了debug模式或者版本號(hào)發(fā)生變化的時(shí)候才會(huì)重新獲取路由信息
3奈籽、獲取路由信息包含了com.alibaba.android.arouter.routes包下自動(dòng)生成的輔助文件的全路徑饥侵,判斷路徑名前綴字符串,知道該類什么類型文件衣屏,通過反射構(gòu)建不同類型對(duì)象躏升,調(diào)用對(duì)象的方法將路由信息存到Warehouse的Map中。
UserHomeActivity狼忱,其path為/account/userHome,ARouter默認(rèn)會(huì)將path的第一個(gè)單詞account作為group膨疏,而UserHomeActivity放到名為app(module名字)的module中
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("account", ARouter$$Group$$account.class);
}
}
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
}
}
LogisticsCenter的init文件名前綴ARouter$$Root$$定位到ARouter$$Root$$app這個(gè)類,然后通過反射構(gòu)建出該對(duì)象钻弄,通過調(diào)用loadInto的頁面時(shí)成肘,反射調(diào)用ARouter$$Group$$account的loadInto方法,按需加載斧蜕,等到需要的時(shí)候再獲取詳細(xì)的路由對(duì)應(yīng)信息。
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
build()通過ARouter中轉(zhuǎn)調(diào)用_ARouter的build()返回Postcard對(duì)象砚偶,用于傳入跳轉(zhuǎn)配置參數(shù)批销,例如:mBundle洒闸、開啟綠色通道greenChannel、跳轉(zhuǎn)動(dòng)畫optionsCompat等
Postcard的navigation()方法會(huì)調(diào)用_ARouter完成Activity跳轉(zhuǎn)均芽。
navigation方法的重點(diǎn)在于LogisticsCenter.completion(postcard)丘逸。按需加載反射調(diào)用ARouteraccount 的 loadInto 方法。completion方法就獲取詳細(xì)路由信息掀宋。通過postcard攜帶的path和group信息從Warehouse取值深纲,如果不是null信息保存到postcard中,為null則拋出NoRouteFoundException
跳轉(zhuǎn)到Activity并注入?yún)?shù)
// 帶參數(shù)跳轉(zhuǎn)
ARouter.getInstance()
.build("/account/userHome")
.withLong("userId",13)
.withString("userName","小明")
.navigation()
// 參數(shù)接收
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
private val TAG = "UserHome-"
@Autowired(name = "userId")
@JvmField
var userId:Long = 0
@Autowired(name = "userName")
@JvmField
var userName = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_home)
// 需要注冊(cè)劲妙,否則無法接收到參數(shù)傳遞
ARouter.getInstance().inject(this)
Log.d(TAG, "onCreate: userId:$userId userName:$userName")
}
}
通過@Autowired注解修飾變量湃鹊。聲明name參數(shù),傳遞鍵值對(duì)的key對(duì)應(yīng)镣奋。通過ARouter.getInstance().inject(this)后币呵,完成參數(shù)賦值。
生產(chǎn)輔助代碼:
public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
UserHomeActivity substitute = (UserHomeActivity)target;
substitute.userId = substitute.getIntent().getLongExtra("userId", substitute.userId);
substitute.userName = substitute.getIntent().getStringExtra("userName");
}
}
攜帶的參數(shù)放到Intent侨颈,inject方法實(shí)現(xiàn)了從Intent取值向變量賦值余赢,要求變量是public的,kotlin需要同時(shí)向變量加上@JvmField注解的原因哈垢。參數(shù)注解:ARouter.getInstance().inject(this)
ARouter通過控制反轉(zhuǎn)拿到AutowiredService對(duì)應(yīng)實(shí)現(xiàn)類AutowiredServiceImpl的對(duì)象妻柒,調(diào)用autowire方法完成參數(shù)注入。
由于參數(shù)注入輔助類的類名具有固定的包名和類名耘分,目標(biāo)類類名+$$ARouter$$Autowired, 所以AutowiredServiceImpl傳入的instance參數(shù)和反射過來生成的對(duì)象举塔,最終調(diào)用inject方法完成參數(shù)注入。
控制反轉(zhuǎn)
跳轉(zhuǎn)Activity并自動(dòng)注入?yún)?shù)屬于依賴注入的一種陶贼,ARouter同時(shí)也支持控制反轉(zhuǎn):通過接口獲取其實(shí)現(xiàn)類實(shí)例
存在一個(gè)ISayHelloService接口啤贩,需要拿到其實(shí)現(xiàn)類實(shí)例,但是不希望使用的時(shí)候和特定實(shí)現(xiàn)類SayHelloService綁定在一起造成耦合拜秧,使用ARouter控制反轉(zhuǎn)功能痹屹,要求ISayHelloService接口繼承了IProvider接口才行
注意:Route(path = "/account/sayhello")在實(shí)現(xiàn)的接口和調(diào)用的類path要一致,才能實(shí)現(xiàn)反轉(zhuǎn)控制枉氮。
// 接口
interface ISayHelloService :IProvider {
fun sayHello()
}
// 具體實(shí)現(xiàn)
@Route(path = "/account/sayhello")
class SayHelloService :ISayHelloService{
private val TAG = "SayHello-"
override fun sayHello() {
Log.d(TAG, "sayHello: ")
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
// 具體使用
@Route(path = "/account/sayhello")
class MainActivity : AppCompatActivity() {
private val TAG = "Main-網(wǎng)絡(luò)測(cè)試"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textNet.setOnClickListener {
ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
}
}
}
在使用的時(shí)候直接傳遞ISayHelloService的Class對(duì)象即可志衍,ARouter會(huì)將SayHelloService以單例形式返回,無需開發(fā)者手動(dòng)構(gòu)建SayHelloService對(duì)象聊替,從而達(dá)到解耦楼肪。
ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
和實(shí)現(xiàn)Activity跳轉(zhuǎn)一樣,ARouter自動(dòng)生成幾個(gè)文件惹悄,包含路由表映射關(guān)系春叫。
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/sayhello", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
}
}
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.george.learnretrofit.test.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
}
}
LogisticsCenter實(shí)現(xiàn)掃描特定包名路徑拿到所有自動(dòng)生成的輔助文件邏輯。最終Warehouse中就會(huì)在初始化的時(shí)候拿到數(shù)據(jù)
Warehouse.groupsIndex:
- account--->class com.alibaba.android.arouter.routes.ARouter$$Group$$account
Warehouse.providersIndex:
com.george.learnretrofit.test.ISayHelloService--->RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648)
LogisticsCenter.completion(postcard)獲取對(duì)象實(shí)例時(shí)候同時(shí)將實(shí)例緩存起來,以后復(fù)用暂殖。
攔截器
可以通過攔截器來判斷用戶是否處于登陸狀態(tài)价匠,還未登陸的話,攔截請(qǐng)求呛每,打開登陸界面踩窖。
可以添加多個(gè)攔截器,每個(gè)攔截器設(shè)置不同優(yōu)先級(jí)
@Interceptor(priority = 100,name="啥也不做的攔截器")
class NothingInterceptor :IInterceptor{
private val TAG = "Nothing-1-"
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
// 不攔截晨横,任其跳轉(zhuǎn)
Log.d(TAG, "process: ")
callback?.onContinue(postcard)
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
@Interceptor(priority = 200, name = "登陸攔截器")
class LoginInterceptor: IInterceptor{
private val TAG = "Nothing-2-"
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
if (postcard?.path == "/account/userHome"){
// 攔截
Log.d(TAG, "process: 攔截-到主頁")
callback?.onInterrupt(null)
ARouter.getInstance().build("/account/sayhello").navigation()
}else{
Log.d(TAG, "process: 不攔截")
// 不攔截洋腮,任其跳轉(zhuǎn)
callback?.onContinue(postcard)
}
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
priority越小優(yōu)先級(jí)越高(越先執(zhí)行)。
_ARouter的navigation方法手形,轉(zhuǎn)交給interceptorService啥供,判斷有沒有開啟綠色通道模式。InterceptorServiceImpl實(shí)現(xiàn)類叁幢,ARouter初始化過程中通過控制反轉(zhuǎn)拿到interceptorService實(shí)例滤灯。
1、第一次獲取InterceptorServiceImpl實(shí)例時(shí)候曼玩,其init方法會(huì)馬上被調(diào)用鳞骤,交由線程池來處理。通過反射生成每個(gè)攔截器對(duì)象黍判,并調(diào)用每個(gè)攔截器的init方法完成攔截器初始化豫尽,將每個(gè)攔截器對(duì)象都存到Warehouse.interceptors中。如果初始化完成顷帖,則喚醒等待的interceptorInitLock上的線程美旧。
2、doInterceptions方法被調(diào)用贬墩,如果第一個(gè)步驟未執(zhí)行完榴嗅,則通過checkInterceptorsInitStatus()方法等待第一次步驟完成。如果10s內(nèi)都未完成陶舞,則走失敗流程直接返回嗽测。
3、在線程池中遍歷攔截器列表肿孵,如果某個(gè)攔截器攔截請(qǐng)求則調(diào)用callback.onInterrupt方法通知內(nèi)部唠粥,否則調(diào)用callback.onContinue()方法繼續(xù)跳轉(zhuǎn)邏輯。
注解處理器
依靠注解器生成輔助文件停做,ARouter才能完成參數(shù)自動(dòng)注入功能晤愧。
APT(Annotation Processing Tool)注解處理器,用在編譯期掃描和處理注解蛉腌,通過注解生成Java文件官份。即注解作為橋梁只厘,預(yù)先設(shè)定好的代碼生成規(guī)則,自動(dòng)生成Java文件舅巷。例如:ButterKnife/Dragger2/EventBus等
Java API已經(jīng)提供了掃描源碼并解析注解的框架懈凹,通過繼承AbstractProcessor類來實(shí)現(xiàn)自己的注解解析邏輯。APT的原理就是在注解了某些代碼元素(如字段悄谐、函數(shù)、類等)后库北。在編譯時(shí)編輯器會(huì)檢查AbstractProcess的子類爬舰,并且自動(dòng)調(diào)用process()方法,然后將指定注解的所有代碼元素作為參數(shù)傳遞給該方法寒瓦,開發(fā)者根據(jù)注解元素在編譯期輸出Java代碼情屹。
ARouter源碼中和注解處理器相關(guān)的module
- router-annotation。Java Module杂腰,包含了Autowired垃你、Interceptor這些注解及RouteMeta等JavaBean
- arouter-compiler。Android Module喂很,包含了AbstractProcessor的實(shí)現(xiàn)類用于生產(chǎn)代碼