首页 > 代码库 > [Android]浮层视频效果,在另外一个Window使用SurfaceView无法正常显示的问题排查与解决

[Android]浮层视频效果,在另外一个Window使用SurfaceView无法正常显示的问题排查与解决

       最近在忙碌视频的事情,而视频的绘制需要使用到SurfaceView。为了完成浮层效果,我们很自然的想到使用多Window的方式。但是问题就来了,当你将你的SurfaceView放置在另外一个window中的时候,一切都变得不正常,为了验证这个东西,我写了一个小的demo:

       

技术分享

     代码非常简单,按下中间那个按钮,弹出一个Window,这个Window里面存放一个简单的SurfaceView,而这个Window的顶层View是一个FrameLayout。Window参数为:

   

private WindowManager.LayoutParams getWindowLayoutParams() {
        mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
        mWindowLayoutParams.setTitle("This is a test");
        mWindowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        mWindowLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
        mWindowLayoutParams.flags
        			|= WindowManager.LayoutParams.FLAG_FULLSCREEN
        			  | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        mWindowLayoutParams.token = MainActivity.this.getWindow().getDecorView().getWindowToken();
        return mWindowLayoutParams;
    }

好了,我们跑一下,就会发现界面没有任何变化,但是界面上的按钮都不可点击。这说明了什么呢?说明了你的Window已经被系统窗口管理服务所接收了,但是,界面显示出问题。我们给SurfaceView 增加一个SurfaceView回调,并在surfaceCreated处打印Log。你会发现,这个回调根本没有走。

@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		Log.v("surface", "surfaceCreated");
	}


这里我插入一句这个回调的作用,这个回调的作用在于告诉你,SurfaceFlinger给你分配的Surface已经可用了,但是这个回调不走,意味着当前状态下你的Surface处于不可用的状态,也就是SurfaceFlinger给你分配的Surface是不可用的。或许你到这里已经一头雾水了,不过不要紧,@非子墨兄刚开始也有点好奇,不过你静下来再想想,一个进程向SurfaceFlinger申请Surfacce并不是直接申请SurfaceFlinger服务申请的,而是向WindowManager服务申请的,也有可能是因为它引起的。我们在整理一下我们遇到的问题。我们增加了一个Window到窗口管理,但是我们看到了一个透明且没有surfacceCreate回调的SurfaceView。实际上这是两个问题:一个问题是透明,一个是没有回调。

   我们先来解决第一个透明的问题,我们在顶层FrameLayout设置了背景后,发现还是透明的,这是为什么呢?是因为SurfaceView这个对象申请显示区域的时候非常特殊,并不是跟你的UI线程一个缓冲上叠加绘制,我们可以简单理解为它在UI线程所绘制的缓冲上开了个口子,然后在自己的Buffer上面绘制。那么怎么解决透明的问题呢?其实非常非常的简单,只需要给SurfaceView设置一个背景,告诉绘制服务你的这个SurfaceView是非透明的就可以了。我给SurfaceView设置了一个蓝色的背景,跑一下果然看到了效果:



技术分享

好了,这样我们解决了第一个问题:透明问题。再次我们来看下第二个问题,SurfaceView不回调的问题。我们刚才对Surface对象无效的问题都纯属于猜测,为了验证我们的问题我们将SurfaceView中的Surface对象参数打印一下:

SurfaceView.java:
private void updateWindow(boolean force, boolean redrawNeeded) {
...
relayoutResult = mSession.relayout(
                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                            visible ? VISIBLE : GONE,
                            WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                            mWinFrame, mOverscanInsets, mContentInsets,
                            mVisibleInsets, mConfiguration, mNewSurface);

/*查看mSurface是否可用*/
booelan result = mSurface.isValid();
...
}
果然不出我们所料,result的值为false。我们来看一下mSurface.isValid()的实现:

public boolean isValid() {
        synchronized (mLock) {
            if (mNativeObject == 0) return false;
            return nativeIsValid(mNativeObject);
        }
    }

可见,mNativeObject对象句柄为null,也就是系统并没有分配给你绘制内存句柄。这个时候,不知道你会不会放弃,告诉自己这是系统的问题,实际上你离真相已经很近了,只要再坚持一会儿就行。我们来看一下WMS的log:

W/WindowManager( 1154): Attempted to add window with token that is a sub-window: android.os.BinderProxy@432d6290.  Aborting.
W/WindowManager( 1154): Failed looking up window
W/WindowManager( 1154): java.lang.IllegalArgumentException: Requested window android.os.BinderProxy@43296170 does not exist
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:7981)
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:7972)
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.relayoutWindow(WindowManagerService.java:2784)
W/WindowManager( 1154):         at com.android.server.wm.Session.relayout(Session.java:190)
W/WindowManager( 1154):         at android.view.IWindowSession$Stub.onTransact(IWindowSession.java:235)
W/WindowManager( 1154):         at com.android.server.wm.Session.onTransact(Session.java:125)
W/WindowManager( 1154):         at android.os.Binder.execTransact(Binder.java:404)
W/WindowManager( 1154):         at dalvik.system.NativeStart.run(Native Method)
W/WindowManager( 1154): Failed looking up window
W/WindowManager( 1154): java.lang.IllegalArgumentException: Requested window android.os.BinderProxy@43296170 does not exist
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:7981)
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.windowForClientLocked(WindowManagerService.java:7972)
W/WindowManager( 1154):         at com.android.server.wm.WindowManagerService.finishDrawingWindow(WindowManagerService.java:3105)
W/WindowManager( 1154):         at com.android.server.wm.Session.finishDrawing(Session.java:224)
W/WindowManager( 1154):         at android.view.IWindowSession$Stub.onTransact(IWindowSession.java:372)
W/WindowManager( 1154):         at com.android.server.wm.Session.onTransact(Session.java:125)
W/WindowManager( 1154):         at android.os.Binder.execTransact(Binder.java:404)
W/WindowManager( 1154):         at dalvik.system.NativeStart.run(Native Method)

我们粗浅的认为第二个堆栈引起的原因是因为第一个堆栈,而第一个堆栈引起的原因有可能是因为这句话:

W/WindowManager( 1154): Attempted to add window with token that is a sub-window: android.os.BinderProxy@432d6290.  Aborting.
这个其实不算是一个异常,可以当成系统提示,也就是说它将我们的Window当成一个简单的sub-window。我们看一下WMS这段代码的实现:

public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, InputChannel outInputChannel) {
...
if (attachedWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && attachedWindow.mAttrs.type <= LAST_SUB_WINDOW) {
                    Slog.w(TAG, "Attempted to add window with token that is a sub-window: "
                            + attrs.token + ".  Aborting.");
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
...
if (addToken) {
                mTokenMap.put(attrs.token, token);
            }
...
}

我们发现,当你以一个子Window的方式加入一个Window的时候,系统服务直接返回,这样就不能往mTokenMap中存放你的Token记录,而这个token不存在,导致了上面两个线程的异常堆栈。这样,我们离成功就只有一步之遥,我们已经定位我们的问题出在这句话:

mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_APPLICATION_SUB_PANEL;
而我们只需要改成FIRST_SUB_WINDOW和LAST_SUB_WINDOW之外的值就可以解决问题了。这里我选用了TYPE_TOAST

private WindowManager.LayoutParams getWindowLayoutParams() {
        mWindowLayoutParams.type = WindowManager.LayoutParams.TYPE_TOAST;
        mWindowLayoutParams.setTitle("This is a test");
        mWindowLayoutParams.width = WindowManager.LayoutParams.MATCH_PARENT;
        mWindowLayoutParams.height = WindowManager.LayoutParams.MATCH_PARENT;
        mWindowLayoutParams.flags
        			|= WindowManager.LayoutParams.FLAG_FULLSCREEN
        			  | WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN;
        mWindowLayoutParams.token = MainActivity.this.getWindow().getDecorView().getWindowToken();
        return mWindowLayoutParams;
    }

这样,SurfaceView的回调就正常了,此刻一切问题都迎刃而解。希望这篇文章能帮助到正在做这项功能的筒子们。

thx


         

[Android]浮层视频效果,在另外一个Window使用SurfaceView无法正常显示的问题排查与解决