SurfaceView淺析


什么是SurfaceView?Surface的意思是表層,表面的意思,那么SurfaceView就是指一個在表層的View對象。為什么 說是在表層呢,這是因為它有點特殊跟其他View不一樣,其他View是繪制在表層外,而它就是充當表層對象。假設你要在一個球上畫畫,那么球的表層就當 做你的畫布對象,你畫的東西會擋住它的表層,我們默認沒使用SurfaceView,那么球的表層就是空白的,如果我們使用了SurfaceView,我 們可以理解為我們拿來的球本身表面就具有紋路,你是畫再紋路之上的,如果你畫的是半透明的,那么你將可以透過你畫的東西看到球面本身的紋路。SDK的文檔 說到:SurfaceView就是在Window上挖一個洞,它就是顯示在這個洞里,其他的View是顯示在Window上,所以View可以顯式在 SurfaceView之上,你也可以添加一些層在SurfaceView之上。

SurfaceView還有其他的特性,上面我們講了它可以控制幀數,那它是什么控制的呢?這就需要了解它的使用機制。一般在很多游戲設計中,我們都是開辟一個后台線程計算游戲相關的數據,然后根據這些計算完的新數據再刷新視圖對象,由於對View執行繪制操作只能在UI線程上, 所以當你在另外一個線程計算完數據后,你需要調用View.invalidate方法通知系統刷新View對象,所以游戲相關的數據也需要讓UI線程能訪 問到,這樣的設計架構比較復雜,要是能讓后台計算的線程能直接訪問數據,然后更新View對象那改多好。我們知道View的更新只能在UI線程中,所以使 用自定義View沒辦法這么做,但是SurfaceView就可以了。它一個很好用的地方就是允許其他線程(不是UI線程)繪制圖形(使用Canvas),根據它這個特性,你就可以控制它的幀數,你如果讓這個線程1秒執行50次繪制,那么最后顯示的就是50幀。

 

如何使用SurfaceView?

首先SurfaceView也是一個View,它也有自己的生命周期。因為它需要另外一個線程來執行繪制操作,所以我們可以在它生命周期的初始化階 段開辟一個新線程,然后開始執行繪制,當生命周期的結束階段我們插入結束繪制線程的操作。這些是由其內部一個SurfaceHolder對象完成的。 SurfaceHolder,顧名思義,它里面保存了一個隊Surface對象的引用,而我們執行繪制方法就是操作這個 Surface,SurfaceHolder因為保存了對Surface的引用,所以使用它來處理Surface的生命周期,說到底 SurfaceView的生命周期其實就是Surface的生命周期,因為SurfaceHolder保存對Surface的引用,所以使用 SurfaceHolder來處理生命周期的初始化。

 

SurfaceView是視圖(View)的繼承類,這個視圖里內嵌了一個專門用於繪制的Surface。你可以控制這個Surface的格式和尺寸。Surfaceview控制這個Surface的繪制位置。
        surface是縱深排序(Z-ordered)的,這表明它總在自己所在窗口的后面。surfaceview提供了一個可見區域,只有在這個可見區域內 的surface部分內容才可見,可見區域外的部分不可見。surface的排版顯示受到視圖層級關系的影響,它的兄弟視圖結點會在頂端顯示。這意味者 surface的內容會被它的兄弟視圖遮擋,這一特性可以用來放置遮蓋物(overlays)(例如,文本和按鈕等控件)。注意,如果surface上面 有透明控件,那么它的每次變化都會引起框架重新計算它和頂層控件的透明效果,這會影響性能。
        你可以通過SurfaceHolder接口訪問這個surface,getHolder()方法可以得到這個接口。
        surfaceview變得可見時,surface被創建;surfaceview隱藏前,surface被銷毀。這樣能節省資源。如果你要查看 surface被創建和銷毀的時機,可以重載surfaceCreated(SurfaceHolder)和 surfaceDestroyed(SurfaceHolder)。
        surfaceview的核心在於提供了兩個線程:UI線程和渲染線程。這里應注意:
        1> 所有SurfaceView和SurfaceHolder.Callback的方法都應該在UI線程里調用,一般來說就是應用程序主線程。渲染線程所要訪問的各種變量應該作同步處理。
        2> 由於surface可能被銷毀,它只在SurfaceHolder.Callback.surfaceCreated()和 SurfaceHolder.Callback.surfaceDestroyed()之間有效,所以要確保渲染線程訪問的是合法有效的surface。

 

接下來呢,說說自己對它的理解
1、定義

可以直接從內存或者DMA等硬件接口取得圖像數據,是個非常重要的繪圖容器。

它的特性是:可以在主線程之外的線程中向屏幕繪圖上。這樣可以避免畫圖任務繁重的時候造成主線程阻塞,從而提高了程序的反應速度。在游戲開發中多用到SurfaceView,游戲中的背景、人物、動畫等等盡量在畫布canvas中畫出。

2、實現

首先繼承SurfaceView並實現SurfaceHolder.Callback接口
使用接口的原因:因為使用SurfaceView 有一個原則,所有的繪圖工作必須得在Surface 被創建之后才能開始(Surface—表面,這個概念在 圖形編程中常常被提到。基本上我們可以把它當作顯存的一個映射,寫入到Surface 的內容
                      可以被直接復制到顯存從而顯示出來,這使得顯示速度會非常快),而在Surface 被銷毀之前必須結束。所以Callback 中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理代碼的邊界。

需要重寫的方法

 (1)public void surfaceChanged(SurfaceHolder holder,int format,int width,int height){}

     //在surface的大小發生改變時激發

 (2)public void surfaceCreated(SurfaceHolder holder){}

     //在創建時激發,一般在這里調用畫圖的線程。

 (3)public void surfaceDestroyed(SurfaceHolder holder) {}

     //銷毀時激發,一般在這里將畫圖的線程停止、釋放。

整個過程:繼承SurfaceView並實現SurfaceHolder.Callback接口 ----> SurfaceView.getHolder()獲得SurfaceHolder對象 ---->SurfaceHolder.addCallback(callback)添加回調函數---->SurfaceHolder.lockCanvas()獲得Canvas對象並鎖定畫布----> Canvas繪畫 ---->SurfaceHolder.unlockCanvasAndPost(Canvas canvas)結束鎖定畫圖,並提交改變,將圖形顯示。


3、SurfaceHolder
這里用到了一個類SurfaceHolder,可以把它當成surface的控制器,用來操縱surface。處理它的Canvas上畫的效果和動畫,控制表面,大小,像素等。
幾個需要注意的方法:
(1)、abstract void addCallback(SurfaceHolder.Callback callback);
// 給SurfaceView當前的持有者一個回調對象。
(2)、abstract Canvas lockCanvas();
// 鎖定畫布,一般在鎖定后就可以通過其返回的畫布對象Canvas,在其上面畫圖等操作了。
(3)、abstract Canvas lockCanvas(Rect dirty);
// 鎖定畫布的某個區域進行畫圖等..因為畫完圖后,會調用下面的unlockCanvasAndPost來改變顯示內容。
// 相對部分內存要求比較高的游戲來說,可以不用重畫dirty外的其它區域的像素,可以提高速度。
(4)、abstract void unlockCanvasAndPost(Canvas canvas);
// 結束鎖定畫圖,並提交改變。
4、實例

這里的例子實現了一個矩形和一個計時器

 

package xl.test;

import android.app.Activity;
import android.content.Context;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.view.SurfaceHolder;
import android.view.SurfaceView;

public class ViewTest extends Activity {
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(new MyView(this));
    }
    //視圖內部類
    class MyView extends SurfaceView implements SurfaceHolder.Callback
    {
        private SurfaceHolder holder;
        private MyThread myThread; 
        public MyView(Context context) {
            super(context);
            // TODO Auto-generated constructor stub
            holder = this.getHolder();
            holder.addCallback(this);
            myThread = new MyThread(holder);//創建一個繪圖線程
        }

        @Override
        public void surfaceChanged(SurfaceHolder holder, int format, int width,
                int height) {
            // TODO Auto-generated method stub
            
        }

        @Override
        public void surfaceCreated(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            myThread.isRun = true;
            myThread.start();
        }

        @Override
        public void surfaceDestroyed(SurfaceHolder holder) {
            // TODO Auto-generated method stub
            myThread.isRun = false;
        }
        
    }
    //線程內部類
    class MyThread extends Thread
    {
        private SurfaceHolder holder;
        public boolean isRun ;
        public  MyThread(SurfaceHolder holder)
        {
            this.holder =holder; 
            isRun = true;
        }
        @Override
        public void run()
        {
            int count = 0;
            while(isRun)
            {
                Canvas c = null;
                try
                {
                    synchronized (holder)
                    {
                        c = holder.lockCanvas();//鎖定畫布,一般在鎖定后就可以通過其返回的畫布對象Canvas,在其上面畫圖等操作了。
                        c.drawColor(Color.BLACK);//設置畫布背景顏色
                        Paint p = new Paint(); //創建畫筆
                        p.setColor(Color.WHITE);
                        Rect r = new Rect(100, 50, 300, 250);
                        c.drawRect(r, p);
                        c.drawText("這是第"+(count++)+"秒", 100, 310, p);
                        Thread.sleep(1000);//睡眠時間為1秒
                    }
                }
                catch (Exception e) {
                    // TODO: handle exception
                    e.printStackTrace();
                }
                finally
                {
                    if(c!= null)
                    {
                        holder.unlockCanvasAndPost(c);//結束鎖定畫圖,並提交改變。

                    }
                }
            }
        }
    }
}

 

結來說,就是SurfaceView有以下三個特點:

 

       A. 具有獨立的繪圖表面;

        B. 需要在宿主窗口上挖一個洞來顯示自己;

        C. 它的UI繪制可以在獨立的線程中進行,這樣就可以進行復雜的UI繪制,並且不會影響應用程序的主線程響應用戶輸入。

什么是雙緩沖?

 

不用畫布,直接在窗口上進行繪圖叫做無緩沖繪圖。用了一個畫布,將所有內容都先畫到畫布上,在整體繪制到窗口上,就該叫做單緩沖繪圖,那個畫布就是一個緩沖區。用了兩個畫布,一個進行臨時的繪圖,一個進行最終的繪圖,這樣就叫做雙緩沖繪圖。

surfaceView自身實現了雙緩沖,而View沒有。其實view你也可以自己實現,但是實現的結構不如surfaceView好。

    surfaceView通過 surfaceHolder.lockCanvas 鎖定畫布,實現下一張圖片的繪制,再通過另外的線程刷新界面,繪制圖片。

    view則是直接在ondraw里繪制圖片,刷新界面。其實view也可以實現雙緩沖機制,你可以在另個出ondraw的方法中繪制下一張bitmap(參見:http://blog.csdn.net/liubingzhao/article/details/5563113),也可以另開一個線程,處理除了繪制圖片以外的操作(參見:http://topic.csdn.net/u/20110901/23/e283f805-20dc-40c3-8381-403dd1ca69b0.html),就實現了view的雙緩沖。

為什么動態繪圖surfaceView要比View好?

因為View是在UI主線程中進行繪制的,繪制時會阻塞主線程,如果ontouch事件又處理的比較多的話會導致界面卡。而surfaceView是另開了一個線程繪制的,再加上雙緩沖機制,所以要高效。不會卡。其實現在一般實現view的時候一般都會在其他出先生成bitmap在給ondraw去畫,所以雙緩沖的作用不是那么明顯了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM