当前位置: 首页 > article >正文

第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应用中&#xff0c;动态调用链路指的是应用在运行时的调用路径。这通常涉及到方法调用的顺序和调用关系&#xff0c;特别是在应用的复杂逻辑中&#xff0c;理解这些调用链路对于调试和性能优化非常重要。 1&#xff0c;动态调用链路获…...

亿级流量系统架构设计与实战(六)

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

浅聊find_package命令的搜索模式(Search Modes)

背景 find_package应该算是我们使用最多的cmake命令了。但是它是如何找到上游库的.cmake文件的&#xff1f; 根据官方文档&#xff0c;整理下find_package涉及到的搜索模式。 搜索模式 find_package涉及到的搜索模式有两种&#xff1a;模块模式(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脚本&#xff0c;单次顺序执行多个多卡训练文件 例如 train1.py train2.py 特点&#xff1a;在执行完 train1.py之后再执行train2.py文件 …...

使用lldb查看Rust不同类型的结构

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

【Linux】线程POSIX信号量

目录 1. 整体学习思维导图 2. 信号量的概念 3. 基本接口 4. 基于环形队列的生产者消费者模型(信号量) 1. 整体学习思维导图 2. 信号量的概念 POSIX信号量和SystemV信号量作用相同&#xff0c;都是用于同步操作&#xff0c;达到无冲突的访问共享资源目的。但 POSIX可以用于线…...

WPF中如何自定义控件

WPF自定义控件简化版&#xff1a;账户菜单按钮&#xff08;AccountButton&#xff09; 我们以**“账户菜单按钮”为例&#xff0c;用更清晰的架构实现一个支持标题显示、渐变背景、选中状态高亮**的自定义控件。以下是分步拆解&#xff1a; 一、控件核心功能 我们要做一个类似…...

大模型MCP更高效的通信:StreamableHTTP协议

随着大语言模型&#xff08;LLMs&#xff09;的飞速发展&#xff0c;模型与应用之间的通信效率和灵活性变得至关重要。Model Context Protocol (MCP) 作为专为模型交互设计的协议&#xff0c;一直在不断进化以满足日益增长的需求。近期&#xff0c;MCP引入了一个令人振奋的新特…...

防火墙在网络安全体系中的核心作用与原理

防火墙在网络安全体系中的核心作用与原理 一、核心作用解析 1. 访问控制中枢 功能维度实现方式典型场景黑白名单控制基于IP/端口/协议的规则过滤限制外部IP访问财务系统&#xff0c;仅开放VPN端口权限分级用户组策略映射&#xff08;如AD集成&#xff09;禁止普通员工访问核心…...

MySQL事务和JDBC中的事务操作

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

每日脚本学习5.10 - XOR脚本

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

【编译原理】总结

核心 闭包&#xff0c;正则闭包 产生式&#xff08;规则&#xff09; 文法 G[S](&#xff0c;&#xff0c;P&#xff0c;S) 一组规则的集合 &#xff1a;非终结符 &#xff1a;终结符 P&#xff1a;产生式 S&#xff1a;开始符号 推导 归约 规范&#xff08;最右&#xff…...

docker创建一个centOS容器安装软件(以宝塔为例)的详细步骤

备忘&#xff1a;后续偶尔忘记了docker虚拟机与宿主机的端口映射关系&#xff0c;来这里查看即可&#xff1a; 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&#xff0c;在这里&#xff1a; https://openvla.github.io/ 可以看到他们的论文、数据、模型。 OpenVLA 是一个拥有 70亿参数的开源 **视觉-语言-动作&#xff08;VLA&#xff09;**模型。它是在 Open X-Embodiment 数据集 中的 97万…...

Matlab/Simulink的一些功能用法笔记(4)

水一篇帖子 01--MATLAB工作区的保护眼睛颜色设置 默认的工作区颜色为白色 在网上可以搜索一些保护眼睛的RGB颜色参数设置 在MATLAB中按如下设置&#xff1a; ①点击预设 ②点击颜色&#xff0c;点击背景色的三角标符号 ③点击更多颜色&#xff0c;找到RGB选项 ④填写颜色参数…...

【比赛真题解析】混合可乐

这次给大家分享一道比赛题:混合可乐。 洛谷链接:U561549 混合可乐 【题目描述】 Jimmy 最近沉迷于可乐中无法自拔。 为了调配出他心目中最完美的可乐,Jimmy买来了三瓶不同品牌的可乐,然后立马喝掉了一些(他实在是忍不住了),所以 第一瓶可口可乐最大容量为 a 升,剩余 …...

Elasticsearch:我们如何在全球范围内实现支付基础设施的现代化?

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

matlab介绍while函数

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

如何解决 PowerShell 显示 “此系统上禁用了脚本运行” 的问题

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

深入浅出之STL源码分析4_类模版

1.引言 我在上面的文章中讲解了vector的基本操作&#xff0c;然后提出了几个问题。 STL之vector基本操作-CSDN博客 1.刚才我提到了我的编译器版本是g 11.4.0&#xff0c;而我们要讲解的是STL&#xff08;标准模板库&#xff09;&#xff0c;那么二者之间的关系是什么&#x…...

探索科技的前沿动态:科技爱好者周刊

探索科技的前沿动态:科技爱好者周刊 在信息爆炸的时代,我们每时每刻都被新技术、新理念包围。而如何在这纷繁复杂的信息中找到对自己有价值的内容,成了一大挑战。今天,我们要介绍的是一个宝贵的资源——科技爱好者周刊,它致力于为科技爱好者提供优质的科技资讯,每周五发…...

初学者入门指南:什么是网络拓扑结构?

初学者入门指南&#xff1a;什么是网络拓扑结构&#xff1f; 在构建或学习计算机网络时&#xff0c;一个绕不开的核心概念便是“网络拓扑结构”&#xff08;Network Topology&#xff09;。它决定了网络中各个设备如何连接、通信以及如何扩展。理解网络拓扑不仅有助于我们更清…...

在 Vue 3 中实现刮刮乐抽奖

&#x1f389; 在 Vue 3 中实现刮刮乐抽奖 当项目中需要做一些活动互动页时&#xff0c;需要实现刮刮乐&#xff0c;请看如下效果&#xff1a; 这里感谢github用户Choicc分享的组件&#xff0c;具体可点击传送门查看 1. 引入组件 将/src/components下ScratchCard.vue复制到自…...

Satori:元动作 + 内建搜索机制,让大模型实现超级推理能力

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

SDC命令详解:使用all_outputs命令进行查询

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

printf调试时候正常,运行时打印不出来

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

解决 TimeoutError: [WinError 10060] 在 FramePack项目中连接 Hugging Face 超时的问题

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

分布式-Redis分布式锁

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