SurfaceView出现ANR:Surface has already been released的解决办法

这篇具有很好参考价值的文章主要介绍了SurfaceView出现ANR:Surface has already been released的解决办法。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

项目中有这样一种场景会引起SurfaceView出现ANR,在主Activity中创建并使用SurfaceView,然后不停的进入子ActivityB ,返回主Activity再进入子ActivityB这样循环,就会出现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=0x75372268 self=0xaabd3ce0
| sysTid=3980 nice=-1 cgrp=top_visible sched=0/0 handle=0xf7762b50
| state=S schedstat=( 22407689743 17485653812 44070 ) utm=1882 stm=358 core=5 HZ=100
| stack=0xff307000-0xff309000 stackSize=8MB
| held mutexes=
at java.lang.Object.wait!(Native method)
- waiting on <0x0488bcf7> (a java.lang.Object)
at java.lang.Thread.parkFor$(Thread.java:1235)
- locked <0x0488bcf7> (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$NonfairSync.lock(ReentrantLock.java:181)
at java.util.concurrent.locks.ReentrantLock.lock(ReentrantLock.java:257)
at android.view.SurfaceView.updateWindow(SurfaceView.java:517)
at android.view.SurfaceView.onWindowVisibilityChanged(SurfaceView.java:246)
at android.view.View.dispatchWindowVisibilityChanged(View.java:9737)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1309)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1309)
at android.view.ViewGroup.dispatchWindowVisibilityChanged(ViewGroup.java:1309)
... repeated 10 times
at android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1415)
at android.view.ViewRootImpl.doTraversal(ViewRootImpl.java:1139)
at android.view.ViewRootImpl$TraversalRunnable.run(ViewRootImpl.java:6238)
at android.view.Choreographer$CallbackRecord.run(Choreographer.java:884)
at android.view.Choreographer.doCallbacks(Choreographer.java:696)
at android.view.Choreographer.doFrame(Choreographer.java:631)
at android.view.Choreographer$FrameDisplayEventReceiver.run(Choreographer.java:870)
at android.os.Handler.handleCallback(Handler.java:743)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:150)
at android.app.ActivityThread.main(ActivityThread.java:5621)
at java.lang.reflect.Method.invoke!(Native method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:794)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:684)

日志最直接应该就是SurfaceView导致的主线程等待问题
at java.lang.Object.wait!(Native method)
at android.view.SurfaceView.updateWindow(SurfaceView.java:517)

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,并且看到了一条异常:

System.err: java.lang.IllegalStateException: 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文章来源地址https://www.toymoban.com/news/detail-427584.html

@Override  
    public void surfaceDestroyed(SurfaceHolder holder) {  
        synchronized (this) {  
            mDrawFlag = false;  
        }  
    }

到了这里,关于SurfaceView出现ANR:Surface has already been released的解决办法的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Pycharm与HuggingFace连接出现连接出现TLS/SSL connection has been closed (EOF) 问题的解决

    最近跑TEXTure: Text-Guided Texturing of 3D Shapes论文代码时遇到TLS/SSL的问题 在配置huggingface的访问令牌时 在使用Hugging Face的功能或资源之前,应该通过运行 huggingface-cli login 命令登录到你的Hugging Face帐户,这将在默认位置存储你的访问令牌。这个访问令牌将用于验证你的身份,以便

    2024年02月04日
    浏览(35)
  • Mingw快捷安装教程 并完美解决出现的下载错误:The file has been downloaded incorrectly

    安装c语言编译器的时候,老是出现The file has been downloaded incorrectly,真的让人++ 直接去官网拿压缩包:https://sourceforge.net/projects/mingw-w64/files/ (往下拉找到那个x86_64-win32-seh的链接,点击后会自动下载) 我这里有准备安装的压缩包 链接:https://pan.baidu.com/s/1mIq_Vbn2w45L_B-AGJk0ww?pw

    2024年02月02日
    浏览(47)
  • 关于wb.write(response.getOutputStream()); 报错getOutputStream() has already been called for this respons

    如果你在使用 response.getOutputStream() 方法时出现 getOutputStream() has already been called for this response 错误,通常是由于多次尝试获取输出流所导致的。 在一个 HTTP 响应中,只能获取一次输出流,否则就会抛出上述错误。这是因为获取输出流时实际上已经开始了 HTTP 响应的正文部分,

    2024年02月05日
    浏览(33)
  • win11安装的Ubuntu20.04子系统出现System has not been booted with systemd as init system (PID 1)问题的解决流程

    目录 一、前言 二、具体解决方法 第一步:切换root用户至自己账号 第二步:重新安装xrdp 第三步:重新配置端口并启动xrdp 第四步:打开远程连接窗口 第五步:点击连接,开始进入Linux子系统​编辑 第六步:切换到的账户的输入密码,并点击认证  第七步:成功进入Ubuntu20

    2024年02月16日
    浏览(35)
  • elasticsearch查询出现Limit of total fields 1000 has been exceeded

    在项目中使用elasticsearch保存日志等相关数据,查询页面查询这些日志数据 提示:这里描述项目中遇到的问题: 今天在检查日志数据时,发现数据出不来,检查后端日志,发现一直在报Limit of total fields 1000 has been exceeded的问题 提示:这里填写问题的分析: 经过问题排查,发现

    2024年02月04日
    浏览(29)
  • [OpenAI]ChatGPT用key连接出现TLS/SSL connection has been closed (EOF) (_ssl.c:1131)

    此篇答案参考网上答案: 知乎: lchatgpt调用api接口出现连接不上的错误 不过他的问题是“由于连接方在一段时间后没有正确答复或连接的主机没有反应,连接尝试失败” ,而我的问题是\\\"Python requests.exception TLS/SSL connection has been closed (EOF) (_ssl.c:1131)\\\" 解决方法都一样,将环境里的

    2024年02月04日
    浏览(38)
  • Pycharm中anaconda创建激活虚拟环境出现错误:Your shell has not been properly configured to use ‘conda activate‘.

    之前用pycharm,安装的第三方库都是默认放在C:Users*******PythonPython36当中 在用anaconda创建虚拟环境后,第三方库都安装在了annaconda下的evns中的以虚拟环境命名的文件夹当中,当不同程序的第三方库要适应不同版本需求时,就不需要像前者先卸载在安装,而是直接可以创建一个

    2024年02月01日
    浏览(48)
  • 成功解决:RuntimeError: implement_array_function method already has a docstring

    写了一个很简单的有关opencv_python的代码,但是出现了这个错误。仔细看了代码,确定代码没有问题,逻辑也没有问题,但是!!!上天对我很厚爱,给了我一个bug!!! 仔细看了一下报错内容,发现它跟opencv,numpy有关。通过自己的思考,大概知道最后一句报错内容说的是

    2024年02月06日
    浏览(19)
  • 关于Surface系列重装系统的操作方法,出现的问题并给出了解决办法。

     我去年在微软官网淘到了一个surface go的平板电脑,由于我把Windows系统从Win10更新到了Win11,导致系统很卡顿(不是更新的Win11的原因,我猜测可能是平板电脑的自身版本有关,处理器性能太低了,带不动),所以我试着去还原系统,在还原的过程中,遇到了一些问题,现记

    2024年02月08日
    浏览(26)
  • 解决No spring.config.import property has been defined,学习笔记三

    在学习Nacos组件,加载多配置集时遇到问题,希望微服务在读取配置文件之前先去nacos中读取配置文件优先于本地配置。配置了 bootstrap.properties 后启动项目报错: 后续找解决方案发现是因为在springcloud 2020.0.2版本中把bootstrap的相关依赖从spring-cloud-starter-config中移除了,所以现

    2024年01月16日
    浏览(22)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包