基於Arouter的路由實現方案

這本是一篇應該寫在去年的文章,但僅僅由於...懶,這篇文章在草稿箱裏靜靜的躺了一年多,被無限期推遲到了如今。最近恰好完成了公司項目的路由改造,藉此機會來對這篇文章作一個告終。java

1、爲何要在項目中引入路由?

在開始以前咱們先來思考一下這個問題。爲何要在項目中引入路由?相信你們的答案可能會有所不一樣,可是應該也不外乎如下幾點:android

1.爲了實現項目組件化

想必不少開發者引入路由的目的都是由於要實現項目組件化。咱們知道,組件化的項目各個業務模塊之間沒有相互的依賴關係。不一樣業務模塊之間的通訊最好的解決方案就是支持頁面路由。git

2.方便APP內部跳轉

可能有些小夥伴會有疑問,App內部直接經過Intent跳轉不是很好嗎,爲何要畫蛇添足引入路由呢?固然,一般狀況下經過Intent跳轉也無傷大雅。可是在某些狀況下,好比像下圖這樣的一個頁面: github

在這裏插入圖片描述
這是一個典型的多Type的RecyclerView頁面,這個頁面中全部的數據都是從服務器獲取的,在引入路由以前全部的點擊跳轉事件都須要後臺給咱們一個type,咱們根據type判斷須要向哪個Activity跳轉,而且須要經過Intent攜帶目的頁面所須要的參數。顯然這樣寫會使咱們代碼變得很是臃腫,代碼之間的耦合度也很是高。然而在引入路由以後一切都變得不同了。咱們只須要後臺返回目的頁面所對應的URL,並在URL上拼接頁面跳轉所須要的參數,此時前臺只須要拿到URL,而後經過路由便可到達對應的頁面。這樣以來使咱們的代碼變得簡潔明瞭,而且保證了代碼的低耦合。

3.方便APP外部跳轉

一般能夠看到不少應用支持從瀏覽器喚醒App並跳轉到對應的頁面。作到比較好的如知乎,體驗過知乎的小夥伴應該知道,知乎能夠從瀏覽器喚醒App而且直接在App中打開當前在瀏覽器中瀏覽的內容。咱們知道,從外部喚起App須要給Activity添加Schema。而若是App內部有許多Activity須要支持外部喚起,咱們不可能爲這些Activity都添加Schema。那麼此時咱們就能夠單獨設置一個支持Schema的Activity,瀏覽器能夠經過Schema喚起這個Activity。而在這個Activity中會接收瀏覽器傳過來的URL,而後根據URL進行路由分發,經過URL路由到對應的頁面便可。json

二 、ARouter的使用

其實很不想在這篇文章中長篇大論如何使用ARouter,由於ARouter的官方文檔上已經很是詳細的告訴了開發者如何去使用,只要仔細的閱讀ARouter的文檔基本上絕大部分問題均可以獲得解決。可是爲了照顧沒有使用過ARouter的小夥伴,這裏仍是再囉嗦一下。若是你對ARouter的使用已經很是熟悉了那麼你能夠忽略此章節,直接到下一章了。小程序

1.添加依賴和配置

android {
    defaultConfig {
        ...
        javaCompileOptions {
            annotationProcessorOptions {
                arguments = [AROUTER_MODULE_NAME: project.getName()]
            }
        }
    }
}

dependencies {
    // 替換成最新版本, 須要注意的是api
    // 要與compiler匹配使用,均使用最新版能夠保證兼容
    implementation 'com.alibaba:arouter-api:x.x.x'
    annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
    ...
}
複製代碼

這裏須要注意,若是你的項目有多個業務模塊,那麼每一個模塊都須要在gradle中添加以上配置。api

2.初始化SDK

if (isDebug()) {           // 這兩行必須寫在init以前,不然這些配置在init過程當中將無效
    ARouter.openLog();     // 打印日誌
    ARouter.openDebug();   // 開啓調試模式(若是在InstantRun模式下運行,必須開啓調試模式!線上版本須要關閉,不然有安全風險)
}
ARouter.init(mApplication); // 儘量早,推薦在Application中初始化
複製代碼

3.添加註解

// 在支持路由的頁面上添加註解(必選)
// 這裏的路徑須要注意的是至少須要有兩級,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
    ...
}
複製代碼

4.發起路由操做

// 1. 應用內簡單的跳轉(經過URL跳轉在'進階用法'中)
ARouter.getInstance().build("/test/activity").navigation();

// 2. 跳轉並攜帶參數
ARouter.getInstance().build("/test/1")
            .withLong("key1", 666L)
            .withString("key3", "888")
            .withObject("key4", new Test("Jack", "Rose"))
            .navigation();
複製代碼

不少狀況下須要經過URL跳轉,ARouter支持直接經過URL跳轉:瀏覽器

Uri uri= Uri.parse(url);
 ARouter.getInstance().build(uri).navigation();
複製代碼

5.路由解析參數

// 爲每個參數聲明一個字段,並使用 @Autowired 標註
// URL中不能傳遞Parcelable類型數據,經過ARouter api能夠傳遞Parcelable對象
@Route(path = "/test/activity")
public class Test1Activity extends Activity {
    @Autowired
    public String name;
    @Autowired
    int age;
    
    // 經過name來映射URL中的不一樣參數
    @Autowired(name = "girl") 
    boolean boy;
    
    // 支持解析自定義對象,URL中使用json傳遞
    @Autowired
    TestObj obj;      
    
    // 使用 withObject 傳遞 List 和 Map 的實現了
    // Serializable 接口的實現類(ArrayList/HashMap)
    // 的時候,接收該對象的地方不能標註具體的實現類類型
    // 應僅標註爲 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);

    // ARouter會自動對字段進行賦值,無需主動獲取
    Log.d("param", name + age + boy);
    }
}


// 若是須要傳遞自定義對象,新建一個類(並不是自定義對象類),而後實現 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);
    }
}
複製代碼

除了使用@Autowired註解注入參數外,還能夠與普通頁面跳轉同樣經過getIntent()獲取參數。安全

以上就是ARouter的一些基本用法,瞭解這些基本用法以後並不等於已經掌握了ARouter。由於當你實際用到項目中的時候可能會面臨諸多問題。bash

三 、ARouter的採坑之路

若是你只是簡單的寫一個ARouter使用的Demo,那麼可能上一章的內容已經足夠了。可是當你在項目中引入ARouter後各類各樣的問題便會接踵而至。

1.使用ARouter實現登陸攔截

這是在項目中引入ARouter後面臨的第一個問題。一般狀況下,大部分App不登陸即可以進入主頁面,在跳轉須要用戶權限的頁面時會首先跳轉到登陸頁面引導用戶登陸。我相信大部分的開發在最初時候都寫過相似這樣的代碼:

if (isLogin) {
     goToDestination();
  } else {
     goToLogin();
  }
複製代碼

在每次跳轉頁面的時候都須要進行是否登陸的判斷,這樣的代碼顯然有很大的弊端。而ARouter爲咱們提供了面向切面的登陸攔截功能,ARouter的文檔上給了咱們一個例子:

// 比較經典的應用就是在跳轉過程當中處理登錄事件,這樣就不須要在目標頁重複作登錄檢查
// 攔截器會在跳轉之間執行,多個攔截器會按優先級順序依次執行
@Interceptor(priority = 8, name = "測試用攔截器")
public class TestInterceptor implements IInterceptor {
    @Override
    public void process(Postcard postcard, InterceptorCallback callback) {
    ...
    callback.onContinue(postcard);  // 處理完成,交還控制權
    // callback.onInterrupt(new RuntimeException("我以爲有點異常"));      // 以爲有問題,中斷路由流程

    // 以上兩種至少須要調用其中一種,不然不會繼續路由
    }

    @Override
    public void init(Context context) {
    // 攔截器的初始化,會在sdk初始化的時候調用該方法,僅會調用一次
    }
}
複製代碼

若是你按着官方文檔上這樣寫,那麼你大機率會碰到不少問題。列舉以下: 如何處理有些頁面須要登陸攔截,有些頁面不須要登陸攔截? 若是你添加了攔截器,那麼在每次路由跳轉時都會優先走到攔截器中,在攔截器的process()方法中你能夠經過判斷當前是否登陸來決定是否繼續該路由操做,若是已經登陸,那麼直接經過 callback.onContinue(postcard)繼續當前路由,而若是沒有登陸,那麼就將目的頁面修改成登陸頁。可是,不要忘了,添加攔截器後全部的路由操做都會優先走到這裏,而咱們的需求是隻有須要用戶權限的時候才須要跳轉到登陸頁,不然即便沒有登陸依然能夠跳轉到目的頁。此時咱們應該怎麼辦? 若是你仔細的看了ARouter的開發文檔,你可能注意到在@Route的註解有一個int類型的extras參數。如此咱們即可以經過這個參數來對Activity進行標記是否須要登陸:

@Route(path = PATH_TEST, extras = IGNORE_LOGIN)
public class TestActivity extends BaseTitleCompatActivity {}
複製代碼

接下來,在攔截器中能夠拿到extras參數,以此來肯定該頁面是否須要登陸:

if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) {  //  已經登陸或者不須要攔截的狀況
	 //  繼續當前路由
      callback.onContinue(postcard);
 } else {	// 未登陸且須要登陸的狀況
    //	路由到登陸頁面
    ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).navigation();
    ...
  }
複製代碼

到這裏這個問題解決了,可是當你興致勃勃的運行起來App,在未登陸的狀況下點擊跳轉到須要用戶權限的頁面,你憧憬着跳轉頁面會被攔截到登陸頁,可是你又被無情的事實打臉了。居然頁面毫無反應?因而你斷點、打Log發現ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).navigation()這句代碼確實執行了,可是爲何沒有跳轉到登陸頁?因而你苦思冥想,忽然靈光一閃,哇!是由於這一句路由也會走到了攔截器裏,如此豈不成了一個死循環。因而你Google如何解決,發現原來須要調用greenChannel()來避免出現死循環。因而有了以下代碼:

if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) {  //  已經登陸或者不須要攔截的狀況
	 //  繼續當前路由
      callback.onContinue(postcard);
 } else {	// 未登陸且須要登陸的狀況
    //	路由到登陸頁面
   ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).greenChannel().navigation();
    ...
  }
複製代碼

修改以後你懷着和剛纔同樣的心情興致勃勃的運行起來App,心想,此次必定沒問題。好!點擊按鈕....居然成功跳轉到了登陸頁面。因而你興奮起來,瘋狂的點擊這些頁面,發現都沒問題。但是...當你點了幾回以後忽然發現,頁面跳轉無效了!!你簡直不敢相信本身的眼睛,剛纔明明是好好的...因而你在此陷入了沉思。 好吧,此次直接公佈答案了,那是由於你須要將原來的路由打斷,而之因此前幾回有效大概猜想是由於greenChannel()去開啓了多個channel,而ARouter的channel是有限的,所以在點擊幾回以後路由再次失效了。因而修改後代碼以下:

if(UserInfoTools.isLogin() || IGNORE_LOGIN == postcard.getExtra()) {  //  已經登陸或者不須要攔截的狀況
	 //  繼續當前路由
      callback.onContinue(postcard);
 } else {	// 未登陸且須要登陸的狀況
    //	路由到登陸頁面
   ARouter.getInstance().build(RoutingTable.PATH_GUEST_LOGIN).greenChannel().navigation();
   callback.onInterrupt(null);
  }
複製代碼

關於登陸攔截看似簡單,實則使用時候居然會碰到這麼多問題!相信第一次使用時都會被虐的掉眼淚。

2.處理一個Activity對應多個路徑的狀況

在某些狀況可能出現一個頁面對應多個路徑的狀況。出現這種狀況的緣由多是前期路由沒有規劃好,致使後邊版本的路由路徑作了修改。從而出現了一個Activity對應多個頁面的狀況。爲了兼容舊版路由,咱們不得不處理這種狀況。可是,Route的註解中path是惟一的,並不能經過@Route註解解決一個Activity對應多個路徑的狀況。此時就須要用到ARouter的重寫URL的功能。只須要實現PathReplaceService 接口,在重寫的方法中對URI或者Path進行替換便可,注意,這個類必定要加@Route註解。代碼參考以下:

@Route(path = "/lost/service")
public class ARouterLostReplaceService implements PathReplaceService {
    @Override
    public String forString(String path) {	//	對於path處理與uri相似
        return path;
    }

    @Override
    public Uri forUri(Uri uri) {	
        String path = uri.getPath();
        if(PATH_LOST1.equals(path)) {
            uri = replaceUriPath(uri, PATH_REAL1);
        } else if(PATH_LOST2.equals(path)) {
            uri = replaceUriPath(uri, PATH_REAL2);
        }
        return uri;
    }

    @Override
    public void init(Context context) {

    }

    /**
     * 替換URI中的path
     * 
     * @param uri 被替換的uri
     * @param path 要替換的path
     * @return 替換後的uri
     */
    private Uri replaceUriPath(Uri uri, String path) {
        StringBuilder resultUrl = new StringBuilder(uri.getScheme() + "://" + uri.getHost() + path);
        String[] split = uri.toString().split("\\?");
        if(split.length >= 2) {
            resultUrl.append("?").append(split[1]);
        }
        return Uri.parse(resultUrl.toString());
    }
}
複製代碼

3.ARouter全局降級策略

在路由跳轉時可能會出現找不到Path對應頁面的狀況,對於這種狀況能夠經過實現DegradeService 接口來處理,一樣這個類也必需要添加@Route註解。這樣當路由跳轉時找不到路徑就會走到這個類的onLost方法中,此時就能夠在這個方法中來作相應的處理了。

// 實現DegradeService接口,並加上一個Path內容任意的註解便可
@Route(path = "/lost/path")
public class DegradeServiceImpl implements DegradeService {
	@Override
	public void onLost(Context context, Postcard postcard) {
	    //  能夠在此處統一處理,好比跳轉到首頁
	}

	@Override
	public void init(Context context) {

	}
}
複製代碼

4、經過瀏覽器跳轉到App對應頁面

1.Schema協議

不少人對於Schema協議比較陌生,可是若是說URL你們必定都很是熟悉。其實URL就是一種Schema協議。Schema協議一般由四部分組成:

[scheme]://[host]/[path]?[query]
 scheme:表示協議名稱
 host:Schema所做用的地址域
 path:Schema指定的路徑
 query:攜帶的參數
複製代碼

拿百度搜索的URL來舉例子:www.baidu.com/s?wd=要搜索的關鍵…

schema::https
host: www.baidu.com
path: /s
query:wd=要搜索的關鍵字
複製代碼

瞭解了Schema協議後,其實咱們徹底能夠按照Schema協議的格式來自定義一個Schema連接,以下:

myApp://www.myApp.com/main/home?id=1
咱們本身定義的Schema連接的對應關係爲:
schema::myApp
host:www.myApp.com
path:/main/home
query:id=1
複製代碼

2.經過Schema連接打開Activity

經過瀏覽器打開App其實就是經過Schema連接來實現的。咱們就以上一節中自定義的Schema連接爲例來實現瀏覽器打開App。首先在項目中添加一個RouterActivity,RouterActivity在AndroidManifest中的配置以下:

<activity
      android:name=".activity.RouterActivity"
      android:configChanges="orientation|keyboardHidden|screenSize"
      android:screenOrientation="portrait">
      <intent-filter>
          <action android:name="android.intent.action.VIEW" />
          <category android:name="android.intent.category.DEFAULT" />
           <category android:name="android.intent.category.BROWSABLE" />
           <data android:scheme="myApp" />
      </intent-filter>
</activity>
複製代碼

咱們在AndroidManifest中爲RouterActivity添加了schema,此時在HTML中寫入如下代碼:

<a href="myApp://www.myApp.com/main/home?id=1">打開APP</a>
複製代碼

經過點擊HTML頁面的"打開App"即可啓動RouterActivity。而且RouterActivity啓動後能夠經過Intent獲取到啓動的URI。代碼以下:

#RouterActivity
  
	@Override
    protected void onCreate(Bundle data) {
        super.onCreate(data);
        Uri launchUri = getIntent().getData();
        dispatchRouterUri(launchUri);
    }
複製代碼

至此,咱們已經能夠經過App來打開項目的RouterActivity。

3.經過路由跳轉到目的頁面

上一節中咱們經過HTML打開了RouterActivity,並在RouterActivity中拿到了跳轉的URI,那麼接下來咱們即可以根據URI的信息打開對應的頁面了。可是在開啓路由跳轉以前爲了保險起見須要對URI進行一些校驗。詳細代碼以下:

private void dispatchRouterUri(Uri launchUri) {
        if(RoutingTable.isValidRouterUri(launchUri)) {  //	判斷是不是合法的URI,這裏只有URI攜帶了Path纔算合法
            if(App.isRootActivityLaunched()) { // app已啓動
                if(RoutingTable.isWxUri(launchUri)) { //	若是是微信的URI那麼目的地是要跳轉到小程序的(此處爲項目中的需求)
                    RoutingTable.openMiniProgram(this, launchUri);
                    finish();
                    return;
                }
                //	經過ARouter路由到目的頁面
               ARouter.getInstance().build(launchUri).navigation();
            } else {  // app未啓動, 保存router uri, 幷嘗試啓動app
               SharedPreferUtil.put(Constants.ROUTER_URI, launchUri.toString());
               launchApp();
            }
        } else {  //	 走到此處多是由於URI沒有攜帶Path,即並不是要跳轉目的頁面,而是要啓動APP  。所以直接啓動App便可
            launchApp();
        }
        finish();
    }
複製代碼

上面代碼中,咱們對URI作了一系列校驗,根據不一樣的URI作不一樣的處理。同時咱們應該也注意到了,若是APP已經啓動了,那麼就能夠直接跳轉對應的頁面了,而若是App沒有啓動,那麼則是先將URI保存到了SharedPreference中,接着啓動了App。那麼此時App啓動後會在MainActivity中讀取SharedPreference中的配置,若是讀取到URI的信息,那麼就先將此數據從SharedPreference中移除,而後經過ARouter跳轉到URI指定的頁面去。MainActivity中的部分代碼以下:

#MainActivity

private void resumeRoute() {
        // Continue for interrupted router uri
        String interruptedLaunchUriString =
                SharedPreferUtil.get(Constants.ROUTER_URI, null);
         //	移除SharedPreference中的URI,避免下次打開MainActivity錯誤跳轉
        SharedPreferUtil.remove(Constants.ROUTER_URI);
        Uri launchUri = null;
        if(interruptedLaunchUriString != null) { // Activity未啓動的狀況下 經過外部Scheme跳轉非MainActivity
            launchUri = Uri.parse(interruptedLaunchUriString);
        }

        if(launchUri == null) {
            return;
        }
		//	經過路由跳轉到URI對應的頁面
        ARouter.getInstance().build(launchUri).navigation();
    }
複製代碼

關於ARouter的路由方案所涉及的內容至此已經所有講完了。

相關文章
相關標籤/搜索