当前位置: 代码迷 >> Android >> 【贪吃蛇—Java软件工程师写Android游戏】系列 3. 用J2ME实现Android的Snake Sample详解
  详细解决方案

【贪吃蛇—Java软件工程师写Android游戏】系列 3. 用J2ME实现Android的Snake Sample详解

热度:103   发布时间:2016-05-01 16:48:24.0
【贪吃蛇—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 {      /** Called when the activity is first created. */      @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 :

// 首先定义paintPaint 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. 键值

J 2ME :

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 实现:

    /**      * <p>Title: Snake</p>      * <p>Copyright: (C) 2011 Gavin's Snake project. Licensed under the Apache License, Version 2.0 (the "License")</p>      * @author Gavin      */      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 系统:

    /**      * <p>Title: Snake</p>      * <p>Copyright: (C) 2011 Gavin's Snake project. Licensed under the Apache License, Version 2.0 (the "License")</p>      * @author Gavin      */      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 类

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

    /**      * <p>Title: Snake</p>      * <p>Copyright: (C) 2011 Gavin's Snake project. Licensed under the Apache License, Version 2.0 (the "License")</p>      * @author Gavin      */      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;      /**      * Snake: a simple game that everyone can enjoy.      *       * 贪吃蛇: 经典游戏,在一个花园中找苹果吃,吃了苹果会变长,速度变快。碰到自己和墙就挂掉      *       */      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);                  }              }          }      }  



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

?

1 楼 archangelwin 2011-04-26  
期待中  
2 楼 deaboway 2011-04-26  
archangelwin 写道
期待中  



已经放出来了,参考:http://www.iteye.com/topic/1013918
3 楼 luozhong915127 2012-02-10  
呵呵额,有点功夫。
  相关解决方案