android的应用类app开发通常只涉及到android sdk为大家提供的四大组件,activity、service、broadcast receiver和Content Provider。只需要在activity完成对窗口的逻辑控制,辅以layout的ui窗口设计,就可以完成用户交互的ui窗口部分,再加上service完成后台逻辑运行部分,以及broadcast receiver完成广播接收部分,content provider(或者sqlite)完成数据存储部分。一个普通的android app就基本完成了整体设计。
涉及到游戏开发,开发者会发现android sdk实际上和其他系统开发类sdk一样,为用户提供了更加底层的开发接口。例如在游戏中,单一窗口要呈现的内容往往带有动画的特点。例如植物大战僵尸中,主游戏界面上必须要有僵尸、植物这两个元素。首先,这两个元素都不是通用的ui组件,是bmp图片展示的两个小人,专业名称叫精灵。这两个精灵会移动,这也是游戏的一大特色,就是在地图之上会有很多移动的元素。这样就涉及到的在android界面上绘图的问题,或者说是让已绘好的bmp图片移动的问题。
而谈到界面中图形在变化或者频繁变动位置,或者有很多精灵,每个都有自己的随机性的动作,这就涉及到一个问题,是不是所有的逻辑都要在activity里处理。而activity我们必须看清楚,他只是一个android sdk提供给开发者的高层封装,他把view给封装了起来,目的是为了方便ui设计与逻辑代码编写的方便性。
实际上,一个android程序跑起来,仍然是一个单独运行的进程。一个 Android 程序开始运行时,就有一个主线程Main Thread被创建。该线程主要负责UI界面
的显示、更新和控件交互,所以又叫UI Thread。由于只有UI线程更新界面所以说Android是单线程模型。随之而来的概念是“android的单线程模型”,简单的说就是
当应用启动,系统会创建一个主线程(main thread)。这个主线程负责向UI组件分发事件(包括绘制事件),也是在这个主线程里,应用和Android的UI组件( android.widget and android.view))发生交互。所以main thread也叫UI thread。而这种单线程模型肯定无法包治百病,因为程序有可能面对很多繁重的任务,比如网络通信,再比如文件读写,再比如读写查找数据库,都会阻塞UI线程,导致事件停止分发(包括绘制事件)。对于用户来说,应用看起来像是卡住了,更坏的情况是,如果UI线程blocked的时间太长(大约超过5秒),用户就会看到ANR(application not responding)的对话框。系统不会为每个组件单独创建线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。结果就是,响应系统回调的方法(比如响应用户动作的onKeyDown()和各种生命周期回调)永远都是在UI线程里运行。
但是繁重的工作谁来做呢,当然会有奴隶。奴隶为ui线程陛下做完了这些繁重任务,又将如何将战果回禀给ui线程陛下呢。这就涉及到两个渠道,一个是handler,一个是奴隶线程。其中,奴隶线程的概念好理解,就是一个单独开启的线程,ui线程创建它,通常代码如下所示:
new Thread(new Runnable() {
public void run() {
Bitmap b = loadImageFromNetwork("http://example.com/image.png");
mImageView.setImageBitmap(b);
}
});
奴隶线程干完活后,会通过给Handler类发送消息告诉ui线程我干完了。
Handler类是什么呢,可以理解为他是消息分发器和接收器。奴隶线程干完活,通常可以发sendEmptyMessage(int)(或者post(Runnable);postAtTime(Runnable,long);postDelayed(Runnable long);sendMessage(Message);sendMessageAtTime(Message,long))来触发一个消息,这个消息会发给ui线程陛下,而ui线程中会有一个Handler类的成员变量h。
Handler h = new Handler() {
@Override
public void handleMessage(Message msg) {
// TODO Auto-generated method stub
super.handleMessage(msg);
switch (msg.what) {
case 4:
img.setBackgroundResource(R.drawable.s3);
break;
case 3:
img.setBackgroundResource(R.drawable.s2);
break;
case 2:
img.setBackgroundResource(R.drawable.s1);
break;
case 1:
img.setBackgroundResource(R.drawable.play);
break;
case 0:
kaishi();
break;
case 11:
。。。(省略)
default:
break;
}
}
};
其中重要的函数public void handleMessage(Message msg),大家看到了吧,他负责处理从奴隶线程中接收到的消息。
说到这里讲个例子,猜拳头游戏,项目名称fingerGame。这个项目的关键代码全部都放在了ButtonLongActivity.java文件中。
简单来说分为三步,第一步,在activity被创建的初期,OnCreate中通常加载view类以表示这个activity会放置什么样的图形图像进行屏幕显示。
这个例子里界面基本是固定的(只有两个拳头在进行bmp文件的更替变换),因此仍采用的是在在资源文件main.xml中进行界面设计,然后在OnCreate()中通过setContentView(R.layout.main);进行界面放置操作。第二步,在main.XML中有三个界面,并对这三个bmp图像对应的onTouchDown事件进行了事件关联。因此在activity中对应这三个图像(相当于按钮)被按下时触发事件逻辑。比如按下中间的“平”,表示玩家认为猜拳会出现平局。对应的逻辑处理函数(activity所在的ui主线程中的一个成员方法)zhong(View view)会触发next()函数。而next()函数很简短,
public void next() {
beginTime = System.currentTimeMillis();
new Thread(game).start();
}
但启动了一个奴役线程game。这个线程主要负责,不停地计时,在规定的游戏时间内,不停地给ui线程发送消息handle.sendEmptyMessage(12);。
这个消息会触发changeImg();函数,这个函数就干一件事,运算一个随机数,通过判断得到的两个随机数的大小来变换两边的拳头的样子,并得到猜拳结果,
猜拳结果以加分减分的形式计较,最后分数累计低于0则失败,时间到而分数多于0则给出A至F的等级评价。
实际上,通过思考我们不难发现,只有上述一个奴隶线程,这个程序是没法完成全部逻辑的,那么还缺哪里呢,没错,实际上还有几个奴隶线程,他们要么负责
积分,要么负责计算游戏时间已经消耗多少,并且将运算结果每次都告诉ui线程陛下的handler。他们分别是re,r,runnable等几个线程。
(未完待续)
参考:
【1】为什么说android UI操作不是线程安全的 http://blog.csdn.net/lvxiangan/article/details/39504145
【2】从零开始安卓游戏编程 这本书的原名是OPhone手机游戏编程,编写时间大概为2012年,因此很多东西比较旧,比如说里面的游戏,坦克大战,游戏的变动掉头的操作,都是用键盘来操作的。但是发展到2015年,安卓的智能手机已经没有硬键盘了。但是书里的游戏编程的思想还在。例如,图层的思想,这在任何平台上的游戏编程里都是存在的。另外还有精灵类的含义等,现在的安卓游戏编程仍然继承了这些。这本书相对于【3】而言,主要是讨论的UI设计。
【3】android2.0游戏开发实战宝典 这本书比较全面的阐述了游戏开发的所有环节,包括ui,网络编程,数据库,音效等等,但每个部分都是浅尝辄止。能起到给新手一个概念的作用吧。