-
What is ARouter
A framework for assisting in the renovation of Android app componentization
官方介绍只有简短的一句话总结,一套协助Android app组件化革新的框架。
-
Why to use it
即便是组件化,我们其实也可以用startActivity的方式来进行跳转,那ARouter又有何特殊之处呢?
- 传统的方式,不管是url传参还是bundle传参都较为繁琐,需要把参数先设置到url或者bundle,然后在跳转页通过getIntent方法来获取传参,ARouter把这部分工作封装了起来。通过链式调用设置参数,通过Autowired注解获取参数。
- 支持组件化,ARouter的组件化在于不需要module间相互依赖就可以互相跳转。
- 支持interceptor,可以通过interceptor做一些跳转前的逻辑,比如需要登录状态才能访问的页面。
- 支持依赖注入,通过AOP思想把注解映射成路由类。
- 支持Instant Run方式编译的apk。
- 支持MultiDex。
- 支持业务类Service,通过Route和Autowired注解和Service类联系起来。
-
How to use it & Why it work
-
依赖
每一个需要使用ARouter的module下的build.gradle中添加下面配置(可以把这块抽出来,通过apply from: '../xx.gradle'去引用):
//如果含有kotlin代码,需要添加一下kotlin plugin apply plugin: 'kotlin-kapt' //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优 apply plugin: 'com.alibaba.arouter' android { defaultConfig { ... javaCompileOptions { annotationProcessorOptions { arguments = [AROUTER_MODULE_NAME: project.getName()] } } } } //使用ARouter auto-register自动扫描路由信息,非必选,通过gradle注册,性能更优 buildscript { repositories { jcenter() } dependencies { //ARouter auto-register,Replace with the latest versios classpath "com.alibaba:arouter-register:?" } } //如果是包含kotlin类的路由,需要添加下面代码 kapt { arguments { arg("AROUTER_MODULE_NAME", project.getName()) } } dependencies { // Replace with the latest version //使用api继承依赖,不需要每个module都添加 api 'com.alibaba:arouter-api:?' //这个不能使用api依赖继承,所以每个module都要添加 annotationProcessor 'com.alibaba:arouter-compiler:?' //如果是kotlin,需要添加kotlin编译jar包 kapt 'com.alibaba:arouter-compiler:?' ... }
-
两种加载方式
通过Route、Autowired、Interceptor等ARouter注解修饰的类需要被加载到一些Map中,这些Map被放在Warehouse类中:
class Warehouse { // Cache route and metas static Map
> groupsIndex = new HashMap<>(); static Map routes = new HashMap<>(); // Cache provider static Map providers = new HashMap<>(); static Map providersIndex = new HashMap<>(); // Cache interceptor static Map > interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]"); static List interceptors = new ArrayList<>(); static void clear() { routes.clear(); groupsIndex.clear(); providers.clear(); providersIndex.clear(); interceptors.clear(); interceptorsIndex.clear(); } } 加载的方式有两种,一种是通过plugin来利用gradle的构建自动生成加载代码的方式加载,另一种是通过java代码手动扫描出含有这些注解的类,这也是初始化的两种方式,官方提倡使用register-plugin自动生成性能更好。
-
初始化
class MyApp : MultiDexApplication() { override fun onCreate() { super.onCreate() if(BuildConfig.DEBUG){ ARouter.openLog() ARouter.openDebug() } ARouter.init(this) } override fun onTerminate() { super.onTerminate() ARouter.getInstance().destroy() } }
为了app安全考虑,非debug版本下不要输出log和打开debug模式。
看一下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的类去做的,Arouter只是它的包装类。__ARouter的init方法返回hasInit标志是否已成功初始化:
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方法:
/** * 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 { long startInit = System.currentTimeMillis(); //billy.qi modified at 2017-12-06 //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() + "]"); } } 首先调用了loadRouterMap()方法来试图通过arouter-auto-register plugin加载route信息:
/** * arouter-auto-register plugin will generate code inside this method * call this method to register all Routers, Interceptors and Providers * @author billy.qi Contact me. * @since 2017-12-06 */ 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()); }
这个方法里什么也没有,因为这里要交给plugin去注册,默认设置为不通过plugin注册,通过注释可以得知,plugin会把加载逻辑代码动态的生成在这个方法里,至于怎样生成的另起篇文章总结。
下载arouter-register的source包查看:
class RouteMethodVisitor extends MethodVisitor { RouteMethodVisitor(int api, MethodVisitor mv) { super(api, mv) } @Override void visitInsn(int opcode) { //generate code before return if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) { extension.classList.each { name -> name = name.replaceAll("/", ".") mv.visitLdcInsn(name)//类名 // generate invoke register method into LogisticsCenter.loadRouterMap() mv.visitMethodInsn(Opcodes.INVOKESTATIC , ScanSetting.GENERATE_TO_CLASS_NAME , ScanSetting.REGISTER_METHOD_NAME , "(Ljava/lang/String;)V" , false) } } super.visitInsn(opcode) } @Override void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack + 4, maxLocals) } }
mv.visitMethodInsn方法就是注入加载代码的地方,采用循环的方式来加载每一个类。ScanSetting.GENERATE_TO_CLASS_NAME是:
/** * The register code is generated into this class */ static final String GENERATE_TO_CLASS_NAME = 'com/alibaba/android/arouter/core/LogisticsCenter'
ScanSetting.REGISTER_METHOD_NAME是:
/** * register method name in class: {@link #GENERATE_TO_CLASS_NAME} */ static final String REGISTER_METHOD_NAME = 'register'
在arouter-api的jar包中找到LogisticsCenter的register方法:
/** * register by class name * Sacrificing a bit of efficiency to solve * the problem that the main dex file size is too large * @author billy.qi Contact me. * @param className class name */ private static void register(String className) { if (!TextUtils.isEmpty(className)) { try { Class clazz = Class.forName(className); Object obj = clazz.getConstructor().newInstance(); if (obj instanceof IRouteRoot) { registerRouteRoot((IRouteRoot) obj); } else if (obj instanceof IProviderGroup) { registerProvider((IProviderGroup) obj); } else if (obj instanceof IInterceptorGroup) { registerInterceptor((IInterceptorGroup) obj); } else { logger.info(TAG, "register failed, class name: " + className + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup."); } } catch (Exception e) { logger.error(TAG,"register class error:" + className); } } }
三个方法分别是注册Root(页面)、Interceptor、IProvider(Service)的逻辑:
/** * method for arouter-auto-register plugin to register Routers * @param routeRoot IRouteRoot implementation class in the package: com.alibaba.android.arouter.core.routers * @author billy.qi Contact me. * @since 2017-12-06 */ private static void registerRouteRoot(IRouteRoot routeRoot) { markRegisteredByPlugin(); if (routeRoot != null) { routeRoot.loadInto(Warehouse.groupsIndex); } } /** * method for arouter-auto-register plugin to register Interceptors * @param interceptorGroup IInterceptorGroup implementation class in the package: com.alibaba.android.arouter.core.routers * @author billy.qi Contact me. * @since 2017-12-06 */ private static void registerInterceptor(IInterceptorGroup interceptorGroup) { markRegisteredByPlugin(); if (interceptorGroup != null) { interceptorGroup.loadInto(Warehouse.interceptorsIndex); } } /** * method for arouter-auto-register plugin to register Providers * @param providerGroup IProviderGroup implementation class in the package: com.alibaba.android.arouter.core.routers * @author billy.qi Contact me. * @since 2017-12-06 */ private static void registerProvider(IProviderGroup providerGroup) { markRegisteredByPlugin(); if (providerGroup != null) { providerGroup.loadInto(Warehouse.providersIndex); } }
markRegisteredByPlugin是修改registerByPlugin标志位:
/** * mark already registered by arouter-auto-register plugin * @author billy.qi Contact me. * @since 2017-12-06 */ private static void markRegisteredByPlugin() { if (!registerByPlugin) { registerByPlugin = true; } }
可以看到,对于不同类型的route类分别保存到Warehouse中对应的Map中,注意这里添加的只是类,不是跳转的所有信息。
回到init方法,判断registerByPlugin是否为true来决定是否手动加载,如果没有设置register-plugin,则会走到else分支,这里就是手动代码检索路由信息的地方:
if (ARouter.debuggable() || PackageUtils.isNewVersion(context))判断如果是debug模式或者是新版本更新的话就重新检索路由信息,核心代码就是ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE),ROUTE_ROOT_PACKAGE是:
public static final String ROUTE_ROOT_PAKCAGE = "com.alibaba.android.arouter.routes";
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; } 这里的工作就是多线程读取所有的dex文件,逐个判断里面的className,如果className是以com.alibaba.android.arouter.routes开头的则存入classNames集合,最终会返回所有com.alibaba.android.arouter.routes开头的类名。
注意在最前面有一个getSourcePaths方法来获取apk的所有dex文件的位置,然后从这些位置读取dex文件:
/** * get all the dex path * * @param context the application context * @return all the dex path * @throws PackageManager.NameNotFoundException * @throws IOException */ public static List
getSourcePaths(Context context) throws PackageManager.NameNotFoundException, IOException { ApplicationInfo applicationInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), 0); File sourceApk = new File(applicationInfo.sourceDir); List sourcePaths = new ArrayList<>(); sourcePaths.add(applicationInfo.sourceDir); //add the default apk path //the prefix of extracted file, ie: test.classes String extractedFilePrefix = sourceApk.getName() + EXTRACTED_NAME_EXT; // 如果VM已经支持了MultiDex,就不要去Secondary Folder加载 Classesx.zip了,那里已经么有了 // 通过是否存在sp中的multidex.version是不准确的,因为从低版本升级上来的用户,是包含这个sp配置的 if (!isVMMultidexCapable()) { //the total dex numbers int totalDexNumber = getMultiDexPreferences(context).getInt(KEY_DEX_NUMBER, 1); File dexDir = new File(applicationInfo.dataDir, SECONDARY_FOLDER_NAME); for (int secondaryNumber = 2; secondaryNumber <= totalDexNumber; secondaryNumber++) { //for each dex file, ie: test.classes2.zip, test.classes3.zip... String fileName = extractedFilePrefix + secondaryNumber + EXTRACTED_SUFFIX; File extractedFile = new File(dexDir, fileName); if (extractedFile.isFile()) { sourcePaths.add(extractedFile.getAbsolutePath()); //we ignore the verify zip part } else { throw new IOException("Missing extracted secondary dex file '" + extractedFile.getPath() + "'"); } } } if (ARouter.debuggable()) { // Search instant run support only debuggable sourcePaths.addAll(tryLoadInstantRunDexFile(applicationInfo)); } return sourcePaths; } isVMMultidexCapable判断是否JVM支持MultiDex了,就是判断JVM的版本(2.1之后的支持):
/** * Identifies if the current VM has a native support for multidex, meaning there is no need for * additional installation by this library. * * @return true if the VM handles multidex */ private static boolean isVMMultidexCapable() { boolean isMultidexCapable = false; String vmName = null; try { if (isYunOS()) { // YunOS需要特殊判断 vmName = "'YunOS'"; isMultidexCapable = Integer.valueOf(System.getProperty("ro.build.version.sdk")) >= 21; } else { // 非YunOS原生Android vmName = "'Android'"; String versionString = System.getProperty("java.vm.version"); if (versionString != null) { Matcher matcher = Pattern.compile("(\\d+)\\.(\\d+)(\\.\\d+)?").matcher(versionString); if (matcher.matches()) { try { int major = Integer.parseInt(matcher.group(1)); int minor = Integer.parseInt(matcher.group(2)); isMultidexCapable = (major > VM_WITH_MULTIDEX_VERSION_MAJOR) || ((major == VM_WITH_MULTIDEX_VERSION_MAJOR) && (minor >= VM_WITH_MULTIDEX_VERSION_MINOR)); } catch (NumberFormatException ignore) { // let isMultidexCapable be false } } } } } catch (Exception ignore) { } Log.i(Consts.TAG, "VM with name " + vmName + (isMultidexCapable ? " has multidex support" : " does not have multidex support")); return isMultidexCapable; }
取到routeMap之后会把它存到SP中,最后遍历routeMap,根据前缀Root、Interceptors、Providers的不同分别存入Warehouse中的对应xxxIndex的map中,这一步和自动加载是一样的。
至此,初始化工作就完成了。
-
路由名称注意
Route注解指定的路由path中格式为/groupName/elseName...,至少含有两个‘/’且groupName不能为空。因为ARouter会自动生成以groupName为后缀的类,可以在build的generated包下找到。
arouter-api:xxx的这个jar包下的routes包中有三个已经存在的类:
ARouter$$Group$$arouter:
public class ARouter$$Group$$arouter implements IRouteGroup { public ARouter$$Group$$arouter() { } public void loadInto(Map
atlas) { atlas.put("/arouter/service/autowired", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648)); atlas.put("/arouter/service/interceptor", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648)); } } ARouter$$Providers$$arouterapi:
public class ARouter$$Providers$$arouterapi implements IProviderGroup { public ARouter$$Providers$$arouterapi() { } public void loadInto(Map
providers) { providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648)); providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648)); } } ARouter$$Root$$arouterapi:
public class ARouter$$Root$$arouterapi implements IRouteRoot { public ARouter$$Root$$arouterapi() { } public void loadInto(Map
> routes) { routes.put("arouter", arouter.class); } } 在build中还会自动生成ARouter$$Providers$$moduleName、ARouter$$Root$$moduleName,moduleName是模块的名字。以module是app为例,ARouter$$Root$$app为:
public class ARouter$$Root$$app implements IRouteRoot { @Override public void loadInto(Map
> routes) { routes.put("john", ARouter$$Group$$john.class); routes.put("rxseriers", ARouter$$Group$$rxseriers.class); } } 前面分析的初始化中会调用到loadInto方法,这里就会把ARouter$$Group$$john.class和ARouter$$Group$$rxseriers.class保存到Warehouse.groupsIndex中。
而Route注解生成的类的类名格式为ARouter$$Group$$groupName,知道了这些之后,我们可以得出结论,路由中的groupName不能是“arouter”,倘若相同,自动生成的class名就会和routes包下的默认存在的class同名,那么routes.put("arouter", ARouter$$Group$$john.class)传入的class就会被routes中的同名class替代,从而导致找不到自定义的路由类的问题。
-
跳转
Example:
ARouter.getInstance().build(Const.LOGIN_ACTIVITY_PATH).withString("name","John").navigation()
通过单例模式获取ARouter实例:
public static ARouter getInstance() { if (!hasInit) { throw new InitException("ARouter::Init::Invoke init(context) first!"); } else { if (instance == null) { synchronized (ARouter.class) { if (instance == null) { instance = new ARouter(); } } } return instance; } }
可以看到实例化之前必须先初始化。
build有多个重载方法:
/** * Build postcard by path and default group */ 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), true); } } /** * Build postcard by uri */ protected Postcard build(Uri uri) { if (null == uri || TextUtils.isEmpty(uri.toString())) { throw new HandlerException(Consts.TAG + "Parameter invalid!"); } else { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { uri = pService.forUri(uri); } return new Postcard(uri.getPath(), extractGroup(uri.getPath()), uri, null); } } /** * Build postcard by path and group */ protected Postcard build(String path, String group, Boolean afterReplace) { if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) { throw new HandlerException(Consts.TAG + "Parameter is invalid!"); } else { if (!afterReplace) { PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { path = pService.forString(path); } } return new Postcard(path, group); } } }
其实就是两种方式,一个参数是String类型的path,这就是Route注解的path属性的值,另一个参数是Uri类型,这种形式最终使用的是Uri的path值,无论哪种形式,都必须是/groupName/???的格式,extractGroup方法决定了这样:
/** * Extract the default group from path. */ private String extractGroup(String path) { if (TextUtils.isEmpty(path) || !path.startsWith("/")) { throw new HandlerException(Consts.TAG + "Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!"); } try { String defaultGroup = path.substring(1, path.indexOf("/", 1)); if (TextUtils.isEmpty(defaultGroup)) { throw new HandlerException(Consts.TAG + "Extract the default group failed! There's nothing between 2 '/'!"); } else { return defaultGroup; } } catch (Exception e) { logger.warning(Consts.TAG, "Failed to extract default group! " + e.getMessage()); return null; }
所以build之后得到了一个Postcard(明信片)对象,接下来是调用它的navigation方法。
navigation方法同样有多个重载方法,所有可传参数有:Activity mContext, int requestCode, NavigationCallback callback。mContext若不传就是构建时传入的application上下文,requestCode是用以forResult方式打开Activity的需求,可以传一个callback以自定义处理一些特殊情况。
最终会调用到_ARouter的navigation方法中:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) { PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; } try { LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); if (debuggable()) { // Show friendly tips for user. runInMainThread(new Runnable() { @Override public void run() { 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); } 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; } if (null != callback) { callback.onFound(postcard); } 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); } return null; }
这里只看核心代码,LogisticsCenter.completion(postcard)用于完善和检查Postcard的必要信息,暂时先不看,先看方法最终的逻辑:
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); } // Set Actions String action = postcard.getAction(); if (!TextUtils.isEmpty(action)) { intent.setAction(action); } // Navigation in main looper. runInMainThread(new Runnable() { @Override public void run() { startActivity(requestCode, currentContext, intent, postcard, callback); } }); 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; } /** * Start activity * * @see ActivityCompat */ private void startActivity(int requestCode, Context currentContext, Intent intent, Postcard postcard, NavigationCallback callback) { if (requestCode >= 0) { // Need start for result if (currentContext instanceof Activity) { ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle()); } else { logger.warning(Consts.TAG, "Must use [navigation(activity, ...)] to support [startActivityForResult]"); } } 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); } }
可以看到,最底层也是调用了startActvity或startActivityForResult(requestCode大于0且context是Activity)来处理回调,动画和NavigationCallback的处理也是在这里。
回过头来我们看一下LogisticsCenter.completion(postcard)方法,它只在navigation方法里面最开始调用:
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 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
resultMap = TextUtils.splitQueryParameters(rawUri); Map paramsType = routeMeta.getParamsType(); if (MapUtils.isNotEmpty(paramsType)) { // Set value by its type, just for params which annotation by @Param for (Map.Entry 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 providerMeta = (Class) 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的时候已经把自动生成的Root类保存进了Warehouse.groupsIndex了吧,这里首先会从Warehouse.groupsIndex中取RouteMeta,如果没取到说明之前没有跳转过,则从Warehouse.groupsIndex中取得之前存入的class,取到之后调用groupMeta.getConstructor().newInstance()反射生成实例,调用它的loadInto(Warehouse.routes)方法,这里我定义的是@Route(path="/john/"),所以生成的是ARouter$$Group$$john:
/** * DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */ public class ARouter$$Group$$john implements IRouteGroup { @Override public void loadInto(Map
atlas) { atlas.put("/john/", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/john/", "john", new java.util.HashMap (){{put("name", 8); put("age", 3); }}, -1, -2147483648)); } } 那么loadInto方法就是这里的,做的工作就是把生成的RouteMeta对象保存进Warehouse.routes,后面再次递归调用completion方法,再次进来,Warehouse.routes内就含有了RouteMeta对象。
我们看一下自动生成的RouteMeta都含有什么关键信息。
首先第一个参数RouteType,标志跳转目标的类型:
public enum RouteType { 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"); int id; String className; public int getId() { return id; } public RouteType setId(int id) { this.id = id; return this; } public String getClassName() { return className; } public RouteType setClassName(String className) { this.className = className; return this; } RouteType(int id, String className) { this.id = id; this.className = className; } public static RouteType parse(String name) { for (RouteType routeType : RouteType.values()) { if (routeType.getClassName().equals(name)) { return routeType; } } return UNKNOWN; } }
第二个参数是Class类型,是跳转目标类。第三个参数是Route定义的path。第四个参数是group。第五个参数是Map,用于保存Autowired参数类型的,value是整型,表示enum.ordinal:
public enum TypeKind { // Base type BOOLEAN, BYTE, SHORT, INT, LONG, CHAR, FLOAT, DOUBLE, // Other type STRING, SERIALIZABLE, PARCELABLE, OBJECT; }
通过setValue方法赋值到postcard的mBundle:
private static void setValue(Postcard postcard, Integer typeDef, String key, String value) { if (TextUtils.isEmpty(key) || TextUtils.isEmpty(value)) { return; } try { if (null != typeDef) { if (typeDef == TypeKind.BOOLEAN.ordinal()) { postcard.withBoolean(key, Boolean.parseBoolean(value)); } else if (typeDef == TypeKind.BYTE.ordinal()) { postcard.withByte(key, Byte.parseByte(value)); } else if (typeDef == TypeKind.SHORT.ordinal()) { postcard.withShort(key, Short.parseShort(value)); } else if (typeDef == TypeKind.INT.ordinal()) { postcard.withInt(key, Integer.parseInt(value)); } else if (typeDef == TypeKind.LONG.ordinal()) { postcard.withLong(key, Long.parseLong(value)); } else if (typeDef == TypeKind.FLOAT.ordinal()) { postcard.withFloat(key, Float.parseFloat(value)); } else if (typeDef == TypeKind.DOUBLE.ordinal()) { postcard.withDouble(key, Double.parseDouble(value)); } else if (typeDef == TypeKind.STRING.ordinal()) { postcard.withString(key, value); } else if (typeDef == TypeKind.PARCELABLE.ordinal()) { // TODO : How to description parcelable value with string? } else if (typeDef == TypeKind.OBJECT.ordinal()) { postcard.withString(key, value); } else { // Compatible compiler sdk 1.0.3, in that version, the string type = 18 postcard.withString(key, value); } } else { postcard.withString(key, value); } } catch (Throwable ex) { logger.warning(Consts.TAG, "LogisticsCenter setValue failed! " + ex.getMessage()); } }
注意这里setValue使用resultMap.get(params.getKey())来尝试获取Uri中的值,就是Uri中‘?’到‘#’(可以没有)之间的‘xx=xx&xx=xx&...’键值对。
第六个参数是priority,设置优先级,通过Route注解的priority属性指定,最后一个参数通过Route注解的extras属性指定。
-
参数传递
对于基本的类型传递,Postcard中都有对应的方法withXxx()来设置,对于实例化类型,如果实现了Serializable或Parcelable的,可以调用withSerializable和withParcelable方法设置。
而对于自定义的未实现上述接口的则需要调用withObject方法:
public Postcard withObject(@Nullable String key, @Nullable Object value) { serializationService = ARouter.getInstance().navigation(SerializationService.class); mBundle.putString(key, serializationService.object2Json(value)); return this; }
可见需要定义实现自SerializationService的类,譬如:
@Route(path = AppConstant.ARouteService.SERVICE_SERIALIZATION) public class ModuleSerializationRouteServiceImpl implements SerializationService { private Gson gson; private GsonBuilder gsonBuilder; @Override public
T json2Object(String input, Class clazz) { return null; } @Override public String object2Json(Object instance) { return gson.toJson(instance); } @Override public T parseObject(String input, Type clazz) { return gson.fromJson(input, clazz); } @Override public void init(Context context) { gsonBuilder = new GsonBuilder(); gsonBuilder.registerTypeAdapter(Class.class, new ClassTypeAdapter()); gson = gsonBuilder.create(); } } 这里的navigation(Class service)是:
protected
T navigation(Class service) { try { Postcard postcard = LogisticsCenter.buildProvider(service.getName()); // Compatible 1.0.5 compiler sdk. // Earlier versions did not use the fully qualified name to get the service if (null == postcard) { // No service, or this service in old version. postcard = LogisticsCenter.buildProvider(service.getSimpleName()); } if (null == postcard) { return null; } LogisticsCenter.completion(postcard); return (T) postcard.getProvider(); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); return null; } } LogisticsCenter.buildProvider从Warehouse.providersIndex中取得provider:
public static Postcard buildProvider(String serviceName) { RouteMeta meta = Warehouse.providersIndex.get(serviceName); if (null == meta) { return null; } else { return new Postcard(meta.getPath(), meta.getGroup()); } }
实例化SerializationService和调用它的init的代码就在completion中的:
switch (routeMeta.getType()) { case PROVIDER: // if the route is provider, should find its instance // Its provider, so it must implement IProvider Class providerMeta = (Class) 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; ... }
所以实际上withObject存储的是对象的json字符串。
-
传参自动注入
传参自然需要获取参数,上面我们知道最终也是通过intent和bundle传递的参数,那么获取的时候当然也可是通过getIntent().getExtras()方法获取参数值。
对于ARouter,这些工作我们还可以通过Autowiredz注解自动完成。
要完成自动注入需要两点,一点是必须在每个需要自动注入的属性上面加Autowired注解,它有三个注解属性,必须要有的是name属性,这个属性的值必须和跳转时传递到bundle的key相同,如果指定了name属性,则字段的名字不必和bundle的key相同,反之则必须相同;第二个是required属性,如果设置为true则表示该Field的值不能为null,否则会crash;最后一个参数是desc,用于描述Field的作用。注意如果是kotlin类,则需要同时加上@JvmField属性,否则不能通过kapt编译。
另一点是需要在使用自动注入的字段的值之前调用ARouter.getInstance().inject(this)方法,否则使用值为null的实例会抛出NullPointerException异常。
下面我们看看,对于这些要求ARouter是怎么通过代码设置的。
首先,含有Autowired注解的类会被自动生成一个名为“类名$$ARouter$$Autowired”的类,那么ARouter.getInstance().inject(this)方法调用了_ARouter的inject方法:
static void inject(Object thiz) { AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation()); if (null != autowiredService) { autowiredService.autowire(thiz); } }
可见,这里会找到path为/arouter/service/autowired的AutowiredService,那这个接口的实现类在哪里,还记得我们初始化的时候for循环loadInto操作吗,就在那个时候前缀为com.alibaba.android.arouter.routes.ARouter$$Providers的类被存到了Warehouse.providesIndex中,所以jar包arouter-api中的ARouter$$Providers$$arouterapi类自然也被加进去了,所以completion中找到这个类调用它的loadInto方法:
public void loadInto(Map
providers) { providers.put("com.alibaba.android.arouter.facade.service.AutowiredService", RouteMeta.build(RouteType.PROVIDER, AutowiredServiceImpl.class, "/arouter/service/autowired", "arouter", (Map)null, -1, -2147483648)); providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648)); } 所以path为/arouter/service/autowired的就是AutowiredServiceImpl类,接下来调用它的autowire方法,传入的是被跳转页Activity的实例:
@Override public void autowire(Object instance) { doInject(instance, null); } /** * Recursive injection * * @param instance who call me. * @param parent parent of me. */ private void doInject(Object instance, Class parent) { Class clazz = null == parent ? instance.getClass() : parent; ISyringe syringe = getSyringe(clazz); if (null != syringe) { syringe.inject(instance); } //这里可见会自动把其父类中的Autowired字段注入 Class superClazz = clazz.getSuperclass(); // has parent and its not the class of framework. if (null != superClazz && !superClazz.getName().startsWith("android")) { doInject(instance, superClazz); } }
来看看getSyringe方法:
private ISyringe getSyringe(Class clazz) { String className = clazz.getName(); try { if (!blackList.contains(className)) { ISyringe syringeHelper = classCache.get(className); if (null == syringeHelper) { // No cache. syringeHelper = (ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance(); } classCache.put(className, syringeHelper); return syringeHelper; } } catch (Exception e) { blackList.add(className); // This instance need not autowired. } return null; }
(ISyringe) Class.forName(clazz.getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance()这行代码就是获取自动生成的名为“类名$$ARouter$$Autowired”的类,这里比如说是LoginActivity类,那么这行代码就会找到LoginActivity$$ARouter$$Autowired:
public class LoginActivity$$ARouter$$Autowired implements ISyringe { private SerializationService serializationService; @Override public void inject(Object target) { serializationService = ARouter.getInstance().navigation(SerializationService.class); LoginActivity substitute = (LoginActivity)target; substitute.name = substitute.getIntent().getExtras() == null ? substitute.name : substitute.getIntent().getExtras().getString("name", substitute.name); substitute.howOld = substitute.getIntent().getIntExtra("age", substitute.howOld); } }
可见这个类实现自ISyringe接口,所以调用ISyringe的inject方法就是调用LoginActivity$$ARouter$$Autowired的inject方法,可以看到,这个方法里给这个Activity实例的相关字段赋值,取值来源也是通过getIntent()。
-
Service Management
// Declaration interface, other components get the service instance through the interface public interface HelloService extends IProvider { String sayHello(String name); } @Route(path = "/yourservicegroupname/hello", name = "test service") public class HelloServiceImpl implements HelloService { @Override public String sayHello(String name) { return "hello, " + name; } @Override public void init(Context context) { } }
结合Autowired就可以实现自动注入,原理同上。
-
NavigationCallback
如果你需要在路由正常跳转或者没找到时做一些回调处理,那么可以在调用navigation方法时传入一个NavigationCallback对象用于处理:
public interface NavigationCallback { /** * Callback when find the destination. * * @param postcard meta */ void onFound(Postcard postcard); /** * Callback after lose your way. * * @param postcard meta */ void onLost(Postcard postcard); /** * Callback after navigation. * * @param postcard meta */ void onArrival(Postcard postcard); /** * Callback on interrupt. * * @param postcard meta */ void onInterrupt(Postcard postcard); }
-
DegradeService
对于NoRouteFoundException发生时,你可以通过NavigationCallback来处理,但需要每次navigation都需要指定,如果你需要指定一个全局通用的onLost处理,你可以定义一个类实现DegradeService接口,重写onLost方法,这样所有的navigation发生NoRouteFoundException时都会进入到此方法,因为navigation方法中:
try { LogisticsCenter.completion(postcard); } catch (NoRouteFoundException ex) { logger.warning(Consts.TAG, ex.getMessage()); if (debuggable()) { // Show friendly tips for user. runInMainThread(new Runnable() { @Override public void run() { 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); } 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; }
可见,优先走NavigationCallback的onLost。
-
Interceptor
navigation方法中,判断postcard.isGreenChannel()是否为false来决定是否需要拦截处理,如果是false,则进行拦截处理:
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()); } });
interceptorService是什么时候实例化的呢?答案还是在初始化里面,init方法里,若成功执行完 _ARouter.init(application)方法则会返回true赋值给hasInit,hasInit为true会执行 _ARouter.afterInit()方法:
static void afterInit() { // Trigger interceptor init, use byName. interceptorService = (InterceptorService) ARouter.getInstance().build("/arouter/service/interceptor").navigation(); }
同AutowiredService一样,初始化时在ARouter$$Providers$$arouterapi的loadInto中path为/arouter/service/interceptor的InterceptorServiceImpl.class被存进了Warehouse.interceptorsIndex,所以这里afterInit会取到InterceptorServiceImpl.class,它的doInterceptions方法如下:
@Override public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) { if (null != Warehouse.interceptors && Warehouse.interceptors.size() > 0) { 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(new HandlerException(postcard.getTag().toString())); } else { callback.onContinue(postcard); } } catch (Exception e) { callback.onInterrupt(e); } } }); } else { callback.onContinue(postcard); } }
checkInterceptorsInitStatus():
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() + "]"); } } } }
只要interceptorHasInit不为true,则会一直阻塞当前线程等待,每10秒重新检查一次,那interceptorHasInit在哪里改变的?还记得在completion里:
switch (routeMeta.getType()) { case PROVIDER: // if the route is provider, should find its instance // Its provider, so it must implement IProvider Class providerMeta = (Class) 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; ... }
此时是InterceptorServiceImpl,为什么InterceptorServiceImpl的Type是PROVIDER而不是INTERCEPTOR呢?因为ARouter$$Providers$$arouterapi的loadInto存入时设置的就是PROVIDER:
providers.put("com.alibaba.android.arouter.facade.service.InterceptorService", RouteMeta.build(RouteType.PROVIDER, InterceptorServiceImpl.class, "/arouter/service/interceptor", "arouter", (Map)null, -1, -2147483648));
所以provider.init()方法就是InterceptorServiceImpl的init方法:
@Override public void init(final Context context) { LogisticsCenter.executor.execute(new Runnable() { @Override public void run() { if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) { for (Map.Entry
> entry : Warehouse.interceptorsIndex.entrySet()) { Class 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(); } } } }); } 可以看到,这里会把Warehouse.interceptorsIndex里的所以interceptor都执行init方法,然后设置interceptorHasInit为true,因为这里的init方法是新建线程执行的,所以在执行的同时,completion后面的doInterceptions方法也在异步执行,所以现在可以理解checkInterceptorsInitStatus的意义了,它就是在等init完成,调用interceptorInitLock.notifyAll()结束wait,从而继续往下执行doInterceptions方法,如果因为等待时间太长被系统终止则会抛出InterruptedException异常。
doInterceptions下面的代码就是开启子线程执行_execute方法,interceptorCounter.await同步等待 _executes执行完:
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 excute over with fatal exception. postcard.setTag(null == exception ? new HandlerException("No message.") : exception.getMessage()); // 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()); // } } }); } }
这里就是对所有的Interceptor依次执行process方法,假如定义了一个Interceptor:
@Interceptor(priority = 8, name = "test interceptor") public class TestInterceptor implements IInterceptor { @Override public void process(Postcard postcard, InterceptorCallback callback) { ... // No problem! hand over control to the framework if(LoginUtils.isLogin()){ callback.onContinue(postcard); }else{ callback.onInterrupt(new Throwable("The Route needs login first!")); } // Interrupt routing process // callback.onInterrupt(new RuntimeException("Something exception")); // The above two types need to call at least one of them, otherwise it will not continue routing } @Override public void init(Context context) { // Interceptor initialization, this method will be called when sdk is initialized, it will only be called once } }
这里模拟了跳转需要登录的逻辑,如果已登录则执行跳转,如果未登录则intertupt。在process中调用callback.onContinue或者callback.onInterrupt方法就会回到_execute中处理。
执行完后判断如果interceptorCounter的getCount大于0说明因Postcard超时时间到期中止,这个超时时间可以设置,默认300秒,如果postcard.getTag()不为null说明有异常产生,若顺利的话会走到callback.onContinue(postcard),就会回调到_ARouter.navigation方法里doIntercaptions传入的匿名InterceptorCallback对象的onContinue方法,从而执行 _navigation方法执行跳转。
至此,拦截工作就完成了。
-
Priority
Interceptor的priority在哪设置的?@Interceptor注解修饰的类会被自动创建的ARouter$$Interceptors$$app存进Warehouse.interceptorsIndex中,原理同上:
public class ARouter$$Interceptors$$app implements IInterceptorGroup { @Override public void loadInto(Map
> interceptors) { interceptors.put(5, LoginInterceptor.class); } } 因为我创建的Interceptor是:
@Interceptor(name = "LoginInInterceptor" , priority = 5) class LoginInterceptor : IInterceptor {
所以interceptors.put的参数分别是5和LoginInterceptor.class,5是设置的priority,为什么把priority作为key呢?因为Warehouse的interceptorsIndex是一个UniqueKeyTreeMap,它的put方法:
@Override public V put(K key, V value) { if (containsKey(key)) { throw new RuntimeException(String.format(tipText, key)); } else { return super.put(key, value); } }
比其父类多的一个操作是首先判断不能添加重复key值的value。然后调用了父类TreeMap的put方法:
public V put(K key, V value) { TreeMapEntry
t = root; if (t == null) { compare(key, key); // type (and possibly null) check root = new TreeMapEntry<>(key, value, null); size = 1; modCount++; return null; } int cmp; TreeMapEntry parent; // split comparator and comparable paths Comparator cpr = comparator; if (cpr != null) { do { parent = t; cmp = cpr.compare(key, t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } else { if (key == null) throw new NullPointerException(); @SuppressWarnings("unchecked") Comparable k = (Comparable) key; do { parent = t; cmp = k.compareTo(t.key); if (cmp < 0) t = t.left; else if (cmp > 0) t = t.right; else return t.setValue(value); } while (t != null); } TreeMapEntry e = new TreeMapEntry<>(key, value, parent); if (cmp < 0) parent.left = e; else parent.right = e; fixAfterInsertion(e); size++; modCount++; return null; } 可见会按照key值将新元素插入到合适的位置,顺序就是key值越小越靠前,而我们前面看到执行process的时候都是从Warehouse.interceptorsIndex的第0个元素开始,这也就是为什么它文档上写的priority越小优先级越高。
Route注解里面也可以设置priority,但是我发现它好像没有用到,我唯一能想到的就是可以在NavigationCallback或者Interceptor中通过Postcard对象取到它然后根据它的值来做点什么逻辑,同样extra属性也是这样。
-
Pretreatment Service
在navigation方法的最前面有这样几行代码:
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class); if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) { // Pretreatment failed, navigation canceled. return null; }
如果说Interceptor是在找到路由后、跳转之前的处理拦截,那PretreatmentService就是在查找路由之前的处理拦截:
@Route(path = "/xxx/xxx") public class PretreatmentServiceImpl implements PretreatmentService { @Override public boolean onPretreatment(Context context, Postcard postcard) { // Do something before the navigation, if you need to handle the navigation yourself, the method returns false } @Override public void init(Context context) { } }
官方注释告诉我们,如果你不希望通过ARouter自动跳转的话这里可以返回false,就不会执行查找路由等操作了。
注意,PretreatmentService只能定义一个,因为:
public class ARouter$$Providers$$app implements IProviderGroup { @Override public void loadInto(Map
providers) { providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, MyPreTreatmentService.class, "/lllSS/myPretreatmentService", "lllSS", null, 1, -2147483648)); providers.put("com.alibaba.android.arouter.facade.service.PretreatmentService", RouteMeta.build(RouteType.PROVIDER, OtherPretreatmentService.class, "/lllSS/otherPretreatmentService", "lllSS", null, 2, -2147483648)); } } 定义多个,key值是一样的,因此只有最后一个会生效。
-
Rewrite URL
在 _ARouter.build方法里有这样的处理:
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class); if (null != pService) { //path是String path = pService.forString(path); //path是Uri //path = pService.forUri(path); }
也就是说出build传入的路由地址还可以通过PathReplaceService的api统一处理。
定义如下:
// Implement the PathReplaceService interface @Route(path = "/xxx/xxx") public class PathReplaceServiceImpl implements PathReplaceService { /** * For normal path. * * @param path raw path */ String forString(String path) { // Custom logic return path; } /** * For uri type. * * @param uri raw uri */ Uri forUri(Uri uri) { // Custom logic return url; } }
同样,定义多个只会以ARouter$$Providers$$moduleName的loadInto里面put的最后一个key为com.alibaba.android.arouter.facade.service.PathReplaceService的为准。
-
-
其他API description
// Build a standard route request ARouter.getInstance().build("/home/main").navigation(); // Build a standard route request, via URI Uri uri; ARouter.getInstance().build(uri).navigation(); // Build a standard route request, startActivityForResult // The first parameter must be Activity and the second parameter is RequestCode ARouter.getInstance().build("/home/main", "ap").navigation(this, 5); // Pass Bundle directly Bundle params = new Bundle(); ARouter.getInstance() .build("/home/main") .with(params) .navigation(); // Set Flag ARouter.getInstance() .build("/home/main") .withFlags(); .navigation(); // For fragment Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation(); // transfer the object ARouter.getInstance() .withObject("key", new TestObj("Jack", "Rose")) .navigation(); // Think the interface is not enough, you can directly set parameter into Bundle ARouter.getInstance() .build("/home/main") .getExtra(); // Transition animation (regular mode) ARouter.getInstance() .build("/test/activity2") .withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom) .navigation(this); // Transition animation (API16+) ActivityOptionsCompat compat = ActivityOptionsCompat. makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0); // ps. makeSceneTransitionAnimation, When using shared elements, you need to pass in the current Activity in the navigation method ARouter.getInstance() .build("/test/activity2") .withOptionsCompat(compat) .navigation(); // Use green channel (skip all interceptors) ARouter.getInstance().build("/home/main").greenChannel().navigation(); // Use your own log tool to print logs ARouter.setLogger(); // Use your custom thread pool ARouter.setExecutor();