【问题描述】
home应用在运行monkey测试6个小时候,Native Heap增长到200MB,怀疑内存泄露。
我们可以动过dumpsys查看Native Heap的大小:
$adb shell dumpsys meminfo com.miui.home
Applications Memory Usage (kB):
Uptime: 12929068 Realtime: 12929060
** MEMINFO in pid 1393 [com.miui.home] **
Pss Private Private Swapped Heap Heap Heap
Total Dirty Clean Dirty Size Alloc Free
------ ------ ------ ------ ------ ------ ------
Native Heap 0 0 0 0 16998 16998 19865
Dalvik Heap 31437 31132 0 0 49618 43555 6063
Dalvik Other 1164 1164 0 0
Stack 192 192 0 0
Other dev 4348 2536 4 0
.so mmap 1118 360 28 0
.apk mmap 145 0 0 0
...
每个应用的Native Heap占用量都不一样,但一般是10MB量级的,如果超过100MB,那很可能就是泄露了。
【分析步骤】
用monkey脚本复现问题:
adb shell monkey -v -v -p com.miui.home --throttle 200 --pct-touch 20 --pct-motion 25 --pct-nav 20 --pct-majornav 15 --pct-appswitch 5 --pct-anyevent 15 --pct-trackball 0 --pct-syskeys 0 --ignore-crashes --monitor-native-crashes --bugreport 5000000 logcat_file=logcat.txt >log1555.txt
在跑monkey的同时,在手机中运行shell命令,实时查看Native Heap的增长情况:
# while true then do date dumpsys meminfo com.miui.home |grep "Native Heap:" sleep 60 done
这个脚本的意思是每分钟打印一次当前时间及home应用的Native Heap大小。
跑13个小时后摘出每个小时的Native Heap大小,得出如下列表:
Tue Jan 5 16:12:53 CST 2016 Native Heap: 13336 Tue Jan 5 17:13:00 CST 2016 Native Heap: 17256 Tue Jan 5 18:13:35 CST 2016 Native Heap: 21476 Tue Jan 5 19:13:09 CST 2016 Native Heap: 25836 Tue Jan 5 20:13:56 CST 2016me Native Heap: 30176 Tue Jan 5 21:13:31 CST 2016 Native Heap: 34836 Tue Jan 5 22:13:07 CST 2016 Native Heap: 38564 Tue Jan 5 23:13:44 CST 2016 Native Heap: 42948 Wed Jan 6 00:13:20 CST 2016 Native Heap: 47216 Wed Jan 6 01:13:57 CST 2016 Native Heap: 50720 Wed Jan 6 02:14:34 CST 2016 Native Heap: 55288 Wed Jan 6 03:14:11 CST 2016 Native Heap: 59360 Wed Jan 6 04:14:49 CST 2016 Native Heap: 63648 Wed Jan 6 05:15:26 CST 2016 Native Heap: 67648 Wed Jan 6 05:39:41 CST 2016 Native Heap: 69440
从上面列表可以看出Native Heap在稳步增长,由此可以看出,确实存在泄露。
而且可以判断可能是基础模块、基础操作有泄露,很可能手动测试就能测出。
将shell脚本的sleep时间缩短为10秒,然后手动操作home界面,发现每次滑动屏幕作左右屏切换时,就会泄露。
进一步通过多种尝试,发现是桌面上存在文件夹图标时,每次左右滑动屏幕必现泄露,而只有应用图标时,则不会泄露。
有了必现路径以后,就可以加log调试了。
libc的malloc有debug功能,只需要改如下代码:
// The value of libc.debug.malloc. #if !defined(LIBC_STATIC) static int g_malloc_debug_level = 2; /*0;*/ #endif
将g_malloc_debug_level从0改成2后,重新生成libc.so和libc_malloc_debug_leak.so后push到手机并重启。
重启后系统中的malloc就会是带有debug功能的malloc了,有啥区别呢?代码如下:
extern "C" void* leak_malloc(size_t bytes) { if (DebugCallsDisabled()) { return g_malloc_dispatch->malloc(bytes); } // allocate enough space infront of the allocation to store the pointer for // the alloc structure. This will making free'ing the structer really fast! // 1. allocate enough memory and include our header // 2. set the base pointer to be right after our header size_t size = bytes + sizeof(AllocationEntry); if (size < bytes) { // Overflow. errno = ENOMEM; return NULL; } void* base = g_malloc_dispatch->malloc(size); if (base != NULL) { uintptr_t backtrace[BACKTRACE_SIZE]; size_t numEntries = GET_BACKTRACE(backtrace, BACKTRACE_SIZE); AllocationEntry* header = reinterpret_cast<AllocationEntry*>(base); header->entry = record_backtrace(backtrace, numEntries, bytes); header->guard = GUARD; // now increment base to point to after our header. // this should just work since our header is 8 bytes. base = reinterpret_cast<AllocationEntry*>(base) + 1; } return base; }
从代码中可以看出,memleak版本的malloc,再申请内存的时候,会保存当前调用栈。
也就是说每一个malloc都会对应一个调用栈,这样我们后续就可以知道泄露的内存是从哪段代码里申请的。
但这种方式有一个问题,就是保存调用栈本身很消耗内存和CPU资源。
而我们系统中malloc/free的调用是非常频繁(每分钟几十万次)的,
所以打开这个debug功能后,系统会非常卡,甚至会出现ANR等一系列系统问题。
所以这个debug功能的实用性不是很强。
好在我们这个问题有必现路径,所以不需要运行太长时间。
打开调试开关,链接eclipse,操作home几分钟后用ddms的native heap的dump功能,
查出有一个从libhwui.so调用的malloc占用的特别多。
但程序无法运行太长时间,加上调用栈和so对不上(不知道什么原因),所以只能怀疑是这个libhwui.so库有泄露。
我们关注的只是home应用,而打开libc的debug开关后,会影响系统里的所有进程,所以这种调试方式有很多不必要的资源浪费。
好在我们有INJECT工具,可以只针对特定的进程加调试code。
关于INJECT:
1、每个进程都会共享so库的code段,但数据段是各自有一个备份。
比如应用A和应用B都会共享libc的malloc函数,但g_malloc_debug_level变量,他们有各自的一个备份。
2、每个模块调用外部模块的函数时,会从一个叫got表的数据区取得目标函数的地址,然后跳转过去的。
got表的每个表项都存有一个外部函数的地址,是一一对应的。而进程启动过程中linker加载动态库时,为每个动态库填写他们的got表。
比如libandroid_runtime.so和libcutils.so都有各自的got表,got表中对应malloc函数的表项里存放有malloc函数的实际地址。
malloc函数的实际地址是linker加载libc.so后确定的,然后再加载libcutils.so时,将malloc函数的实际地址值填写到libc.so的got表中,malloc对应的表项里。
3、修改代码会影响系统中所有进程,但修改got表只影响单个进程。甚至只影响单个进程的单个模块。
比如我们把A进程的libcutils.so的got表中malloc函数的跳转地址改掉,那只会影响libcutils.so中调用的malloc,libandroid_runtime.so中调用的malloc则不会受影响。
4、我们可以自己定义一个inject_malloc,里面可以加一些调试code,如打印LR寄存器,打印调用栈等,然后再调用malloc。
如:
extern "C" void* inject_malloc(size_t bytes){ GET_LINK_REGISTER(_lr); void* ptr; ptr = malloc(bytes); LOGD("malloc ptr=%p,size=%08lld,lr=%p",ptr,(unsigned long long)bytes,(void*)_lr); return ptr; } extern "C" void inject_free(void* p){ if(p) { LOGD("free ptr=%p",p); } free(p); }
由于还不能确定到底是哪个so库泄露,
所以先将home应用加载的所有so库的got表中的malloc和free的跳转地址,改成自定义的inject_malloc/inject_free,
并在自定义的函数里打印如上的LOG,其中malloc函数中,除了要打印buffer地址和大小外,还会打印LR寄存器的值。
这样我们就能确定是从哪个库里调用了malloc。
... 36037:01-07 10:11:25.662 9388 9437 D INJECT : malloc ptr=0xa7e84800,size=00008196,lr=0xb5d9260d 36038:01-07 10:11:25.663 9388 9437 D INJECT : free ptr=0x9934fc00 36041:01-07 10:11:25.664 9388 9388 D INJECT : malloc ptr=0x99372c00,size=00004100,lr=0xb5d926af 36044:01-07 10:11:25.668 9388 9388 D INJECT : malloc ptr=0x99376400,size=00004100,lr=0xb5d926af 36053:01-07 10:11:25.669 9388 9437 D INJECT : free ptr=0xa7e84800 36064:01-07 10:11:25.684 9388 9437 D INJECT : free ptr=0x99377800 36093:01-07 10:11:25.703 9388 9437 D INJECT : free ptr=0x9936f000 36132:01-07 10:11:25.726 9388 9437 D INJECT : free ptr=0x99378c00 36165:01-07 10:11:25.748 9388 9437 D INJECT : free ptr=0x99298800 36198:01-07 10:11:25.767 9388 9437 D INJECT : malloc ptr=0x99298800,size=00004100,lr=0xb5d926af 36208:01-07 10:11:25.782 9388 9437 D INJECT : malloc ptr=0xa7e84800,size=00008196,lr=0xb5d9260d ...
复现问题,得到如上的log后除去成对出现的malloc和free,如上面的红色部分。
这样剩下的只有malloc,没有free的就是泄露的buffer了。
虽然只有home应用打印log,但数据两还是很庞大的,得用脚本或程序解析这个log。
我这里是用程序解析,解析后的结论是,95%以上没free的malloc都有如下共同点:
1、size大小为4100,
2、LR为0xb5d926af
通过map表,可以查到0xb5d926af是libhwui.so的code段,因此确定libhwui.so中有泄露。
下一步INJECT只需要修改libhwui.so的malloc/free对应的跳转地址即可。
由于log量进一步减少,所以我们可以在malloc/free时多加一些调试code,比如调用栈:
extern "C" void* inject_malloc(size_t bytes){ GET_LINK_REGISTER(_lr); void* ptr; ptr = malloc(bytes); LOGD("malloc ptr=%p,size=%08lld,lr=%p",ptr,(unsigned long long)bytes,(void*)_lr); if(bytes == 4100) { android::CallStack stack(LOG_TAG,3); } return ptr; }
同样,利用解析程序可以得出泄露时候的调用栈如下:
01-07 15:32:14.108 12058 12091 D INJECT : malloc ptr=0xa9948400,size=00004100,lr=0xb5d926af 01-07 15:32:14.122 12058 12091 D INJECT : #00 pc 00052905 /system/lib/libhwui.so 01-07 15:32:14.122 12058 12091 D INJECT : #01 pc 00039b2f /system/lib/libhwui.so 01-07 15:32:14.122 12058 12091 D INJECT : #02 pc 0003d7f1 /system/lib/libhwui.so 01-07 15:32:14.122 12058 12091 D INJECT : #03 pc 0001a761 /system/lib/libhwui.so 01-07 15:32:14.122 12058 12091 D INJECT : #04 pc 0001c3ef /system/lib/libhwui.so 01-07 15:32:14.122 12058 12091 D INJECT : #05 pc 0001f3d5 /system/lib/libhwui.so (android::uirenderer::renderthread::RenderThread::threadLoop()+80) 01-07 15:32:14.122 12058 12091 D INJECT : #06 pc 0001006d /system/lib/libutils.so (android::Thread::_threadLoop(void*)+112) 01-07 15:32:14.122 12058 12091 D INJECT : #07 pc 0005fa87 /system/lib/libandroid_runtime.so (android::AndroidRuntime::javaThreadShell(void*)+70) 01-07 15:32:14.122 12058 12091 D INJECT : #08 pc 0003f4cf /system/lib/libc.so (__pthread_start(void*)+30) 01-07 15:32:14.122 12058 12091 D INJECT : #09 pc 00019c2b /system/lib/libc.so (__start_thread+6)
用addr2line解析:
#0层
$ addr2line -f -e libhwui.so 00052905 _ZN7android10uirenderer13DisplayListOpnwEjRNS0_15LinearAllocatorE frameworks/base/libs/hwui/DisplayListOp.h:67
对应源码:
class DisplayListOp {
public:
// These objects should always be allocated with a LinearAllocator, and never destroyed/deleted.
// standard new() intentionally not implemented, and delete/deconstructor should never be used.
virtual ~DisplayListOp() { LOG_ALWAYS_FATAL("Destructor not supported"); }
static void operator delete(void* ptr) { LOG_ALWAYS_FATAL("delete not supported"); }
static void* operator new(size_t size) = delete; /** PURPOSELY OMITTED **/
static void* operator new(size_t size, LinearAllocator& allocator) {
return allocator.alloc(size);
}
#1层
$ addr2line -f -e libhwui.so 00039b2f _ZN7android10uirenderer5Layer5deferERKNS0_14OpenGLRendererE frameworks/base/libs/hwui/Layer.cpp:246 (discriminator 1)
对应源码:
void Layer::defer(const OpenGLRenderer& rootRenderer) { ATRACE_LAYER_WORK("Optimize"); updateLightPosFromRenderer(rootRenderer); const float width = layer.getWidth(); const float height = layer.getHeight(); if (dirtyRect.isEmpty() || (dirtyRect.left <= 0 && dirtyRect.top <= 0 && dirtyRect.right >= width && dirtyRect.bottom >= height)) { dirtyRect.set(0, 0, width, height); } deferredList.reset(new DeferredDisplayList(dirtyRect)); DeferStateStruct deferredState(*deferredList, *renderer, RenderNode::kReplayFlag_ClipChildren); renderer->setViewport(width, height); renderer->setupFrameState(dirtyRect.left, dirtyRect.top, dirtyRect.right, dirtyRect.bottom, !isBlend()); renderNode->computeOrdering(); renderNode->defer(deferredState, 0); deferredUpdateScheduled = false; }
#2层:
$ addr2line -f -e libhwui.so 0003d7f1 _ZN7android10uirenderer14OpenGLRenderer11updateLayerEPNS0_5LayerEb frameworks/base/libs/hwui/OpenGLRenderer.cpp:389
对应源码:
bool OpenGLRenderer::updateLayer(Layer* layer, bool inFrame) { if (layer->deferredUpdateScheduled && layer->renderer && layer->renderNode.get() && layer->renderNode->isRenderable()) { if (inFrame) { endTiling(); debugOverdraw(false, false); } if (CC_UNLIKELY(inFrame || Properties::drawDeferDisabled)) { layer->render(*this); } else { layer->defer(*this); } if (inFrame) { resumeAfterLayer(); startTilingCurrentClip(); } layer->debugDrawUpdate = Properties::debugLayersUpdates; layer->hasDrawnSinceUpdate = false; return true; } return false; }
这里的malloc比较特殊,hwui模块自定义了内存管理模块LinearAllocator,泄露的内存就是通过这个类来管理的。
接下来就是得研究这部分代码了。
首先了解一下alloc过程:
我们从调用栈可以看到#1层的renderNode->defer()到#0层的allocator.alloc(size),
中间丢了很多细节。这主要是因为很多中间函数被内联展开了,所以不会在调用栈中体现。
class Layer : public VirtualLightRefBase { public: ... sp<RenderNode> renderNode;
因此renderNode->defer()就会调用下面的函数:
void RenderNode::defer(DeferStateStruct& deferStruct, const int level) { DeferOperationHandler handler(deferStruct, level); issueOperations<DeferOperationHandler>(deferStruct.mRenderer, handler); } template <class T> void RenderNode::issueOperations(OpenGLRenderer& renderer, T& handler) { ... LinearAllocator& alloc = handler.allocator(); int restoreTo = renderer.getSaveCount(); handler(new (alloc) SaveOp(SkCanvas::kMatrix_SaveFlag | SkCanvas::kClip_SaveFlag), PROPERTY_SAVECOUNT, properties().getClipToBounds()); ...
其中SaveOp是继承自DisplayListOp:
class DisplayListOp { public: ... static void* operator new(size_t size, LinearAllocator& allocator) { return allocator.alloc(size); } class StateOp : public DisplayListOp { ... class SaveOp : public StateOp {
DispayListOp重载了new操作符,allocator.alloc()相当于
RenderNode::issueOperations()中的handler.allocator().alloc(),
而这个handler是DeferOperationHandler()类对象,它的allocator()定义如下:
class DeferOperationHandler { public: DeferOperationHandler(DeferStateStruct& deferStruct, int level) : mDeferStruct(deferStruct), mLevel(level) {} ... inline LinearAllocator& allocator() { return *(mDeferStruct.mAllocator); } ... };
allocator()是mDeferStruct.mAllocator,而mDeferStruct又是构造函数中的参数。
RenderNode::defer()中调用了DeferOperationHandler()的构造函数,并传入参数deferStruct。
而RenderNode::defer()中的deferStruct又是其上一级函数Layer::defer()中构造并传进来的。
void Layer::defer(const OpenGLRenderer& rootRenderer) { ... deferredList.reset(new DeferredDisplayList(dirtyRect)); DeferStateStruct deferredState(*deferredList, *renderer, RenderNode::kReplayFlag_ClipChildren); ... renderNode->defer(deferredState, 0);
现在我们要找deferredState的mAllocator:
struct PlaybackStateStruct { protected: PlaybackStateStruct(OpenGLRenderer& renderer, int replayFlags, LinearAllocator* allocator) : mRenderer(renderer) , mReplayFlags(replayFlags) , mAllocator(allocator) {} public: ... LinearAllocator * const mAllocator; ... }; ... struct DeferStateStruct : public PlaybackStateStruct { DeferStateStruct(DeferredDisplayList& deferredList, OpenGLRenderer& renderer, int replayFlags) : PlaybackStateStruct(renderer, replayFlags, &(deferredList.mAllocator)), mDeferredList(deferredList) {} DeferredDisplayList& mDeferredList; };
DeferStateStruct继承自PlaybackStateStruct,
其mAllocator是构造函数中转进来的deferredList的mAllocator。
而这个deferredList就是Layer::defer()中new出来的。
deferredList.reset(new DeferredDisplayList(dirtyRect));
DeferredDisplayList()类的定义及它的mAllocator如下:
class DeferredDisplayList { public: ... LinearAllocator mAllocator; };
注意这里的mAllocator已经不是指针了,这意味着它的构造和析构是构造和析构DeferredDisplayList时被调用的。
自此,绕了一大圈我们找到了#0中return allocator.alloc(size);中的alloctor,它就是DeferredDisplayList中的mAllocator。
继续看LinearAllocator::alloc():
void* LinearAllocator::alloc(size_t size) { size = ALIGN(size); ... ensureNext(size); void* ptr = mNext; mNext = ((char*)mNext) + size; mWastedSpace -= size; return ptr; } void LinearAllocator::ensureNext(size_t size) { if (fitsInCurrentPage(size)) return; if (mCurrentPage && mPageSize < MAX_PAGE_SIZE) { mPageSize = min(MAX_PAGE_SIZE, mPageSize * 2); mPageSize = ALIGN(mPageSize); } mWastedSpace += mPageSize; Page* p = newPage(mPageSize); if (mCurrentPage) { mCurrentPage->setNext(p); } mCurrentPage = p; if (!mPages) { mPages = mCurrentPage; } mNext = start(mCurrentPage); } LinearAllocator::Page* LinearAllocator::newPage(size_t pageSize) { pageSize = ALIGN(pageSize + sizeof(LinearAllocator::Page)); ... void* buf = malloc(pageSize); // <<<<这里申请内存 return new (buf) Page(); } LinearAllocator::LinearAllocator() : mPageSize(INITIAL_PAGE_SIZE) ... , mDedicatedPageCount(0) {}
mPageSize的初始值INITIAL_PAGE_SIZE是4096,
所以newPage中加上LinearAllocator::Page的大小后,最终pageSized大小就是4100,这与前面得出的结论是一致的。
关于LinearAllocator:
LinearAllocator是管理hwui模块的内存管理单元,由于hwui频繁申请和释放小字节的对象,
如果每个对象都用系统的malloc,则会增加内存碎片并降低效率,所以这里就用内存池管理这些小内存。
第一次申请时先从用malloc向系统申请4KB的堆内存作为内存池(Page类),
后面申请的小内存都从这个内存池里拿(fitsInCurrentPage()就是做这事情的)。
当这个4KB的内存池满了以后,它会再次调用malloc申请8KB的内存,然后将这个作为新的内存池,加入到内存池链表中。
这样的做法有个限制,就是不方便释放。
因为如果要释放内存,我们还得用额外的机制来管理这些释放的内存,这样会增加模块复杂度,效率也会降低。
所以LinearAllocator的做法是,当LinearAllocator析构时,才将内存池链表里的内存给释放掉。如:
LinearAllocator::~LinearAllocator(void) { while (mDtorList) { auto node = mDtorList; mDtorList = node->next; node->dtor(node->addr); } Page* p = mPages; while (p) { Page* next = p->next(); p->~Page(); free(p); // <<<<这里释放内存 RM_ALLOCATION(mPageSize); p = next; } }
如果这里有内存泄露,只有两种可能性
1、内存池一直在涨,LinearAllocator不会被析构
2、LinearAllocator本身有泄露,也就是只有构造,没有析构。
在ensureNext()里调用newPage()之前将mPageSize的大小打印出来,
发现绝大部分mPageSize的size是4096,偶尔会有一两个8192,因此可以判断LinearAllocator有泄露。
我只需要在LinearAllocator的构造和析构函数里打印log,看是否成对出现就可以了。
复测的结果确定是LinearAllocator对象有泄露,
又LinearAllocator是DeferredDisplayList的成员,可以判断DeferredDisplayList对象泄露了。
用同样的方法,很容易确定DeferredDisplayList对象有泄露。
在DeferredDisplayList的构造和析构函数里打印调用栈,
发现都是指向Layer::defer()中的deferredList.reset(new DeferredDisplayList(dirtyRect)):
void Layer::defer(const OpenGLRenderer& rootRenderer) { ... deferredList.reset(new DeferredDisplayList(dirtyRect)); //<<<<<< DeferStateStruct deferredState(*deferredList, *renderer, RenderNode::kReplayFlag_ClipChildren); ... renderNode->defer(deferredState, 0);
构造指向这一行很好理解,为什么析构函数会指向这里呢?
从deferredList的定义可以知道,deferredList是智能指针。
std::unique_ptr<DeferredDisplayList> deferredList;
它的reset()方法会将旧的指针替换为新的指针,并调用旧指针指向的对象的析构函数。如:
template <typename _Tp, typename _Dp = default_delete<_Tp> > class unique_ptr { ... void reset(pointer __p = pointer()) noexcept { using std::swap; swap(std::get<0>(_M_t), __p); if (__p != pointer()) get_deleter()(__p); } ...
从log中我们还可以看到虽然DeferredDisplayList的构造和析构的调用栈都指向同一个地址,但它们并非成对出现。
构造会比析构多很多,也就是说当reset()时deferredList如果指向空指针,这里就不会有析构函数了。
既然不成对出现,那肯定还有别的地方要析构这个DeferredDisplayList对象。而要析构这个对象肯定会用到deferredList。
由于deferredList是private的成员,所以肯定只会在DeferredDisplayList.cpp和DeferredDisplayList.h中会用到。
可疑的代码只有一处:
void Layer::cancelDefer() { renderNode = nullptr; deferredUpdateScheduled = false; deferredList.release(); }
在这里将deferredList指向的指针打印出来,发现只要在这里打出来的地址必定是泄露的。
且deferredList.release()时不会调用DeferredDisplayList()的析构函数。
Layer::defer()和Layer::cancelDefer()代码上是挨着写的,
既然Layer::defer()里会申请内存,那Layer::cancelDefer()里应该会释放内存。
难道作者对智能指针的理解有误?首先从代码角度确定release()确实不会调用析构函数。
pointer release() noexcept { pointer __p = get(); std::get<0>(_M_t) = pointer(); return __p; }
显然,这里确实不存在析构。
为此在cancelDefer()里加上deferredList.reset(0);
void Layer::cancelDefer() { renderNode = nullptr; deferredUpdateScheduled = false; deferredList.reset(0); deferredList.release(); }
重新编译libhwui.so,push后重启手机,反复滑动桌面,泄露现象不再了。
这样基本上就能确定问题了。应该是原生问题。
从git log中发现这段code是14年底时候在M上加上去的。L的deferredList是普通指针。修改记录如下:
diff --git a/libs/hwui/Layer.h b/libs/hwui/Layer.h - DeferredDisplayList* deferredList; + std::unique_ptr<DeferredDisplayList> deferredList; diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp void Layer::cancelDefer() { renderNode = NULL; deferredUpdateScheduled = false; - if (deferredList) { - delete deferredList; - deferredList = NULL; - } + deferredList.release(); }
用L试了下确实没泄露,只有M的机型包括NEXUS也有泄露。
接着给根据提交记录找到google工程师的邮箱,发邮件反馈的一下。
没过10分钟就收到回复说这个问题已经解决,并提供了path。
【解决方案】
diff --git a/libs/hwui/Layer.cpp b/libs/hwui/Layer.cpp void Layer::cancelDefer() { renderNode = NULL; deferredUpdateScheduled = false; - deferredList.release(); + deferredList.reset(nullptr); }
该修改是1个月前提交的,还是比google工程师晚了一步!