【贪吃蛇—Java程序员写Android游戏】系列5.Android新浪微博客户端实现——准备篇

大家想必都使用过微博,或者是每天牢骚不断的强迫症用户,或者是随便注册个帐户的酱油党。毋容置疑,从2010年开始到现在,微博在中国又重新焕发出第二春(第一春是饭否、叽歪。。。2009年被封杀过,不过最近貌似饭否又活过来了。。。),而新浪微博在其中居功至伟。

其实,中国的微博大同小异,学习的都是鼻祖:Twitter;就是好像中国的SNS都是学习的Facebook,而且学得非常像!(笑*^-^*)就拿新浪微博为例,其API到现在为止还有一些直接用Twitter
API的例子,本博跟他们反映过,得到的回复是:没有人在维护它。而其它微博的API也基本都是一个模式。

不管怎么说,新浪微博目前都是国内微博的执牛耳者,(因为它叫“面包牌面包”嘛,再笑*^-^*),而本博之前也写过几个新浪微博的应用,还算熟悉;因此,从本次开始,我们来一起把贪吃蛇游戏跟新浪微博结合起来,做一个新浪微博头像的贪吃蛇。

一、 资料

1. 新浪微博API

要开发新浪微博的Android客户端,首先,要对其开放的API及相关规范比较熟悉,因此需要参考http://open.t.sina.com.cn/wiki/

2. 相关教程

我们不是第一个吃螃蟹的人,我们要站在巨人的肩膀上。现在网上流传比较广的教程是一位叫“水的右边”的朋友写的《android开发我的新浪微博客户端》,据他自己说,他开始本系列文章写作的时候,接触Android三个星期的时间。所以,如果朋友你也是初学者,要有信心经过一段时间的努力熟练掌握Android开发。这里,为了方便大家,我把eoe一位斑竹整理好的全部文章放在如下地址,大家可以自行下载:

http://ishare.iask.sina.com.cn/f/14857560.html

二、 开发准备

要使用新浪微博开放API,需要有新浪微博,并创建新浪微博应用,获取App Key和App
Secret。因此,我们需要登陆新浪微博,并进入“我的应用”页面,你可以直接访问http://open.t.sina.com.cn/apps
。在这里创建一个新的应用,本博这里创建如下:

clip_image002

然后,我们就进入了微博开发阶段。通过这里列出的Key可以正常调用新浪微博API。

此外,我们还要准备些图片素材,本博基本就是从互联网上捞一些,然后PS下。可能不是那么美观,大家将就下啦。

三、 创建项目

在Eclipse中创建一个叫SnakeSina的项目,并实现基本的Splash页面和新浪微博Oauth认证页面。这里就不做详细的展开,各位可以自行参考前面推荐的资料。

1. 登陆应用时的Splash界面:

clip_image004

2. 第一次登陆,询问用户是否进行登录授权:

clip_image006

3. 使用Sqlite保存授权成功后的useid、key、secret等信息。

下次,我们将通过新浪微博API获取资源并保存。

【贪吃蛇—Java程序员写Android游戏】系列 4.用Google SVN管理开源的Android项目

最近在写一个新浪微博团购分享的手机客户端(感兴趣的朋友可以到这里下载http://sharetuan.sinaapp.com/
,是J2ME版本的,以后我基本就不会进行J2ME版本的开发,注意精力放在Android上了),因此博客更新慢了点。不过,我会尽量保证一周至少更新一次。

本次讲讲如何使用Google的SVN来管理我们的Android开源项目。

一、创建Project

1. 访问http://code.google.com/hosting/

2. 用Google帐户登录登录,并点击左下角的Create a new
project
,你会看到如下界面:

clip_image002

3. 填入相应的信息如下:

clip_image004

4. 这里简单介绍下Google SVN上列出的各种开源协议:

Apache License 2

Apache
License是著名的非盈利开源组织Apache采用的协议。该协议和BSD类似,同样鼓励代码共享和尊重原作者的著作权,同样允许代码修改,再发布(作为开源或商业软件)。需要满足的条件也和BSD类似:

· 需要给代码的用户一份Apache License

· 如果你修改了代码,需要在被修改的文件中说明。

·
在延伸的代码中(修改和有源代码衍生的代码中)需要带有原来代码中的协议,商标,专利声明和其他原来作者规定需要包含的说明。

· 如果再发布的产品中包含一个Notice文件,则在Notice文件中需要带有Apache
License。你可以在Notice中增加自己的许可,但不可以表现为对Apache License构成更改。

Apache
License也是对商业应用友好的许可。使用者也可以在需要的时候修改代码来满足需要并作为开源或商业产品发布/销售。

英文原文:http://www.apache.org/licenses/LICENSE-2.0.html

Artistic License/GPL

使作者保持对进一步开发的控制。

英文原文:

http://www.perlfoundation.org/artistic_license_1_0

http://www.perlfoundation.org/artistic_license_2_0

Eclipse Public License 1.0

英文原文:www.eclipse.org/legal/epl-v10.html

GNU GPL v2

我们很熟悉的Linux就是采用了GPL。GPL协议和BSD, Apache
License等鼓励代码重用的许可很不一样。GPL的出发点是代码的开源/免费使用和引用/修改/衍生代码的开源/免费使用,但不允许修改后和衍生的代
码做为闭源的商业软件发布和销售。这也就是为什么我们能用免费的各种linux,包括商业公司的linux和linux上各种各样的由个人,组织,以及商
业软件公司开发的免费软件了。

GPL协议的主要内容是只要在一个软件中使用(”使用”指类库引用,修改后的代码或者衍生代码)GPL
协议的产品,则该软件产品必须也采用GPL协议,既必须也是开源和免费。这就是所谓的”传染性”。GPL协议的产品作为一个单独的产品使用没有任何问题,还可以享受免费的优势。

由于GPL严格要求使用了GPL类库的软件产品必须使用GPL协议,对于使用GPL协议的开源代码,商业软件或者对代码有保密要求的部门就不适合集成/采用作为类库和二次开发的基础。

其它细节如再发布的时候需要伴随GPL协议等和BSD/Apache等类似。

英文原文:www.gnu.org/licenses/gpl-2.0.html

GNU GPL v3

与GNU GPL v2的区别如下:

a、对用户的专利保护

新版本GPL最重要的特性,就是明确了专利许可。任何分发GPLv3软件的人必须自动为软件用户提供许可证,让他们可以使用所有相关的专利。
从理论上来讲,这样做可以保护所有GPL专利享有者的诉讼权利,防止Linux经销商与专利持有者,如微软公司进行独家经营。

b、不包含任何网络服务条款

GPLv3的早期草案中包含了一个可选择条款,即要求所有的网络应用程序为远程用户提供源代码。如今,这个条款已被取消。幸亏如此,否则对于网络开发商和用户来说都会是极大的负担。尽管这样做的初衷是为了保证拥有此软件服务的用户享有的服务与二进制文件代表的服务完全相同,而这种做法却可能会
潜在地产生更为广泛的影响。

但是,对SaaS用户提供源代码的要求并没有被放弃。FSF仍然要求进行Affero许可,而这个许可在GPL的基础上还增加了一项特别条
款。这或许是在告诫采取双重许可策略的经销商们,因为他们强烈激励服务供应商购买商业许可,而不是使用开放源代码版本。

c、对DRM的限制

绝大多数对GPLv3有争议的条款都涉及到对数字版权的管理。新版本的GPL并没有要求禁用DRM,但是通过两种非常重要的途径对它进行了限
制。

首先是项声明,即基于GPL代码为基础的DRM不会构成一种“有效的技术手段”。这项声明旨在保证在DMCA和惩戒逆向开发DRM系统的法律
条款下,打破基于GPL代码为基础的DRM为合法。这项声明主要是针对用户的应用,但是如果它能够阻止使用DRM时对互操作性的限制,那么对企业用户来说
很有利。

其次,要求应用GPL软件的家用设备必须允许用户执行他们自己修改的版本。这样做是为了阻止那些使用GPL代码、但需要由硬件供应商认可的设
备,如
TiVO等。但是,这只能影响到家用设备:供应商仍然能够利用数字签名来锁定GPL代码。这种差异都是缘于签署代码的开发者们拥有合法的安全应用程序,而
这恰恰是企业所需要的。

英文原文:www.gnu.org/licenses/gpl.html

GNU Lesser GPL

LGPL是GPL的一个为主要为类库使用设计的开源协议。和GPL要求任何使用/修改/衍生之GPL类库的的软件必须采用GPL协议不同。LGPL
允许商业软件通过类库引用(link)方式使用LGPL类库而不需要开源商业软件的代码。这使得采用LGPL协议的开源代码可以被商业软件作为类库引用并
发布和销售。

但是如果修改LGPL协议的代码或者衍生,则所有修改的代码,涉及修改部分的额外代码和衍生的代码都必须采用LGPL协议。因此LGPL协议的开源代码很适合作为第三方类库被商业软件引用,但不适合希望以LGPL协议代码为基础,通过修改和衍生的方式做二次开发的商业软件采用。

GPL/LGPL都保障原作者的知识产权,避免有人利用开源代码复制并开发类似的产品

英文原文:http://www.gnu.org/copyleft/lesser.html

MIT License

MIT是和BSD一样宽范的许可协议,作者只想保留版权,而无任何其他了限制.也就是说,你必须在你的发行版里包含原许可协议的声明,无论你是以二进制发布的还是以源代码发布的.

英文原文:http://www.opensource.org/licenses/mit-license.php

Mozilla Public License 1.1

允许免费重发布、免费修改,但要求修改后的代码版权归软件的发起者。这种授权维护了商业软件的利益,它要求基于这种软件的修改无偿贡献版权给该软件。

英文原文:http://www.mozilla.org/MPL/MPL-1.1.html

New BSD License

BSD开源协议是一个给于使用者很大自由的协议。基本上使用者可以”为所欲为”,可以自由的使用,修改源代码,也可以将修改后的代码作为开源或者专有软件再发布。

但”为所欲为”的前提当你发布使用了BSD协议的代码,或则以BSD协议代码为基础做二次开发自己的产品时,需要满足三个条件:

· 如果再发布的产品中包含源代码,则在源代码中必须带有原来代码中的BSD协议。

· 如果再发布的只是二进制类库/软件,则需要在类库/软件的文档和版权声明中包含原来代码中的BSD协议。

· 不可以用开源代码的作者/机构名字和原来产品的名字做市场推广。

BSD
代码鼓励代码共享,但需要尊重代码作者的著作权。BSD由于允许使用者修改和重新发布代码,也允许使用或在BSD代码上开发商业软件发布和销售,因此是对
商业集成很友好的协议。而很多的公司企业在选用开源产品的时候都首选BSD协议,因为可以完全控制这些第三方的代码,在必要的时候可以修改或者二次开发。

英文原文:http://www.opensource.org/licenses/bsd-license.php

Other Open Source

Common Development and Distribution License

2005年,SUN公司宣布将开放操作系统Solaris的源代码,并推出CDDL(Common Development and
Distribution License)作为Open Solaris的许可证。CDDL许可证是MPL许可证(Mozilla
Public License,用来管理Mozilla网页浏览器及相关软件)的“升级版”。

英文原文:http://www.sun.com/cddl/cddl.html

Common Public License – v 1.0

英文原文:http://www.eclipse.org/legal/cpl-v10.html

还有微软的一些开源协议,这里就不列了,用的人不多,反正好像只有微软在用吧,哈哈

大家可以根据自己的情况进行选择。

5. 点击Create project按钮之后,就完成了Project的创建,如下:

clip_image006

6. 你可以访问http://code.google.com/p/support/wiki/GettingStarted,来学习如何管理一个SVN上的Project,当然,我们接下来也会进行相关介绍。

注意: Google的SVN是强制开源的!

二、提交Project

1. 现在就可以直接通过http://code.google.com/p/snake-book/访问刚刚创建的项目了,点击source可以看到如下图所示。待会咱们就可以使用Eclipse提交文件到https://snake-book.googlecode.com/svn/trunk/地址,并通过帐户密码进行update,需要注意的是,以后管理该项目的密码就是这里“googlecode.com
password”帮我们生成的比较复杂安全性比较高的密码。另外一个地址:http://snake-book.googlecode.com/svn/trunk/给匿名用户checkout代码出来使用,只读权限。

clip_image008

2. 前面的文章中介绍过Eclipse环境的搭建,现在我们要访问http://subclipse.tigris.org/来安装Eclipse的svn插件,以方便我们以后代码的提交和管理。安装的地址如下图,输入地址后,根据向导安装后重启Eclipse即可。

clip_image010

3.
现在我们就可以把源代码提交到svn服务器上,打开要上传的项目,右键->team->share
Project->svn,写入https路径。下一步,输入Google账号和上传密码,起个名,finish。如下图:

clip_image012

clip_image014

clip_image016

注意:提交的时候使用Googlecode.com
帮我们生成的密码;只提交源文件而不要包含生成的文件。

4. 现在我们可以直接在浏览器中通过http://snake-book.googlecode.com/svn/trunk/
访问刚刚提交的代码了,也可以直接在Eclipse中使用svn插件,或者使用其它svn客户端进行代码的访问。

三、使用Project

1.
现在,Eclipse看到的项目会多出svn的图标,如下图,这表示本项目已经纳入了版本控制。以后做的修改可以通过提交到Google的code进行版本控制了。

clip_image018

2.
在我们进行了代码或者文件的更新之后,就可以通过:右键->team->commit来进行代码的提交,如下图:

clip_image020

3. 更多的操作会在以后用到的时候讲解;也请自行参考相关文档。今天就到这里吧,谢谢大家。

预告:下次将为大家带来新浪微博的头像贪吃蛇实现(准备篇)。

如何正确使用Timer

在需要按时间计划执行简单任务的情况下,Timer是最常被使用到的工具类。使用Timer来调度TimerTask的实现者来执行任务,有两种方式,一种是使任务在指定时间被执行一次,另一种是从某一指定时间开始周期性地执行任务。

 

下面是一个简单的Timer例子,它每隔10秒钟执行一次特定操作doWork

Timer timer = new
Timer();

                       
TimerTask  task = new
TimerTask (){

                       
public void run() {

        
                   
doWork();

                       
  
}

};

              timer.schedule
(task, 10000L, 10000L);

可以看到,具体的任务由TimerTask的子类实现,Timer负责管理、执行TimerTask

 

Timer 的使用

在不同的场景下,需要使用不同的Timer接口。如上所说,主要区分两种情况

1) 在指定时间执行任务,只执行一次

         
public
void schedule(TimerTask task, long delay)

         
public
void schedule(TimerTask task, Date time)

 

2)从指定时间开始,周期性地重复执行,直到任务被cancel掉。其中又分两种类型:

2.1
一种是按上一次任务执行的时间为依据,计算本次执行时间,可以称为相对时间法。比如,如果第一次任务是110秒执行的,周期为5秒,因系统繁忙(比如垃圾回收、虚拟内存切换),115秒没有得到机会执行,直到116秒才有机会执行第二次任务,那么第3次的执行时间将是121秒,偏移了1秒。

         
public
void schedule(TimerTask task, long delay, long
period)

         
public
void schedule(TimerTask task, Date firstTime, long
period)

 

2.2
另一种是绝对时间法,以用户设计的起始时间为基准,第n次执行时间为“起始时间+n*周期时间”。比如,在上面的情况下,虽然因为系统繁忙,第二执行时间被推后1秒,但第3次的时间点仍然应该是120秒。

         
public
void scheduleAtFixedRate(TimerTask task, long delay, long
period)

         
public
void scheduleAtFixedRate(TimerTask task, Date
firstTime, 

long period)

 

相对时间法,关注于满足短时间内的执行间隔,绝对时间法,则更关注在一个长时间范围内,任务被执行的次数。如果我们要编写一个程序,用timer控制文档编辑器中提示光标的闪烁,用哪种更合适? 当然是相对时间法。如果改用绝对时间法,当从系统繁忙状态恢复后,光标会快速连续闪烁多次,以弥补回在系统繁忙期间没有被执行的任务,这样的情况会用户来说比较难以接受。又如,每10分钟检查一次新邮件的到来,也适合于使用相对时间法。

Timer timer = new
Timer();

                       
TimerTask  task = new
TimerTask (){

                        
public void run() {

                               
displayCursor();

                       
  
}

};

             
timer.schedule (task, 1000L, 1000L); //
每秒闪烁一次光标

 

作为对比,我们来考虑一种绝对时间法的应用场景——倒数任务,比如,要求在10秒内做倒数计时,每秒做一次doworkPerSecond操作,10秒结束时做一次doworkEnd操作,然后结束任务。

Timer timer = new
Timer();

                       
TimerTask  task = new
TimerTask (){

                             
private int count=10;

                             
public void run() {

                                  
if(count>0){

                                     
doWorkPerSecond();

                                     count–;
     
     
     
     
     
     
     

}else{

                              
doWorkEnd();

                            
 
cancel();

                            
}

                       
  
}

};

                       
timer. scheduleAtFixedRate (task, 1000L, 1000L);

 

 

Timer及相关类的内部实现

         
Timer的内部会启动一个线程TimerThread。即使有多个任务被加入这个Timer,它始终只有一个线程来管理这些任务。

         
TimerThreadThread的子类。加入Timer的所有任务都会被最终放入TimerThread所管理的TaskQueue中。TimerThread会不断查看TaskQueue中的任务,取出当前时刻应该被执行的任务执行之,并且会重新计算该任务的下一次执行时间,重新放入TaskQueue。直到所有任务执行完毕(单次任务)或者被cancel(重复执行的任务),该线程才会结束。

         
TaskQueue,由数组实现的二叉堆,堆的排序是以任务的下一次执行时间为依据的。二叉堆的使用使得TimerThread以简洁高效的方式快速找到当前时刻需要执行的TimerTask,因为,堆排序的特性是保证最小(或者最大)值位于堆叠顶端,在这里,queue[1]始终是下次执行时间(nextExecutionTime)最小的,即应该最先被执行的任务

 

比如,同一个timer管理两个任务task1task2

timer.schedule (task1,
4000L, 10000L);

timer.
scheduleAtFixedRate (task2, 2000L, 15000L);

则,TaskQueue中会有两个任务:task1task2task2会排在头部queue[1],当task2执行时间到,task2被执行,同时修改其nextExecutionTime =当前的nextExecutionTime +15000L(绝对时间法)并重新在二叉堆中排序。排序后,task1被放到头部。当task1执行时间到,task1被执行,并修改其nextExecutionTime =当前时间+10000L,然后重新在二叉堆中对其排序………

当收到客户端请求时,服务端生成一个Response对象。服务端希望客户端访问该对象的间隔时间不能超过20秒,否则,服务端认为客户端已经异常关闭或者网络异常,此时销毁掉该对象并打印错误日志。每次访问都会重新开始计时。

 

class Response{

private
TimerTask 
timeout;

           
public void init(){

        
………

Timer timer = new
Timer();

timeout = new
TimeOutTask();

                       
timer.schedule (timeout, 20000L);

}

 

public void
invoke(){

   
timeout.cancel();//
取消当前的timeout任务

   
….

   timeout = new
TimeOutTask();

                       
  
timer.schedule (timeout, 20000L);//
重新开始计时

}

 

void
destroy(){

  
……..

}

 

class TimeOutTask
extends TimerTask{

          
public void run() {

TraceTool.error(“Time out, destroy the Response
object.”);

destroy();

因为Timer不支持对任务重置计时,所以此处采取了先cancel当前的任务再重新加入新任务来达到重置计时的目的。注意,对一个已经cancel的任务,不能通过schedule重新加入Timer中执行。TimerTask的状态机如下:


 

一个新生成的TimerTask其状态为VIRGINTimer只接受状态为VIRGIN的任务,否则会有IllegalStateException异常抛出。

调用任务的cancel方法,该任务就转入CANCELLED状态,并很快从TaskQueue中删除。对单次执行的任务,一旦执行结束,该任务也会从中删除。这意味着TimerTask将不再被timer所执行了。

http://li53262182.blog.163.com/blog/static/12839338720099985825964/

【贪吃蛇—Java程序员写Android游戏】系列 3. 用J2ME实现Android的Snake Sample详

本次会详细讲解将Android的Snake Sample移植到J2ME上,从而比较二者的区别和联系。

在《1.Android
SDK
Sample-Snake详解
》中,我们已经详细介绍了Android实现的Snake项目结构,现在我们要将这个项目用J2ME实现。

一、 J2ME vs. Android

Android的UI实用、方便,而且很美观,基本无需改动且定制方便。而J2ME的高级用户界面比较鸡肋,在现在大多数的应用里都看不到,多数稍微复杂点的界面都是手工画,或是用一些开源的高级UI库。接下来我们简单比较下二者的区别,为Snake项目从Android到J2ME的移植做准备。

1. 平台

J2ME

开发平台

Android

操作系统

2. 工程结构

J2ME

res:资源文件

src:源代码

Android

src:源代码

res\drawable:图片

res\raw:声音

res\values:字符串

assets:数据文件

3. 安装包

J2ME

jad,jar

Android

apk

4. 代码结构

J2ME

MIDlet,Canvas,采用继承的方式,只有一个MIDlet,一般只有一个Canvas

Android

Activity,View,采用继承的方式,只有一个Activity,一般只有一个View

5. 入口程序

J2ME

MIDlet类

Android

Activity类

6. 主程序结构

J2ME

package com.deaboway.j2me;

import javax.microedition.midlet.MIDlet;

import javax.microedition.midlet.MIDletStateChangeException;

public class MyMidlet extends MIDlet {

protected void destroyApp(boolean arg0) throws
MIDletStateChangeException {

// TODO Auto-generated method stub

}

protected void pauseApp() {

// TODO Auto-generated method stub

}

protected void startApp() throws MIDletStateChangeException
{

// TODO Auto-generated method stub

}

}

Android

package com.deaboway.android;

import android.app.Activity;

import android.os.Bundle;

public class myActivity extends Activity {

@Override

public void onCreate(Bundle icicle) {

super.onCreate(icicle);

setContentView(R.layout.main);

}

}

7. 生命周期-开始

J2ME

startApp(),活动状态,启动时调用,初始化。

Android

onCreate(),返回时也会调用此方法。

onCreate()后调用onStart(),onStart()后调用onResume(),此时Activity进入运行状态。

8. 生命周期-暂停

J2ME

pauseApp(),暂停状态,如来电时,调用该接口。

Android

onPause()。

9. 生命周期-销毁

J2ME

destroyApp(),销毁状态,退出时调用。

Android

onStop(),程序不可见时调用onDestroy(),程序销毁时调用。

10. 刷新

J2ME

高级UI组件由内部刷新实现。低级UI,canvas中通过调用线程结合repaint()来刷新,让线程不断循环。低级UI架构可以用MVC方式来实现,建议使用二级缓存。

Android

高级UIHandler类通过消息的机制刷新。onDraw()刷新接口,低级UI开发者用线程控制更新,在lockCanvas()和unlockCanvasAndPost()方法之间绘制。

如果去读API,我们可以发现J2ME中Canvas的repaint()与Android中View的invalidate()/postInvalidate()方法实现了相同的功能(连说明文字几乎都一样…),但是invalidate()/postInvalidate()两者却有着区别:invalidate()
只能在UI这个线程里通过调用onDraw(Canvas
canvas)来update屏幕显示,而postInvalidate()是要在non-UI线程里做同样的事情的。这就要求我们做判断,哪个调用是本
线程的,哪个不是,这在做多线程callback的时候尤为重要。而在J2ME中,不管怎样直接调用repaint()就好了。

11. 绘画

J2ME

Displayable类。J2me中所有可显示的组件都是直接或间接的继承了Displayable,直接的是Canvas和Screen。不同的继承导致了低级
UI和高级UI的区别。J2me中现成的UI组件都是直接或者间接继承了Screen。只要调用Display.getDisplay(MIDLet
instan).setCurrrent(Displayable
disp),就可以把组件显示到手机界面上。切换界面的时候也可以使用该接口。

Android

View类。可见的组件直接或者间接继承了android.view.View。通过
Activity.setContentView(View
view)就可以显示在android手机界面上,切换界面的时候也可以使用该接口。如果是直接继承了View而不是Android自带的UI组件,那么
还要自己去实现它的刷新,类似J2me的低级UI组件。

12. 画笔

J2ME

高级UI组件由内部刷新实现。低级UI,canvas中通过调用线程结合repaint()来刷新,让线程不断循环。低级UI架构可以用MVC方式来实现,建议使用二级缓存。

Android

Canvas类,Android绘
制的时候会传入一个参数Paint。该对象表示绘制的风格,比如颜色,字体大小,字体格式等。Android的Canvas不同于J2ME的Canvas,它更像于J2ME的Graphics,用来绘制。

13. 全屏

J2ME

Canvas中SetFullScreenMode()。

Android

getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,WindowManager.LayoutParams.FLAG_FULLSCREEN);requestWindowFeature(Window.FEATURE_NO_TITLE);

14. 获得屏幕尺寸

J2ME

Canvas类的getHeight()和getWidth()

Android

Display d = getWindowManager().getDefaultDisplay();

screenWidth = d.getWidth();

screenHeight = d.getHeight();

15. 可绘区域

J2ME

int clipX = g.getClipX();

int clipY = g.getClipY();

int clipWidth = g.getClipWidth();

int clipHeight = g.getClipHeight();

g.clipRect(x, y, width, height);

g.setClip(clipX, clipY, clipWidth, clipHeight);//释放当前状态

Android

canvas.save();//保存当前状态

canvas.clipRect(x,y, x+width, y+height)

cavnas.resave();//释放当前状态

16. 清屏操作

J2ME

g.setColor(Color.WHITE);

g.fillRect(0,0,getWidth(),getHeight());

Android

// 首先定义paint

Paint paint = new Paint();

// 绘制矩形区域-实心矩形

// 设置颜色

paint.setColor(Color.WHITE);

// 设置样式-填充

paint.setStyle(Style.FILL);

// 绘制一个矩形

canvas.drawRect(new Rect(0, 0, getWidth(), getHeight()),
paint);

17. 双缓冲

J2ME

Image bufImage=Image.createImage(bufWidth, bufHeight);

Graphics bufGraphics=bufImage.getGraphics();

Android

Bitmap carBuffer = Bitmap.createBitmap(bufWidth, bufHeight,
Bitmap.Config.ARGB_4444);

Canvas carGp = new Canvas(carBuffer);

18. 图片类

J2ME

Image类,Image.createImage(path);

Android

BitMap类,BitmapFactory.decodeResource(getResources(),R.drawable.map0);

19. 绘制矩形

J2ME

drawRect的后两个参数为宽度和高度

Android

drawRect的后两个参数为结束点的坐标

20. 按键

J2ME

keyPressed()

keyRepeated()

keyReleased()

Android

onKeyDown()

onKeyUp()

onTracKballEvent()

21. 键值

J2ME

Canvas.LEFT…

Android

KeyEvent.KEYCODE_DPAD_LEFT…

22. 触笔

J2ME

pointerPressed(),pointerReleased(),pointerDragged()

Android

onTouchEvent()

23. 数据存储

J2ME

Record Management System (RMS)

Android

SQLite数据库,SharedPreferences类

24. 连接

J2ME

从Connector打开,可以直接在Connector.Open时设置连接是否可读写,以及超时设置

Android

从URL对象打开,必须使用setDoInput(boolean)和setDoOutput(boolean)方法设置,使用setConnectTimeout(int)不仅可以对连接超时进行设置,还能设置超时时间,参数为0时忽略连接超时

25. 游戏开发包

J2ME

javax.microedition.lcdui.game.*;

GameCanvas/Layer/LayerManager/ Sprite/TiledLayer

Android

无专门针对游戏的开发包。

26. 音效

J2ME

Player s=Manager.createPlayer(InputStream);

s.prepare();//创建

s.start();//播放

s.stop();//暂停

s.stop();//关闭

s.release();//释放

Android

MediaPlayer类处理背景音乐

SoundPool类处理一些简单的音效

27. 显示文本

J2ME

String

Android

TextView类

28. 打印信息

J2ME

System.out.println()

Android

Log类

二、 迁移关键点

1. 基础类和结构

J2ME程序的主体从Activity改变为MIDlet,TileView从View改变为Canvas,相关的方法也需要进行调整,但是主体结构和逻辑还是一致的。此外,有些J2ME不支持的类,需要做对应处理。

资源获取,从xml改为自行获取:

private void initSnakeView() {

// 获取图片资源

try {

imageRED_STAR =
Image.createImage(“/redstar.png”);

imageRED_STAR =
Utils.zoomImage(imageRED_STAR,mTileSize,mTileSize);

imageYELLOW_STAR =
Image.createImage(“/yellowstar.png”);

imageYELLOW_STAR =
Utils.zoomImage(imageYELLOW_STAR,mTileSize,mTileSize);

imageGREEN_STAR =
Image.createImage(“/greenstar.png”);

imageGREEN_STAR =
Utils.zoomImage(imageGREEN_STAR,mTileSize,mTileSize);

} catch(Exception e) {

Log.info(“Create Images: “+e);

}

// 设置贴片图片数组

resetTiles(4);

// 把三种图片存到Bitmap对象数组

loadTile(RED_STAR, imageRED_STAR);

loadTile(YELLOW_STAR, imageYELLOW_STAR);

loadTile(GREEN_STAR, imageGREEN_STAR);

}

ArrayList,用Vector替换掉:

// 坐标数组转整数数组,把Coordinate对象的x y放到一个int数组中——用来保存状态

private String coordVectorToString(Vector cvec) {

int count = cvec.size();

StringBuffer rawArray = new StringBuffer();

for (int index = 0; index < count;
index++) {

Coordinate c = (Coordinate) cvec.elementAt(index);

rawArray.append(c.x+”,”);

rawArray.append(c.y+”,”);

Log.info(“coordVectorToString(),
c.x=”+c.x+”,c.y=”+c.y);

}

Log.info(“coordVectorToString(),
rawArray.toString=”+rawArray);

return rawArray.toString();

}

// 整数数组转坐标数组,把一个int数组中的x y放到Coordinate对象数组中——用来恢复状态

// @J2ME 还是用Vector替换ArrayList

private Vector coordStringToVector(String raw) {

Vector coordArrayList = new Vector();

Log.info(“coordStringToVector(), raw=”+raw);

String[] rawArray = Utils.splitUtil(raw,”,”);

Log.info(“coordStringToVector(),
rawArray.length=”+rawArray.length);

int coordCount = rawArray.length;

for (int index = 0; index <
coordCount; index += 2) {

Coordinate c = new
Coordinate(Integer.parseInt(rawArray[index]),
Integer.parseInt(rawArray[index + 1]));

coordArrayList.addElement(c);

}

return coordArrayList;

}

Bundle,用RMS实现:

package com.deaboway.snake.util;

import java.io.ByteArrayInputStream;

import java.io.ByteArrayOutputStream;

import java.io.DataInputStream;

import java.io.DataOutputStream;

import javax.microedition.rms.RecordStore;

public class Bundle extends BaseRMS {

private static String[] SECTION = {

“”AL”:”,””DT”:”,

“”ND”:”,””MD”:”,

“”SC”:”,””ST”:”};

private static int LEN = SECTION.length;

private static boolean inited = false;

private static Bundle INSTANCE;

public static void INIT(){

if(inited)return;

inited = true;

INSTANCE = new Bundle();

INSTANCE.loadBundles();

}

public static Bundle getInstance(){

return INSTANCE;

}

private String[] CONTENT = new String[LEN];

private Bundle() {

super(“snake-view”);

}

public void loadBundles() {

try {

this.open();

this.close();

} catch (Exception e) {

try {

this.close();

RecordStore.deleteRecordStore(this.getRMSName());

this.open();

this.close();

} catch (Exception ex) {

}

}

}

public void resetBundles() {

try {

this.close();

RecordStore.deleteRecordStore(this.getRMSName());

this.open();

this.close();

} catch (Exception ex) {

}

}

public void updateBundles() throws Exception {

try {

this.openonly();

updateData();

if (this.getRecordStore() != null)

this.close();

} catch (Exception e) {

throw new Exception(this.getRMSName() + “::updateBundles::” +
e);

}

}

protected void loadData() throws Exception {

try {

byte[] record = this.getRecordStore().getRecord(1);

DataInputStream istream = new DataInputStream(

new ByteArrayInputStream(record, 0, record.length));

String content = istream.readUTF();

int[] start = new int[LEN+1];

for(int i =0;i<LEN;i++){

start[i] = content.indexOf(SECTION[i]);

}

start[LEN]=content.length();

for(int i =0;i<LEN;i++){

CONTENT[i] = content.substring(start[i]+5,start[i+1]);

Log.info(“CONTENT[“+i+”]=”+CONTENT[i]);

}

} catch (Exception e) {

throw new Exception(this.getRMSName() + “::loadData::” + e);

}

}

protected void createDefaultData() throws Exception {

try {

ByteArrayOutputStream bstream = new
ByteArrayOutputStream(12);

DataOutputStream ostream = new DataOutputStream(bstream);

CONTENT[0] = “9,20,9,7”;

CONTENT[1] = “1”;

CONTENT[2] = “1”;

CONTENT[3] = “600”;

CONTENT[4] = “0”;

CONTENT[5] = “7,7,6,7,5,7,4,7,3,7,2,7”;

StringBuffer sb = new StringBuffer();

for(int i=0;i<LEN;i++){

sb.append(SECTION[i]);

sb.append(CONTENT[i]);

}

ostream.writeUTF( sb.toString());

ostream.flush();

ostream.close();

byte[] record = bstream.toByteArray();

this.getRecordStore().addRecord(record, 0, record.length);

} catch (Exception e) {

throw new Exception(this.getRMSName() + “::createDefaultData::”
+ e);

}

}

protected void updateData() throws Exception {

try {

ByteArrayOutputStream bstream = new
ByteArrayOutputStream(12);

DataOutputStream ostream = new DataOutputStream(bstream);

StringBuffer sb = new StringBuffer();

for(int i=0;i<LEN;i++){

sb.append(SECTION[i]);

sb.append(CONTENT[i]);

}

ostream.writeUTF(sb.toString());

ostream.flush();

ostream.close();

byte[] record = bstream.toByteArray();

this.getRecordStore().setRecord(1, record, 0,
record.length);

} catch (Exception e) {

throw new Exception(this.getRMSName() + “::updateData::” +
e);

}

}

public String getValue(int key) {

return CONTENT[key];

}

public void setValue(int key, String value) {

CONTENT[key] = value;

}

}

Log,自己实现Log系统:

package com.deaboway.snake.util;

public class Log{

private static final int FATAL = 0;

private static final int ERROR = 1;

private static final int WARN = 2;

private static final int INFO = 3;

private static final int DEBUG = 4;

private static int LOG_LEVEL = INFO;

public static void info(String string){

if(LOG_LEVEL >= INFO){

System.out.println(“[Deaboway][ INFO] ” + string);

}

}

public static void debug(String string){

if(LOG_LEVEL >= DEBUG){

System.out.println(“[Deaboway][DEBUG] ” + string);

}

}

public static void warn(String string){

if(LOG_LEVEL >= WARN){

System.out.println(“[Deaboway][ WARN] ” + string);

}

}

public static void error(String string){

if(LOG_LEVEL >= ERROR){

System.out.println(“[Deaboway][ERROR] ” + string);

}

}

public static void fatal(String string){

if(LOG_LEVEL >= FATAL){

System.out.println(“[Deaboway][FATAL] ” + string);

}

}

}

2. TileView

(1)用Image替换BitMap,“private Image[] mTileArray;”

(2)private final Paint mPaint = new
Paint();不再需要了。直接在Graphics中drawImage即可

(3)onSizeChanged()
不会被自动调用,需要在构造函数中主动调用,以实现对应功能。onSizeChanged(this.getWidth(),this.getHeight());

(4)最重要的,用paint替换onDraw,呵呵,Canvas的核心啊!失去它你伤不起!!!咱也试试咆哮体!!!!!!

3. SnakeView

(1)J2ME 没有Handler,直接用Thread定期repaint()就OK。这里要啰嗦几句。

熟悉Windows编程的朋友可能知道Windows程序是消息驱动的,并且有全局的消息循环系统。而Android应用程序也是消息驱动的,按道理来说也应该提供消息循环机制。实际上Android中也实现了类似Windows的消息循环机制,它通过Looper、Handler来实现消息循环机制,Android消息循环是针对线程的(每个线程都可以有自己的消息队列和消息循环)。

Android系统中Looper负责管理线程的消息队列和消息循环。Handler的作用是把消息加入特定的(Looper)消息队列中,并分发和处理该消息队列中的消息。构造Handler的时候可以指定一个Looper对象,如果不指定则利用当前线程的Looper创建。

一个Activity中可以创建多个工作线程或者其他的组件,如果这些线程或者组件把他们的消息放入Activity的主线程消息队列,那么该消息就会在主线程中处理了。因为主线程一般负责界面的更新操作,并且Android系统中的weget不是线程安全的,所以这种方式可以很好的实现Android界面更新。在Android系统中这种方式有着广泛的运用。

如果另外一个线程要把消息放入主线程的消息队列,就需要通过Handle对象,只要Handler对象以主线程的Looper创建,那么调用Handler的sendMessage等接口,将会把消息放入队列都将是放入主线程的消息队列。并且将会在Handler主线程中调用该handler的handleMessage接口来处理消息。

之所以Android有这些处理,是因为Android平台来说UI控件都没有设计成为线程安全类型,所以需要引入一些同步的机制来使其刷新。而对于J2ME来说,Thread比较简单,直接匿名创建重写run方法,调用start方法执行即可。或者,也可以从Runnable接口继承。实现如下:

class RefreshHandler extends Thread {

public void run() {

while (true) {

try {

//delay一个延迟时间单位

Thread.sleep(mMoveDelay);

} catch (Exception e) {

e.printStackTrace();

}

// 更新View对象

SnakeView.this.update();

// 强制重绘

SnakeView.this.repaint();

}

}

};

(2)直接使用String代替TextView类,在Canvas的paint()中直接绘制各种提示信息。

(3)在一些地方需要主动调用repaint()进行强制重绘。

其它具体参考源代码。

4. Snake

本类就比较简单了,直接把源代码贴出来如下:

package com.deaboway.snake;

import javax.microedition.lcdui.Display;

import javax.microedition.midlet.MIDlet;

import javax.microedition.midlet.MIDletStateChangeException;

import com.deaboway.snake.util.BaseRMS;

import com.deaboway.snake.util.Bundle;

import com.deaboway.snake.util.Log;

public class Snake extends MIDlet {

public static Display display;

public static SnakeView mSnakeView;

public static MIDlet SNAKE;

public Snake() {

Bundle.INIT();

display=Display.getDisplay(this);

SNAKE = this;

mSnakeView = new SnakeView();

mSnakeView.setTextView(“”);

mSnakeView.setMode(SnakeView.READY);

display.setCurrent(mSnakeView);

}

protected void destroyApp(boolean arg0) throws
MIDletStateChangeException {

mSnakeView.saveState();

}

protected void pauseApp() {

mSnakeView.setMode(SnakeView.PAUSE);

}

protected void startApp() throws MIDletStateChangeException
{

// 检查存贮状态以确定是重新开始还是恢复状态

Log.debug(“startApp(), BaseRMS.FIRST=”+BaseRMS.FIRST);

if (BaseRMS.FIRST) {

// 存储状态为空,说明刚启动可以切换到准备状态

mSnakeView.setMode(SnakeView.READY);

} else {

// 已经保存过,那么就去恢复原有状态

// 恢复状态

if (!mSnakeView.restoreState()) {

// 恢复状态不成功,设置状态为暂停

mSnakeView.setMode(SnakeView.PAUSE);

}

}

}

}

本次就大概介绍这么多,源代码将在下次放出。下次主要讲解源代码的存储和维护,敬请期待。

【贪吃蛇—Java程序员写Android游戏】系列 2. 用J2ME实现Android的Snake Sample预览

为了让大家更好的理解J2ME和Android编程的差别,我用J2ME重新实现了Android的Snake Sample。

下次,我会详细介绍在将Snake从Android移植到J2ME上时,需要特别注意的问题,并对Android和J2ME的区别和联系进行粗略的比较。

本次,暂时把J2ME实现的运行画面列出如下。JAVA的好处就是一次编写多处运行,:-),我的PC上各种模拟器都有,因此在WTK、黑莓、Nokia
N97上都跑了跑。

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>2. <wbr>用J2ME实现Android的Snake <wbr>Sample预览2. 用J2ME实现Android的Snake Sample预览” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 2. 用J2ME实现Android的Snake Sample预览” />

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>2. <wbr>用J2ME实现Android的Snake <wbr>Sample预览2. 用J2ME实现Android的Snake Sample预览” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 2. 用J2ME实现Android的Snake Sample预览” />

【贪吃蛇—Java程序员写Android游戏】系列 <wbr>2. <wbr>用J2ME实现Android的Snake <wbr>Sample预览2. 用J2ME实现Android的Snake Sample预览” TITLE=”【贪吃蛇—Java程序员写Android游戏】系列 2. 用J2ME实现Android的Snake Sample预览” />

可运行的包也给大家一下:

http://ishare.iask.sina.com.cn/f/14364755.html

注意:向上是开始,确定键(中键、Fire键)是暂停或者退出。