android 排序函数,一个能让你了解所有函数调用顺序以及函数耗时的Android库

本文介绍了一个Android项目,通过自定义gradle任务利用traceview和dmtracedump工具,实现无需修改代码就能获取包内函数调用顺序。在大量代码或新人学习时,提供了一种高效理解代码逻辑的方法。项目通过过滤指定包名,生成order.txt文件,清晰展示函数执行路径。此外,还提供了一个Windows下的小工具进行一键分析。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

背景:当项目代码量很大的时候,或者你作为一名新人要快速掌握代码的时候,给函数打上log,来了解代码执行逻辑,这种方式会显然成本太大,要改动项目编译运行,NO!太耗时;或者你想debug的方式来给你想关注的几个函数,来了解代码执行逻辑,NO!因为你肯定会漏掉函数;也许你可以固执的给你写的项目打满log说这样也行,但是你要知道你方法所调用的jdk的函数或者第三方aar或者jar再或者android sdk中的函数调用顺序你怎么办,还能打log吗?显然不行吧,来~这个项目给让可以让你以包名为过滤点过滤你想要知道所有函数调用顺序。

作者列表(排名按代码贡献时间顺序):二精-霁雪清虹,xingstarx,大精-wing,pighead4u ,Tesla ,lijunjie,三精-虹猫

1. 效果奉上

AAffA0nNPuCLAAAAAElFTkSuQmCC

动作简介:首先点击MainActivity的自定义MyTextView然后进入SecondActivity再点击textview之后finish跳转回MainActivity

下面是库处理过所得到的函数调用顺序order.txt文件(我这里屏蔽了jdk函数,第三方库函数,以及android sdk中函数,换句话说我就保留了我自己包名中的函数顺序)

832 ent 67593 .....com.zjw.appmethodorder.MainActivity.onClick (Landroid/view/View;)V MainActivity.java

832 ent 99956 ..........com.zjw.appmethodorder.MainActivity.onPause ()V MainActivity.java

832 ent 99970 ...........com.zjw.appmethodorder.BaseActivity.onPause ()V BaseActivity.java

832 ent 100472 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()V BaseActivity.java

832 ent 128540 ........com.zjw.appmethodorder.SecondActivity. ()V SecondActivity.java

832 ent 128562 .........com.zjw.appmethodorder.BaseActivity. ()V BaseActivity.java

832 ent 213911 ........com.zjw.appmethodorder.SecondActivity.onCreate (Landroid/os/Bundle;)V SecondActivity.java

832 ent 213928 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)V BaseActivity.java

832 ent 258414 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()V BaseActivity.java

832 ent 1440503 .........com.zjw.appmethodorder.SecondActivity.onResume ()V SecondActivity.java

832 ent 1440563 ..........com.zjw.appmethodorder.BaseActivity.onResume ()V BaseActivity.java

832 ent 1445675 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()V BaseActivity.java

832 ent 2954291 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)V MyTextView.java

832 ent 3065664 ........com.zjw.appmethodorder.MainActivity.onStop ()V MainActivity.java

832 ent 3065701 .........com.zjw.appmethodorder.BaseActivity.onStop ()V BaseActivity.java

832 ent 3069155 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()V BaseActivity.java

832 ent 3139519 .......com.zjw.appmethodorder.SecondActivity.click (Landroid/view/View;)V SecondActivity.java

832 ent 3146300 ........com.zjw.appmethodorder.SecondActivity.finish ()V SecondActivity.java

832 ent 3183478 ..........com.zjw.appmethodorder.SecondActivity.onPause ()V SecondActivity.java

832 ent 3183498 ...........com.zjw.appmethodorder.BaseActivity.onPause ()V BaseActivity.java

832 ent 3183843 ............com.zjw.appmethodorder.BaseActivity.baseOnPause ()V BaseActivity.java

832 ent 3209420 ........com.zjw.appmethodorder.MainActivity. ()V MainActivity.java

832 ent 3209438 .........com.zjw.appmethodorder.BaseActivity. ()V BaseActivity.java

832 ent 3283359 ........com.zjw.appmethodorder.MainActivity.onCreate (Landroid/os/Bundle;)V MainActivity.java

832 ent 3283378 .........com.zjw.appmethodorder.BaseActivity.onCreate (Landroid/os/Bundle;)V BaseActivity.java

832 ent 3330938 ..........com.zjw.appmethodorder.BaseActivity.baseOnCreate ()V BaseActivity.java

832 ent 4363295 .....................com.zjw.appmethodorder.MyTextView. (Landroid/content/Context;Landroid/util/AttributeSet;)V MyTextView.java

832 ent 4436094 ..................com.zjw.appmethodorder.MyTextView.onFinishInflate ()V MyTextView.java

832 ent 4449689 .........com.zjw.appmethodorder.MainActivity.initView ()V MainActivity.java

832 ent 4539427 .........com.zjw.appmethodorder.MainActivity.onResume ()V MainActivity.java

832 ent 4539467 ..........com.zjw.appmethodorder.BaseActivity.onResume ()V BaseActivity.java

832 ent 4543597 ...........com.zjw.appmethodorder.BaseActivity.baseOnResume ()V BaseActivity.java

832 ent 4917854 .................com.zjw.appmethodorder.MyTextView.onAttachedToWindow ()V MyTextView.java

832 ent 4918658 .................com.zjw.appmethodorder.MyTextView.onWindowVisibilityChanged (I)V MyTextView.java

832 ent 5090653 ...................................com.zjw.appmethodorder.MyTextView.onMeasure (II)V MyTextView.java

832 ent 5355203 ..................................com.zjw.appmethodorder.MyTextView.onMeasure (II)V MyTextView.java

832 ent 5456681 .......................................com.zjw.appmethodorder.MyTextView.onSizeChanged (IIII)V MyTextView.java

832 ent 5467577 ....................................com.zjw.appmethodorder.MyTextView.onLayout (ZIIII)V MyTextView.java

832 ent 5876623 ...........................................com.zjw.appmethodorder.MyTextView.onDraw (Landroid/graphics/Canvas;)V MyTextView.java

832 ent 6121967 ........com.zjw.appmethodorder.SecondActivity.onStop ()V SecondActivity.java

832 ent 6121986 .........com.zjw.appmethodorder.BaseActivity.onStop ()V BaseActivity.java

832 ent 6123689 ..........com.zjw.appmethodorder.BaseActivity.baseOnStop ()V BaseActivity.java

832 ent 6127522 ........com.zjw.appmethodorder.SecondActivity.onDestroy ()V SecondActivity.java

832 ent 6127679 .........com.zjw.appmethodorder.BaseActivity.onDestroy ()V BaseActivity.java

832 ent 6133301 ..........com.zjw.appmethodorder.BaseActivity.baseOnDestroy ()V BaseActivity.java

OK!发现是不是很炫酷啊,下面来介绍该库的原理(求star!!!)

2 原理

本库其实并没有什么黑科技,本库也没有java代码,核心就是2个build.gradle中的task。首先,原理就是基于android sdk中提供的工具----traceview,和dmtracedump。traceview会生成.trace文件,该文件记录了函数调用顺序,函数耗时,函数调用次数等等有用的信息。而dmtracedump 工具就是基于trace文件生成报告的工具,具体用法不细说。dmtracedump 工具大家一般用的多的选项就是生成html报告,或者生成调用顺序图片(看起来很不直观)。首先说说为什么要用traceview,和dmtracedump来作为得到函数调用顺序的,因为这个工具既然能知道cpu执行时间和调用次数以及函数调用树(看出函数调用顺序很费劲)比如android studio是这样呈现.trace文件的解析视图的

AAffA0nNPuCLAAAAAElFTkSuQmCC

或者这样的

AAffA0nNPuCLAAAAAElFTkSuQmCC

(上面这张图是网上找的,侵删)

用上面这2个图发现你要清晰知道函数调用看懂了才是见鬼了。或者使用dmtracedump 工具解析生成的html长下面这样(dmtracedump 生成图片就不说了 生成出的图片也根本看不出顺序这个就略过了)

AAffA0nNPuCLAAAAAElFTkSuQmCC

一开始我以为 Method 序列号有戏于是乎冲动的我把带序号的东西内容复制出来写了一个脚本对他们进行排序代码如下:

import java.io.BufferedReader;

import java.io.FileReader;

import java.io.IOException;

import java.util.ArrayList;

import java.util.Arrays;

import java.util.Collections;

import java.util.regex.Matcher;

import java.util.regex.Pattern;

public class Sort implements Comparable {

static String uri = "D:/Application/eclipse/javaworkspace/Test/src/JB/1.text";

String str = "";

String content = "";

public Sort(String str,String content) {

super();

this.str = str;

this.content = content;

}

public static void main(String[] args) throws IOException {

// TODO Auto-generated method stub

ArrayList list = new ArrayList<>();

BufferedReader in = new BufferedReader(new FileReader(uri));

String a = "";

while ((a = in.readLine()) != null) {

//System.out.println(Long.valueOf(getIndexStr(a)));

if(a.contains("com.zjw.appmethodorder")){

list.add(new Sort(getIndexStr(a),a));

}

}

Collections.sort(list);

for (Sort sort : list) {

System.out.println(sort.content);

}

}

public static String getIndexStr(String str) {

String regEx = "(?<=\\[)(\\S+)(?=\\])";// 匹配[]中的数字

Pattern p = Pattern.compile(regEx);

Matcher m = p.matcher(str.trim());

while (m.find()) {

return m.group().trim();

}

return "";

}

@Override

public int compareTo(Sort o) {

// TODO Auto-generated method stub

//return 0;

return (int) (Long.valueOf(str) - Long.valueOf(o.str));

}

}

结果发现过滤后的东西序列号是按顺序的但是并不是代码执行的逻辑顺序。我擦怎么办,工具代码也写了,居然不是我想要的结果,好在花了一些时间发现dmtracedump -ho 选项,经过研究发现,这玩意输出的内容居然是按逻辑顺序从上到下的,于是基于这一点我写一个项目,其实核心就是2个task完成了了解所有函数调用顺序的目的。

//核心任务:在captures文件目录下输出 基于最新.trace文件的函数调用信息的txt版本

//说明:dmtracedump 为 android sdk自带工具,要执行dmtracedump命令则需要先添加环境变量

task AppOutPutMethodOrder() {

doLast {

def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures";

def capturesDir = new File(capturesDirPath);

capturesDir.traverse {

if (it.isFile() && it.name.endsWith(".trace")) {

def orderName = it.name.replace("trace", "txt")

def orderFile = new File(capturesDirPath, orderName)

orderFile.write("")

def dmtracedumpDir = getDmtraceDumpDir();

//说明:dmtracedump 为 android sdk自带工具,要执行dmtracedump命令则需要先添加环境变量

def baseComand = dmtracedumpDir + "dmtracedump -ho " + it.absolutePath + " >> " + orderFile.absolutePath

println baseComand

String osNameMatch = System.getProperty("os.name").toLowerCase();

if (osNameMatch.contains("windows")) {

("cmd /c start /b " + baseComand).execute()

} else {

["bash", "-c", baseComand].execute()

}

}

}

}

}

/**

* read the sdk dir from local.properties

* eg :

* sdk.dir = /home/env/sdk

* so:

* dmtracedump.dir = /home/env/sdk/platform-tools

*

* @return the dir which dmtracedump tools exists

*/

def getDmtraceDumpDir() {

def rootDir = project.rootDir

def localProperties = new File(rootDir, "local.properties")

def sdkDir = null;

if (localProperties.exists()) {

Properties properties = new Properties()

localProperties.withInputStream { instr ->

properties.load(instr)

}

sdkDir = properties.getProperty('sdk.dir')

}

if (sdkDir == null || !(new File(sdkDir).exists())) {

sdkDir = android.getSdkDirectory().getAbsolutePath()

}

if (sdkDir == null || !(new File(sdkDir).exists())) {

sdkDir = android.plugin.getSdkFolder().getAbsolutePath()

}

def dmtraceDumpToolDir = sdkDir + File.separator + "platform-tools" + File.separator

if (new File(dmtraceDumpToolDir).exists()) {

return dmtraceDumpToolDir;

}

return ""

}

//这里AppFilterMethodOrder 任务其实也不需要 执行找到 \captures 目录找到 base_order.txt

//用Notepad++ 使用正则 先过滤 带 xit 的行 (我们只关注ent 行就行,ent代表进入执行函数 xit代表退出函数)再过滤掉你不关心的包名

// Notepad++ 中过滤将会使用到的命令行如下

//^.*xit.*$ //去除掉 含有 xit 字符串的行 然后替换为空

// ^((?!XXX).)*$ //去除不包含XXX的行 然后替换为空

//^\s+ //合并空行 然后替换为空

task AppFilterMethodOrder() {

doLast {

//TODO 替换为你想要过滤的包名

def filterPackageName = "com.zjw.appmethodorder"

if (project.hasProperty("package_name")) {

filterPackageName = project.getProperty("package_name")

}

//处理包名

def filterSignature = filterPackageName.replaceAll("[.]", "/")

def capturesDirPath = project.getProjectDir().getParentFile().path + File.separator + "captures";

def capturesDir = new File(capturesDirPath);

capturesDir.traverse {

if (it.isFile() && it.name.endsWith(".txt") && !it.name.contains("--filter")) {

def orderName = it.name.replace(".txt", "--filter.txt")

def orderTimeName = it.name.replace(".txt", "--timefilter.txt")

def orderFile = new File(capturesDirPath, orderName)

orderFile.write("")

def orderTimeFile = new File(capturesDirPath, orderTimeName)

orderTimeFile.write("")

it.eachLine { line ->

if (line.contains(" ent ")

//兼容不同版本traceview 有的是方法包名有的是方法签名

&& (line.contains(filterPackageName)

|| line.contains(filterSignature))

) {

orderFile.append(line + "\n")

}

//生成带xit 和 ent 的trace行 函数耗时计算方式: xit字符后 数值 减去 ent字符后的 数字 (差值就是耗时 单位:微妙)

//注意:好像函数体中含Thread.sleep的计算不准确

if (line.contains(filterPackageName)

|| line.contains(filterSignature)){

orderTimeFile.append(line + "\n")

}

}

}

}

}

}

3.如何使用

讲了一堆原理我们来说说这个库怎么用吧。

注意:请先确保 anroid sdk 中的dmtracedump 工具加入在你的环境变量中(Mac同学因为task面板执行的bug 需要把gradle添加到环境变量中)

首先编译运行项目,然后点击下图的时钟(这是使用工具打trace start 和 end)进行操作,可以参考上文所说的动作简介(记住你操作想想你的生命周期函数调用顺序,待会可以和生成的captures目录下base_order.txt或者生成的order.txt中的函数顺序做做对比)然后再点一次下图那个时钟。还有一种记录trace start 和 end的方式就是在修改代码,即使用android.os.Debug.startMethodTracing();和android.os.Debug.stopMethodTracing();

AAffA0nNPuCLAAAAAElFTkSuQmCC

以上操作完成后即会在captures目录生成com.zjw.appmethodorder_2017.03.25_21.41.trace文件,android studio会默认打开一个可视化窗口

AAffA0nNPuCLAAAAAElFTkSuQmCC

然后双击右侧面板的 AppOutPutMethodOrder任务 (特别注意:用Mac的同学注意了,现在已知双击执行task会输出空文件,貌似是studio的BUG,可以使用 ./gradlew AppOutPutMethodOrder执行该任务)如下图

AAffA0nNPuCLAAAAAElFTkSuQmCC

这一步完成就将在captures目录生成和trace文件同名的txt文件(如示例com.zjw.appmethodorder_2017.03.25_21.41.trace),该文件包含所有函数执行顺序

等待任务执行完成,再双击执行AppFilterMethodOrder任务 (特别注意:用Mac的同学注意了,现在已知双击执行task会输出空文件,貌似是studio的BUG,可以使用 ./gradlew AppFilterMethodOrder -P package_name=com.zjw.appmethodorder执行该任务)如下图窗口

AAffA0nNPuCLAAAAAElFTkSuQmCC

该任务目的就是过滤其他非相关包名,留下自己包名的函数,任务执行完成将在captures目录生成和·trace文件同名+--filter.txt文件(例如示例AppMethodOrder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt)

如下图:

AAffA0nNPuCLAAAAAElFTkSuQmCC

接下来打开trace文件同名+--filter.txt文件(例如示例AppMethodOrder\captures\com.zjw.appmethodorder_2017.03.25_21.41--filter.txt)就是上文效果中的那样啦。

4.关于扩展和改造

AAffA0nNPuCLAAAAAElFTkSuQmCC

这里改成你想要过滤的包名即可。

5.小工具

Windows 环境下 可使用tool文件夹下的Method-trace-analysis.jar 直接导入.trace文件,一键分析

AAffA0nNPuCLAAAAAElFTkSuQmCC

6.执行AppFilterMethodOrder 任务 新增后缀为--filterTime.txt 的文件,用于计算方法耗时

AAffA0nNPuCLAAAAAElFTkSuQmCC

如上图例:MainActivity.onPause方法执行耗时为149231-148152 = 1079,最终耗时为 1079μs(微秒) 约为 1毫秒

1.生成带xit 和 ent 的trace行 函数耗时计算方式: xit字符后 数值 减去 ent字符后的 数字 (差值就是耗时 单位:微妙)

2.注意:函数体中含Thread.sleep的计算不准确

7. 计算得出的函数耗时不是准确的时间

根据官方文档(https://developer.android.com/studio/profile/traceview.html) Interpreted code runs more slowly when profiling is enabled. Don't try to generate absolute timings from the profiler results (such as, "function X takes 2.5 seconds to run"). The times are only useful in relation to other profile output, so you can see if changes have made the code faster or slower relative to a previous profiling run.所知(我也用了代码在函数开头结尾用System.nanoTime()时间相减,发现,生成trace的时候确实会拖慢所以函数的执行时间,比System.nanoTime()时间相减得出的时间差的比较多)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值