做过组件化开发的小伙伴应该都比较了解Arouter使用,那么Arouter的拦截器就更不用说了,一般用拦截器作用很多,比如在跳转之前做一些额外的操作(经典用法检查是否登陆,没登陆跳到登陆界面,实现一个拦截器也很简单,加一条注解就ok:@Interceptor(priority = 7),priority代表的是优先级。既然加一条注解以后每次跳转都会回调到process方法,如下:
- public class Interceptor1 implements IInterceptor {
- /**
- * The operation of this interceptor.
- *
- * @param postcard meta
- * @param callback cb
- */
- @Override
- public void process(final Postcard postcard, final InterceptorCallback callback){}
那么肯定有用编译期注解的方式,如下:
- annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
- compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
在android中如果想在编译期根据注解产生新的java类的话,那么:auto-service是必不可少,然后你继承AbstractProcessor即可实现效果,这里处理拦截器的注解类的是InterceptorProcessor,实现处理的方法如下:
- public boolean process(Set extends TypeElement> annotations, RoundEnvironment roundEnv) {
- if (CollectionUtils.isNotEmpty(annotations)) {
- Set extends Element> elements = roundEnv.getElementsAnnotatedWith(Interceptor.class);
- try {
- parseInterceptors(elements);
- } catch (Exception e) {
- logger.error(e);
- }
- return true;
- }
-
- return false;
- }
真正实现方法如下:
- private void parseInterceptors(Set extends Element> elements) throws IOException {
- if (CollectionUtils.isNotEmpty(elements)) {
- logger.info(">>> Found interceptors, size is " + elements.size() + " <<<");
-
- // Verify and cache, sort incidentally.
- for (Element element : elements) {
- if (verify(element)) { // Check the interceptor meta
- logger.info("A interceptor verify over, its " + element.asType());
- Interceptor interceptor = element.getAnnotation(Interceptor.class);
-
- Element lastInterceptor = interceptors.get(interceptor.priority());
- if (null != lastInterceptor) { // Added, throw exceptions
- throw new IllegalArgumentException(
- String.format(Locale.getDefault(), "More than one interceptors use same priority [%d], They are [%s] and [%s].",
- interceptor.priority(),
- lastInterceptor.getSimpleName(),
- element.getSimpleName())
- );
- }
-
- interceptors.put(interceptor.priority(), element);
- } else {
- logger.error("A interceptor verify failed, its " + element.asType());
- }
- }
-
- // Interface of ARouter.
- TypeElement type_IInterceptor = elementUtils.getTypeElement(IINTERCEPTOR);
- TypeElement type_IInterceptorGroup = elementUtils.getTypeElement(IINTERCEPTOR_GROUP);
-
- /**
- * Build input type, format as :
- *
- * ```Map
>``` - */
- ParameterizedTypeName inputMapTypeOfTollgate = ParameterizedTypeName.get(
- ClassName.get(Map.class),
- ClassName.get(Integer.class),
- ParameterizedTypeName.get(
- ClassName.get(Class.class),
- WildcardTypeName.subtypeOf(ClassName.get(type_IInterceptor))
- )
- );
-
- // Build input param name.
- ParameterSpec tollgateParamSpec = ParameterSpec.builder(inputMapTypeOfTollgate, "interceptors").build();
-
- // Build method : 'loadInto'
- MethodSpec.Builder loadIntoMethodOfTollgateBuilder = MethodSpec.methodBuilder(METHOD_LOAD_INTO)
- .addAnnotation(Override.class)
- .addModifiers(PUBLIC)
- .addParameter(tollgateParamSpec);
-
- // Generate
- if (null != interceptors && interceptors.size() > 0) {
- // Build method body
- for (Map.Entry
entry : interceptors.entrySet()) { - loadIntoMethodOfTollgateBuilder.addStatement("interceptors.put(" + entry.getKey() + ", $T.class)", ClassName.get((TypeElement) entry.getValue()));
- }
- }
-
- // Write to disk(Write file even interceptors is empty.)
- JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
- TypeSpec.classBuilder(NAME_OF_INTERCEPTOR + SEPARATOR + moduleName)
- .addModifiers(PUBLIC)
- .addJavadoc(WARNING_TIPS)
- .addMethod(loadIntoMethodOfTollgateBuilder.build())
- .addSuperinterface(ClassName.get(type_IInterceptorGroup))
- .build()
- ).build().writeTo(mFiler);
-
- logger.info(">>> Interceptor group write over. <<<");
- }
- }
这个方法的意思就是取得Interceptor所有注解,然后将优先级做为key,存到TreeMap中,因为key是Integer类型实现了Comparable接口,所以TreeMap是根据优先级排序的,排好序之后,用javaPoet去生成ARouter
这个拦截器什么时候被使用的呢?当然是在跳转前面,如下:
- */
- protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
- //去掉无用代码......
-
- 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(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(postcard, requestCode, callback);
- }
-
- return null;
- }
可以看到最终拦截器是通过interceptorService类调用,它的实现类是InterceptorServiceImpl,实现方法如下:
- public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
- if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
-
- checkInterceptorsInitStatus();
-
- if (!interceptorHasInit) {
- callback.onInterrupt(new HandlerException("Interceptors initialization takes too much time."));
- return;
- }
-
- LogisticsCenter.executor.execute(new Runnable() {
- @Override
- public void run() {
- CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
- try {
- _execute(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((Throwable) postcard.getTag());
- } else {
- callback.onContinue(postcard);
- }
- } catch (Exception e) {
- callback.onInterrupt(e);
- }
- }
- });
- } else {
- callback.onContinue(postcard);
- }
- }
- private static void _execute(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();
- _execute(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 execute over with fatal exception.
-
- postcard.setTag(null == exception ? new HandlerException("No message.") : exception); // save the exception message for backup.
- counter.cancel();
- // Be attention, maybe the thread in callback has been changed,
- // then the catch block(L207) will be invalid.
- // The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
- // if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn't throw the exception if the thread is main thread.
- // throw new HandlerException(exception.getMessage());
- // }
- }
- });
- }
- }
_execute在内部一直调用的递归,遍历map中所有而编译器用的TreeMap保证了优先级,好了到这拦截器的流程基本走完了,那么还有一个问题就是拦截器的集合 Warehouse.interceptors是什么时候填充的呢?当然是ARouter初始化的时候,如下:
- protected static synchronized boolean init(Application application) {
- mContext = application;
- LogisticsCenter.init(mContext, executor);
- logger.info(Consts.TAG, "ARouter init success!");
- hasInit = true;
- mHandler = new Handler(Looper.getMainLooper());
-
- return true;
- }
最终调用LogisticsCenter.init,如下:
- public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
- mContext = context;
- executor = tpe;
-
- try {
- long startInit = System.currentTimeMillis();
- //load by plugin first
- loadRouterMap();
- if (registerByPlugin) {
- logger.info(TAG, "Load router map by arouter-auto-register plugin.");
- } else {
- Set
routerMap; -
- // It will rebuild router map every times when debuggable.
- if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
- logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
- // These class was generated by arouter-compiler.
- routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
- if (!routerMap.isEmpty()) {
- 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 {
- logger.info(TAG, "Load router map from cache.");
- routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet
())); - }
-
- logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
- startInit = System.currentTimeMillis();
-
- for (String className : routerMap) {
- 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);
- }
- }
- }
-
- logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
-
- 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() + "]");
- }
- }
其中getFileNameByPackageName真正实现了class的储存加载,如下代码:
- public static Set
getFileNameByPackageName(Context context, final String packageName) throws PackageManager.NameNotFoundException, IOException, InterruptedException { - final Set
classNames = new HashSet<>(); -
- List
paths = getSourcePaths(context); - final CountDownLatch parserCtl = new CountDownLatch(paths.size());
-
- for (final String path : paths) {
- DefaultPoolExecutor.getInstance().execute(new Runnable() {
- @Override
- public void run() {
- DexFile dexfile = null;
-
- try {
- if (path.endsWith(EXTRACTED_SUFFIX)) {
- //NOT use new DexFile(path), because it will throw "permission error in /data/dalvik-cache"
- dexfile = DexFile.loadDex(path, path + ".tmp", 0);
- } else {
- dexfile = new DexFile(path);
- }
-
- Enumeration
dexEntries = dexfile.entries(); - while (dexEntries.hasMoreElements()) {
- String className = dexEntries.nextElement();
- if (className.startsWith(packageName)) {
- classNames.add(className);
- }
- }
- } catch (Throwable ignore) {
- Log.e("ARouter", "Scan map file in dex files made error.", ignore);
- } finally {
- if (null != dexfile) {
- try {
- dexfile.close();
- } catch (Throwable ignore) {
- }
- }
-
- parserCtl.countDown();
- }
- }
- });
- }
-
- parserCtl.await();
-
- Log.d(Consts.TAG, "Filter " + classNames.size() + " classes by packageName <" + packageName + ">");
- return classNames;
- }
用的DexFile加载所有编译期产生的新java类的class,然后将class反射实例化,实现如下:
- for (String className : routerMap) {
- 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);
- }
- }
- }
最终InterceptorServiceImpl的init方法实现,如下:
- public void init(final Context context) {
- LogisticsCenter.executor.execute(new Runnable() {
- @Override
- public void run() {
- if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
- for (Map.Entry
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();
- }
- }
- }
- });
- }