android调用栈
① Android webkit怎么打印函数调用栈呢
栈函数很多都没
② 如何调试分析Android中发生的tombstone
Android中较容易出现以下三类问题:Force close / ANR / Tombstone
前两者主要是查看当前的进程或者系统框架层的状态和堆栈就基本可以分析出来,本文主要讨论一下tombstone的情况。
tombstone一般是由Dalvik错误、状态监视调试器、C层代码以及libc的一些问题导致的。
当
系统发生tombstone的时候,kernel首先会上报一个严重的警告信号(signal),上层接收到之后,进程的调试工具会把进程中当时的调用栈
现场保存起来,并在系统创建了data/tombstones目录后把异常时的进程信息写在此目录里面,开发者需要通过调用栈来分析整个调用流程来找出出
问题的点。
基本工具:
prebuilt/linux-x86/toolchain/arm-eabi-4.4.0/bin
在分析的时候仔细读取汇编会获得更多有用的异常发生时的信息。
1.arm-eabi-addr2line 将类似libxxx.so 0x00012345的调用栈16进制值翻译成文件名和函数名
arm-eabi-addr2line -e libxxx.so 0x00012345
2.arm-eabi-nm 列出文件的符号信息
arm-eabi-nm -l -C -n -S libdvm.so > dvm.data
3.arm-eabi-objmp 列出文件的详细信息
arm-eabi-objmp -C -d libc.so > libc.s
通过以上工具的分析 ,我们可以得到较完整的调用栈以及调用逻辑的汇编码。
然后需要结合ARM架构及ARM汇编的知识(有些情况下可能需要使用gdb)
来分析出现tombstone的原因,以下是本人遇到过的一些tombstone的情况:
1.无效的函数指针:指针为NULL或者已经被重新赋值
2.strlen崩溃:导致不完全的栈信息,栈被破坏
3.FILE操作:因为stdio并非线程安全的,多线程操作时,容易出现异常。
望采纳。
③ android 主线程栈native代表什么意思
调用栈的作用, 栈可以记录你运行中的函数调用(或者叫做函数执行顺序), 每个线程都有一个独自的调用栈, 至于为什么, 你可以想一下, 如果线程和调用它的进程共用调用栈的话, 线程还能并行进行么? 调用栈记录了运行顺序, 开创独立的栈的原因就是为了并行进行.... 这就是线程最重要的作用
④ 如何调试分析android中发生的tombstone
1.arm-eabi-addr2line 将类似libxxx.so 0x00012345的调用栈16进制值翻译成文件名和函数名
arm-eabi-addr2line -e libxxx.so 0x00012345
2.arm-eabi-nm 列出文件的符号信息
arm-eabi-nm -l -C -n -S libdvm.so > dvm.data
3.arm-eabi-objmp 列出文件的详细信息
arm-eabi-objmp -C -d libc.so > libc.s
通过以上工具的分析 ,我们可以得到较完整的调用栈以及调用逻辑的汇编码。
然后需要结合ARM架构及ARM汇编的知识(有些情况下可能需要使用gdb)
来分析出现tombstone的原因,以下是本人遇到过的一些tombstone的情况:
1.无效的函数指针:指针为NULL或者已经被重新赋值
2.strlen崩溃:导致不完全的栈信息,栈被破坏
3.FILE操作:因为stdio并非线程安全的,多线程操作时,容易出现异常。
⑤ Android 10.0 Activity的启动流程
本文主要学习记录,基于Android 10的源码,有错误欢迎指正,主要目的是梳理流程图。
以进程为单位的调用栈图如下:
1.activity中的startActivity方法最终都会通过拿到ATSM的代理IActivityTaskManager调用的startActivity;
2.之后进入system server进程中的ATMS startActivity,ATMS 经过收集Intent信息,然后使用ActivityStackSupervisor.startSpecificActivityLocked,如果进程已经存在,则直接使用realStartActivityLocked,通过App的binder客户端的代理ApplicationThread调用回到bindApplication,走入Activity的启动流程;如果进程不存在则通过socket链接Zygote,请求fork新的进程;
3.App进程创建完成后,进程启动会调用ActivityThread.main方法,初始化主线程Handler,接着走入attach方法,然后通过AMS的代理调用AMS的attachApplication方法,并将App进程的通信代理ApplicationThread传入AMS;
4.AMS获取到ATMS调用ApplicationThread的bindApplication回到App进程的ActivityThread.ApplicationThread.bindApplication方法中,然后使用Handler切换到主线程执行handleBindApplication,这里初始化了App的进程名字、时间,用户的硬件配置,包括App的文件系统,创建了App的Context实例,Instrumentation实例,调用App的onCreate回调方法,同时告诉AMS APP初始化工作完毕;
5.AMS接着会调用ATMS的attachApplication,最后调用ClientLifecycleManager的scheleTransaction方法,通过App的Binder代理ApplicationThread回到ActivityThread;
6.进入ActivityThread.ApplicationThread.scheleTransaction方法之后就进入了Activity的onStart、onResume回调
创建进程之前的过程主要是AMS的内部信息收集的判断的过程,下面主要看一下App进程启动的源码流程
从应用进程被创建开始,ActivityThread.main被执行
调用ActivityThread的attach方法,然后将activity和AMS通信的Binder代理IApplicationThread实例传入AMS
接着进入AMS进程,ActivityManagerService.attachApplicationLocked
1.thread.bindApplication :该方法主要讲App进程的配置信息通过IApplicationThread Binder通信回传到ActivityThread中
2.mAtmInternal.attachApplication :mAtmInternal实际就是ActivityTaskManager的实例,通过LocalServices加载
那么这里相当于走到了ActivityTaskManagerServer的attachApplication中
先看第一条:
注意:ActivityThread中存在于Binder通信的代理--》ApplicationThread extends IApplicationThread.Stub
ActivityThread--》ApplicationThread--》bindApplication
这里的bindApplication主要初始化了AppBindData,然后发送BIND_APPLICATION给APP的主线程BIND_APPLICATION,最后执行了handleBindApplication
handleBindApplication如下:
ActivityThread--》class H extends Handler
该方法主要在App进程中对App的一些硬件资源配置申请的属性、App的文件夹等完成App基本信息的初始化
接着看第二条:mAtmInternal.attachApplication
mAtmInternal.attachApplication最终会调用mRootActivityContainer.attachApplication(wpc)
RootActivityContainer.attachApplication
接着调用ActivityStackSupervisor.realStartActivityLocked开始创建Activity
ActivityStackSupervisor.realStartActivityLocked
创建ClientLifecycleManager和ClientTransactionHandler来辅助管理Activity的生命周期
注意
clientTransaction.addCallback是LaunchActivityItem
lifecycleItem是ResumeActivityItem
ClientLifecycleManager.scheleTransaction最终会调用ClientTransaction的schele方法
那么这个mClient是IApplicationThread的实例,那么此时也就回到了ActivityThread的ApplicationThread中
ActivityThread的ApplicationThread中
因为ActivityThread继承ClientTransactionHandler,所以到了ClientTransactionHandler中
通过Handler发送消息EXECUTE_TRANSACTION到H中
接着TransactionExecutor的execute方法
LaunchActivityItem.execute方法
client其实是在ActivityThread的实例,那么就回到了ActivityThread的handleLaunchActivity
接着调用performLaunchActivity
在performLaunchActivity中,主要是加载App的资源包,然后创建了Activity的context实例,并创建了Activity的实例,接着调用activity.attach方法,attach执行完之后调用了onCreate方法。
activity.attach
activity.attach中主要
1.创建了PhoneWindow实例
2.设置了Window接口的监听
3.初始化了成员变量,包括线程和WindowManager
到此Oncreate已经完成,那么OnStart和OnResume去哪了?
TransactionExecutor的execute方法
之前们只分析了executeCallbacks,接着executeLifecycleState方法
TransactionExecutor的executeLifecycleState方法
cycleToPath:lifecycleItem即为ResumeActivityItem
第一点:
int finish = lifecycleItem.getTargetState()
lifecycleItem对应ResumeActivityItem,如下:
ResumeActivityItem的getTargetState方法
对应ActivityLifecycleItem中的枚举类型:
第二点:ActivityClientRecord中的mLifecycleState,由于在前面已经执行了handleLaunchActivity所以mLifecycleState=1
对应ActivityLifecycleItem中的枚举类型:
PRE_ON_CREATE = 0
所以final int star = 1
接着看getLifecyclePath,此时start=1,finish=3
那么返回的IntArray就是2
接着看performLifecycleSequence
最终执行的是handleStartActivity所以最终走到了ActivityThread的handleResumeActivity
两点:
调用activity.performStart
调用Instrumetation.callActivityOnPostCreate
performStart方法:
调用了Instrumentation.callActivityOnStart方法:
最终到了activity的onStart方法
第二点:Instrumentation.callActivityOnPostCreate
上面主要走了cycleToPath,接着ResumeActivityItem.execute
调用了handleResumeActivity方法
handleResumeActivity最终调用performResumeActivity
调用了Instrumentation.callActivityOnResume,
到了activity.onResume()方法
参考文章: https://blog.csdn.net/u011386173/article/details/87802765
⑥ Android App内存优化
内存优化就是对内存问题的一个预防和解决,做内存优化能让应用挂得少、活得好和活得久。
挂的少:
“挂”指的是 Crash,内存问题导致 Crash 的具体表现就是内存溢出异常 OOM。
活得好:
活得好指的是使用流畅,Android 中造成界面卡顿的原因有很多种,其中一种就是由内存问题引起的。内存问题之所以会影响到界面流畅度,是因为垃圾回收(GC,Garbage Collection),在 GC 时,所有线程都要停止,包括主线程,当 GC 和绘制界面的操作同时触发时,绘制的执行就会被搁置,导致掉帧,也就是界面卡顿。
活得久:
活得久指的是我们的应用在后台运行时不会被干掉。Android 会按照特定的机制清理进程,清理进程时优先会考虑清理后台进程。清理进程的机制就是LowMemoryKiller。在 Android 中不同的进程有着不同的优先级,当两个进程的优先级相同时,低杀会优先考虑干掉消耗内存更多的进程。也就是如果我们应用占用的内存比其他应用少,并且处于后台时,我们的应用能在后台活下来,这也是内存优化为我们应用带来竞争力的一个直接体现。
内存占用是否越少越好?
当系统 内存充足 的时候,我们可以多用 一些获得更好的性能。当系统 内存不足 的时候,我们希望可以做到 ”用时分配,及时释放“。内存优化并不能一刀切。
我们都知道,应用程序的内存分配和垃圾回收都是由Android虚拟机完成的,在Android 5.0以下,使用的是Dalvik虚拟机,5.0及以上,则使用的是ART虚拟机。
Android虚拟机Dalvik和ART
1、内存区域划分
详细请看以下两篇文章(建议全看):
java内存四大区_JVM内存区域划分
Android 内存机制
2、内存回收
垃圾收集的标记算法(找到垃圾):
垃圾收集算法(回收垃圾):
引用类型:强引用、软引用、弱引用、虚引用
对象的有效性=可达性+引用类型
JAVA垃圾回收机制-史上最容易理解看这一篇就够了
Android:玩转垃圾回收机制与分代回收策略
android中还存在低杀机制,这种情况属于系统整机内存不足,直接把应用进程杀掉的情况。
Android后台杀死系列:LowMemoryKiller原理
1、内存溢出
系统会给每个App分配内存空间也就是heap size值,当app占用的内存加上申请的内存超过这个系统分配的内存限额,最终导致OOM(OutOfMemory)使程序崩溃。
通过命令 getprop |grep dalvik.vm.heapsize 可以获取系统允许的最大
注意:在设置了heapgrowthlimit的状况下,单个进程可用最大内存为heapgrowthlimit值。在android开发中,若是要使用大堆,须要在manifest中指定android:largeHeap为true,这样dvm heap最大可达heapsize。
关于heapsize & heapgrowthlimit
2、内存泄漏
Android系统虚拟机的垃圾回收是通过虚拟机GC机制来实现的。GC会选择一些还存活的对象作为内存遍历的根节点GC Roots,通过对GC Roots的可达性来判断是否需要回收。内存泄漏就是 在当前应用周期内不再使用的对象被GC Roots引用,造成该对象无法被系统回收,以致该对象在堆中所占用的内存单元无法被释放而造成内存空间浪费,使实际可使用内存变小。简言之,就是 对象被持有导致无法释放或不能按照对象正常的生命周期进行释放。
Android常见内存泄漏汇总
3、内存抖动
指的是在短时间内大量的新对象被实例化,运行时可能无法承载这样的内存分配,在这种情况下就会导致垃圾回收事件被大量调用,影响到应用程序的UI和整体性能,最终可能导致卡顿和OOM。
常见情况:在一些被频繁调用的方法内不断地创建对象。例如在View 的onDraw方法内new 一些新的对象。
注意内存抖动也会导致 OOM,主要原因有如下两点:
1、Android Studio Profiler
作用
优点
内存抖动问题处理实战
理解内存抖动的概念的话,我们就能明白只要能找到抖动过程中所产生的对象及其调用栈,我们就能解决问题,刚好Android Studio 的Porfiler里面的Memory工具就能帮我们记录下我们操作过程中或静止界面所产生的新对象,并且能清晰看到这些对象的调用栈。
选择Profile 中 的Memory ,选择 Record Java/Kotlin allocations,再点击Record开始记录, Record Java/Kotlin allocations 选项会记录下新增的对象。
操作完成之后,点击如图所示的红脑按钮,停止记录。
停止记录后,我们就可以排序(点击 Allocations可以排序)看看哪些对象或基本类型在短时间被频繁创建多个,点击这些新增的对象就可以看到它的完成的调用链了,进而就找找到导致内存抖动的地方在哪里了。
2、利用DDMS 和 MAT(Memory Analyzer tool)来分析内存泄漏
我们利用工具进行内存泄漏分析主要是用对比法:
a.先打开正常界面,不做任何操作,先抓取一开始的堆文件。
b.一顿胡乱操作,回到原来操作前的界面。主动触发一两次GC,过10秒再抓取第二次堆文件。
c.通过工具对比,获取胡乱操作后新增的对象,然后分析这些新增的对象。
DDMS作用:抓取堆文件,主动触发GC。(其实也是可以用Android Studio 的Profile里面的Memory工具来抓取堆文件的,但是我这边在利用Profile 主动触发gc 的时候会导致程序奔溃,也不知道是不是手机的问题,所以没用Android Studio的Profiler)
MAT作用:对堆文件进行对比,找到多出的对象,找到对象的强引用调用链。
以下是详细的过程:
步骤1.打开DDMS,选择需要调试的应用,打开初始界面,点击下图的图标(Dump Hprof File)先获取一次堆文件。
步骤2.对应用随便操作后,回到一开始的界面,先多触发几次GC ,点击下图的图标(Cause Gc)来主动触发GC,然后再次点击 Dump Hprof File 图标来获取堆文件。
步骤3.通过Android Studio Profile 或者 DDMS mp 的堆文件无法在MAT 打开,需要借助android sdk包下的一个工具hprof-conv.exe来转换。
格式为 hprof-conv 旧文件路径名 要转换的名称;
例如:hprof-conv 2022-04-13_17-54-40_827.hprof change.hprof
步骤4.把两份堆文件导入MAT,然后选择其中第二次获取的堆文件,点击 如图所示的 Histogram查看。
步骤5.点击下图图标,Compare To Another Heap Dump ,选择另一份堆文件。
6.会得出下图所示的 Hitogram 展示,我们主要看Objects 这一列。 如下图所示 “+ 2” 则代表前面两份堆文件对比,这个对象多了两个,我们主要就是要分析这些多了出来,没有被回收的对象。
7.加入我们从增加的对象中,看到了MainActivity ,则需要从一开始打开的Hitogram 展示里面找到这个对象的调用栈。如下图所示,搜索MainActivity
8.看到下图所示解雇,然后鼠标右键点击下图红色圈圈着的MainActivity ,选择 Merger Shortest Paths to Gc Roots ,再选择 exclude all phantom/weak/soft etc.references ,就可以看到这个MainActivity 对象的强引用链,至此我们就可以找到MainActivity对象是被什么引用导致无法回收了。
3、内存泄露检测神器之LeakCanary(线下集成)
自行学习了解,接入简单,使用简单,基本可以解决大部分内存泄漏问题。
github地址 : https://github.com/square/leakcanary/
学习地址 : https://square.github.io/leakcanary/changelog/#version-22-2020-02-05
针对内存抖动的建议:
针对内存泄漏问题的建议:
针对内存溢出问题的建议(主要就是要减少内存占用):
建议参考:
深入探索 Android 内存优化(炼狱级别)
对于 优化的大方向,我们应该优先去做见效快的地方,主要有以下三部分:内存泄漏、内存抖动、Bitmap。完善监控机制也是我们的重点,能帮助我们对内存问题快速分析和处理。
参考:
深入探索 Android 内存优化(炼狱级别)
⑦ android怎样将activity放入全局栈
Activity是Android程序的表现层。程序的每一个显示屏幕就是一个Activity。正在运行的Activity处在栈的最顶端,它是运行状态的。
当有新的Activity进入屏幕最上端时,原来的Activity就会被压入第二层。如果他的屏幕没有被完 全遮盖,那么他处于Paused状态,如果他被遮盖那么处于Stop状态。
不管处于任何一层,都可能在系统觉得资源不足时被强行关闭,当然关闭时栈底的程序最先被关闭。
譬如:当你在程序中调用 Activity.finish()方法时,结果和用户按下 BACK 键一样:他告诉 Activity Manager该Activity实例可以被“回收”。随后 Activity Manager 激活处于栈第二层的 Activity 并重 新入栈,把原 Activity 压入到栈的第二层,从 Running 状态转到 Paused 状态。
在BlackBerry中,提供了一个管理Screen的栈,用来从任何地方来关闭位于最上一层的Screen,使用UiApplication.getUiApplication().getActiveScreen()来得到位于最上一层的Screen的实例,并且使用UiApplication.getUiApplication().popScreen()来关闭一个Screen或关闭当前最上一层的Screen,但是Android却未提供相应的功能,只能在一个Activity的对象里面调用finish来关闭自己,不能关闭其他的Activity。比如我们想实现一个功能从屏幕A—>屏幕B—>屏幕C—>屏幕D,然后在在转到屏幕D之前将屏幕B和C关闭,在屏幕B和屏幕C界面点击会退按钮都可以回退到上一个屏幕,但是在屏幕D上点击会退按钮让其回退到A,此外在一些循环跳转的界面上如果不在合适的地方将一些不需要的屏幕关闭,那么经过多次跳转后回导致内存溢出。对此我们可以设计一个全局的Activity栈,使用这个栈来管理Activity。管理Activity的类的定义如下:
import java.util.Stack;
import android.app.Activity;
public class ScreenManager {
private static Stack activityStack;
private static ScreenManager instance;
private ScreenManager(){
}
public static ScreenManager getScreenManager(){
if(instance==null){
⑧ Android-Choreographer 垂直同步 Vsync
view.requestLayout 调用的是 parent.requestLayout,直到 DecorView 最终到 ViewRootImpl.requestLayout 方法。
提示: requestLayout() 跟 invalidate() 区别在于 PFLAG_FORCE_LAYOUT、PFLAG_INVALIDATED,invalidate 不会重新测量布局,只会重新绘制
调用栈:mChoreographer.postCallback(int callbackType, Runnable action, Object token) --> postCallbackDelayed() --> postCallbackDelayedInternal()
至此从调用 requestLayout 到请求 Vsync 信号过程已经结束。
下面看收到 Vsync 信号后,如何处理 mTraversalRunnable 任务。
doTraversal()方法则是 测量、布局、绘制 入口,此处不做分析。
Vsync 垂直同步:
涉及到垂直刷新脉冲、vsync 、gpu 缓冲区 Frame Buffer、Back Buffer 三重缓存,跟 Choreographer
gpu 像素栅格化
垂直同步使得显卡的输出帧数和屏幕的刷新速度保持一致,其中 vsync 用来同步信息,buffer 缓存数据,当 vsync 出现时,cpu 会立即处理下一帧数据写入到缓存中,
之后gpu再渲染数据写在同一个缓存中,当vsync时,下一帧的 buffer 跟当前帧所在的buffer数据交换,当如果之前帧未显示完,是不会进行数据交换的。屏幕扫描下一次的数据显示。
当一个信号来时,假设a b buffer都被占用,此时gpu使用c缓存下一帧的数据,可以有效减少掉帧的几率。
1、view.requestLayout 调用的是 parent.requestLayout ,直到 DecorView 最终到 ViewRootImpl.requestLayout 方法。
2、首先判断正在测量布局,没有则 checkThread 检验当前是否在主线程。在 scheleTraversals 首先中执行同步屏障,其次再将任务 postCallback 给 Choreographer,Choreographer 将任务保存在 mCallbackQueues 中,同时发送 MSG_DO_SCHEDULE_CALLBACK 的同步消息给FrameHandler。FrameHandler 的优先执行 CALLBACK 同步消息调用 doScheleCallback,mCallbackQueues 不为空且 callback 不是延迟执行,调用 scheleFrameLocked 方法请求 Vsync 信号。当运行在 Looper 线程,则立刻调度 vsync,否则,发送消息到UI线程再调度 vsync。其中是通过 FrameDisplayEventReceiver 调度 vysnc。
FrameDisplayEventReceiver 有两个作用,一个是 scheleVsync 请求调度,另一个是接收 vsync 信号回调 onVsync,当接收到 vsync 信号时,调用doFrame 方法,开始渲染下一帧。
doFrame 可以分为三步:一是计算掉帧逻辑,二是记录帧绘制信息,三是处理多种 callback,依次是 input 调用栈,会回调到 DecorView 的 dispatchTouchEvent。
二是 animation 调用栈,执行动画;三是 Traversal 调用栈,即最发送给 Choreographer 的任务
动画如何流畅执行: 调用animation.start时,最终在AnimationHandler会给Choreographer.FrameCallback 回调 doFrame,里面 post了自己。
⑨ android打印调用堆栈
修改Android.bp,加入callstack模块
注意这里android是命名空间,如果已经在android命名空间内则不需要写android::
⑩ 如何打出Android程序调用stack trace
找出程序的调用堆栈 trace 可以知道是谁调用了这个接口,也能快速学习程序的调用流程,非常实用。但需要注意的是,不能在正式代码中使用,只能用于调试,这个非常耗资源也会造成 log 泛滥。
下面就介绍如何在 Android Java/C++/C 程序当中打印出程序调用 trace,如果需要在其他环境中使用的话 C++/C 部分需要移植 corkscrew 库。
Java
非常简单,创建一个 Throwable 对象,就可以得到当前的 stack trace。下面例子是打出调用 foobar() 函数的 trace:
1 private void foobar() {
2 Throwable t = new Throwable();
3 Log.d(TAG, "stack trace is ", t);
4 }
C++
也比较简单,使用 utils/Callstack 类即可。头文件位于 frameworks/native/include/utils/CallStack.h,一般无需修改 Android.mk 可直接使用。下面例子是打出调用 Foo::bar() 函数的 trace:
复制代码
1 #include <utils/CallStack.h>
2
3 void Foo::bar() {
4 // CallStack::CallStack(const char* logtag, int32_t ignoreDepth, int32_t maxDepth)
5 CallStack *t = new CallStack("Trace", 1, 30);
6 delete t;
7 }
复制代码
C
稍微麻烦一点,需要直接调用 corkscrew/backtrace。其实 C++ 里的 utils/Callstack 也是使用 corkscrew/backtrace,只是进行了封装更易于使用。我们参照 CallStack.cpp 里面代码即可。下面例子是打出调用 foobar() 函数的 trace:
NOTE: C 不能直接调用 C++ 代码,除非在 C++ 类中添加相应的 C wrapper,或者通过 dlsym 动态载入。
复制代码
1 #include <corkscrew/backtrace.h>
2
3 void mpStackTrace(const char* logtag, int32_t ignoreDepth, int32_t maxDepth) const {
4 static const int MAX_DEPTH = 31;
5 static const int MAX_BACKTRACE_LINE_LENGTH = 800;
6
7 if (maxDepth > MAX_DEPTH) {
8 maxDepth = MAX_DEPTH;
9 }
10 backtrace_frame_t mStack[MAX_DEPTH];
11 ssize_t count = unwind_backtrace(mStack, ignoreDepth + 1, maxDepth);
12 if (count <= 0) {
13 LOGE("Can not get stack trace");
14 return;
15 }
16
17 backtrace_symbol_t symbols[count];
18
19 get_backtrace_symbols(mStack, count, symbols);
20 for (size_t i = 0; i < count; i++) {
21 char line[MAX_BACKTRACE_LINE_LENGTH];
22 format_backtrace_line(i, &mStack[i], &symbols[i],
23 line, MAX_BACKTRACE_LINE_LENGTH);
24 ALOG(LOG_DEBUG, logtag, "%s%s",
25 "",
26 line);
27 }
28 free_backtrace_symbols(symbols, count);
29 }
30
31 void foobar() {
32 mpStackTrace("Trace", 1, 30);
33 }
复制代码
头文件位于 system/core/include/corkscrew/backtrace.h,在 Android.mk 中还需要加入:
1 LOCAL_SHARED_LIBRARIES += libcorkscrew