隨着項目業務邏輯和功能點日益遞增, 邏輯的耦合程度也逐漸升高, 組件化技術能夠很好的解決這個問題, 公司大佬最近也在搞組件化工程, 我想是時候分析一下組件化的實現方案了, Alibaba 的 ARouter 是很是出名的一個庫, 筆者抱着學習的態度去了解其實現原理, 以便於對組件化有更深入的瞭解.java
github.com/alibaba/ARo…android
/**
* Mark a page can be route by router.
*
* @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @version 1.0
* @since 16/8/15 下午9:29
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
/**
* 跳轉地址
*/
String path();
/**
* 跳轉地址所屬的組, 必須是通用的組名(通常不填/或者填寫當前 module 名)
*/
String group() default "";
/**
* 用於生成 java 文檔名(可忽略)
*/
String name() default "";
/**
* Extra data, can be set by user.
* Ps. U should use the integer num sign the switch, by bits. 10001010101010
*/
int extras() default Integer.MIN_VALUE;
/**
* 路由的優先級, 用於攔截路由的分發
*/
int priority() default -1;
}
複製代碼
/**
* Mark a interceptor to interception the route.
* BE ATTENTION : This annotation can be mark the implements of #{IInterceptor} ONLY!!!
*
* @author Alex <a href="mailto:zhilong.liu@aliyun.com">Contact me.</a>
* @version 1.0
* @since 16/8/23 14:03
*/
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Interceptor {
/**
* The priority of interceptor, ARouter will be excute them follow the priority.
*/
int priority();
/**
* The name of interceptor, may be used to generate javadoc.
*/
String name() default "Default";
}
複製代碼
編譯時註解的掃描與相關類的生成git
提供路由的基本服務github
graph TB
arouter-api-->arouter-annotation
arouter-compiler-->arouter-annotation
複製代碼
接下來開始分析 ARouter 的工做流程api
/**
* ARouter.init
*/
public static void init(Application application) {
if (!hasInit) {
// 用於 ARouter 的 logger 打印, 以後會忽略這部分的源碼
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
// 1. 調用了 _ARouter 中的 init
hasInit = _ARouter.init(application);
if (hasInit) {
// 2. 調用 _ARouter 中的 afterInit 處理初始化以後的相關操做
_ARouter.afterInit();
}
}
}
複製代碼
能夠看到 ARouter 其實就是個外殼, 具體的實現均由 _ARouter 提供, 可見路由的初始化分爲兩步進行數組
先從 _ARouter.init 開始緩存
/**
* _ARouter.init
*/
protected static synchronized boolean init(Application application) {
mContext = application;
// 1. 調用了 LogisticsCenter 的 init 方法
LogisticsCenter.init(mContext, executor);
// 2. 更新標記位
hasInit = true;
// 3. 綁定主線程的 Handler
mHandler = new Handler(Looper.getMainLooper());
return true;
}
複製代碼
主要作了三件事情bash
接下來分析一下 LogisticsCenter 初始化作了哪些事情app
/**
* LogisticsCenter.init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
// 給線程池賦值
executor = tpe;
try {
// 嘗試使用插件註冊 ARouter
loadRouterMap();
if (registerByPlugin) {
// ...
} else {
// 這裏咱們着重分析非插件註冊的初始化實現
Set<String> routerMap;
// 1. 判斷是不是調試(調試會生成類), 如果調試, 則從生成文件夾下找尋生成類
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
// 1.1 每次調試, 會經過編譯時註解, 生成相關文件保存到本地 com.alibaba.android.arouter.routes 文件夾下
// 當程序運行時, 經過 getFileNameByPackageName 找到對應的全限定名
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
// 1.2 更新生成文件的集合
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit()
.putStringSet(AROUTER_SP_KEY_MAP, routerMap)
.apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
// 2. 若不是調試, 則直接從本地的緩存中獲取, 非調試環境, 不會生成新的代碼
routerMap = new HashSet<>(
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE)
.getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>())
);
}
// 3. 遍歷集合, 將緩存的集合加載進相應的集合中
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// 3.1 加載根元素(com.alibaba.android.arouter.routes.ARouter$$Root$$XXX)
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// 3.2 加載攔截器標籤元素(com.alibaba.android.arouter.routes.ARouter$$Interceptors$$XXX)
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// 3.3 加載提供器元素(com.alibaba.android.arouter.routes.ARouter$$Providers$$XXX)
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
// ...忽略日誌代碼
} catch (Exception e) {
// ...忽略異常代碼
}
}
/**
* LogisticsCenter.loadRouterMap 若啓用了插件註冊 ARouter , 將會在這個方法中自動生成代碼
*/
private static void loadRouterMap() {
registerByPlugin = false;
//auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
// registerRouteRoot(new ARouter..Root..modulejava());
// registerRouteRoot(new ARouter..Root..modulekotlin());
}
/**
* Warehouse : ARouter 的數據倉庫
*/
class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
static Map<String, RouteMeta> routes = new HashMap<>();
// Cache provider
static Map<Class, IProvider> providers = new HashMap<>();
static Map<String, RouteMeta> providersIndex = new HashMap<>();
// Cache interceptor
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();
static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}
複製代碼
經過 LogisticsCenter.init 的分析, 可知該方法起到了很是重要的做用, 主要作了如下工做less
/**
* CreateTime: @Router 註解做用在類上時, 被生成
* Description:
* 將全部被 @Router(path="", group="m2") 註解標記而且 group = m2 的類, 添加到 Warehouse.routes 中
* Warehouse.routes(key = @Router中的path, value = @Router標記類的信息生成的RouteMeta對象)
*/
public class ARouter$$Group$$m2 implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/module/2", RouteMeta.build(RouteType.ACTIVITY, TestModule2Activity.class, "/module/2", "m2", null, -1, -2147483648));
}
}
/**
* CreateTime: 一個 Module 中如有 ARouter$$Group$$XXX 存在時, 被生成
* Description:
* 收集一個 Module 下全部的 ARouter$$Group$$XXX 添加到 Warehouse.groupsIndex 中
* Warehouse.groupsIndex (key = Group名, value = ARouter$$Group$$XXX.class)
*/
public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("m2", ARouter$$Group$$m2.class);
}
}
/**
* CreateTime: @Router 註解做用在實現了 IProvider 的類上時, 被生成
* Description:
* 收集一個 Module 下全部的 IProvider 實現類信息添加到 Warehouse.providersIndex 中
* Warehouse.providersIndex(key = 實現的接口類的全限定名, value = @Router標記類的信息生成的RouteMeta對象);
*/
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.alibaba.android.arouter.demo.testservice.HelloService",
RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/service/hello", "service", null, -1, -2147483648)
);
}
}
/**
* CreateTime: @Interceptor 註解做用在實現了 IInterceptor 的類上時, 被生成
* Description:
* 收集一個 Module 下全部的 IInterceptorGroup 實現類信息添加到 Warehouse.interceptorsIndex 中
* Warehouse.interceptorsIndex (key = @Interceptor中的優先級信息, value = 實現類的類型)
*/
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(7, Test1Interceptor.class);
}
}
複製代碼
可見 ARouter.init 方法的主要任務, 爲初始化路由表, 保存在 Warehouse 中, 方便後續路由請求, 找到目標地址
static void afterInit() {
// 初始化攔截服務
interceptorService = (InterceptorService) ARouter.getInstance()
.build("/arouter/service/interceptor")
.navigation();
}
複製代碼
好的, 這裏調用了 ARouter.build, 咱們看看它作了些什麼
/**
* ARouter.build
*/
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
/**
* ARouter.build
*/
@Deprecated
public Postcard build(String path, String group) {
return _ARouter.getInstance().build(path, group);
}
/**
* ARouter.build
*/
public Postcard build(Uri url) {
return _ARouter.getInstance().build(url);
}
複製代碼
能夠看到三個重載方法都調用了 _ARouter 的方法, 這裏與 WindowManager 中的方法使用 WindowManagerGlobal 橋接的手法很相似
/**
* _ARouter.build
* 使用默認的 Group 組, 構建 Postcard 明信片
*/
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 1. 獲取 PathReplaceService 服務
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
// 調用 extractGroup 方法從 path 獲取路由的 group, 調用了重載方法
return build(path, extractGroup(path));
}
}
/**
* _ARouter.build
* 經過 Uri 來構建 Postcard 明信片
*/
protected Postcard build(Uri uri) {
if (null == uri || TextUtils.isEmpty(uri.toString())) {
throw new HandlerException(Consts.TAG + "Parameter invalid!");
} else {
// 1. 獲取 PathReplaceService 服務
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
uri = pService.forUri(uri);
}
// 2. 經過 path/group/uri 來構建明信片
return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null);
}
}
/**
* _ARouter.build
* 經過路徑和指定的 group(用於合併路由) 構建明信片
*/
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 1. 獲取 PathReplaceService 服務
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
// 2. 經過 path / group 來構建明信片
return new Postcard(path, group);
}
}
複製代碼
經過 _ARouter.build 重載方法可知有一個很是重要的點 經過調用 ARouter.navigation(PathReplaceService.class) 獲取了一個 PathReplaceService 服務, 接下來看看這個服務獲取的流程
/**
* ARouter.navigation
*/
public <T> T navigation(Class<? extends T> service) {
return _ARouter.getInstance().navigation(service);
}
/**
* _ARouter.navigation
*/
protected <T> T navigation(Class<? extends T> service) {
try {
// 1 經過全限定類名嘗試獲取 service 的 Postcard
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
if (null == postcard) {
// 嘗試經過簡單類名獲取 Postcard
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
// 2. 沒找到則返回 null
if (null == postcard) {
return null;
}
// 3. 找到了則調用 LogisticsCenter.completion 對這個 postcard 進行數據填充
LogisticsCenter.completion(postcard);
// 4. 經過 postcard.getProvider() 返回數據
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
// ...
}
}
/**
* LogisticsCenter.buildProvider
*/
public static Postcard buildProvider(String serviceName) {
// Warehouse.providersIndex 在 LogisticsCenter.init 中進行數據注入, 在上面已經分析過
// 經過 serviceName 獲取對應類的信息封裝對象 RouteMeta
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
// 構建明信片
return new Postcard(meta.getPath(), meta.getGroup());
}
}
複製代碼
能夠看到 _ARouter.navigation 中主要處理了如下事務
PathReplaceService 不爲空的話, 會調用其 forString(path)/forUri(uri) 進行自定義解析(通常爲空, ARouter 默認沒有實現這個接口)
至此 ARouter.build 完成以後獲取到了一個 Postcard 對象, 而後會調用 Postcard.navigation() 獲取所需對象實例
接下來分析一下 Postcard.navigation() 是如何進行路由尋址的
路由跳轉是由 Postcard.navigation() 發起的, 咱們看看他的實現
/**
* Postcard.navigation
*/
public Object navigation() {
return navigation(null);
}
/**
* Postcard.navigation
*/
public Object navigation(Context context) {
return navigation(context, null);
}
/**
* Postcard.navigation
*/
public Object navigation(Context context, NavigationCallback callback) {
// 最終仍是回到了 ARouter.navigation 方法中
return ARouter.getInstance().navigation(context, this, -1, callback);
}
複製代碼
能夠看到 Postcard.navigation 的一系列重載方法, 最終都會調用到 ARouter.navigation 中, 這裏纔是重頭戲的開始
接下來分析 ARouter.navigation(context, this, -1, callback) 這個方法的處理了哪些事務
/**
* ARouter.navigation
*/
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
/**
* _ARouter.navigation
*/
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
// 1. 經過 postcard 明信片, 進行導航查找
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// ...回調一些沒有找到目標的操做
return null;
}
// 2. 回調經過 postcard 明信片定位到了目標
if (null != callback) {
callback.onFound(postcard);
}
// 3. 判斷 postcard 是否爲綠色通道, 若不是則啓動攔截器服務進行攔截
if (!postcard.isGreenChannel()) { // 必須運行在異步線程, 不然可能 ANR
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
// 4. 回調 _navigation 方法
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
/**
* _ARouter._navigation
*/
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
// 4.1 處理 Activity 的導航
case ACTIVITY:
// 4.1.1 構建 Intent 意圖
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// 4.1.2 獲取並設置 Flags
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// 4.1.3 設置 Intent 的 Action
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// 4.1.4 在主線程中處理 Activity 的啓動
if (Looper.getMainLooper().getThread() != Thread.currentThread()) {
mHandler.post(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
} else {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
break;
// 4.2 處理 PROVIDER("com.alibaba.android.arouter.facade.template.IProvider") 的導航
case PROVIDER:
return postcard.getProvider();
// 4.3 處理 BOARDCAST/CONTENT_PROVIDER/FRAGMENT 的導航
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
// 4.3.1 反射建立 Fragment 對象
Object instance = fragmentMeta.getConstructor().newInstance();
// 4.3.2 給 Fragment 設置參數
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
// ...
}
// 4.4 處理 METHOD/SERVICE 的導航
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
複製代碼
經過上面的源碼分析能夠很是清晰的看到 _ARouter.navigation 主要作了如下的事情
能夠看到 ARouter.navigation(context, this, -1, callback) 中最終會調用 LogisticsCenter.completion 進行數據注入, 前面 2.1 中提到 ARouter.navigation(Class<? extends T> service) 一樣也調用了這個方法, 其重要程度可見一斑
最後的操做, 即是經過 LogisticsCenter.completion 進行路由跳轉了
/**
* LogisticsCenter.completion
*/
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
// 1. 從緩存池中 Warehouse.routes 獲取對應的 ARouter$$Group$$xxx.class 類的信息封裝對象 routeMeta
// 初始化的時候, 必定爲 null, 在 LogisticsCenter.init 中, 只對 Warehouse.groupsIndex 進行了數據填充
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 2. 從索引池 Warehouse.groupsIndex 中獲取 IRouteGroup 對應的實體類型(ARouter$$Group$$xxx.class)
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (null == groupMeta) {
// ...異常日誌
} else {
try {
// 2.1 實例化 IRouteGroup(ARouter$$Group$$xxx.class)
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
// 2.2 調用其 loadInto 方法, 將該 Group 下全部類的信息 RouteMeta 填充到 Warehouse.routes 中
iGroupInstance.loadInto(Warehouse.routes);
// 2.3 說明當前 group 下的 RouteMeta 已經解析到 Warehouse.routes 中了, 從集合中移除
Warehouse.groupsIndex.remove(postcard.getGroup());
} catch (Exception e) {
// ... 異常日誌
}
// 2.4 從新調用 LogisticsCenter.completion, 下次將會走到 3 中
completion(postcard); // Reload
}
} else {
// 3. 給 postcard 注入數據
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
// 3.1 獲取 URI
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
// 3.2 分割 uri 參數
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
// 3.3 獲取參數類型而且注入 postcard 中
Map<String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry<String, Integer> params : paramsType.entrySet()) {
setValue(postcard, params.getValue(), params.getKey(), resultMap.get(params.getKey()));
}
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// 3.5 mBundle 向 Bundle 中保存 URI鍵值對
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
// 4. 根據 routeMeta 的類型信息處理相應的操做
switch (routeMeta.getType()) {
case PROVIDER: // 4.1 爲 ARouter 的 IProvider
// 4.1.1 構建實例對象
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
// 根據 IProvider 的信息從 providers 中獲取緩存的實例對象
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) {
IProvider provider;
try {
// 反射建立實例對象
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
// 添加到緩存池
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
// ...
}
}
// 4.1.2 給 postcard 注入實例對象
postcard.setProvider(instance);
// 4.1.3 IProvider 類型的目標不容許設置爲不可攔截(綠色通道)
postcard.greenChannel();
break;
case FRAGMENT :// 4.2 Fragment 的類型
// 4.2.1 FRAGMENT 類型的目標不容許設置爲不可攔截(綠色通道)
postcard.greenChannel();
default:
break;
}
}
}
複製代碼
可見這個方法真的作了不少重要的事情
從緩存池中 Warehouse.routes 獲取對應的 ARouter$$Group$$xxx.class 類的信息封裝對象 routeMeta
routeMeta 在緩存中不存在
routeMeta 在緩存中存在
routeMeta 的類型信息處理相應的操做
PROVIDER:
FRAGMENT:
以後即可以經過 postcard.getProvider(); 返回 ARouter.navigation 的實例對象了
經過本次對 ARouter 進行分析, 藉此釐清 ARouter 初始化的工做原理, 雖說是初始化, 但能夠發現不少核心代碼都一併被分析了, 裏面的緩存思想和代碼設計是極好的, 很是值得學習, 筆者有不少分析不到位的地方, 但願你們可以批評指出
筆者參考了 ARouter 的實現方式, 手寫一個 SRouter 庫, 以加深對 Router 的理解, 其中攔截器思想使用的 OkHttp