Android SurfaceView 分析及 C/C++通过surface绘制UI

         Android 系统提供一种特殊的视图,称为SurfaceView,它是View的之类。与普通View不同的是SurfaceView拥有独立的绘图层,可以在主线程之外的线程中向屏幕绘图,由于不占用主线程资源,SurfaceView一方面可以实现复杂而高效的UI,在游戏画面、视频播放中都有应用。

       linux 平台上的应用绘图一般是操作系统的framebuffer来实现OSD显示,如果C/C++层直接通过SurfaceFlinger来申请surface,该应用绘图就脱离了Android 窗口管理,将无法管理Z order。由于SurfaceView具有独立绘图层,它为移植一下linux平台上需要操作图形成的应用提供了可能性。本文将介绍SurfaceView的基本原理及如何通过native 语言(C/C++)来实现绘图。

        一般来说,每一个窗口在SurfaceFlinger服务中都对应有一个Layer,用来描述它的绘图表面。对于那些具有SurfaceView的窗口来 说,每一个SurfaceView在SurfaceFlinger服务中还对应有一个独立的Layer,用来单独描述它的绘图 表面,以区别于它的宿主窗口的绘图表面,同时layer的Z序由android 窗口系统管理。下图是dump出SurfaceFlinger信息摘录,应用程序窗口layer  Layer 0x41873008 (com.hybroad.launcher/com.hybroad.launcher.activity.MainActivity),同时存在SurfaceView 的Layer,该layer是具有内存空间的,可通过操作这块空间来绘制图形。
      
+  Layer 0x413f7008 (SurfaceView)
  Region transparentRegion (this=0x413f716c, count=1)
    [ 0, 0, 0, 0]
 Region visibleRegion (this=0x413f7010, count=1)
    [ 0, 0, 0, 0]
  layerStack= 0, z= 21010, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff,  flags=0x00000000,   tr=[1.00, 0.00][0.00, 1.00]
client=0x41879050
format= 4, activeBuffer=[ 0x 0: 0, 0], queued-frames=0, mRefreshPending=0
mTexName=10 mCurrentTexture=-1 CurrentCrop=[-1,0,0,0] mCurrentTransform=0   mAbandoned=0
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=4, transform-hint=00, FIFO(0)={}
             [00] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
             [01] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
             [02] state=FREE , crop=[0,0,-1,-1], xform=0x00, time=0, scale=FREEZE
+ Layer 0x41873008 (com.hybroad.launcher/com.hybroad.launcher.activity.MainActivity)
  Region transparentRegion (this=0x4187316c, count=1)
    [ 0, 0, 0, 0]
  Region visibleRegion (this=0x41873010, count=1)
    [ 0, 0, 1920, 1080]
 layerStack= 0, z= 21015, pos=(0,0), size=(1920,1080), crop=( 0, 0,1920,1080), isOpaque=0, invalidate=0, alpha=0xff, flags=0x00000000, tr=[1.00, 0.00][0.00, 1.00]
client=0x41879050
format= 1, activeBuffer=[1920x1080:1920, 1], queued-frames=0, mRefreshPending=0 mTexName=9 mCurrentTexture=2 mCurrentCrop=[0,0,0,0] mCurrentTransform=0 mAbandoned=0
-BufferQueue maxBufferCount=3, mSynchronousMode=1, default-size=[1920x1080], default-format=1, transform-hint=00, FIFO(0)={}
             [00] state=FREE , crop=[0,0,0,0], xform=0x00, time=0xyix26ea6e1d862, scale=FREEZE, 0x41417d60 [1920x1080:1920, 1]
             [01] state=FREE , crop=[0,0,0,0], xform=0x00, time=0x26eb122a85d, scale=FREEZE, 0x400d4300 [1920x1080:1920, 1]
            >[02] state=ACQUIRED, crop=[0,0,0,0], xform=0x00, time=0x26f007b9884, scale=FREEZE, 0x413f9f88 [1920x1080:1920, 1]

           上述APP窗口的布局中存在一个SurfaceView 及Android 其他控件如:textView 、imageView。这样该Activity 窗口在surfaceFlinger中对应两个Layer,surfaceView具有一层layer,其余控件均与DecorView顶层视图共用另一Layer。surfaceview 对应的Layer在java层的标识就是SurfaceView中的成员surface。

         从总体上描述了SurfaceView的大致实现原理之后,接下来我们就详细分析它的使用方法,包括surface层的创建过程,绘图过程及通过本地语言绘图的实现方法。 
         1. SurfaceView 中surface的创建。
         当一个Android窗口需要刷新UI时,就会调用ViewRoot类的成员函数performTraversals。ViewRoot类的成员函数 performTraversals在执行的过程中,如果发现当前窗口的绘图表面还没有创建,或者发现当前窗口的绘图表面已经失效了,那么就会请求 WindowManagerService服务创建一个新的绘图表面,同时,它还会通过一系列的回调函数来让嵌入在窗口里面的SurfaceView有机会创建自己的绘图层。过程如图2所示,共有8个步骤。这里假定读者对Android 窗口管理有一定了解,本地不对SurfaceView如何关联到窗口系统中去做描述,只关注surfaceView中surface的创建。       
                                            图1  SurfaceView 的创建
        如图1中描述,SurfaceView最终会通过成员函数updateWindow来更新当前正在处理的SurfaceView。在更新的过程中,如果发现当前正在处理的SurfaceView还没有创建绘图表面,那么就地请求WindowManagerService服务为它创建一个。
         
 private void updateWindow(boolean force, boolean redrawNeeded) {
   if (!mHaveFrame) {
            return;
        }
   ... ...
  
  if (force || creating || formatChanged || sizeChanged || visibleChanged
            || typeChanged || mLeft != mLocation[0] || mTop != mLocation[1]
            || mUpdateWindowNeeded || mReportDrawNeeded || redrawNeeded) {
     ... ...
        mSurfaceLock.lock();
                try {
                  ... ...
    
                    relayoutResult = mSession.relayout(
                        mWindow, mWindow.mSeq, mLayout, mWidth, mHeight,
                            visible ? VISIBLE : GONE,
                            WindowManagerGlobal.RELAYOUT_DEFER_SURFACE_DESTROY,
                            mWinFrame, mContentInsets,
                            mVisibleInsets, mConfiguration, mNewSurface);
                  ... ... 
                } finally {
                    mSurfaceLock.unlock();
                }
             
                try {
                    redrawNeeded |= creating | reportDrawNeeded
                     ... ...
                    if (mSurfaceCreated && (surfaceChanged || (!visible && visibleChanged))) {
                        mSurfaceCreated = false;
                        if (mSurface.isValid()) {
                           ... ...
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceDestroyed(mSurfaceHolder);
                            }
                        }
                    }
                      
                    if (visible && mSurface.isValid()) {
                        if (!mSurfaceCreated && (surfaceChanged || visibleChanged)) {
                          ... ...
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceCreated(mSurfaceHolder);
                            }
                        }

                        if (creating || formatChanged || sizeChanged
                                || visibleChanged || realSizeChanged) {
                          ... ...
                            for (SurfaceHolder.Callback c : callbacks) {
                                c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight);
                            }
                        }
                        ... ...
                  } finally {
                   ... ...
                }
            } catch (RemoteException ex) {
            }
        ... ... 
   }
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。
        从updateWindow方法中可以看到,在SurfaceView第一次增加到WindowManagerService服务中去后会通过成员变量mSession所描述的一个Binder代理对象的成员函数relayout来请求WindowManagerService服务对SurfaceView的UI进行布局。布局过程中如果发现窗口的绘制surface还未创建,或者需要需要重新创建,那么就会为请求SurfaceFlinger服务为该窗口创建一个新的绘图surface,并且将该绘图surface返回来给调用者。代码段中的mSurface就是SurfaceView类的成员变量,为该SurfaceView的绘图对象,也是本文描述的C/C++绘图将要操作的图层在java层的标示。
      
2. SurfaceView 中surface的使用。
      2.1 java 层的绘图过程
         UI 绘图包括下面三个步骤
         (1). 在绘图surface上建立一块画布,即获得一个Canvas对象。

         (2). 利用Canvas类提供的绘图接口在前面获得的画布上绘制任意的UI。

         (3). 将已经填充好了UI数据的画布缓冲区提交给SurfaceFlinger服务,以便SurfaceFlinger服务可以它合成到显示屏幕上去。SurfaceView提供了一个SurfaceHolder接口,通过SurfaceHolder 可以完成上述绘图流程。
代码示例:
             
SurfaceView sv = (SurfaceView )findViewById(R.id.sv);  
sfh =sv.getHolder();
.... ...
private void draw() {
try {
 //  步骤1
canvas = sfh.lockCanvas(); // 得到一个canvas实例  
//  步骤2
canvas.drawColor(Color.WHITE);// 刷屏                 
canvas.drawText("test", 100, 100, paint);// 画文字文本
} catch (Exception ex) {
} finally { 
 // 步骤3
if (canvas != null)
 sfh.unlockCanvasAndPost(canvas); // 将画好的画布提交
}
}

    SurfaceHolder 代码段如下,从中可以看到通过SurfaceHolder 来获取canvas其实就是从上surface上获取一块画图区域。
 private SurfaceHolder mSurfaceHolder = new SurfaceHolder() {
        ... ...
        public void addCallback(Callback callback) {
         ... ...
        }
        public void removeCallback(Callback callback) {
          ... ...
        }
        ... ... 
        public Canvas lockCanvas(Rect dirty) {
            return internalLockCanvas(dirty);
        }
        private final Canvas internalLockCanvas(Rect dirty) {
            mSurfaceLock.lock();
            ... ...
              if (!mDrawingStopped && mWindow != null) {
                ... ... 
                try {
                    c = mSurface.lockCanvas(dirty);
                } catch (Exception e) {
                    Log.e(LOG_TAG, "Exception locking surface", e);
                }
            }


            mSurfaceLock.unlock();
           
            return null;
        }
        public void unlockCanvasAndPost(Canvas canvas) {
            mSurface.unlockCanvasAndPost(canvas);
            mSurfaceLock.unlock();
        }
        public Surface getSurface() {
            return mSurface;
        }
        public Rect getSurfaceFrame() {
            return mSurfaceFrame;
        }
    };
}
这个函数定义在文件frameworks/base/core/java/android/view/SurfaceView.java中。

 2.2  C/C++中使用surface绘图
         C/C++中要在绘制图形是需要拿到一个绘图层surface,由它来提供填充数据的buffer,如果直接通过请求SurfaceFlinger服务来创建surface,我们将无法通过窗口系统来管理Z order。而通过SurfaceView 可以很好的解决这个问题,获取到SurfaceView中的surface,通过JNI(JNI 方法参考 Android JNI 介绍)的方式传递到Native层,C/C++便可以很方便地再该图层上绘制图形。
        从updateWindow方法中可以看到,当surface状态变化的时候都会通过callback来通知,当surface创建完成会调用 c.surfaceCreated(mSurfaceHolder),当surface大小变化时会通过 c.surfaceChanged(mSurfaceHolder, mFormat, myWidth, myHeight)来通知。因此可以当surface创建时可以通过callback获取到SurfaceHolder,SurfaceHolder提供方法getSurface可以拿到。
        SurfaceView的图层,将改surface 设置到native层即可。获取Surface的示例代码如下:

 SurfaceHolder  mSurfaceHolder = getHolder();  
     mSurfaceHolder.addCallback(this); 
     @Override  
     public void surfaceChanged(SurfaceHolder arg0, int arg1, int arg2,  
    int arg3) {  
    // surfaceView的大小发生改变的时候        
    }   
     @Override  
    public void surfaceCreated(SurfaceHolder arg0) { 
       // 创建surface后可以通过SurfaceHolder 来获取该surface,并根据需要设置位图格式
       // 将这个localSurface通过JNI可以提供给Native代码绘图使用
      Surface localSurface = arg0.getSurface();
      arg0.setFormat(PixelFormat.RGBA_8888); 
      }  
     @Override  
     public void surfaceDestroyed(SurfaceHolder arg0) {  
     // surfaceView销毁的时候  
  
    } 

       再回过头来看java层UI绘制的三个步骤。从图2中可以看出,java层的操作与native层是一一对应。通过通过native层的surface 的lock方法获取到绘图buffer,通过surface的unlockAndPost方法将填充好的数据提交给SurfaceFlinger来合成显示。

 
                                                       图2: surface绘制UI流程




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值