Arouter框架分析

Arouter框架结构

Arouter框架结构中有注解定义和注解处理器相关的内容,Arouter本身也可以算是一个示例。
Arouter框架分析
文章图片

arouter-api对Arouter初始化
Arouter框架使用的静态注解处理,为适应多模块,使用moduleName后缀生成了一组统一规则的注册类。这些注册类分布在各自的module内部,需要一个管理类把他们聚合在一起,提供统一的注册和调用入口。
初始化入口 集成Arouter路由框架,需要在Application初始化过程中调用下面的方法对Arouter框架进行初始化。
ARouter.init(sInstance);

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(mContext, executor); 中完成。需要注意下面的这个判断:
ARouter.debuggable() || PackageUtils.isNewVersion(context),在debug或者更新app版本的条件下才会更新路由表,扫描的路由文件列表在SharedPreference中保存。
  • registerByPlugin 是 com.alibaba.arouter 插件标记,标明是否在编译阶段已经进行了路由表注册工作,直接跳过;
  • 使用 ClassUtils 扫描package(com.alibaba.android.arouter.routes)中所有类文件 - 因为所有模块的路由都创建在这个包路径中;
  • 把所有扫描到的文件,按照规则生成实例,注册到管理器 Warehouse 中;
/** * LogisticsCenter init, load all metas in memory. Demand initialization */ public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException { // 有删减 loadRouterMap(); if (!registerByPlugin) { Set routerMap; if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) { 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 { routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet())); } for (String className : routerMap) { if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) { ((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) { ((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex); } else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) { ((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex); } } } }

使用arouter-register插件
Arouter-register是AutoRegister插件在Arouter框架中的实现,主要目的是在编译阶段完成路由表的初始化操作,降低Arouter初始化耗时。
  • Arouter框架生成类包路径: com.alibaba.android.arouter
  • com.android.build.api.transform.TransForm
路由文件和初始化类扫描
@Override void transform(Context context, Collection inputs , Collection referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {// 有删减 boolean leftSlash = File.separator == '/' if (!isIncremental){ outputProvider.deleteAll() }inputs.each { TransformInput input ->// scan all jars input.jarInputs.each { JarInput jarInput -> String destName = jarInput.name // rename jar files def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath) if (destName.endsWith(".jar")) { destName = destName.substring(0, destName.length() - 4) } // input file File src = https://www.it610.com/article/jarInput.file // output file File dest = outputProvider.getContentLocation(destName +"_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)//scan jar file to find classes if (ScanUtil.shouldProcessPreDexJar(src.absolutePath)) { ScanUtil.scanJar(src, dest) } FileUtils.copyFile(src, dest)} // scan class files input.directoryInputs.each { DirectoryInput directoryInput -> File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY) String root = directoryInput.file.absolutePath if (!root.endsWith(File.separator)) root += File.separator directoryInput.file.eachFileRecurse { File file -> def path = file.absolutePath.replace(root, '') if (!leftSlash) { path = path.replaceAll("\\", "/") } if(file.isFile() && ScanUtil.shouldProcessClass(path)){ ScanUtil.scanClass(file) } }// copy to dest FileUtils.copyDirectory(directoryInput.file, dest) } }if (fileContainsInitClass) { registerList.each { ext -> if (!ext.classList.isEmpty()) { ext.classList.each { Logger.i(it) } RegisterCodeGenerator.insertInitCodeTo(ext) } } } }static void scanJar(File jarFile, File destFile) { if (jarFile) { def file = new JarFile(jarFile) Enumeration enumeration = file.entries() while (enumeration.hasMoreElements()) { JarEntry jarEntry = (JarEntry) enumeration.nextElement() String entryName = jarEntry.getName() if (entryName.startsWith(ScanSetting.ROUTER_CLASS_PACKAGE_NAME)) { InputStream inputStream = file.getInputStream(jarEntry) scanClass(inputStream) inputStream.close() } else if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) { // com/alibaba/android/arouter/core/LogisticsCenter RegisterTransform.fileContainsInitClass = destFile } } file.close() } }/** * scan class file * @param class file */ static void scanClass(File file) { scanClass(new FileInputStream(file)) }static void scanClass(InputStream inputStream) { ClassReader cr = new ClassReader(inputStream) ClassWriter cw = new ClassWriter(cr, 0) ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw) cr.accept(cv, ClassReader.EXPAND_FRAMES) inputStream.close() }

static class ScanClassVisitor extends ClassVisitor {ScanClassVisitor(int api, ClassVisitor cv) { super(api, cv) }void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces) RegisterTransform.registerList.each { ext -> if (ext.interfaceName && interfaces != null) { interfaces.each { itName -> if (itName == ext.interfaceName) { //fix repeated inject init code when Multi-channel packaging if (!ext.classList.contains(name)) { ext.classList.add(name) } } } } } } }

目标文件字节码操作
通过上面的扫描操作:获取到Arouter框架生成类的路径存放于RegisterTransform.registerList中的ScanSetting对象中,以及arouter-api初始化类 LogisticsCenter 所在的文件由 RegisterTransform.fileContainsInitClass 持有。 扫描完成后,调用 RegisterCodeGenerator.insertInitCodeTo(ext) ,遍历RegisterTransform.registerList中的ScanSetting对象作为输入,对 LogisticsCenter 类所在的文件读写操作。
// 读写过程:创建临时文件optJar,从源文件jarFile中读取数据,转写到临时optJar文件中,完成后使用optJar覆盖源文件jarFile。 private File insertInitCodeIntoJarFile(File jarFile) { if (jarFile) { def optJar = new File(jarFile.getParent(), jarFile.name + ".opt") if (optJar.exists()){ optJar.delete() } def file = new JarFile(jarFile) Enumeration enumeration = file.entries() JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(optJar))while (enumeration.hasMoreElements()) { JarEntry jarEntry = (JarEntry) enumeration.nextElement() String entryName = jarEntry.getName() ZipEntry zipEntry = new ZipEntry(entryName) InputStream inputStream = file.getInputStream(jarEntry) jarOutputStream.putNextEntry(zipEntry) if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) { def bytes = referHackWhenInit(inputStream) jarOutputStream.write(bytes) } else { jarOutputStream.write(IOUtils.toByteArray(inputStream)) } inputStream.close() jarOutputStream.closeEntry() } jarOutputStream.close() file.close()if (jarFile.exists()) { jarFile.delete() } optJar.renameTo(jarFile) } return jarFile }// 找到com/alibaba/android/arouter/core/LogisticsCenter.class,调用这个方法 private byte[] referHackWhenInit(InputStream inputStream) { ClassReader cr = new ClassReader(inputStream) ClassWriter cw = new ClassWriter(cr, 0) ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw) cr.accept(cv, ClassReader.EXPAND_FRAMES) return cw.toByteArray() }// 在LogisticsCenter.class中查找 loadRouterMap 方法 class MyClassVisitor extends ClassVisitor {MyClassVisitor(int api, ClassVisitor cv) { super(api, cv) }void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces) } @Override MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions) //generate code into this method if (name == ScanSetting.GENERATE_TO_METHOD_NAME) { mv = new RouteMethodVisitor(Opcodes.ASM5, mv) } return mv } }// 向 loadRouterMap 插入语句:retister(className) // register函数在 LogisticsCenter 中定义,用于把给定的类名生成实例,注册到 Warehouse 管理器中。 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) } }

arouter-register与arouter-api 初始化Arouter Arouter-register是AutoRegister的一个实现,可以参考\# AutoRegister框架分析。
  • Arouter-api在运行时,通过扫描类文件进行初始化操操作。
  • Arouter-register在编译阶段的尾声部分,扫描jar文件和.class文件找到路由注册类,在 LogisticsCenter.class#loadRouterMap() 方法中插入LogisticsCenter.class#register(className) 调用语句,使得loadRouterMap可以直接完成路由注册,节省了运行时扫描的时间。
  • Arouter-register工作在Arouter-api的基础上,register(className)和 loadRouterMap()都由Arouter-api库提供。
插件中使用的方法记录
JarFile
  • file.entries() -> Enumeration : Jar文件中打包的.class文件集合
  • enumeration.nextElement() -> JarEntry : Jar文件中打包的.class 文件
类访问 使用 ClassVisiter、ClassReader、ClassWriter 对.class文件的读写操作,都在这一组类中。
void scanClass(InputStream inputStream) { ClassReader cr = new ClassReader(inputStream) ClassWriter cw = new ClassWriter(cr, 0) ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw) cr.accept(cv, ClassReader.EXPAND_FRAMES) inputStream.close() }byte[] referHackWhenInit(InputStream inputStream) { ClassReader cr = new ClassReader(inputStream) ClassWriter cw = new ClassWriter(cr, 0) ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw) cr.accept(cv, ClassReader.EXPAND_FRAMES) return cw.toByteArray() }static class MyClassVisitor extends ClassVisitor {ScanClassVisitor(int api, ClassVisitor cv) { super(api, cv) }@Override void visit(int version, int access, String name, String signature, String superName, String[] interfaces) { super.visit(version, access, name, signature, superName, interfaces) }@Override MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) { MethodVisitor mv = super.visitMethod(access, name, desc, signature, exceptions) //generate code into this method if (name == ScanSetting.GENERATE_TO_METHOD_NAME) { mv = new RouteMethodVisitor(Opcodes.ASM5, mv) } return mv } }

方法访问 使用 MethodVisitor 对类方法进行读写,可以插入代码。
class RouteMethodVisitor extends MethodVisitor {RouteMethodVisitor(int api, MethodVisitor mv) { super(api, mv) }@Override void visitInsn(int opcode) { super.visitInsn(opcode) } @Override void visitMaxs(int maxStack, int maxLocals) { super.visitMaxs(maxStack + 4, maxLocals) } }

ASM库
Arouter-register插件,是基于字节码插桩技术实现的。ASM是一个插桩框架,框架集成在 com.android.tools.build:gradle 内,下表有删减。
\--- com.android.tools.build:gradle:2.1.3 \--- com.android.tools.build:gradle-core:2.1.3 +--- com.android.tools.build:builder:2.1.3 |+--- org.ow2.asm:asm:5.0.3 |\--- org.ow2.asm:asm-tree:5.0.3 |\--- org.ow2.asm:asm:5.0.3 +--- org.ow2.asm:asm:5.0.3 +--- org.ow2.asm:asm-commons:5.0.3 |\--- org.ow2.asm:asm-tree:5.0.3 (*) +--- net.sf.proguard:proguard-gradle:5.2.1 |\--- net.sf.proguard:proguard-base:5.2.1 +--- org.jacoco:org.jacoco.core:0.7.6.201602180812 |\--- org.ow2.asm:asm-debug-all:5.0.4 \--- org.antlr:antlr:3.5.2 +--- org.antlr:antlr-runtime:3.5.2 \--- org.antlr:ST4:4.0.8 (*)

相关教程 Android基础系列教程:
Android基础课程U-小结_哔哩哔哩_bilibili
Android基础课程UI-布局_哔哩哔哩_bilibili
Android基础课程UI-控件_哔哩哔哩_bilibili
Android基础课程UI-动画_哔哩哔哩_bilibili
Android基础课程-activity的使用_哔哩哔哩_bilibili
Android基础课程-Fragment使用方法_哔哩哔哩_bilibili
Android基础课程-热修复/热更新技术原理_哔哩哔哩_bilibili
【Arouter框架分析】本文转自 https://juejin.cn/post/7044826883719954469,如有侵权,请联系删除。

    推荐阅读