https://my.oschina.net/u/3168816/blog
朴英敏,小米MIUI部门。从事嵌入式开发和调试工作8年多,擅长逆向分析方法,主要负责解决安卓系统稳定性问题。
上周音乐组同事反馈了一个必现Native Crash问题,tombstone如下:
崩溃的原因是pc指向了一个没有可执行权限的内存地址上。
初步分析:
对应的代码如下:
其中,Functor类重载了()操作符:
因此,()操作其实就是调用了Functor类的一个虚函数,它的具体实现目前还不清楚。
对应的汇编代码如下:
崩溃时的寄存器值如下:
可以看到,r5和pc值是相等的,可以知道,确定是崩溃在2302e这一行汇编代码中。
而查看寄存器对应的内存值,发现有点问题:
[r0] = [7ac59c98] = 798223298,这个和r3值(400fc1b8)不一样,
同样
[r3+8] = [400fc1b8 + 8] = 7c04acb8,这个值也和r5值(79801f28)不一样。
这在平时的tombstone里是非常少见的!
乍一看非常不可思议,但仔细想想tombstone的生成过程,就能发现其中的问题。
原来寄存器信息是错位崩溃时的cpu context,保存在崩溃时的线程私有的信号栈和内核栈中,直到debuggerd去获取这个值,它是不会被修改的。
而内存是进程中的各个线程共享的,所以在发生异常到debuggerd打印内存信息这段过程中(其实是相对很长的一个过程),别的线程是有可能修改内存值的。
为了证明别的线程在改这个内存值,在callDrawGLFunction()函数中的若干处打印了Functor和它的vtbl(虚函数表地址)值:
抓到的log如下:
可以确定确实有别的线程在修改这个值。
这里就存在两个可能性了:
1、别的线程也持有functor指针,并修改内容
2、functor是野指针,对应的内存已经还回系统,其他模块可任意使用。
而对象的vtbl一般是不会修改的,所以2的可能性更大一些。
为了查明是哪个线程在改,对functor指向的内存做了写保护操作:
push到手机中复现问题,很容易抓到访问权限引起的crash。
而每次的crash的线程和位置都不一样,也就是不同的线程在不同的函数中读写这个地址。
这样基本上就确定是野指针问题,进入下一阶段的分析。
关于野指针:
所谓野指针就是一个对象被释放后又被使用,可能是释放的问题,也可能是使用的问题。
我们已经知道使用的位置,接下来要找出是从哪释放的。
找到释放对象的最笨的方法,是在free()函数里打印调用栈。
但这么做有两个问题:
1、log太量多,一秒内可能会有成千上万的malloc/free函数被调用。
2、打印调用栈的函数本身会调用free函数,这样会陷入死循环。
为了解决上面两个问题,需要用到hook技术。
关于hook技术:
要了解hook技术,得先了解外部函数的调用过程。
所谓外部函数就是外部模块中定义的函数。比如,libhwui.so中的某个源文件中调用了malloc函数,而这个malloc函数是libc.so中定义的。
当编译libhwui.so的这个源文件时,对应调用malloc的地方会生成如下的汇编代码:
这里blx是arm的跳转指令,addr是目标地址,也就是malloc函数的地址,那这个malloc函数的地址如何确定?
这个编译的阶段是无法确定的,只有当运行时进程加载完libc.so以后,malloc函数的地址才能被确定。
所以编译器在编译的时候会在libbinder.so中留出一部分空间作为地址表,专门用于存放外部函数的地址,这个区域叫got表。
每一个本模块调用到的外部函数都对应got表中的一项。
当然got表里面的内容是在进程启动阶段,加载动态库时被连接器linker填充的。
而编译阶段我们只需要将代码写成:
1、从got表对应位置获取外部函数地址
2、跳转到这个外部函数的地址
这个动作需要由若干的指令来完成,所以跳转指令blx addr中的addr其实指向本模块的一组指令:
这组指令所在的区域就是elf文件结构里的plt表,plt表中每一个外部函数都对应一个表项,如:
0000cb74
cb74: e28fc600 add ip, pc, #0, 12
cb78: e28cca29 add ip, ip, #167936 ;
cb7c: e5bcf1e8 ldr pc, [ip, #488]! ;
0000c8bc
c8bc: e28fc600 add ip, pc, #0, 12
c8c0: e28cca29 add ip, ip, #167936 ;
c8c4: e5bcf3b8 ldr pc, [ip, #952]! ;
每一个plt表项都是做相同操作:
1、先获取got表中外目标函数对应的地址(前两行);
2、从got表中获取地址目标函数的地址,并赋给pc寄存器(第三行)。
下面给出got表和plt表在so文件中的位置:
readelf -S libhwui.so
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 0] NULL 00000000 000000 000000 00 0 0 0
[ 1] .interp PROGBITS 00000134 000134 000013 00 A 0 0 1
[ 2] .dynsym DYNSYM 00000148 000148 002420 10 A 3 1 4
[ 3] .dynstr STRTAB 00002568 002568 0056a4 00 A 0 0 1
[ 4] .hash HASH 00007c0c 007c0c 001134 04 A 2 0 4
[ 5] .rel.dyn REL 00008d40 008d40 002bc8 08 A 2 0 4
[ 6] .rel.plt REL 0000b908 00b908 000a78 08 A 2 7 4
=>[ 7] .plt PROGBITS 0000c380 00c380 000fc8 00 AX 0 0 4
[ 8] .text PROGBITS 0000d348 00d348 01ef30 00 AX 0 0 8
[ 9] .ARM.exidx ARM_EXIDX 0002c278 02c278 001fb8 08 AL 8 0 4
[10] .ARM.extab PROGBITS 0002e230 02e230 000930 00 A 0 0 4
[11] .rodata PROGBITS 0002eb60 02eb60 0036a4 00 A 0 0 4
[12] .fini_array FINI_ARRAY 00034010 033010 000004 00 WA 0 0 4
[13] .data.rel.ro PROGBITS 00034018 033018 001910 00 WA 0 0 8
[14] .init_array INIT_ARRAY 00035928 034928 00000c 00 WA 0 0 4
[15] .dynamic DYNAMIC 00035934 034934 000140 08 WA 3 0 4
=>[16] .got PROGBITS 00035a74 034a74 00058c 00 WA 0 0 4
[17] .data PROGBITS 00036000 035000 00025c 00 WA 0 0 4
[18] .bss NOBITS 0003625c 03525c 000068 00 WA 0 0 4
[19] .comment PROGBITS 00000000 03525c 000010 01 MS 0 0 1
[20] .note.gnu.gold-ve NOTE 00000000 03526c 00001c 00 0 0 4
[21] .ARM.attributes ARM_ATTRIBUTES 00000000 035288 00003e 00 0 0 1
[22] .gnu_debuglink PROGBITS 00000000 0352c6 000010 00 0 0 1
[23] .shstrtab STRTAB 00000000 0352d6 0000dc 00 0 0 1
我们的hook技术就是通过修改so的got表来截获so中的某些外部函数调用。
so的代码段是多个进程共享的,但它的数据段私有的,而got表就是数据段。
所以我们只修改music应用进程的libhwui.so的got表中free函数对应的项,影响范围将大大减少。
那改成什么值呢?一般是我们自己定义的函数,比如:
为了不影响原来的逻辑,打印完debug信息,还是要调用原来被hook的函数。
有了hook技术后能完美的解决野指针中的两个问题,下面继续分析问题。
实施hook:
我们有了hook,但目前还不知道是哪个so中释放了functor。73635000-73646000 rw-p 00000000 00:00 0
73646000-73648000 r-xp 00000000 b3:18 1287 /system/lib/libwebviewchromium_plat_support.so
=>73648000-73649000 r--p 00001000 b3:18 1287 /system/lib/libwebviewchromium_plat_support.so
73649000-7364a000 rw-p 00002000 b3:18 1287 /system/lib/libwebviewchromium_plat_support.so
7364a000-73684000 rw-p 00000000 00:00 0
73684000-73696000 r-xp 00000000 b3:18 1034 /system/lib/libjavacrypto.so
73696000-73697000 r--p 00011000 b3:18 1034 /system/lib/libjavacrypto.so
73697000-73698000 rw-p 00012000 b3:18 1034 /system/lib/libjavacrypto.so
而需要注意的是,C++对象的释放是delete函数,
libwebviewchromium_plat_support.so不会直接调用libc的free函数,而是调用libc++.so中的delete函数,再由delete函数调用free函数,
所以我们得hook libc++.so的free函数,但打印调用栈的模块也依赖libc++.so,所以如果在hook函数中打印调用栈,也会遇到死循环问题。
所以我们得hook libwebviewchromium_plat_support.so中的delete函数,这样既减少log量,也能避免死循环。
先确认libwebviewchromium_plat_support.so是否依赖了delete函数:
$ readelf -s libwebviewchromium_plat_support.so |grep UND
0: 00000000 0 NOTYPE LOCAL DEFAULT UND
1: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_finalize
2: 00000000 0 FUNC GLOBAL DEFAULT UND __cxa_atexit
4: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr0
5: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_unwind_cpp_pr1
6: 00000000 0 FUNC GLOBAL DEFAULT UND getrlimit
7: 00000000 0 FUNC GLOBAL DEFAULT UND setrlimit
8: 00000000 0 FUNC GLOBAL DEFAULT UND __errno
9: 00000000 0 FUNC GLOBAL DEFAULT UND strerror
10: 00000000 0 FUNC GLOBAL DEFAULT UND __android_log_print
=> 11: 00000000 0 FUNC GLOBAL DEFAULT UND _Znwj
=> 12: 00000000 0 FUNC GLOBAL DEFAULT UND _ZdlPv
14: 00000000 0 FUNC GLOBAL DEFAULT UND __android_log_assert
...
51: 00000000 0 FUNC GLOBAL DEFAULT UND __aeabi_llsr
52: 00000000 0 OBJECT GLOBAL DEFAULT UND __popcount_tab
其中11项_Znwj是new的符号,_ZdlPv是delete的符号。
接下来就用工具hook libwebviewchromium_plat_support.so的delete函数:
extern void _ZdlPv(void *);
void inject__ZdlPv(void* ptr) {
LOGD("delete %p",ptr);
dumpNativeStack();
dumpJavaStack();
_ZdlPv(ptr);
}
hook后复现问题,抓到的log如下:
10-27 21:19:52.961 8027 8027 D ObserverLayout: onStop: clz=com.miui.player.display.view.DisplayFragmentLayout{45665838 V.E..... ........ 0,0-1080,1920 #7f080039 app:id/content}
10-27 21:19:52.965 8027 8027 I MusicBaseFragment: onDestroyView the view is still attached, delay destroy
10-27 21:19:52.966 8027 8027 D INJECT : delete 0x7a7b8530
10-27 21:19:52.986 8027 8027 D INJECT : #00 pc 000015f6 /system/lib/libinject.so (inject__ZdlPv+21)
10-27 21:19:52.986 8027 8027 D INJECT : #01 pc 00001134 /system/lib/libwebviewchromium_plat_supp
10-27 21:19:52.986 8027 8027 D INJECT : #02 pc 00001088 /system/lib/libwebviewchromium_plat_supp
10-27 21:19:52.987 8027 8027 D INJECT : #03 pc 0001d30c /system/lib/libdvm.so (dvmPlatformInvoke+112)
10-27 21:19:52.987 8027 8027 D INJECT : #04 pc 0004d8da /system/lib/libdvm.so (dvmCallJNIMethod(unsigned int const*, JV+397)
10-27 21:19:52.987 8027 8027 D INJECT : #05 pc 00026720 /system/lib/libdvm.so
10-27 21:19:52.987 8027 8027 D INJECT : #06 pc 0002d790 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
10-27 21:19:52.987 8027 8027 D INJECT : #07 pc 0002adf4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JVa+184)
10-27 21:19:52.988 8027 8027 D INJECT : #08 pc 00060058 /system/lib/libdvm.so (dvmInvokeMethod(Object*, Method const*, +391)
10-27 21:19:52.988 8027 8027 D INJECT : #09 pc 00067ff6 /system/lib/libdvm.so
10-27 21:19:52.988 8027 8027 D INJECT : #10 pc 00026720 /system/lib/libdvm.so
10-27 21:19:52.988 8027 8027 D INJECT : #11 pc 0002d790 /system/lib/libdvm.so (dvmMterpStd(Thread*)+76)
10-27 21:19:52.988 8027 8027 D INJECT : #12 pc 0002adf4 /system/lib/libdvm.so (dvmInterpret(Thread*, Method const*, JVa+184)
10-27 21:19:52.988 8027 8027 D INJECT : #13 pc 0005fd74 /system/lib/libdvm.so (dvmCallMethodV(Thread*, Method const*, O+335)
10-27 21:19:52.988 8027 8027 D INJECT : #14 pc 000494c2 /system/lib/libdvm.so
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.webview.chromium.DrawGLFunctor.nativeDestroyGLFunctor(Native Method)
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.webview.chromium.DrawGLFunctor.access$000(DrawGLFunctor.java:31)
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.webview.chromium.DrawGLFunctor$DestroyRunnable.run(DrawGLFunctor.java:91)
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.org.chromium.content.common.CleanupReference.runCleanupTaskInternal(CleanupReference.java:159)
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.org.chromium.content.common.CleanupReference.access$300(CleanupReference.java:32)
10-27 21:19:52.989 8027 8027 D INJECT : at com.android.org.chromium.content.common.CleanupReference$LazyHolder$1.handleMessage(CleanupReference.java:93)
10-27 21:19:52.990 8027 8027 D INJECT : at com.android.org.chromium.content.common.CleanupReference.handleOnUiThread(CleanupReference.java:147)
10-27 21:19:52.990 8027 8027 D INJECT : at com.android.org.chromium.content.common.CleanupReference.cleanupNow(CleanupReference.java:141)
10-27 21:19:52.990 8027 8027 D INJECT : at com.android.webview.chromium.DrawGLFunctor.destroy(DrawGLFunctor.java:46)
10-27 21:19:52.990 8027 8027 D INJECT : at com.android.webview.chromium.WebViewChromium.destroy(WebViewChromium.java:430)
10-27 21:19:52.990 8027 8027 D INJECT : at android.webkit.WebView.destroy(WebView.java:667)
10-27 21:19:52.990 8027 8027 D INJECT : at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64)
10-27 21:19:52.990 8027 8027 D INJECT : at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:115)
10-27 21:19:52.990 8027 8027 D INJECT : at com.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216)
10-27 21:19:52.991 8027 8027 D INJECT : at android.app.Fragment.performDestroyView(Fragment.java:1898)
10-27 21:19:52.991 8027 8027 D INJECT : at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:954)
10-27 21:19:52.991 8027 8027 D INJECT : at android.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167)
10-27 21:19:52.991 8027 8027 D INJECT : at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715)
10-27 21:19:52.991 8027 8027 D INJECT : at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544)
10-27 21:19:52.992 8027 8027 D INJECT : at android.app.FragmentManagerImpl$3.run(FragmentManager.java:502)
10-27 21:19:52.992 8027 8027 D INJECT : at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449)
10-27 21:19:52.992 8027 8027 D INJECT : at android.app.FragmentManagerImpl$1.run(FragmentManager.java:443)
10-27 21:19:52.992 8027 8027 D INJECT : at android.os.Handler.handleCallback(Handler.java:733)
10-27 21:19:52.992 8027 8027 D INJECT : at android.os.Handler.dispatchMessage(Handler.java:95)
10-27 21:19:52.992 8027 8027 D INJECT : at android.os.Looper.loop(Looper.java:136)
10-27 21:19:52.993 8027 8027 D INJECT : at android.app.ActivityThread.main(ActivityThread.java:5016)
10-27 21:19:52.993 8027 8027 D INJECT : at java.lang.reflect.Method.invokeNative(Native Method)
10-27 21:19:52.993 8027 8027 D INJECT : at java.lang.reflect.Method.invoke(Method.java:515)
10-27 21:19:52.993 8027 8027 D INJECT : at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
10-27 21:19:52.993 8027 8027 D INJECT : at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
10-27 21:19:52.993 8027 8027 D INJECT : at dalvik.system.NativeStart.main(Native Method)
10-27 21:19:53.020 8027 8027 I OpenGLRenderer: functor=0x7a7b8530,vtbl=0x400fc1b8
从log中可以看到,确实是在distroy view的时候释放了Functor,而随后再Renderer中又使用了这个Functor。
打印崩溃时的java调用栈如下:
10-27 21:19:53.274 8027 8027 I dalvikvm: "main" prio=5 tid=1 TIMED_WAIT10-27 21:19:53.279 8027 8027 I dalvikvm: | group="main" sCount=0 dsCount=0 obj=0x41716ca8 self=0x415344f8
10-27 21:19:53.279 8027 8027 I dalvikvm: | sysTid=6895 nice=-6 sched=0/0 cgrp=apps handle=1074409812
10-27 21:19:53.280 8027 8027 I dalvikvm: | state=R schedstat=( 0 0 0 ) utm=184 stm=61 core=3
10-27 21:19:53.280 8027 8027 I dalvikvm: at android.view.GLES20Canvas.nDrawDisplayList(Native Method)
10-27 21:19:53.281 8027 8027 I dalvikvm: at android.view.GLES20Canvas.drawDisplayList(GLES20Canvas.java:420)
10-27 21:19:53.281 8027 8027 I dalvikvm: at android.view.HardwareRenderer$GlRenderer.drawDisplayList(HardwareRenderer.java:1709)
10-27 21:19:53.281 8027 8027 I dalvikvm: at android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:1525)
10-27 21:19:53.282 8027 8027 I dalvikvm: at android.view.ViewRootImpl.draw(ViewRootImpl.java:2475)
10-27 21:19:53.282 8027 8027 I dalvikvm: at android.view.ViewRootImpl.performDraw(ViewRootImpl.java:2347)
10-27 21:19:53.283 8027 8027 I dalvikvm: at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1977)
10-27 21:19:53.284 8027 8027 I dalvikvm: at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1094)
10-27 21:19:53.285 8027 8027 I dalvikvm: at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:5703)
10-27 21:19:53.285 8027 8027 I dalvikvm: at android.view.Choreographer$CallbackRecord.run(Choreographer.java:764)
10-27 21:19:53.286 8027 8027 I dalvikvm: at android.view.Choreographer.doCallbacks(Choreographer.java:577)
10-27 21:19:53.287 8027 8027 I dalvikvm: at android.view.Choreographer.doFrame(Choreographer.java:547)
10-27 21:19:53.288 8027 8027 I dalvikvm: at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:750)
10-27 21:19:53.289 8027 8027 I dalvikvm: at android.os.Handler.handleCallback(Handler.java:733)
10-27 21:19:53.289 8027 8027 I dalvikvm: at android.os.Handler.dispatchMessage(Handler.java:95)
10-27 21:19:53.290 8027 8027 I dalvikvm: at android.os.Looper.loop(Looper.java:136)
10-27 21:19:53.291 8027 8027 I dalvikvm: at android.app.ActivityThread.main(ActivityThread.java:5016)
10-27 21:19:53.291 8027 8027 I dalvikvm: at java.lang.reflect.Method.invokeNative(Native Method)
10-27 21:19:53.292 8027 8027 I dalvikvm: at java.lang.reflect.Method.invoke(Method.java:515)
10-27 21:19:53.293 8027 8027 I dalvikvm: at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
10-27 21:19:53.293 8027 8027 I dalvikvm: at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
10-27 21:19:53.293 8027 8027 I dalvikvm: at dalvik.system.NativeStart.main(Native Method)
正常情况下,view在被destroy后不应该再被绘制,通过跟孙念沟通,得知这种情况可能是view在destroy前没有remove导致的。
分析代码:
上面delete时的调用栈中有特别的两行:
10-27 21:19:52.990 8027 8027 D INJECT : at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:64)
10-27 21:19:52.990 8027 8027 D INJECT : at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:115)
这个是应用的代码,而这个问题只有在这个应用上出现过,所以很可能是应用的代码引起的,
所以查了下opengrok中的代码,发现有两处destroyHybridView()的实现:
@v8-kk-pisces-alpha/packages/apps/MiuiMusic/common/music_sdk/hybrid/src/com/xiaomi/music/hybrid/HybridFragment.java
private void destroyHybridView() {
for (HybridView view : mHybridViews) {
if (view != null) {
view.destroy();
}
}
mHybridViews.clear();
}
@v8-kk-pisces-alpha/packages/apps/MiuiSdk/library/src/java/miui/hybrid/HybridFragment.java
private void destroyHybridView() {
for (HybridView view : mHybridViews) {
if (view != null) {
=> if (view.getParent() != null) {
=> ((ViewGroup) view.getParent()).removeView(view);
=> }
view.destroy();
}
}
mHybridViews.clear();
}
跟应用的同事沟通后得知,音乐应用是用上面的代码,也就是没有removeView的代码。
将上面代码中添加removeView的逻辑后不再复现问题。
虽然问题得到解决,但还不清楚为什么没有removeView会导致野指针。
为了找到根源仔细阅读了相关代码,发现代码中Render中有detachFunctor的代码:
class GLES20Canvas extends HardwareCanvas {
...
public void detachFunctor(int functor) {
nDetachFunctor(mRenderer, functor);
}
用studio在这个代码中设置断点,得到如下调用栈:
java.lang.Thread.State: RUNNABLE
at android.view.GLES20Canvas.detachFunctor(GLES20Canvas.java:321)
at android.view.HardwareRenderer$GlRenderer.detachFunctor(HardwareRenderer.java:1791)
at android.view.ViewRootImpl.detachFunctor(ViewRootImpl.java:744)
at com.android.webview.chromium.DrawGLFunctor$DestroyRunnable.detachNativeFunctor(DrawGLFunctor.java:97)
at com.android.webview.chromium.DrawGLFunctor.detach(DrawGLFunctor.java:53)
at com.android.webview.chromium.WebViewChromium.onDetachedFromWindow(WebViewChromium.java:1718)
at android.webkit.WebView.onDetachedFromWindow(WebView.java:2108)
at android.view.View.dispatchDetachedFromWindow(View.java:12631)
at android.view.ViewGroup.dispatchDetachedFromWindow(ViewGroup.java:2587)
at android.view.ViewGroup.removeViewInternal(ViewGroup.java:3845)
at android.view.ViewGroup.removeViewInternal(ViewGroup.java:3818)
at android.view.ViewGroup.removeView(ViewGroup.java:3750)
at com.xiaomi.music.hybrid.HybridFragment.destroyHybridView(HybridFragment.java:66)
at com.xiaomi.music.hybrid.HybridFragment.onDestroyView(HybridFragment.java:119)
at com.miui.player.component.MusicBaseFragment.onDestroyView(MusicBaseFragment.java:216)
at android.app.Fragment.performDestroyView(Fragment.java:1898)
at android.app.FragmentManagerImpl.moveToState(FragmentManager.java:954)
at android.app.FragmentManagerImpl.removeFragment(FragmentManager.java:1167)
at android.app.BackStackRecord.popFromBackStack(BackStackRecord.java:715)
at android.app.FragmentManagerImpl.popBackStackState(FragmentManager.java:1544)
at android.app.FragmentManagerImpl$3.run(FragmentManager.java:502)
at android.app.FragmentManagerImpl.execPendingActions(FragmentManager.java:1449)
at android.app.FragmentManagerImpl$1.run(FragmentManager.java:443)
at android.os.Handler.handleCallback(Handler.java:733)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:136)
at android.app.ActivityThread.main(ActivityThread.java:5016)
at java.lang.reflect.Method.invokeNative(Method.java:-1)
at java.lang.reflect.Method.invoke(Method.java:515)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:792)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:608)
at dalvik.system.NativeStart.main(NativeStart.java:-1)
加了removeView后,会从Render中删除Functor,这样Render在绘制时,不再调用这个Functor。
这个问题只会在KK上有,L以后对Render做的很大改动,即使不做removeView,也不会存在野指针问题。