DroidAssist (源码) 是一个轻量级的 Android 字节码编辑插件,基于 Javassist 对字节码操作,根据 xml 配置处理 class 文件,以达到对 class 文件进行动态修改的效果。和其他 AOP 方案不同,DroidAssist 提供了一种更加轻量,简单易用,无侵入,可配置化的字节码操作方式,你不需要 Java 字节码的相关知识,只需要在 Xml 插件配置中添加简单的 Java 代码即可实现类似 AOP 的功能,同时不需要引入其他额外的依赖。
使用方式 DroidAssist 适用于 Android Studio 工程 application model 或者 library model,使用 DroidAssist 需要接入 DroidAssist 插件并编写专有配置文件。
在 root project 的 build.gradle 里添加:
dependencies { classpath "com.didichuxing.tools:droidassist:1.1.1"}
注意:目前似乎只在 jcenter 有发布,所以需要在 repositories 里添加 jcenter() 。
在需要处理的 model project 的 build.gradle 里添加:
apply plugin: 'com.didichuxing.tools.droidassist'droidAssistOptions { config file("droidassist.xml"),file("droidassist2.xml") //插件配置文件(必选配置,支持多配置文件)}
埋点配置 1、埋点代码 ViewInject.ktpackage com.example.droidassisttestimport android.app.Activityimport android.content.DialogInterfaceimport android.os.SystemClockimport android.util.Logimport android.view.Viewimport android.widget.CompoundButtonimport android.widget.RadioGroupimport android.widget.SeekBarimport androidx.fragment.app.Fragmentobject ViewInject { private const val TAG = "ViewInject" @JvmStatic fun injectClick(view: View) { Log.d(TAG, "injectClick on $view") } @JvmStatic fun injectDialogClick(dialog: DialogInterface, which: Int) { Log.d(TAG, "injectDialogClick on $dialog: $which") } @JvmStatic fun injectGroupCheckedChanged(radioGroup: RadioGroup, which: Int) { Log.d(TAG, "injectGroupCheckedChanged on $radioGroup: $which") } @JvmStatic fun injectSeekClick(seekBar: SeekBar) { Log.d(TAG, "injectSeekClick on $seekBar") } @JvmStatic fun injectCheckedChanged(button: CompoundButton, value: Boolean) { Log.d(TAG, "injectCheckedChanged on $button: $value") } @JvmStatic fun injectActivityOnResume(activity: Activity) { Log.d(TAG, "injectActivityonResume $activity") activity.window.decorView.setTag(R.id.activity_resume_tag, SystemClock.uptimeMillis()) } @JvmStatic fun injectActivityOnPause(activity: Activity) { val pauseTime = SystemClock.uptimeMillis() val resumeTime = (activity.window.decorView.getTag(R.id.activity_resume_tag) as? Long) ?: pauseTime Log.d(TAG, "injectActivityonPause $activity time=${pauseTime - resumeTime}") } @JvmStatic fun injectFragmentOnResume(fragment: Fragment) { Log.d(TAG, "injectFragmentonResume $fragment") } @JvmStatic fun injectFragmentOnPause(fragment: Fragment) { Log.d(TAG, "injectFragmentonPause $fragment") } @JvmStatic fun injectFragmentOnResume(fragment: android.app.Fragment) { Log.d(TAG, "injectFragmentonResume $fragment") } @JvmStatic fun injectFragmentOnPause(fragment: android.app.Fragment) { Log.d(TAG, "injectFragmentonPause $fragment") }}
2、埋点 xml 配置表 路径为 DroidAssistTest/app/droidassist.xml
记得在 xml 头部添加 https://github.com/didi/DroidAssist/blob/master/docs/droidassist.dtd 的内容,为了方便编写配置文件,在 IDE 中能自动提示。
]>
<?xml version="1.0" encoding="utf-8"?>
package com.example.droidassisttestimport android.content.DialogInterfaceimport android.os.Bundleimport android.util.Logimport android.view.Viewimport android.widget.CompoundButtonimport android.widget.RadioGroupimport android.widget.SeekBarimport androidx.appcompat.app.alertDialogimport androidx.appcompat.app.AppCompatActivityimport com.example.droidassisttest.databinding.ActivityMainBindingclass MainActivity : AppCompatActivity() { companion object { private const val TAG = "MainActivity" } private lateinit var binding: ActivityMainBinding override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) setContentView(binding.root) val dialogBuilder = alertDialog.Builder(this@MainActivity).apply { setCancelable(true) setTitle("alertDialog Test") setPositiveButton("Ok", object : DialogInterface.OnClickListener { override fun onClick(dialog: DialogInterface?, which: Int) { Log.d(TAG, "$dialog: onClick $which") } }) } binding.run { buttonView.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View) { Log.d(TAG, "$v: onClick") } }) showDialog.setOnClickListener { dialogBuilder.show() } switchView.setOnCheckedChangeListener(object : CompoundButton.OnCheckedChangeListener { override fun onCheckedChanged(buttonView: CompoundButton, isChecked: Boolean) { Log.d(TAG, "$buttonView isChecked=$isChecked") } }) radioGroup.setOnCheckedChangeListener(object : RadioGroup.OnCheckedChangeListener { override fun onCheckedChanged(group: RadioGroup?, checkedId: Int) { Log.d(TAG, "$group checkedId=$checkedId") } }) seekbarView.setOnSeekBarChangeListener(object : SeekBar.OnSeekBarChangeListener { override fun onProgressChanged( seekBar: SeekBar, progress: Int, fromUser: Boolean ) { Log.d(TAG, "$seekBar onProgressChanged progress=$progress") } override fun onStartTrackingTouch(seekBar: SeekBar) { Log.d(TAG, "$seekBar onStartTrackingTouch") } override fun onStopTrackingTouch(seekBar: SeekBar) { Log.d(TAG, "$seekBar onStopTrackingTouch") } }) } }}
Log 输出D/ViewInject: injectActivityonResume com.example.droidassisttest.MainActivity@1a24434D/ViewInject: injectClick on com.google.android.material.button.MaterialButton{42efbfd VFED..C.、...P...、355,151-725,277 #7f080064 app:id/button_view}D/ViewInject: injectDialogClick on androidx.appcompat.app.alertDialog@c607c20: -1D/ViewInject: injectCheckedChanged on androidx.appcompat.widget.SwitchCompat{3b12a95 VFED..C.、...P..ID 477,706-603,832 #7f080198 app:id/switch_view}: trueD/ViewInject: injectGroupCheckedChanged on android.widget.RadioGroup{42a6f5b V.E.....、.......D 0,983-1080,1235 #7f080150 app:id/radio_group}: 1D/ViewInject: injectSeekClick on androidx.appcompat.widget.AppCompatSeekBar{3677909 VFED....、...P...、278,1386-803,1433 #7f08016e app:id/seekbar_view}
4、注意事项 对于需要自动埋点的地方,不支持使用 Java 或者 kotlin 的 lambda 表达式,否则不会自动埋点。
比如:
buttonView.setOnClickListener(object : View.OnClickListener { override fun onClick(v: View) { Log.d(TAG, "$v: onClick") }}
不能用 lambda 表达式代替:
buttonView.setOnClickListener { Log.d(TAG, "$it: onClick")}