【Android游戏开发十九】(必看篇)SurfaceView运行机制详解—剖析Back与Home按键及切入后台等异常处理!

Himi  原创, 欢迎转载,转载请在明显处注明! 谢谢。原文地址:http://blog.csdn.net/xiaominghimi/archive/2011/01/18/6149816.aspx

     在这里先向各位童鞋道个歉!我解释下:当我在给大家讲解的时候会附带上源码,可是这个源码是演示代码,为了让大家看的清楚,所以我会尽可能把一些与其无关的删掉,但是发现演示代码还是被一些童鞋们效仿,导致不少童鞋问我为什么程序执行后切入后台重新进入会报异常的问题!(这里我就全面讲解下运行机制,希望以后大家有类似问题自己就能解决了哈~)

      切入后台操作比如点击HOME按键,点击返回按键...

      第一:提交画布异常!如下图(模拟器错误提示,以及Logcat Detail)

       

       

     解决代码

   
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一旦为空,提交画布这里就会出现参数异常的错误!

     

     

     这种异常只是在当你程序运行期间点击Home按钮后再次进入程序的时候报的异常,异常说咱们的线程已经启动!为什么返回按钮就没事?OK,下面我们就要来先详细讲解一下Android中Back和Home按键的机制!然后分析问题,并且解决问题!先看下面MySurfaceViewAnimation.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接口老框架了不多说了,其中我在本类的构造、创建、状态改变、消亡函数都加上打印!

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创建函数中不就好了??!!

      没错,可以!但是当你反复几次之后你发现你的程序中会多出很多条进程!(如下图)备注:new Thread之后这个线程就是确定的了,只能启动一个,如果再次调用thread.start()那么就会出现:java.lang.IlleagleThreadStateException:Thread already started.的异常。

 虽然可以避免出现线程已经启动的异常,很明显这不是我们想要的结果!

 那么下面给大家介绍最合适的解决方案:

 修改MySurfaceViewAnimation.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方法执行结束,线程就销毁了,所以我增加了一个布尔值的成员变量 himi(备注1),这里可以控制我们的线程消亡的一个开关!(备注4)

       2.在启动线程之前,设置这个布尔值为ture,让线程一直运行.

       3.在view销毁时,设置这个布尔值为false,销毁当前线程!(备注3)

     OK,这里图和解释够详细了,希望大家以后真正开发一款游戏的时候,一定要严谨代码,不要留有后患哈~


      看了这篇文章确实明白了不少问题:

      1.一个线程启动后,只要run方法执行结束,线程就销毁了,所以我增加了一个布尔值的成员变量 himi(备注1),这里可以控制我们的线程消亡的一个开关!

      2.

private void draw() {
				mCanvas=mSurfaceHolder.lockCanvas();
				mCanvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);
				mCanvas.save();
				mCanvas.drawText("GaoMatrix is handsome!", mBmpX-2, mBmpY-10, mTextPaint);
				mCanvas.drawBitmap(mBitmap, mBmpX, mBmpY, mBackPaint);
				mCanvas.restore();
				mSurfaceHolder.unlockCanvasAndPost(mCanvas);
		}
这是之前在draw()里面的 代码,现在看来里面到处都是错误,首先
mCanvas=mSurfaceHolder.lockCanvas();

这个获得了Canvas之后就没有对它进行非空的判断,结果导致不管是Back还是Home都会在

mCanvas.drawRect(0, 0, mWidth, mHeight, mBackPaint);

时出现空指针异常。
   3.再就是这里:
mSurfaceHolder.unlockCanvasAndPost(mCanvas);
没有将整个代码写在try-catch-finally里面,把 sfh.unlockCanvasAndPost(canvas); 写在finally中,主要是为了保证能正常的提交画布.这里一定要判定下canvas是否为空,因为当程序切入后台的时候,canvas是获取不到的!那么canvas一旦为空,提交画布这里就会出现参数异常的错误!











      

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值