文章內容是在 ARouter 官方文檔 基礎上做了補充以及完善,所以會出現大量的重複。
Arouter系列:
【ARouter】接入筆記
【ARouter】初始化過程分析
【ARouter】跳轉 Activity 過程分析
文章目錄
ARouter
什麼是 ARouter?
一個用於幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦
github地址:https://github.com/alibaba/ARouter
爲什麼需要路由?
Android 本身是提供了頁面間跳轉的比如 startActivity 以及 Uri 的方式等,但是這些方式在一些場景下不夠靈活。比如:
- 在推送需要打開特定頁面的情況下,需要很靈活的能跳轉到相應的頁面,這個時候如果有完整的路由系統,則跳轉到任何頁面都不成問題(當然通過反射的形式進行跳轉也不是不可以);
- 在多 module 的情況下,如果沒有路由系統模塊間跳轉是很麻煩的一件事情,引入路由系統能很好地做到模塊間解耦;
- 有了完整的路由系統我們就可以統一的對跳轉做處理,比如打點,攔截等等;
爲什麼是 ARouter?
下面摘抄自 ARouter 官方文檔:
- 支持直接解析標準URL進行跳轉,並自動注入參數到目標頁面中
- 支持多模塊工程使用
- 支持添加多個攔截器,自定義攔截順序
- 支持依賴注入,可單獨作爲依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關係按組分類、多級管理,按需初始化
- 支持用戶指定全局降級與局部降級策略
- 頁面、攔截器、服務等組件均自動註冊到框架
- 支持多種方式配置轉場動畫
- 支持獲取Fragment
- 完全支持Kotlin以及混編
- 支持第三方 App 加固(使用 arouter-register 實現自動註冊)
- 支持生成路由文檔
ARouter 使用
配置
java 和 kotlin 的配置方式不太一樣,java 的配置方式:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
如果使用 kotlin 則需要下面這樣配置:
apply plugin: 'kotlin-kapt'
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
dependencies {
compile 'com.alibaba:arouter-api:x.x.x'
kapt 'com.alibaba:arouter-compiler:x.x.x'
...
}
混淆
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式獲取 Service,需添加下面規則,保護接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 單類注入,即不定義接口實現 IProvider,需添加下面規則,保護實現
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
初始化以及簡單跳轉
初始化
推薦在 Application 中進行初始化。
private fun initARouter() {
if (BuildConfig.DEBUG) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(this)
}
推薦在開發的時候打開openDebug,這樣能看到詳細的類掃描結果,並且每次都會重新掃描是否有新增的類,否則可能出現新增了類卻找不到的情況。
簡單跳轉
簡單的兩步,在需要跳轉的 activity 上增加註解
//路徑必須爲兩級,第一級是可以重複的,代表了組名
@Route(path = "/main/mainActivityTest")
class MainTestActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val textView = TextView(this)
textView.text = "TestActivity"
setContentView(textView)
}
}
使用 navigation 方法進行簡單跳轉:
//簡單跳轉
ARouter.getInstance().build("/main/mainActivityTest").navigation()
//(不推薦)即使手動指定了組名,也要給出全路徑 否則會找不到類
ARouter.getInstance().build("/main/mainActivityTest", "main").navigation()
由於 ARouter 的路由加載機制是按組加載的,只有當一組中某一個路由被訪問了,這個組的路由纔會被加載到內存中,所以一樣要做好分組工作。
傳遞參數
同時也可以在跳轉過程中攜帶相應的參數,和使用 intent 類似,目標 activity 接收和使用 intent 相同:
ARouter.getInstance().build("/main/mainActivityTest")
.withString("test", "傳遞一個字符串過來")
navigation(this, naviagtion)
ARouter 支持多種類型的參數,可以看做和 intent 一樣:
使用 URL 進行跳轉
ARouter 一樣支持通過 URL 來進行跳轉,個人感覺這個最好的應用場景就是通過一個 host 和 scheme 來做一個 activity 跳轉的中間頁,然後通過 ARouter 自動解析 scheme 中的 path 來再次跳轉,這樣就不用在 AndroidManifest 中對多個 activity 進行添加。
假設有一箇中間頁面它的清單文件註冊如下:
<activity android:name=".activity.SchemeActivity">
<!-- Schame -->
<intent-filter>
<data
android:host="m.maintel.cn"
android:scheme="arouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
模擬通過 scheme 跳轉到它,然後通過它再進行轉發:
val uri = Uri.parse("arouter://m.maintel.cn/test/schameTest")
ARouter.getInstance().build(uri).navigation(this, 100, naviagtion)
註冊一個 path 爲 /test/schameTest
的 activity,則會自動跳轉到指定的 activity,ARouter 會自動解析 Url 路徑中的 path 來跳轉。但是 ARouter 本身不支持通過 URI 來進行隱式的跳轉。
解析參數
ARouter 支持通過註解的方式自動對字段進行賦值。
聲明字段,並添加 @Autowired 註解,ARouter 就能自動的對跳轉中的參數進行解析,並對這些字段自動賦值。支持通過 api 或者 URL 的形式傳遞。
// 目標 activity
@Route(path = "/test/schameTest")
class SchameTestActivity : AppCompatActivity() {
// 可以手動指定名稱,如果不指定 name 則默認爲字段名
@Autowired(name = "name")
@JvmField
public var account = ""
@Autowired
@JvmField
public var age = 0
@Autowired
@JvmField
public var student: Student? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 如果要自動注入,必須添加這一行代碼
ARouter.getInstance().inject(this)
val textview = TextView(this)
textview.text = "$account::$age::student::${student.toString()}"
setContentView(textview)
}
}
在 Kotlin 中使用的時候注意在字段上加上 @JvmField 註解,否則即使指定了 public 也無法編譯通過,因爲在 Kotlin 中 public 的含義和 java 中是不同的。
解析 URL 中的參數
用URL跳轉比如可以這樣:
val uri = Uri.parse("arouter://m.maintel.cn/test/schameTest?name=maintel&age=100" +
"&student={name:\"老王\",age:20}")
ARouter.getInstance().build(uri).navigation()
如果要在 URL 中傳遞 Obj 則需要使用 json 形式的,並且需要自定義 json 解析器。也很簡單隻要實現 SerializationService 接口即可,使用方式如下:
// path 爲任意即可,同時要注意的是解析器只能有一個,如果定義了多個,則只有一個會生效。
@Route(path = "/jsonService/json")
class JsonServiceImpl : SerializationService {
override fun <T : Any?> json2Object(input: String?, clazz: Class<T>?): T {
return Gson().fromJson(input, clazz)
}
override fun init(context: Context?) {
}
override fun object2Json(instance: Any?): String {
return Gson().toJson(instance)
}
override fun <T : Any?> parseObject(input: String?, clazz: Type?): T {
return Gson().fromJson(input, clazz)
}
}
URL 中是不能傳遞 Parcelable 類型的數據的,如果想要傳遞就需要 API 來傳遞。
使用 API 傳遞參數
API 的傳遞參數的方法在上面簡單說明了已經,鏈式的調用 .withxxx 即可。
ARouter.getInstance().build("/test/schameTest")
.withString("name", "maintel")
.withInt("age", 100)
.withObject("student", Student("老王", 20))
.navigation()
不過要注意的是,如果使用了 withObject 方法,則也需要如使用 URL 的方式一樣,定義一個解析器,否則不能傳遞數據並引起崩潰。
還有一點要注意的是,在使用 URL 進行跳轉的時候,即使不需要自動注入,那麼可以不寫 ARouter.getInstance().inject(this)
代碼,但是在字段名上也要加上 @Autowired
註解,否則即使通過 intent 一樣獲取不到想要的數據。
攔截器
使用攔截器可以在使用 ARouter 跳轉的過程中進行一些處理,比如打點,檢測登錄狀態等等。使用也很簡單,實現 IInterceptor 接口,然後加上 @Interceptor 即可:
@Interceptor(priority = 10, name = "攔截測試")
class TestInterceptor : IInterceptor {
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
if (notLoging) {
// 繼續執行跳轉
callback?.onContinue(postcard)
} else {
// 拋出一個異常攔截掉登錄,或者跳轉到特定頁面
// callback?.onContinue(ARouter.getInstance().build("/login/loginActivity"))
callback?.onInterrupt(RuntimeException("非法路由"))
}
}
override fun init(context: Context?) {
println("TestInterceptor 初始化")
}
}
使用時要注意:
callback.onContinue
或者callback.onInterrupt
必須有一個要被調用;- 攔截器可以設置多個,可以通過 priority 指定優先級,數值越低優先級越高,越先執行;
- 攔截器是在子線程中執行的;
- 可以使用
ARouter.getInstance().build("/home/main").greenChannel().navigation();
跳過所有攔截器
跳轉結果及全局策略
ARouter 支持在跳轉過程中對跳轉結果的回調,我們可以自己處理這些回調:
ARouter.getInstance().build(ACTIVITY_MAIN_TEST).navigation(this, object : NavigationCallback {
override fun onLost(postcard: Postcard?) {
// 未找到路由
println("onLost")
}
override fun onFound(postcard: Postcard?) {
//找到路由
println("onFound")
}
override fun onInterrupt(postcard: Postcard?) {
// 被攔截
println("onInterrupt")
}
override fun onArrival(postcard: Postcard?) {
// 到達 到達會在目標 activity 的 onCreate 之前執行
println("onArrival")
}
})
同時也可以全局的處理未找到路由的情況,實現 DegradeService 接口,並且給一個任意路由即可,這樣我們就能全局的對未找到路由的情況進行處理,而且它的優先級是要比在跳轉時指定的回調低的,即如果在跳轉時指定了跳轉結果的回調,則不會再走全局的處理方法。
@Route(path = "/degrade/impl")
class DegradeServiceImpl : DegradeService {
override fun onLost(context: Context?, postcard: Postcard?) {
// 要注意的是這裏的 context 可能爲 null,它和 ARouter..navigation() 中傳遞的 context 是同一個。
ARouter.getInstance().build(ACTIVITY_MAIN).navigation()
}
override fun init(context: Context?) {
}
}
通過依賴注入解耦:服務管理
這裏說的服務,並不是 Android 四大組件中的 service,而是ARouter 提供的一種接口,通過實現這個接口並提供路由,然後通過 ARouter 可以實現在不同模塊間進行數據交互以及通訊的功能。簡單的使用就想下面這樣:
聲明接口,並繼承自 IProvider
interface TestService : IProvider {
fun test(str: String): String
fun getdata(): String
}
實現接口:
@Route(path = "/serviceTest/test", name = "test service")
class TestServiceImpl : TestService {
var data = ""
override fun test(str: String): String {
return "TestServiceImpl::$str"
}
override fun getdata(): String {
return data
}
override fun init(context: Context?) {
println("TestServiceImpl init")
}
}
在某個 Activity 中初始化 TestServiceImpl,並提供數據:
val testService = ARouter.getInstance().build("/serviceTest/test").navigation() as TestServiceImpl
testService.data = "隔壁老王"
然後在另外某個地方就能通過同樣的方式來獲取數據:
@Route(path = PARENT_ACTIVITY_MAIN)
class ParentMainActivity : AppCompatActivity() {
@Autowired(name = "/serviceTest/test")
@JvmField
public var testService: TestService? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
val textView = TextView(this)
textView.text = testService?.getdata()
setContentView(textView)
}
}
可以看到上面兩個 activity 之間沒有任何關聯,但是通過 ARouter 提供的服務,兩者完成了數據交互。
需要注意的是在服務設置數據以及獲取數據的時候必須使用 ARouter 提供的方式來獲取實例,否則達不到效果。ARouter 提供了多種方式來獲取服務的實例:
@Autowired
HelloService helloService;
//如果一個接口有多個實現的話,必須通過路由的方式來獲取
@Autowired(name = "/yourservicegroupname/hello")
HelloService helloService2;
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
以上基本上就是 ARouter 使用的全部內容了,總體來說使用還是非常簡單方便的,而且並沒有什麼坑的地方,大家不妨動手試一下。