Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> [Android]浮層視頻效果,在另外一個Window使用SurfaceView無法正常顯示的問題排查與解決

[Android]浮層視頻效果,在另外一個Window使用SurfaceView無法正常顯示的問題排查與解決

編輯:關於Android編程

最近在忙碌視頻的事情,而視頻的繪制需要使用到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的回調就正常了,此刻一切問題都迎刃而解。希望這篇文章能幫助到正在做這項功能的筒子們。

 

 

 

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved