標籤:添加 setup abstract 使用者 局限性 imp 擴充 優點 exception
本文轉自: http://blog.csdn.net/qibin0506/article/details/53373412
前幾個月有幸參加了CSDN組織的MDCC移動開發人員大會, 一天下來我最大的收穫就是瞭解到了模組化開發, 回來之後我就一直在思考模組化的一些優點, 不說別的, 提供一種可插拔的開發方式就足夠我們興奮一會了~ 接下來自己開始嘗試了一些小demo, 發現在模組化開發中最大的問題就是組件間通訊, 例如: 在模組化架構中, 商城和個人中心分別是兩個獨立的模組, 在開發階段, 個人中心如何想要跳轉商城的某個頁面咋辦? 這裡就需要引入一個路由的概念了. 做過web開發的都知道, 在大部分web架構中url路由也是架構中很重要的組成部分, 如果大家對路由的概念還不是很清楚, 可以先來看一下我這篇go web開發之url路由設計來瞭解下路由的概念, 這裡稍稍解釋一下路由就是起到一個轉寄的作用.
一張圖來體會一下路由的作用, 因為我本地沒有UML工具, 新的還在下載中… 900M+, 我這網速有點受不了. 所以我選擇KolourPaint手動繪製一張具有魔性的圖片先來體會一下.
自己實現一個路由的動機
那到了我們Android開發中呢? 如果我們把項目模組化了, 那兩個組件間進行通訊或者跳轉, 我們一般構建Intent的方式就不再使用了, 很簡單, 因為在模組A中根本找不到模組B中的C類, 這就需要我們自訂路由規則, 繞一道彎去進行跳轉, 說白了就是給你的類起一個別名, 我們用別用去引用. 其實在我準備自己去實現一個路由的時候我是google了一些解決方案的, 這些方案大致可分為兩種.
- 完全自己實現路由, 完全封裝跳轉參數
- 利用隱式意圖跳轉
對於這兩種方式我總結了一下, 個人認為第一種方式封裝的太多, 甚至有些架構是RESTFul like的, 這樣的封裝一是學習成本太高, 二是舊項目改動起來太麻煩. 那第二種方式呢? 利用隱式意圖是一種不錯的選擇, 而且Android原生支援, 這也是大家在嘗試模組化開發時的一個選擇, 不過這種方式僅支援Activity, Service, BroadcastReceiver, 擴充性太差. 綜上因素, 我還是決定自己實現一個路由, 參考自上面的局限性, 我們的路由具有一下2個特點.
- 上手簡單, 目標是與原生方式一行代碼之差就能實現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模組吧.
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介面的定義如下,
/** * 路由規則介面<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中找到~
【轉】 Android路由實現