Android SurfaceView運行機制剖析--處理切換到后臺再重新進入程序時的異常
有不少朋友都遇到過這種問題,程序執(zhí)行時切換到后臺,然后再重新進入會報異常,本文就這種問題全面講解下SurfaceView的運行機制,了解了這些原理你就能自己解決這些問題了。
我們通常會通過單擊HOME按鍵或返回按鍵等操作切換到后臺,之后可能會再次進入程序,這個時候就有可能報異常。這里SurfaceView可能報的異常主要有兩點,如下:
一、提交畫布異常。如下圖(模擬器錯誤提示,以及Logcat Detail)

Java代碼
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一旦為空,提交畫布這里就會出現(xiàn)參數(shù)異常的錯誤!
二、線程啟動異常。如下圖(模擬器錯誤提示,以及Logcat Detail)

這種異常只是在當你程序運行期間點擊Home按鈕后再次進入程序的時候報的異常,異常說咱們的線程已經(jīng)啟動!為什么返回按鈕就沒事?
OK,下面我們就要來先詳細講解一下Android中Back和Home按鍵的機制!然后分析問題,并且解決問題!
先看下面MySurfaceViewAnimation.java的類中的代碼:
Java代碼
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接口老框架了不多說了,其中我在本類的構(gòu)造、創(chuàng)建、狀態(tài)改變、消亡函數(shù)都加上打??!
OK,下面看第一張圖:(剛運行程序)

上圖的左邊部分是Dubug。這里顯示我們有一條線程在運行,名字叫”himi_Thread_one”。
上圖的右邊部分是LogCat日志。大家很清晰的看到,當?shù)谝淮芜M入程序的時候,會先進入view構(gòu)造函數(shù)、然后是創(chuàng)建view,然后是view狀態(tài)改變,OK,這個大家都知道!
下面是我來點擊Home(手機上的小房子)按鍵,這時程序處于后臺,然后重新進入程序的過程!

上圖可以看出我們的線程還是一條,這里主要觀察從點擊home到再次進入程序的過程,如下所述:
點擊home 調(diào)用了view銷毀,然后進入程序會先進入view創(chuàng)建,最后是view狀態(tài)改變。
上面的過程很容易理解,重要的角色上場了~Back 按鈕!點我點擊Back按鈕看看發(fā)生了什么!

先看左邊的Debug一欄,多了一條線程! 看LogCat發(fā)現(xiàn)比點擊Home按鍵多調(diào)用了一次構(gòu)造函數(shù)!
好了,從我們測試的程序來看,無疑,點擊Home 和 點擊 Back按鈕再次進入程序的時候,步驟是不一樣的,線程數(shù)量也變了!
那么這里就能解釋為什么我們點擊Back按鈕不異常,點擊Home會異常了!
原因:因為點擊Back按鈕再次進入程序的時候先進入的是view構(gòu)造函數(shù)里,那么就是說這里又new了一個線程出來,并啟動!那么而我們點擊Home卻不一樣了,因為點擊home之后再次進入程序不會進入構(gòu)造函數(shù),而是直接進入了view創(chuàng)建這個函數(shù),而在view創(chuàng)建這個函數(shù)中我們有個啟動線程的操作,其實第一次啟動程序的線程還在運行,so~這里就一定異常了,說線程已經(jīng)啟動!
有些童鞋會問,我們?yōu)楹尾话裻h = new Thread(this, “himi_Thread_one”);放在view創(chuàng)建函數(shù)中不就好了?!
沒錯,可以!但是當你反復幾次之后你發(fā)現(xiàn)你的程序中會多出很多條進程?。ㄈ缦聢D)

雖然可以避免出現(xiàn)線程已經(jīng)啟動的異常,很明顯這不是我們想要的結(jié)果!
那么下面給大家介紹最合適的解決方案:
修改MySurfaceViewAnimation.java:
Java代碼
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方法執(zhí)行結(jié)束,線程就銷毀了,所以我增加了一個布爾值的成員變量 himi(備注1),這里可以控制我們的線程消亡的一個開關?。▊渥?)
2、在啟動線程之前,設置這個布爾值為ture,讓線程一直運行。
3、在view銷毀時,設置這個布爾值為false,銷毀當前線程?。▊渥?)
OK,這里圖和解釋夠詳細了,希望大家以后真正開發(fā)一款游戲的時候,一定要嚴謹代碼,不要留有后患哈~
以上就對Android SurfaceView運行機制詳細介紹,后續(xù)繼續(xù)補充相關知識,謝謝大家對本站的支持!
相關文章
android Launcher AppWidget添加步驟介紹
大家好,本篇文章主要講的是android Launcher AppWidget添加步驟介紹,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下,方便下次瀏覽2022-01-01
Android編程使用WebView實現(xiàn)文件下載功能的兩種方法
這篇文章主要介紹了Android編程使用WebView實現(xiàn)文件下載功能的兩種方法,涉及Android基于WebView的相關文件傳輸與線程操作技巧,需要的朋友可以參考下2018-02-02
Android中Button實現(xiàn)點擊換圖案及顏色
大家好,本篇文章主要講的是Android中Button實現(xiàn)點擊換圖案及顏色,感興趣的同學趕快來看一看吧,對你有幫助的話記得收藏一下2022-01-01
更新Android Studio 3.0碰到的問題小結(jié)
本文是小編給大家分享的更新Android Studio 3.0碰到的問題小結(jié),非常不錯,具有參考借鑒價值,需要的朋友參考下吧2017-11-11
Android中使用ContentProvider管理系統(tǒng)資源的實例
這篇文章主要介紹了Android中使用ContentProvider管理系統(tǒng)資源的實例,講解了ContentProvider對系統(tǒng)中聯(lián)系人及多媒體資源的管理例子,需要的朋友可以參考下2016-04-04
Android自定義View實現(xiàn)球形動態(tài)加速球
這篇文章主要為大家詳細介紹了Android自定義View實現(xiàn)球形動態(tài)加速球,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-06-06
Android控件RefreshableView實現(xiàn)下拉刷新
這篇文章主要為大家詳細介紹了Android控件RefreshableView實現(xiàn)下拉刷新,具有一定的參考價值,感興趣的小伙伴們可以參考一下2016-11-11

