命令的用法:
由于项目原因,需要用am profile 命令来收集目标进程运行过程中的函数调用关系,其用法直接在通过adb 登录真机或模拟器(./adb shell)之后运行 am 即可得到:
开始收集: am profile start profiling: am profile <PROCESS> start <FILE>
停止收集: am profile <PROCESS> stop
(其中<PROCESS> 是所要收集的进程,直接用进程号即可, <FILE>是收集后trace所在的文件,一般直接放在/sdcard 目录下,所以直接用/sdcard/文件名 即可。)
通过以上命令可以收集到正确的trace,但该trace 是不可读得。要分析该trace 只需通过 ./adb pull 命令将/sdcard 下的trace 文件pull到本地然后运行源码(已编译)中该目录“out/host/linux-x86/bin” 下的 traceview 命令(./traceview xxx ,其来龙去脉留做下一篇分析)。该命令运行完之后便可以生成图形界面的traceview 界面,信息非常丰富,非常直观,对于分析程序的执行状况有极大的帮助,网上介绍的资料很多,此处不再介绍。
深度追踪:
以上介绍了am profile 收集trace 的方法,和对收集到的trace 进行分析的方法。为了更深入的了解 am profile 所做的工作,这里有必要再进一步来看一下所用的am profile 命令到底做了什么工作,而收集了trace。
通过在android 源码中执行 "find -name am" ,发现am的源码文件为“frameworks/base/cmds/am/src/com/android/commands/am/Am.java”,那对am 命令的探索当然要从这里开始了。
从Am.java 的 main() 函数开始,首先通过“(new Am()).run(args);” 创建了自身的一个实体,并以main()函数的参数调用自己类的 run(args) 函数。目标转到run()函数,这里首先执行“mAm = ActivityManagerNative.getDefault();” 得到系统的ActivityManagerService 的代理对象(binder机制),然后解析传入的参数,即args ,对不同的参数执行不同的处理,以am 开头的命令有很多(如am start 、am startservice等)。由于收集trace 传入的参数是 profile ,所以这里直接跳到对profile 处理的函数runProfile()即可,在该函数里对profile 之后的参数进行处理,若之后的参数为start 则将函数中的start 由false 置为true ,并取参数中start 之后的下一个参数即进程号给process ; 若参数为stop ,则取其下一个参数即进程号给process。之后通过解析args 得到的参数调用函数刚开始得到的mAm 的mAm.profileControl(process, userId, start, profileFile, fd, profileType),函数。
现在转入frameworks/base/core/java/android/app/ActivityManagerNative.java文件中的profileControl(process, userId, start, profileFile, fd, profileType)函数,该函数是ActivityManagerProxy类(mAm是ActivityManagerService的代理对象)中的一个函数,该函数对传入的参数装入Parcel 类型的data对像中,然后调用mRemote.transact(PROFILE_CONTROL_TRANSACTION, data, reply, 0); 向活动管理服务发送PROFILE_CONTROL_TRANSACTION类型的命令(典型的binder通信架构)。至此终于明白来,以am profile 为代表的 am 命令,在Am.java中的只是调用的一个接口,真正实现am 命令的是ActivityManagerService。也就是应用了android 的binder机制,将服务和对服务的使用分离,对服务的使用通过代理对象向服务发送命令来实现。
接着继续分析对PROFILE_CONTROL_TRANSACTION的处理,该类型命令的处理在ActivityManagerNative的onTransct函数里面,该函数用于处理由代理端通过binder传送过来的命令。对PROFILE_CONTROL_TRANSACTION的处理首先是通过出入的data 解析出process(进程号) 、start(是否开始)等信息,然后调用 profileControl(process, userId, start, path, fd, profileType); 函数继续处理。该函数在frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件中。
转入frameworks/base/services/java/com/android/server/am/ActivityManagerService.java文件继续分析,在 profileControl(String process, int userId, boolean start,
String path, ParcelFileDescriptor fd, int profileType)函数中首先解析参数,如果start 为true ,则调用proc.thread.profilerControl(start, path, fd, profileType);函数进行处理,若start 为false ,则调用stopProfilerLocked(proc, path, profileType);函数停止收集trace 。这两个函数最终调用的都是frameworks/base/core/java/android/app/ActivityThread.java文件中的profilerControl(boolean start, String path, ParcelFileDescriptor fd, int profileType) 函数,由start来标志最终将要完成的工作。
转入frameworks/base/core/java/android/app/ActivityThread.java文件继续分析。 profilerControl(...)函数最终调用queueOrSendMessage(H.PROFILER_CONTROL, pcd, start ? 1 : 0, profileType); 明显使用了android 的消息机制来发送了一个异步消息,那么其处理函数必然在 handleMessage(...)中,果然在 handleMessage()函数中有处理
PROFILER_CONTROL类型消息的case,其处理方式是调用handleProfilerControl(msg.arg1 != 0, (ProfilerControlData)msg.obj, msg.arg2);函数。进入该函数分析如果start 为true ,则最终调用mProfiler.startProfiling()函数;如果start为false ,则调用mProfiler.stopProfiling()函数。其中 mProfiler为 Profiler类型的对象。
转入Profiler类进行分析。首先是startProfiling()函数,该函数最终调用Debug.startMethodTracing(profileFile, profileFd.getFileDescriptor(), 8 * 1024 * 1024, 0);该函数调用了类Debug的startMethodTracing()函数,在这里的函数说明可以看到参数“8 * 1024 * 1024”代表为收集到的trace所分配的默认buffer大小为8M;而Debug中的函数由调用了VMDeg中的函数,之后就通过JNI调到本地来具体实现收集trace 了,以后再分析,这里不再展开。stopProfiling()函数有与startProfiling()函数相似的执行路径,这里不再赘述。
这里指出native的执行函数为:/dalvik/vm/native/dalvik_system_VMDebug.cpp中的“Dalvik_dalvik_system_VMDebug_startMethodTracingNative()”函数。
DONE
至此整个am profile命令 (可以代表其他的am 命令)从最初的调用的JAVA层的具体执行路径就分析完了。在其执行过过程中主要用到了binder机制和Message机制,抓住了这两点便比较好分析了。