SurfaceView運行機制詳解—剖析Back與Home按鍵及切入後臺等異常處理!

轉載自【黑米GameDev街區】 原文鏈接: http://www.himigame.com/android-game/346.html

          ☞ 點擊訂閱 ☜
 本博客最新動態!及時將最新博文通知您!

在這裏先向各位童鞋道個歉!我解釋下:當我在給大家講解的時候會附帶上源碼,可是這個源碼是演示代碼,爲了讓大家看的清楚,所以我會儘可能把一些與其無關的刪掉,但是發現演示代碼還是被一些童鞋們效仿,導致不少童鞋問我爲什麼程序執行後切入後臺重新進入會報異常的問題!(這裏我就全面講解下運行機制,希望以後大家有類似問題自己就能解決了哈~)

                                                    切入後臺操作比如點擊HOME按鍵,點擊返回按鍵...

 那麼重新進入程序報異常主要Surfaceiew 有兩點會報異常:

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

解決代碼:

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

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

布.今天主要說說備註2這裏一定要判定下canvas是否爲空,因爲當程序切入後臺的時候,canvas是獲取不到的!那麼canvas一旦爲空,提交畫

布這裏就會出現參數異常的錯誤!

下面來說另外一種情況:線程啓動異常!如下圖(模擬器錯誤提示,以及Logcat Detail)

 

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

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

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

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

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

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

 

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

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

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

 

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

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

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

 

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

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

     那麼這裏就能解釋爲什麼我們點擊Back按鈕不異常、點擊Home會異常了!

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

 

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

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

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

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

修改MySurfaceViewAnimation.java

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

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

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

2.在啓動線程之前,設置這個布爾值爲ture,讓線程一直運行.

3.在view銷燬時,設置這個布爾值爲false,銷燬當前線程!(備註3

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章