Skip to content

Commit

Permalink
异步并发处理
Browse files Browse the repository at this point in the history
  • Loading branch information
Peakmain committed May 19, 2022
1 parent 22dc550 commit 9e2afec
Show file tree
Hide file tree
Showing 15 changed files with 261 additions and 95 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
package com.peakmain.analytics.plugin.transform

import com.peakmain.analytics.plugin.ext.MonitorConfig
import com.peakmain.analytics.plugin.visitor.PeakmainVisitor
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.compress.utils.IOUtils
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassWriter

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.regex.Matcher

class MonitorAnalyticsTransform {
private static HashSet<String> exclude = new HashSet<>(['com.peakmain.sdk',
'android.support',
'androidx',
'com.qiyukf',
'android.arch',
'com.google.android',
"com.tencent.smtt",
"com.umeng.message",
"com.xiaomi.push",
"com.huawei.hms",
"cn.jpush.android",
"cn.jiguang",
"com.meizu.cloud.pushsdk",
"com.vivo.push",
"com.igexin",
"com.getui",
"com.xiaomi.mipush.sdk",
"com.heytap.msp.push",
'com.bumptech.glide',
'com.tencent.tinker'])
/** 将一些特例需要排除在外 */
private static final HashSet<String> special = ['android.support.design.widget.TabLayout$ViewPagerOnTabSelectedListener',
'com.google.android.material.tabs.TabLayout$ViewPagerOnTabSelectedListener',
'android.support.v7.app.ActionBarDrawerToggle',
'androidx.appcompat.app.ActionBarDrawerToggle',
'androidx.fragment.app.FragmentActivity',
'androidx.core.app.NotificationManagerCompat',
'androidx.core.app.ComponentActivity',
'android.support.v4.app.NotificationManagerCompat',
'android.support.v4.app.SupportActivity',
'cn.jpush.android.service.PluginMeizuPlatformsReceiver',
'androidx.appcompat.widget.ActionMenuPresenter$OverflowMenuButton',
'android.widget.ActionMenuPresenter$OverflowMenuButton',
'android.support.v7.widget.ActionMenuPresenter$OverflowMenuButton']
/**
* 过滤不需要修改的class
*/
protected static boolean isShouldModify(String className) {
if (!isAndroidGenerated(className)) {
for (pkgName in special) {
if (className.startsWith(pkgName)) {
return true
}
}
} else {
if (!isLeanback(className)) {
for (pkgName in exclude) {
if (className.startsWith(pkgName)) {
return false
}
}
}
}
return true
}

private static boolean isLeanback(String className) {
return className.startsWith("android.support.v17.leanback") || className.startsWith("androidx.leanback")
}

private static boolean isAndroidGenerated(String className) {
return className.contains('R$') ||
className.contains('R2$') ||
className.contains('R.class') ||
className.contains('R2.class') ||
className.contains('BuildConfig.class')
}

static File modifyClassFile(File dir, File classFile, File tempDir,MonitorConfig monitorConfig) {
File modified = null
try {
String className = path2ClassName(classFile.absolutePath.replace(dir.absolutePath + File.separator, ""))
byte[] sourceClassBytes = IOUtils.toByteArray(new FileInputStream(classFile))
byte[] modifiedClassBytes = modifyClass(sourceClassBytes,monitorConfig)
if (modifiedClassBytes) {
modified = new File(tempDir, className.replace('.', '') + '.class')
if (modified.exists()) {
modified.delete()
}
modified.createNewFile()
new FileOutputStream(modified).write(modifiedClassBytes)
}
} catch (Exception e) {
e.printStackTrace()
modified = classFile
}
return modified
}

private static byte[] modifyClass(byte[] srcClass,MonitorConfig monitorConfig) throws IOException {
ClassWriter classWriter = new ClassWriter(ClassWriter.COMPUTE_MAXS)
PeakmainVisitor classVisitor = new PeakmainVisitor(classWriter,monitorConfig)
ClassReader cr = new ClassReader(srcClass)
cr.accept(classVisitor, ClassReader.SKIP_FRAMES)
return classWriter.toByteArray()
}

static File modifyJar(File jarFile, File tempDir, boolean nameHex,MonitorConfig monitorConfig) {
/**
* 读取原 jar
*/
def file = new JarFile(jarFile, false)

/**
* 设置输出到的 jar
*/
def hexName = ""
if (nameHex) {
hexName = DigestUtils.md5Hex(jarFile.absolutePath).substring(0, 8)
}
def outputJar = new File(tempDir, hexName + jarFile.name)
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(outputJar))
Enumeration enumeration = file.entries()
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
InputStream inputStream = null
try {
inputStream = file.getInputStream(jarEntry)
} catch (Exception e) {
return null
}
String entryName = jarEntry.getName()
if (entryName.endsWith(".DSA") || entryName.endsWith(".SF")) {
//ignore
} else {
String className
JarEntry jarEntry2 = new JarEntry(entryName)
jarOutputStream.putNextEntry(jarEntry2)

byte[] modifiedClassBytes = null
byte[] sourceClassBytes = IOUtils.toByteArray(inputStream)
if (entryName.endsWith(".class")) {
className = entryName.replace(Matcher.quoteReplacement(File.separator), ".").replace(".class", "")
if (isShouldModify(className)) {
modifiedClassBytes = modifyClass(sourceClassBytes,monitorConfig)
}
}
if (modifiedClassBytes == null) {
modifiedClassBytes = sourceClassBytes
}
jarOutputStream.write(modifiedClassBytes)
jarOutputStream.closeEntry()
}
}
jarOutputStream.close()
file.close()
return outputJar
}
static String path2ClassName(String pathName) {
pathName.replace(File.separator, ".").replace(".class", "")
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,31 +2,29 @@ package com.peakmain.analytics.plugin.transform

import com.android.build.api.transform.*
import com.android.build.gradle.internal.pipeline.TransformManager
import com.android.ide.common.internal.WaitableExecutor
import com.peakmain.analytics.plugin.ext.MonitorConfig
import com.peakmain.analytics.plugin.visitor.PeakmainVisitor
import groovy.io.FileType
import org.apache.commons.codec.digest.DigestUtils
import org.apache.commons.io.FileUtils
import org.apache.commons.io.IOUtils
import org.gradle.api.Project
import org.objectweb.asm.ClassReader
import org.objectweb.asm.ClassVisitor
import org.objectweb.asm.ClassWriter

import java.util.jar.JarEntry
import java.util.jar.JarFile
import java.util.jar.JarOutputStream
import java.util.zip.ZipEntry
import java.util.concurrent.Callable

class MonitorTransform extends Transform {
private static Project project
private MonitorConfig monitorConfig
private WaitableExecutor waitableExecutor

MonitorTransform(Project project) {
this.project = project
}

void setMonitorConfig(MonitorConfig monitorConfig) {
this.monitorConfig = monitorConfig
if (!monitorConfig.disableMultiThreadBuild) {
waitableExecutor = WaitableExecutor.useGlobalSharedThreadPool()
}
}

@Override
Expand Down Expand Up @@ -89,95 +87,85 @@ class MonitorTransform extends Transform {
inputs.each { TransformInput input ->
//遍历目录
input.directoryInputs.each { DirectoryInput directoryInput ->
handleDirectoryInput(directoryInput, outputProvider)
if (waitableExecutor) {
waitableExecutor.execute(new Callable<Object>() {
@Override
Object call() throws Exception {
handleDirectoryInput(context, directoryInput, outputProvider,monitorConfig)
return null
}
})
} else {
handleDirectoryInput(context, directoryInput, outputProvider,monitorConfig)
}
}
// 遍历jar 第三方引入的 class
input.jarInputs.each { JarInput jarInput ->
handleJarInput(jarInput, outputProvider)
if (waitableExecutor) {
waitableExecutor.execute(new Callable<Object>() {
@Override
Object call() throws Exception {
handleJarInput(context,jarInput, outputProvider,monitorConfig)
return null
}
})
} else {
handleJarInput(context,jarInput, outputProvider,monitorConfig)
}
}
}
if (waitableExecutor) {
waitableExecutor.waitForTasksWithQuickFail(true)
}
println("[MonitorTransform]: 此次编译共耗时:${System.currentTimeMillis() - startTime}毫秒")
}

void handleDirectoryInput(DirectoryInput directoryInput, TransformOutputProvider outputProvider) {
if (directoryInput.file.isDirectory()) {
directoryInput.file.eachFileRecurse { File file ->
String name = file.name
if (filterClass(name)) {
// 用来读 class 信息
ClassReader classReader = new ClassReader(file.bytes)
// 用来写
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
//todo 改这里就可以了
ClassVisitor classVisitor = new PeakmainVisitor(classWriter, monitorConfig)
// 下面还可以包多层
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
// 重新覆盖写入文件
byte[] code = classWriter.toByteArray()
FileOutputStream fos = new FileOutputStream(
file.parentFile.absolutePath + File.separator + name)
fos.write(code)
fos.close()
}
}
}
// 把修改好的数据,写入到 output
def dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes,
directoryInput.scopes, Format.DIRECTORY)
FileUtils.copyDirectory(directoryInput.file, dest)
}
void handleDirectoryInput(Context context, DirectoryInput directoryInput, TransformOutputProvider outputProvider,MonitorConfig monitorConfig) {

void handleJarInput(JarInput jarInput, TransformOutputProvider outputProvider) {
if (jarInput.file.absolutePath.endsWith(".jar")) {
// 重名名输出文件,因为可能同名,会覆盖
def jarName = jarInput.name
def md5Name = DigestUtils.md5Hex(jarInput.file.getAbsolutePath())
if (jarName.endsWith(".jar")) {
jarName = jarName.substring(0, jarName.length() - 4)
File dest = outputProvider.getContentLocation(directoryInput.name, directoryInput.contentTypes, directoryInput.scopes, Format.DIRECTORY)
File dir = directoryInput.file
if (dir) {
HashMap<String, File> modifyMap = new HashMap<>()
dir.traverse(type: FileType.FILES, nameFilter: ~/.*\.class/) {
File classFile ->
if (MonitorAnalyticsTransform.isShouldModify(classFile.name)) {
File modified = MonitorAnalyticsTransform.modifyClassFile(dir, classFile, context.getTemporaryDir(),monitorConfig)
if (modified != null) {
String ke = classFile.absolutePath.replace(dir.absolutePath, "")
modifyMap.put(ke, modified)
}
}
}
JarFile jarFile = new JarFile(jarInput.file)
Enumeration enumeration = jarFile.entries()
File tmpFile = new File(jarInput.file.getParent() + File.separator + "classes_temp.jar")
if (tmpFile.exists()) {
tmpFile.delete()
FileUtils.copyDirectory(directoryInput.file, dest)
modifyMap.entrySet().each {
Map.Entry<String, File> en ->
File target = new File(dest.absolutePath + en.getKey())
if (target.exists()) {
target.delete()
}
FileUtils.copyFile(en.getValue(), target)
en.getValue().delete()
}
JarOutputStream jarOutputStream = new JarOutputStream(new FileOutputStream(tmpFile))
//用于保存
while (enumeration.hasMoreElements()) {
JarEntry jarEntry = (JarEntry) enumeration.nextElement()
String entryName = jarEntry.getName()
ZipEntry zipEntry = new ZipEntry(entryName)
InputStream inputStream = jarFile.getInputStream(jarEntry)
//插桩class
if (filterClass(entryName)) {
//class文件处理
jarOutputStream.putNextEntry(zipEntry)
ClassReader classReader = new ClassReader(IOUtils.toByteArray(inputStream))
ClassWriter classWriter = new ClassWriter(classReader, ClassWriter.COMPUTE_MAXS)
//todo 改这里就可以了
ClassVisitor classVisitor = new PeakmainVisitor(classWriter, monitorConfig)
// 下面还可以包多层
classReader.accept(classVisitor, ClassReader.EXPAND_FRAMES)
byte[] code = classWriter.toByteArray()
jarOutputStream.write(code)
} else {
jarOutputStream.putNextEntry(zipEntry)
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
jarOutputStream.closeEntry()
}
//结束
jarOutputStream.close()
jarFile.close()
def dest = outputProvider.getContentLocation(jarName + md5Name,
jarInput.contentTypes, jarInput.scopes, Format.JAR)
FileUtils.copyFile(tmpFile, dest)
tmpFile.delete()
}
}

static boolean filterClass(String className) {
return (className.endsWith(".class") && !className.startsWith("R\$")
&& "R.class" != className && "BuildConfig.class" != className)
void handleJarInput(Context context,JarInput jarInput, TransformOutputProvider outputProvider,MonitorConfig monitorConfig) {
String destName = jarInput.file.name

/**截取文件路径的 md5 值重命名输出文件,因为可能同名,会覆盖*/
def hexName = DigestUtils.md5Hex(jarInput.file.absolutePath).substring(0, 8)
/** 获取 jar 名字*/
if (destName.endsWith(".jar")) {
destName = destName.substring(0, destName.length() - 4)
}

/** 获得输出文件*/
File dest = outputProvider.getContentLocation(destName + "_" + hexName, jarInput.contentTypes, jarInput.scopes, Format.JAR)
def modifiedJar = MonitorAnalyticsTransform.modifyJar(jarInput.file, context.getTemporaryDir(), true,monitorConfig)
if (modifiedJar == null) {
modifiedJar = jarInput.file
}
FileUtils.copyFile(modifiedJar, dest)
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import com.peakmain.analytics.plugin.utils.MethodFieldUtils
/***
* .::::.
* .::::::::.
* ::::::::::: FUCK YOU
* :::::::::::
* ..:::::::::::'
* '::::::::::::'
* .::::::::::
Expand Down Expand Up @@ -43,6 +43,17 @@ class Logger {
static void setDebug(boolean isDebug) {
debug = isDebug
}
/**
* 打印日志
*/
def static info(Object msg) {
if (debug)
try {
println "[PeakmainPlugin]: ${msg}"
} catch (Exception e) {
e.printStackTrace()
}
}

def static error(Object msg) {
if (!debug)
Expand Down
Binary file not shown.
Loading

0 comments on commit 9e2afec

Please sign in to comment.