Android SurfaceView运行机制剖析--处理切换到后台再重新进入程序时的异常

所属分类: 软件编程 / Android 阅读数: 51
收藏 0 赞 0 分享

        有不少朋友都遇到过这种问题,程序执行时切换到后台,然后再重新进入会报异常,本文就这种问题全面讲解下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网络编程之获取网络上的Json数据实例

这篇文章主要介绍了Android网络编程之获取网络上的Json数据实例,本文用完整的代码实例讲解了在Android中读取网络中Json数据的方法,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中的windowSoftInputMode属性详解

这篇文章主要介绍了Android中的windowSoftInputMode属性详解,本文对windowSoftInputMode的9个属性做了详细总结,需要的朋友可以参考下
收藏 0 赞 0 分享

Android网络编程之UDP通信模型实例

这篇文章主要介绍了Android网络编程之UDP通信模型实例,本文给出了服务端代码和客户端代码,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中使用ListView实现漂亮的表格效果

这篇文章主要介绍了Android中使用ListView实现漂亮的表格效果,本文用详细的代码实例创建了一个股票行情表格,需要的朋友可以参考下
收藏 0 赞 0 分享

Android中刷新界面的二种方法

这篇文章主要介绍了Android中刷新界面的二种方法,本文使用Handler、postInvalidate两种方法实现界面刷新,需要的朋友可以参考下
收藏 0 赞 0 分享

Android SDK三种更新失败及其解决方法

这篇文章主要介绍了Android SDK三种更新失败及其解决方法,需要的朋友可以参考下
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(一)

Android3.0(API level 11)开始,Android设备不再需要专门的菜单键。随着这种变化,Android app应该取消对传统6项菜单的依赖。取而代之的是提供anction bar来提供基本的用户功能
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(二)

这次将继续上一篇文章没有讲完的Menu的学习,上下文菜单(Context menu)和弹出菜单(Popup menu)
收藏 0 赞 0 分享

Android学习笔记——Menu介绍(三)

今天继续昨天没有讲完的Menu的学习,主要是Popup Menu的学习,需要的朋友可以参考下
收藏 0 赞 0 分享

Android显示网络图片实例

这篇文章主要介绍了Android显示网络图片的方法,以实例形式展示了Android程序显示网络图片的方法,非常具有实用价值,需要的朋友可以参考下
收藏 0 赞 0 分享
查看更多