SurfaceView源码分析

作者: 秋阳君 | 来源:发表于2018-11-22 11:41 被阅读4次

    基于 android-27源码
    https://blog.csdn.net/Luoshengyang/article/details/8661317

    1. 什么是SurfaceView,和普通的View的区别?

    1. View适用于主动更新的情况,而SurfaceView则适用于被动更新的情况,比如频繁刷新界面。
    2. View在主线程中对页面进行刷新,而SurfaceView则开启一个子线程来对页面进行刷新。
    3. View在绘图时没有实现双缓冲机制,SurfaceView在底层机制中就实现了双缓冲机制。(当一个动画争先显示时,程序又在改变它,前面还没有显示完,程序又请求重新绘制,这样屏幕就会不停地闪烁。而双缓冲技术是把要处理的图片在内存中处理好之后,再将其显示在屏幕上。双缓冲主要是为了解决 反复局部刷屏带来的闪烁。把要画的东西先画到一个内存区域里,然后整体的一次性画出来。)

    2. SurfaceView的绘制原理

    Android应用程序窗口是通过SurfaceFlinger服务来绘制自己的UI。一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。


    image

    3. SurfaceView的绘制过程

    image

    SurfaceView的绘图表面的创建过程从ViewRoot类的成员函数performTraversals开始

    3.1 ViewRoot.performTraversals

    host = ViewRoot.mView 指向DecorView对象,描述当前窗口的顶级视图
    attachInfo = ViewRoot.mAttachInfo 指向的AttachInfo是描述串口信息对象
    这个方法的主要作用是判断绘图表面是否创建,通知View(ViewGroup)附加到窗口 3.2 ,判断并通知当前窗口的可见性是否变化 3.5

    3.2 ViewGroup.dispatchAttachedToWindow() (DecorView ViewGroup)

    遍历通知子视图添加到窗口 3.3

    3.3 View.dispatchAttachedToWindow() (View)

    保存窗口信息mAttachInfo,调用子类的onAttachedToWindow 3.4

    3.4 SurfaceView.onAttachedToWindow() (SurfaceView)

    主要做了两件事:
    1 通知父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上挖一个洞,即需要在宿主窗口的绘图表面上设置一块透明区域。 待更新
    2 调用从父类View继承下来的成员函数getWindowSession()来获得一个实现了IWindowSession接口的Binder代理对象. mSession指向这个对象.主要是想通过binder请求绘制绘图表面.

    3.5 ViewGroup.dispatchWindowVisibilityChanged() (DecorView ViewGroup)

    遍历设置子View的可见性 3.6

    3.6 View.dispatchWindowVisibilityChanged() (View)

    调用成员函数onWindowVisibilityChanged()来让子类处理可见性变化

    3.7 SurfaceView.onWindowVisibilityChanged() (SurfaceView)

    设置当前SurfaceView的可见性,调用3.8方法,更新视图,如果还没有绘制SurfaceView,就请求绘制

    3.8 SurfaceView.updateWindow

    重要的成员变量解释:
    mSurface:指向特定的绘图表面,其他的View的绘图表面是共享的,SurfaceView的是特有的;
    mWindow:指向MyWindow对象,每个SurfaceView都关联了一个实现了IWindow接口的Binder本地对象
    mWindowType:描述SurfaceView的窗口类型,默认是显示多媒体的类型,可通过窗口设置层级,比如media的在下面,media_overlate的在上面;也可以通过修改setZXXX()的值来提升在Z轴的显示层级
    mRequestedType:绘图表面类型 Layer or LayerBuffer 对应的在SurfaceFlinger的内存分配也不一致.

    3.8.1 绘图表面的创建过程:
    1. 判断并准备宿主窗口

    2. 获得SurfaceView宽高

    3. 更新记录SurfaceView的绘制信息,可见性、位置、大小、绘图表面像素格式和类型等等 image
    4. 检查成员变量mWindow的值是否等于null,相当于检测是否添加到WindowManagerService服务

    5. 调用成员变量mSession所描述的一个Binder代理对象的成员函数relayout来请求WindowManagerService服务对SurfaceView的UI进行布局

    3.8.2 SurfaceView的挖洞过程:
    image
    1. SurfaceView -- SurfaceView.onAttachedToWindow
      调用mParent.requestTransparentRegion(SurfaceView.this);来请求在宿主窗口挖洞;
    2. ViewGroup -- requestTransparentRegion()
      设置标志位mPrivateFlags为顶层绘制透明窗口,调用mParent(ViewRoot)的方法;
    3. ViewRootImp -- requestTransparentRegion()
      检查线程(为非主线程),检查viewRoot指向的对象和传入的参数是否是同个对象;设置标志位,调requestLayout()开始刷新窗口;
    4. ViewRootImp -- performTraversals()
      在窗口的UI布局完成之后,并且在窗口的UI绘制之前,收集嵌入在它里面的SurfaceView所设置的透明区域的,这样子View的大小和位置才能确定;
    5. ViewGroup -- gatherTransparentRegion() 挖洞过程
      5.1 调用父类View的成员函数gatherTransparentRegion来检查当前正在处理的视图容器是否需要绘制。
      5.2 遍历子类的gatherTransparentRegion来继续往下收集透明区域。
    6. SurfaceView -- gatherTransparentRegion()
      假设当前正在处理的SurfaceView不是用作窗口面板,并且也是不需要在宿主窗口的绘图表面上进行绘制的,而参数region的值又不等于null,那么SurfaceView类的成员函数gatherTransparentRegion就会先计算好当前正在处理的SurfaceView所占据的区域,然后再将该区域添加到参数region所描述的区域中去,这样就可以得到窗口的一个新的透明区域。
    3.8.3 SurfaceView的绘制过程:
    image.png

    如果要在一个绘图表面进行UI绘制,那么就顺序执行以下的操作:
    (1). 在绘图表面的基础上建立一块画布,即获得一个Canvas对象。
    (2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。
    (3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以将它合成到屏幕上去。
    SurfaceView提供了一个SurfaceHolder接口,通过这个SurfaceHolder接口就可以执行第(1)和引(3)个操作;

    SurfaceView sv = (SurfaceView )findViewById(R.id.surface_view);
    SurfaceHolder sh = sv.getHolder();
    Cavas canvas = sh.lockCanvas()
    //Draw something on canvas
    ......
    sh.unlockCanvasAndPost(canvas);
    
    1. SurfaceView.getHolder
      获得SurfaceHolder对象
    2. SurfaceHolder.lockCanvas
      因为SurfaceView是在子线程执行绘制的,画布的绘制过程不是线程安全的,所以在绘制的时候需要对当前的绘图表面进行锁保护--mSurfaceLock;
    3. Surface.lockCanvas
      通过JNI方法来在当前正在处理的绘图表面上获得一个图形缓冲区,并且将这个图形绘冲区封装在一块类型为Canvas的画布中返回给调用者使用。
    mLockedObject = nativeLockCanvas(mNativeObject, mCanvas, inOutDirty);
    

    这样就可以开始在mCanvas中绘制

    1. SurfaceHolder.unlockCanvasAndPost
      SurfaceHolder类的成员函数unlockCanvasAndPost再调用当前正在处理的SurfaceView的成员变量mSurfaceLock所指向的一个ReentrantLock对象的成员函数unlock来解锁当前正在处理的SurfaceView的绘图表面
    2. Surface.unlockCanvasAndPost
    mHwuiContext.unlockAndPost(canvas);
    

    将在前面的Step 3中所获得的一个图形缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以在合适的时候将该图形缓冲区合成到屏幕上去显示,这样就可以将对应的SurfaceView的UI展现出来了;绘制后释放锁

    总结:

    SurfaceView有以下三个特点:
    A. 具有独立的绘图表面;
    B. 需要在宿主窗口上挖一个洞来显示自己;
    C. 它的UI绘制可以在独立的线程中进行,这样就可以进行复杂的UI绘制,并且不会影响应用程序的主线程响应用户输入。

    相关文章

      网友评论

        本文标题:SurfaceView源码分析

        本文链接:https://www.haomeiwen.com/subject/ufnmxqtx.html