com.hina.analytics.android.plugin.js.SensorsAnalyticsWebViewMethodVisitor.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of auto-plugin Show documentation
Show all versions of auto-plugin Show documentation
The official Android SDK Plugin for Hina Data Track.
The newest version!
package com.hina.analytics.android.plugin.js
import com.hina.analytics.android.gradle.replaceSlashByDot
import com.hina.analytics.android.gradle.ClassInheritance
import com.hina.analytics.android.plugin.ClassNameAnalytics
import com.hina.analytics.android.plugin.utils.Logger
import com.hina.analytics.android.plugin.utils.SAUtils
import org.objectweb.asm.Label
import org.objectweb.asm.MethodVisitor
import org.objectweb.asm.Opcodes
import org.objectweb.asm.Type
import org.objectweb.asm.commons.AdviceAdapter
/**
* 判断逻辑:
*
* 如果当前方法所在的类是 WebView 的子类,并且被处理的方法是目标方法中的一个就不处理;
* 否则就判断 owner 是否是 WebView 的子类,如果是就处理,否则不处理。
*/
class SensorsAnalyticsWebViewMethodVisitor(
api: Int,
methodVisitor: MethodVisitor?,
access: Int,
name: String?,
descriptor: String,
private val classNameAnalytics: ClassNameAnalytics,
private val classInheritance: ClassInheritance
) : AdviceAdapter(api, methodVisitor, access, name, descriptor), Opcodes {
companion object {
const val JS_BRIDGE_API = "com/hina/analytics/h5/aop/WebViewHookAop"
val TARGET_NAME_DESC = mutableListOf(
"loadUrl(Ljava/lang/String;)V", "loadUrl(Ljava/lang/String;Ljava/util/Map;)V",
"loadData(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
"loadDataWithBaseURL(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)V",
"postUrl(Ljava/lang/String;[B)V"
)
const val VIEW_DESC = "Landroid/view/View;"
val OWNER_WHITE_SET = mutableSetOf("android/webkit/WebView", "com/tencent/smtt/sdk/WebView")
}
private val methodNameDesc: String = name + descriptor
private var shouldSkip = false
init {
//如果当前方法符合目标定义,而且此类是 WebView 的子类,那么就跳过 visitMethodInsn 的指令
if (TARGET_NAME_DESC.contains(methodNameDesc) && isAssignableWebView(classNameAnalytics.className)) {
shouldSkip = true
}
}
override fun visitMethodInsn(
opcode: Int,
owner: String,
name: String,
desc: String,
itf: Boolean
) {
if (shouldSkip) {
super.visitMethodInsn(opcode, owner, name, desc, itf)
return
}
try {
if (opcode != INVOKESTATIC && TARGET_NAME_DESC.contains(name + desc)) {
// 解决 NoClassDefError 问题
if (classNameAnalytics.superClass == "com/tencent/smtt/sdk/WebViewClient") {
super.visitMethodInsn(opcode, owner, name, desc, itf)
return
}
if (isAssignableWebView(owner)) {
val argTypes = Type.getArgumentTypes(desc)
val positionList = mutableListOf()
//依次复制操作数栈顶的元素到局部变量表中保存
for (element in argTypes) {
val position = newLocal(element)
storeLocal(position)
positionList.add(position)
}
val ownerPosition = newLocal(Type.getObjectType(owner))
storeLocal(ownerPosition)
positionList.add(ownerPosition)
//处理 Dcloud 中 WebView 的写法,缩小范围为 AdaWebView
if (isDCloud(owner, classNameAnalytics.className)) {
hookDCloud(positionList, name, desc)
} else {
//将局部变量表中的数据压入操作数栈中触发我们需要插入的方法
positionList.reversed().forEach { tmp ->
loadLocal(tmp)
}
val newDesc = SAUtils.appendDescBeforeGiven(desc, VIEW_DESC)
mv.visitMethodInsn(INVOKESTATIC, JS_BRIDGE_API, name, newDesc, false)
}
//将局部变量表中的数据压入操作数栈中触发原有的方法
positionList.reversed().forEach { tmp ->
loadLocal(tmp)
}
super.visitMethodInsn(opcode, owner, name, desc, itf)
return
}
}
} catch (throwable: Throwable) {
Logger.warn("Can not auto handle webview, if you have any questions, please contact our technical services: classname:${classNameAnalytics.className}, method:${methodNameDesc}, exception: ${throwable}")
}
super.visitMethodInsn(opcode, owner, name, desc, itf)
}
private fun isDCloud(owner: String, className: String): Boolean {
return owner == "io/dcloud/common/adapter/ui/webview/DCWebView" && className.contains("AdaWebview")
}
private fun hookDCloud(positionList: MutableList, name: String, desc: String) {
var isCask = false
var tmpPosition = positionList.last()
loadLocal(tmpPosition)
mv.visitTypeInsn(INSTANCEOF, "android/view/View")
val label = Label()
mv.visitJumpInsn(IFEQ, label)
positionList.reversed().forEach { tmp ->
loadLocal(tmp)
if (!isCask) {
isCask = true
mv.visitTypeInsn(CHECKCAST, "android/view/View")
}
}
val newDesc = SAUtils.appendDescBeforeGiven(desc, VIEW_DESC)
//为保持新 SDK 使用旧版插件问题,会使用新 SDK loadUrl后缀的方法
mv.visitMethodInsn(INVOKESTATIC, JS_BRIDGE_API, name, newDesc, false)
mv.visitLabel(label)
}
/**
* 判断方法的 owner 是否是 WebView 的子类
*
* @param owner
* @return true 表示是 WebView 的子类,否则不是
*/
private fun isAssignableWebView(owner: String): Boolean {
try {
if (OWNER_WHITE_SET.contains(owner)) {
return true
}
if (isDCloud(owner, classNameAnalytics.className)) {
return true
}
return checkAndroidWebView(owner) || checkX5WebView(owner)
} catch (throwable: Throwable) {
Logger.warn("Can not load class, if you have any questions, please contact our technical services: classname:${classNameAnalytics.className}, exception: ${throwable}")
}
return false
}
private fun checkAndroidWebView(owner: String): Boolean {
if (classInheritance.isAssignableFrom(owner.replaceSlashByDot(), "android.webkit.WebView")) {
OWNER_WHITE_SET.add(owner)
return true
}
return false
}
private fun checkX5WebView(owner: String): Boolean {
if (classInheritance.isAssignableFrom(owner.replaceSlashByDot(), "com.tencent.smtt.sdk.WebView")) {
OWNER_WHITE_SET.add(owner)
return true
}
return false
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy