ARouter的使用
官網github:
https://github.com/alibaba/ARouter/blob/master/README_CN.md
這裏對其做了分類整理,以及一些想法,便於更方便理解。
跳轉
跳轉Activity
1、path跳轉
path的規則:/group/child…至少兩個“/”;和Activity的@Route註解值匹配
// 構建標準的路由請求
ARouter.getInstance().build("/home/main").navigation();
// 構建標準的路由請求,並指定分組
ARouter.getInstance().build("/home/main", "ap").navigation();
2、uri跳轉
Uri uri;
ARouter.getInstance().build(uri).navigation();
3、startActivityForResult
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
navigation的第一個參數***必須是Activity***,第二個參數則是RequestCode,因爲不指定activity,無法得知應該從哪個任務棧啓動,導致收不到result。
總結:
跳轉activity的時候,但凡涉及activity的任務棧,必須使用navigation(context)並且context只能是activity。建議,如果當前可以獲取activity,最好傳了。
跳轉Fragment
使用方式同Activity,navigation()方法會返回要跳到的對象實例,跳轉Fragment可以拿到Fragment實例操作。
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
傳值
ARouter提供了豐富的傳值方式,基本滿足使用。
// 直接傳遞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();
// 對象傳遞
ARouter.getInstance()
.withObject("key", new TestObj("Jack", "Rose"))
.navigation();
// 覺得接口不夠多,可以直接拿出Bundle賦值
ARouter.getInstance()
.build("/home/main")
.getExtra();
還提供了int、long、short等基本變量和String、Object、Serializable、Parcelable和對應的數組、List。種類相當豐富。
帶動畫的跳轉
// 轉場動畫(常規方式)
ARouter.getInstance()
.build("/test/activity2")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
// 轉場動畫(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
ARouter.getInstance()
.build("/test/activity2")
.withOptionsCompat(compat)
.navigation();
注意 ,makeSceneTransitionAnimation 使用共享元素的時候,需要在navigation方法中傳入當前Activity。
more
我們經常需要在目標頁面中配置一些屬性,比方說"是否需要登陸"之類的
可以通過 Route 註解中的 extras 屬性進行擴展,這個屬性是一個 int值,換句話說,單個int有4字節,也就是32位,可以配置32個開關 剩下的可以自行發揮,通過字節操作可以標識32個開關,通過開關標記目標頁面的一些屬性,在攔截器中可以拿到這個標記進行業務邏輯判斷
@Route(path = “/test/activity”, extras = Consts.XXXX)
服務
服務是ARouter中很重要的一個概念,ARouter的所有功能都是服務,本質上是接口IProvider的實現類,它也是組件化之間通信的橋樑:
public interface IProvider {
/**
* Do your init work in this method, it well be call when processor has been load.
*
* @param context ctx
*/
void init(Context context);
}
後面的攔截器、重定向、降級、di注入都是服務。只需要實現對應接口,並添加註解,就能自動將服務註冊到框架。
攔截器
攔截器可以控制是不是可以繼續跳轉,以及跳轉到哪裏。
public interface IInterceptor extends IProvider {
/**
* The operation of this interceptor.
*
* @param postcard meta
* @param callback cb
*/
void process(Postcard postcard, InterceptorCallback callback);
}
如果可以繼續,調用callback.continue(postcard), postcard是跳轉的信息類,包含了uri、path、傳遞的數據等,可以在這裏對其做處理;如果不繼續,可以調用callback.interrept()停止跳轉。
注意:
1、使用綠色通道可以跳過所有攔截器:
ARouter.getInstance().build("/home/main").greenChannel().navigation();
2、服務本身不會被攔截;
3、fragment 的navigation()不會被攔截
重定向
重定向是在跳轉開始前發生的“準備工作”
public interface PathReplaceService extends IProvider {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path);
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri);
}
提供了對path和Uri兩種重定向。入參爲當前路徑,出參爲重定向後的路徑。
降級
降級是一個成熟框架應有的優秀設計。
當跳轉失敗(路由不正確、版本未升級等),會回掉給onLost方法,可以在這時給出提示或者轉到備用方案。
public interface DegradeService extends IProvider {
/**
* Router has lost.
*
* @param postcard meta
*/
void onLost(Context context, Postcard postcard);
}
di自動注入
前面說到ARouter提供了強大豐富的跳轉傳參,與其匹配的,就是怎麼接參數。
public interface AutowiredService extends IProvider {
/**
* Autowired core.
* @param instance the instance who need autowired.
*/
void autowire(Object instance);
}
這個服務框架幫我們實現好了,只需要在跳轉的對象裏使用Autowired註解,並且調用了
ARouter.getInstance().inject(this);
就可以獲取到對應的值。示例:
/**
* 身份證名字
*/
@Autowired(name = Extras.ID_CARD_NAME,required = true)
String name ;
required = true 表示必傳。
自定義服務
除了框架定義的服務,我們可以自己定義服務,這是組件化中最重要的一環,通過定義服務,註冊到ARouter,實現Client-Router-Client隔離。
可以對着上述的幾個服務依樣畫葫蘆:
服務提供方Client1:
// 聲明接口,其他組件通過接口來調用服務
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 實現接口
@Route(path = "/service/hello", name = "測試服務")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
然後服務使用方Client2:
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. (推薦)使用依賴注入的方式發現服務,通過註解標註字段,即可使用,無需主動獲取
// Autowired註解中標註name之後,將會使用byName的方式注入對應的字段,不設置name屬性,會默認使用byType的方式發現服務(當同一接口有多個實現的時候,必須使用byName的方式發現服務)
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");
// 2. 使用依賴查找的方式發現服務,主動去發現服務並使用,下面兩種方式分別是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/service/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}
這樣就可以完成調用。從上面的示例可以看出,服務本身就支持AOP。
注意:這種調用方式,決定了自定義的服務必須要在基礎架構的包裏,Client1和Client2都依賴這個基礎架構包,不然Client2找不到這個類;
applink
從外部喚起app有兩種場景,applink和推送,這時候沒有辦法直接使用ARouter,依然需要一箇中間Activity來做中轉,在中轉Activity中再使用ARouter跳轉:
<activity
android:name=".RouterActivity"
android:exported="true"
android:screenOrientation="portrait"
android:theme="@android:style/Theme.Translucent.NoTitleBar">
<!-- App Links -->
<intent-filter android:autoVerify="true">
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.DEFAULT" />
<category android:name="android.intent.category.BROWSABLE" />
<data
android:host="yourhost"
android:scheme="yourscheme" />
</intent-filter>
</activity>
點擊帶有scheme/host 的url,會喚起RouterActivity:
public class RouterManagerActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
finish();
}
}
踩坑
1、startActivityForResult 跳轉,一定要帶上當前的activity;
2、withFlag 時,如果是clearTop的flag,也要帶當前activity;
3、使用Arouter接受參數,要用autowired,也要inject(this),不然getIntent拿不到數據;
4、path必須至少有兩個"/";
5、攔截器、重定向器、降級服務的執行順序是:起點-》重定向器-》降級服務-》攔截器-》終點;如果降級onLost了,攔截器不會生效。
6、1.2.4版本build的時候,強制要求uri有path,就是說,像https://www.baidu.com 這樣的uri會直接報錯
7、降級發生後,debug下ARouter會默認Toast,但是這個我們可能並不想要。可以在初始化時不開啓debugable。
8、如果在navigation 方法中給了callback,降級service會被中斷(個人覺得實在不合理。。。一個service級別的東西被一個callback 給中斷了)。
思考
ARouter應該怎麼用?從上面的分析看,ARouter功能兩個大板塊,一個是跳轉,一個是服務。那麼使用也該分兩個方向:
1、跳轉
使用路由跳轉屏蔽了具體要跳到的XXXActivity.class,用字符串(path)做映射,有兩個好處,第一個是多目錄包下不需要依賴XXXActivity.class,方便拆成多個aar或者apk;第二個是可以動態切換跳轉的目的地,只要build的path由服務端下發。
但是是不是每個Activity都要註冊在ARouter呢?其實不需要,對於每個組件來說,前一個頁面和後一個頁面基本是確定的,並且耦合了參數,其實只要控制好組件的入口activity就行了,組件內部怎麼跳轉並不重要,因爲他們的關係是固定的。
2、服務
除了框架提供的,自己自定義的服務可以用來做組件間通信。
假設Client1、Client2、ARouter是三個獨立的包,那麼Provider放在ARouter所在的包裏,Client1和Client2依賴ARouter,那麼這種可以直接拿到服務實例,向上文自定義服務寫的例子。但是這種例子的問題是,需要把這個服務的類,放到ARouter的包裏,否則Client2拿不到實例。
使用ARouter實現全路由分發
背景:許多跳轉路由都是服務下發的,客戶端透轉給路由,交由路由跳轉,但是服務端下發的,可能是本地在ARouter中註冊的path,也可能是一個h5鏈接,希望用h5打開。原來的方案,是Arouter處理本地路由,對於h5,再單獨處理,這樣,如果一個接口後端原來給的是本地path,後來希望指向另一個h5頁面,就沒辦法動態切換;反之亦然。
解決方案:
1、升級ARouter(我本地用的1.3.1),升級後的版本,可以用重定向,在url後面追加固定的path如“/DEAULT/PATH”,繞開build時必須要有path,這樣就可以處理類似“https://www.baidu.com” 這樣的鏈接;示例:
@Route(path = "/router/MyPathReplaceService") // 必須標明註解
public class MyPathReplaceService implements PathReplaceService {
public static final String DEFAULT_PATH = "/DEFAULT/PATH";
@Override
public String forString(String path) {
}
@Override
public Uri forUri(Uri uri) {
// 空path的uri 添加默認path
if (TextUtils.isEmpty(uri.getPath())) {
uri = uri.buildUpon().appendEncodedPath(DEFAULT_PATH).build();
}
return uri;
}
@Override
public void init(Context context) {
}
}
2、利用降級服務,示例如下:
@Route(path = “/router/MyDegradeService”) // 必須標明註解
public class MyDegradeService implements IProvider{
public void onLost(Context context, Postcard postcard) {
//判斷是否是想跳轉本地,如果是,走降級邏輯;不是,認爲是跳轉到h5.
if (isJumpToNative(postcard)) {
Toasts.shortToast(“升級版本!”);
} else {
Uri targetUri = postcard.getUri();
//如果有默認path,恢復
if (MyPathReplaceService.DEFAULT_PATH.equals(postcard.getUri().getPath())) {
targetUri = targetUri.buildUpon().path("").build();
}
jumpToWebView(targetUri);
}
}
}
說明:
1、爲什麼不用攔截器?因爲攔截器的執行順序是在降級之後,並且很多情況攔截器不起效(上文有說,個人認爲攔截器的定位沒有設計好)
2、跳轉h5會有toast彈出來,這個是ARouter走到降級後自己在debug環境下彈出的(如果想用降級搞點事情,這個Toast有些多餘)
3、判斷是否是想跳本地,可以通過path判斷,因爲本地路由,都是/group/child或者nativeshceme/group/child這種格式。
4、由於ARouter自己降級服務會被callback中斷,所以可以不實現ARouter自己的降級服務,改爲設置默認callback代理:
...
degradeService = ARouter.getInstance().navigation(MyDegradeService::class.java)
...
private var defaultCallBack: NavigationCallback = object : NavigationCallback {
var callback : NavigationCallback?=null
override fun onLost(postcard: Postcard?) {
degradeService?.onLost(postcard)
callback?.onLost(postcard)
}
override fun onFound(postcard: Postcard?) {
callback?.onLost(postcard)
}
override fun onInterrupt(postcard: Postcard?) {
callback?.onInterrupt(postcard)
}
override fun onArrival(postcard: Postcard?) {
callback?.onArrival(postcard)
}
}
....
fun navigation(context: Context?): Any? {
return ARouter.getInstance().navigation(context, postcard, -1, defaultCallBack)
}
...
fun navigation(context: Context?,callback :NavigationCallback?): Any? {
defaultCallBack.callback = callbak
return ARouter.getInstance().navigation(context, postcard, -1, defaultCallBack)
}