Android视图SurfaceView的实现原理分析

 在 Android 系统 中,有一种特殊的视图,称为SurfaceView,它拥有独立的绘图表面,即它不与其宿主窗口共享同一个绘图表面。由于拥有独立的绘图表面,因此SurfaceView的UI就可以在一个独立的线程中进行行绘制。又由于不占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,另一方面又不会导致用户输入得不到及时响应。在本文中,我们就详细分析SurfaceView的实现原理。
老罗的新浪微博:http://weibo.com/shengyangluo,欢迎关注!
        在前面Android控件TextView的实现原理分析一文中提到,普通的Android控件,例如TextView、Button和CheckBox等,它们都是将自己的UI绘制在宿主窗口的绘图表面之上,这意味着它们的UI是在应用程序的主线程中进行绘制的。由于应用程序的主线程除了要绘制UI之外,还需要及时地响应用户输入,否则的话,系统就会认为应用程序没有响应了,因此就会弹出一个ANR对话框出来。对于一些游戏画面,或者摄像头预览、视频播放来说,它们的UI都比较复杂,而且要求能够进行高效的绘制,因此,它们的UI就不适合在应用程序的主线程中进行绘制。这时候就必须要给那些需要复杂而高效UI的视图生成一个独立的绘图表面,以及使用一个独立的线程来绘制这些视图的UI。
        在前面Android应用程序与SurfaceFlinger服务的关系概述和学习计划和Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系统的文章中,我们主要分析了Android应用程序窗口是如何通过SurfaceFlinger服务来绘制自己的UI的。一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer或者LayerBuffer,用来单独描述它的绘图表面,以区别于它的宿主窗口的绘图表面。
        无论是LayerBuffer,还是Layer,它们都是以LayerBase为基类的,也就是说,SurfaceFlinger服务把所有的LayerBuffer和Layer都抽象为LayerBase,因此就可以用统一的流程来绘制和合成它们的UI。由于LayerBuffer的绘制和合成与Layer的绘制和合成是类似的,因此本文不打算对LayerBuffer的绘制和合成操作进行分析。需要深入理解LayerBuffer的绘制和合成操作的,可以参考Android应用程序与SurfaceFlinger服务的关系概述和学习计划和Android系统Surface机制的SurfaceFlinger服务简要介绍和学习计划这两个系统的文章。
        为了接下来可以方便地描述SurfaceView的实现原理分析,我们假设在一个Activity窗口的视图结构中,除了有一个DecorView顶层视图之外,还有两个TextView控件,以及一个SurfaceView视图,这样该Activity窗口在SurfaceFlinger服务中就对应有两个Layer或者一个Layer的一个LayerBuffer,如图1所示:
图1 SurfaceView及其宿主Activity窗口的绘图表面示意图
         在图1中,Activity窗口的顶层视图DecorView及其两个TextView控件的UI都是绘制在SurfaceFlinger服务中的同一个Layer上面的,而SurfaceView的UI是绘制在SurfaceFlinger服务中的另外一个Layer或者LayerBuffer上的。
         注意,用来描述SurfaceView的Layer或者LayerBuffer的Z轴位置是小于用来其宿主Activity窗口的Layer的Z轴位置的,但是前者会在后者的上面挖一个“洞”出来,以便它的UI可以对用户可见。实际上,SurfaceView在其宿主Activity窗口上所挖的“洞”只不过是在其宿主Activity窗口上设置了一块透明区域。
        从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的具体实现过程,包括它的绘图表面的创建过程、在宿主窗口上面进行挖洞的过程,以及绘制过程。
        1. SurfaceView的绘图表面的创建过程
        由于SurfaceView具有独立的绘图表面,因此,在它的UI内容可以绘制之前,我们首先要将它的绘图表面创建出来。尽管SurfaceView不与它的宿主窗口共享同一个绘图表面,但是它仍然是属于宿主窗口的视图结构的一个结点的,也就是说,SurfaceView仍然是会参与到宿主窗口的某些执行流程中去。
        从前面Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析一文可以知道,每当一个窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals。ViewRoot类的成员函数performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建,或者发现当前窗口的绘图表面已经失效了,那么就会请求WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的SurfaceView有机会创建自己的绘图表面。
       接下来,我们就从ViewRoot类的成员函数performTraversals开始,分析SurfaceView的绘图表面的创建过程,如图2所示:
图2 SurfaceView的绘图表面的创建过程
        这个过程可以分为8个步骤,接下来我们就详细分析每一个步骤。
        Step 1. ViewRoot.performTraversals
[java]  
public final class ViewRoot extends Handler implements ViewParent,  
        View.AttachInfo.Callbacks {  
    ......  
  
    private void performTraversals() {  
        // cache mView since it is used so much below...  
        final View host = mView;  
        ......  
  
        final View.AttachInfo attachInfo = mAttachInfo;  
  
        final int viewVisibility = getHostVisibility();  
        boolean viewVisibilityChanged = mViewVisibility != viewVisibility  
                || mNewSurfaceNeeded;  
        ......  
  
  
        if (mFirst) {  
            ......  
  
            if (!mAttached) {  
                host.dispatchAttachedToWindow(attachInfo, 0);  
                mAttached = true;  
            }  
              
            ......  
        }  
   
        ......  
  
        if (viewVisibilityChanged) {  
            ......  
  
            host.dispatchWindowVisibilityChanged(viewVisibility);  
  
            ......  
        }  
  
        ......  
  
        mFirst = false;  
  
        ......  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/ViewRoot.java中。
        ViewRoot类的成员函数performTraversals的详细实现可以参考Android应用程序窗口(Activity)的绘图表面(Surface)的创建过程分析和Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析这两篇文章,这里我们只关注与SurfaceView的绘图表面的创建相关的逻辑。
        我们首先分析在ViewRoot类的成员函数performTraversals中四个相关的变量host、attachInfo、viewVisibility和viewVisibilityChanged。
        变量host与ViewRoot类的成员变量mView指向的是同一个DecorView对象,这个DecorView对象描述的当前窗口的顶层视图。
        变量attachInfo与ViewRoot类的成员变量mAttachInfo指向的是同一个AttachInfo对象。在Android系统中,每一个视图附加到它的宿主窗口的时候,都会获得一个AttachInfo对象,用来被附加的窗口的信息。
        变量viewVisibility描述的是当前窗口的可见性。
        变量viewVisibilityChanged描述的是当前窗口的可见性是否发生了变化。
        ViewRoot类的成员变量mFirst表示当前窗口是否是第一次被刷新UI。如果是的话,那么它的值就会等于true,说明当前窗口的绘图表面还未创建。在这种情况下,如果ViewRoot类的另外一个成员变量mAttached的值也等于true,那么就表示当前窗口还没有将它的各个子视图附加到它的上面来。这时候ViewRoot类的成员函数performTraversals就会从当前窗口的顶层视图开始,通知每一个子视图它要被附加到宿主窗口上去了,这是通过调用变量host所指向的一个DecorView对象的成员函数dispatchAttachedToWindow来实现的。DecorView类的成员函数dispatchAttachedToWindow是从父类ViewGroup继承下来的,在后面的Step 2中,我们再详细分析ViewGroup类的成员数dispatchAttachedToWindow的实现。
        接下来, ViewRoot类的成员函数performTraversals判断当前窗口的可见性是否发生了变化,即检查变量viewVisibilityChanged的值是否等于true。如果发生了变化,那么就会从当前窗口的顶层视图开始,通知每一个子视图它的宿主窗口的可见发生变化了,这是通过调用变量host所指向的一个DecorView对象的成员函数dispatchWindowVisibilityChanged来实现的。DecorView类的成员函数dispatchWindowVisibilityChanged是从父类ViewGroup继承下来的,在后面的Step 5中,我们再详细分析ViewGroup类的成员数dispatchWindowVisibilityChanged的实现。
        我们假设当前窗口有一个SurfaceView,那么当该SurfaceView接收到它被附加到宿主窗口以及它的宿主窗口的可见性发生变化的通知时,就会相应地将自己的绘图表面创建出来。接下来,我们就分别分析ViewGroup类的成员数dispatchAttachedToWindow和dispatchWindowVisibilityChanged的实现,以便可以了解SurfaceView的绘图表面的创建过程。 
         Step 2. ViewGroup.dispatchAttachedToWindow
[java] 
public abstract class ViewGroup extends View implements ViewParent, ViewManager {  
    ......  
  
    // Child views of this ViewGroup  
    private View[] mChildren;  
    // Number of valid children in the mChildren array, the rest should be null or not  
    // considered as children  
    private int mChildrenCount;  
    ......  
  
    @Override  
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {  
        super.dispatchAttachedToWindow(info, visibility);  
        visibility |= mViewFlags & VISIBILITY_MASK;  
        final int count = mChildrenCount;  
        final View[] children = mChildren;  
        for (int i = 0; i < count; i++) {  
            children[i].dispatchAttachedToWindow(info, visibility);  
        }  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。
        ViewGroup类的成员变量mChildren保存的是当前正在处理的视图容器的子视图,而另外一个成员变量mChildrenCount保存的是这些子视图的数量。
        ViewGroup类的成员函数dispatchAttachedToWindow的实现很简单,它只是简单地调用当前正在处理的视图容器的每一个子视图的成员函数dispatchAttachedToWindow,以便可以通知这些子视图,它们被附加到宿主窗口上去了。
        当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,由于前面我们当前正在处理的窗口有一个SurfaceView,因此这一步就会调用到该SurfaceView的成员函数dispatchAttachedToWindow。
        由于SurfaceView类的成员函数dispatchAttachedToWindow是从父类View继承下来的,因此,接下来我们就继续分析View类的成员函数dispatchAttachedToWindow的实现。
        Step 3. View.dispatchAttachedToWindow
[java] 
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
    ......  
  
    AttachInfo mAttachInfo;  
    ......  
  
    void dispatchAttachedToWindow(AttachInfo info, int visibility) {  
        //System.out.println("Attached! " + this);  
        mAttachInfo = info;  
        ......  
  
        onAttachedToWindow();  
  
        ......  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/View.java中。
        View类的成员函数dispatchAttachedToWindow首先将参数info所指向的一个AttachInfo对象保存在自己的成员变量mAttachInfo中,以便当前视图可以获得其所附加在的窗口的相关信息,接下来再调用另外一个成员函数onAttachedToWindow来让子类有机会处理它被附加到宿主窗口的事件。
        前面我们已经假设了当前处理的是一个SurfaceView。SurfaceView类重写了父类View的成员函数onAttachedToWindow,接下来我们就继续分析SurfaceView的成员函数onAttachedToWindow的实现,以便可以了解SurfaceView的绘图表面的创建过程。
        Step 4. SurfaceView.onAttachedToWindow
[java]  
public class SurfaceView extends View {  
    ......  
  
    IWindowSession mSession;  
    ......  
  
    @Override  
    protected void onAttachedToWindow() {  
        super.onAttachedToWindow();  
        mParent.requestTransparentRegion(this);  
        mSession = getWindowSession();  
        ......  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
        SurfaceView类的成员函数onAttachedToWindow做了两件重要的事。
        第一件事情是通知父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上挖一个洞,即需要在宿主窗口的绘图表面上设置一块透明区域。当前正在处理的SurfaceView的父视图保存在父类View的成员变量mParent中,通过调用这个成员变量mParent所指向的一个ViewGroup对象的成员函数requestTransparentRegion,就可以通知到当前正在处理的SurfaceView的父视图,当前正在处理的SurfaceView需要在宿主窗口的绘图表面上设置一块透明区域。在后面第2部分的内容中,我们再详细分析SurfaceView在宿主窗口的绘图表面的挖洞过程。
       第二件事情是调用从父类View继承下来的成员函数getWindowSession来获得一个实现了IWindowSession接口的Binder代理对象,并且将该Binder代理对象保存在SurfaceView类的成员变量mSession中。从前面Android应用程序窗口(Activity)实现框架简要介绍和学习计划这个系列的文章可以知道,在Android系统中,每一个应用程序进程都有一个实现了IWindowSession接口的Binder代理对象,这个Binder代理对象是用来与WindowManagerService服务进行通信的,View类的成员函数getWindowSession返回的就是该Binder代理对象。在接下来的Step 8中,我们就可以看到,SurfaceView就可以通过这个实现了IWindowSession接口的Binder代理对象来请求WindowManagerService服务为自己创建绘图表面的。
        这一步执行完成之后,返回到前面的Step 1中,即ViewRoot类的成员函数performTraversals中,我们假设当前窗口的可见性发生了变化,那么接下来就会调用顶层视图的成员函数dispatchWindowVisibilityChanged,以便可以通知各个子视图,它的宿主窗口的可见性发生化了。
        窗口的顶层视图是使用DecorView类来描述的,而DecroView类的成员函数dispatchWindowVisibilityChanged是从父类ViewGroup类继承下来的,因此,接下来我们就继续分析GroupView类的成员函数dispatchWindowVisibilityChanged的实现,以便可以了解包含在当前窗口里面的一个SurfaceView的绘图表面的创建过程。
        Step 5. ViewGroup.dispatchWindowVisibilityChanged
[java]  
public abstract class ViewGroup extends View implements ViewParent, ViewManager {   
    ......  
  
    @Override  
    public void dispatchWindowVisibilityChanged(int visibility) {  
        super.dispatchWindowVisibilityChanged(visibility);  
        final int count = mChildrenCount;  
        final View[] children = mChildren;  
        for (int i = 0; i < count; i++) {  
            children[i].dispatchWindowVisibilityChanged(visibility);  
        }  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/ViewGroup.java中。
        ViewGroup类的成员函数dispatchWindowVisibilityChanged的实现很简单,它只是简单地调用当前正在处理的视图容器的每一个子视图的成员函数dispatchWindowVisibilityChanged,以便可以通知这些子视图,它们所附加在的宿主窗口的可见性发生变化了。
        当前正在处理的视图容器即为当前正在处理的窗口的顶层视图,由于前面我们当前正在处理的窗口有一个SurfaceView,因此这一步就会调用到该SurfaceView的成员函数dispatchWindowVisibilityChanged。
        由于SurfaceView类的成员函数dispatchWindowVisibilityChanged是从父类View继承下来的,因此,接下来我们就继续分析View类的成员函数dispatchWindowVisibilityChanged的实现。
        Step 6. View.dispatchWindowVisibilityChanged
[java] 
public class View implements Drawable.Callback, KeyEvent.Callback, AccessibilityEventSource {  
    ......  
  
    public void dispatchWindowVisibilityChanged(int visibility) {  
        onWindowVisibilityChanged(visibility);  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/View.java中。
        View类的成员函数dispatchWindowVisibilityChanged的实现很简单,它只是调用另外一个成员函数onWindowVisibilityChanged来让子类有机会处理它所附加在的宿主窗口的可见性变化事件。
        前面我们已经假设了当前处理的是一个SurfaceView。SurfaceView类重写了父类View的成员函数onWindowVisibilityChanged,接下来我们就继续分析SurfaceView的成员函数onWindowVisibilityChanged的实现,以便可以了解SurfaceView的绘图表面的创建过程。
        Step 7. SurfaceView.onWindowVisibilityChanged
[java]  
public class SurfaceView extends View {  
    ......  
  
    boolean mRequestedVisible = false;  
    boolean mWindowVisibility = false;  
    boolean mViewVisibility = false;  
    .....  
  
    @Override  
    protected void onWindowVisibilityChanged(int visibility) {  
        super.onWindowVisibilityChanged(visibility);  
        mWindowVisibility = visibility == VISIBLE;  
        mRequestedVisible = mWindowVisibility && mViewVisibility;  
        updateWindow(false, false);  
    }  
  
    ......  
}  
        这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
        SurfaceView类有三个用来描述可见性的成员变量mRequestedVisible、mWindowVisibility和mViewVisibility。其中,mWindowVisibility表示SurfaceView的宿主窗口的可见性,mViewVisibility表示SurfaceView自身的可见性。只有当mWindowVisibility和mViewVisibility的值均等于true的时候,mRequestedVisible的值才为true,表示SurfaceView是可见的。
        参数visibility描述的便是当前正在处理的SurfaceView的宿主窗口的可见性,因此,SurfaceView类的成员函数onWindowVisibilityChanged首先将它记录在成员变量mWindowVisibility,接着再综合另外一个成员变量mViewVisibility来判断当前正在处理的SurfaceView是否是可见的,并且记录在成员变量mRequestedVisible中。
        最后,SurfaceView类的成员函数onWindowVisibilityChanged就会调用另外一个成员函数updateWindow来更新当前正在处理的SurfaceView。在更新的过程中,如果发现当前正在处理的SurfaceView还没有创建绘图表面,那么就地请求WindowManagerService服务为它创建一个。
        接下来,我们就继续分析SurfaceView类的成员函数updateWindow的实现,以便可以了解SurfaceView的绘图表面的创建过程。
        Step 8. SurfaceView.updateWindow
[java]  
public class SurfaceView extends View {  
    ......  
  
    final Surface mSurface = new Surface();  
    ......  
  
    MyWindow mWindow;  
    .....  
  
    int mWindowType = WindowManager.LayoutParams.TYPE_APPLICATION_MEDIA;  
    ......  
  
    int mRequestedType = -1;  
    ......  
  
    private void updateWindow(boolean force, boolean redrawNeeded) {  
        if (!mHaveFrame) {  
            return;  
        }  
        ......  
  
        int myWidth = mRequestedWidth;  
        if (myWidth <= 0) myWidth = getWidth();  
        int myHeight = mRequestedHeight;  
        if (myHeight <= 0) myHeight = getHeight();  
  
        getLocationInWindow(mLocation);  
        final boolean creating = mWindow == null;  
        final boolean formatChanged = mFormat != mRequestedFormat;  
        final boolean sizeChanged = mWidth != myWidth || mHeight != myHeight;  
        final boolean visibleChanged = mVisible != mRequestedVisible  
                || mNewSurfaceNeeded;  
        final boolean typeChanged = mType != mRequestedType;  
        if (force || creating || formatChanged || sizeChanged || visibleChanged  
            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]  
            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {  
            ......  
  
            try {  
                final boolean visible = mVisible = mRequestedVisible;  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值