Android实例剖析笔记(八)

简介:   和Snake的比较      就界面Layout来说,这个程序其实和Snake没有什么不同,同样是采用了FrameLayout,而且游戏的主界面由一个自定义的View来实现,这里是LunarView。

   和Snake的比较

      就界面Layout来说,这个程序其实和Snake没有什么不同,同样是采用了FrameLayout,而且游戏的主界面由一个自定义的View来实现,这里是LunarView。读过上一篇文章的朋友也许会发现,Snake的架构是“定时器+系统调用onDraw”来实现的,这里有一个最大的缺陷就是onDraw是由Android系统来调用的,我们只能依赖它,却无法自行控制。这就好比一个黑盒,当然,总是能把我们要的东西给做出来,可却无法控制其做事的细节,这对于游戏这样高效率的东西可是不利的,因此最好的解决之道当然是把绘制这部分工作自己”承包“过来,告别吃大锅饭的,进入”联产承包制”时代。

      此外,由于游戏的本质就是连续两帧图片之间发生些许差异,那么要不断催生这种差异的发生,只要有某种连续不断发生的事件在进行就可以,例如Snake中使用的定时器,就是在不断地产生这种“差异源”,与此类似,一个线程也是不断在运行中,通过它也是可以不断产生这种“差异源”的。

  SurfaceView初探

      如果说Snake中使用的Layout加自定义View是一把小型武器的话,那在SurfaceView对于android中游戏的开发来说就算是重型武器了。我们使用前者时总是容易把游戏中某个对象(比如上文的每一个方格)当做一个小组件来处理,而后者则根本没有这种划分的概念,在它眼中,所有东西都是在Canvas(画布)中自行绘制出来的(背景,人物等)。

  SurfaceView提供直接访问一个可画图的界面,可以控制在界面顶部的子视图层。SurfaceView是提供给需要直接画像素而不是使用窗体部件的应用使用的。Android图形系统中一个重要的概念和线索是surface。View及其子类(如TextView, Button)

  要画在surface上。每个surface创建一个Canvas对象(但属性时常改变),用来管理view在surface上的绘图操作,如画点画线。还要注意的是,使用它的时候,一般都是出现在最顶层的:The view hierarchy will take care of correctly compositing with the Surface any siblings of the SurfaceView that would normally appear on top of it. 使用的SurfaceView的时候,一般情况下还要对其进行创建,销毁,改变时的情况进行监视,这就要用到SurfaceHolder.Callback.

class  LunarView  extends  SurfaceView  implements  SurfaceHolder.Callback
{
    
public   void  surfaceChanged(SurfaceHolder holder, int  format, int  width, int  height){}
// 在surface的大小发生改变时激发
     public   void  surfaceCreated(SurfaceHolder holder){}
// 在创建时激发,一般在这里调用画图的线程。
     public   void  surfaceDestroyed(SurfaceHolder holder) {}
// 销毁时激发,一般在这里将画图的线程停止、释放。
}

 surfaceCreated会首先被调用,然后是surfaceChanged,当程序结束时会调用surfaceDestroyed。下面来看看LunarView最重要的成员变量,也就是负责这个View所有处理的线程

private  LunarThread thread;  //  实际工作线程
        thread  =   new  LunarThread(holder, context,  new  Handler() {
            @Override
            
public   void  handleMessage(Message m) 
            {
                mStatusText.setVisibility(m.getData().getInt(
" viz " ));
                mStatusText.setText(m.getData().getString(
" text " ));
            }
        });

  这个线程由私有类LunarThread实现,它里面还有一个自己的消息队列处理器,用来接收游戏状态消息,并在屏幕上显示当前状态(而这个功能在Snake中是通过View自己控制其包含的TextView是否显示来实现的,相比之下,LunarThread的消息处理机制更为高效)。由于有了LunarThread这个负责具体工作的对象,所以LunarView的大部分工作都委托给后者去执行。

     public   void  surfaceChanged(SurfaceHolder holder,  int  format,  int  width, int  height)
    {
        thread.setSurfaceSize(width, height);
    }
public   void  surfaceCreated(SurfaceHolder holder)
{
// 启动工作线程结束
        thread.setRunning( true );
        thread.start();
    }
    
public   void  surfaceDestroyed(SurfaceHolder holder)
    {
        
boolean  retry  =   true ;
        thread.setRunning(
false );
        
while  (retry) 
        {
            
try
            {
// 等待工作线程结束,主线程才结束
                thread.join();
                retry 
=   false ;
            } 
            
catch  (InterruptedException e) 
            {
            }
        }
    }

  工作线程LunarThread

  由于SurfaceHolder是一个共享资源,因此在对其操作时都应该实行“互斥操作“,即需要使用synchronized进行”封锁“机制。

      再来讨论下为什么要使用消息机制来更新界面的文字信息呢?其实原因是这样的,渲染文字的工作实际上是主线程(也就是LunarView类)的父类View的工作,而并不属于工作线程LunarThread,因此在工作线程中式无法控制的。所以我们改为向主线程发送一个Message来代替,让主线程通过Handler对接收到的消息进行处理,从而更新界面文字信息。再回顾上一篇SnakeView里的文字信息更新,由于是SnakeView自己(就这一个线程)对其包含的TextView做控制,当然没有这样的问题了。

 

   public   void  setState( int  mode, CharSequence message) 
        {
            
synchronized  (mSurfaceHolder)
            {
                mMode 
=  mode;
                
if  (mMode  ==  STATE_RUNNING)
                {
// 运行中,隐藏界面文字信息
                    Message msg  =  mHandler.obtainMessage();
                    Bundle b 
=   new  Bundle();
                    b.putString(
" text " "" );
                    b.putInt(
" viz " , View.INVISIBLE);
                    msg.setData(b);
                    mHandler.sendMessage(msg);
                } 
                
else  
                {
// 根据当前状态设置文字信息
                    mRotating  =   0 ;
                    mEngineFiring 
=   false ;
                    Resources res 
=  mContext.getResources();
                    CharSequence str 
=   "" ;
                    
if  (mMode  ==  STATE_READY)
                        str 
=  res.getText(R.string.mode_ready);
                    
else   if  (mMode  ==  STATE_PAUSE)
                        str 
=  res.getText(R.string.mode_pause);
                    
else   if  (mMode  ==  STATE_LOSE)
                        str 
=  res.getText(R.string.mode_lose);
                    
else   if  (mMode  ==  STATE_WIN)
                        str 
=  res.getString(R.string.mode_win_prefix)
                                
+  mWinsInARow  +   "   "
                                
+  res.getString(R.string.mode_win_suffix);
                    
if  (message  !=   null ) {
                        str 
=  message  +   " \n "   +  str;
                    }
                    
if  (mMode  ==  STATE_LOSE) 
                        mWinsInARow 
=   0 ;
                    Message msg 
=  mHandler.obtainMessage();
                    Bundle b 
=   new  Bundle();
                    b.putString(
" text " , str.toString());
                    b.putInt(
" viz " , View.VISIBLE);
                    msg.setData(b);
                    mHandler.sendMessage(msg);
                }
            }
        }

  下面就是LunaThread这个工作线程的执行函数了,它一直不断在重复做一件事情:锁定待绘制区域(这里是整个屏幕),若游戏还在进行状态,则更新底层的数据,然后直接强制界面重新绘制。

    public   void  run() 
        {
            
while  (mRun) 
            {
                Canvas c 
=   null ;
                
try  
                {
                    
// 锁定待绘制区域
                    c  =  mSurfaceHolder.lockCanvas( null );
                    
synchronized  (mSurfaceHolder)
                    {
                        
if  (mMode  ==  STATE_RUNNING) 
                            updatePhysics();
// 更新底层数据,判断游戏状态
                        doDraw(c); // 强制重绘制
                    }
                } 
                
finally  
                {
                    
if  (c  !=   null ) {
                        mSurfaceHolder.unlockCanvasAndPost(c);
                    }
                }
            }
        }

  这里要注意的是最后要调用unlockCanvasAndPost来结束锁定画图,并提交改变

  强行自绘制

   doDraw这段代码就是在自己的Canvas上进行绘制,具体的绘制就不解释了,主要就是用drawBitmap,drawRect,drawLine。值得注意的一段代码是下面这个:

    canvas.save();
            canvas.rotate((
float ) mHeading, ( float ) mX, mCanvasHeight
                    
-  ( float ) mY);
            
if  (mMode  ==  STATE_LOSE) {
                mCrashedImage.setBounds(xLeft, yTop, xLeft 
+  mLanderWidth, yTop
                        
+  mLanderHeight);
                mCrashedImage.draw(canvas);
            } 
else   if  (mEngineFiring) {
                mFiringImage.setBounds(xLeft, yTop, xLeft 
+  mLanderWidth, yTop
                        
+  mLanderHeight);
                mFiringImage.draw(canvas);
            } 
else  {
                mLanderImage.setBounds(xLeft, yTop, xLeft 
+  mLanderWidth, yTop
                        
+  mLanderHeight);
                mLanderImage.draw(canvas);
            }
            canvas.restore();

  在绘制火箭的前后,调用了save()和restore(),它是先保存当前矩阵,将其复制到一个私有堆栈上。然后接下来对rotate的调用还是在原有的矩阵上进行操作,但当restore调用后,以前保存的设置又重新恢复。不过,在这里还是看不出有什么用处。。。

  暂停/继续机制

      LunarLancher的暂停其实并没有不再强制重绘制,而是没有对底层的数据做任何修改,依然绘制同一帧画面,而继续则是把mLastTime设置为当前时间+100毫秒的时间点,因为以前暂停时mLastTime就不再更新了,这样做事为了与当前时间同步起来。

      public   void  pause()
        {
// 暂停
             synchronized  (mSurfaceHolder) 
            {
                
if  (mMode  ==  STATE_RUNNING)
                    setState(STATE_PAUSE);
            }
        }
        
public   void  unpause()
        {
//  继续
            
//  Move the real time clock up to now
             synchronized  (mSurfaceHolder)
            {
                mLastTime 
=  System.currentTimeMillis()  +   100 ;
            }
            setState(STATE_RUNNING);
        }

  这样做的目的是为了制造延迟的效果,都是因为updatePhysics函数里这两句

            if  (mLastTime  >  now)  return ;
        
double  elapsed  =  (now  -  mLastTime)  /   1000.0 ;

至于游戏的控制逻辑和判定部分就不介绍了,没有多大意思。

目录
相关文章
|
1月前
|
Web App开发 安全 程序员
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
多年的互联网寒冬在今年尤为凛冽,坚守安卓开发愈发不易。面对是否转行或学习新技术的迷茫,安卓程序员可从三个方向进阶:1)钻研谷歌新技术,如Kotlin、Flutter、Jetpack等;2)拓展新功能应用,掌握Socket、OpenGL、WebRTC等专业领域技能;3)结合其他行业,如汽车、游戏、安全等,拓宽职业道路。这三个方向各有学习难度和保饭碗指数,助你在安卓开发领域持续成长。
60 1
FFmpeg开发笔记(五十五)寒冬里的安卓程序员可进阶修炼的几种姿势
|
22天前
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
73 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
6月前
|
Android开发
Android应用实例(一)之---有道辞典VZ.0
Android应用实例(一)之---有道辞典VZ.0
42 2
|
3月前
|
JavaScript 前端开发 Java
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
IT寒冬使APP开发门槛提升,安卓程序员需转型。选项包括:深化Android开发,跟进Google新技术如Kotlin、Jetpack、Flutter及Compose;研究Android底层框架,掌握AOSP;转型Java后端开发,学习Spring Boot等框架;拓展大前端技能,掌握JavaScript、Node.js、Vue.js及特定框架如微信小程序、HarmonyOS;或转向C/C++底层开发,通过音视频项目如FFmpeg积累经验。每条路径都有相应的书籍和技术栈推荐,助你顺利过渡。
59 3
FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向
|
3月前
|
编解码 安全 Ubuntu
Android Selinux 问题处理笔记
这篇文章是关于处理Android系统中SELinux权限问题的笔记,介绍了如何通过分析SELinux拒绝的日志、修改SELinux策略文件,并重新编译部署来解决权限问题,同时提供了一些SELinux的背景知识和实用工具。
60 0
|
4月前
|
API Android开发
Android 监听Notification 被清除实例代码
Android 监听Notification 被清除实例代码
|
5月前
|
安全 Java Android开发
使用Unidbg进行安卓逆向实例讲解
使用Unidbg进行安卓逆向实例讲解
128 2
|
6月前
|
安全 Linux Android开发
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
该文介绍了如何在Linux服务器上交叉编译Android的FFmpeg库以支持HTTPS视频播放。首先,从GitHub下载openssl源码,解压后通过编译脚本`build_openssl.sh`生成64位静态库。接着,更新环境变量加载openssl,并编辑FFmpeg配置脚本`config_ffmpeg_openssl.sh`启用openssl支持。然后,编译安装FFmpeg。最后,将编译好的库文件导入App工程的相应目录,修改视频链接为HTTPS,App即可播放HTTPS在线视频。
107 3
FFmpeg开发笔记(十六)Linux交叉编译Android的OpenSSL库
|
5月前
|
Java API Android开发
技术经验分享:Android源码笔记——Camera系统架构
技术经验分享:Android源码笔记——Camera系统架构
54 0
|
6月前
|
Java 测试技术 开发工具
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
Android 笔记:AndroidTrain , Lint , build(1),只需一篇文章吃透Android多线程技术
下一篇
无影云桌面