Android路由實現
本文已授權微信公眾號:鴻洋(hongyangAndroid)在微信公眾號平臺原創首發。
前幾個月有幸參加了CSDN組織的MDCC移動開發者大會, 一天下來我最大的收穫就是了解到了模組化開發, 回來之後我就一直在思考模組化的一些優點, 不說別的, 提供一種可插拔的開發方式就足夠我們興奮一會了~ 接下來自己開始嘗試了一些小demo, 發現在模組化開發中最大的問題就是元件間通訊, 例如: 在模組化架構中, 商城和個人中心分別是兩個獨立的模組, 在開發階段, 個人中心如何想要跳轉商城的某個頁面咋辦? 這裡就需要引入一個路由的概念了. 做過web開發的都知道, 在大部分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中找到~