com.flyjingfish.android_aop_plugin.tasks.AssembleAndroidAopTask.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of android-aop-plugin Show documentation
Show all versions of android-aop-plugin Show documentation
Lightweight Aop for Android platform, you deserve it, action is worse than your heartbeat
package com.flyjingfish.android_aop_plugin.tasks
import com.flyjingfish.android_aop_plugin.beans.ClassMethodRecord
import com.flyjingfish.android_aop_plugin.beans.MethodRecord
import com.flyjingfish.android_aop_plugin.config.AndroidAopConfig
import com.flyjingfish.android_aop_plugin.scanner_visitor.RegisterMapWovenInfoCode
import com.flyjingfish.android_aop_plugin.scanner_visitor.ReplaceBaseClassVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.ReplaceInvokeMethodVisitor
import com.flyjingfish.android_aop_plugin.scanner_visitor.SuspendReturnScanner
import com.flyjingfish.android_aop_plugin.scanner_visitor.WovenIntoCode
import com.flyjingfish.android_aop_plugin.utils.AopTaskUtils
import com.flyjingfish.android_aop_plugin.utils.ClassFileUtils
import com.flyjingfish.android_aop_plugin.utils.ClassPoolUtils
import com.flyjingfish.android_aop_plugin.utils.FileHashUtils
import com.flyjingfish.android_aop_plugin.utils.InitConfig
import com.flyjingfish.android_aop_plugin.utils.Utils
import com.flyjingfish.android_aop_plugin.utils.Utils._CLASS
import com.flyjingfish.android_aop_plugin.utils.WovenInfoUtils
import com.flyjingfish.android_aop_plugin.utils.computeMD5
import com.flyjingfish.android_aop_plugin.utils.getFileClassname
import com.flyjingfish.android_aop_plugin.utils.getRelativePath
import com.flyjingfish.android_aop_plugin.utils.inRules
import com.flyjingfish.android_aop_plugin.utils.isJarSignatureRelatedFiles
import com.flyjingfish.android_aop_plugin.utils.toClassPath
import org.gradle.api.DefaultTask
import org.gradle.api.file.Directory
import org.gradle.api.file.RegularFile
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.ListProperty
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFiles
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import java.io.BufferedOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.FileOutputStream
import java.io.InputStream
import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import kotlin.system.measureTimeMillis
abstract class AssembleAndroidAopTask : DefaultTask() {
@get:Input
abstract var variant :String
@get:Input
abstract var reflectInvokeMethod :Boolean
@get:InputFiles
abstract val allJars: ListProperty
@get:InputFiles
abstract val allDirectories: ListProperty
@get:OutputFile
abstract val output: RegularFileProperty
private lateinit var jarOutput: JarOutputStream
private val ignoreJar = mutableSetOf()
private val ignoreJarClassPaths = mutableListOf()
private lateinit var aopTaskUtils : AopTaskUtils
@TaskAction
fun taskAction() {
aopTaskUtils = AopTaskUtils(project,variant)
ClassPoolUtils.release(project)
ClassFileUtils.debugMode = false
ClassFileUtils.reflectInvokeMethod = reflectInvokeMethod
WovenInfoUtils.isCompile = false
WovenInfoUtils.checkHasInvokeJson(project, variant)
WovenInfoUtils.checkHasOverrideJson(project, variant)
println("AndroidAOP woven info code start")
ClassFileUtils.outputDir = File(Utils.aopTransformTempDir(project,variant))
ClassFileUtils.clear()
ClassFileUtils.outputDir.deleteRecursively()
SuspendReturnScanner.hasSuspendReturn = false
jarOutput = JarOutputStream(BufferedOutputStream(FileOutputStream(output.get().asFile)))
val scanTimeCost = measureTimeMillis {
scanFile()
}
jarOutput.close()
println("AndroidAOP woven info code finish, current cost time ${scanTimeCost}ms")
}
private fun scanFile() {
loadJoinPointConfig()
searchJoinPointLocation()
wovenIntoCode()
}
private fun loadJoinPointConfig(){
val isClassesJar = allDirectories.get().isEmpty() && allJars.get().size == 1
WovenInfoUtils.addBaseClassInfo(project)
ignoreJar.clear()
ignoreJarClassPaths.clear()
allJars.get().forEach { file ->
if (isClassesJar){
ignoreJar.add(file.asFile.absolutePath)
return@forEach
}
val jarFile = JarFile(file.asFile)
val enumeration = jarFile.entries()
while (enumeration.hasMoreElements()) {
val jarEntry = enumeration.nextElement()
try {
val entryName = jarEntry.name
if (entryName.isJarSignatureRelatedFiles()){
ignoreJar.add(file.asFile.absolutePath)
break
}
} catch (e: Exception) {
e.printStackTrace()
}
}
jarFile.close()
}
if (ignoreJar.isNotEmpty()){
val temporaryDir = File(Utils.aopTransformIgnoreJarDir(project,variant))
for (path in ignoreJar) {
val destDir = "${temporaryDir.absolutePath}${File.separatorChar}${File(path).name.computeMD5()}"
val destFile = File(destDir)
destFile.deleteRecursively()
Utils.openJar(path,destDir)
ignoreJarClassPaths.add(destFile)
}
}
fun processFile(file : File,directory:File,directoryPath:String){
aopTaskUtils.processFileForConfig(file, directory, directoryPath)
}
if (!ClassFileUtils.reflectInvokeMethod){
WovenInfoUtils.addClassPath(ClassFileUtils.outputDir.absolutePath)
}
for (directory in ignoreJarClassPaths) {
val directoryPath = directory.absolutePath
WovenInfoUtils.addClassPath(directoryPath)
directory.walk().forEach { file ->
processFile(file, directory, directoryPath)
}
}
//第一遍找配置文件
allDirectories.get().forEach { directory ->
// printLog("directory.asFile.absolutePath = ${directory.asFile.absolutePath}")
val directoryPath = directory.asFile.absolutePath
WovenInfoUtils.addClassPath(directory.asFile.absolutePath)
directory.asFile.walk().forEach { file ->
processFile(file,directory.asFile,directoryPath)
}
}
allJars.get().forEach { file ->
if (file.asFile.absolutePath in ignoreJar){
return@forEach
}
aopTaskUtils.processJarForConfig(file.asFile)
}
aopTaskUtils.loadJoinPointConfigEnd(true)
}
private fun searchJoinPointLocation(){
aopTaskUtils.searchJoinPointLocationStart(project)
val addClassMethodRecords = mutableMapOf()
val deleteClassMethodRecords = mutableSetOf()
fun processFile(file : File,directory:File,directoryPath:String){
aopTaskUtils.processFileForSearch(file, directory, directoryPath,addClassMethodRecords, deleteClassMethodRecords)
}
for (directory in ignoreJarClassPaths) {
val directoryPath = directory.absolutePath
directory.walk().forEach { file ->
processFile(file, directory, directoryPath)
}
}
allDirectories.get().forEach { directory ->
val directoryPath = directory.asFile.absolutePath
directory.asFile.walk().forEach { file ->
processFile(file,directory.asFile,directoryPath)
}
}
allJars.get().forEach { file ->
if (file.asFile.absolutePath in ignoreJar){
return@forEach
}
aopTaskUtils.processJarForSearch(file.asFile, addClassMethodRecords, deleteClassMethodRecords)
}
aopTaskUtils.searchJoinPointLocationEnd(addClassMethodRecords, deleteClassMethodRecords)
}
private fun wovenIntoCode(){
WovenInfoUtils.initAllClassName()
WovenInfoUtils.makeReplaceMethodInfoUse()
// logger.error("getClassMethodRecord="+WovenInfoUtils.classMethodRecords)
val hasReplace = WovenInfoUtils.hasReplace()
val hasReplaceExtendsClass = WovenInfoUtils.hasModifyExtendsClass()
val newClasses = mutableListOf()
fun processFile(file : File,directory:File,directoryPath:String){
if (file.isFile) {
val entryName = file.getFileClassname(directory)
if (entryName.isEmpty() || entryName.startsWith("META-INF/") || "module-info.class" == entryName) {
return
}
try {
val entryClazzName = entryName.replace(_CLASS,"")
val relativePath = file.getRelativePath(directory)
val thisClassName = Utils.slashToDotClassName(entryName).replace(_CLASS,"")
val isClassFile = file.name.endsWith(_CLASS)
val isWovenInfoCode = isClassFile
&& AndroidAopConfig.inRules(thisClassName)
&& !entryClazzName.startsWith("kotlinx/") && !entryClazzName.startsWith("kotlin/")
val methodsRecord: HashMap? = WovenInfoUtils.getClassMethodRecord(file.absolutePath)
val isSuspend:Boolean
val realMethodsRecord: HashMap? = if (methodsRecord == null && SuspendReturnScanner.hasSuspendReturn && isWovenInfoCode){
isSuspend = true
val clazzName = entryName.replace(_CLASS,"")
WovenInfoUtils.getAopMethodCutInnerClassInfoInvokeClassInfo(clazzName)
}else {
isSuspend = false
methodsRecord
}
val jarEntryName: String = relativePath.toClassPath()
fun realCopy(){
file.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
}
if (realMethodsRecord != null){
FileInputStream(file).use { inputs ->
val byteArray = try {
WovenIntoCode.modifyClass(inputs.readAllBytes(),realMethodsRecord,hasReplace,isSuspend)
} catch (e: Exception) {
realCopy()
if (isSuspend){
logger.error("Merge directory error1 entry:[${entryName}], error message:$e,如果这个类是包含必须的切点类,请到Github联系作者")
}else{
logger.error("Merge directory error1 entry:[${entryName}], error message:$e,通常情况下你需要先重启Android Studio,然后clean一下项目即可,如果还有问题请到Github联系作者")
}
null
}
byteArray?.let { bytes ->
bytes.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
newClasses.add(bytes)
}
}
}else if (Utils.dotToSlash(Utils.JoinAnnoCutUtils) + _CLASS == entryName) {
FileInputStream(file).use { inputs ->
val originInject = inputs.readAllBytes()
val resultByteArray = RegisterMapWovenInfoCode().execute(originInject.inputStream())
resultByteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
}
}else{
fun copy(){
if (WovenInfoUtils.isHasAopMethodCutInnerClassInfo(entryClazzName)){
FileInputStream(file).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val cr = ClassReader(byteArray)
val cw = ClassWriter(cr,0)
val cv = object : ClassVisitor(Opcodes.ASM9, cw) {
lateinit var className:String
lateinit var superClassName:String
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array?
) {
className = name
superClassName = superName
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array?
): MethodVisitor {
val mv = super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
)
return ReplaceInvokeMethodVisitor(mv,className,superClassName)
}
}
cr.accept(cv, 0)
val newByteArray = cw.toByteArray()
newByteArray.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
} catch (e: Exception) {
realCopy()
}
}else{
realCopy()
}
}
}else{
realCopy()
}
}
val hasCollect = WovenInfoUtils.getAopCollectClassMap()[thisClassName] != null
if (isWovenInfoCode && hasReplace){
FileInputStream(file).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val newByteArray = aopTaskUtils.wovenIntoCodeForReplace(byteArray)
newByteArray.byteArray.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else if (isWovenInfoCode && hasReplaceExtendsClass){
val replaceExtendsClassName = WovenInfoUtils.getModifyExtendsClass(Utils.slashToDotClassName(entryClazzName))
if (replaceExtendsClassName !=null){
FileInputStream(file).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val newByteArray = aopTaskUtils.wovenIntoCodeForExtendsClass(byteArray)
newByteArray.byteArray.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else{
copy()
}
}else if (hasCollect) {
FileInputStream(file).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val cr = ClassReader(byteArray)
val cw = ClassWriter(cr,0)
var thisHasStaticClock = false
val cv = object : ReplaceBaseClassVisitor(cw) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array?
): MethodVisitor? {
val mv = super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
)
thisHasStaticClock = isHasStaticClock
return ReplaceInvokeMethodVisitor(mv,clazzName,oldSuperName)
}
}
cr.accept(cv, 0)
if (!thisHasStaticClock){
WovenIntoCode.wovenStaticCode(cw, thisClassName)
}
val newByteArray = cw.toByteArray()
newByteArray.inputStream().use {
jarOutput.saveEntry(jarEntryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else{
copy()
}
}
} catch (e: Exception) {
logger.error("Merge directory error2 entry:[${entryName}], error message:$e,通常情况下你需要先重启Android Studio,然后clean一下项目即可,如果还有问题请到Github联系作者")
}
}
}
for (directory in ignoreJarClassPaths) {
val directoryPath = directory.absolutePath
directory.walk().sortedBy {
it.name.length
}.forEach { file ->
processFile(file, directory, directoryPath)
}
}
allDirectories.get().forEach { directory ->
val directoryPath = directory.asFile.absolutePath
directory.asFile.walk().sortedBy {
it.name.length
}.forEach { file ->
processFile(file,directory.asFile,directoryPath)
}
}
allJars.get().forEach { file ->
if (file.asFile.absolutePath in ignoreJar){
return@forEach
}
val jarFile = JarFile(file.asFile)
val enumeration = jarFile.entries()
val jarEntryList = mutableListOf()
while (enumeration.hasMoreElements()) {
val jarEntry = enumeration.nextElement()
val entryName = jarEntry.name
// if (jarEntry.isDirectory || entryName.isEmpty() || !entryName.endsWith(_CLASS) || entryName.startsWith("META-INF/")) {
// continue
// }
if (jarEntry.isDirectory || entryName.isEmpty() || entryName.startsWith("META-INF/") || "module-info.class" == entryName) {
continue
}
jarEntryList.add(jarEntry)
}
jarEntryList.sortedBy {
it.name.length
}.forEach { jarEntry ->
try {
val entryName = jarEntry.name
val entryClazzName = entryName.replace(_CLASS,"")
val thisClassName = Utils.slashToDotClassName(entryName).replace(_CLASS,"")
val isClassFile = entryName.endsWith(_CLASS)
val isWovenInfoCode = isClassFile
&& AndroidAopConfig.inRules(thisClassName)
&& !entryName.startsWith("kotlinx/") && !entryName.startsWith("kotlin/")
val methodsRecord: HashMap? = WovenInfoUtils.getClassMethodRecord(entryName)
val isSuspend:Boolean
val realMethodsRecord: HashMap? = if (methodsRecord == null && SuspendReturnScanner.hasSuspendReturn && isWovenInfoCode){
isSuspend = true
WovenInfoUtils.getAopMethodCutInnerClassInfoInvokeClassInfo(entryClazzName)
}else {
isSuspend = false
methodsRecord
}
fun realCopy(){
jarFile.getInputStream(jarEntry).use {
jarOutput.saveEntry(entryName,it)
}
}
if (realMethodsRecord != null){
jarFile.getInputStream(jarEntry).use { inputs ->
val byteArray = try {
WovenIntoCode.modifyClass(inputs.readAllBytes(),realMethodsRecord,hasReplace,isSuspend)
} catch (e: Exception) {
realCopy()
if (isSuspend){
logger.error("Merge jar error1 entry:[${jarEntry.name}], error message:$e,如果这个类是包含必须的切点类,请到Github联系作者")
}else{
logger.error("Merge jar error1 entry:[${jarEntry.name}], error message:$e,通常情况下你需要先重启Android Studio,然后clean一下项目即可,如果还有问题请到Github联系作者")
}
null
}
byteArray?.let {
it.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
newClasses.add(it)
}
}
}else if (Utils.dotToSlash(Utils.JoinAnnoCutUtils) + _CLASS == entryName) {
jarFile.getInputStream(jarEntry).use { inputs ->
val originInject = inputs.readAllBytes()
val resultByteArray = RegisterMapWovenInfoCode().execute(originInject.inputStream())
resultByteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
}
} else{
fun copy(){
if (WovenInfoUtils.isHasAopMethodCutInnerClassInfo(entryClazzName)){
jarFile.getInputStream(jarEntry).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val cr = ClassReader(byteArray)
val cw = ClassWriter(cr,0)
val cv = object : ClassVisitor(Opcodes.ASM9, cw) {
lateinit var className:String
lateinit var superClassName:String
override fun visit(
version: Int,
access: Int,
name: String,
signature: String?,
superName: String,
interfaces: Array?
) {
className = name
superClassName = superName
super.visit(version, access, name, signature, superName, interfaces)
}
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array?
): MethodVisitor {
val mv = super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
)
return ReplaceInvokeMethodVisitor(mv,className,superClassName)
}
}
cr.accept(cv, 0)
val newByteArray = cw.toByteArray()
newByteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
realCopy()
}
}else{
realCopy()
}
}
}else{
realCopy()
}
}
val hasCollect = WovenInfoUtils.getAopCollectClassMap()[thisClassName] != null
if (isWovenInfoCode && hasReplace){
jarFile.getInputStream(jarEntry).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val newByteArray = aopTaskUtils.wovenIntoCodeForReplace(byteArray)
newByteArray.byteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else if(isWovenInfoCode && hasReplaceExtendsClass){
val replaceExtendsClassName = WovenInfoUtils.getModifyExtendsClass(Utils.slashToDotClassName(entryClazzName))
if (replaceExtendsClassName !=null){
jarFile.getInputStream(jarEntry).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val newByteArray = aopTaskUtils.wovenIntoCodeForExtendsClass(byteArray)
newByteArray.byteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else{
copy()
}
}else if (hasCollect) {
jarFile.getInputStream(jarEntry).use { inputs ->
val byteArray = inputs.readAllBytes()
if (byteArray.isNotEmpty()){
try {
val cr = ClassReader(byteArray)
val cw = ClassWriter(cr,0)
var thisHasStaticClock = false
val cv = object : ReplaceBaseClassVisitor(cw) {
override fun visitMethod(
access: Int,
name: String,
descriptor: String,
signature: String?,
exceptions: Array?
): MethodVisitor? {
val mv = super.visitMethod(
access,
name,
descriptor,
signature,
exceptions
)
thisHasStaticClock = isHasStaticClock
return ReplaceInvokeMethodVisitor(mv,clazzName,oldSuperName)
}
}
cr.accept(cv, 0)
if (!thisHasStaticClock){
WovenIntoCode.wovenStaticCode(cw, thisClassName)
}
val newByteArray = cw.toByteArray()
newByteArray.inputStream().use {
jarOutput.saveEntry(entryName,it)
}
// newClasses.add(newByteArray)
} catch (e: Exception) {
copy()
}
}else{
copy()
}
}
}else{
copy()
}
}
} catch (e: Exception) {
// e.printStackTrace()
// throw RuntimeException("Merge jar error entry:[${jarEntry.name}], error message:$e,通常情况下你需要先重启Android Studio,然后clean一下项目即可,如果还有问题请到Github联系作者")
logger.error("Merge jar error2 entry:[${jarEntry.name}], error message:$e,通常情况下你需要先重启Android Studio,然后clean一下项目即可,如果还有问题请到Github联系作者")
}
}
jarFile.close()
}
ClassFileUtils.wovenInfoInvokeClass(newClasses)
if (!ClassFileUtils.reflectInvokeMethod){
for (file in ClassFileUtils.outputDir.walk()) {
if (file.isFile) {
val className = file.getFileClassname(ClassFileUtils.outputDir)
val invokeClassName = Utils.slashToDot(className).replace(_CLASS,"")
if (!WovenInfoUtils.containsInvokeClass(invokeClassName)){
file.inputStream().use {
jarOutput.saveEntry(className,it)
}
}
}
}
}
val collectDir = File(Utils.aopTransformCollectTempDir(project,variant))
WovenIntoCode.createCollectClass(collectDir)
for (file in collectDir.walk()) {
if (file.isFile) {
val className = file.getFileClassname(collectDir)
val invokeClassName = Utils.slashToDot(className).replace(_CLASS,"")
if (!WovenInfoUtils.containsInvokeClass(invokeClassName)){
file.inputStream().use {
jarOutput.saveEntry(className,it)
}
}
}
}
if (!AndroidAopConfig.debug){
ClassFileUtils.outputDir.deleteRecursively()
collectDir.deleteRecursively()
}
exportCutInfo()
}
private fun JarOutputStream.saveEntry(entryName: String, inputStream: InputStream) {
putNextEntry(JarEntry(entryName))
inputStream.copyTo( this)
closeEntry()
}
private fun exportCutInfo(){
if (!AndroidAopConfig.cutInfoJson){
return
}
InitConfig.exportCutInfo()
}
}