今天我們接著來拆下ARouter的攔截器溪食,這個(gè)是ARouter路由框架的第六篇分享了。在Android系統(tǒng)自帶的startActivity的方法調(diào)用后迅办,我們是沒有辦法在跳轉(zhuǎn)的中間過程插入一些其它處理的力惯,ARouter中的攔截器就實(shí)現(xiàn)了這種功能,可以在跳轉(zhuǎn)過程中添加自定義的功能南捂,比如添加攜帶參數(shù),判斷是否需要登錄等旧找,是針對AOP切面編程思想的實(shí)現(xiàn)溺健。
今天攔截器的分享會涉及到多線程
,APT
等技術(shù),有一些反復(fù)的內(nèi)容在前面的系列文章說過了钮蛛,比如ARouter單例初始化過程鞭缭,PostCard等內(nèi)容,這里就不再贅述了魏颓。今天我們從下面幾個(gè)方面來解析攔截器:
1.攔截器的注冊
2.攔截器的初始化
3.攔截器的攔截過程源碼分析
開始之前我們先看下官方Demo中攔截器的實(shí)現(xiàn)效果:
點(diǎn)擊攔截岭辣,會彈出攔截提示框點(diǎn)擊加點(diǎn)料就會攔截,在攔截器中添加參數(shù):
好了甸饱,開始我們的攔截器之旅吧~~~
1.攔截器注冊
攔截器使用和Activity@Route
差不多沦童,只不過攔截器是使用另外一個(gè)注解@Interceptor
仑濒。有兩個(gè)變量,priority是攔截器的優(yōu)先級偷遗,值越小優(yōu)先級越高墩瞳,會優(yōu)先攔截。name是攔截器的名稱氏豌,開發(fā)也不怎么用喉酌。
@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";
}
攔截器都默認(rèn)實(shí)現(xiàn)IInterceptor
接口:
public interface IInterceptor extends IProvider {
/**
* The operation of this interceptor.
*
* @param postcard meta
* @param callback cb
*/
void process(Postcard postcard, InterceptorCallback callback);
}
在攔截器實(shí)現(xiàn)類使用注解@Interceptor
會自動(dòng)注冊,就是在編譯期間會自動(dòng)生成映射關(guān)系類泵喘,使用到的就是APT技術(shù)泪电,不了解的小伙伴可參考Android模塊開發(fā)之APT技術(shù).
這里因?yàn)槭亲詣?dòng)注冊的,所以可以將不同功能的攔截器放在不同功能的模塊中纪铺,只有模塊被打包到整個(gè)項(xiàng)目中相速,因?yàn)樽詣?dòng)注冊機(jī)制所以攔截器就會生效,如果不將這些攔截器放到模塊并打包到項(xiàng)目中鲜锚,那就不會生效和蚪,這樣就不用去做很多注冊與反注冊的工作,這也是ARouter適用于模塊開發(fā)的原因之一烹棉。
看看自動(dòng)生成的映射關(guān)系類是怎樣,官方Demo提供了兩個(gè)攔截器,Test1Interceptor
攔截器在app模塊中怯疤,TestInterceptor90
攔截器在test-module-1中浆洗。下面文件路徑ARouter/app/build/generated/source/apt/debug/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$app.java
public class ARouter$$Interceptors$$app implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(7, Test1Interceptor.class);
}
}
下面文件路徑:ARouter/test-module-1/build/generated/source/apt/release/com/alibaba/android/arouter/routes/ARouter$$Interceptors$$testmodule1.java
public class ARouter$$Interceptors$$testmodule1 implements IInterceptorGroup {
@Override
public void loadInto(Map<Integer, Class<? extends IInterceptor>> interceptors) {
interceptors.put(90, TestInterceptor90.class);
}
}
生成的映射關(guān)系類就實(shí)現(xiàn)一個(gè)功能,將攔截器實(shí)現(xiàn)類加載緩存集峦。接下來我們看看攔截器在什么時(shí)候加載進(jìn)緩存伏社,也就是初始化。
2.攔截器初始化
因?yàn)閿r截器是在每次跳轉(zhuǎn)中間都需要判斷的塔淤,所以在ARouter初始化的時(shí)候就會加載進(jìn)來摘昌,初始化過程肯定需要和編譯期間生成的映射關(guān)系類打交道,所以邏輯自然就放在LogisticsCenter
中高蜂。我們看看初始化的源碼,和攔截器無關(guān)的邏輯我做了刪減聪黎,為了看起來方便點(diǎn)。
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
try {
// These class was generate by arouter-compiler.
List<String> classFileNames = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
for (String className : classFileNames) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
}
}
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() + "]");
}
}
可以看出來备恤,攔截器映射關(guān)系會加載到倉庫的mapWarehouse.interceptorsIndex
中稿饰,要分清楚這個(gè)時(shí)候只是代碼緩存中知道了有框架中有哪些攔截器,但是還沒初始化露泊。
我在這里下了個(gè)斷點(diǎn)喉镰,看看有幾個(gè)攔截器。
可以看出來有兩個(gè)映射文件惭笑,這個(gè)和前面注冊的分析是一樣的侣姆。
再看看加載到倉庫中的是不是兩個(gè)生真,可以看出來確實(shí)是兩個(gè),好厲害:)
到這里只是找到了攔截器捺宗,還沒有進(jìn)行初始化柱蟀,我們接著看源碼,在_ARouter.init
就是主要工作就是調(diào)用LogisticsCenter
進(jìn)行初始化,這里就是上面的分析工程,然后會調(diào)用_ARouter.afterInit()
偿凭。
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.afterInit()
,獲取interceptorService
产弹。
static void afterInit() {
// Trigger interceptor init, use byName.
interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation();
}
在獲取interceptorService
過程中會在LogisticsCenter
中實(shí)例化interceptorService
實(shí)現(xiàn)類InterceptorServiceImpl
后調(diào)用init(Context)
方法。
switch (routeMeta.getType()) {
case PROVIDER:
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;
}
我們接著跟到init方法中,可以看出弯囊,就是從倉庫Warehouse.interceptorsIndex
中取出攔截器然后進(jìn)行實(shí)例化痰哨,接著將實(shí)例對象添加到Warehouse.interceptors
中。
@Override
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehouse.interceptors.add(iInterceptor);
} catch (Exception ex) {
throw new HandlerException(TAG + "ARouter init interceptor error! name = [" + interceptorClass.getName() + "], reason = [" + ex.getMessage() + "]");
}
}
interceptorHasInit = true;
logger.info(TAG, "ARouter interceptors init over.");
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
}
}
});
}
到這里攔截器的初始化過程就比較清楚了匾嘱,有一點(diǎn)需要知道斤斧,就是攔截器怎么根據(jù)優(yōu)先級進(jìn)行攔截?原理就在倉庫的兩個(gè)緩存map中霎烙,一個(gè)就是存儲攔截器類的Warehouse.interceptorsIndex
,另外一個(gè)就是存儲攔截器實(shí)例的Warehouse.interceptors
.我們看下這兩個(gè)類型:
// 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<>();
其中interceptorsIndex
的類型是TreeMap
,原理是紅黑樹撬讽,可以實(shí)現(xiàn)按順序存儲,所以我們攔截器就可以按照優(yōu)先級存儲在這個(gè)緩存中悬垃。實(shí)例化的時(shí)候也是按照優(yōu)先級取出游昼,然后實(shí)例化存儲到ArrayList
中,優(yōu)先級值越小就在隊(duì)列中越靠前尝蠕,這就是攔截器優(yōu)先級的原理烘豌。細(xì)節(jié)處見功夫啊看彼!
接下來就看看攔截器是怎么攔截的~~~
3.攔截器的攔截過程源碼分析
攔截是在跳轉(zhuǎn)之前攔截廊佩,所以我們到_ARouter中的navigation
去看,如果需要攔截就通過interceptorService
進(jìn)行攔截,不需要攔截就直接跳轉(zhuǎn)靖榕。
if (!postcard.isGreenChannel()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
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);
}
interceptorService
是InterceptorServiceImpl
的實(shí)例對象.
@Route(path = "/arouter/service/interceptor")
public class InterceptorServiceImpl implements InterceptorService
我們接著看攔截方法:
1.首先看下
Warehouse.interceptors
是否有攔截器标锄,如果沒有就直接調(diào)用回調(diào)接口跳轉(zhuǎn);有攔截器就進(jìn)行后面步驟茁计。
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
//1.是否有攔截器
if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) {
//2.檢查攔截器初始化情況
checkInterceptorsInitStatus();
if (!interceptorHasInit) {//未初始化就拋異常
callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
return;
}
//3.攔截
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_excute(0, interceptorCounter, postcard);
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn't return anythings.
callback.onInterrupt(new HandlerException("The interceptor processing timed out."));
} else if (null != postcard.getTag()) { // Maybe some exception in the tag.
callback.onInterrupt(new HandlerException(postcard.getTag().toString()));
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
2.首先檢查攔截器的初始化情況
checkInterceptorsInitStatus
料皇,這是個(gè)同步方法,攔截器未初始化就會等待一定時(shí)間星压,超時(shí)就拋出錯(cuò)誤瓶蝴。在前面init
方法是在線程池中異步執(zhí)行的,中如果初始化成功就會釋放鎖interceptorInitLock
租幕。為什么要異步舷手?因?yàn)閿r截器可能數(shù)目比較多或者初始化比較耗時(shí)。
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
接著看看checkInterceptorsInitStatus
方法劲绪,當(dāng)攔截器還沒初始化完成男窟,這里就在鎖上同步等待時(shí)間10 * 1000盆赤,超時(shí)還未獲取到鎖就會報(bào)錯(cuò)。
private static void checkInterceptorsInitStatus() {
synchronized (interceptorInitLock) {
while (!interceptorHasInit) {
try {
interceptorInitLock.wait(10 * 1000);
} catch (InterruptedException e) {
throw new HandlerException(TAG + "Interceptor init cost too much time error! reason = [" + e.getMessage() + "]");
}
}
}
}
攔截器都初始化成功后歉眷,來到第三步攔截實(shí)現(xiàn)牺六,攔截也是在線程池中異步執(zhí)行,同上面init的原因一樣汗捡。
3.攔截過程就在_excute方法中實(shí)現(xiàn),這個(gè)我們在第四步中再分析淑际。攔截器不可能無限時(shí)長攔截吧,那么怎么實(shí)現(xiàn)扇住?就是通過Java提供的異步工具
CountDownLatch
.在等待interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);后進(jìn)行判斷interceptorCounter.getCount是否等于0或者postcard.getTag是否是空春缕,如果滿足就是攔截出現(xiàn)問題;否則就繼續(xù)放行艘蹋。
接著看看攔截的真正實(shí)現(xiàn)邏輯:
3.攔截真正過程在_execute中, 邏輯比較簡單锄贼,就是依次取出攔截器實(shí)例,然后調(diào)用process方法女阀,傳入回調(diào)接口
InterceptorCallback
宅荤,當(dāng)前攔截器處理完畢回調(diào)onContinue,在這里面遞減counter.countDown浸策,然后取出下一個(gè)攔截器循環(huán)上面過程冯键。
private static void _excute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
_excute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}
@Override
public void onInterrupt(Throwable exception) {
// Last interceptor excute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // save the exception message for backup.
counter.cancel();
}
});
}
}
攔截器的實(shí)現(xiàn)流程就是以上。接著我們挑一個(gè)攔截器Test1Interceptor的實(shí)現(xiàn)看看庸汗。這個(gè)就是上面Demo演示的彈框攔截器惫确,如果選擇選擇“加點(diǎn)料”,就會添加參數(shù)夫晌,然后調(diào)用回調(diào)接口的callback.onContinue,繼續(xù)到下一個(gè)攔截器昧诱。
@Override
public void process(final Postcard postcard, final InterceptorCallback callback) {
if ("/test/activity4".equals(postcard.getPath())) {
final AlertDialog.Builder ab = new AlertDialog.Builder(MainActivity.getThis());
ab.setCancelable(false);
ab.setTitle("溫馨提醒");
ab.setMessage("想要跳轉(zhuǎn)到Test4Activity么晓淀?(觸發(fā)了\"/inter/test1\"攔截器,攔截了本次跳轉(zhuǎn))");
ab.setNegativeButton("繼續(xù)", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callback.onContinue(postcard);
}
});
ab.setNeutralButton("算了", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
callback.onInterrupt(null);
}
});
ab.setPositiveButton("加點(diǎn)料", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
postcard.withString("extra", "我是在攔截器中附加的參數(shù)");
callback.onContinue(postcard);
}
});
MainLooper.runOnUiThread(new Runnable() {
@Override
public void run() {
ab.create().show();
}
});
} else {
callback.onContinue(postcard);
}
}
4.總結(jié)
今天的攔截器內(nèi)容會比較多一點(diǎn)盏档,攔截器的注冊和其它的activity或者service是差不多的凶掰,主要是注解不同。攔截器的初始化過程是在線程池中進(jìn)行蜈亩,為了是攔截器可能耗時(shí)的問題懦窘。攔截器的攔截過程其實(shí)就是在線程池中從倉庫里依次取出攔截器實(shí)例進(jìn)行攔截,稍微難一點(diǎn)的地方就是在多線程的同步問題上稚配,用的還是Java的多線程技術(shù)畅涂。
有需要的小伙伴可以看下前面的系列分享:
ARouter解析一:基本使用及頁面注冊源碼解析
ARouter解析二:頁面跳轉(zhuǎn)源碼分析
ARouter解析三:URL跳轉(zhuǎn)本地頁面源碼分析
ARouter解析四:發(fā)現(xiàn)服務(wù)和Fragment
ARouter解析五:IoC與依賴注入
今天的攔截器的內(nèi)容就是這樣,希望對大家有點(diǎn)幫助道川,謝謝午衰!
歡迎關(guān)注公眾號:JueCode