一、起源
- 由于業(yè)務(wù)的增長提高了項目的復(fù)雜性,為了更好的適應(yīng)團隊開發(fā),提高開發(fā)效率,實行組件化乃大勢所趨。
- 組件化可以讓我們程序更容易的擴展、更方便的維護,更快捷的同步開發(fā)與更簡單的單元調(diào)試。
- 但是由于各個組件在不同的model甚至在不同的項目中,這樣他們之間的通信就成了問題,而
ARouter的出現(xiàn)就是讓組件間、模塊間是實現(xiàn)完全的獨立。 - 并且后臺還可以通過路由的機制控制Android界面調(diào)用實現(xiàn)遠程控制和高度解耦。
二、是什么?
一個用于幫助 Android App 進行組件化改造的框架 —— 支持模塊間的路由、通信、解耦
三、功能介紹
- 支持直接解析標準URL進行跳轉(zhuǎn),并自動注入?yún)?shù)到目標頁面中
- 支持多模塊工程使用
- 支持添加多個攔截器,自定義攔截順序
- 支持依賴注入,可單獨作為依賴注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射關(guān)系按組分類、多級管理,按需初始化
- 支持用戶指定全局降級與局部降級策略
- 頁面、攔截器、服務(wù)等組件均自動注冊到框架
- 支持多種方式配置轉(zhuǎn)場動畫
- 支持獲取Fragment
- 完全支持Kotlin以及混編
- 支持第三方 App 加固(使用 arouter-register 實現(xiàn)自動注冊)
- 支持生成路由文檔
- 提供 IDE 插件便捷的關(guān)聯(lián)路徑和目標類
四、原理解析
1、調(diào)用關(guān)系
-
兩個界面之間的跳轉(zhuǎn),調(diào)用關(guān)系如下:
界面調(diào)用用例圖
- 注冊:B界面先在ARouter里注冊一個唯一標識;
- 查詢:A界面調(diào)用時,ARouter會先查詢B界面是否已注冊;
- 整合:ARouter查詢到B界面后,會將A界面?zhèn)魅胄畔⑦M行整合;
- 調(diào)用:整合好信息后,會調(diào)起B(yǎng)界面并將傳入信息傳遞到B界面。
2、流程解析
- 界面跳轉(zhuǎn),arouter做了以下工作:
ARouter流程圖
五、原理解析
- 從外部URL映射到內(nèi)部頁面,以及參數(shù)傳遞與解析
- 跨模塊頁面跳轉(zhuǎn),模塊間解耦
- 攔截跳轉(zhuǎn)過程,處理登陸、埋點等邏輯
- 跨模塊API調(diào)用,通過控制反轉(zhuǎn)來做組件解耦
六、基礎(chǔ)功能
1.添加依賴和配置
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'
}
2.初始化SDK
if (isDebug()) { // 這兩行必須寫在init之前,否則這些配置在init過程中將無效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 開啟調(diào)試模式(如果在InstantRun模式下運行,必須開啟調(diào)試模式!線上版本需要關(guān)閉,否則有安全風(fēng)險)
}
ARouter.init(mApplication); // 盡可能早,推薦在Application中初始化
3.簡單跳轉(zhuǎn)
- 目標界面
// 在支持路由的頁面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
- 跳轉(zhuǎn)操作
// 1. 應(yīng)用內(nèi)簡單的跳轉(zhuǎn)
ARouter.getInstance().build("/test/activity").navigation();
//等同于startActivityForResult
ARouter.getInstance().build("/test/activity").navigation(getActivity(),REQUEST_CODE);
4.帶參跳轉(zhuǎn)
- 目標界面
@Route(path = "/test/1")
public class YourActivity extend Activity {
//獲取數(shù)據(jù)三種方式
//1.通過Autowired注解表明key & 需要在onCreate中調(diào)用ARouter.getInstance().inject(this);配合使用
// 通過name來映射URL中的不同參數(shù)
@Autowired(name = "key1")
public long data;
//2.通過Autowired注解 & 將key1作為屬性的名稱 & 需要在onCreate中調(diào)用ARouter.getInstance().inject(this);配合使用
@Autowired()
public long key2;
//3.通過Bundle獲取
getIntent().getExtras().getLong("key3")
}
- 跳轉(zhuǎn)操作
// 2. 跳轉(zhuǎn)并攜帶參數(shù)
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key2", "888")
.withObject("key3", new Test("Jack", "Rose"))
.navigation();
七、進階用法
1.通過URL跳轉(zhuǎn)
方法一
- AndroidManifest.xml
<activity android:name=".activity.SchameFilterActivity">
<!-- Schame -->
<intent-filter>
<data
android:host="m.aliyun.com"
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>
- 跳轉(zhuǎn)操作
// 新建一個Activity用于監(jiān)聽Schame事件,之后直接把url傳遞給ARouter即可
public class SchameFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
方法二
- 跳轉(zhuǎn)操作
Uri testUriMix = Uri.parse("arouter://m.aliyun.com/test/activity2");
ARouter.getInstance().build(testUriMix)
.withString("key1", "value1")
.navigation();
- 目標界面
@Route(path = "/test/activity2")
public class Test2Activity extends AppCompatActivity {
}
2.解析參數(shù)(傳遞Object)
- 定義解析Obeject的SerializationService
// 如果需要傳遞自定義對象,新建一個類(并非自定義對象類),然后實現(xiàn) SerializationService,并使用@Route注解標注(方便用戶自行選擇序列化方式),例如:
@Route(path = "/yourservicegroupname/json")
public class JsonServiceImpl implements SerializationService {
@Override
public void init(Context context) {
}
@Override
public <T> T json2Object(String text, Class<T> clazz) {
return JSON.parseObject(text, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
}
- 跳轉(zhuǎn)操作
ARouter.getInstance().build("/test/1")
.withObejct("obj", new TestObj ("Jack", "Rose"))
.navigation();
- 目標界面
// 為每一個參數(shù)聲明一個字段,并使用 @Autowired 標注
// URL中不能傳遞Parcelable類型數(shù)據(jù),通過ARouter api可以傳遞Parcelable對象
@Route(path = "/test/activity")
public class Test1Activity extends Activity {
// 支持解析自定義對象,URL中使用json傳遞
@Autowired
TestObj obj;
// 使用 withObject 傳遞 List 和 Map 的實現(xiàn)了
// Serializable 接口的實現(xiàn)類(ArrayList/HashMap)
// 的時候,接收該對象的地方不能標注具體的實現(xiàn)類類型
// 應(yīng)僅標注為 List 或 Map,否則會影響序列化中類型
// 的判斷, 其他類似情況需要同樣處理
@Autowired
List<TestObj> list;
@Autowired
Map<String, List<TestObj>> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class);
serializationService.init(this);
obj = serializationService.parseObject(getIntent().getStringExtra("key4"), User.class);
}
}
3.聲明攔截器(攔截跳轉(zhuǎn)過程,面向切面編程)
// 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過程中處理登陸事件,這樣就不需要在目標頁重復(fù)做登陸檢查
// 攔截器會在跳轉(zhuǎn)之間執(zhí)行,多個攔截器會按優(yōu)先級順序依次執(zhí)行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
callback.onContinue(postcard); // 處理完成,交還控制權(quán)
// callback.onInterrupt(new RuntimeException("我覺得有點異常")); // 覺得有問題,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種,否則不會繼續(xù)路由
}
@Override
public void init(Context context) {
// 攔截器的初始化,會在sdk初始化的時候調(diào)用該方法,僅會調(diào)用一次
}
}
4.處理跳轉(zhuǎn)結(jié)果
// 使用兩個參數(shù)的navigation方法,可以獲取單次跳轉(zhuǎn)的結(jié)果
ARouter.getInstance().build("/test/1").navigation(this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
...
}
@Override
public void onLost(Postcard postcard) {
...
}
});.
ARouter.getInstance().build("/test/activity2")
.navigation(this, new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
}
@Override
public void onInterrupt(Postcard postcard) {
Log.d("ARouter", "被攔截了");
}
});
5.自定義全局降級策略
// 實現(xiàn)DegradeService接口,并加上一個Path內(nèi)容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// do something.
}
@Override
public void init(Context context) {
}
}
6.為目標頁面聲明更多信息
// 我們經(jīng)常需要在目標頁面中配置一些屬性,比方說"是否需要登陸"之類的
// 可以通過 Route 注解中的 extras 屬性進行擴展,這個屬性是一個 int值,換句話說,單個int有4字節(jié),也就是32位,可以配置32個開關(guān)
// 剩下的可以自行發(fā)揮,通過字節(jié)操作可以標識32個開關(guān),通過開關(guān)標記目標頁面的一些屬性,在攔截器中可以拿到這個標記進行業(yè)務(wù)邏輯判斷
@Route(path = "/test/activity", extras = Consts.XXXX)
7.通過依賴注入解耦:服務(wù)管理
- 暴露服務(wù)
// 聲明接口,其他組件通過接口來調(diào)用服務(wù)
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 實現(xiàn)接口
@Route(path = "/yourservicegroupname/hello", name = "測試服務(wù)")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
- 發(fā)現(xiàn)服務(wù)
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/yourservicegroupname/hello")
HelloService helloService2;
HelloService helloService3;
HelloService helloService4;
public Test() {
ARouter.getInstance().inject(this);
}
public void testService() {
// 1. (推薦)使用依賴注入的方式發(fā)現(xiàn)服務(wù),通過注解標注字段,即可使用,無需主動獲取
// Autowired注解中標注name之后,將會使用byName的方式注入對應(yīng)的字段,不設(shè)置name屬性,會默認使用byType的方式發(fā)現(xiàn)服務(wù)(當(dāng)同一接口有多個實現(xiàn)的時候,必須使用byName的方式發(fā)現(xiàn)服務(wù))
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");
// 2. 使用依賴查找的方式發(fā)現(xiàn)服務(wù),主動去發(fā)現(xiàn)服務(wù)并使用,下面兩種方式分別是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}