实现 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 {
@Override
String getName() {
return "RouterMappingTransform"
}
@Override
Set getInputTypes() {
return TransformManager.CONTENT_CLASS
}
@Override
Set super QualifiedContent.Scope> getScopes() {
return TransformManager.SCOPE_FULL_PROJECT
}
@Override
boolean isIncremental() {
return false
}
@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收集目标类{ @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 mappingClassNames = new HashSet<>()
Set getMappingClassName() {
return mappingClassNames
}
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)
}
}
}
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
@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 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,
"",
"()V",
null,
null)
mv.visitCode()
mv.visitVarInsn(Opcodes.ALOAD,0)
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "", "()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;",
null)
mv.visitCode() // 开启字节码访问或编辑
mv.visitTypeInsn(NEW,"java/util/HashMap")
mv.visitInsn(DUP)
mv.visitMethodInsn(INVOKESPECIAL,
"java/util/HashMap",
"" ,
"()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



