SurfaceView原理简述
2017-10-08 12:25
295 查看
以前有个疑问,SurfaceView为什么可以开一个新的线程进行绘制,而其他的不行。我们知道View的布局绘制是在主线程执行的,通过ViewRootImpl的performTraversals方法开始驱动顶层DecorView和它的子View执行measure,layout, draw。同样是View类,那为什么SurfaceView的绘制操作却可以单独开新的线程去执行呢?
在这里先提前总结一下原理。View的绘制是由ViewRootImpl的Surface对象将绘制后的ui数据交给WindowManagerService,WindowManagerService会将多个Surface数据合并成一整屏的ui数据,交给SurfaceFlinger渲染对应的Layer。而SurfaceView内部维护着一块Surface用于ui数据的的绘制,同时在WindowManagerService端会创建一个新的绘图对象,对应着SurfaceFlinger的一个新的Layer,因此SurfaceView中绘制的数据就由新的Layer,而不是宿主DecorView的Layer,意思就是SurfaceView有和宿主DecorView对应的ViewRootImpl一样的一套绘制渲染模型,两者分别独立渲染。
现在简单讲讲SurfaceView的工作流程。
1.在onAttachedToWindow中进行对SurfaceView的初始化和准备工作。完成透明区域的请求,获取WindowManagerService的本地代理对象mSession,和对绘制的监听addOnPreDrawListener
2.在onWindowVisibilityChanged中将被显示时,调用updateWindow
3.updateWindow,初始化MyWindow对象,用于WindowManagerService通知SurfaceView的状态变化,mSession.relayout以请求WindowManagerService对Surface的UI进行布局,这样SurfaceFlinger就会为其创建一个独立的Layer绘图对象。这里同时会回调SurfaceHolder.Callback的surfaceCreated和surfaceChanged等方法,通知当前的SurfaceView是否准备好了。
4.然后我们就可以通过getHolder获取SurfaceHolder的对象,在新线程中,通过lockCanvas锁定画布,然后用Cavas对象进行绘制,内部是通过SurfaceView的Surface对象来取得Canvas对象,该Canvas通过JNI调用到底层的Surface的Canvas进行操作。
5.在onDetachedFromWindow中进行对SurfaceView的清理工作。移除绘制监听,调用updateWindow通知回调SurfaceHolder.Callback的surfaceDestroyed,mSession移除mWindow以使WindowManagerService解除对SurfaceView的操作。
再次总结,因为普通View走的是ViewRootImpl的绘制流程,在里面有对线程进行检查,非主线程的话会抛异常,目的就是实现单线程绘制模型,同时又要接受输入事情等。而SurfaceView有独立的绘制机制,比如独立的客户端Surface,WindowManagerService中独立的绘图对象,SurfaceFlinger中独立的Layer渲染。因为它里面可以只负责绘制,所以效率要更高。当然,SurfaceView的其他处理比如输入事件还是继承使用了View的那一套。
好了,简述完了之后,觉得还是有必要贴出些源码,让描述显得更加有依据一些。
此处初始化,目的是不进行View的那一套绘制,专心做新线程的绘制。
当SurfaceView被添加到Window上即将显示时,调用mParent.requestTransparentRegion(this);请求顶层View给自己留出空白区域,这样我们的SurfaceView才能不会被其他View遮挡住。然后这里获取了WindowManager的代理对象,以后后面对WindowManager的请求操作。然后添加滚动监听和绘制监听
这样,即将要绘制的时候,就会执行updateWindow方法了。
SurfaceView依附的Window要显示或隐藏时,也同样调用updateWindow,那updateWindow是做什么操作的呢?
可见,updateWindow的主要作用是SurfaceView更新的处理,包括mWindow,通知WindowMangerService中Surface的创建,还有Surface的创建,更新,销毁的通知回调等。
我们在看看MyWindow这个Binder类
可见,MyWindow就是WindowMangerService用来通知SurfaceView的沟通工具,通知SurfaceView对应的Surface状态的变化。
最后就是mSurfaceHolder了,顾名思义就是Surface的持有者,通过它来操作Surface对象。
可见,SurfaceHolder就是SurfaceView对外提供访问Surface的接口,这里访问Surface有做同步,为空时重复请求的处理。
到这里的话,SurfaceView的大部分逻辑都在这了,就先到这里了。
在这里先提前总结一下原理。View的绘制是由ViewRootImpl的Surface对象将绘制后的ui数据交给WindowManagerService,WindowManagerService会将多个Surface数据合并成一整屏的ui数据,交给SurfaceFlinger渲染对应的Layer。而SurfaceView内部维护着一块Surface用于ui数据的的绘制,同时在WindowManagerService端会创建一个新的绘图对象,对应着SurfaceFlinger的一个新的Layer,因此SurfaceView中绘制的数据就由新的Layer,而不是宿主DecorView的Layer,意思就是SurfaceView有和宿主DecorView对应的ViewRootImpl一样的一套绘制渲染模型,两者分别独立渲染。
现在简单讲讲SurfaceView的工作流程。
1.在onAttachedToWindow中进行对SurfaceView的初始化和准备工作。完成透明区域的请求,获取WindowManagerService的本地代理对象mSession,和对绘制的监听addOnPreDrawListener
2.在onWindowVisibilityChanged中将被显示时,调用updateWindow
3.updateWindow,初始化MyWindow对象,用于WindowManagerService通知SurfaceView的状态变化,mSession.relayout以请求WindowManagerService对Surface的UI进行布局,这样SurfaceFlinger就会为其创建一个独立的Layer绘图对象。这里同时会回调SurfaceHolder.Callback的surfaceCreated和surfaceChanged等方法,通知当前的SurfaceView是否准备好了。
4.然后我们就可以通过getHolder获取SurfaceHolder的对象,在新线程中,通过lockCanvas锁定画布,然后用Cavas对象进行绘制,内部是通过SurfaceView的Surface对象来取得Canvas对象,该Canvas通过JNI调用到底层的Surface的Canvas进行操作。
5.在onDetachedFromWindow中进行对SurfaceView的清理工作。移除绘制监听,调用updateWindow通知回调SurfaceHolder.Callback的surfaceDestroyed,mSession移除mWindow以使WindowManagerService解除对SurfaceView的操作。
再次总结,因为普通View走的是ViewRootImpl的绘制流程,在里面有对线程进行检查,非主线程的话会抛异常,目的就是实现单线程绘制模型,同时又要接受输入事情等。而SurfaceView有独立的绘制机制,比如独立的客户端Surface,WindowManagerService中独立的绘图对象,SurfaceFlinger中独立的Layer渲染。因为它里面可以只负责绘制,所以效率要更高。当然,SurfaceView的其他处理比如输入事件还是继承使用了View的那一套。
好了,简述完了之后,觉得还是有必要贴出些源码,让描述显得更加有依据一些。
public SurfaceView(Context context) { super(context); init(); } public SurfaceView(Context context, AttributeSet attrs) { super(context, attrs); init(); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr) { super(context, attrs, defStyleAttr); init(); } public SurfaceView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { super(context, attrs, defStyleAttr, defStyleRes); init(); } private void init() { setWillNotDraw(true); }
此处初始化,目的是不进行View的那一套绘制,专心做新线程的绘制。
@Override protected void onAttachedToWindow() { super.onAttachedToWindow(); mParent.requestTransparentRegion(this); mSession = getWindowSession(); mLayout.token = getWindowToken(); mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle()); mViewVisibility = getVisibility() == VISIBLE; if (!mGlobalListenersAdded) { ViewTreeObserver observer = getViewTreeObserver(); observer.addOnScrollChangedListener(mScrollChangedListener); observer.addOnPreDrawListener(mDrawListener); mGlobalListenersAdded = true; } }
当SurfaceView被添加到Window上即将显示时,调用mParent.requestTransparentRegion(this);请求顶层View给自己留出空白区域,这样我们的SurfaceView才能不会被其他View遮挡住。然后这里获取了WindowManager的代理对象,以后后面对WindowManager的请求操作。然后添加滚动监听和绘制监听
private final ViewTreeObserver.OnScrollChangedListener mScrollChangedListener = new ViewTreeObserver.OnScrollChangedListener() { @Override public void onScrollChanged() { updateWindow(false, false); } }; private final ViewTreeObserver.OnPreDrawListener mDrawListener = new ViewTreeObserver.OnPreDrawListener() { @Override public boolean onPreDraw() { // reposition ourselves where the surface is mHaveFrame = getWidth() > 0 && getHeight() > 0; updateWindow(false, false); return true; } };
这样,即将要绘制的时候,就会执行updateWindow方法了。
@Override protected void onWindowVisibilityChanged(int visibility) { super.onWindowVisibilityChanged(visibility); mWindowVisibility = visibility == VISIBLE; mRequestedVisible = mWindowVisibility && mViewVisibility; updateWindow(false, false); }
SurfaceView依附的Window要显示或隐藏时,也同样调用updateWindow,那updateWindow是做什么操作的呢?
protected void updateWindow(boolean force, boolean redrawNeeded) { ..... final boolean creating = mWindow == null; final boolean formatChanged = mFormat != mRequestedFormat; final boolean sizeChanged = mWindowSpaceWidth != myWidth || mWindowSpaceHeight != myHeight; final boolean visibleChanged = mVisible != mRequestedVisible; final boolean layoutSizeChanged = getWidth() != mLayout.width || getHeight() != mLayout.height; //此处判断SurfaceView是否有发生变化,需要更新 if (force || creating || formatChanged || sizeChanged || visibleChanged || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) { //需要刷新SurfaceView,更新mLayout这个SurfaceView的LayoutParams的信息 ...... if (mWindow == null) { //MyWindow是一个Binder类,用于WindowMangerService通知回调SurfaceView,这里创建MyWindow对象mWIndow,并通过mSession.addToDisplayWithoutInputChannel将这些参数传给WindowMangerService,通知它为SurfaceView创建一块不接收输入事件的Surface,以便后面绘图所用。 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, mStableInsets); } boolean realSizeChanged; boolean reportDrawNeeded; int relayoutResult; //此处需要用锁锁住,防止其他线程同时修改 mSurfaceLock.lock(); try { mUpdateWindowNeeded = false; reportDrawNeeded = mReportDrawNeeded; mReportDrawNeeded = false; mDrawingStopped = !visible; //通过mSession这个binder对象请求WindowMangerService为SurfaceView的UI进行布局,然后WindowMangerService就会填充mNewSurface这个Surface对象,以便后面可以通过它获取画布Canvas,进行绘图操作。 relayoutResult = mSession.relayout( mWindow, mWindow.mSeq, mLayout, mWindowSpaceWidth, mWindowSpaceHeight, visible ? VISIBLE : GONE, WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY, mWinFrame, mOverscanInsets, mContentInsets, mVisibleInsets, mStableInsets, mOutsets, mBackdropFrame, mConfiguration, mNewSurface); if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) { reportDrawNeeded = true; } if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "New surface: " + mNewSurface + ", vis=" + visible + ", frame=" + mWinFrame); 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(); } try { redrawNeeded |= creating | reportDrawNeeded; SurfaceHolder.Callback callbacks[] = null; final boolean surfaceChanged = (relayoutResult & WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED) != 0; if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) { mSurfaceCreated = false; if (mSurface.isValid()) { if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "visibleChanged -- surfaceDestroyed"); callbacks = getSurfaceCallbacks(); //这里判断了Surface被销毁了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了 for (SurfaceHolder.Callback c : callbacks) { c.surfaceDestroyed(mSurfaceHolder); } } } //这里将最新状态的mNewSuface对象的数据更新到当前的mSurface中 mSurface.transferFrom(mNewSurface); if (visible && mSurface.isValid()) { if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) { mSurfaceCreated = true; mIsCreating = true; if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "visibleChanged -- surfaceCreated"); if (callbacks == null) { callbacks = getSurfaceCallbacks(); } //这里判断了Surface被创建了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了 for (SurfaceHolder.Callback c : callbacks) { c.surfaceCreated(mSurfaceHolder); } } if (creating || formatChanged || sizeChanged || visibleChanged || realSizeChanged) { if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "surfaceChanged -- format=" + mFormat + " w=" + myWidth + " h=" + myHeight); if (callbacks == null) { callbacks = getSurfaceCallbacks(); } //这里判断了Surface被更新了,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了 for (SurfaceHolder.Callback c : callbacks) { c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight); } } if (redrawNeeded) { if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "surfaceRedrawNeeded"); if (callbacks == null) { callbacks = getSurfaceCallbacks(); } //这里判断了Surface需要被重绘,回调实现了SurfaceHolder.Callback的对象,大部分情况就是我们继承实现的SurfaceView了 for (SurfaceHolder.Callback c : callbacks) { if (c instanceof SurfaceHolder.Callback2) { ((SurfaceHolder.Callback2)c).surfaceRedrawNeeded( mSurfaceHolder); } } } } } finally { mIsCreating = false; if (redrawNeeded) { if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "finishedDrawing"); mSession.finishDrawing(mWindow); } mSession.performDeferredDestroy(mWindow); } } catch (RemoteException ex) { Log.e(TAG, "Exception from relayout", ex); } } else { //除了以上情况,如果是SufaceView的位置或大小发生改变,就进行UI线程的布局更新 // Calculate the window position in case RT loses the window // and we need to fallback to a UI-thread driven position update getLocationInWindow(mLocation); final boolean positionChanged = mWindowSpaceLeft != mLocation[0] || mWindowSpaceTop != mLocation[1]; if (positionChanged || layoutSizeChanged) { // Only the position has changed mWindowSpaceLeft = mLocation[0]; mWindowSpaceTop = mLocation[1]; // For our size changed check, we keep mLayout.width and mLayout.height // in view local space. mLocation[0] = mLayout.width = getWidth(); mLocation[1] = mLayout.height = getHeight(); transformFromViewToWindowSpace(mLocation); mWinFrame.set(mWindowSpaceLeft, mWindowSpaceTop, mLocation[0], mLocation[1]); if (mTranslator != null) { mTranslator.translateRectInAppWindowToScreen(mWinFrame); } if (!isHardwareAccelerated() || !mRtHandlingPositionUpdates) { try { if (DEBUG) Log.d(TAG, String.format("%d updateWindowPosition UI, " + "postion = [%d, %d, %d, %d]", System.identityHashCode(this), mWinFrame.left, mWinFrame.top, mWinFrame.right, mWinFrame.bottom)); mSession.repositionChild(mWindow, mWinFrame.left, mWinFrame.top, mWinFrame.right, mWinFrame.bottom, -1, mWinFrame); } catch (RemoteException ex) { Log.e(TAG, "Exception from relayout", ex); } } } } }
可见,updateWindow的主要作用是SurfaceView更新的处理,包括mWindow,通知WindowMangerService中Surface的创建,还有Surface的创建,更新,销毁的通知回调等。
我们在看看MyWindow这个Binder类
private static class MyWindow extends BaseIWindow { private final WeakReference<SurfaceView> mSurfaceView; public MyWindow(SurfaceView surfaceView) { //弱引用持有当前的SurfaceView,防止内存泄露 mSurfaceView = new WeakReference<SurfaceView>(surfaceView); } @Override public void resized(Rect frame, Rect overscanInsets, Rect contentInsets, Rect visibleInsets, Rect stableInsets, Rect outsets, boolean reportDraw, Configuration newConfig, Rect backDropRect, boolean forceLayout, boolean alwaysConsumeNavBar) { //WindowMangerService中SurfaceView对应的Surface大小发生变化,发消息通知窗口布局发生了变化 SurfaceView surfaceView = mSurfaceView.get(); if (surfaceView != null) { if (DEBUG) Log.v(TAG, surfaceView + " got resized: w=" + frame.width() + " h=" + frame.height() + ", cur w=" + mCurWidth + " h=" + mCurHeight); surfaceView.mSurfaceLock.lock(); try { if (reportDraw) { surfaceView.mUpdateWindowNeeded = true; surfaceView.mReportDrawNeeded = true; surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); } else if (surfaceView.mWinFrame.width() != frame.width() || surfaceView.mWinFrame.height() != frame.height() || forceLayout) { surfaceView.mUpdateWindowNeeded = true; surfaceView.mHandler.sendEmptyMessage(UPDATE_WINDOW_MSG); } } finally { surfaceView.mSurfaceLock.unlock(); } } } @Override public void dispatchAppVisibility(boolean visible) { // The point of SurfaceView is to let the app control the surface. } @Override public void dispatchGetNewSurface() { //通知对应的新的Surface创建了,这里消息处理会调用updateWindow,在其中会获得最新的Surface填充到mNewSurface中 SurfaceView surfaceView = mSurfaceView.get(); if (surfaceView != null) { Message msg = surfaceView.mHandler.obtainMessage(GET_NEW_SURFACE_MSG); surfaceView.mHandler.sendMessage(msg); } } @Override public void windowFocusChanged(boolean hasFocus, boolean touchEnabled) { Log.w("SurfaceView", "Unexpected focus in surface: focus=" + hasFocus + ", touchEnabled=" + touchEnabled); } @Override public void executeCommand(String command, String parameters, ParcelFileDescriptor out) { } int mCurWidth = -1; int mCurHeight = -1; }
可见,MyWindow就是WindowMangerService用来通知SurfaceView的沟通工具,通知SurfaceView对应的Surface状态的变化。
最后就是mSurfaceHolder了,顾名思义就是Surface的持有者,通过它来操作Surface对象。
private final SurfaceHolder mSurfaceHolder = new SurfaceHolder() { private static final String LOG_TAG = "SurfaceHolder"; @Override public boolean isCreating() { return mIsCreating; } @Override public void addCallback(Callback callback) { //添加callback回调 synchronized (mCallbacks) { // This is a linear search, but in practice we'll // have only a couple callbacks, so it doesn't matter. if (mCallbacks.contains(callback) == false) { mCallbacks.add(callback); } } } @Override public void removeCallback(Callback callback) { //移除callback回调 synchronized (mCallbacks) { mCallbacks.remove(callback); } } @Override public void setFixedSize(int width, int height) { if (mRequestedWidth != width || mRequestedHeight != height) { mRequestedWidth = width; mRequestedHeight = height; requestLayout(); } } @Override public void setSizeFromLayout() { if (mRequestedWidth != -1 || mRequestedHeight != -1) { mRequestedWidth = mRequestedHeight = -1; requestLayout(); } } @Override public void setFormat(int format) { // for backward compatibility reason, OPAQUE always // means 565 for SurfaceView if (format == PixelFormat.OPAQUE) format = PixelFormat.RGB_565; mRequestedFormat = format; if (mWindow != null) { updateWindow(false, false); } } /** * @deprecated setType is now ignored. */ @Override @Deprecated public void setType(int type) { } @Override public void setKeepScreenOn(boolean screenOn) { Message msg = mHandler.obtainMessage(KEEP_SCREEN_ON_MSG); msg.arg1 = screenOn ? 1 : 0; mHandler.sendMessage(msg); } /** * Gets a {@link Canvas} for drawing into the SurfaceView's Surface * * After drawing into the provided {@link Canvas}, the caller must * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. * * The caller must redraw the entire surface. * @return A canvas for drawing into the surface. */ @Override public Canvas lockCanvas() { return internalLockCanvas(null); } /** * Gets a {@link Canvas} for drawing into the SurfaceView's Surface * * After drawing into the provided {@link Canvas}, the caller must * invoke {@link #unlockCanvasAndPost} to post the new contents to the surface. * * @param inOutDirty A rectangle that represents the dirty region that the caller wants * to redraw. This function may choose to expand the dirty rectangle if for example * the surface has been resized or if the previous contents of the surface were * not available. The caller must redraw the entire dirty region as represented * by the contents of the inOutDirty rectangle upon return from this function. * The caller may also pass <code>null</code> instead, in the case where the * entire surface should be redrawn. * @return A canvas for drawing into the surface. */ @Override public Canvas lockCanvas(Rect inOutDirty) { return internalLockCanvas(inOutDirty); } private final Canvas internalLockCanvas(Rect dirty) { //锁定并获取Canvas画布对象,以进行后面的绘画 mSurfaceLock.lock(); if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Locking canvas... stopped=" + mDrawingStopped + ", win=" + mWindow); Canvas c = null; if (!mDrawingStopped && mWindow != null) { try { c = mSurface.lockCanvas(dirty); } catch (Exception e) { Log.e(LOG_TAG, "Exception locking surface", e); } } if (DEBUG) Log.i(TAG, System.identityHashCode(this) + " " + "Returned canvas: " + c); 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; } /** * Posts the new contents of the {@link Canvas} to the surface and * releases the {@link Canvas}. * * @param canvas The canvas previously obtained from {@link #lockCanvas}. */ @Override public void unlockCanvasAndPost(Canvas canvas) { mSurface.unlockCanvasAndPost(canvas); mSurfaceLock.unlock(); } @Override public Surface getSurface() { return mSurface; } @Override public Rect getSurfaceFrame() { return mSurfaceFrame; } };
可见,SurfaceHolder就是SurfaceView对外提供访问Surface的接口,这里访问Surface有做同步,为空时重复请求的处理。
到这里的话,SurfaceView的大部分逻辑都在这了,就先到这里了。
相关文章推荐
- 【Android游戏开发二十六】追加简述SurfaceView 与 GLSurfaceView效率!
- Android视图SurfaceView的实现原理分析
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整(原理:底层SurfaceView+上层绘制ImageView)
- Android学习之旅(第一篇) SurfaceView的原理以及使用场景
- Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析
- Android SurfaceView相关概念及原理
- (转)【Android游戏开发二十六】追加简述SurfaceView 与 GLSurfaceView效率!
- Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析
- android SurfaceView绘制实现原理解析
- Android视图SurfaceView的实现原理分析
- 【Android游戏开发】追加简述SurfaceView 与 GLSurfaceView效率!
- Android开发 摄像头SurfaceView预览 背景带矩形框 实现(原理:双surfaceview,顶层画矩形框,底层预览视频)
- Android摄像头:只拍摄SurfaceView预览界面特定区域内容(矩形框)---完整实现(原理:底层SurfaceView+上层绘制ImageView)
- Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析 Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析
- Android视图SurfaceView的实现原理分析