寫了幾個demo之後感覺稍微有點悟了,繼續肝它!
統一項目中的線程池,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確實是可以爲所欲爲啊。