【轉】 Android路由實現

本文轉自: http://blog.csdn.net/qibin0506/article/details/53373412java

前幾個月有幸參加了CSDN組織的MDCC移動開發者大會, 一天下來我最大的收穫就是了解到了模塊化開發, 回來以後我就一直在思考模塊化的一些優勢, 不說別的, 提供一種可插拔的開發方式就足夠咱們興奮一會了~ 接下來本身開始嘗試了一些小demo, 發如今模塊化開發中最大的問題就是組件間通信, 例如: 在模塊化架構中, 商城和我的中心分別是兩個獨立的模塊, 在開發階段, 我的中心如何想要跳轉商城的某個頁面咋辦? 這裏就須要引入一個路由的概念了. 作過web開發的都知道, 在大部分web框架中url路由也是框架中很重要的組成部分, 若是你們對路由的概念還不是很清楚, 能夠先來看一下我這篇go web開發之url路由設計來了解下路由的概念, 這裏稍稍解釋一下路由就是起到一個轉發的做用. 
一張圖來體會一下路由的做用, 由於我本地沒有UML工具, 新的還在下載中… 900M+, 我這網速有點受不了. 因此我選擇KolourPaint手動繪製一張具備魔性的圖片先來體會一下.git

本身實現一個路由的動機

那到了咱們Android開發中呢? 若是咱們把項目模塊化了, 那兩個組件間進行通信或者跳轉, 咱們通常構建Intent的方式就再也不使用了, 很簡單, 由於在模塊A中根本找不到模塊B中的C類, 這就須要咱們自定義路由規則, 繞一道彎去進行跳轉, 說白了就是給你的類起一個別名, 咱們用別用去引用. 其實在我準備本身去實現一個路由的時候我是google了一些解決方案的, 這些方案大體可分爲兩種.github

  1. 徹底本身實現路由, 徹底封裝跳轉參數
  2. 利用隱式意圖跳轉

對於這兩種方式我總結了一下, 我的認爲第一種方式封裝的太多, 甚至有些框架是RESTFul like的, 這樣的封裝一是學習成本過高, 二是舊項目改動起來太麻煩. 那第二種方式呢? 利用隱式意圖是一種不錯的選擇, 並且Android原生支持, 這也是你們在嘗試模塊化開發時的一個選擇, 不過這種方式僅支持Activity, Service, BroadcastReceiver, 擴展性太差. 綜上因素, 我仍是決定本身實現一個路由, 參考自上面的侷限性, 咱們的路由具備一下2個特色.web

  1. 上手簡單, 目標是與原生方式一行代碼之差就能實現Activity, Service, BroadcastReceiver調用.
  2. 擴展性強, 開發者能夠任意添加本身的路由實現, 不只僅侷限於Activity, Service, BroadcastReceiver.

體驗一下

在瞭解具體實現代碼以前, 咱們先來了解一下新的路由怎麼使用, 使用起來是否是符合上面兩點, 首先咱們先創建三個moduler, 分別是殼app, 商城模塊shoplib, bbs模塊bbslib. app模塊就是咱們的殼了, 咱們須要利用app模塊去打包, 並且app也是依賴shoplib和bbslib的, 因此咱們能夠在app的application裏進行路由的註冊.架構

public class App extends Application { @Override public void onCreate() { super.onCreate(); setupRouter(); } private void setupRouter() { Router.router(ActivityRule.ACTIVITY_SCHEME + "shop.main", ShopActivity.class); Router.router(ActivityRule.ACTIVITY_SCHEME + "bbs.main", BBSActivity.class); } }

這裏註冊了兩個路由, 分別是商城模塊的的ShopActivity和bbs模塊的BBSActivity, 它們都是經過Router類的靜態方法router方法進行註冊的, 兩個參數, 第一個參數是路由地址(也能夠理解成別名), 第二個參數對應的類. 註冊完了, 那接下來就是如何使用了, 咱們來看看在商城模塊如何跳轉BBS模塊吧.app

public class ShopActivity extends AppCompatActivity { @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); TextView tv = new TextView(this); tv.setTextSize(50); tv.setText("SHOP!!!"); setContentView(tv); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Intent it = Router.invoke(ShopActivity.this, ActivityRule.ACTIVITY_SCHEME + "bbs.main"); startActivity(it); } }); } }

主要代碼是在click事件裏, 咱們調用了Router.invoke方法, 第一個參數是當前Activity, 第二個參數就是咱們前面註冊的路由了, 這裏都很好理解, 關鍵是看它的返回值, 這裏直接返回了一個Intent, 這一點是最棒的~ 返回Intent也就是說明下面的代碼和咱們使用原生方式沒有任何區別! 這一點符合上面咱們說到的上手簡單的目的.框架

至於第二點目標, 高擴展性, 你們能夠實現Rule接口自定義路由Rule, 而後調用Router.addRule(String scheme, Rule rule)方法進行路由規則的註冊. Rule接口的定義以下,ide

/** * 路由規則接口<br/> * Created by qibin on 2016/10/8. */ public interface Rule<T, V> { /** * 添加路由 * @param pattern 路由uri * @param klass 路由class */ void router(String pattern, Class<T> klass); /** * 路由調用 * @param ctx Context * @param pattern 路由uri * @return {@code V} 返回對應的返回值 */ V invoke(Context ctx, String pattern); }

解釋一下, 首先是Rule接口的兩個範型, 第一個T是咱們註冊的路由類型, 例如前面使用的Activity類型, 第二個V是invoke方法的返回值類型, 例如前面使用的Intent類型.至於自定義的代碼, 這裏我賴了~, 沒有提供demo~~~ 你們能夠嘗試自定義一下.模塊化

路由實現代碼

接下來咱們開始進入實現代碼環節~ 在來是代碼以前, 仍是先來一張圖瞭解下這個Router的結構.工具

帶着上面的圖片, 咱們來看代碼, 首先咱們來看看Router類, 畢竟咱們在使用的時候都是在和Router打交道.

/** * Usage: <br /> * <pre> * step 1. 調用Router.router方法添加路由 * step 2. 調用Router.invoke方法根據pattern調用路由 * </pre> * Created by qibin on 2016/10/9. */ public class Router { /** * 添加自定義路由規則 * @param scheme 路由scheme * @param rule 路由規則 * @return {@code RouterInternal} Router真實調用類 */ public static RouterInternal addRule(String scheme, Rule rule) { RouterInternal router = RouterInternal.get(); router.addRule(scheme, rule); return router; } /** * 添加路由 * @param pattern 路由uri * @param klass 路由class * @return {@code RouterInternal} Router真實調用類 */ public static <T> RouterInternal router(String pattern, Class<T> klass) { return RouterInternal.get().router(pattern, klass); } /** * 路由調用 * @param ctx Context * @param pattern 路由uri * @return {@code V} 返回對應的返回值 */ public static <V> V invoke(Context ctx, String pattern) { return RouterInternal.get().invoke(ctx, pattern); } }

哈, Router的代碼很簡單, 主要就是起到一個相似靜態代理的做用, 主要的代碼仍是在RouterInternal裏, 那來看看RouterInternal的結構吧.

public class RouterInternal { private static RouterInternal sInstance; /** scheme->路由規則 */ private HashMap<String, Rule> mRules; private RouterInternal() { mRules = new HashMap<>(); initDefaultRouter(); } /** * 添加默認的Activity,Service,Receiver路由 */ private void initDefaultRouter() { addRule(ActivityRule.ACTIVITY_SCHEME, new ActivityRule()); addRule(ServiceRule.SERVICE_SCHEME, new ServiceRule()); addRule(ReceiverRule.RECEIVER_SCHEME, new ReceiverRule()); } /*package */ static RouterInternal get() { if (sInstance == null) { synchronized (RouterInternal.class) { if (sInstance == null) { sInstance = new RouterInternal(); } } } return sInstance; } }

首先RouterInternal是一個單例, 一個mRules變量用來保存咱們的路由規則, 在構造中咱們註冊了三個默認的路由規則, 這三個路由規則想都不用想就知道是Activity, Service和BroadcastReceiver的. 接下來看看其餘的方法.

/** * 添加自定義路由規則 * @param scheme 路由scheme * @param rule 路由規則 * @return {@code RouterInternal} Router真實調用類 */ public final RouterInternal addRule(String scheme, Rule rule) { mRules.put(scheme, rule); return this; }

addRule方法是添加路由規則的實現, 這裏咱們是直接向mRules這個HashMap中添加的.

private <T, V> Rule<T, V> getRule(String pattern) { HashMap<String, Rule> rules = mRules; Set<String> keySet = rules.keySet(); Rule<T, V> rule = null; for (String scheme : keySet) { if (pattern.startsWith(scheme)) { rule = rules.get(scheme); break; } } return rule; }

getRule的做用是根據pattern來獲取規則, 這是一個私有的方法, 因此在使用的時候不須要關心, 它的原理很簡單, 就是根據你的pattern來匹配 scheme來獲取對應的Rule.

/** * 添加路由 * @param pattern 路由uri * @param klass 路由class * @return {@code RouterInternal} Router真實調用類 */ public final <T> RouterInternal router(String pattern, Class<T> klass) { Rule<T, ?> rule = getRule(pattern); if (rule == null) { throw new NotRouteException("unknown", pattern); } rule.router(pattern, klass); return this; }

這個router方法就是咱們添加路由的實現了, 首先咱們根據路由的uri來獲取對應的Rule, 而後調用該Rule的 router方法, 至於Rule.router方法如何實現的, 咱們稍後看~

/** * 路由調用 * @param ctx Context * @param pattern 路由uri * @return {@code V} 返回對應的返回值 */ /*package*/ final <V> V invoke(Context ctx, String pattern) { Rule<?, V> rule = getRule(pattern); if (rule == null) { throw new NotRouteException("unknown", pattern); } return rule.invoke(ctx, pattern); }

invoke方法就是咱們調用的時候執行的代碼的, 返回值T是返回的Rule範型中指定的類型, 例如前面的Intent
綜上代碼, 咱們發現RouterInternal其實就是一個管理Rule的類, 具體的調用仍是在各個Rule中實現, 上面提到過, Rule是一個接口, 它具備兩個範型, 分別對應的調用 invoke的返回值類型和咱們要路由的類的類型. 解析來咱們就來看看默認的幾個路由規則是如何實現的.

對於Activity, Service, BroadcastReceiver的調用, 總結了一下, 它們其實都是返回的Intent類型, 因此咱們能夠先構建一個指定返回值是Intent的Base類型.

/** * 返回Intent的路由規則的基類<br /> * Created by qibin on 2016/10/9. */ public abstract class BaseIntentRule<T> implements Rule<T, Intent> { private HashMap<String, Class<T>> mIntentRules; public BaseIntentRule() { mIntentRules = new HashMap<>(); } /** * {@inheritDoc} */ @Override public void router(String pattern, Class<T> klass) { mIntentRules.put(pattern, klass); } /** * {@inheritDoc} */ @Override public Intent invoke(Context ctx, String pattern) { Class<T> klass = mIntentRules.get(pattern); if (klass == null) { throwException(pattern);} return new Intent(ctx, klass); } /** * 當找不到路由規則時拋出異常 * @param pattern 路由pattern */ public abstract void throwException(String pattern); }

router方法很少說, 仍是向Map中添加鍵值對, invoke方法, 咱們經過參數中的patternmIntentRules目標類, 而後構建一個Intent返回, 最後一個throwException是一個抽象方法, 用來在調用沒有router的類時拋出異經常使用~, 能夠發現, 其實大部分的實如今這裏都實現了, 對於Activity繼承這個BaseIntentRule,而且指定要路由類的類型是Activity, 而且實現throwException方法就能夠了.

/** * activity路由規則<br /> * Created by qibin on 2016/10/8. */ public class ActivityRule extends BaseIntentRule<Activity> { /** activity路由scheme*/ public static final String ACTIVITY_SCHEME = "activity://"; /** * {@inheritDoc} */ @Override public void throwException(String pattern) { throw new ActivityNotRouteException(pattern); } }

ActivityRule首先繼承了BaseIntentRule並指定了範型是Activity, 實現的throwException方法也很簡單, 就是拋出了一個ActivityNotRouteException異常, 對於這個異常, 你們能夠在文章最後的源碼下載部分找到~ 看完ActivityRule的實現, 其實其餘兩個默認Rule的實現都同樣了~ 你們也是本身去看代碼吧.

其實實現一個路由很簡單, 原理就是給咱們要路由的類定義一個別名, 而後在調用的地方經過別名去調用. 並且在封裝的時候儘可能要符合如今用戶的使用習慣, 不要過多的封裝而忽略了使用者的感覺.

好了, 這篇文章就到這裏了, 文章中的代碼你們能夠到https://github.com/qibin0506/Module2Module一個模塊化開發的小demo中找到~

相關文章
相關標籤/搜索