SurfaceView源码分析

基于 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绘制,并且不会影响应用程序的主线程响应用户输入。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,123评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,031评论 2 384
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,723评论 0 345
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,357评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,412评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,760评论 1 289
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,904评论 3 405
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,672评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,118评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,456评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,599评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,264评论 4 328
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,857评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,731评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,956评论 1 264
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,286评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,465评论 2 348

推荐阅读更多精彩内容