其他
Android 字节码插桩库,也许有你需要的
The following article is from 字节数组 Author 业志陈
ASM 字节码插桩:实现双击防抖 ASM 字节码插桩:进行线程整治 ASM 字节码插桩:助力隐私合规 ASM 字节码插桩:监控大图加载 ASM 字节码插桩:从 Lambda 表达式讲起 ASM 字节码插桩:Jetpack Compose 实现双击防抖
Trace 的 Github 地址:https://github.com/leavesCZY/Trace
应用双击防抖。包括 Android 原生的 View 体系以及目前流行的 Jetpack Compose,对应第一篇和最后两篇文章。 替换 Class 的继承关系。可用于非侵入式地实现监控大图加载的功能,对应第四篇文章。 修复 Toast 在 Android 7.1 上的系统 bug。这是我新写的一个功能点,用于解决在 Android 7.1 系统上 Toast 由于 WindowToken 失效从而导致应用崩溃的问题。
下面就来介绍如何在项目中接入 Trace,主要的实现思路参照以上文章即可。
//grovy
plugins {
id "io.github.leavesczy.trace" version "latestVersion" apply false
}
//kts
plugins {
id("io.github.leavesczy.trace").version("latestVersion").apply(false)
}
//grovy
plugins {
id("io.github.leavesczy.trace")
}
clickTrace {
view.onClickClass = "x"
view.onClickMethodName = "x"
view.uncheckViewOnClickAnnotation = "x"
view.include = []
view.exclude = []
compose.onClickClass = "x"
compose.onClickWhiteList = "x"
}
replaceClassTrace {
originClass = "x"
targetClass = "x"
include = []
exclude = []
}
toastTrace {
toasterClass = "x"
showToastMethodName = "x"
}
//kts
plugins {
id("io.github.leavesczy.trace")
}
clickTrace {
view {
onClickClass = "x"
onClickMethodName = "x"
uncheckViewOnClickAnnotation = "x"
include = listOf()
exclude = listOf()
}
compose {
onClickClass = "x"
onClickWhiteList = "x"
}
}
replaceClassTrace {
originClass = "x"
targetClass = "x"
include = listOf()
exclude = listOf()
}
toastTrace {
toasterClass = "x"
showToastMethodName = "x"
}
1、View
clickTrace {
//必填参数
view.onClickClass = "x"
view.onClickMethodName = "x"
//可选参数
view.uncheckViewOnClickAnnotation = "x"
view.include = []
view.exclude = []
}
//插桩前
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(view: View) {
//TODO
}
})
//插桩后
view.setOnClickListener(object : View.OnClickListener {
override fun onClick(view: View) {
if (!ViewClickMonitor.isEnabled(view)){
return
}
//TODO
}
})
object ViewClickMonitor {
@JvmStatic
fun isEnabled(view: View): Boolean {
val isEnabled: Boolean
//TODO
return isEnabled
}
}
package github.leavesczy.trace
object ViewClickMonitor {
private const val MIN_DURATION = 500L
private var lastClickTime = 0L
private var clickIndex = 0
@JvmStatic
fun isEnabled(view: View): Boolean {
clickIndex++
val currentTime = SystemClock.elapsedRealtime()
val isEnabled = currentTime - lastClickTime > MIN_DURATION
if (isEnabled) {
lastClickTime = currentTime
}
log("onClick $clickIndex , isEnabled : $isEnabled")
return isEnabled
}
private fun log(log: String) {
Log.e(javaClass.simpleName, log)
}
}
clickTrace {
view.onClickClass = "github.leavesczy.trace.ViewClickMonitor"
view.onClickMethodName = "isEnabled"
}
clickTrace {
//过滤包含特定注解的 onClick 事件
view.uncheckViewOnClickAnnotation = "x"
//仅对特定类或者特定包名中的 onClick 事件进行拦截检测
view.include = ["x"]
//过滤特定类或者特定包名中的 onClick 事件
view.exclude = ["x"]
}
package github.leavesczy.trace
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class UncheckViewOnClick
findViewById<View>(R.id.tvObjectUnCheck).setOnClickListener(
object : View.OnClickListener {
@UncheckViewOnClick
override fun onClick(view: View) {
onClickView()
}
})
include 用于设定 ClickTrace 的生效范围。参数值在为空的情况下代表着对所有所有模块均生效,传值后则只对该参数值代表的模块生效。 exclude 用于设定 ClickTrace 的排除范围。用于在 include 限定的范围内再排除特定模块。
包含 UncheckViewOnClick 注解的 onClick 回调不会进行双击防抖。 仅在 github.leavesczy.trace.xxx 包名下的类会进行双击防抖,但 github.leavesczy.trace.mylibrary.xxx 包名下的类除外。
clickTrace {
view.uncheckViewOnClickAnnotation = "github.leavesczy.trace.UncheckViewOnClick"
view.include = ["^github\\.leavesczy\\.trace.*"]
view.exclude = ["^github\\.leavesczy\\.trace\\.mylibrary.*"]
}
2、Jetpack Compose
clickTrace {
//必填参数
compose.onClickClass = "x"
//可选参数
compose.onClickWhiteList = "x"
}
class ComposeOnClick(private val onClick: () -> Unit) : Function0<Unit> {
override fun invoke() {
//TODO
}
}
package github.leavesczy.trace
class ComposeOnClick(private val onClick: () -> Unit) : Function0<Unit> {
companion object {
private const val MIN_DURATION = 500L
private var lastClickTime = 0L
}
override fun invoke() {
val currentTime = SystemClock.elapsedRealtime()
val isEnabled = currentTime - lastClickTime > MIN_DURATION
log("onClick isEnabled : $isEnabled")
if (isEnabled) {
lastClickTime = currentTime
onClick()
}
}
private fun log(log: String) {
Log.e(
javaClass.simpleName,
"${System.identityHashCode(this)} ${System.identityHashCode(onClick)} $log"
)
}
}
clickTrace {
compose.onClickClass = "github.leavesczy.trace.ComposeOnClick"
compose.onClickWhiteList = "notCheck"
}
replaceClassTrace {
//必填参数
originClass = "x"
targetClass = "x"
//可选参数
include = []
exclude = []
}
replaceClassTrace {
originClass = "android.widget.ImageView"
targetClass = "github.leavesczy.trace.MonitorImageView"
include = []
exclude = [".*\\.IgnoreImageView\$"]
}
toastTrace {
toasterClass = "x"
showToastMethodName = "x"
}
package github.leavesczy.trace
object Toaster {
@JvmStatic
fun showToast(toast: Toast) {
hookToastIfNeed(toast)
toast.show()
}
@SuppressLint("DiscouragedPrivateApi")
private fun hookToastIfNeed(toast: Toast) {
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.N_MR1) {
try {
val cToast = Toast::class.java
val fTn = cToast.getDeclaredField("mTN")
fTn.isAccessible = true
val oTn = fTn.get(toast)
val cTn = oTn.javaClass
val fHandle = cTn.getDeclaredField("mHandler")
fHandle.isAccessible = true
fHandle.set(oTn, ProxyHandler(fHandle.get(oTn) as Handler))
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
private class ProxyHandler(private val mHandler: Handler) : Handler(mHandler.looper) {
override fun handleMessage(msg: Message) {
try {
mHandler.handleMessage(msg)
} catch (e: Throwable) {
e.printStackTrace()
}
}
}
}
toastTrace {
toasterClass = "github.leavesczy.trace.Toaster"
showToastMethodName = "showToast"
}
Trace 在 AGP 7.0+ 和 8.0+ 均已测试通过,更低版本的 AGP 则没有特意进行试验。 Trace 目前处于刚起步阶段,可能还会存在一些 bug,但由于 Trace 是以 Gradle Plugin 的形式引入到项目中的,引入成本和移除成本都很低,有需要的话还是值得一试的。 Trace Plugin 也托管到了 GradlePluginPortal,可以在此查看:https://plugins.gradle.org/plugin/io.github.leavesczy.trace
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!