Android 進入退出應用動畫卡頓分析

    最近在新項目上,出現了再進入退出應用時,動畫會卡頓的現象。因爲有過版本切換,爲了區分,分別叫做A、B版,A是舊版本,B是新版本。先說A版本的情況,A版本在應用退出的時候比較容易出現卡頓。有同事說有做alpha變化的時候,動畫都會卡頓。既然這個動畫也卡頓,而且也做了alpha變化,是不是因爲做了alpha變化呢?於是把alpha變化去掉,問題還是存在的,應該是跟做不做alpha動畫沒有關係,問題不在這。中間很多過程不記得了,大概折騰了一週以後發現,在SurfaceFlinger.cpp裏面把mDebugDisableHWC(0)改成了mDebugDisableHWC(1),相當於繪圖合成的時候,MDP是關閉的,都是用GPU進行合成,結果就出現了卡頓的情況。當然,也可以通過adb shell dumpsys SurfaceFlinger可以看到
h/w composer state:
  h/w composer present and disable
沒有使用h/w composer,即使用了GPU繪圖。
這個版本把overlay打開以後,應用進入與退出的動畫是好了,沒有什麼問題。
當我正在A版本解決這個問題的時候,系統確有切換到了新的B版本,結果B版本動畫還是卡的。我一看,overlay還是關閉的,理所當然的認爲把overlay打開以後,應該就不卡了。結果大出我意料之外,無論打開還是關閉MDP,動畫還是會卡頓的。這讓我有多糾結呀!本來覺得跟了一週,問題已經解決了,到新的版本B上,又出現了問題。我還沒緩過神了!~

B版本上有就分析吧!
B版本在進入退出應用的時候,從Systrace分析來看,在卡頓的時候是在hwc_prepare_primary耗時異常:
 Title hwc_prepare_primary
 Start 2003.446 ms
 Duration 29.842 ms
在繪製一幀的時候一個函數耗時接近30ms的情況,而且繪製的時候會發生聯想幾次這樣的情況,一幀16ms才能保證有60fps的幀率,一個函數就會出現耗時30ms,當然就會卡頓了,正常情況下這個函數是不耗時的。
通過追蹤hwc_prepare_primary函數,最後是在耗時都在HAL層的setOverlay裏面:
 qdoverlay: setOverlay : 31ms
但setOverlay函數操作簡單,只有一個ioctl操作:
inline bool setOverlay(int fd, mdp_overlay& ov) {
      if (ioctl(fd, MSMFB_OVERLAY_SET, &ov) < 0) {
         ALOGE("Failed to call ioctl MSMFB_OVERLAY_SET err=%s",
                 strerror(errno));
         return false;
      }
      return true;
 }
對應在kernel下面的ioctl語句也很簡單:
 
 ...
 case MSMFB_OVERLAY_SET:
  ret = copy_from_user(&req, argp, sizeof(req));
  if (!ret) {
   ret = mdss_mdp_overlay_set(mfd, &req);
   if (!IS_ERR_VALUE(ret))
    ret = copy_to_user(argp, &req, sizeof(req));
  }
  if (ret)
   pr_debug("OVERLAY_SET failed (%d)\n", ret);
  break;
 ...
mdss_mdp_overlay_set只是得到了一個鎖,而且打印出來kernel裏面執行的時間:

mdss_mdp_overlay_ioctl_handler end time_sec: 0, time_nsec :21771

只有0.021ms,user space(30ms)跟kernel space(0.021)的時間不是在一個數量級的。這種user space跟kernel space耗時完全不一致的原因,可能是因爲進程調度引起,當然真正的原因需要分析,只是猜測。

通過使用Ftrace分析,產生的原因是SurfaceFlinger在做這個動畫的時候,會有block的情況:

 sched_stat_blocked: comm=SurfaceFlinger pid=379 delay=26675625 [ns]

SurfaceFlinger被阻塞了(26ms)。但爲什麼被阻塞以及是否因爲阻塞導致setOverlay 耗時異常,卻不得而知。如果知道setOverlay耗時異常的原因,也就找到了產生問題的真正原因。

    這個問題分析到這的時候,我已經跟了三週了。而三週後,我確依然毫無頭緒,因爲分析到到這的時候,我沒有辦法繼續往下走了,芯片商有4個工程師跟過此問題了,但原因還沒有找到。此問題變的越來越棘手了,我卻越來越失望,越來越消沉了,我看不到希望,我開始慢慢浮躁,慢慢焦慮,自從跟這個問題以來,我睡不好,吃不爽,心裏總有疙瘩一樣,現在問題卻這樣不死不活,我能不失望,能不浮躁嗎?此時,我有多想念SystemTap、Dtrace呀!可惜Android沒有這種Dynamic Trace工具!

    我不想就這樣放棄這個問題,不想把希望僅僅寄託着芯片供應商上,而且從當前的效果來看,他們並沒有起到實質性的作用,只能靠自己了,硬着頭皮上吧!。我重頭開始分析問題,當問題原因無法找到時,只能再來一遍,如果還是沒有結果,那就繼續在來一遍,我也不知道自己試了多少遍,如果沒有找到問題的原因,說明遺漏了什麼。

     終於有一個週五的下午天,我似乎有點眉目了,但是因爲是週五下午,而且還出現了另外一個問題,把我打斷了。我只能在痛苦中渡過了雙休,其實我想去加班看一下的,但我想還是算了,自己覺得有不太對,週一過來驗證一下自己的猜想吧!

     問題是這樣的,我發現A版本上(也就是不會出現卡頓的版本),每次進入退出動畫時,SurfaceFlinger調用dequeueBuffe次數比B版本上(也就是有卡頓的版本上)少。但是週五的我初步的看了一下,dequeueBuffe的調用流程是這樣的:
doTraversal   ...ViewRootImpl.java
->performTraversals ...ViewRootImpl.java
-> performDraw ...ViewRootImpl.java
-> draw ....ViewRootImpl.java
->mHardwareRenderer.draw ...ViewRootImpl.java
->drawSoftware ...ViewRootImpl.java
->lockCanvas ..ViewRootImpl.java
->surface.lock ...android_view_surface.cpp
->Surface::lock ...Surface.cpp
->Surface::dequeueBuffer Surface.cpp
dequeueBuffe獲取一個可用的buffer。
->unlockCanvasAndPost ...ViewRootImpl.java
要求跟新顯示,把視圖繪製到屏幕上。
           
     我多希望在應用進出的動畫時不僅僅走的不是這個流程呀?如果僅僅走的是這個流程的話,我的跟蹤將又是豪無意義的的。爲什麼呢?如果僅走的是這個流程,說明繪製的時候用的是software的方式,怎麼可能是這樣呢?我希望應用進出的動畫裏面的dequeueBuffer是希望在別的地方調用的,那樣也行還有一點希望。
     
     註定我的週末是痛苦的,我之所以沒有抱太多的希望,是因爲我已經經歷過很多次這樣了,每次的猜測都被證明是錯誤的,雖然沒有證明一千種材料都不適合做燈絲那麼多!
     
     過完週末,週一回來後,我在這個流程的裏面添加log後發現dequeueBuffe次數比這個流程的次數多,說明還有地方使得系統調用了dequeueBuffe。
     
     當然,如果你知道WindowAnimator.java裏面用的是
        SurfaceControl.openTransaction();
        SurfaceControl.setAnimationTransaction();
...
SurfaceControl.closeTransaction();
我想,應該是可以知道沒有到View的一級去的,直接就到SurfaceFlinger裏面了。
      
     通過打出堆棧信息,驗證我的結論是正確的。SurfaceFlinger在handle去handleMessageRefresh裏面是會通過OpenGL ES去調用可以去dequeuebuffer,堆棧信息如下:
12-23 12:51:25.785 11736 11738 D : #00 pc 0001e7c6 /system/lib/libgui.so (android::BufferQueue::dequeueBuffer(int*, android::sp<android::Fence>*, unsigned int, unsigned int, unsigned int, unsigned int)+117)
12-23 12:51:25.785 11736 11738 D : #01 pc 000270fe /system/lib/libgui.so (android::Surface::dequeueBuffer(ANativeWindowBuffer**, int*)+89)
12-23 12:51:25.785 11736 11738 D : #02 pc 0002746c /system/lib/libgui.so (android::Surface::hook_dequeueBuffer_DEPRECATED(ANativeWindow*, ANativeWindowBuffer**)+47)
12-23 12:51:25.785 11736 11738 D : #03 pc 000037ba /system/vendor/lib/egl/eglsubAndroid.so
12-23 12:51:25.785 11736 11738 D : #04 pc 0000a66c /system/vendor/lib/egl/libEGL_adreno.so (egliSyncBackBuffer+248)
12-23 12:51:25.785 11736 11738 D : #05 pc 00076b90 /system/vendor/lib/egl/libGLESv2_adreno.so (rb_surface_sync_for_resolve+27) 
12-23 12:51:25.785 11736 11738 D : #06 pc 00076bf8 /system/vendor/lib/egl/libGLESv2_adreno.so (rb_perform_rendering_target_sync+35)
12-23 12:51:25.785 11736 11738 D : #07 pc 00076c44 /system/vendor/lib/egl/libGLESv2_adreno.so (rb_setup_resolve+27)
12-23 12:51:25.785 11736 11738 D : #08 pc 0007702c /system/vendor/lib/egl/libGLESv2_adreno.so (rb_perform_resolve+187)
12-23 12:51:25.785 11736 11738 D : #09 pc 000773f0 /system/vendor/lib/egl/libGLESv2_adreno.so (rb_resolve+363)
12-23 12:51:25.785 11736 11738 D : #10 pc 0007ba0a /system/vendor/lib/egl/libGLESv2_adreno.so (rb_surface_swap+213)
12-23 12:51:25.785 11736 11738 D : #11 pc 0005fee6 /system/vendor/lib/egl/libGLESv2_adreno.so (gl2_surface_swap+69)
12-23 12:51:25.785 11736 11738 D : #12 pc 00051ad2 /system/vendor/lib/egl/libGLESv2_adreno.so (oglSwapBuffer+85)
12-23 12:51:25.785 11736 11738 D : #13 pc 0002448c /system/vendor/lib/egl/libGLESv1_CM_adreno.so (oglSwapBuffer+7)
12-23 12:51:25.785 11736 11738 D : #14 pc 00012bf4 /system/vendor/lib/egl/libEGL_adreno.so (qeglDrvAPI_eglSwapBuffers+1864)
12-23 12:51:25.785 11736 11738 D : #15 pc 00006bd0 /system/vendor/lib/egl/libEGL_adreno.so (eglSwapBuffers+16)
12-23 12:51:25.785 11736 11738 D : #16 pc 0000fb1a /system/lib/libEGL.so (eglSwapBuffers+277)
12-23 12:51:25.785 11736 11738 D : #17 pc 0001ead4 /system/lib/libsurfaceflinger.so (android::DisplayDevice::swapBuffers(android::HWComposer&) const+131)
12-23 12:51:25.785 11736 11738 D : #18 pc 00024b2e /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::doDisplayComposition(android::sp<android::DisplayDevice const> const&, android::Region const&)+121) 
12-23 12:51:25.785 11736 11738 D : #19 pc 000270ec /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::doComposition()+95) 
12-23 12:51:25.785 11736 11738 D : #20 pc 00027348 /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::handleMessageRefresh()+47)
12-23 12:51:25.785 11736 11738 D : #21 pc 00027e0c /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::onMessageReceived(int)+63)
12-23 12:51:25.785 11736 11738 D : #22 pc 000150ea /system/lib/libutils.so (android::Looper::pollInner(int)+381)
12-23 12:51:25.785 11736 11738 D : #23 pc 000151ec /system/lib/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+91)
12-23 12:51:25.785 11736 11738 D : #24 pc 00023444 /system/lib/libsurfaceflinger.so (android::MessageQueue::waitMessage()+35)
12-23 12:51:25.785 11736 11738 D : #25 pc 000241d0 /system/lib/libsurfaceflinger.so (android::SurfaceFlinger::threadLoop()+1)
12-23 12:51:25.785 11736 11738 D : #26 pc 00011a8a /system/lib/libutils.so (android::Thread::_threadLoop(void*)+213)
12-23 12:51:25.785 11736 11738 D : #27 pc 0001157e /system/lib/libutils.so
12-23 12:51:25.785 11736 11738 D : #28 pc 0000cb60 /system/lib/libc.so (__thread_entry+72)
12-23 12:51:25.785 11736 11738 D : #29 pc 0000ccdc /system/lib/libc.so (pthread_create+208)

    這就是說A版本跟B版本在繪圖的流程上是有不同的,通過這個堆棧,不斷的debug後發現發現是HWComposer::prepare進行layer的合成是不一樣的。在A版本中HWComposer::prepare中判斷的layer是HWC_OVERLAY,即合成的時候使用的是overlay的方式,在B版本上,HWComposer::prepare中判斷的layer是HWC_FRAMEBUFFER,即合成的時候,使用的是GPU的方式。

    通過對比A、B的版本後發現,應用進出動畫時:
 1、A版本,SurfaceFlinger Layer只用了overlay合成
 2、B版本,SurfaceFlinger Layer合成是overlay與GPU一起完成的。
這也能解釋出A版本,把overlay關閉只用GUP合成也是會卡頓,以及B版本把SystemUI.apk刪除後(刪除SystemUI.apk後這個版本應用進出時只使用了overlay合成),動畫不會卡頓,頓時問題豁然開朗了,心裏的一顆大石頭落下來了。

    如果僅僅從performance角度來說,這個問題可以到此爲止了,因爲已經定位出來了問題的模塊了,由模塊跟蹤就可以了,但悲催的是,我還得繼續跟蹤着,唉!。
   
    而此時,芯片供應商也換了一個做display的高手來處理問題了,通過他們的調試後發現,卡頓的時候SurfaceFlinger會同時進行六、七層的合成,而他們的平臺在四層以下性能是最好的,此時GPU性能完全不夠了,需要對這塊進行優化。
    
    我很早就想把這個問題總結以下了,苦於一直忙的不可開交,吾日三省吾身的機會都沒有,趁着今天元旦放假休息,記錄下來,以此祭奠自己那不悔的青春,前前後後在此問題上花了至少一個半月的時間,真狗血。騷年,還是不要去做performance吧!看着華麗,僅僅是看着!這是一個坑呀!玩笑而已,不要當真!其實做performance還是有一定的樂趣的,雖然苦一定、累一定!個人感覺!

2014//01//06更新:
當我看到有卡頓的情況時,GPU的頻率是這樣:
330000000
330000000
330000000
330000000
330000000
330000000
330000000

而修改了一個不卡頓的版本時,GPU的頻率是這樣:

330000000

578000000

330000000

330000000

578000000

578000000

578000000

578000000

578000000

578000000

462400000

這是在不停測試的時候發現的問題,我希望這輩子都不要再遇到這種什麼CPU調頻或者GPU調頻機制的問題。看來最有希望找到問題原因的人,還是自己,相信自己吧!不要老把希望寄託在別人身上!自己是唯一能靠的住的人!別人頂多能協助一下你。

當我週五的時候回去7點就睡了,第二天早上10點纔起來的時候,就知道有多折騰,當芯片供應商有display team、performance team、graphic team的四五個人在跟這個問題,我確一個人把他扛了兩個月的時候,就知道我有多悲催。此處省略一萬字的感慨!




發佈了54 篇原創文章 · 獲贊 43 · 訪問量 37萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章