阿裡P7移動網際網路架構師進階視訊(每日更新中)免費學習請點選: https://space.bilibili.com/474380680
背景
當項目的業務越來越複雜,業務線越來越多的時候,就需要按照業務線去分不同的子產品去開發,這樣專門的人負責專門的業務子產品,最終上線由殼工程去負責進行組合打包各個module,完成業務的快速疊代。整個過程會涉及到各個子產品間進行通信,比如訂單子產品和個人中心子產品,可能會需要頻繁的傳遞資料和頁面跳轉,這個時候怎麼去處理呢?我們能想到的方案就是采用類名反射,來動态建立需要跳轉和互動的類,這樣編譯時就不會報錯,運作時又可以完成子產品間的互動。阿裡巴巴推出的開源路由架構——ARouter就是基于反射和注解來解決這個問題的,本文不講基本使用(基本使用在項目的github首頁上已經将的非常詳細了),通過分析整個路由過程來講解它的基本原理。
說在前面
首先在我們需要用到的類的類名加上注解@Route(“/group/name”),注意這裡需要至少兩層路徑(第一個是分組,第二個一般是類名)。這個注解就是代表這個類可以被其他子產品找到的一個路徑的注解,并且它是一個編譯時注解,這就意味着在編譯時就已經生成了相應的輔助類。ARouter把路由一共分為以下幾類:
ACTIVITY(0, “android.app.Activity”),
SERVICE(1, “android.app.Service”),
PROVIDER(2, “com.alibaba.android.arouter.facade.template.IProvider”),
CONTENT_PROVIDER(-1, “android.app.ContentProvider”),
BOARDCAST(-1, “”),
METHOD(-1, “”),
FRAGMENT(-1, “android.app.Fragment”),
UNKNOWN(-1, “Unknown route type”);
其中我們常用的就是ACTIVITY,PROVIDER,FRAGMENT這三個了,也基本上滿足了我們子產品化開發的需求。另外一點就是分組的概念,ARouter是按照組來進行整理的,也就是第一層的路徑,是以前面說必須要兩層路徑,否則不知道歸到哪裡去,一般一個module按照子產品名采用統一的分組辨別。我們來看看注解生成的類(這裡隻包含了Activity,Fragment,Provider):
package com.alibaba.android.arouter.routes;
//。。。import省略
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter
$$
Group
$$
Personal implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/Personal/EARNING", RouteMeta.build(RouteType.ACTIVITY, PerEarningActivity.class, "/personal/earning", "personal", null, -1, -2147483648));
//...省略Activity,Fragment
atlas.put("/Personal/main", RouteMeta.build(RouteType.FRAGMENT, PerMainFragment.class, "/personal/main", "personal", null, -1, -2147483648));
atlas.put("/Personal/service", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/personal/service", "personal", null, -1, -2147483648));
}
}
以上就是所有注解的路徑的資訊集合,包含了所有的Activity,Fragment,Provider(一般一個module一個Provider就夠用了,專門用來跟其他子產品互動),并都以路徑為key放到這個map中。
package com.alibaba.android.arouter.routes;
//。。。import省略
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter
$$
Providers
$$
modlue_personal implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.tb.test.service.ModulePersonalService", RouteMeta.build(RouteType.PROVIDER, PerServiceImpl.class, "/Personal/service", "personal", null, -1, -2147483648));
}
}
這個類是專門的Provider的索引的集合,所有的provider都被以全類名為索引放到一個map中。
package com.alibaba.android.arouter.routes;
//。。。import省略
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter
$$
Root
$$
modlue_personal implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("Personal", ARouter
$$
Group
$$
Personal.class);
}
}
這個類是所有的group的資訊收集,全部都以group的名字為key,以注解生成的不同的group的類的class對象為value放入到一個map中。
總共就生成這三種類型的類,當然,如果你有不同的分組還會生成其他的類,不過都是這三種裡面的一種。
完成了這些注解資訊的收集,下面就會去使用這些資訊來完成我們的跨子產品互動了。
初始化過程
使用ARouter必須先要進行初始化:
if (isDebug()) {
// These two lines must be written before init, otherwise these configurations will be invalid in the init process
ARouter.openLog(); // Print log
ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk)
}
ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application
上面這段話就是去初始化Arouter,我們來看看init裡面到底做了什麼事。。。
/**
* Init, it must be call before used router.
*/
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
可以看到,這裡使用了外觀模式,最終調用都是在_ARouter這個類裡面,跟進去:
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
代碼也很簡單,核心就是LogisticsCenter.init這句話,跟進去看看,核心代碼如下:
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
//
for (String className : classFileNames) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
我們可以看到,首先是去擷取到所有的app裡的由ARouter注解生成的類的類名,他們的統一特點就是在同一個包下,包名為:com.alibaba.android.arouter.routes
然後就是循環周遊這些類,也就是剛才我們說的那三種類。在這裡,有一個Warehouse類,看下代碼:
/**
* Storage of route meta and other data.
*
* @author zhilong <a href="mailto:[email protected]">Contact me.</a>
* @version 1.0
* @since 2017/2/23 下午1:39
*/
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();
}
}
很簡單,定義了幾個靜态map,在初始化的時候來存放之前的注解生成的那些相關資訊。初始化裡面存的就是所有group索引的map,所有攔截器(本文不講)索引的map,所有provider索引的map。至此,之前的那些注解類裡面的資訊都被存儲起來了,這樣後續在查找的時候就很友善可以找到對應的類,我們繼續看初始化之後的afterInit方法:
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
我們跟蹤之後,發現最終會調用LogisticsCenter中的completion方法:
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should completion by this method.
*/
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
Map<String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
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()));
}
// Save params name which need autoinject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must be implememt IProvider
Class<? extends IProvider> providerMeta = (Class<? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // There's no instance of this provider
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
這個方法有點長,不過我們可以看到,核心功能就是postcard的資訊完善。postcard就是整個路由過程中的信使,類似于生活中的明信片功能,包含了路由所有需要的資訊。通過第34行的遞歸調用,根據groupsIndex和providersIndex保證了Warehouse裡面的另外兩個靜态map(routes,providers)的指派,這樣最終都會走到36行else分支,去保證所有路由資訊的完整性,另外swtich…case裡面的postcard.greenChannel()其實是activity跳轉專用的,目的是用來攔截activity跳轉,來對跳轉過程進行幹預,在之前或者之後做一些自己的處理,是以greenChannel就是綠色通道,不進行攔截。另外代碼裡面也可以看到,類的生成都是采用getConstructor().newInstance()這種反射來進行的,最終調用:
ModulePersonalService service = (ModulePersonalService) ARouter.getInstance().build("/Personal/service").navigation();
得到這個跨子產品服務之後,裡面的所有方法都可以去調用來實作功能需求了。
調用過程
Activity的跳轉如下:
ARouter.getInstance().build("/Personal/main").navigation(activity);
最終調用代碼則是_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()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set 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);
}
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
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) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
}
可以看到對Activity的處理,最終就是調用startActivity方法,對provider就是傳回一個類的執行個體,而BOARDCAST、CONTENT_PROVIDER、FRAGMENT也都是生成一個執行個體傳回,對于METHOD、SERVICE暫時是沒有處理的。
攔截器和自動注入的功能,本文沒有去分析,一般跳到某一個頁面需要判斷是否登陸的時候,可以使用攔截器,自動注入可以在頁面間傳遞資料,非常友善。
原文連結:
https://blog.csdn.net/binbinqq86/article/details/80927885