SurfaceView介绍
SurfaceView是Andoird GUI系统中一种特殊的控件,它可以在非UI线程进行绘图。UI线程的绘制在view绘制流程一篇中介绍过了,本篇将对SurfaceView的绘制进行介绍。在此之前,我们看看SurfaceView的一般用法。
public class SurfaceViewDemo extends SurfaceView
implements SurfaceHolder.Callback, Runnable {
private SurfaceHolder mHolder;
private Canvas mCanvas;
private boolean bDrawing;
public SurfaceViewDemo(Context context) {
super(context);
initView();
}
public SurfaceViewDemo(Context context, AttributeSet attrs) {
super(context, attrs);
initView();
}
public SurfaceViewDemo(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
initView();
}
private void initView() {
mHolder = getHolder();
mHolder.addCallback(this);
setFocusable(true);
setFocusableInTouchMode(true);
this.setKeepScreenOn(true);
}
@Override
public void surfaceCreated(SurfaceHolder holder) {
mIsDrawing = true;
new Thread(this).start();
}
@Override
public void surfaceChanged(SurfaceHolder holder,
int format, int width, int height) {
}
@Override
public void surfaceDestroyed(SurfaceHolder holder) {
mIsDrawing = false;
}
@Override
public void run() {
while (bDrawing) {
draw();
}
}
private void draw() {
try {
mCanvas = mHolder.lockCanvas();
// draw something
} catch (Exception e) {
} finally {
if (mCanvas != null)
mHolder.unlockCanvasAndPost(mCanvas);
}
}
}
SurfaceView的生命周期回调surfaceCreated->SurfaceChanged->surfaceDestroyed。这里在surfaceCreated中进行绘制,这个绘制可以在单独的线程中进行,并不依赖于ui线程。至于为何这样,后面我们再解释。
首先看看surfaceview的源码,先看其成员都有哪些
public class SurfaceView extends View {
static private final String TAG = "SurfaceView";
static private final boolean DEBUG = false;
final ArrayList<SurfaceHolder.Callback> mCallbacks
= new ArrayList<SurfaceHolder.Callback>();
final int[] mLocation = new int[2];
final ReentrantLock mSurfaceLock = new ReentrantLock();
final Surface mSurface = new Surface(); // Current surface in use surfaceView使用的绘图表面
final Surface mNewSurface = new Surface(); // New surface we are switching to
boolean mDrawingStopped = true;
final WindowManager.LayoutParams mLayout
= new WindowManager.LayoutParams();
IWindowSession mSession;
MyWindow mWindow;
final Rect mVisibleInsets = new Rect();
final Rect mWinFrame = new Rect();
final Rect mOverscanInsets = new Rect();
final Rect mContentInsets = new Rect();
final Configuration mConfiguration = new Configuration();
static final int KEEP_SCREEN_ON_MSG = 1;
static final int GET_NEW_SURFACE_MSG = 2;
static final int UPDATE_WINDOW_MSG = 3;
int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;
boolean mIsCreating = false;
……
}
在SurfaceView中有两个Surface绘图表面,所有绘制操作都是在这个绘图表面上进行的,这里有两个Surface是为了进行绘图时的前后台切换,这样当后台进行绘制时,前台可以显示之前绘制好的Surface表面。IWindowSession是用来和WMS进行会话的session,因为SurfaceView它本质上也是一个Window,它还有一个MyWindow成员,它类似于ViewRootImpl中的W对象,是用来和WMS进行通信交互的。因此它也是一个Binder对象,在WMS一端,它唯一标志了一个窗口对象Window。
SurfaceView的绘制流程
SurfaceView的绘制依然属于view树绘制的一部分,它依赖于宿主窗口,在View树的绘制流程中,我们看看SurfaceView是如何进行自身的绘制的,在performTraversals中绘制view树之前,会通知view树Attach到宿主窗口上,这是通过host.dispatchAttachedToWindow(attachInfo, 0)来实现的,这里的host即DecorView,它是个ViewGroup。在ViewGroup中又会调用子view的dispatchAttachedToWindow方法
//ViewGroup
@Override
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mGroupFlags |= FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
super.dispatchAttachedToWindow(info, visibility);
mGroupFlags &= ~FLAG_PREVENT_DISPATCH_ATTACHED_TO_WINDOW;
final int count = mChildrenCount;//子视图的数目
final View[] children = mChildren;//子视图数组
for (int i = 0; i < count; i++) {
final View child = children[i];
child.dispatchAttachedToWindow(info,
visibility | (child.mViewFlags & VISIBILITY_MASK));//通知每个子视图他们被添加到宿主窗口上去了
}
}
//view
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
mAttachInfo = info;//保存所附加窗口的信息
if (mOverlay != null) {
mOverlay.getOverlayView().dispatchAttachedToWindow(info, visibility);
}
mWindowAttachCount++;
// We will need to evaluate the drawable state at least once.
mPrivateFlags |= PFLAG_DRAWABLE_STATE_DIRTY;
if (mFloatingTreeObserver != null) {
info.mTreeObserver.merge(mFloatingTreeObserver);
mFloatingTreeObserver = null;
}
if ((mPrivateFlags&PFLAG_SCROLL_CONTAINER) != 0) {
mAttachInfo.mScrollContainers.add(this);
mPrivateFlags |= PFLAG_SCROLL_CONTAINER_ADDED;
}
performCollectViewAttributes(mAttachInfo, visibility);
onAttachedToWindow();//onAttachedToWindow 让子类处理它被附加到宿主窗口时的事件
ListenerInfo li = mListenerInfo;
final CopyOnWriteArrayList<OnAttachStateChangeListener> listeners =
li != null ? li.mOnAttachStateChangeListeners : null;
if (listeners != null && listeners.size() > 0) {
for (OnAttachStateChangeListener listener : listeners) {
listener.onViewAttachedToWindow(this);
}
}
int vis = info.mWindowVisibility;
if (vis != GONE) {
onWindowVisibilityChanged(vis);//通知宿主窗口的可见性发生了变化
}
if ((mPrivateFlags&PFLAG_DRAWABLE_STATE_DIRTY) != 0) {
// If nobody has evaluated the drawable state yet, then do it now.
refreshDrawableState();
}
needGlobalAttributesUpdate(false);
}
对于子View,它的dispatchAttachedToWindow会回调onAttachedToWindow通知view被添加到宿主window上,随后宿主窗口可见,还会通过onWindowVisibilityChanged通知可见性发生了变化。这里我们看看SurfaceView它是如何做处理的。
//surfaceView被添加到宿主窗口时回调
@Override
protected void onAttachedToWindow() {
super.onAttachedToWindow();
mParent.requestTransparentRegion(this);//请求父窗口设置一块透明区域 即 在父窗口视图上挖一个洞 mParent为其父类View
mSession = getWindowSession();//获取IWindowSession 这是一个binder负责和WMS通信 后续通过这个session来向WMS请求绘图表面
mLayout.token = getWindowToken();//这个token也是ViewRootImpl中的W
mLayout.setTitle("SurfaceView");
mViewVisibility = getVisibility() == VISIBLE;
if (!mGlobalListenersAdded) {
ViewTreeObserver observer = getViewTreeObserver();
observer.addOnScrollChangedListener(mScrollChangedListener);
observer.addOnPreDrawListener(mDrawListener);
mGlobalListenersAdded = true;
}
}
在SurfaceView的onAttachedToWindow中,SurfaceView会向父窗口请求设置一块透明区域,这个透明区域是为了使SurfaceView在宿主窗口中可见,因为SurfaceView被添加时它的Z序是小于宿主窗口的,即它是显示在宿主窗口下面的,要显示SurfaceView就需要在宿主窗口设置对应大小的透明区域。同时还会取到IWindowSession 用来和WMS进行通信。
@Override
protected void onWindowVisibilityChanged(int visibility) {//宿主窗口的可见性发生了变化
super.onWindowVisibilityChanged(visibility);
//mWindowVisibility表示SurfaceView的宿主窗口的可见性,mViewVisibility表示SurfaceView自身的可见性。
//只有当mWindowVisibility和mViewVisibility的值均等于true的时候,mRequestedVisible的值才为true,表示SurfaceView是可见的
mWindowVisibility = visibility == VISIBLE;
mRequestedVisible = mWindowVisibility && mViewVisibility;
updateWindow(false, false);
}
关于可见性的变化,SurfaceView计算当前宿主窗口的可见性,并通过updateWindow来更新自身的窗口。
private void updateWindow(boolean force, boolean redrawNeeded) {
……
getLocationInWindow(mLocation);
final boolean creating = mWindow == null;
final boolean formatChanged = mFormat != mRequestedFormat;
final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;
final boolean visibleChanged = mVisible != mRequestedVisible;
if (force || creating || formatChanged || sizeChanged || visibleChanged
|| mLeft != mLocation[0] || mTop != mLocation[1]
|| mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
try {
final boolean visible = mVisible = mRequestedVisible;
mLeft = mLocation[0];
mTop = mLocation[1];
mWidth = myWidth;
mHeight = myHeight;
mFormat = mRequestedFormat;
// Places the window relative
mLayout.x = mLeft;
mLayout.y = mTop;
mLayout.width = getWidth();
mLayout.height = getHeight();
if (mTranslator != null) {
mTranslator.translateLayoutParamsInAppWindowToScreen(mLayout);
}
mLayout.format = mRequestedFormat;
mLayout.flags |=WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_LAYOUT_NO_LIMITS
| WindowManager.LayoutParams.FLAG_SCALED
| WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
| WindowManager.LayoutParams.FLAG_NOT_TOUCHABLE;
if (!getContext().getResources().getCompatibilityInfo().supportsScreen()) {
mLayout.privateFlags |=
WindowManager.LayoutParams.PRIVATE_FLAG_COMPATIBLE_WINDOW;
}
mLayout.privateFlags |= WindowManager.LayoutParams.PRIVATE_FLAG_NO_MOVE_ANIMATION;
if (mWindow == null) {
Display display = getDisplay();
mWindow = new MyWindow(this);
mLayout.type = mWindowType;
mLayout.gravity = Gravity.START|Gravity.TOP;
mSession.addToDisplayWithoutInputChannel(mWindow, mWindow.mSeq, mLayout,
mVisible ? VISIBLE : GONE, display.getDisplayId(), mContentInsets);
//为surfaceview添加窗口,对应于WMS一端
}
boolean realSizeChanged;
boolean reportDrawNeeded;
int relayoutResult;
mSurfaceLock.lock();
try {
mUpdateWindowNeeded = false;
reportDrawNeeded = mReportDrawNeeded;
mReportDrawNeeded = false;
mDrawingStopped = !visible;
relayoutResult = mSession.relayout(
mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
visible ? VISIBLE : GONE,
WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
mWinFrame, mOverscanInsets, mContentInsets,
mVisibleInsets, mConfiguration, mNewSurface);//计算窗口大小,分配绘图表面
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
mReportDrawNeeded = true;
}
mSurfaceFrame.left = 0;
mSurfaceFrame.top = 0;
if (mTranslator == null) {
mSurfaceFrame.right = mWinFrame.width();
mSurfaceFrame.bottom = mWinFrame.height();
} else {
float appInvertedScale = mTranslator.applicationInvertedScale;
mSurfaceFrame.right = (int) (mWinFrame.width() * appInvertedScale + 0.5f);
mSurfaceFrame.bottom = (int) (mWinFrame.height() * appInvertedScale + 0.5f);
}
final int surfaceWidth = mSurfaceFrame.right;
final int surfaceHeight = mSurfaceFrame.bottom;
realSizeChanged = mLastSurfaceWidth != surfaceWidth
|| mLastSurfaceHeight != surfaceHeight;
mLastSurfaceWidth = surfaceWidth;
mLastSurfaceHeight = surfaceHeight;
} finally {
mSurfaceLock.unlock();
}
……
if (visible && mSurface.isValid()) {
if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
mSurfaceCreated = true;
mIsCreating = true;
if (DEBUG) Log.i(TAG, "visibleChanged -- surfaceCreated");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
if (creating || formatChanged || sizeChanged
|| visibleChanged || realSizeChanged) {
if (DEBUG) Log.i(TAG, "surfaceChanged -- format=" + mFormat
+ " w=" + myWidth + " h=" + myHeight);
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
}
}
if (redrawNeeded) {
if (DEBUG) Log.i(TAG, "surfaceRedrawNeeded");
if (callbacks == null) {
callbacks = getSurfaceCallbacks();
}
for (SurfaceHolder.Callback c : callbacks) {
if (c instanceof SurfaceHolder.Callback2) {
((SurfaceHolder.Callback2)c).surfaceRedrawNeeded(
mSurfaceHolder);
}
}
}
}
……
} catch (RemoteException ex) {}
}
}
对于SurfaceView来说,它拥有独立的Window,这个Window同样受WMS的管理,因此在绘制时需要将该Window添加到WMS中去,同时,它也有它自己的绘图表面,因此在绘制之前需要计算它自身的窗口大小并向WMS请求绘图表面。关于分配绘图缓冲在其它章节已做了描述,本篇不再多做介绍。
SurfaceView的挖洞过程
SurfaceView的窗口类型一般都是TYPE_APPLICATION_MEDIA或者TYPE_APPLICATION_MEDIA_OVERLAY,它在添加到宿主窗口上时,会在父窗口的表面设置一个透明区域以显示SurfaceView自身的内容,这是通过requestTransparentRegion来进行的,下面我们就看看这个过程是如何完成的。
//ViewGroup
public void requestTransparentRegion(View child) {
if (child != null) {
child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
if (mParent != null) {
mParent.requestTransparentRegion(this);
}
}
}
//ViewRootImpl
@Override
public void requestTransparentRegion(View child) {
// the test below should not fail unless someone is messing with us
checkThread();
if (mView == child) {
mView.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
// Need to make sure we re-evaluate the window attributes next
// time around, to ensure the window has the correct format.
mWindowAttributesChanged = true;
mWindowAttributesChangesFlag = 0;
requestLayout();
}
}
SurfaceView请求透明区域的过程实际上为父View打上PFLAG_REQUEST_TRANSPARENT_REGIONS标记,这表示它要在宿主窗口上设置透明区域,这个过程直到ViewRootImpl,ViewRootImpl它也是ViewParent,它将调用requestLayout触发performTraversals来请求窗口重新布局和绘制,从而收集透明区域,最后通过WMS为该宿主窗口设置一个总的透明区域。
private void performTraversals() {
……
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// reconfigure window manager
try {
mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
} catch (RemoteException e) {
}
}
}
……
}
收集透明区域也是从DecorView开始的,它调用gatherTransparentRegion来完成,这个区域是由mTransparentRegion来存储维护的。如果透明区域发生了变化这时候就需要重新设置该透明区域到WMS中去。
//ViewGroup.java
@Override
public boolean gatherTransparentRegion(Region region) {
// If no transparent regions requested, we are always opaque.
final boolean meOpaque = (mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0;
if (meOpaque && region == null) {
// The caller doesn't care about the region, so stop now.
return true;
}
super.gatherTransparentRegion(region);
final View[] children = mChildren;
final int count = mChildrenCount;
boolean noneOfTheChildrenAreTransparent = true;
for (int i = 0; i < count; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
if (!child.gatherTransparentRegion(region)) {
noneOfTheChildrenAreTransparent = false;
}
}
}
return meOpaque || noneOfTheChildrenAreTransparent;
}
收集透明区域会遍历父view下的所有子view的透明区域。在开始收集之前,首先将透明区域设置为DecorView视图的大小,然后遍历子view,如果view是不透明的区域则将其从初始的透明区域中移除,这样最后保留下来的就是需要设置的透明区域。
下面我们看看SurfaceView它是如何进行计算它的透明区域的。
@Override
public boolean gatherTransparentRegion(Region region) {
if (mWindowType == WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
return super.gatherTransparentRegion(region);
}
boolean opaque = true;
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// this view draws, remove it from the transparent region
opaque = super.gatherTransparentRegion(region);
} else if (region != null) {
int w = getWidth();
int h = getHeight();
if (w>0 && h>0) {
getLocationInWindow(mLocation);
// otherwise, punch a hole in the whole hierarchy
int l = mLocation[0];
int t = mLocation[1];
region.op(l, t, l+w, t+h, Region.Op.UNION);
}
}
if (PixelFormat.formatHasAlpha(mRequestedFormat)) {
opaque = false;
}
return opaque;
}
SurfaceView首先判断窗口类型是否作为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL,如果是则表示SurfaceView是用来作为应用面板的,这时候调用父类view的gatherTransparentRegion来处理,如果需要绘制,则将view的绘制区域从从参数region所描述的透明区域中移除。
SurfaceView的绘制
SurfaceView具有独立的绘图Surface,但它仍然属于View树中的子节点,它依附在宿主窗口上,所以它自身的view也是需要绘制到宿主窗口的Surface上的。在介绍view绘制流程一节中我们知道了view的绘制会触发draw和dispatchDraw方法,前者绘制它自身,后者负责绘制子view。
//SurfaceView.java
@Override
public void draw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// draw() is not called when SKIP_DRAW is set
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == 0) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.draw(canvas);
}
@Override
protected void dispatchDraw(Canvas canvas) {
if (mWindowType != WindowManager.LayoutParams.TYPE_APPLICATION_PANEL) {
// if SKIP_DRAW is cleared, draw() has already punched a hole
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// punch a whole in the view-hierarchy below us
canvas.drawColor(0, PorterDuff.Mode.CLEAR);
}
}
super.dispatchDraw(canvas);
}
draw和dispatchDraw方法的参数canvas代表的是宿主窗口的绘图表面的画布,这表示surfaceview自身的ui是绘制在 宿主绘图表面上的,如果mWindowType不为WindowManager.LayoutParams.TYPE_APPLICATION_PANEL表示SurfaceView不作为应用面板,那么将其view所占区域设置为黑色。
除了在宿主窗口上绘制ui,SurfaceView可以在自身的绘图表面上绘制内容,一般的绘制流程如下:
SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);
SurfaceHolder sh = sv.getHolder();
Cavas canvas = sh.lockCanvas()
//Draw something on canvas
......
sh.unlockCanvasAndPost(canvas);
需要注意的是,当绘图表面的类型设置为SURFACE_TYPE_PUSH_BUFFERS时,表示绘图表面的缓冲区不受我们控制,它是由摄像头或者视频播放服务来提供的。因此不能在随意在上面进行内容绘制。
@Override
public Canvas lockCanvas() {
return internalLockCanvas(null);
}
private final Canvas internalLockCanvas(Rect dirty) {
mSurfaceLock.lock();
Canvas c = null;
if (!mDrawingStopped && mWindow != null) {
try {
c = mSurface.lockCanvas(dirty);
} catch (Exception e) {
Log.e(LOG_TAG, "Exception locking surface", e);
}
}
if (c != null) {
mLastLockTime = SystemClock.uptimeMillis();
return c;
}
// If the Surface is not ready to be drawn, then return null,
// but throttle calls to this function so it isn't called more
// than every 100ms.
long now = SystemClock.uptimeMillis();
long nextTime = mLastLockTime + 100;
if (nextTime > now) {
try {
Thread.sleep(nextTime-now);
} catch (InterruptedException e) {
}
now = SystemClock.uptimeMillis();
}
mLastLockTime = now;
mSurfaceLock.unlock();
return null;
}
SurfaceView的lockCanvas是从其绘图表面申请缓冲区,也就是Canvas,应用上层可以使用这个Canvas来进行内容的绘制,需要注意的是这个画布并不是线程安全的,需要通过mSurfaceLock的锁保护。