本文轉自: 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
- 徹底本身實現路由, 徹底封裝跳轉參數
- 利用隱式意圖跳轉
對於這兩種方式我總結了一下, 我的認爲第一種方式封裝的太多, 甚至有些框架是RESTFul like的, 這樣的封裝一是學習成本過高, 二是舊項目改動起來太麻煩. 那第二種方式呢? 利用隱式意圖是一種不錯的選擇, 並且Android原生支持, 這也是你們在嘗試模塊化開發時的一個選擇, 不過這種方式僅支持Activity, Service, BroadcastReceiver, 擴展性太差. 綜上因素, 我仍是決定本身實現一個路由, 參考自上面的侷限性, 咱們的路由具備一下2個特色.web
- 上手簡單, 目標是與原生方式一行代碼之差就能實現Activity, Service, BroadcastReceiver調用.
- 擴展性強, 開發者能夠任意添加本身的路由實現, 不只僅侷限於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
方法, 咱們經過參數中的pattern
從mIntentRules
目標類, 而後構建一個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中找到~