4

Android中使用ASPECTJ进行用户操作路径跟踪与日志搜集

 3 years ago
source link: http://www.u3coding.com/2021/02/07/android%e4%b8%ad%e4%bd%bf%e7%94%a8aspectj%e8%bf%9b%e8%a1%8c%e7%94%a8%e6%88%b7%e6%93%8d%e4%bd%9c%e8%b7%af%e5%be%84%e8%b7%9f%e8%b8%aa%e4%b8%8e%e6%97%a5%e5%bf%97%e6%90%9c%e9%9b%86/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

在Android App开发中,出现了bug和崩溃测试们就会提着手机上门,然后开发一顿操作,bug消失了,测试们又只有进行大量的操作来复现。

这样的情况想必大家都遇到过,更极端的是线上出现了bug,虽然可以设置崩溃日志上传来收集崩溃日志,但是用户是怎么操作的,我们也只能靠猜

为什么不能有一个工具,记录下最近打开了什么界面,点击了哪些按钮,并且记录到本地,方便开发们查看呢?于是笔者有了写这个工具的想法。

我们来缕一缕:我们的工具要实现的几个功能
1. 记录activity的打开和关闭情况
2. 记录fragment的打开和关闭情况
3. 记录控件的点击情况
4. 把这些日志存放到本地,方便上传和查阅

用什么技术实现

有了目标,我们就可以考虑怎样实现了,大概有以下几种方式实现操作收集

这种技术有个名称,叫做埋点,因为我们就像埋地雷一样在指定位置设置代码,当触发的时候打日志并收集

  1. 人工手动埋点
  2. 使用特殊控件,原理与1类似,不过工作转移给了写控件的人
  3. 使用aop框架进行自动化埋点,比如aspectj或者asm
  4. AccessibilityService配合ContentDescription进行埋点

选择哪项技术呢,我们逐项分析
1. 虽然最简单,但是工作量大,容错错埋漏埋,放弃
2. 工作量巨大,使用者学习成本高,放弃
3. 使用简单,通用,考虑使用
4. 需要开发时添加ContentDescription并且某些机型无法开启AccessibilityService相关设置,有局限性

综合考虑之后,我们选择了使用aop的方式埋点,我们现在有两个选择,aspectj或者asm,但是有于asm过于灵活学习和使用均有一定门槛,所以我们选择简单易用的aspectj

通过对aspectj的学习,我们发现可以对onclick方法以及生命周期方法进行切点设置,至于什么是切点,将会在后续部分解释

日志方面,我们采用了开源项目ZLog进行记录,该框架实现了日志写到文件,以及对文件的数量和大小管理功能,比较方便

对于最后一项,由于使用itembinding等框架的时候会遇到捕获不到具体类的情况,所以按需要进行捕捉

关于Aspectj

这个工具最关键的还是怎样使用aspectj去做埋点,关于aspectj的使用网上有很多例子,这里就不赘述了,下面贴出一个简单的使用文件,大家配合注解看一下,如果还是不太明白建议先去搜一些基本使用的帖子

@Aspect//标注这个类是一个aspectj需要处理的类
class AspectJTest {
    @Pointcut("execution(void _internalCallbackOnClick(..))")//切点,检测返回值为void的_internalCallbackOnClick方法
    fun onBindingClick() {
    @Around("onBindingClick()")//在合适的时机对切点进行处理
    @Throws(Throwable::class)
    fun onClickMethodBinding(joinPoint: ProceedingJoinPoint) {
        val args = joinPoint.args//获取方法参数
        if (args.size >= 1 && args[1] is View) {
            val view = args[1] as View//获取view
            val id = view.id
            //处理该view或者打印日志
        joinPoint.proceed() //执行原来的代码

需要解决的问题

虽然看起来aspectj用起来很简单,但还是有一些问题需要我们处理
1. 怎样获取点击事件的控件id和它所在的类名
2. 怎么获取在list中的点击事件,并获取它的位置信息
关于第一点,我们普通使用setOnClick设置是可以拿到的,但是当使用databinding等技术的时候情况就比较复杂了,我们如果使用以下代码,就会获取到生成类里的一个onclick文件,根本不知道哪个页面的控件被点击了

@Around("onClick()")
    @Throws(Throwable::class)
    fun onClickMethodAround(joinPoint: ProceedingJoinPoint) {
        //获取点击事件view对象及名称,可以对不同按钮的点击事件进行统计
        val target = joinPoint.target
        var className = ""
        if (target != null) {
            className = target.javaClass.name
            if (className.contains("$")) {
                className = className.split("\\$").toTypedArray()[0]
            if (className.contains("_ViewBinding")) {
                className = className.split("_ViewBinding").toTypedArray()[0]
        }//看似可以获取,但是实际使用itembinding的时候只能拿到生成的onclick文件名,没有其他信息
        joinPoint.proceed() //执行原来的代码

要处理这个问题,我们需要监听生成类中的点击事件,在笔者这里,这个事件方法名叫做_internalCallbackOnClick,我们添加一个对它的切点就可以了

对于问题2,我们可以通过获取view的父view,并判断它的类型,再通过强转来解决,代码如下

  val view = args[0] as View
                var index = -1
                if (view.parent is RecyclerView) {
                    index = (view.parent as RecyclerView).getChildPosition(view)

当完成了埋点之后,我们就可以把日志输出到文件里了,这里我使用了一个叫ZLog的库,不过由于有一些年头了,我直接复制了文件到项目中。如果大家有兴趣可以去看看ZLog代码仓库并点个star

总结与完全代码

解决了上面的问题,我们就可以检测到想要的信息并保存了,以后测试来找我们的时候排查bug又多了一点点线索,线上用户报bug的时候也不用胡乱猜测了,是不是感觉很有用呢?(可能并没有

如果想看完全的代码,可以到github上的这里来看看如果能顺便点个star就再好不过了,如果有任何问题,也可以留言讨论


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK