惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

AI
AI
TaoSecurity Blog
TaoSecurity Blog
H
Heimdal Security Blog
Help Net Security
Help Net Security
cs.CV updates on arXiv.org
cs.CV updates on arXiv.org
Microsoft Azure Blog
Microsoft Azure Blog
www.infosecurity-magazine.com
www.infosecurity-magazine.com
Google DeepMind News
Google DeepMind News
爱范儿
爱范儿
The Cloudflare Blog
OSCHINA 社区最新新闻
OSCHINA 社区最新新闻
人人都是产品经理
人人都是产品经理
大猫的无限游戏
大猫的无限游戏
N
News | PayPal Newsroom
V2EX - 技术
V2EX - 技术
博客园 - 【当耐特】
D
Darknet – Hacking Tools, Hacker News & Cyber Security
S
Secure Thoughts
C
CERT Recently Published Vulnerability Notes
罗磊的独立博客
Exploit-DB.com RSS Feed
Exploit-DB.com RSS Feed
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
P
Privacy & Cybersecurity Law Blog
有赞技术团队
有赞技术团队
S
Schneier on Security
S
SegmentFault 最新的问题
Google Online Security Blog
Google Online Security Blog
H
Hacker News: Front Page
The Last Watchdog
The Last Watchdog
Schneier on Security
Schneier on Security
PCI Perspectives
PCI Perspectives
IT之家
IT之家
Project Zero
Project Zero
博客园 - 司徒正美
P
Privacy International News Feed
Recent Commits to openclaw:main
Recent Commits to openclaw:main
Jina AI
Jina AI
Security Latest
Security Latest
Hacker News - Newest:
Hacker News - Newest: "LLM"
腾讯CDC
C
CXSECURITY Database RSS Feed - CXSecurity.com
阮一峰的网络日志
阮一峰的网络日志
C
Check Point Blog
aimingoo的专栏
aimingoo的专栏
V
Vulnerabilities – Threatpost
W
WeLiveSecurity
NISL@THU
NISL@THU
Webroot Blog
Webroot Blog
N
Netflix TechBlog - Medium
L
Lohrmann on Cybersecurity

u3blog

从手动配置到自然语言生成规则:一个 Android AI 工具的重构实录 - u3blog 学习版游戏自制云存档同步的方法探索以及总结 - u3blog 鸿蒙next中web组件和navigation的一个bug以及处理方案 - u3blog ChatGPT是怎么帮我写ios代码的 - u3blog Android项目cicd流程总结(使用jenkins) - u3blog Android使用Logger开发App本地日志记录功能 - u3blog 一张启动图引发的思考--探索.9图原理和应用场景 - u3blog 跟我一起玩Paging3 - u3blog 彻底摆脱数据线——远程ADB调试小工具开发过程记录 - u3blog 自己实现一个Android网络图片加载器 - u3blog
Android中使用ASPECTJ进行用户操作路径跟踪与日志搜集 - u3blog
2022-10-08 · via u3blog

编写初衷

在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就再好不过了,如果有任何问题,也可以留言讨论