坑: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出現ANR:Surface 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修正",不少
控制檯報錯: java.lang.IllegalStateException: getOutputStream() has already been called for this response.
1、錯誤描述 Caused by: java .lang .IllegalStateException:
異常: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語句時程式就不動了,然後我就
Python:pip 安裝第三方庫,速度很慢的解決辦法
場景 想安裝 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的執行計劃