Android SurfaceView运行机制剖析--处理切换到后台再重新进入程序时的异常
有不少朋友都遇到过这种问题,程序执行时切换到后台,然后再重新进入会报异常,本文就这种问题全面讲解下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一旦为空,提交画布这里就会出现参数异常的错误!
二、线程启动异常。如下图(模拟器错误提示,以及logcat detail)
这种异常只是在当你程序运行期间点击home按钮后再次进入程序的时候报的异常,异常说咱们的线程已经启动!为什么返回按钮就没事?
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接口老框架了不多说了,其中我在本类的构造、创建、状态改变、消亡函数都加上打印!
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:
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,这里图和解释够详细了,希望大家以后真正开发一款游戏的时候,一定要严谨代码,不要留有后患哈~
以上就对android surfaceview运行机制详细介绍,后续继续补充相关知识,谢谢大家对本站的支持!
上一篇: Android 手势操作编程详解