统一项目中的线程池,Executors封装的几个线程池比较好操作,就以此为切入点了。闲话不多说,开始撸。
几个静态方法ThreadUtil
public class ThreadUtil {
private static final int coreSize = Runtime.getRuntime().availableProcessors() + 1;
private static final ExecutorService fix = Executors.newFixedThreadPool(coreSize);
private static final ExecutorService single = Executors.newSingleThreadExecutor();
private static final ExecutorService cache = Executors.newCachedThreadPool();
private static final ExecutorService scheduled = Executors.newScheduledThreadPool(coreSize);
public static ExecutorService threadPool() {
return cache;
}
}
准备将项目中所有的线程池替换成上面的cache
,写个threadPool()
静态方法供asm替换。
老样子ThreadClassVisitor
class ThreadClassVisitor(classVisitor: ClassVisitor) : ClassVisitor(Opcodes.ASM9, classVisitor) {
override fun visitMethod(
access: Int,
name: String?,
descriptor: String?,
signature: String?,
exceptions: Array<out String>?
): MethodVisitor {
val mv = cv.visitMethod(access, name, descriptor, signature, exceptions)
return ThreadMethodVisitor(mv, access, name, descriptor)
}
}
ThreadMethodVisitor
重写visitMethodInsn()
方法。
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
visitMethodInsn()
中根据参数判断是否是Executors.newCachedThreadPool()
等创建线程池的方法。这里多说一嘴,站在字节码的角度看,owner
、name
、descriptor
就可以确定是哪个类的方法。以Executors此为例,看看字节码。图方便,直接对上述ThreadUtil.class
文件使用ASM Bytecode Viewer
插件。
先看字节码
Bytecode
GETSTATIC com/chenxuan/hook/ThreadUtil.coreSize : I
INVOKESTATIC java/util/concurrent/Executors.newFixedThreadPool (I)Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.fix : Ljava/util/concurrent/ExecutorService;
INVOKESTATIC java/util/concurrent/Executors.newSingleThreadExecutor ()Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.singe : Ljava/util/concurrent/ExecutorService;
INVOKESTATIC java/util/concurrent/Executors.newCachedThreadPool ()Ljava/util/concurrent/ExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.cache : Ljava/util/concurrent/ExecutorService;
GETSTATIC com/chenxuan/hook/ThreadUtil.coreSize : I
INVOKESTATIC java/util/concurrent/Executors.newScheduledThreadPool (I)Ljava/util/concurrent/ScheduledExecutorService;
PUTSTATIC com/chenxuan/hook/ThreadUtil.scheduled : Ljava/util/concurrent/ExecutorService;
对应看asm api ASMmified
methodVisitor.visitFieldInsn(GETSTATIC, "com/chenxuan/hook/ThreadUtil", "coreSize", "I");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newFixedThreadPool", "(I)Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "fix", "Ljava/util/concurrent/ExecutorService;")
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newSingleThreadExecutor", "()Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "singe", "Ljava/util/concurrent/ExecutorService;");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newCachedThreadPool", "()Ljava/util/concurrent/ExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "cache", "Ljava/util/concurrent/ExecutorService;");
methodVisitor.visitFieldInsn(GETSTATIC, "com/chenxuan/hook/ThreadUtil", "coreSize", "I");
methodVisitor.visitMethodInsn(INVOKESTATIC, "java/util/concurrent/Executors", "newScheduledThreadPool", "(I)Ljava/util/concurrent/ScheduledExecutorService;", false);
methodVisitor.visitFieldInsn(PUTSTATIC, "com/chenxuan/hook/ThreadUtil", "scheduled", "Ljava/util/concurrent/ExecutorService;");
很清晰了:
- opcode->INVOKESTATIC 静态方法
- owner->java/util/concurrent/Executors 所属类
- name->newCachedThreadPool 方法名
- descriptor->()Ljava/util/concurrent/ExecutorService 方法参数和返回值
- isInterface->false 是否接口方法
为了方便匹配这几个方法和做替换,建一个实体类ThreadMethod
描述方法模型。
data class ThreadMethod(
var opcode: Int = Opcodes.INVOKESTATIC,
var owner: String?,
var name: String?,
var descriptor: String?,
var isInterface: Boolean = false
) {
fun equalThreadMethod(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
) =
this.opcode == opcode && this.owner == owner && this.name == name && this.descriptor == descriptor
}
写个equalThreadMethod()
方法匹配visitMethodInsn
传过来的参数,还需要一个集合threadMethods
保存Executors的几个静态方法。
internal val threadMethods = mutableListOf<ThreadMethod>().apply {
add(
ThreadMethod(
owner = "java/util/concurrent/Executors",
name = "newCachedThreadPool",
descriptor = "()Ljava/util/concurrent/ExecutorService;"
)
)
}
偷个懒,先匹配Executors.newCachedThreadPool()
,接下来在visitMethodInsn
中判断并替换为自定义的线程池。
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (containsThread(opcode, owner, name, descriptor)) {
//替换
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
private fun containsThread(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
): Boolean {
threadMethods.forEach {
if (it.equalThreadMethod(opcode, owner, name, descriptor)) {
return true
}
}
return false
}
}
替换的方法直接在这里写死也不太好,用ThreadMethod
包装一下
internal val realThreadMethod = ThreadMethod(
owner = "com/chenxuan/hook/ThreadUtil",
name = "threadPool",
descriptor = "()Ljava/util/concurrent/ExecutorService;"
)
修改visitMethodInsn()
替换处
object ThreadMethodVisitor {
operator fun invoke(
mv: MethodVisitor,
access: Int,
name: String?,
descriptor: String?,
): MethodVisitor {
return object : AdviceAdapter(Opcodes.ASM9, mv, access, name, descriptor) {
override fun visitMethodInsn(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?,
isInterface: Boolean
) {
if (containsThread(opcode, owner, name, descriptor)) {
super.visitMethodInsn(
realThreadMethod.opcode,
realThreadMethod.owner,
realThreadMethod.name,
realThreadMethod.descriptor,
realThreadMethod.isInterface
)
} else {
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
}
}
}
}
private fun containsThread(
opcode: Int,
owner: String?,
name: String?,
descriptor: String?
): Boolean {
threadMethods.forEach {
if (it.equalThreadMethod(opcode, owner, name, descriptor)) {
return true
}
}
return false
}
}
跑个测试用例MainActivity
,build。
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
trackMethod()
loadPic()
hookThread()
}
private fun hookThread() {
val cache = Executors.newCachedThreadPool()
}
private fun loadPic() {
Glide
.with(this)
.load("https://pic.3gbizhi.com/2014/0430/20140430043839656.jpg")
.into(findViewById(R.id.ivAvatar))
}
@Track
private fun trackMethod() {
val data = mutableListOf<String>()
}
}
查看transform下处理过的MainActivity并反编译成Java,关注hookThread()就好。
成功替换为ThreadUtil.threadPool()。后续补充threadMethods将其它几个静态方法添加进去,当然还有ThreadPoolExecutor构造方法,收敛所有创建线程池的方法,然后还有new Thread之类的写法处理到cacheThreadPool中基本就ok了。还可以增加白名单,并非所有线程池都需要替换。感觉吧,asm确实是可以为所欲为啊。