SurfaceView的介绍

SurfaceView与View的区别

  • View是在主线程下对画面进行刷新的操作,而SurfaceView是一个独立线程来进行画面的刷新
  • SufaceView有自己独立的绘图图层(View则是与其宿主窗口共享同一个绘图表面),这也就是为什么SurfaceView可以在独立线程中绘制
  • SurfaceView底层是实现了双缓冲机制,而View是没有使用双缓冲机制的
  • SurfaceView适合进行频繁的,或者刷新数据处理大的场景

相关的核心类

  • SurfaceView
  • Surface
  • SurfaceHolder

一般的使用

一个例子,自动的绘制sin()曲线:

/**
 * Created by panda on 2018/8/1
 **/
public class MySurfaceView extends SurfaceView implements SurfaceHolder.Callback, Runnable {

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    int x = 0;
    int y = 400;
    private Path mPath = new Path();
    private Paint mPaint = new Paint();

    public MySurfaceView(Context context) {
        super(context);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MySurfaceView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);
        mPath.moveTo(x, y);
        mPaint.setStyle(Paint.Style.STROKE);
//        mPaint.setColor(Color.WHITE);
        mPaint.setStrokeWidth(10);
    }

    @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 (mIsDrawing) {
            try {
                mCanvas = mHolder.lockCanvas();
                mCanvas.drawColor(Color.WHITE);
                x += 1;
                y = (int) (100 * Math.sin(x * 2 * Math.PI / 180) + 400);
                mPath.lineTo(x, y);
                mCanvas.drawPath(mPath, mPaint);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (null != mCanvas) {
                    mHolder.unlockCanvasAndPost(mCanvas);
                }
            }
        }
    }
}

这里需要注意一点,获取到的Canvas对象还是上次的Canvas对象,而不是一个新的Canvas对象。因此,之前的绘图操作都会被保留。如果需要擦除,则可以在绘制前,通过drawColor()方法来进行清屏操作。

还有个例子,写字板功能:

public class MySurfaceViewForPath extends SurfaceView implements SurfaceHolder.Callback,Runnable{

    private SurfaceHolder mHolder;
    //用于绘图的canvas
    private Canvas mCanvas;
    //子线程标志位
    private boolean mIsDrawing;

    private Path mPath;
    private Paint mPaint;

    public MySurfaceViewForPath(Context context) {
        super(context);
        initView();
    }

    public MySurfaceViewForPath(Context context, AttributeSet attrs) {
        super(context, attrs);
        initView();
    }

    public MySurfaceViewForPath(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        initView();
    }

    private void initView() {
        mHolder = getHolder();
        mHolder.addCallback(this);
        setFocusable(true);
        setFocusableInTouchMode(true);
        this.setKeepScreenOn(true);

        mPaint = new Paint();
        mPath = new Path();
        mPaint.setStyle(Paint.Style.STROKE);
        mPaint.setStrokeWidth(10);
    }

    @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 boolean onTouchEvent(MotionEvent event) {
        int x = (int) event.getX();
        int y = (int) event.getY();
        switch (event.getAction()){
            case MotionEvent.ACTION_DOWN:
                mPath.moveTo(x,y);
                break;
            case MotionEvent.ACTION_MOVE:
                mPath.lineTo(x,y);
                break;
        }

        return true;
    }

    @Override
    public void run() {
        long start = System.currentTimeMillis();
        while (mIsDrawing){
            draw();
        }
        long end = System.currentTimeMillis();
        if(end-start<100){
            try {
                Thread.sleep(100-(end-start));
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    private void draw() {
        try {
            mCanvas = mHolder.lockCanvas();
            mCanvas.drawColor(Color.WHITE);
            mCanvas.drawPath(mPath,mPaint);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if(null!=mCanvas){
                mHolder.unlockCanvasAndPost(mCanvas);
            }
        }
    }
}

SurfaceView的内部原理

https://blog.csdn.net/Luoshengyang/article/details/8661317

主要的流程是:

1、SurfaceView绘图图层的创建

触发的流程就不说了,我们看下最后onAttachedToWindow方法:

@Override
    protected void onAttachedToWindow() {
        super.onAttachedToWindow();
        mParent.requestTransparentRegion(this);
        mSession = getWindowSession();
        mLayout.token = getWindowToken();
        mLayout.setTitle("SurfaceView - " + getViewRootImpl().getTitle());
        mLayout.packageName = mContext.getOpPackageName();
        mViewVisibility = getVisibility() == VISIBLE;

        if (!mGlobalListenersAdded) {
            ViewTreeObserver observer = getViewTreeObserver();
            observer.addOnScrollChangedListener(mScrollChangedListener);
            observer.addOnPreDrawListener(mDrawListener);
            mGlobalListenersAdded = true;
        }
    }

主要是要知道2核心点:

  • 通知父视图,需要在宿主窗口的绘图层面打开一个透明的区域。surface是Z-orderd的排序,它总是在窗口的后面。调用父view的requestTransparentRegion()方法。
  • 通过getWindowSession()方法来得到实现了IWindowSession接口的Binder代理对象,保存到成员变量mSession中,来实现和WMS进行通信,这里SurfaceView可以请求WMS服务来创建绘制图层。

接着,将会调用顶层视图的视图的dispatchWindowVisibilityChanged()方法来通知各子view的可见性。这里会调用到SurfaceView.onWindowVisibilityChanged()

@Override
    protected void onWindowVisibilityChanged(int visibility) {
        super.onWindowVisibilityChanged(visibility);
        mWindowVisibility = visibility == VISIBLE;
        mRequestedVisible = mWindowVisibility && mViewVisibility;
        updateWindow(false, false);
    }

只有在mWindowVisibility(表示SurfaceView的宿主窗口的可见性)和mViewVisibility(表示SurfaceView自身的可见性),只有当都可见的时候才表示SurfaceView是可见的。

主要是在updateWindow()完成操作的。

  • 成员变量mSurface,Surface对象,描述的就是SurfaceView专有的绘图表面,它其实是个Parcelable类
  • 成员变量 mWindow是IWindow接口的实现类,这有什么意义呢?意义是WMS认为Activityc窗口和SurfaceView窗口地位低同等的,都有绘图能力。
  • 成员变量mWindowType描述的是SurfaceView的窗口类型
    • WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA:显示多媒体
    • WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA_OVERLAY:介于TYPE_APPLICATION_MEDIA和application窗口之间的SurfaceView。比如可以用来显示字幕等信息。
    • WindowManager.LayoutParams.TYPE_APPLICATION_PANEL:设置了这个,那么SurfaceView就会在application窗口上面
    • 可以通过setWindowType()setZOrderMediaOverlay()setZOrderOnTop()来改变mWindowType的值,从而改变SurfaceView的Z-ordered位置
  • mHaveFrame:描述SurfaceView的宿主窗口的大小是否已经计算好了,只有宿主窗口大小ready,才能更新自己
  • mRequestedHeight/mRequestedWidth:描述SurfaceView最后一次被请求的宽高
  • mRequestedFormat:SurfaceView绘制的像素格式,缺省是PixelFormat.RGB_565
  • mUpdateWindowNeeded:SurfaceView是否被WindowManagerService服务通知执行一次UI更新操作。
  • mReportDrawNeeded:SurfaceView是否被WindowManagerService服务通知执行一次UI绘制操作
  • mLayout,WindowManager.LayoutParams对象,用来传递SurfaceView的布局参数以及属性值给WMS服务,以便WMS服务可以正确地维护它的状态

updateWindow()大致的一个流程是:

WMS服务在对一个窗口进行布局的时候,如果发现该窗口的绘制表面还未创建,或者需要需要重新创建,那么就会为请求SurfaceFlinger服务为该窗口创建一个新的绘图表面,并且将该绘图表面返回来给调用者。在我们这个情景中,WMS服务返回来的绘图表面就会保存在成员变量mSurface。注意,这一步由于可能会修改SurfaceView的绘图表面,即修改成员变量mSurface的指向的一个Surface对象的内容,因此,就需要在获得成员变量mSurfaceLock所描述的一个锁的情况下执行,避免其它线程同时修改该绘图表面的内容,这是因为我们可能会使用一个独立的线程来来绘制SurfaceView的UI。

2、SurfaceView展现出自己

由于SurfaceView是在application窗口的下面的,所以为了能显示出自己,就需要突破application窗口,主要是调用ViewGroup.requestTransparentRegion()方法。

实际上就是在其宿主窗口的绘图表面上设置一块透明区域,以便可以将自己显示出来。

SurfaceView会调用到DecorView,DecorView继承自ViewGroup:

public void requestTransparentRegion(View child) {
        if (child != null) {
            child.mPrivateFlags |= View.PFLAG_REQUEST_TRANSPARENT_REGIONS;
            if (mParent != null) {
                mParent.requestTransparentRegion(this);
            }
        }
    }

设置child的(即SurfaceView)的mPrivateFlags的值为View.REQUEST_TRANSPARENT_REGIONS,表示宿主窗口上设置透明区域。然后再调用当前ViewGroup的父容器的requestTransparentRegion()来继续向上请求设置透明区域,知道窗口的顶层视图,这就到了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();
        }
    }

在判断了线程是当前应用程序的主线程,以及传入的child正好是当前正在处理的ViewRootImpl所关联的顶层视图后,就设置当前顶层视图的mPrivateFlags的值为View.REQUEST_TRANSPARENT_REGIONS

由于窗口发生了变化,最后就调用了requestLayout()对窗口的UI进行重新布局和绘制,这又回到了performTraversals()方法这里。

可以看出,SurfaceView的透明区域的设置实在窗口的完成layout之后draw之前。

再来看ViewGroup.gatherTransparentRegion:

在视图容器下,会遍历每一个子视图,调用每个View的gatherTransparentRegion()方法来继续往下收集。

3、绘制SurfaceView

SurfaceView虽然具有独立的绘图表面,不过它仍然是宿主窗口的视图结构中的一个结点,因此,它仍然是可以参与到宿主窗口的绘制流程中去的。

每一个子视图的成员函数draw或者dispatchDraw都会被调用到,以便它们可以绘制自己的UI。

@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);
    }

这里有个判断,如果当前正在处理的SurfaceView不是用来作宿主窗口,即不是WindowManager.LayoutParams.TYPE_APPLICATION_PANEL的时候,SurfaceView类的成员函数draw只是简单地将它所占据的区域绘制为黑色。(这里就解释了为啥不给SurfaceView绘制Color,它是黑色的了)。

绘图的整个过程大致就是如下的顺序进行:

  1. 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。
  2. 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
  3. 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。

可以概述为:

SurfaceHolder sh = sv.getHolder();
Cavas canvas = sh.lockCanvas()
// Draw something on canvas
// ......
sh.unlockCanvasAndPost(canvas);

上面的代码即可以在主线程调用,也可以在独立的线程调用(上面的demo是在独立线程)。

results matching ""

    No results matching ""