1. 程式人生 > >坑:SurfaceView出現ANR:Surface has already been released的解決辦法

坑:SurfaceView出現ANR:Surface has already been released的解決辦法

想說一句,CSDN部落格的編輯器真的很爛,而且有一個重大的漏洞(可以盜CSND的號,親自測試成功,細節不說了,哈哈),後來我告訴他們了,他們修復了並且給我一件衣服和揹包,然後升級後這個漏洞又出現了。
衣服髒了,揹包破了,也該換新的了,於是我用QQ聯絡客服,暫時迴應。。。。。

開始說正題

直播間的飄贊使用SurfaceView,但是當直播間Activity到後臺時(SurfaceView銷燬了),極少情況下會出現ANR。我通過檢視SurfaceView原始碼發現了一個坑,其實很多人使用的姿勢不對,他們沒有出現ANR只是幸運而已。



1、如何找ANR日誌
出現ANR之後我立刻想到要拿到ANR日誌,可以通過如下命令獲取ANR日誌:
adb pull data/anr/traces.txt


這樣就把ANR日誌下載到電腦了。
2、分析ANR日誌
開啟ANR日誌,可以看到main執行緒的堆疊資訊


"main" prio=5 tid=1 Waiting
  | group="main" sCount=1 dsCount=0 obj=0x76c353e8 self=0xf4a64500
  | sysTid=7047 nice=-11 cgrp=default sched=0/0 handle=0xf7276b4c
  | state=S schedstat=( 0 0 0 ) utm=2795 stm=388 core=7 HZ=100
  | stack=0xff030000-0xff032000 stackSize=8MB
  | held mutexes=
  at java.lang.Object.wait!(Native method)
  - waiting on <0x03fd06cb> (a java.lang.Object)
  at java.lang.Thread.parkFor$(Thread.java:1220)
  - locked <0x03fd06cb> (a java.lang.Object)
  at sun.misc.Unsafe.park(Unsafe.java:299)
  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:158)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:810)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireQueued(AbstractQueuedSynchronizer.java:843)
  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquire(AbstractQueuedSynchronizer.java:1172)
  at java.util.concurrent.locks.ReentrantLock$FairSync.lock(ReentrantLock.java:196)
  at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:257)
  at android.view.SurfaceView.updateWindow(SurfaceView.java:638)
  at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:316)
  at android.view.View.dispatchWindowVisibilityChanged(View.java:10434)
  at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328)
  at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328)
  at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1328)
  ... repeated 1 times
  at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1750)
  at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1437)
  at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:7397)
  at android.view.Choreographer$CallbackRecord.run(Choreographer.java:920)
  at android.view.Choreographer.doCallbacks(Choreographer.java:695)
  at android.view.Choreographer.doFrame(Choreographer.java:631)
  at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:906)
  at android.os.Handler.handleCallback(Handler.java:739)
  at android.os.Handler.dispatchMessage(Handler.java:95)
  at android.os.Looper.loop(Looper.java:158)
  at android.app.ActivityThread.main(ActivityThread.java:7237)
  at java.lang.reflect.Method.invoke!(Native method)
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1230)
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1120)




1、分析SurfaceView原始碼:
根據log可以看出是SurfacecView導致的ANR,直播間的飄贊動畫就是用SurfaceView實現的,在執行SurfaceView.updateWindow方法裡面的ReentrantLock.lock()時一直阻塞在這裡,導致了ANR。


開啟SurfaceView原始碼,看到updateWindow方法裡面果然有mSurfaceLock.lock()方法。
mSurfaceLock是這樣被定義的:final ReentrantLock mSurfaceLock = new ReentrantLock();


肯定是有一個地方沒有呼叫unlock釋放鎖,導致呼叫lock時一直無法獲得鎖,想到Canvas有lock,並且需要開發者及時unlock。

操作畫布的程式碼並沒有問題,在finally裡unlock也是正確的,如下:
    Canvas canvas = mHolder.lockCanvas();
    if(canvas != null){
        try {
            for (Heart heart : mHeartArray) {
                canvas.drawBitmap(heart.bitmap, null, heart.dst, mPaint);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            mHolder.unlockCanvasAndPost(canvas);
        }
    }



自己反覆讓Activity前後臺切換,因為SurfaceView不可見會被銷燬,可見後會被建立。這時終於復現了ANR,並且看到了一條異常:
Surface has already been released.


於是開始具體分析原始碼,先看unlockCanvasAndPost實現,因為可能unlock
    // SurfaceView.SurfaceHolder的實現
    @Override
    public void unlockCanvasAndPost(Canvas canvas) {
        mSurface.unlockCanvasAndPost(canvas);
        mSurfaceLock.unlock();
    }


    // Surface類
    public void unlockCanvasAndPost(Canvas canvas) {
        synchronized (mLock) {
            checkNotReleasedLocked();
            //...
        }
    }
    // 找到了那個拋異常位置,如果在這裡丟擲異常,那麼在就不會執行SurfaceLock.unlock了,最後導致再次lock的時候出現ANR。
    // 當mNativeObject=0時,會拋這個異常,接著看mNativeObject什麼情況下回置為0.
    private void checkNotReleasedLocked() {
        if (mNativeObject == 0) {
            throw new IllegalStateException("Surface has already been released.");
        }
    }


   // 原來這個方法會把mNativeObject置為0,接分析哪裡呼叫這個方法
    private void setNativeObjectLocked(long ptr) {
        //...
        mNativeObject = ptr;
        //...
    }


   // 搜尋了一下,原來這裡呼叫了setNativeObjectLocked(0)
    @Deprecated
    public void transferFrom(Surface other) {


        if (other != this) {
            //...
            other.setNativeObjectLocked(0);
            //...
        }
    }




// SurfaceView裡呼叫transferFrom
    /** @hide */
    protected void updateWindow(boolean force, boolean redrawNeeded) {
        mSurfaceLock.lock();
        try {


        } finally {
            mSurfaceLock.unlock();
        }
        try {
            ....
            if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                mSurfaceCreated = false;
                if (mSurface.isValid()) {


                    callbacks = getSurfaceCallbacks();
                    for (SurfaceHolder.Callback c : callbacks) {
                        c.surfaceDestroyed(mSurfaceHolder);
                    }
                }
            }


            mSurface.transferFrom(mNewSurface);
            ....
        } finally {
        }
    }


}


SurfaceView生命週期如下:
surfaceCreated:當從不可見狀態變為可見狀態時
surfaceChanged:當大小改變時
surfaceDestroyed:當從可見狀態變為不可見狀態時
根據BUG復現步驟,點選聊天按鈕,跳轉到聊天頁面,此時直播間處於不可見狀態,因此SurfaeView會被銷燬,所以會呼叫surfaceDestroyed。


// 從上面程式碼可以看到,先回調surfaceDestroyed,然後執行mSurface.transferFrom(mNewSurface),這時會將mNativeObject置為0,
// 如果恰好此時呼叫unlockCanvasAndPost,會丟擲異常,並且不能呼叫unlock,導致下次建立SurfaceView時發生ANR。

產生ANR的原因:簡而言之,處於在lockCanvas和unlockCanvasAndPost之間時,SurfaceView銷燬了,導致unlock失敗,出現了死鎖。


總結本次ANR過程:
第一步:執行了mHolder.lockCanvas(),lock成功獲得鎖
第二步:此時恰巧遇到SurfaceView銷燬,surfaceDestroyed執行,並且將mNativeObject置為0
第三步:呼叫unlockCanvasAndPost,但是由於mNativeObject為0,所以丟擲異常,並沒有成功unlock
第四步:SurfaceView重新建立,嘗試lock,因為上次的鎖沒有釋放,所以進入了無限等待。


解決方法:分為2步
1、在操作畫布過程增加同步鎖,讓整個操作畫布過程作為一個整體

        synchronized (this) {
            if (mDrawFlag) {
                Canvas canvas = mHolder.lockCanvas();
                if (canvas != null) {
                    try {
        		 for (Heart heart : mHeartArray) {
               		    canvas.drawBitmap(heart.bitmap, null, heart.dst, mPaint);
         		 }
                          
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    } finally {
                        try {
                            mHolder.unlockCanvasAndPost(canvas);
                        } catch (Exception e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

2、在SurfaceView銷燬回撥增加同步鎖,可以保證mNativeObject不會在lockCanvas和unlockCanvasAndPost之間置為0
    @Override
    public void surfaceDestroyed(SurfaceHolder holder) {
        synchronized (this) {
            mDrawFlag = false;
        }
    }


解決這個ANR,簡而言之,阻止SurfaceView在lockCanvas和unlockCanvasAndPost之間銷燬,在上面兩處加上了同步塊。

相關推薦

SurfaceView出現ANRSurface has already been released解決辦法

想說一句,CSDN部落格的編輯器真的很爛,而且有一個重大的漏洞(可以盜CSND的號,親自測試成功,細節不說了,哈哈),後來我告訴他們了,他們修復了並且給我一件衣服和揹包,然後升級後這個漏洞又出現了。衣服髒了,揹包破了,也該換新的了,於是我用QQ聯絡客服,暫時迴應。。。。。

java.lang.IllegalStateException: getOutputStream() has already been called 解決辦法

今天上班做從資料庫查詢圖片到jsp頁面顯示,結果報java.lang.IllegalStateException: getOutputStream() has already been called 錯誤,折騰了將近半天的時間才弄出來。 在網上查閱了很多資料基本上都是一家之

java.lang.IllegalStateException: getOutputStream() has already been called 解決方法之一

       為什麼說是解決方法之一呢。因為遇到這個問題後看到了很多此問題的解決方案。很多文章寫的原因是檔案下載是報的這個錯誤,而我遇到這個問題的時候根本就沒有寫檔案下載的功能。(我用的是SpringBoot)       其實說來這個問題的出現也是自己馬虎。報錯如下  

樹莓派GPIO介面命名規則 A different mode has already been set解決方案

在編寫python呼叫GPIO介面的時候,發現很多python設定的 GPIO.setmode(GPIO.BCM) GPIO.setmode(GPIO.BOARD) 太麻煩了,之前沒有把樹莓派的命名規

Hive執行過程中出現Caused by java.lang.ClassNotFoundException: org.cloudera.htrace.Trace的錯誤解決辦法(圖文詳解)

pre wid logs In 實用 過程 ase edit 微信     不多說,直接上幹貨! 問題詳情     如下   這個錯誤的意思是缺少 htrace-core-2.04.jar。  解決辦法:   

遠程連接阿裏雲服務器出現"遠程桌面,身份驗證錯誤要求的函數不受支持"解決辦法

cin 還需要 阿裏 tools pac arch 控制面板 star -c ---恢復內容開始--- 阿裏雲服務器買好了,按照教程跟著來的,然後在遠程連接的時候出現了 的這樣的東東,按照上面的提示,"是由於Cred SSP 加密 oracle修正",不少

異常getReader() has already been called for this request

一個流不能讀兩次異常,這種異常一般出現在框架或者攔截器中讀取了request中的流的資料,我們在業務程式碼中再次讀取(如@requestBody),由於流中的資料已經沒了,所以第二次讀取的時候就會丟擲異常。 解決方案:定義一個過濾器將流中的資料讀取到一個數組中,並重寫getInput

檔案下載java.lang.IllegalStateException: getOutputStream() has already been called for this response

1.報錯資訊 一月 24, 2018 10:23:47 下午 org.apache.catalina.core.ApplicationDispatcher invoke 嚴重: Servlet.service() for servlet jsp threw

selenium執行時出現錯誤資訊Message: 'geckodriver' executable needs to be in PATH 的解決辦法

Windows 環境,Selenium 基於Python,執行時出錯: 下載解壓後將getckodriver.exe複製到Firefox的安裝目錄下,如(C:\Program Files\Mozilla Firefox),並在環境變數Path中新增路徑:C:\Pro

轉載oracle執行update語句時卡住問題分析及解決辦法

oracle執行update語句時卡住問題分析及解決辦法  這篇文章主要介紹了oracle執行update語句時卡住問題分析及解決辦法,涉及記錄鎖等相關知識,具有一定參考價值,需要的朋友可以瞭解。 問題 開發的時候debug到一條update的sql語句時程式就不動了,然後我就

Pythonpip 安裝第三方庫,速度很慢的解決辦法

場景 想安裝 Django 庫 在 cmd 敲入命令 pip install Django 但是發現下載安裝檔案非常慢 原因:實質訪問的下載網站是 https://pypi.Python.org/simple/ 這是一個國外網站,速度比較慢   解決辦法 使用國

iOS開發技巧之相機獲取到的圖片自動旋轉90度解決辦法

本文轉載自這裡:http://blog.csdn.net/hitwhylz/article/details/39518463 今天寫demo的時候發現, 如果把通過相機獲取到的圖片,直接進行操作, 比如裁剪, 縮放, 則會把原圖片向又旋轉90度。 剛開始覺得莫名其妙, 不知所措。 後來百度了

【轉】 VMWare虛擬機器提示鎖定檔案失敗,打不開磁碟的解決辦法

如果執行虛擬機器時,物理機突然崩潰,則會導致這種問題。 這是因為虛擬機器在執行的時候,會鎖定檔案,防止被修改,而如果突然系統崩潰了,虛擬機器就來不急把已經鎖定的檔案解鎖,所以你在啟動的時候,就會提示無法鎖定檔案,如下圖:   解決方法如下:   開啟你存放虛擬機器系統硬碟的所在資料夾,注意

行動硬碟 使用驅動器X中的光碟之前需要將其格式化 的 解決辦法

                個人習慣,喜歡直接插入,直接拔下U盤或行動硬碟,就是這麼任性!!! 今晚,再次任性插上的時候系統提示:使用驅動器X:中的光碟之前需要將其格式化!!! 當時,整個人就呆了,啊,我的資源,我的資源啊,幾百G的資料、程式碼和中間結果啊!啊!啊! 腫麼辦,網上的答案更是高階

Eclipse中啟動Tomcat時丟擲異常java.lang.ClassNotFoundException: org.apache.juli.logging.LogFactory的解決辦法

錯誤描述,如圖:  原因分析: eclipse-Version: 3.4.2沒有tomcat7.0的選項,所以報瞭如上的錯誤。 網上查找了下原因,才發現在呼叫的時候需要加入juli這個包。 此包位於tomcat根目錄bin目錄下。我的路徑是:D:\stru

C++開發python擴充套件模組ImportError: dynamic module does not define init function (initRabbit)解決辦法

用C++為Python編寫擴充套件模組(動態連結庫),並在Python中呼叫C++開發的擴充套件功能函式過程,遇到如下錯誤的童鞋,系不繫很苦惱啊:  ImportError: dynamic module does notdefine init function (ini

android studio 建立動畫時報錯Error: style attribute '@android:attr/windowExitAnimation' not found的解決辦法

在編寫安卓動畫時,遇到了Error: style attribute '@android:attr/windowExitAnimation' not found的報錯style中的程式碼如下<!-- 進出場動畫都用到的anim style--> <s

繼續摘抄關於/etc/resolv.conf經常自動改回來的解決辦法

sudo gedit /etc/dhcp3/dhclient.conf 在 /etc/dhcp3/dhclient.conf 中找到 #prepend domain-name-servers,去掉註釋並增加如下設定: 程式碼: prepend domain-name-se

Oracle sql 優化增加order by 慢 去掉之後就變快 解決辦法

一 問題背景: 1.oracle的版本的為11g 2.資料是由生產庫匯入部分資料至開發庫 3.表為範圍分割槽表 4.order by 的欄位為分割槽表的分割槽欄位 5.資料記錄大概有一千萬; 5.使用的分析工具為toad10 二 問題描述 1.加上order by的執行計劃