1. 程式人生 > >Android Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之間的關係

Android Surface、SurfaceView、SurfaceHolder及SurfaceHolder.Callback之間的關係

最近學習Google的zxing掃碼,接觸到Surface、Surface和Viewfindview,這裡先記下Surface學習到的好東西

轉載:http://blog.csdn.net/pathuang68/article/details/7351317

一、Surface

Surface就是“表面”的意思。在SDK的文件中,對Surface的描述是這樣的:“Handle onto a raw buffer that is being managed by the screen compositor”,翻譯成中文就是“由螢幕顯示內容合成器(screen compositor)所管理的原生緩衝器的控制代碼”,這句話包括下面兩個意思:

1.      通過Surface(因為Surface是控制代碼)就可以獲得原生緩衝器以及其中的內容。就像在C語言中,可以通過一個檔案的控制代碼,就可以獲得檔案的內容一樣;

2.      原生緩衝器(rawbuffer)是用於儲存當前視窗的畫素資料的。

引伸地,可以認為Android中的Surface就是一個用來畫圖形(graphics)或影象(image)的地方。根據Java方面的常規知識,我們知道通常畫圖是在一個Canvas物件上面進行的,由此,可以推知一個Surface物件中應該包含有一個Canvas物件,事實上的確如此,而且這一點可以很容易通過debug執行程式的方式得到證明(將游標停留在物件變數surface上,會彈出一個對話方塊,其中紅色方框的內容,就表面surface中有一個CompatileCanvas成員變數)當然,看原始碼也是可以證明這一點:

因此,在前面提及的兩個意思的基礎上,可以再加上一條:

3.      Surface中有一個Canvas成員,專門用於畫圖的。

所以,Surface中的Canvas成員,是專門用於供程式設計師畫圖的場所,就像黑板一樣;其中的原生緩衝器是用來儲存資料的地方;Surface本身的作用類似一個控制代碼,得到了這個控制代碼就可以得到其中的Canvas、原生緩衝器以及其它方面的內容。

 

二、SurfaceView

SurfaceView,顧名思義就是Surface的View,通過SurfaceView就可以看到Surface的部分或者全部的內容,下面用一個圖來形象地描述一下Surface和SurfaceView的關係:

也就是說,Surface是用通過SurfaceView才能展示其中的內容。從這個意思上來說,SurfaceView中的View之確切的含義應該是viewport即“視口”的意思,做過資料庫設計的朋友知道,假定一個數據表有20個欄位,但我們常常只用到其中的5個欄位,那麼就可以在原資料表的基礎上,通過SQL語句CREATEVIEW來建立只包含那5個欄位內容的view。

另一方面,SurfaceView是Android中View的子類。事實上,在Android中所有用於介面展示的類皆為View的子類,包括那些不可見的、各種各樣的Layout。

所以說,SurfaceView中的View有兩個含義:

1.      視口(viewport)的意思

2.      SurfaceView是View的派生類

在Android中Surface是從Object派生而來,且實現了Parcelable介面。看到Parcelable就讓人能很自然地想到資料容器,SurfaceView就是用來展示Surface中的資料的。在這個層面上而言,Surface就是管理資料的地方,SurfaceView就是展示資料的地方。

 

三、SurfaceHolder

SurfaceHolder是一個介面,其作用就像一個關於Surface的監聽器。提供訪問和控制SurfaceView背後的Surface 相關的方法 (providingaccess and control over this SurfaceView's underlying surface),它通過三個回撥方法,讓我們可以感知到Surface的建立、銷燬或者改變。在SurfaceView中有一個方法getHolder,可以很方便地獲得SurfaceView所對應的Surface所對應的SurfaceHolder(有點拗口吧)。

 

除下面將要提到的SurfaceHolder.Callback外,SurfaceHolder還提供了很多重要的方法,其中最重要的就是:

1.        abstract void addCallback(SurfaceHolder.Callbackcallback)

為SurfaceHolder新增一個SurfaceHolder.Callback回撥介面。

2.        abstract Canvas lockCanvas()

獲取一個Canvas物件,並鎖定之。所得到的Canvas物件,其實就是Surface中一個成員。

3.        abstract Canvas lockCanvas(Rectdirty)

同上。但只鎖定dirty所指定的矩形區域,因此效率更高。

4.        abstract void unlockCanvasAndPost(Canvascanvas)

當修改Surface中的資料完成後,釋放同步鎖,並提交改變,然後將新的資料進行展示,同時Surface中相關資料會被丟失。

5.      public abstract void setType (int type)

         設定Surface的型別,接收如下的引數:

SURFACE_TYPE_NORMAL:用RAM快取原生資料的普通Surface

SURFACE_TYPE_HARDWARE:適用於DMA(Direct memory access )引擎和硬體加速的Surface

SURFACE_TYPE_GPU:適用於GPU加速的Surface

SURFACE_TYPE_PUSH_BUFFERS:表明該Surface不包含原生資料,Surface用到的資料由其他物件提供,在Camera影象預覽中就使用該型別的Surface,有Camera負責提供給預覽Surface資料,這樣影象預覽會比較流暢。如果設定這種型別則就不能呼叫lockCanvas來獲取Canvas物件了。需要注意的是,在高版本的Android SDK中,setType這個方法已經被depreciated了。

2、3、4中的同步鎖機制的目的,就是為了在繪製的過程中,Surface中的資料不會被改變。

從設計模式的高度來看,Surface、SurfaceView和SurfaceHolder實質上就是廣為人知的MVC,即Model-View-Controller。Model就是模型的意思,或者說是資料模型,或者更簡單地說就是資料,也就是這裡的Surface;View即檢視,代表使用者互動介面,也就是這裡的SurfaceView;SurfaceHolder很明顯可以理解為MVC中的Controller(控制器)。這樣看起來三者之間的關係就清楚了很多。

 

四、SurfaceHolder.Callback

前面已經講到SurfaceHolder是一個介面,它通過回到方法的方式,讓我們可以感知到Surface的建立、銷燬或者改變。其實這一點是通過其內部的靜態子介面SurfaceHolder.Callback來實現的。SurfaceHolder.Callback中定義了三個介面方法:

1.     abstract void surfaceChanged(SurfaceHolderholder, int format, int width, int height)

當surface發生任何結構性的變化時(格式或者大小),該方法就會被立即呼叫。

2.      abstract void surfaceCreated(SurfaceHolderholder)

當surface物件建立後,該方法就會被立即呼叫。

3.      abstract void surfaceDestroyed(SurfaceHolderholder)

當surface物件在將要銷燬前,該方法會被立即呼叫。

 

在Android SDK文件中,關於SurfaceView的描述裡面,有一段這樣的話:

One of the purposes of this class is to provide a surface in which a secondarythread can render into the screen. If you are going to use it this way, youneed to be aware of some threading semantics:

 

-        All SurfaceView and SurfaceHolder.Callbackmethods will be called from the thread running the SurfaceView's window(typically the main thread of the application). They thus need to correctlysynchronize with any state that is also touched by the drawing thread.

-      You must ensure that the drawingthread only touches the underlying Surface while it is valid -- betweenSurfaceHolder.Callback.surfaceCreated() andSurfaceHolder.Callback.surfaceDestroyed().

這段話很重要,大致意思如下:

這個類的目的之一,就是提供一個可以用另外一個執行緒(第二個執行緒)進行螢幕渲染的surface(譯註:即UI執行緒和繪製執行緒可以分離)。如果你打算這樣使用,那麼應當注意一些執行緒方面的語義:

-           所有SurfaceView和SurfaceHolder.Callback中宣告的方法,必須在執行SurfaceView視窗中的執行緒中呼叫(典型地,就是應用的主執行緒。譯註:即UI執行緒),因為它們需要正確地將同時被繪製執行緒訪問的各種狀態進行同步。

-           必須保證,只有在背後的Surface有效的時候 – 在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()這兩個方法呼叫之間,訪問它。

 

下面,我們通過一個非常簡單例子來實際感受一下(程式碼摘自http://www.cnblogs.com/xuling/archive/2011/06/06/android.html,並做了一些結構性的改動),請留意程式碼中的註釋:

1.        在Eclipse中建立一個Android Project專案TestSurfaceView,並選擇生成預設的Activity TestSurfaceViewActivity

2.        建立一個繪製執行緒如下:

package com.pat.testsurfaceview;
 
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
importandroid.view.SurfaceHolder;
 
// 繪製執行緒
public class MyThread extendsThread
{
         private SurfaceHolder holder;
         private boolean run;
        
         public MyThread(SurfaceHolder holder)
         {
                   this.holder = holder;
                   run = true;
         }
 
         @Override
         public void run()
         {
                   int counter = 0;
                   Canvas canvas = null;
                   while(run)
                   {
                            // 具體繪製工作
                            try
                            {
                                     // 獲取Canvas物件,並鎖定之
                                     canvas= holder.lockCanvas();
                                    
                                     // 設定Canvas物件的背景顏色
                                     canvas.drawColor(Color.WHITE);
                                    
                                     // 建立畫筆
                                     Paintp = new Paint();
                                     // 設定畫筆顏色
                                     p.setColor(Color.BLACK);
                                     // 設定文字大小
                                     p.setTextSize(30);
                                    
                                     // 建立一個Rect物件rect
                                     Rect rect = new Rect(100, 50, 380, 330);
                                     // 在canvas上繪製rect
                                     canvas.drawRect(rect,p);
                                     // 在canvas上顯示時間
                                     canvas.drawText("Interval = " + (counter++) + " seconds.", 100, 410, p);
                                     Thread.sleep(1000);
                            }
                            catch(Exception e)
                            {
                                     e.printStackTrace();
                            }
                            finally
                            {
                                     if(canvas != null)
                                     {
                                               // 解除鎖定,並提交修改內容
                                               holder.unlockCanvasAndPost(canvas);
                                     }
                            }
                   }
         }
 
         public boolean isRun()
         {
                   return run;
         }
        
         public void setRun(boolean run)
         {
                   this.run = run;
         }
}

3.      自定義一個SurfaceView類如下:

package com.pat.testsurfaceview;
 
import android.content.Context;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
 
public class MySurfaceView extends SurfaceView
implements
SurfaceHolder.Callback
{
         private SurfaceHolder holder;
         private MyThread myThread;
        
         publicMySurfaceView(Context context)
         {
                   super(context);
                  
                   // 通過SurfaceView獲得SurfaceHolder物件
                   holder = getHolder();
                  
                   // 為holder添加回調結構SurfaceHolder.Callback
                   holder.addCallback(this);
                  
                   // 建立一個繪製執行緒,將holder物件作為引數傳入,這樣在繪製執行緒中就可以獲得holder
                   // 物件,進而在繪製執行緒中可以通過holder物件獲得Canvas物件,並在Canvas上進行繪製
                   myThread = new MyThread(holder);
         }
 
         // 實現SurfaceHolder.Callback介面中的三個方法,都是在主執行緒中呼叫,而不是在繪製執行緒中呼叫的
         @Override
         public void surfaceChanged(SurfaceHolder holder, int format, int width, int height)
         {
         }
 
         @Override
         public void surfaceCreated(SurfaceHolder holder)
         {
                   // 啟動執行緒。當這個方法呼叫時,說明Surface已經有效了
                   myThread.setRun(true);
                   myThread.start();
         }
 
         @Override
         public void surfaceDestroyed(SurfaceHolderholder)
         {
                   // 結束執行緒。當這個方法呼叫時,說明Surface即將要被銷燬了
                   myThread.setRun(false);
         }
}


 
4.    修改TestSurfaceViewActivity.java程式碼,使之如下:

package com.pat.testsurfaceview;
 
import android.app.Activity;
import android.os.Bundle;
 
public class TestSurfaceViewActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);
        //setContentView(R.layout.main);
        setContentView(new MySurfaceView(this));
    }
}
 


執行結果:

很顯然,我們可以在MyThread的run方法中,做很多更有意思的事情。弄清楚了Surface、SurfaceView、SurfaceHolder和SurfaceHolder.Callback這些概念,以及它們之間的關係,對我們更好地使用它們應該會有相當大的幫助。