生成汇总映射表

利用字节码插桩技术实现路由框架生成汇总映射表

实现 Transform

实现类的拷贝

  • buildSrc build.gradle 新增配置

    // 声明仓库地址
    repositories {
        jcenter()
         google()
    }
    
    // 声明依赖的包
    dependencies {
        implementation gradleApi()
        implementation localGroovy()
        implementation 'com.android.tools.build:gradle:4.1.1'
    }
    
    
  • 新建 RouterMappingTransform.groovy 类 实现类的拷贝逻辑

      class RouterMappingTransform extends Transform {
    
    	    /**
    	     * 当前 Transform 名称
    	     * @return
    	     */
    	    @Override
    	    String getName() {
    	        return "RouterMappingTransform"
    	    }
    	
    	    /**
    	     * 告知编译期,当前 Transform 需要消费的输入类型
    	     * @return
    	     */
    	    @Override
    	    Set<QualifiedContent.ContentType> getInputTypes() {
    	        return TransformManager.CONTENT_CLASS
    	    }
    	
    	    /**
    	     * 告知编译期,当前 Transform 需要搜集的范围
    	     * @return
    	     */
    	    @Override
    	    Set<? super QualifiedContent.Scope> getScopes() {
    	        return TransformManager.SCOPE_FULL_PROJECT
    	    }
    	
    	    /**
    	     * 是否支持增量
    	     * @return
    	     */
    	    @Override
    	    boolean isIncremental() {
    	        return false
    	    }
    	
    	    /**
    	     * 所有的 class 收集好之后,会被打包传入此方法
    	     * @param transformInvocation
    	     * @throws TransformException
    	     * @throws InterruptedException
    	     * @throws IOException
    	     */
    	    @Override
    	    void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
    	        // 1. 遍历所有的 Input
    	        // 2. 对 Input 进行二次处理
    	        // 3. 将 Input 拷贝到目标目录
    	
    	        transformInvocation.inputs.each {
    	            // 把文件夹类型的输入,拷贝到目标目录
    	            it.directoryInputs.each {directoryInput ->
    	                def destDir = transformInvocation.outputProvider.getContentLocation(
    	                        directoryInput.name,directoryInput.contentTypes,directoryInput.scopes, Format.DIRECTORY
    	                )
    	                FileUtils.copyDirectory(directoryInput.file,destDir)
    	            }
    	            // 把 jar 类型的输入,拷贝到目标目录
    	            it.jarInputs.each {jarInput ->
    	                def jarDestDir = transformInvocation.outputProvider.getContentLocation(
    	                        jarInput.name,jarInput.contentTypes,jarInput.scopes, Format.JAR
    	                )
    	                FileUtils.copyFile(jarInput.file,jarDestDir)
    	            }
    	        }
    	    }
    }
    
  • 在 RouterPlugin 中注入 Transform

    class RouterPlugin implements Plugin<Project> {
        /**
         * 实现 apply 方法,注入插件的逻辑
         * @param project
         */
        @Override
        void apply(Project project) {
    
            // 注册 Transform
            if (project.plugins.hasPlugin(AppPlugin)) {
                AppExtension extension = project.extensions.getByType(AppExtension)
                Transform transform =  new RouterMappingTransform()
                extension.registerTransform(transform)
            }
           }
     }
    

收集目标类

  • 创建子工程 biz-reading

    	plugins {
    	    id 'com.android.library'
    	    id 'kotlin-android'
    	    id 'kotlin-android-extensions'
    	    id 'kotlin-kapt'
    	    id 'com.imooc.router'
    	}
    	dependencies {
    	    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    	    implementation 'androidx.core:core-ktx:1.2.0'
    	    implementation 'androidx.appcompat:appcompat:1.1.0'
    	    implementation 'com.google.android.material:material:1.1.0'
    	    testImplementation 'junit:junit:4.+'
    	    androidTestImplementation 'androidx.test.ext:junit:1.1.1'
    	    androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
    	
    	    // 引入自定义注解
    	    implementation project(':router-annotations')
    	    kapt project(':router-processor')
    	}
    
  • 新建 ReadingActivity

    @Destination(
            url = "router://reading",
            description = "阅读"
    )
    class ReadingActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_reading)
        }
    }
    
  • 主工程 build.gradle 引入 biz-reading

    implementation project(':biz-reading')
    
  • buildSrc 工程新建 RouterMappingCollector 完成映射表类名的收集

    	class RouterMappingCollect {
    	    private static final String PACKAGE_NAME = "com/imooc/router/mapping"
    	    private static final String CLASS_NAME_PREFIX = "RouterMapping_"
    	    private static final String CLASS_NAME_SUFFIX = ".class"
    	
    	    private final Set<String> mappingClassNames = new HashSet<>()
    	
    	    /**
    	     * 获取收集好的映射表类名
    	     * @return
    	     */
    	    Set<String> getMappingClassName() {
    	        return mappingClassNames
    	    }
    	
    	    /**
    	     * 收集 class 文件或者 class 文件目录中的映射表类
    	     * @param classFile
    	     */
    	    void collect(File classFile){
    	        if (classFile == null || !classFile.exists()) return
    	        if (classFile.isFile()){
    	            if (classFile.absolutePath.contains(PACKAGE_NAME)
    	            && classFile.name.startsWith(CLASS_NAME_PREFIX)
    	            && classFile.name.endsWith(CLASS_NAME_SUFFIX)){
    	                String className =
    	                        classFile.name.replace(CLASS_NAME_SUFFIX,"")
    	                mappingClassName.add(className)
    	            }
    	        }else{
    	            classFile.listFiles().each {
    	                collect(it)
    	            }
    	        }
    	    }
    	    /**
    	     * 收集 jar 包中的目标类
    	     * @param classFile
    	     */
    	    void collectFormJarFile(File classFile){
    	        java.util.Enumeration enumeration = new JarFile(classFile).entries()
    	        while (enumeration.hasMoreElements()){
    	            JarEntry jarEntry = enumeration.nextElement()
    	            String entryName = jarEntry.name
    	            if (entryName.contains(PACKAGE_NAME)
    	                    && entryName.contains(CLASS_NAME_PREFIX)
    	                    && entryName.contains(CLASS_NAME_SUFFIX)){
    	                String className = entryName
    	                        .replace(PACKAGE_NAME,"")
    	                        .replace("/","")
    	                        .replace(CLASS_NAME_SUFFIX,"")
    	                mappingClassName.add(className)
    	            }
    	        }
    	    }
    }
    
  • 在 RouterMappingTransform 中调用前面定义的 RouterMappingCollect

    /**
         * 所有的 class 收集好之后,会被打包传入此方法
         * @param transformInvocation
         * @throws TransformException
         * @throws InterruptedException
         * @throws IOException
         */
        @Override
        void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException {
            // 1. 遍历所有的 Input
            // 2. 对 Input 进行二次处理
            // 3. 将 Input 拷贝到目标目录
    
           RouterMappingCollect collect = new RouterMappingCollect()
    
            transformInvocation.inputs.each {
                // 把文件夹类型的输入,拷贝到目标目录
                it.directoryInputs.each {directoryInput ->
                    def destDir = transformInvocation.outputProvider.getContentLocation(
                            directoryInput.name,
                            directoryInput.contentTypes,
                            directoryInput.scopes,
                            Format.DIRECTORY
                    )
    
                    collect.collect(directoryInput.file)
                    FileUtils.copyDirectory(directoryInput.file,destDir)
                }
                // 把 jar 类型的输入,拷贝到目标目录
                it.jarInputs.each {jarInput ->
                    def jarDestDir = transformInvocation.outputProvider.getContentLocation(
                            jarInput.name,
                            jarInput.contentTypes,
                            jarInput.scopes,
                            Format.JAR
                    )
                    collect.collectFormJarFile(jarInput.file)
                    FileUtils.copyFile(jarInput.file,jarDestDir)
                }
            }
        }
    

生成汇总映射表

  • 编写生成字节码的逻辑

    class RouterMappingByteCodeBuilder implements Opcodes {
    
        public static final String CLASS_NAME = "com/imooc/router/mapping/generated/RouterMapping"
    
        static byte[] get(Set<String> allMappingNames) {
            // 1. 创建一个类
            // 2. 创建构造方法
            // 3. 创建get 方法
                // i.   创建Map
                // ii.  添加所有映射表的内容
                // iii. 返回Map
    
            ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_MAXS)
    
            // 实例化一个类
            cw.visit(V1_7,
                ACC_PUBLIC + ACC_SUPER,
                CLASS_NAME,
                null,
                "java/lang/Object",
                null)
    
            // 创建构造方法
            MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC,
                        "<init>",
                         "()V",
                         null,
                         null)
            mv.visitCode()
            mv.visitVarInsn(Opcodes.ALOAD,0)
            mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false)
            mv.visitInsn(Opcodes.RETURN)
            mv.visitMaxs(1,1)
            mv.visitEnd()
    
            // 创建 get 方法
            cw.visitMethod(ACC_PUBLIC + ACC_STATIC ,
                    "get",
                    "()Ljava/util/Map;",
                    "()Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;",
                     null)
    
            mv.visitCode() // 开启字节码访问或编辑
    
    
            mv.visitTypeInsn(NEW,"java/util/HashMap")
            mv.visitInsn(DUP)
            mv.visitMethodInsn(INVOKESPECIAL,
                        "java/util/HashMap",
                        "<init>" ,
                        "()V",false)
            mv.visitVarInsn(ASTORE,0) // 存储创建的 HashMap 实例
    
            // 向 Map 中 逐个塞入所有映射表的内容
            allMappingNames.each {
                mv.visitVarInsn(ALOAD,0)
                mv.visitMethodInsn(INVOKESTATIC,
                    "com/imooc/router/mapping/$it",
                    "get", "()Ljava/util/Map;", false)
                mv.visitMethodInsn(INVOKEINTERFACE,
                    "java/util/Map",
                    "putAll",
                    "(Ljava/util/Map;)V", true)
            }
    
            // 返回 Map
            mv.visitVarInsn(ALOAD,0)
            mv.visitInsn(ARETURN)
            mv.visitMaxs(2,2)
            mv.visitEnd()
    
            return cw.toByteArray()
        }
    }
    
  • 将生成的字节码,写入到本地文件

    RouterMappingTransform

           if (mappingJarFile.getParentFile().exists()) {
                mappingJarFile.getParentFile().mkdirs()
            }
    
            if(mappingJarFile.exists()){
                mappingJarFile.delete()
            }
    
            // 将生成的字节码,写入本地文件
            java.io.FileOutputStream fos = new FileOutputStream(mappingJarFile)
            JarOutputStream jarOutputStream = new JarOutputStream(fos)
            ZipEntry zipEntry = new ZipEntry(RouterMappingByteCodeBuilder.CLASS_NAME + ".class")
            jarOutputStream.putNextEntry(zipEntry)
            jarOutputStream.write(
                    RouterMappingByteCodeBuilder.get(collect.mappingClassName)
            )
            jarOutputStream.closeEntry()
            jarOutputStream.close()
            fos.close()
    
  • 执行构建编译,查看生成的字节码文件

    gradle :app:assembleDebug
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值