记录一个因为dumpsys导致应用出现ANR的问题
admin
2024-03-24 20:11:16
0

我们先看一下这个ANR的主线程状态:

"main" prio=5 tid=1 Native| group="main" sCount=1 dsCount=0 flags=1 obj=0x7569ab08 self=0x77afe14c00| sysTid=3188 nice=0 cgrp=default sched=0/0 handle=0x78359a6550| state=S schedstat=( 4907584566 6966621388 17409 ) utm=341 stm=149 core=0 HZ=100| stack=0x7fd97db000-0x7fd97dd000 stackSize=8MB| held mutexes=kernel: (couldn't read /proc/self/task/3188/stack)native: #00 pc 000000000001f06c  /system/lib64/libc.so (syscall+28)native: #01 pc 00000000000221d0  /system/lib64/libc.so (__futex_wait_ex(void volatile*, bool, int, bool, timespec const*)+140)native: #02 pc 0000000000080f4c  /system/lib64/libc.so (pthread_cond_wait+60)native: #03 pc 000000000047f1fc  /system/lib64/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::postAndWait()+260)native: #04 pc 000000000047f0c8  /system/lib64/libhwui.so (android::uirenderer::renderthread::DrawFrameTask::drawFrame()+44)at android.view.ThreadedRenderer.nSyncAndDrawFrame(Native method)at android.view.ThreadedRenderer.draw(ThreadedRenderer.java:823)at android.view.ViewRootImpl.draw(ViewRootImpl.java:3312)at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:3116)at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:2481)at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1457)at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7202)at android.view.Choreographer$CallbackRecord.run(Choreographer.java:949)at android.view.Choreographer.doCallbacks(Choreographer.java:761)at android.view.Choreographer.doFrame(Choreographer.java:696)at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:935)at android.os.Handler.handleCallback(Handler.java:873)at android.os.Handler.dispatchMessage(Handler.java:99)at android.os.Looper.loop(Looper.java:193)at android.app.ActivityThread.main(ActivityThread.java:6734)at java.lang.reflect.Method.invoke(Native method)at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:506)at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)

从主线程的堆栈我们可以看出,应用block在了renderthread的postAndWait方法,我们接着看一下这个方法的实现:

void DrawFrameTask::postAndWait() {AutoMutex _lock(mLock);mRenderThread->queue().post([this]() { run(); });mSignal.wait(mLock);
}

从这里我们可以看出是mSignal这个在等待锁释放,进一步搜索发现mSignal是在unblockUiThread方法中释放的

void DrawFrameTask::unblockUiThread() {AutoMutex _lock(mLock);mSignal.signal();
}

那么unblockUiThread方法是什么时候调用的呢?搜索代码发现该方法在DrawFrameTask的run方法中调用

void DrawFrameTask::run() {ATRACE_NAME("DrawFrame");bool canUnblockUiThread;bool canDrawThisFrame;{TreeInfo info(TreeInfo::MODE_FULL, *mContext);canUnblockUiThread = syncFrameState(info);canDrawThisFrame = info.out.canDrawThisFrame;if (mFrameCompleteCallback) {mContext->addFrameCompleteListener(std::move(mFrameCompleteCallback));mFrameCompleteCallback = nullptr;}}// Grab a copy of everything we needCanvasContext* context = mContext;std::function callback = std::move(mFrameCallback);mFrameCallback = nullptr;// From this point on anything in "this" is *UNSAFE TO ACCESS*if (canUnblockUiThread) {unblockUiThread();}// Even if we aren't drawing this vsync pulse the next frame number will still be accurateif (CC_UNLIKELY(callback)) {context->enqueueFrameWork([callback, frameNr = context->getFrameNumber()]() {callback(frameNr);});}if (CC_LIKELY(canDrawThisFrame)) {context->draw();} else {// wait on fences so tasks don't overlap next framecontext->waitOnFences();}if (!canUnblockUiThread) {unblockUiThread();}
}

根据postAndWait方法我们可以看到run方法实际是在renderthread中的消息队列执行的,这里的消息队列可以理解为和Android Java层中的Handler那个消息队列差不多。因此我们可以得出结论主线程被DrawFrameTask::postAndWait方法block,是因为上一次postAndWait放入到renderthread消息队列的run方法并未执行,所以下一步我们需要找出renderthread这个线程的堆栈信息,看看这个线程在干什么

"RenderThread" daemon prio=7 tid=19 Native| group="main" sCount=1 dsCount=0 flags=1 obj=0x13280ed8 self=0x778f662c00| sysTid=3558 nice=0 cgrp=default sched=0/0 handle=0x778e87f4f0| state=R schedstat=( 36485428771 60660326144 33388 ) utm=1153 stm=2495 core=2 HZ=100| stack=0x778e784000-0x778e786000 stackSize=1009KB| held mutexes=kernel: (couldn't read /proc/self/task/3558/stack)native: #00 pc 000000000006f29c  /system/lib64/libc.so (write+8)native: #01 pc 000000000006c80c  /system/lib64/libc.so (__dwrite+16)native: #02 pc 000000000007d94c  /system/lib64/libc.so (fflush+180)native: #03 pc 000000000006c7c4  /system/lib64/libc.so (vdprintf+176)native: #04 pc 000000000007b7bc  /system/lib64/libc.so (dprintf+112)native: #05 pc 000000000012fc64  /system/lib64/libhwui.so (android::uirenderer::JankTracker::dumpFrames(int)+224)native: #06 pc 0000000000112f90  /system/lib64/libhwui.so (_ZNSt3__120__packaged_task_funcIZN7android10uirenderer12renderthread11RenderProxy15dumpProfileInfoEiiE4$_27NS_9allocatorIS5_EEFvvEEclEv$d1b19a60f4b1bb5f6a5927e3e6deb93d+84)native: #07 pc 000000000048290c  /system/lib64/libhwui.so (std::__1::packaged_task::operator()()+88)native: #08 pc 00000000004366c4  /system/lib64/libhwui.so (android::uirenderer::WorkQueue::process()+168)native: #09 pc 0000000000114ee8  /system/lib64/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+240)native: #10 pc 000000000000fa68  /system/lib64/libutils.so (android::Thread::_threadLoop(void*)+280)native: #11 pc 0000000000081a08  /system/lib64/libc.so (__pthread_start(void*)+36)native: #12 pc 00000000000234cc  /system/lib64/libc.so (__start_thread+68)(no managed stack frames)

从堆栈里面,我们可以很容易看到renderthread线程正在执行一个dumpFrames方法,而且block在了dprintf这个函数上。

void JankTracker::dumpFrames(int fd) {dprintf(fd, "\n\n---PROFILEDATA---\n");for (size_t i = 0; i < static_cast(FrameInfoIndex::NumIndexes); i++) {dprintf(fd, "%s", FrameInfoNames[i].c_str());dprintf(fd, ",");}for (size_t i = 0; i < mFrames.size(); i++) {FrameInfo& frame = mFrames[i];if (frame[FrameInfoIndex::SyncStart] == 0) {continue;}dprintf(fd, "\n");for (int i = 0; i < static_cast(FrameInfoIndex::NumIndexes); i++) {dprintf(fd, "%" PRId64 ",", frame[i]);}}dprintf(fd, "\n---PROFILEDATA---\n\n");
}

查阅资料得知,dprintf是一个往管道写入数据的方法。而写管道卡住可能有如下原因:
1、管道的read被关闭
2、管道的write写入太多数据,read太慢
3、整个系统的I/O出现了重大问题
因此我们需要看一下这个dumpFrames方法的管道是谁创建的,最终我们查阅源码的调用栈发现,这个管道是在AMS里面创建的

final void dumpGraphicsHardwareUsage(FileDescriptor fd,PrintWriter pw, String[] args) {...for (int i = procs.size() - 1 ; i >= 0 ; i--) {ProcessRecord r = procs.get(i);if (r.thread != null) {pw.println("\n** Graphics info for pid " + r.pid + " [" + r.processName + "] **");pw.flush();try {TransferPipe tp = new TransferPipe();try {r.thread.dumpGfxInfo(tp.getWriteFd(), args);tp.go(fd);} finally {tp.kill();}} catch (IOException e) {pw.println("Failure while dumping the app: " + r);pw.flush();} catch (RemoteException e) {pw.println("Got a RemoteException while dumping the app " + r);pw.flush();}}}}

可以看到这里实际是用户执行了dumpsys命令以后,AMS为每个Android进程创建了一个TransferPipe线程以及对应的管道,然后去获取对应的显示信息。我们再来看看管道的创建:

protected TransferPipe(String bufferPrefix, String threadName) throws IOException {mThread = new Thread(this, threadName);mFds = ParcelFileDescriptor.createPipe();mBufferPrefix = bufferPrefix;}
public static ParcelFileDescriptor[] createPipe() throws IOException {try {final FileDescriptor[] fds = Os.pipe();return new ParcelFileDescriptor[] {new ParcelFileDescriptor(fds[0]),new ParcelFileDescriptor(fds[1]) };} catch (ErrnoException e) {throw e.rethrowAsIOException();}}

从上面创建管道的代码中,我们可以看到这是一个阻塞的管道,也就是说如果write写满了,会直接阻塞write。然后我们再结合AMS的代码以及测试提供的dumpsys文件,我们可以看出其他进程的显示信息均正常打印,而我们这个ANR 应用的显示信息却没有,说明是ANR应用的显示信息太多了,导致占满了管道,进而阻塞了renderthread,进而阻塞了主线程,最终导致了ANR的发生。

相关内容

热门资讯

linux入门---制作进度条 了解缓冲区 我们首先来看看下面的操作: 我们首先创建了一个文件并在这个文件里面添加了...
C++ 机房预约系统(六):学... 8、 学生模块 8.1 学生子菜单、登录和注销 实现步骤: 在Student.cpp的...
A.机器学习入门算法(三):基... 机器学习算法(三):K近邻(k-nearest neigh...
数字温湿度传感器DHT11模块... 模块实例https://blog.csdn.net/qq_38393591/article/deta...
有限元三角形单元的等效节点力 文章目录前言一、重新复习一下有限元三角形单元的理论1、三角形单元的形函数(Nÿ...
Redis 所有支持的数据结构... Redis 是一种开源的基于键值对存储的 NoSQL 数据库,支持多种数据结构。以下是...
win下pytorch安装—c... 安装目录一、cuda安装1.1、cuda版本选择1.2、下载安装二、cudnn安装三、pytorch...
MySQL基础-多表查询 文章目录MySQL基础-多表查询一、案例及引入1、基础概念2、笛卡尔积的理解二、多表查询的分类1、等...
keil调试专题篇 调试的前提是需要连接调试器比如STLINK。 然后点击菜单或者快捷图标均可进入调试模式。 如果前面...
MATLAB | 全网最详细网... 一篇超超超长,超超超全面网络图绘制教程,本篇基本能讲清楚所有绘制要点&#...
IHome主页 - 让你的浏览... 随着互联网的发展,人们越来越离不开浏览器了。每天上班、学习、娱乐,浏览器...
TCP 协议 一、TCP 协议概念 TCP即传输控制协议(Transmission Control ...
营业执照的经营范围有哪些 营业执照的经营范围有哪些 经营范围是指企业可以从事的生产经营与服务项目,是进行公司注册...
C++ 可变体(variant... 一、可变体(variant) 基础用法 Union的问题: 无法知道当前使用的类型是什...
血压计语音芯片,电子医疗设备声... 语音电子血压计是带有语音提示功能的电子血压计,测量前至测量结果全程语音播报࿰...
MySQL OCP888题解0... 文章目录1、原题1.1、英文原题1.2、答案2、题目解析2.1、题干解析2.2、选项解析3、知识点3...
【2023-Pytorch-检... (肆十二想说的一些话)Yolo这个系列我们已经更新了大概一年的时间,现在基本的流程也走走通了,包含数...
实战项目:保险行业用户分类 这里写目录标题1、项目介绍1.1 行业背景1.2 数据介绍2、代码实现导入数据探索数据处理列标签名异...
记录--我在前端干工地(thr... 这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前段时间接触了Th...
43 openEuler搭建A... 文章目录43 openEuler搭建Apache服务器-配置文件说明和管理模块43.1 配置文件说明...