第3.2.3节 Android动态调用链路的获取
3.2.3 Android App动态调用链路
在Android应用中,动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系,特别是在应用的复杂逻辑中,理解这些调用链路对于调试和性能优化非常重要。
1,动态调用链路获取方法
要获取Android应用的动态调用链路,可以使用以下几种方法:
(1)使用调试工具:
(2)使用日志:
(3)使用性能分析工具:
(4)使用第三方工具:
(5)动态分析工具:
(6)字节码插桩:
每种方法都有其优缺点,选择合适的方法取决于你的具体需求和应用的复杂性。如果你只是想简单地查看某个功能的调用链路,使用调试工具或日志可能是最简单的选择;如果需要更深入的分析,Profiler或第三方工具可能更合适。
2,动态调用链路的使用
静态调用链路主要用来分析需求开发的影响范围,界定测试范围。动态调用链路主要和用例关联起来,需要知道哪些用例执行过程中,会调用哪些类或是函数,如下图所示:
通过获取用例的动态调用链路后,可以做如下事情:
- 过滤用例执行中的前置和后置步骤,减少用例关联的代码;
- 通过路径覆盖,推荐用例,用最小的用例集来覆盖最大的功能面;
- 用例执行失败,问题定位与修复。
Android App的动态调用链路采取插桩方式进行记录,由于jacoco Android插件没有开源,可以借助于服务端的jacoco来开发新的插件来实现,主要流程如下:
一,Trace插件开发
1,保存类函数对应关系
借助于jacoco插件的核心功能,对项目代码进行插桩,对每一个类和函数进行插桩,记录类的classid, methodid,执行次序和执行时间。插桩的过程中,将classid, 类名,methodid,函数名与参数等信息记录到class-method-map.txt文件中,打包的时候会将文件生成保存到工作目录中。
public class JacocoPlugin extends Transform implements Plugin<Project> {.....@Override
public void transform(TransformInvocation transformInvocation)throws TransformException, InterruptedException, IOException {super.transform(transformInvocation);System.out.println("AutoTest " + project.getName() + "," + project.getDisplayName() + "," + project.getPath());System.out.println("--------------------------");long start = System.currentTimeMillis();long copyCost = 0;long instrumentCost = 0;long copyDirCost = 0;Collection<TransformInput> inputs = transformInvocation.getInputs();TransformOutputProvider outputProvider = transformInvocation.getOutputProvider();for (TransformInput input : inputs) {//对项目中的jar包进行处理for (JarInput jarInput : input.getJarInputs()) {String jarName = jarInput.getName();File dest = outputProvider.getContentLocation(jarName,jarInput.getContentTypes(), jarInput.getScopes(), Format.JAR);//暂时不对app应用引用的jar包进行注入,防止因引用包造成二次注入 2024013if (filterJar(jarInput, sKwaiTestConfig) && project.getPlugins().hasPlugin("com.android.library")) {System.out.println("process jar is:" + jarName);System.out.println("process jar abspath is :" + jarInput.getFile().getAbsolutePath());sKwaiTestConfig.classJars.add(jarInput.getFile().getAbsolutePath());long tStart = System.currentTimeMillis();transformJar(jarInput, dest, jarName);long tEnd = System.currentTimeMillis();instrumentCost += (tEnd - tStart);} else {long cStart = System.currentTimeMillis();FileUtils.copyFile(jarInput.getFile(), dest);long cEnd = System.currentTimeMillis();copyCost += (cEnd - cStart);System.out.println("App Project " + project.getName() + "," + project.getDisplayName() + "," + project.getPath() + " not transform jar files!");}//System.out.println("transform jar的位置:"+dest.getAbsolutePath());}long dStart = System.currentTimeMillis();for (DirectoryInput directoryInput : input.getDirectoryInputs()) {File dest = outputProvider.getContentLocation(directoryInput.getName(),directoryInput.getContentTypes(), directoryInput.getScopes(), Format.DIRECTORY);if (filterDir(directoryInput, sKwaiTestConfig)) {String relatePath = directoryInput.getFile().getAbsolutePath();System.out.println("process dir file is:" + relatePath);sKwaiTestConfig.classDirs.add(relatePath);Instrumenter instrumenter = new Instrumenter(new OfflineInstrumentationAccessGenerator());transformDirectory(directoryInput.getFile(), dest, relatePath, instrumenter);} else {FileUtils.copyDirectory(directoryInput.getFile(), dest);}//System.out.println("transform 类的位置:" + dest.getAbsolutePath());}long dEnd = System.currentTimeMillis();copyDirCost = dEnd - dStart;}if (FLAG.decrementAndGet() == 0) {if (!TraceMethodVisitor.sMap.isEmpty()) {Utils.writeTraceMap(sTraceMapPath, TraceMethodVisitor.sMap);sKwaiTestConfig.customTraceMapPath = sTraceMapPath;//清除早期的trac数据TraceMethodVisitor.sMap.clear();} else {System.out.println("TraceMethodVisitor 为空!");}String configStr = (new Gson()).toJson(sKwaiTestConfig);Utils.writeKwaiTestFile(sKwaiTestConfigPath, configStr);System.out.println("Auto test jacoco writeKwaiTestFile");}long end = System.currentTimeMillis();System.out.println("---------------Auto test jacoco transform end-----------");System.out.println("Auto test jacoco copy jar cost:" + copyCost + "ms");System.out.println("Auto test jacoco copy dir cost:" + copyDirCost + "ms");System.out.println("Auto test jacoco instrument cost:" + instrumentCost + "ms");System.out.println("Auto test jacoco transform cost:" + (end - start) / 1000 + "s");System.out.println("---------执行 transform---------------");
}
.....
}
2,记录用例执行路径
在类函数中添加记录用例执行路径的操作,如:新建CSTraceHelper类
public class CSTraceHelper {....private Thread sConsumer = new Thread(() -> {if (sFirstInit) {sFirstInit = false;} else {return;}if (sStoragePath == null) {return;}for (; ; ) {flushTraceData();try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}}});public static void flushTraceData() {if (sStoragePath != null && (!sKDSStore.isEmpty() || !sNativeStore.isEmpty())) {appendDataToFile();}
}
/**
将trace信息写入文件
**/
private static void appendDataToFile() {synchronized (sNativeStore) {FileWriter fileWriter = null;BufferedWriter bw = null;try {File dataDir = new File(sStoragePath);if (!dataDir.exists()) {dataDir.mkdirs();}File dataFile = new File(dataDir, TRACE_FILE_NAME);if (!dataFile.exists()) {dataFile.createNewFile();}Log.d(TAG, "Trace文件:" + dataFile.getAbsolutePath());fileWriter = new FileWriter(dataFile, true);bw = new BufferedWriter(fileWriter);Long value = sNativeStore.poll();HashMap<String, TraceDataSimple> map = new HashMap<>();while (value != null) {// 从 long 值中恢复三个 int 值int classId = (int) (value >> 42);int methodId = (int) ((value >> 21) & 0x1FFFFF);int line = (int) (value & 0x1FFFFF);//Log.d(TAG,"appendDataToFile cid:"+classId+",mId:"+methodId+",line:"+line+",value="+value);String key = classId + "" + methodId;if (map.containsKey(key)) {map.get(key).lines.add(String.valueOf(line));} else {TraceDataSimple method = new TraceDataSimple();method.tName = "t";method.timeMillis = System.currentTimeMillis();method.cid = classId;method.mid = methodId;method.lines.add(String.valueOf(line));map.put(key, method);}value = sNativeStore.poll();}Iterator<Map.Entry<String, TraceDataSimple>> it = map.entrySet().iterator();while (it.hasNext()) {bw.write(it.next().getValue().toSimpleString());}bw.flush();} catch (IOException e) {Log.d(TAG, "appendDataToFile catch error:" + e.getMessage());e.printStackTrace();} finally {try {if (bw != null) {bw.close();}if (fileWriter != null) {fileWriter.close();}} catch (IOException e) {Log.d(TAG, "appendDataToFile finally error:" + e.getMessage());e.printStackTrace();}}Log.d(TAG,"Trace 数据写入完成:"+sStoragePath);}
}
/*** 重新注入项目* @param cId 类ID* @param mId 方法ID* @param line 行号*/
public static void injectTcEp(long cId, int mId, int line) {// Log.d(TAG,"sStoragePath="+sStoragePath+",sNeedInject="+sNeedInject);if (sStoragePath != null && sNeedInject) {// 将三个 int 值存储到一个 long 值中long value = ((long) cId << 42) | ((long) mId << 21) | (long) line;Log.d(TAG,"Inject cid:"+cId+",mId:"+mId+",line:"+line+",value="+value);sNativeStore.offer(value);}
}
......
}
同时在Android插件JacocoPlugin的TraceMethodVisitor类中添加给函数注入记录trace信息的操作
public class TraceMethodVisitor extends MethodVisitor {......private void injectMethod() {mv.visitLdcInsn(classID);mv.visitLdcInsn(id);mv.visitLdcInsn(line);mv.visitMethodInsn(INVOKESTATIC, "com/kwai/test/core/CSTraceHelper", "injectTcEp","(JII)V", false);
}
.......
}
执行打包命令
./gradlew upload
将会在指定的目录下,生成新插件的所有包。
二,新插件使用
在要关联用例的App中,添加新的jacoco插件,打包后就会自动对函数中的类,函数进行插桩。如果想同时采集覆盖率数据,控制好相关的开关即可。打包完成后做如下操作:
1,将打包后的class文件上传到指定位置,如精准测试平台,在生成覆盖率报告时候要用到。
2,将生成的class-method-map.txt文件上传到精准测试平台,解析用例与代码的关联关系时需要查询类和函数信息。
C: com.gavin.asmdemo.MainActivity 1 com/gavin/asmdemo/MainActivity.java
M: onCreate(android.os.Bundle) 0 12
M: toSecond(android.view.View) 1 18
M: toThrid(android.view.View) 2 24
M: onStop() 3 8
M: onStart() 4 8
C: com.gavin.asmdemo.ThridActivity 2 com/gavin/asmdemo/ThridActivity.java
M: onCreate(android.os.Bundle) 0 14
M: toShow(android.view.View) 1 19
M: thToIndexPage(android.view.View) 2 26
M: onStop() 3 10
M: onStart() 4 10
C: com.gavin.asmdemo.BaseActivity 3 com/gavin/asmdemo/BaseActivity.java
M: onCreate(android.os.Bundle) 0 17
M: onStart() 1 24
M: onStop() 2 32
C: com.gavin.asmdemo.SecondActivity 4 com/gavin/asmdemo/SecondActivity.java
M: onCreate(android.os.Bundle) 0 11
M: seToThrid(android.view.View) 1 16
M: toIndexPage(android.view.View) 2 21
M: onStop() 3 7
M: onStart() 4 7
3,在测试需求时,根据需求,上传覆盖率,trace信息文件。
4,生成覆盖率报告时,解析trace信息,记录追溯关系。
5,根据业务特点,过滤一下trace信息,就可以拿到用例与代码的精准关联关系。
相关文章:

第3.2.3节 Android动态调用链路的获取
3.2.3 Android App动态调用链路 在Android应用中,动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系,特别是在应用的复杂逻辑中,理解这些调用链路对于调试和性能优化非常重要。 1,动态调用链路获…...

亿级流量系统架构设计与实战(六)
微服务架构与网络调用 当某个业务从单体服务架构转变为微服务架构后,多个服务之间会通过网络调用形式形成错综复杂的依赖关系。 在微服务架构中 , 一个微服务正常工作依赖它与其他微服务之间的多级网络调用。 网络是脆弱的 , RPC 请求有较大的概率会遇到超时 、 抖动 、 断…...

浅聊find_package命令的搜索模式(Search Modes)
背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的? 根据官方文档,整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种:模块模式(Module mode)和配置模式(Conf…...
开发搭载OneNet平台的物联网数据收发APP的设计与实现
一、开发环境与工具准备 工具安装 下载HBuilderX开发版(推荐使用开发版以避免插件兼容性问题)安装Node.js和npm(用于依赖管理及打包)配置Android Studio(本地打包需集成离线SDK)项目初始化 创建uni-app项目,选择“默认模板”或“空白模板”安装必要的UI库(如uView或Van…...

【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B
【LLaMA-Factory】使用LoRa微调训练DeepSeek-R1-Distill-Qwen-7B 本地环境说明禁用开源驱动nouveau安装nvidia-smi安装Git环境安装Anaconda(conda)环境下载DeepSeek-R1-Distill-Qwen-7B模型安装LLaMA-Factory下载LLaMA-Factory安装LLaMA-Factory依赖修改环境变量安装deepspeedA…...
sh脚本多卡顺序执行训练文件
常规的单机多卡训练脚本一般为 python -m torch.distributed.run --nproc_per_node 2 train.py 上述脚本采用 2 张显卡训练 采用sh脚本,单次顺序执行多个多卡训练文件 例如 train1.py train2.py 特点:在执行完 train1.py之后再执行train2.py文件 …...

使用lldb查看Rust不同类型的结构
目录 前言 正文 标量类型 复合类型——元组 复合类型——数组 函数 &str struct 可变数组vec Iter String Box Rc Arc RefCell Mutex RwLock Channel 总结 前言 笔者发现这个lldb挺好玩的,可以查看不同类型的结构,虽然这好像是C的东…...

【Linux】线程POSIX信号量
目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同,都是用于同步操作,达到无冲突的访问共享资源目的。但 POSIX可以用于线…...
WPF中如何自定义控件
WPF自定义控件简化版:账户菜单按钮(AccountButton) 我们以**“账户菜单按钮”为例,用更清晰的架构实现一个支持标题显示、渐变背景、选中状态高亮**的自定义控件。以下是分步拆解: 一、控件核心功能 我们要做一个类似…...
大模型MCP更高效的通信:StreamableHTTP协议
随着大语言模型(LLMs)的飞速发展,模型与应用之间的通信效率和灵活性变得至关重要。Model Context Protocol (MCP) 作为专为模型交互设计的协议,一直在不断进化以满足日益增长的需求。近期,MCP引入了一个令人振奋的新特…...
防火墙在网络安全体系中的核心作用与原理
防火墙在网络安全体系中的核心作用与原理 一、核心作用解析 1. 访问控制中枢 功能维度实现方式典型场景黑白名单控制基于IP/端口/协议的规则过滤限制外部IP访问财务系统,仅开放VPN端口权限分级用户组策略映射(如AD集成)禁止普通员工访问核心…...

MySQL事务和JDBC中的事务操作
一、什么是事务 事务是数据库操作的最小逻辑单元,具有"全有或全无"的特性。以银行转账为例: 典型场景: 从A账户扣除1000元 向B账户增加1000元 这两个操作必须作为一个整体执行,要么全部成功,要么全部失败…...

每日脚本学习5.10 - XOR脚本
xor运算的简介 异或就是对于二进制的数据可以 进行同0异1 简单的演示 : 结果是 这个就是异或 异或的作用 1、比较两数是否相等 2、可以进行加密 加密就是需要key 明文 :0b010110 key : 0b1010001 这个时候就能进行加密 明文 ^ key密文 还有这个加密比…...

【编译原理】总结
核心 闭包,正则闭包 产生式(规则) 文法 G[S](,,P,S) 一组规则的集合 :非终结符 :终结符 P:产生式 S:开始符号 推导 归约 规范(最右ÿ…...

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤
备忘:后续偶尔忘记了docker虚拟机与宿主机的端口映射关系,来这里查看即可: docker run -d \ --name baota \ --privilegedtrue \ -p 8888:8888 \ -p 8880:80 \ -p 8443:443 \ -p 8820:20 \ -p 8821:21 \ -v /home/www:/www/wwwroot \ centos…...

OpenVLA:开源的视觉-语言-动作模型
1. 简介 让我们先来介绍一下什么是OpenVLA,在这里: https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作(VLA)**模型。它是在 Open X-Embodiment 数据集 中的 97万…...

Matlab/Simulink的一些功能用法笔记(4)
水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置: ①点击预设 ②点击颜色,点击背景色的三角标符号 ③点击更多颜色,找到RGB选项 ④填写颜色参数…...
【比赛真题解析】混合可乐
这次给大家分享一道比赛题:混合可乐。 洛谷链接:U561549 混合可乐 【题目描述】 Jimmy 最近沉迷于可乐中无法自拔。 为了调配出他心目中最完美的可乐,Jimmy买来了三瓶不同品牌的可乐,然后立马喝掉了一些(他实在是忍不住了),所以 第一瓶可口可乐最大容量为 a 升,剩余 …...

Elasticsearch:我们如何在全球范围内实现支付基础设施的现代化?
作者:来自 Elastic Kelly Manrique SWIFT 和 Elastic 如何应对基础设施复杂性、误报问题以及日益增长的合规要求。 金融服务公司在全球范围内管理实时支付方面面临前所未有的挑战。SWIFT(Society for Worldwide Interbank Financial Telecommunication -…...

matlab介绍while函数
MATLAB 中的 while 语句介绍 在 MATLAB 中,while 语句是一种循环结构,用于在满足特定条件时反复执行一段代码块。与 for 循环不同,while 循环的执行次数是动态的,取决于循环条件是否为真。 语法 while condition% 循环体代码 e…...

如何解决 PowerShell 显示 “此系统上禁用了脚本运行” 的问题
在 Windows 11 或 10 的 PowerShell 中运行脚本时,你可能会遇到一个错误,提示系统上禁用了脚本运行。这是一种安全功能,而不是系统问题,旨在防止可能有害的脚本自动运行。然而,如果你需要运行脚本来完成某些任务,或者你在系统上做了软件开发或测试的环境,那么你需要在 P…...

深入浅出之STL源码分析4_类模版
1.引言 我在上面的文章中讲解了vector的基本操作,然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0,而我们要讲解的是STL(标准模板库),那么二者之间的关系是什么&#x…...
探索科技的前沿动态:科技爱好者周刊
探索科技的前沿动态:科技爱好者周刊 在信息爆炸的时代,我们每时每刻都被新技术、新理念包围。而如何在这纷繁复杂的信息中找到对自己有价值的内容,成了一大挑战。今天,我们要介绍的是一个宝贵的资源——科技爱好者周刊,它致力于为科技爱好者提供优质的科技资讯,每周五发…...

初学者入门指南:什么是网络拓扑结构?
初学者入门指南:什么是网络拓扑结构? 在构建或学习计算机网络时,一个绕不开的核心概念便是“网络拓扑结构”(Network Topology)。它决定了网络中各个设备如何连接、通信以及如何扩展。理解网络拓扑不仅有助于我们更清…...
在 Vue 3 中实现刮刮乐抽奖
🎉 在 Vue 3 中实现刮刮乐抽奖 当项目中需要做一些活动互动页时,需要实现刮刮乐,请看如下效果: 这里感谢github用户Choicc分享的组件,具体可点击传送门查看 1. 引入组件 将/src/components下ScratchCard.vue复制到自…...

Satori:元动作 + 内建搜索机制,让大模型实现超级推理能力
Satori:元动作 内建搜索机制,让大模型实现超级推理能力 论文大纲一、背景:LLM 推理增强的三类方法1. 基于大规模监督微调(SFT)的推理增强2. 借助外部机制在推理时进行搜索 (RLHF / 多模型 / 工具)3. 现有局限性总结 二…...

SDC命令详解:使用all_outputs命令进行查询
相关阅读 SDC命令详解https://blog.csdn.net/weixin_45791458/category_12931432.html all_outputs命令用于创建一个输出端口对象集合,关于设计对象和集合的更详细介绍,可以参考下面的博客。 Synopsys:设计对象https://chenzhang.blog.csdn…...

printf调试时候正常,运行时打印不出来
问题是在添加了 printf 功能后,程序独立运行时无法正常打印输出,而调试模式下正常。这表明问题可能与 printf 的重定向实现、标准库配置、或编译器相关设置有关。 解决: 原来是使用 Keil/IAR,printf可能需要启用 MicroLIB 或正确…...

解决 TimeoutError: [WinError 10060] 在 FramePack项目中连接 Hugging Face 超时的问题
#工作记录 以下是针对 TimeoutError: [WinError 10060] 的完整排查方案,适用于 FramePack项目中。 (一般该错误的发生原因请重点排查Hugging Face模型仓库受限需要登录的情形) FramePack项目参考资料 FramePack部署(从PyCharm解…...

分布式-Redis分布式锁
Redis实现分布式锁优点 (1)Redis有很高的性能; (2)Redis命令对此支持较好,实现起来比较方便 实现思路 (1)获取锁的时候,使用setnx加锁,并使用expire命令为锁…...