Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android游戲 >> Android游戲開發 >> Android游戲開發19:SurfaceView運行機制剖析

Android游戲開發19:SurfaceView運行機制剖析

編輯:Android游戲開發

       有不少朋友都遇到過這種問題,程序執行時切換到後台,然後再重新進入會報異常,本文就這種問題全面講解下SurfaceView的運行機制,了解了這些原理你就能自己解決這些問題了。

       我們通常會通過單擊HOME按鍵或返回按鍵等操作切換到後台,之後可能會再次進入程序,這個時候就有可能報異常。這裡SurfaceView可能報的異常主要有兩點,如下:

       一、提交畫布異常。如下圖(模擬器錯誤提示,以及Logcat Detail)

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

Java代碼
  1. public void draw() {   
  2.     try {   
  3.         canvas = sfh.lockCanvas();   
  4.         if (canvas != null) {   
  5.             canvas.drawColor(Color.WHITE);   
  6.             canvas.drawBitmap(bmp, bmp_x, bmp_y, paint);   
  7.         }   
  8.     } catch (Exception e) {   
  9.         Log.v("Himi", "draw is Error!");   
  10.     } finally {//備注1   
  11.         if (canvas != null)//備注2   
  12.             sfh.unlockCanvasAndPost(canvas);   
  13.     }   
  14. }  

       先看備注1這裡,之前的文章中我給大家解釋過為什麼要把 sfh.unlockCanvasAndPost(canvas); 寫在finally中,主要是為了保證能正常的提交畫布。

       今天主要說說備注2,這裡一定要判定下canvas是否為空,因為當程序切入後台的時候,canvas是獲取不到的!那麼canvas一旦為空,提交畫布這裡就會出現參數異常的錯誤!

       二、線程啟動異常。如下圖(模擬器錯誤提示,以及Logcat Detail)

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

       這種異常只是在當你程序運行期間點擊Home按鈕後再次進入程序的時候報的異常,異常說咱們的線程已經啟動!為什麼返回按鈕就沒事?

       OK,下面我們就要來先詳細講解一下Android中Back和Home按鍵的機制!然後分析問題,並且解決問題!

       先看下面MySurfaceViewAnimation.java的類中的代碼:

Java代碼
  1. public class MySurfaceViewAnimation extends SurfaceView implements Callback, Runnable {   
  2.     private Thread th;   
  3.     private SurfaceHolder sfh;   
  4.     private Canvas canvas;   
  5.     private Paint paint;   
  6.     private Bitmap bmp;   
  7.     private int bmp_x, bmp_y;   
  8.     public MySurfaceViewAnimation(Context context) {   
  9.         super(context);   
  10.         this.setKeepScreenOn(true);   
  11.         bmp = BitmapFactory.decodeResource(getResources(), R.drawable.himi_dream);   
  12.         sfh = this.getHolder();   
  13.         sfh.addCallback(this);   
  14.         paint = new Paint();   
  15.         paint.setAntiAlias(true);   
  16.         this.setLongClickable(true);   
  17.         th = new Thread(this, "himi_Thread_one");   
  18.         Log.e("Himi", "MySurfaceViewAnimation");   
  19.     }   
  20.     public void surfaceCreated(SurfaceHolder holder) {   
  21.         th.start();   
  22.         Log.e("Himi", "surfaceCreated");   
  23.     }   
  24.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {   
  25.         Log.e("Himi", "surfaceChanged");   
  26.     }   
  27.     public void surfaceDestroyed(SurfaceHolder holder) {   
  28.         Log.e("Himi", "surfaceDestroyed");   
  29.     }   
  30.     public void draw() {   
  31.         try {   
  32.             canvas = sfh.lockCanvas();   
  33.             if (canvas != null) {   
  34.                 canvas.drawColor(Color.WHITE);   
  35.                 canvas.drawBitmap(bmp, bmp_x, bmp_y, paint);   
  36.             }   
  37.         } catch (Exception e) {   
  38.             Log.v("Himi", "draw is Error!");   
  39.         } finally {//備注1   
  40.             if (canvas != null)//備注2   
  41.                 sfh.unlockCanvasAndPost(canvas);   
  42.         }   
  43.     }   
  44.     public void run() {   
  45.         while (true) {   
  46.             draw();   
  47.             try {   
  48.                 Thread.sleep(100);   
  49.             } catch (Exception ex) {   
  50.             }   
  51.         }   
  52.     }   
  53. }  

       以上是我們常用的自定義SurfaceView,並且使用Runnable接口老框架了不多說了,其中我在本類的構造、創建、狀態改變、消亡函數都加上打印!

       OK,下面看第一張圖:(剛運行程序)

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

       上圖的左邊部分是Dubug。這裡顯示我們有一條線程在運行,名字叫”himi_Thread_one”。

       上圖的右邊部分是LogCat日志。大家很清晰的看到,當第一次進入程序的時候,會先進入view構造函數、然後是創建view,然後是view狀態改變,OK,這個大家都知道!

       下面是我來點擊Home(手機上的小房子)按鍵,這時程序處於後台,然後重新進入程序的過程!

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

       上圖可以看出我們的線程還是一條,這裡主要觀察從點擊home到再次進入程序的過程,如下所述:

       點擊home 調用了view銷毀,然後進入程序會先進入view創建,最後是view狀態改變。

       上面的過程很容易理解,重要的角色上場了~Back 按鈕!點我點擊Back按鈕看看發生了什麼!

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

       先看左邊的Debug一欄,多了一條線程! 看LogCat發現比點擊Home按鍵多調用了一次構造函數!

       好了,從我們測試的程序來看,無疑,點擊Home 和 點擊 Back按鈕再次進入程序的時候,步驟是不一樣的,線程數量也變了!

       那麼這裡就能解釋為什麼我們點擊Back按鈕不異常,點擊Home會異常了!

       原因:因為點擊Back按鈕再次進入程序的時候先進入的是view構造函數裡,那麼就是說這裡又new了一個線程出來,並啟動!那麼而我們點擊Home卻不一樣了,因為點擊home之後再次進入程序不會進入構造函數,而是直接進入了view創建這個函數,而在view創建這個函數中我們有個啟動線程的操作,其實第一次啟動程序的線程還在運行,so~這裡就一定異常了,說線程已經啟動!

       有些童鞋會問,我們為何不把th = new Thread(this, “himi_Thread_one”);放在view創建函數中不就好了?!

       沒錯,可以!但是當你反復幾次之後你發現你的程序中會多出很多條進程!(如下圖)

Android游戲開發19:SurfaceView運行機制剖析--處理切換到後台再重新進入程序時的異常

       雖然可以避免出現線程已經啟動的異常,很明顯這不是我們想要的結果!

       那麼下面給大家介紹最合適的解決方案:

       修改MySurfaceViewAnimation.java:

Java代碼
  1. public class MySurfaceViewAnimation extends SurfaceView implements Callback, Runnable {   
  2.     private Thread th;   
  3.     private SurfaceHolder sfh;   
  4.     private Canvas canvas;   
  5.     private Paint paint;   
  6.     private Bitmap bmp;   
  7.     private int bmp_x, bmp_y;   
  8.     private boolean himi; //備注1   
  9.     public MySurfaceViewAnimation(Context context) {   
  10.         super(context);   
  11.         this.setKeepScreenOn(true);   
  12.         bmp = BitmapFactory.decodeResource(getResources(), R.drawable.himi_dream);   
  13.         sfh = this.getHolder();   
  14.         sfh.addCallback(this);   
  15.         paint = new Paint();   
  16.         paint.setAntiAlias(true);   
  17.         this.setLongClickable(true);   
  18.         Log.e("Himi", "MySurfaceViewAnimation");   
  19.     }   
  20.     public void surfaceCreated(SurfaceHolder holder) {   
  21.         himi = true;   
  22.         th = new Thread(this, "himi_Thread_one");//備注2   
  23.         th.start();   
  24.         Log.e("Himi", "surfaceCreated");   
  25.     }   
  26.     public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {   
  27.         Log.e("Himi", "surfaceChanged");   
  28.     }   
  29.     public void surfaceDestroyed(SurfaceHolder holder) {   
  30.         himi = false;//備注3   
  31.         Log.e("Himi", "surfaceDestroyed");   
  32.     }   
  33.     public void draw() {   
  34.         try {   
  35.             canvas = sfh.lockCanvas();   
  36.             if (canvas != null) {   
  37.                 canvas.drawColor(Color.WHITE);   
  38.                 canvas.drawBitmap(bmp, bmp_x, bmp_y, paint);   
  39.             }   
  40.         } catch (Exception e) {   
  41.             Log.v("Himi", "draw is Error!");   
  42.         } finally {   
  43.             if (canvas != null)   
  44.                 sfh.unlockCanvasAndPost(canvas);   
  45.         }   
  46.     }   
  47.     public void run() {   
  48.         while (himi) {//備注4   
  49.             draw();   
  50.             try {   
  51.                 Thread.sleep(100);   
  52.             } catch (Exception ex) {   
  53.             }   
  54.         }   
  55.     }   
  56. }  

       這裡修改的地方有以下幾點:

       1、我們都知道一個線程啟動後,只要run方法執行結束,線程就銷毀了,所以我增加了一個布爾值的成員變量 himi(備注1),這裡可以控制我們的線程消亡的一個開關!(備注4

       2、在啟動線程之前,設置這個布爾值為ture,讓線程一直運行。

       3、在view銷毀時,設置這個布爾值為false,銷毀當前線程!(備注3

       OK,這裡圖和解釋夠詳細了,希望大家以後真正開發一款游戲的時候,一定要嚴謹代碼,不要留有後患哈~

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved