Android路由框架ARouter分析
一、路由方案
原生的路由方案缺點:
顯式:直接的類依賴,耦合嚴重
隱式:規(guī)則集中式管理,協(xié)作困難
Manifest擴展性較差
跳轉(zhuǎn)過程無法控制
失敗無法降級
ARouter的優(yōu)勢:
使用注解,實現(xiàn)了映射關(guān)系自動注冊 與 分布式路由管理
編譯期間處理注解,并生成映射文件,沒有使用反射,不影響運行時性能
映射關(guān)系按組分類、多級管理,按需初始化
靈活的降級策略,每次跳轉(zhuǎn)都會回調(diào)跳轉(zhuǎn)結(jié)果,避免StartActivity()一旦失敗將會拋出運營級異常
自定義攔截器,自定義攔截順序,可以對路由進行攔截,比如登錄判斷和埋點處理
支持依賴注入,可單獨作為依賴注入框架使用,從而實現(xiàn) 跨模塊API調(diào)用
支持直接解析標(biāo)準(zhǔn)URL進行跳轉(zhuǎn),并自動注入?yún)?shù)到目標(biāo)頁面中
支持獲取Fragment
支持多模塊使用,支持組件化開發(fā)
…….
這么多好處,是時候來了解一下 ARouter 了。
二、ARouter框架

上圖是根據(jù) ARouter 一次基本的路由導(dǎo)航過程,整理的基本框架圖,涉及到主要流程,下面進行詳細介紹。
三、路由管理
1.注冊
通過注解,在編譯時收集使用了注解的類或變量并經(jīng)過Android Process Tool處理進行統(tǒng)一管理。
包含三種注解@Autowired,@Interceptor,@Route。
@Route
注解定義
String path();//路徑URL字符串 String group() default "";//組名,默認為一級路徑名;一旦被設(shè)置,跳轉(zhuǎn)時必須賦值 String name() default "undefined";//該路徑的名稱,用于產(chǎn)生JavaDoc int extras() default Integer.MIN_VALUE;//額外配置的開關(guān)信息;譬如某些頁面是否需要網(wǎng)絡(luò)校驗、登錄校驗等 int priority() default -1;//該路徑的優(yōu)先級
實現(xiàn) @Route 注解
BlankFragment @Route(path = "/test/fragment") Test1Activity @Route(path = "/test/activity1")
該注解主要用于描述路由中的路徑URL信息,使用該注解標(biāo)注的類將被自動添加至路由表中。
@Autowired
注解定義
boolean required() default false; String desc() default "No desc.";
實現(xiàn) @Autowired 注解
@Autowired int age = 10; @Autowired HelloService helloService;
該注解是在頁面跳轉(zhuǎn)時參數(shù)傳遞用的。目標(biāo)Class中使用該注解標(biāo)志的變量,會在頁面被路由打開的時候,在調(diào)用 inject() 后自動賦予傳遞的參數(shù)值。
@Interceptor
注解定義
int priority();//該攔截器的優(yōu)先級 String name() default "Default";//該攔截器的名稱,用于產(chǎn)生JavaDoc
實現(xiàn) @Interceptor 注解
一般應(yīng)用于IInterceptor的實現(xiàn)類,是路由跳轉(zhuǎn)過程中的攔截器,不分module,應(yīng)用全局。
@Interceptor(priority = 7)
public class Test1Interceptor implements IInterceptor {
@Override
public void process(final Postcard postcard, final InterceptorCallback callback) {
............
}
}
2.收集
在編譯期間自動生成映射文件,arouter-compiler實現(xiàn)了一些注解處理器,目標(biāo)在于生成映射文件與輔助文件。

三種類型的注解處理器,都實現(xiàn)了 AbstractProcessor ,主要功能如下:
首先通過注解處理器掃出被標(biāo)注的類文件
按照不同種類的源文件進行分類
按照固定的命名格式生成映射文件
這樣就可以在運行期初始化的時候通過固定的包名來加載映射文件。
關(guān)于注解處理的源碼詳解見 阿里路由框架--ARouter 源碼解析之Compiler 。

以官方demo為例,通過注解處理器,按照固定的命名格式生成映射文件。
具體以 ARouter$$Root$$app 為例,看下注解處理器生成的類文件的內(nèi)容:
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("service", ARouter$$Group$$service.class);
routes.put("test", ARouter$$Group$$test.class);
}
}
通過調(diào)用 loadInto() 方法將其管理的 group 類文件加載到集合中,方便后續(xù)路由查找。
3.加載
前面的收集都是在編譯器處理獲得的,那么加載就是到了運行期。 ARouter 為了避免內(nèi)存和性能損耗,提出了“分組管理,按需加載”的方式。在前面的編譯處理的過程中,已經(jīng)按照不同種類生成對應(yīng)的映射文件。
以官方demo為示例,一個app模塊有一個Root結(jié)點,管理各個Group分組,每個Group分組下有著多個界面;此外app模塊下還有著Interceptor結(jié)點,以及provider結(jié)點。
其中Interceptor結(jié)點對應(yīng)于自定義的攔截器,provider結(jié)點對應(yīng)于IOC,以實現(xiàn)跨模塊API調(diào)用。
ARouter 在初始化的時候只會一次性地加載所有的root結(jié)點,而不會加載任何一個Group結(jié)點,這樣就會極大地降低初始化時加載結(jié)點的數(shù)量。當(dāng)某一個分組下的某一個頁面第一次被訪問的時候,整個分組的全部頁面都會被加載進去。
初始加載
ARouter 其實是一個代理類,它的所有函數(shù)實現(xiàn)都交給 _ARouter 去實現(xiàn),兩個都是單例模式。
public static void init(Application application) {//靜態(tài)函數(shù)進行初始化,不依賴對象
if (!hasInit) {
logger = _ARouter.logger; //持有 日志打印的 全局靜態(tài)標(biāo)量
_ARouter.logger.info(Consts.TAG, "ARouter init start.");//打印 ARouter初始化日志
hasInit = _ARouter.init(application);//移交 _ARouter去 初始化
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");//打印 ARouter初始化日志
}
}
繼續(xù)看一下 _ARouter 的初始化方法
protected static synchronized boolean init(Application application) {
mContext = application;// Application的上下文
LogisticsCenter.init(mContext, executor);//移交邏輯中心進行初始化,并傳入線城池對象
logger.info(Consts.TAG, "ARouter init success!");//打印日志
hasInit = true;//標(biāo)示是否初始化完成
// It's not a good idea.
// if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
// application.registerActivityLifecycleCallbacks(new AutowiredLifecycleCallback());
// }
return true;
}
繼續(xù)往下走,看 LogisticsCenter 的初始化方法
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context; //靜態(tài)持有Application的上下文
executor = tpe;//靜態(tài)持有 線城池
try {
// These class was generate by arouter-compiler.
// 通過指定包名com.alibaba.android.arouter.routes,找到所有 編譯期產(chǎn)生的routes目錄下的類名(不包含裝載類)
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
for (String className : classFileNames) {//組別列表com.alibaba.android.arouter.routes.ARouter\$\$Root
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)) {//模塊內(nèi)的攔截器列表com.alibaba.android.arouter.routes.ARouter\$\$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)) {//IOC的動作路由列表com.alibaba.android.arouter.routes.ARouter\$\$Providers
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
通過上述代碼,實現(xiàn)了“分組管理,按需加載”的方式,加載了對應(yīng)的三個注解處理器生成的類中管理的結(jié)點到路由集合中。

其中內(nèi)存?zhèn)}庫 Warehouse 緩存了全局應(yīng)用的組別的清單列表、IOC的動作路由清單列表、模塊內(nèi)的攔截器清單列表,3個map對象。
class Warehouse {
// Cache route and metas
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();//組別的列表 包含了組名與對應(yīng)組內(nèi)的路由清單列表Class的映射關(guān)系
static Map<String, RouteMeta> routes = new HashMap<>();//組內(nèi)的路由列表 包含了對應(yīng)分組下的,路由URL與目標(biāo)對象Class的映射關(guān)系
// Cache provider
static Map<Class, IProvider> providers = new HashMap<>(); //緩存IOC 目標(biāo)class與已經(jīng)創(chuàng)建了的對象
static Map<String, RouteMeta> providersIndex = new HashMap<>();//IOC 的動作路由列表包含了使用依賴注入方式的某class的 路由URL 與class映射關(guān)系
// Cache interceptor
//模塊內(nèi)的攔截器列表 包含了某個模塊下的攔截器 與 優(yōu)先級的映射關(guān)系
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();//已排序的攔截器實例對象
}
四、路由查找
ARouter.getInstance().build("/test/activity2").navigation();</pre>
以上述例子為例,看一下 ARouter 路由查找的過程。首先看一下 build 過程
1. build()
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
其使用了代理類_ARouter的build()并構(gòu)建和返回PostCard對象。 一個Postcard對象就對應(yīng)了一次路由請求,作用于本次路由全過程。
這部分代碼主要包含兩個部分:
- 使用 IOC byType()方式尋找PathReplaceService.class接口的實現(xiàn)類,該實現(xiàn)類的作用就是實現(xiàn) “運行期動態(tài)修改路由”。
- 繼續(xù)進行本次路由導(dǎo)航
首先來看一下PathReplaceService.class接口:
public interface PathReplaceService extends IProvider {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path);
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri);
}
主要包含forString()和forUri兩個方法,針對路徑進行預(yù)處理,實現(xiàn) “運行期動態(tài)修改路由”。
接下下,繼續(xù)通過build(path, extractGroup(path))進行路由導(dǎo)航,其中extractGroup()是從路徑中獲取默認的分組信息。
然后build()方法會返回一個Postcard對象,并把對應(yīng)的路徑和分組信息傳入該對象。
分析完上面的過程,下面來詳細看下PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);中的navigation()方法,該方法實際調(diào)用了代理類_ARouter的navigation(Class<? extends T> service)方法。
2. navigation(Class<? extends T> service)
protected <T> T navigation(Class<? extends T> service) {
try {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
if (null == postcard) { // No service, or this service in old version.
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}
首先 LogisticsCenter.buildProvider(service.getName()) 根據(jù) Warehouse 保存的 providersIndex 的信息查找并構(gòu)建返回一個 PostCard 對象
然后執(zhí)行 LogisticsCenter.completion(postcard) ,該方法會根據(jù) Warehouse 保存的 routes 的路由信息完善postcard對象,該方法在下面還會出現(xiàn),到時候具體介紹
再回到上文介紹 ARouter.getInstance().build("/test/activity2").navigation() ,返回 PostCard 對象后,開始調(diào)用對應(yīng)的 navigation() 方法。
3. navigation()
觀察 PostCard 中的該方法
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
public void navigation(Activity mContext, int requestCode) {
navigation(mContext, requestCode, null);
}
public void navigation(Activity mContext, int requestCode, NavigationCallback callback) {
ARouter.getInstance().navigation(mContext, this, requestCode, callback);
}
最終調(diào)用了 ARouter 中的 navigation() 方法,在其中其實是調(diào)用了 _ARouter 中的 navigation() 方法。
該方法包含查找回調(diào)的調(diào)用、降級處理、攔截器處理具體路由操作。
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
if (debuggable()) { // Show friendly tips for user.
Toast.makeText(mContext, "There's no route matched!\n" +
" Path = [" + postcard.getPath() + "]\n" +
" Group = [" + postcard.getGroup() + "]", Toast.LENGTH_LONG).show();
}
if (null != callback) {
callback.onLost(postcard);//觸發(fā)路由查找失敗
} else { // No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
//找到了路由元信息,觸發(fā)路由查找的回調(diào)
if (null != callback) {
callback.onFound(postcard);
}
//綠色通道校驗 需要攔截處理
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
//調(diào)用攔截器截面控制器,遍歷內(nèi)存?zhèn)}庫的自定義攔截器,并在異步線程中執(zhí)行攔截函數(shù)
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(context, postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, "Navigation failed, termination by interceptor : " + exception.getMessage());
}
});
} else {
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
其中最重要的兩個方法就是 LogisticsCenter.completion() 和 _navigation() ,下面詳細介紹。
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
//根據(jù)路徑URL獲取到路徑元信息
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
//可能沒加載組內(nèi)清單路徑,從組別的清單列表拿到對應(yīng)組
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 {
//將該組的組內(nèi)清單列表加入到內(nèi)存?zhèn)}庫中,并把組別移除
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); // 再次觸發(fā)完善邏輯
}
} else {
postcard.setDestination(routeMeta.getDestination());//目標(biāo) class
postcard.setType(routeMeta.getType());//路由類
postcard.setPriority(routeMeta.getPriority());//路由優(yōu)先級
postcard.setExtra(routeMeta.getExtra());//額外的配置開關(guān)信息
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 auto inject.
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 implement 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 ,來實現(xiàn)一次路由導(dǎo)航。
接下來介紹另一個方法 _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://如果是Acitvity,則實現(xiàn)Intent跳轉(zhuǎn)
// 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 ((-1 != postcard.getEnterAnim() && -1 != 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://如果是IOC,則返回目標(biāo)對象實例
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT://如果是Fragment,則返回實例,并填充bundle
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;
}
至此我們就完成了一次路由跳轉(zhuǎn)。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
android H5本地緩存加載優(yōu)化的實戰(zhàn)
這篇文章主要介紹了android H5本地緩存加載優(yōu)化的實戰(zhàn),幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-04-04
開箱即用的Google與百度定位坐標(biāo)系轉(zhuǎn)換實例
這篇文章主要為大家介紹了開箱即用的Google與百度定位坐標(biāo)系轉(zhuǎn)換實例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
Android O實現(xiàn)Framework層CENTER鍵長按功能方法
這篇文章主要為大家介紹了Android O實現(xiàn)Framework層CENTER鍵長按功能方法詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪2022-08-08
Android編程開發(fā)之性能優(yōu)化技巧總結(jié)
這篇文章主要介紹了Android編程開發(fā)之性能優(yōu)化技巧,較為詳細的總結(jié)了Android編程中關(guān)于性能優(yōu)化的常用技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-11-11
Android實現(xiàn)從底部彈出的Dialog示例(一)
這篇文章主要介紹了Android實現(xiàn)從底部彈出的Dialog示例(一),具有一定的參考價值,感興趣的小伙伴們可以參考一下。2017-01-01

