阿里路由框架 ARouter 接入筆記

文章內容是在 ARouter 官方文檔 基礎上做了補充以及完善,所以會出現大量的重複。

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 使用的全部內容了,總體來說使用還是非常簡單方便的,而且並沒有什麼坑的地方,大家不妨動手試一下。

參考

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章