Android游戏开发十九】(必看篇)SurfaceView运行机制详解

简介:

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

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

    那么重新进入程序报异常主要Surfaceiew 有两点会报异常: 

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

 

 解决代码: 

 

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

 

      先看备注1这里,之前的文章中我给大家解释过为什么要把 sfh.unlockCanvasAndPost(canvas); 写在finally中,主要是为了保证能正常的提交画

布.今天主要说说备注2这里一定要判定下canvas是否为空,因为当程序切入后台的时候,canvas是获取不到的!那么canvas一旦为空,提交画布这里就会出现参数异常的错误! 

     下面来说另外一种情况:线程启动异常!如下图(模拟器错误提示,以及Logcat Detail)

   

      这种异常只是在当你程序运行期间点击Home按钮后再次进入程序的时候报的异常,异常说咱们的线程已经启动!为什么返回按钮就没事?

    OK,下面我们就要来先详细讲解一下Android中Back和Home按键的机制!然后分析问题,并且解决问题!

先看下面MySurfaceViewAnimation.java的类中的代码:

 

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

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

 

  这里改的地方有以下几点:

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

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

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

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

 









本文转自 xiaominghimi 51CTO博客,原文链接:http://blog.51cto.com/xiaominghimi/606803,如需转载请自行联系原作者
目录
相关文章
|
3月前
|
缓存 Android开发 开发者
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
Flutter环境配置完成后,如何在Android设备上运行Flutter应用程序?
534 62
|
3月前
|
开发工具 Android开发 开发者
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
在Android设备上运行Flutter应用程序时,如果遇到设备未授权的问题该如何解决?
209 61
|
2月前
|
前端开发 Android开发 UED
讲讲Android为自定义view提供的SurfaceView
本文详细介绍了Android中自定义View时使用SurfaceView的必要性和实现方式。首先分析了在复杂绘制逻辑和高频界面更新场景下,传统View可能引发卡顿的问题,进而引出SurfaceView作为解决方案。文章通过Android官方Demo展示了SurfaceView的基本用法,包括实现`SurfaceHolder.Callback2`接口、与Activity生命周期绑定、子线程中使用`lockCanvas()`和`unlockCanvasAndPost()`方法完成绘图操作。
|
2月前
|
消息中间件 Android开发
Android Handler的使用方式以及其机制的简单介绍
Handler 是 Android 中实现线程间通信的重要机制,可传递任意两线程数据。常用场景包括子线程向主线程(UI 线程)传递结果,以及主线程向子线程发送消息。其核心涉及四个类:Handler(发送/接收消息)、Message(消息载体)、MessageQueue(消息队列)和 Looper(消息循环泵)。基本流程为:Handler 发送 Message 至 MessageQueue,Looper 从队列中按 FIFO 取出并处理。
|
8月前
|
存储 安全 Android开发
探索Android与iOS的隐私保护机制
在数字化时代,移动设备已成为我们生活的一部分,而隐私安全是用户最为关注的问题之一。本文将深入探讨Android和iOS两大主流操作系统在隐私保护方面的策略和实现方式,分析它们各自的优势和不足,以及如何更好地保护用户的隐私。
|
9月前
|
算法 数据处理 Android开发
掌握安卓性能优化的秘诀:电池寿命与运行效率的提升
【10月更文挑战第6天】 本文深入探讨了安卓应用开发中的性能优化技巧,重点分析了影响电池寿命和运行效率的关键因素,并提供了针对性的优化策略。通过代码优化、资源管理、后台任务处理等方法,开发者可以显著提升应用的续航能力和流畅度。同时,结合具体案例,展示了如何在实际开发中应用这些技巧,确保应用在各种场景下都能保持高效运行。本文旨在为安卓开发者提供实用的性能优化指导,助力其打造更优质的应用体验。
228 2
|
9月前
|
消息中间件 存储 Java
Android消息处理机制(Handler+Looper+Message+MessageQueue)
Android消息处理机制(Handler+Looper+Message+MessageQueue)
114 2
|
5月前
|
缓存 Java 测试技术
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
407 3
【01】噩梦终结flutter配安卓android鸿蒙harmonyOS 以及next调试环境配鸿蒙和ios真机调试环境-flutter项目安卓环境配置-gradle-agp-ndkVersion模拟器运行真机测试环境-本地环境搭建-如何快速搭建android本地运行环境-优雅草卓伊凡-很多人在这步就被难倒了
|
8月前
|
Linux Android开发 iOS开发
深入探索Android与iOS的多任务处理机制
在移动操作系统领域,Android和iOS各有千秋,尤其在多任务处理上展现出不同的设计理念和技术实现。本文将深入剖析两大平台在后台管理、资源分配及用户体验方面的策略差异,揭示它们如何平衡性能与电池寿命,为用户带来流畅而高效的操作体验。通过对比分析,我们不仅能够更好地理解各自系统的工作机制,还能为开发者优化应用提供参考。
|
8月前
|
算法 Linux 调度
深入探索安卓系统的多任务处理机制
【10月更文挑战第21天】 本文旨在为读者提供一个关于Android系统多任务处理机制的全面解析。我们将从Android操作系统的核心架构出发,探讨其如何管理多个应用程序的同时运行,包括进程调度、内存管理和电量优化等方面。通过深入分析,本文揭示了Android在处理多任务时所面临的挑战以及它如何通过创新的解决方案来提高用户体验和设备性能。
398 1