一酒甸、ARouter介紹及主要應(yīng)用場(chǎng)景:
1礁叔、介紹:
是ARouter是阿里巴巴開(kāi)源的Android平臺(tái)中對(duì)頁(yè)面、服務(wù)提供路由功能的中間件泼菌,提倡的是簡(jiǎn)單且夠用谍肤。
2、原生的路由方案存在的問(wèn)題
首先談一談原生的路由方案存在的問(wèn)題以及為什么需要路由框架哗伯。我們所使用的原生路由方案一般是通過(guò)顯式intent和隱式intent兩種方式實(shí)現(xiàn)的超升,而在顯式intent的情況下捷沸,因?yàn)闀?huì)存在直接的類(lèi)依賴(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)依賴(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í)方式。
3障贸、為什么要用路由組件
為什么要用路由組件
前面提到的主要是開(kāi)發(fā)與協(xié)作中的問(wèn)題错森,而使用一款路由框架時(shí)還會(huì)涉及到其他的兩個(gè)大方面:一方面是組件化,而另一方面就是Native和H5的問(wèn)題篮洁。剛才所提到的主要是開(kāi)發(fā)和協(xié)作中作為開(kāi)發(fā)者所需要面對(duì)的問(wèn)題涩维,而一旦一款A(yù)PP達(dá)到一定體量的時(shí)候,業(yè)務(wù)就會(huì)膨脹得比較嚴(yán)重袁波,而開(kāi)發(fā)團(tuán)隊(duì)的規(guī)模也會(huì)越來(lái)越大瓦阐,這時(shí)候一般都會(huì)提出組件化的概念。組件化就是將APP按照一定的功能和業(yè)務(wù)拆分成多個(gè)小組件篷牌,不同的組件由不同的開(kāi)發(fā)小組來(lái)負(fù)責(zé)睡蟋,這樣就可以解決大型APP開(kāi)發(fā)過(guò)程中的開(kāi)發(fā)與協(xié)作的問(wèn)題,將這些問(wèn)題分散到小的APP中枷颊。目前而言組件化已經(jīng)有非常多比較成熟的方案了戳杀,而自定義路由框架也可以非常好地解決整個(gè)APP完成組件化之后模塊之間沒(méi)有耦合的問(wèn)題,因?yàn)闆](méi)有耦合時(shí)使用原生的路由方案肯定是不可以的夭苗。
另外一個(gè)問(wèn)題就是Native與H5的問(wèn)題豺瘤,因?yàn)楝F(xiàn)在的APP很少是純Native的,也很少會(huì)有純H5的听诸,一般情況下都是將兩者進(jìn)行結(jié)合。這時(shí)候就需要非常便捷并且統(tǒng)一的跳轉(zhuǎn)方案蚕泽,因?yàn)樵贖5中是無(wú)法使用StartActivity()跳轉(zhuǎn)到Native頁(yè)面的晌梨,而從Native跳轉(zhuǎn)到H5頁(yè)面也只能通過(guò)配置瀏覽器的方式實(shí)現(xiàn)。
4须妻、路由框架的特點(diǎn)
為了解決以上的問(wèn)題就需要實(shí)現(xiàn)一個(gè)自定義的路由框架仔蝌,而路由框架一般都具有以下的三種特點(diǎn):
分發(fā):把一個(gè)URL或者請(qǐng)求按照一定的規(guī)則分配給一個(gè)服務(wù)或者頁(yè)面來(lái)處理,這個(gè)流程就是分發(fā)荒吏,分發(fā)是路由框架最基本的功能敛惊,當(dāng)然也可以理解成為簡(jiǎn)單的跳轉(zhuǎn)。
管理:將組件和頁(yè)面按照一定的規(guī)則管理起來(lái)绰更,在分發(fā)的時(shí)候提供搜索瞧挤、加載锡宋、修改等操作,這部分就是管理特恬,也是路由框架的基礎(chǔ)执俩,上層功能都是建立在管理之上。
控制:就像路由器一樣癌刽,路由的過(guò)程中役首,會(huì)有限速、屏蔽等一些控制操作显拜,路由框架也需要在路由的過(guò)程中衡奥,對(duì)路由操作做一些定制性的擴(kuò)展,比方剛才提到的AOP远荠,后期的功能更新矮固,也是圍繞這個(gè)部分來(lái)做的。
5矮台、ARouter的7個(gè)優(yōu)勢(shì)
優(yōu)勢(shì)一:直接解析URL路由乏屯,解析參數(shù)并賦值到對(duì)應(yīng)目標(biāo)字段的頁(yè)面中。
優(yōu)勢(shì)二:支持多模塊項(xiàng)目瘦赫,因?yàn)楝F(xiàn)在很少有APP是單模塊的項(xiàng)目辰晕,一般都是多模塊單工程的,由不同的團(tuán)隊(duì)負(fù)責(zé)不同的模塊開(kāi)發(fā)确虱,這時(shí)候支持多模塊項(xiàng)目開(kāi)發(fā)就顯得尤為重要含友。
優(yōu)勢(shì)三:支持InstantRun,目前很多路由框架并不支持InstantRun校辩,而InstantRun是Google在AndroidStudio2.0阿爾法版本中提供的新功能窘问,其類(lèi)似于代碼的日更新,其只不過(guò)面向的是開(kāi)發(fā)過(guò)程宜咒,這樣做可以在開(kāi)發(fā)的過(guò)程中減少開(kāi)發(fā)和編譯的次數(shù)惠赫,可以簡(jiǎn)單地將代碼修改即時(shí)地同步到APK中,從而可以大規(guī)模降低開(kāi)發(fā)復(fù)雜度故黑。
優(yōu)勢(shì)四:允許自定義攔截器儿咱,ARouter是支持?jǐn)r截器的,而攔截器其實(shí)就是AOP的實(shí)現(xiàn)场晶,可以自定義多個(gè)攔截器解決一些面向行為編程上出現(xiàn)的問(wèn)題混埠。
優(yōu)勢(shì)五:ARouter可以提供IoC容器,IoC其實(shí)就是控制反轉(zhuǎn)诗轻,這一部分做過(guò)服務(wù)端開(kāi)發(fā)的朋友可能比較了解钳宪,因?yàn)榉?wù)端開(kāi)發(fā)經(jīng)常用到的Spring框架能夠提供的一個(gè)非常重要的能力就是控制反轉(zhuǎn)。
優(yōu)勢(shì)六:映射關(guān)系自動(dòng)注冊(cè),在頁(yè)面不是很多的小型APP上面吏颖,自動(dòng)注冊(cè)并不會(huì)體現(xiàn)出太大優(yōu)勢(shì)搔体,但是對(duì)于大型APP而言,可能頁(yè)面數(shù)量已經(jīng)達(dá)到的幾十個(gè)或者數(shù)百個(gè)侦高,在這樣的情況下嫉柴,自動(dòng)注冊(cè)就顯得非常重要了,因?yàn)椴豢赡軐⒚恳粋€(gè)頁(yè)面都通過(guò)代碼的方式進(jìn)行注冊(cè)奉呛。
優(yōu)勢(shì)七:靈活的降級(jí)策略计螺,ARouter可以提供很多種降級(jí)策略供用戶(hù)自行選擇,而原生的路由方案存在無(wú)法靈活降級(jí)的問(wèn)題瞧壮,StartActivity()一旦失敗將會(huì)拋出運(yùn)營(yíng)級(jí)異常登馒。
6、應(yīng)用場(chǎng)景:
- 從外部URL映射到內(nèi)部頁(yè)面咆槽,以及參數(shù)傳遞與解析
- 跨模塊頁(yè)面跳轉(zhuǎn)陈轿,模塊間解耦
- 攔截跳轉(zhuǎn)過(guò)程,處理登陸秦忿、埋點(diǎn)等邏輯
- 跨模塊API調(diào)用麦射,通過(guò)控制反轉(zhuǎn)來(lái)做組件解耦
GitHub:https://github.com/alibaba/ARouter
二、ARouter的基本使用步驟:
1灯谣、添加依賴(lài)和配置
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用潜秋,均使用最新版可以保證兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
// 替換成最新版本, 需要注意的是api
// 要與compiler匹配使用,均使用最新版可以保證兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
-
這里替換相應(yīng)的Api和compiler
2胎许、添加注解及初始化
- ARouter初始化
if (isDebug()) { // 這兩行必須寫(xiě)在init之前峻呛,否則這些配置在init過(guò)程中將無(wú)效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 開(kāi)啟調(diào)試模式(如果在InstantRun模式下運(yùn)行,必須開(kāi)啟調(diào)試模式辜窑!線上版本需要關(guān)閉,否則有安全風(fēng)險(xiǎn))
}
ARouter.init(mApplication); // 盡可能早钩述,推薦在Application中初始化
- 在需要跳轉(zhuǎn)的Activity上進(jìn)行注解配置
// 在支持路由的頁(yè)面上添加注解(必選)
// 這里的路徑需要注意的是至少需要有兩級(jí),/xx/xx
@Route(path = "/com/ARouter2Activity")
public class ARouter2Activity extends AppCompatActivity {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_arouter2);
//....
}
- 應(yīng)用中的簡(jiǎn)單跳轉(zhuǎn)
ARouter.getInstance().build("/com/ARouter2Activity").navigation();
- 應(yīng)用中攜帶參數(shù)的跳轉(zhuǎn)
ARouter.getInstance().build("/com/ARouter2Activity")
.withString("name","onex")
.withInt("age",22)
.navigation();
- 在目標(biāo)Activity中接受相關(guān)的參數(shù)
為每一個(gè)參數(shù)聲明一個(gè)字段穆碎,并使用 @Autowired 標(biāo)注
URL中不能傳遞Parcelable類(lèi)型數(shù)據(jù)牙勘,通過(guò)ARouter api可以傳遞
Parcelable對(duì)象
@Route(path = "/com/ARouter2Activity")
public class ARouter2Activityextends Activity {
@Autowired
public String name;
@Autowired
int age;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter會(huì)自動(dòng)對(duì)字段進(jìn)行賦值,無(wú)需主動(dòng)獲取
Log.d("param", name + age + boy);
}
}
這里需要注意的是
ARouter.getInstance().inject(this);
三所禀、進(jìn)階使用
1方面、Activity之間傳遞自定義對(duì)象
- 如果需要傳遞自定義對(duì)象,需要實(shí)現(xiàn) SerializationService,并使用@Route注解標(biāo)注(方便用戶(hù)自行選擇序列化方式)
@Route(path = "/service/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);
}
}
- 以下是ARouter支持的傳遞的數(shù)據(jù)類(lèi)型
TestParcelable testParcelable = new TestParcelable("jack", 666);
TestObj testObj = new TestObj("Rose", 777);
List<TestObj> objList = new ArrayList<>();
objList.add(testObj);
Map<String, List<TestObj>> map = new HashMap<>();
map.put("testMap", objList);
ARouter.getInstance().build("/test/activity1")
.withString("name", "老王")
.withInt("age", 18)
.withBoolean("boy", true)
.withLong("high", 180)
.withString("url", "https://a.b.c")
.withParcelable("pac", testParcelable)
.withObject("obj", testObj)
.withObject("objList", objList)
.withObject("map", map)
.navigation();
需要注意的是:我們?cè)趥鬟f的時(shí)候使用的是什么樣的KEY,在目標(biāo)Activity的地方使用相同的KEY,進(jìn)行聲明變量例如:
Man man = new Man("OneX","男");
ARouter.getInstance().build("/com/ARouter2Activity")
.withObject("obj",man)
.navigation();
目標(biāo)頁(yè)面Code
@Autowired
Man obj;
2北秽、通過(guò)Url進(jìn)行跳轉(zhuǎn)
- 新建一個(gè)Activity用于監(jiān)聽(tīng)Schame事件,之后直接把url傳遞給ARouter即可(類(lèi)似于轉(zhuǎn)發(fā)路由的Activity)
public class SchameFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
在AndroidManifest.xml中進(jìn)行如下配置
<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>
3、設(shè)置攔截器
- 比較經(jīng)典的應(yīng)用就是在跳轉(zhuǎn)過(guò)程中處理登陸事件最筒,這樣就不需要在目標(biāo)頁(yè)重復(fù)做登陸檢攔截器會(huì)在跳轉(zhuǎn)之間執(zhí)行
- 多個(gè)攔截器會(huì)按優(yōu)先級(jí)順序依次執(zhí)行
@Interceptor(priority = 8, name = "測(cè)試用攔截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
callback.onContinue(postcard); // 處理完成贺氓,交還控制權(quán)
// callback.onInterrupt(new RuntimeException("我覺(jué)得有點(diǎn)異常")); // 覺(jué)得有問(wèn)題,中斷路由流程
// 以上兩種至少需要調(diào)用其中一種,否則不會(huì)繼續(xù)路由
}
@Override
public void init(Context context) {
// 攔截器的初始化辙培,會(huì)在sdk初始化的時(shí)候調(diào)用該方法蔑水,僅會(huì)調(diào)用一次
}
}
4、監(jiān)聽(tīng)路由跳轉(zhuǎn)結(jié)果
// 使用兩個(gè)參數(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) {
...
}
});
5搀别、獲取Fragment的對(duì)象
// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
6、通過(guò)依賴(lài)注入解耦:服務(wù)管理尾抑,暴露服務(wù)
// 聲明接口,其他組件通過(guò)接口來(lái)調(diào)用服務(wù)
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 實(shí)現(xiàn)接口
@Route(path = "/service/hello", name = "測(cè)試服務(wù)")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
在Class中的相關(guān)使用
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/service/hello")
HelloService helloService2;
HelloService helloService3;
HelloService helloService4;
public Test() {
ARouter.getInstance().inject(this);
}
public void testService() {
// 1. (推薦)使用依賴(lài)注入的方式發(fā)現(xiàn)服務(wù),通過(guò)注解標(biāo)注字段,即可使用歇父,無(wú)需主動(dòng)獲取
// Autowired注解中標(biāo)注name之后,將會(huì)使用byName的方式注入對(duì)應(yīng)的字段再愈,不設(shè)置name屬性榜苫,會(huì)默認(rèn)使用byType的方式發(fā)現(xiàn)服務(wù)(當(dāng)同一接口有多個(gè)實(shí)現(xiàn)的時(shí)候,必須使用byName的方式發(fā)現(xiàn)服務(wù))
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");
// 2. 使用依賴(lài)查找的方式發(fā)現(xiàn)服務(wù)翎冲,主動(dòng)去發(fā)現(xiàn)服務(wù)并使用垂睬,下面兩種方式分別是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}
四、注意事項(xiàng)及混淆配置
1抗悍、初始化中的其他配置
ARouter.openLog(); // 開(kāi)啟日志
ARouter.openDebug(); // 使用InstantRun的時(shí)候驹饺,需要打開(kāi)該開(kāi)關(guān),上線之后關(guān)閉缴渊,否則有安全風(fēng)險(xiǎn)
ARouter.printStackTrace(); // 打印日志的時(shí)候打印線程堆棧
2赏壹、詳細(xì)API:
// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求
ARouter.getInstance().build("/home/main").navigation();
// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求,并指定分組
ARouter.getInstance().build("/home/main", "ap").navigation();
// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求疟暖,通過(guò)Uri直接解析
Uri uri;
ARouter.getInstance().build(uri).navigation();
// 構(gòu)建標(biāo)準(zhǔn)的路由請(qǐng)求卡儒,startActivityForResult
// navigation的第一個(gè)參數(shù)必須是Activity,第二個(gè)參數(shù)則是RequestCode
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
// 直接傳遞Bundle
Bundle params = new Bundle();
ARouter.getInstance()
.build("/home/main")
.with(params)
.navigation();
// 指定Flag
ARouter.getInstance()
.build("/home/main")
.withFlags();
.navigation();
// 獲取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
// 對(duì)象傳遞
ARouter.getInstance()
.withObject("key", new TestObj("Jack", "Rose"))
.navigation();
// 覺(jué)得接口不夠多俐巴,可以直接拿出Bundle賦值
ARouter.getInstance()
.build("/home/main")
.getExtra();
// 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(常規(guī)方式)
ARouter.getInstance()
.build("/test/activity2")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
// 轉(zhuǎn)場(chǎng)動(dòng)畫(huà)(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
// ps. makeSceneTransitionAnimation 使用共享元素的時(shí)候骨望,需要在navigation方法中傳入當(dāng)前Activity
ARouter.getInstance()
.build("/test/activity2")
.withOptionsCompat(compat)
.navigation();
// 使用綠色通道(跳過(guò)所有的攔截器)
ARouter.getInstance().build("/home/main").greenChannel().navigation();
// 使用自己的日志工具打印日志
ARouter.setLogger();
3、路由中的分組概念
- SDK中針對(duì)所有的路徑(/test/1 /test/2)進(jìn)行分組欣舵,分組只有在分組中的某一個(gè)路徑第一次被訪問(wèn)的時(shí)候擎鸠,該分組才會(huì)被初始化
- 通過(guò) @Route 注解主動(dòng)指定分組,否則使用路徑中第一段字符串(/*/)作為分組
- 注意:一旦主動(dòng)指定分組之后缘圈,應(yīng)用內(nèi)路由需要使用 ARouter.getInstance().build(path, group) 進(jìn)行跳轉(zhuǎn)劣光,手動(dòng)指定分組,否則無(wú)法找到
@Route(path = "/test/1", group = "app")
4糟把、添加混淆規(guī)則(如果使用了Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式獲取 Service绢涡,需添加下面規(guī)則,保護(hù)接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 單類(lèi)注入遣疯,即不定義接口實(shí)現(xiàn) IProvider雄可,需添加下面規(guī)則,保護(hù)實(shí)現(xiàn)
-keep class * implements com.alibaba.android.arouter.facade.template.IProvider
本文以上部分內(nèi)容參考:https://yq.aliyun.com/articles/71687?spm=5176.100240.searchblog.7.5qkuq2