【解疑答惑】—— SurfaceView播放视频 来回切换出现ANR

项目中有用到SurfaceView 作视频播放,可是一直有一个问题,测试哥们今天又从新提了,貌似上任开发者并无解决,看看吧。。。
这里写图片描述java

本身试了试,确实是有这样的bug,可是是报ANR,奇了怪了,全屏放大不至于会报ANR啊,看了看代码,发现全屏播放实际是建立了一个包含surfaceView新的activity,也就是又建立一个SurfaceView,搞不懂为何这么设计,orz….android

首先,看一下日志 找半天才找到web

Surface has already been released.sql

接着 抓一下traces日志看看(adb pull data/anr/traces.txt)canvas

"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)
app

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

直接粗暴看代码,先看SurfaceView.updateWindow源码部分:
最关键的一部分,mSurfaceLock获取lock对象svg

mSurfaceLock.lock();

相应的,有获取就会有释放lock,绘制视频时会有释放lock的操做(m_surfaceHolder.unlockCanvasAndPost(m_canvas);)oop

try {
    if(m_surfaceHolder.getSurface().isValid()){
        m_canvas = m_surfaceHolder.lockCanvas(null);
        if(m_canvas!=null){
            m_canvas.drawColor(Color.WHITE);
            m_bitmap.copyPixelsFromBuffer(m_buffer);
            m_buffer.clear();
            Log.i("------", "---------m_winWidth---------" + m_winWidth + "---------------m_winHeight-----------------" + m_winHeight);
            m_viewRect.set(0, 0, m_winWidth, m_winHeight);
            m_canvas.drawBitmap(m_bitmap, null, m_viewRect, m_paint);
        }
    }
} catch (Exception e) {
    return;
} finally {
    if(m_surfaceHolder.getSurface().isValid()){
        m_surfaceHolder.unlockCanvasAndPost(m_canvas);
    }
    //   Log.i("------","---------unlockCanvasAndPost---------");
}

缘由:SurfaceView在建立时会获取lock,可是没有unlock,SurfaceView被销毁 再次建立时会出现等待lock的状况,就会出现ANR。。。。测试

总结本次ANR过程:
第一步:执行了mHolder.lockCanvas(),lock成功得到锁
第二步:此时恰巧遇到SurfaceView销毁,surfaceDestroyed执行,而且将mNativeObject置为0
第三步:调用unlockCanvasAndPost,可是因为mNativeObject为0,因此抛出异常,并无成功unlock
第四步:SurfaceView从新建立,尝试lock,由于上次的锁没有释放,因此进入了无限等待。

解决办法:
在绘制视频和销毁的过程当中都加上同步锁

synchronized (this.holder) {
    Canvas c = null;
    try {
        c = holder.lockCanvas(null);
        if (c != null) {
            c.drawColor(Color.BLACK);
            c.drawARGB(0, 0, 0, 0);
            if (!msg.isEmpty()) {
                Paint paint = new Paint();
                paint.setColor(Color.WHITE);
                paint.setTextSize((float) 25.0);
                paint.setTextAlign(Paint.Align.CENTER);
                c.drawText(msg, width / 2, height / 2, paint);
            }
        }
    } catch (Exception e) {
        Log.i("------", e.getMessage());
    } finally {
        holder.unlockCanvasAndPost(c);
    }
}

surfaceDestroyed方法中(SurfaceView不可见时):

@Override
  public void surfaceDestroyed(SurfaceHolder holder) {
      synchronized (this.holder) {
          Logger.t(TAG).d("surfaceDestroyed--------" + Config.isShow);
      }
  }