J2ME简明教程 下

史默
2023-12-01

J2ME简明教程( 第七章)

MIDP2.0 Game API入门
一、 Game API结构体系
五个类构成:
GameCanvas继承自Canvas,具有Canvas提供的所有的功能,在Canvas基础上增加了便于游戏设计的功能:
1、 键盘事件处理方面:
过去要等keyPressed()/keyRelease()/keyRepeated()被调用之后才能知道按键被按下的状态。而在GameCanvas中提供了getKeyStates()方法,可以在同一个线程中自己检测按键的状态。某些设备,getKeyStates()可以检测到很多按钮同时间被按下的情形。
2、 图形绘制方法:
提供flushGraphics()方法,相当于过去repaint()再调用serviceRepaints(),而且还带有双缓冲区的概念,但flushGraphics并不会产生重绘事件,而是直接将Off-Screen的内容显示到屏幕上,所以在GameCanvas中,paint()的地位就不像过去那样重要了。
3、 图层管理:
利用LayerManager可以实现管理许多图层的功能,可以方便的将前景与背景混合在同一个画面之后再输出到屏幕上。LayerManager中可以有多个Layer子类。
二、 使用GameCanvas
每产生一个GameCanvas子类,其内部就会产生一块Off-Screen,大小与全屏幕模式的宽高相同。所以,除非必要,不要产生太多的GameCanvas,这样会占用太多的内存空间。
CameCanvas的paint()方法默认情况下就是绘出Off-Screen的内容:
public void paint(Graphics g)
{
g.drawImage(offscreen_buffer, 0, 0, 0);
}
所以,一般我们不需要在我们编写的类中重写paint()方法。
GameCanvas唯一的构造方法有一个参数,该boolean型参数的意义是:是否抑制键盘事件,true抑制,false不抑制。传入true,系统抑制大多数键盘事件的产生,keyPressed()/keyRelease()/keyRepeated()将不会被调用。传入false,则用户按下按钮,就会产生键盘事件。
可见,构造方法的第一件事就是super(true)或者super(false)。
GameCanvas中之所以可以选择抑制键盘事件的发生,是因为我们可以通过getKeyStates()取得按键被按下的状态。
注意:抑制键盘事件,只在当前画面有效。
GameCanvas中,图形都被绘制到Off-Screen上,而不是直接被绘在屏幕上。程序中调用getGraphics取得的Graphics对象,是属于Off-Screen的。
绘制好Off-Screen后,可以调用flushGraphics()将Off-Screen的内容绘制到屏幕上。flushGaphics()会等到Off-Screen真正被绘制到屏幕上才会返回。相当于过去调用repaint()再调用serviceRepaints()的功能,并且带有双缓冲概念。但flushGraphics()并不会产生重绘事件,而是直接将Off-Screen的内容显示到屏幕上,所以,调用flushGraphics()时,并不会调用paint()方法。
如果希望只是重绘Off-Screen的某些部分,可以调用flushGraphics()具有四个参数的重载方法,给定x、y、宽度、高度即可。
使用GameCanvas基本步骤:
1、 import javax.microedition.lcdui.game.* ;
import javax.microedition.lcdui.* ;
2、 extends GameCanvas;
3、 构造方法中调用super(true)或者super(false)选择抑制或者不抑制键盘事件;
4、 获得Off-Screen的Graphics实例g;
5、 利用Off-Screen的g绘图至Off-Screen并调用flushGraphics将其显示。
例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
//1.extends GameCanvas
public class BaseGameCanvas extends GameCanvas
{
private Graphics g;
public BaseGameCanvas()
{
//2.调用super()选择是否抑制键盘事件
super(true);
//3.获得Off-Screen的Graphics
g = getGraphics();
//绘图
render(g);
}
public void render(Graphics g)
{
//4.利用获取的Off-Screen在其表面绘制图形
g.setColor(127, 127, 127);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(255, 0, 0);
g.drawString("Hello the world!", 10, 50, 0);
//5.将Off-Screen输出到屏幕
flushGraphics();
}
}
三、 取得键盘状态
GameCanvas中改变了以往等待键盘事件发生再决定动作的做法,使用getKeyStates()方法主动查询键盘状态。
GameCanvas提供的键盘码常量有9个:
常量
功能
UP_PRESSED
向上方向键
DOWN_PRESSED
向下
LEFT_PRESSED
向左
RIGHT_PRESSED
向右
FIRE_PRESSED
发射
GAME_A_PRESSED
游戏A键,不是所有设备都支持
GAME_B_PRESSED
游戏B键,不是所有设备都支持
GAME_C_PRESSED
游戏C键,不是所有设备都支持
GAME_D_PRESSED
游戏D键,不是所有设备都支持
其中,UP_PRESSED、DOWN_PRESSED、LEFT_PRESSED、RIGHT_PRESSED及FIRE_PRESSED对应手机键盘上的方向键及Select键或者有的手机对应2、8、4、6
及5键。
GAME_A_PRESSED、GAME_B_PRESSED、GAME_C_PRESSED、
GAME_D_PRESSED分别对应键盘的1、3、7、9键。
在GameCanvas中获得手机键盘码并进行验证的方法如下例所示,验证了键盘是否被按下上键和下键:
int keystate = getKeyStates();
if ((keystate & GameCanvas.UP_PRESSED) != 0)
{
y = y - 2;
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
}
由于GameCanvas中键盘状态的获得并没有提供键盘监听的方法,仅仅调用getKeyStates()不能够保证一定能够监听到键盘是否已经被按下,因此需要一个无限循环语句来监听键盘事件。
while (true)
{
int keystate = getKeyStates();
… …
}
通常该循环会被写在一个线程中。
例如:
public void run()
{
long startTime = 0;
long endTime = 0;
while (loop)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) {
try
{
Thread.sleep(rate - (endTime-startTime));
}
catch (java.lang.InterruptedException e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & GameCanvas.UP_PRESSED) != 0)
{
y = y - 2;
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
}
}
四、 Sprite
Sprite就是画面上能够独立移动的图形,是为了实现游戏中角色动画、移动和碰撞检测而设计的。Game API中提供了Sprite类用来方便的建立Sprite。
Sprite类先根据读入的图像在其建立一个Raw Frame数组,另外一个Frame Sequence数组的内容都是画面的索引值。Current Frame指的是目前屏幕上显示的画面。
Sprite开发基础
分割图片:为了便于动画图片资源的管理和内存的合理使用,往往把完整动画的图像的每一帧都绘制在同一完整的图片中,所以在游戏开发的时候需要分割图片。
Sprite分割图片的规则:从左到到右,从上到下。
序号的分配:按照分割的顺序分配的。
分割的要求:既可以按照正方形来分块,也可以按照长方形分块。
Sprite的创建和使用
3个构造方法:
public Sprite(Image image)
public Sprite(Image image, int frameWidth, int frameHeight)
public Sprite(Sprite s)
其中,参数image为要分割的原始图像;参数frameWidth,frameHeight分别制定了将以什么样的宽度和高度分割原始图像。
使用步骤:
1) 创建一个用于读取图像资源的Image对象;
private Image spriteImage;

try
{
spriteImage = Image.createImage(“/picName.png”);
}
catch (Exception e)
{
}
2) 构造Sprite,可以指定以什么样的宽高分割图像:
private Sprite sprite;

sprite = new Sprite(spriteImage, 32, 32);
3) 成功创建Sprite之后,可以设置图片分割后动画播放的顺序:
可以使用setFrameSequence()方法设置动画播放的序列,该播放序列存放在一个一维数组中。
例,指定播放序号为0,1,2的图片:
private int seq[] = {0, 1, 2}
sprite.setFrameSequence(seq);
注意:数组索引是从0开始的。
4) Sprite动画播放数组设置好后,使用paint()方法可以把Sprite的一帧图像显示
在屏幕上了,例:
Graphics g;
g = this.getGraphics();
sprite.paint(g);
flushGraphics();
5) 使用sprite.nextFrame()方法可以显示下一帧图像,也可以使用setFrame(int index)指定需要播放的图片。
6) 使用sprite.setPosition(int x, int y)可以改变精灵图片在屏幕上显示位置的坐标。
五、 封装Sprite
移动坦克使其在指定区域内移动。
例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class TankSpriteCanvas extends GameCanvas implements Runnable //3.
{
private Graphics g;
private int rate = 50;
private LayerManager lm;
private Sprite tank;
private int x = 10;
private int y = 10;
private boolean isStopped = false;
public TankSpriteCanvas()
{
super(true);
g = getGraphics();
lm = new LayerManager();
tank = createTank("/res/tank.png");
lm.append(tank);
//render(g);
new Thread(this).start();
}
public Sprite createTank(String pic)
{
try
{
Image tankImg = Image.createImage(pic);
tank = new Sprite(tankImg, 32, 32);
}
catch (Exception e)
{
e.printStackTrace();
}
return tank;
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
//1.
tank.setPosition(x, y);
lm.paint(g, 0, 0);
//2.
g.setColor(0, 0, 0);
g.drawRect(10, 10, 128, 128);
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!isStopped)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) {
try
{
Thread.sleep(rate - (endTime - startTime));
}
catch (Exception e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & UP_PRESSED) != 0)
{
y = y - 2;
if (y {
y = 10;
}
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
y = y + 2;
if (y >= 138 - tank.getHeight())
{
y = 138 - tank.getHeight();
}
}
else if ((keystate & GameCanvas.LEFT_PRESSED) != 0)
{
x = x - 2;
if (x {
x = 10;
}
}
else if ((keystate & GameCanvas.RIGHT_PRESSED) != 0)
{
x = x + 2;
if (x >= 138 - tank.getWidth())
{
x = 138 - tank.getWidth();
}
}
}
}
可以看到,程序虽然可以运行,但是不够结构化,许多代码纠缠在一起,为了让程序看起来更加清晰,将程序重构如下:
//TankSprite
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class TankSprite extends Sprite
{
private int bx = 0;
private int by = 0;
private int speed = 2;
public TankSprite(Image img, int w, int h, int bx, int by)
{
super(img, w, h);
this.bx = bx;
this.by = by;
}
public void moveUp()
{
move(0, -speed);
if (getY() {
setPosition(getX(), 0);
}
}
public void moveDown()
{
move(0, speed);
if (getY() >= (by - getHeight()))
{
setPosition(getX(), by - getHeight());
}
}
public void moveLeft()
{
move(-speed, 0);
if (getX() {
setPosition(0, getY());
}
}
public void moveRight()
{
move(speed, 0);
if (getX() >= (bx - getWidth()))
{
setPosition((bx - getWidth()), getY());
}
System.out.println("********");
}
}
上述代码将角色(坦克)封装成一个独立的类。
//
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
public class TankSpriteCanvas extends GameCanvas implements Runnable //3.
{
private Graphics g;
private int rate = 50;
private LayerManager lm;
private TankSprite tank;
private boolean isStopped = false;
public TankSpriteCanvas()
{
super(true);
g = getGraphics();
lm = new LayerManager();
tank = createTank("/res/tank.png");
lm.append(tank);
//render(g);
new Thread(this).start();
}
public TankSprite createTank(String pic)
{
try
{
Image tankImg = Image.createImage(pic);
tank = new TankSprite(tankImg, 32, 32, 128, 128);
}
catch (Exception e)
{
e.printStackTrace();
}
return tank;
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
//1.
//tank.setPosition(x, y);
lm.paint(g, 10, 10);
//2.
g.setColor(0, 0, 0);
g.drawRect(10, 10, 128, 128);
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!isStopped)
{
startTime = System.currentTimeMillis();
input();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) {
try
{
Thread.sleep(rate - (endTime - startTime));
}
catch (Exception e)
{
}
}
}
}
public void input()
{
int keystate = getKeyStates();
if ((keystate & UP_PRESSED) != 0)
{
tank.moveUp();
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
tank.moveDown();
}
else if ((keystate & GameCanvas.LEFT_PRESSED) != 0)
{
tank.moveLeft();
}
else if ((keystate & GameCanvas.RIGHT_PRESSED) != 0)
{
tank.moveRight();
}
}
}
从修改结果看来,现在的程序有条理多了。
六、 Sprite的绘制
默认情况下,Layer的绘制起点(即Layer左上角的点)为相对于LayerManager起点(0, 0)的位置,可以用getX()或者getY()取得当前Sprite的绘制起点位置。
Sprite之中引入了一个称作Reference Pixel的概念,而且每个Sprite默认的Reference Pixel为坐标(0, 0)的位置。可以利用defineReferencePixel()方法来设置Reference Pixel的坐标。
Reference Pixel除了可以用来做为setRefPixelPosition()的参考位置之外,也可以当作setTransform()的参考位置。
可以利用getRefPixelX()/getRefPixelY()来得到参考点实际在LayerManager上的位置。
七、 Sprite的旋转
我们可以借助Sprite提供的名为setTransform()的方法转动Sprite。转动的时候以Reference Pixel为转动中心。如果我们希望以整张图片的中心转动,通常会把Reference Pixel设定在Sprite的中心点(getWidth()/2, getHeight()/2)。
setTransform()方法可以接受的参数有:
z TRANS_NONE,不转动坐标系;
z TRANS_ROT90,以Reference Pixel为转动中心,坐标系顺时针转动90度;
z TRANS_ROT180, 以Reference Pixel为转动中心,坐标系顺时针转动180度;
z TRANS_ROT270, 以Reference Pixel为转动中心,坐标系顺时针转动270度;
z TRANS_MIRROR,以Reference Pixel所在的X坐标为准,镜像坐标系;
z TRANS_MIRROR_ROT90, 以Reference Pixel所在的X坐标为准,镜像坐标系,再顺时针转动坐标系90度;
z TRANS_MIRROR_ROT180
z TRANS_MIRROR_ROT270
需要注意的是,这些值并没有叠加效果,也就是说,不会因为前一次转动了90度,然后再要求转动90度,而使得最后屏幕上呈现的是转动180度之后的效果。
例子:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class TankSprite extends Sprite
{
private int bx = 0;
private int by = 0;
private int speed = 2;
private int state = 0;
public TankSprite(Image img, int w, int h, int bx, int by)
{
super(img, w, h);
this.bx = bx;
this.by = by;
defineReferencePixel(w/2, h/2);
}
public void moveUp()
{
move(0, -speed);
if (getY() {
setPosition(getX(), 0);
}
}
public void moveDown()
{
move(0, speed);
if (getY() >= (by - getHeight()))
{
setPosition(getX(), by - getHeight());
}
}
public void moveLeft()
{
move(-speed, 0);
if (getX() {
setPosition(0, getY());
}
}
public void moveRight()
{
move(speed, 0);
if (getX() >= (bx - getWidth()))
{
setPosition((bx - getWidth()), getY());
}
System.out.println("********");
}
public void rotate()
{
state++;
if (state > 3)
{
state = 0;
}
setTankDirection(state);
}
public void setTankDirection(int state)
{
switch (state)
{
case 0:
setTransform(Sprite.TRANS_NONE);
setFrame(0);
break;
case 1:
setTransform(Sprite.TRANS_ROT90);
setFrame(0);
break;
case 2:
setTransform(Sprite.TRANS_ROT180);
setFrame(0);
break;
case 3:
setTransform(Sprite.TRANS_ROT270);
setFrame(0);
break;
}
}
}
在GameCanvas中按钮控制Sprite旋转:
public void input()
{
int keystate = getKeyStates();
if ((keystate & UP_PRESSED) != 0)
{
tank.moveUp();
}
else if ((keystate & GameCanvas.DOWN_PRESSED) != 0)
{
tank.moveDown();
}
else if ((keystate & GameCanvas.LEFT_PRESSED) != 0)
{
tank.moveLeft();
}
else if ((keystate & GameCanvas.RIGHT_PRESSED) != 0)
{
tank.moveRight();
}
else if ((keystate & GameCanvas.FIRE_PRESSED) != 0)
{
tank.rotate();
}
}
八、 精灵图片的参照点
Sprite中的方法,都是通过图片参照点的坐标来计算的,例如setPosition(x, y)方法、getX()、getY()都是通过参照点来计算坐标的。这些方法的一个共同特点就是参照点都是默认为图片或者屏幕的左上角,但是这个参照点是可以更改的。
Sprite类提供了一个defineReferencePixel(x, y)方法可以更改参照点。
参照点可以是动画的一帧图片中的某个位置,需要的话也可以定义在图片区域的外面。
定义了参照点之后,以后图片的移动或者放置于屏幕都将按照参照点的位置进行计算。
九、 Sprite的碰撞检测
Sprite可用于下列情况的碰撞检测:
z Sprite与Sprite ;
z Sprite与Image;
z Sprite与TiledLayer。
每种碰撞模式的检测都有两种:
z 像素检测:只有在两个不透明的像素交会时,才被判定为发生碰撞。如果是透明像素和透明像素相交会或者透明像素与非透明像素相交会,则不会判定为发生碰撞。
z 矩形检测:只要两个Sprite默认的碰撞边界相交,就被判定为发生碰撞。
(注意:Sprite的默认的碰撞边界与Sprite本身一样大,可以利用
defineCollisionRectangle()设置Sprite的碰撞边界)
像素检测的优、缺点:图片的矩形范围发生交会,并不会发生碰撞,从视觉效果上看更真实。缺点是计算复杂,浪费大量的系统资源。因此,如果没有特殊需求,我们都尽量使用矩形检测的方法。
Sprite类提供的检测碰撞的方法:
public final boolean collidesWith(Sprite s, boolean pixelLevel)
public final boolean collidesWith(TiledLayer t, boolean pixelLevel)
public final boolean collidesWith(Image image, int x, int y, Boolean pixelLevel)
方法一是检测精灵(Sprite)是否与其他精灵发生碰撞。当pixelLevel为true,使用像素检测;当pixelLevel为false时,使用矩形检测。要求两个精灵都处于可见状态。
方法二是检测精灵是否与其他TiledLayer元素发生碰撞,要求精灵与TiledLayer都处于可见状态。
方法三检测精灵是否与其他非透明的图片区域发生了相交。碰撞的判断是把精灵放置到坐标(x, y)中,然后再判断在这个坐标下精灵是否与图片发生了相交。
在进行矩形检测时,默认检测的区域就是图片的大小范围,但是可以通过使用defineCollisionRectangle方法设置精灵图片检测碰撞的区域,其原形如下:
public void defineCollisionRectangle(int x, int y, int width, int height)
十、 TiledLayer类的开发
Tiled中文译为平铺,TiledLayer类就是用图片来平铺屏幕背景的类,即常说的贴图。游戏的背景通常很大,为了显示人物移动,背景往往需要不断的滚动显示。如果背景使用一整幅很大的图片,制作以及程序的处理都将是低效的。
由于背景的很多局部画面是相同的,所以我们可以使用很多小的图片平铺成完整的背景图片。
创建TiledLayer对象,需要使用TiledLayer类的构造方法:
public TiledLayer(int columns, int rows, Image image, int tileWidth, int tileHeight)
参数:
columns和rows分别指定了图的列数和行数;
image指定了包括贴图在内所需要的所有分块的图像;
tileWidth和tileHeight指定以怎样的宽高分割图像。
例,一幅名为”tiled.png”的图片,宽192像素,高96像素
使用这张图片来创建一个游戏的背景。
1. 首先创建一个拥有3列,6行的TiledLayer对象,同时指定图片”/res/tiled.png”按照宽64像素,高32像素来分割,代码如下:
TiledLayer background;
Image tiledImage = Image.createImage(“/res/tiled.png”);
background = new TiledLayer(3, 6, titleImage, 64, 32);
(可以理解为将titleImage,以宽64,高32分割,然后放到3,6列的TiledLayer中)
由于分配规则是按照64x32的大小来分割,那么一个192x96大小的图片
被分割成9块。分割情况是3行3列,每个分割后的图片将被分配一个序号,
具体分配情况如下:
2. 假设我们要显示的地图是6行3列,那么我们定义一个相应的地图数组,在数组中指定显示图片的序号:
private byte[] titledMap = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{7, 7, 7},
{7, 7, 7},
{7, 7, 7},
};
如果数组中的数值使用0表示不显示(与Sprite不同)。如果使用的数值超过分配序号的最大数值,将会抛出异常。
3. 显示分割后的小图片需要使用setCell(int col, int row, int tileIndex)方法。其中col指定列,row指定行,tileIndex指定小图片索引。即,将在TiledLayer上的第col列,row行的图片贴为tileIndex指定的图片。
Graphics g = getGraphics();
background.setCell(2, 3, 3);
background.paint(g);
flushGraphics();
范例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Allan
*/
public class TiledLayerCanvas extends GameCanvas
{
private Graphics g;
private Image tiledImage;
private TiledLayer background;
//定义地图数组
private byte[][] tiledMap = {
{1, 2, 3},
{4, 1, 6},
{7, 8, 9},
{7, 7, 7},
{7, 7, 7},
{7, 7, 7}
};
public TiledLayerCanvas()
{
super(true);
g = getGraphics();
try
{
tiledImage = Image.createImage("/res/tiled.png");
}
catch (Exception e)
{
}
background = new TiledLayer(3, 6, tiledImage, 64, 32);
render(g);
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
for (int i=0; i{
for (int j=0; j{
//TiledLayer的图片索引从1开始,0不显示
background.setCell(j, i, tiledMap[i][j]);
}
}
background.paint(g);
flushGraphics();
}
}
现在我们只有一个背景,可以加入行走的精灵。
//MamSprite.java
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Allan
*/
public class ManSprite extends Sprite
{
private int bx;
private int by;
private int speed = 2;
public ManSprite(Image img, int w, int h, int bx, int by)
{
super(img, w, h);
this.bx = bx;
this.by = by;
}
public void moveDown()
{
move(0, speed);
}
public void walk(int x, int y)
{
setPosition(x, y);
}
… …
}
//GameCanvas.java
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Allan
*/
public class TiledLayerCanvas extends GameCanvas implements Runnable
{
private Graphics g;
private int rate = 150;
private boolean exit = false;
private Image tiledImage;
private TiledLayer background;
//定义地图数组
private byte[][] tiledMap = {
{1, 2, 3},
{4, 1, 6},
{7, 8, 9},
{7, 7, 7},
{7, 7, 7},
{7, 7, 7}
};
private ManSprite man;
private int[] down_seq = { 0, 1, 2};
public TiledLayerCanvas()
{
super(true);
g = getGraphics();
try
{
tiledImage = Image.createImage("/res/tiled.png");
}
catch (Exception e)
{
}
background = new TiledLayer(3, 6, tiledImage, 64, 32);
//建立精灵
man = createManSprite("/res/man.png");
man.setFrameSequence(down_seq);
new Thread(this).start();
}
public ManSprite createManSprite(String pic)
{
Image manImg = null;
try
{
manImg = Image.createImage(pic);
}
catch (Exception e)
{
e.printStackTrace();
}
return new ManSprite(manImg, 32, 48, getWidth(), getHeight());
}
public void render(Graphics g)
{
g.setColor(0x00FFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
//绘制背景
for (int i=0; i{
for (int j=0; j{
//TiledLayer的图片索引从1开始
background.setCell(j, i, tiledMap[i][j]);
}
}
background.paint(g);
//绘制人物
man.nextFrame();
man.walk(getWidth()/2, getHeight()/2);
man.paint(g);
//绘制Off-Screen
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!exit)
{
startTime = System.currentTimeMillis();
render(g);
endTime = System.currentTimeMillis();
if ((endTime-startTime) {
try
{
Thread.sleep(rate-(endTime-startTime));
}
catch (Exception e)
{
}
}
}
}
}
运行效果:
十一、 LayerManager类的开发
一款游戏的背景通常不是仅由一个图片的贴图构成,可以使用多个图层构成复杂的背景。
LayerManager类可以用来管理多个图层,可将这些图层组合起来显示于屏幕上。LayerManage不仅可以组合图层,还可以控制图层是否显示于屏幕;设置具体显示窗口,可以仅在LayerManager定义的窗口上显示背景的一部分。
可以使用LayerManager的append()方法添加Layer。每个Layer都会有个索引值,从0开始,索引值越小表示距离屏幕越近,越大距离屏幕越远。
若加入时,该Layer已经存在于LayerManager之中,那么该Layer会先从LayerManager中删除,然后再加入。
可以利用remove()将某个Layer从LayerManager中删除,索引值会被重新索引。
可以利用insert()在任意位置插入Layer。
若插入时,该Layer已经存在于LayerManager之中,那么该Layer会先从LayerManager中删除,然后再进行插入。
可以利用getLayer()取得制定索引的Layer,getSize()取得LayerManager中Layer的数量。
LayerManager内部有一个坐标系,该坐标系与外界无关。可以把LayerManager本身当作一个虚拟屏幕来考虑,不管外部的干扰。
LayerManager类的使用:
z 使用LayerManager类的构造方法创建LayerManager对象;
z 使用append()方法添加图层,先添加的图层将显示在屏幕的最前面。
例:
LayerManager lm;
lm = new LayerManager();
//添加图层3,最先加入,最先显示
lm.append(layer_3);
lm.append(layer_2);
lm.append(layer_1);
实例:将下面2幅图片分割后构成2个背景图层,然后利用LayerManager将这2个图层结合起来。
图片1 图片2
第一个图层由图片1分割为3x3共9个小图片贴图而成,每个小图片宽64,高32,图层在屏幕上的坐标为(0,0)。数据表示如下:
{
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{7, 7, 7},
{7, 7, 7},
{7, 7, 7}
}
第二个图层由图片2分割为1x2共2个小图片贴图而成,每个小图片宽28,高59。该图层在屏幕上的坐标为(0, 屏幕高度减去59)。其数据表示:
{
{2, 2, 2, 2},
{2, 2, 2, 2}
}
例:
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Allan
*/
public class MyGameCanvas extends GameCanvas
{
private Graphics g;
//准备背景TiledLayer
private Image bgImg1;
private Image bgImg2;
private TiledLayer bg1;
private TiledLayer bg2;
private LayerManager lm;
//背景1数组
private byte[][] bg1Map = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9},
{7, 7, 7},
{7, 7, 7},
{7, 7, 7}
};
//背景2数组
private byte[][] bg2Map = {
{2, 2, 2, 2},
{2, 2, 2, 2}
};
public MyGameCanvas()
{
super(true);
g = getGraphics();
//准备背景TiledLayer
bgImg1 = loadImage("/res/tiled1.png");
bgImg2 = loadImage("/res/tiled2.png");
bg1 = new TiledLayer(3, 6, bgImg1, 64, 32);
bg2 = new TiledLayer(4, 2, bgImg2, 59, 28);
//图层管理
lm = new LayerManager();
//移动背景2到屏幕下方
bg2.move(0, this.getHeight()-59);
//先将背景2加入LayerManager,则背景2最先加入,最先显示
lm.append(bg2);
lm.append(bg1);
render(g);
}
//获取图片的方法
public Image loadImage(String pic)
{
Image tmp = null;
try
{
tmp = Image.createImage(pic);
}
catch (Exception e)
{
}
return tmp;
}
//绘制屏幕的方法
public void render(Graphics g)
{
//绘出背景1
for (int i=0; i{
for (int j=0; j{
bg1.setCell(j, i, bg1Map[i][j]);
}
}
//绘出背景2
for (int i=0; i{
for (int j=0; j{
bg2.setCell(j, i, bg2Map[i][j]);
}
}
//绘出LayerManager
lm.paint(g, 0, 0);
//绘出Off-Screen
flushGraphics();
}
}
结果如下:
控制可视区域:
LayerManager类除了提供了图层合并的功能以外,还提供了控制图层显示区域大小的功能。
显示区域(View Window):就是相对于LayerManager默认坐标系统位置的一个可视区域, 即LayerManager让屏幕显示出来的部分。例如,背景图片很大,但限于手机屏幕的大小,只能在屏幕上显示一部分图像,这个显示的部分就是显示区域。
通过更改显示区域的坐标,可以达到背景移动的效果。
使用setViewWindow()方法设置View Window的大小:
layerManger.setViewWindow(30, 40, 50, 50);
显示区域的优点,每次设置新的显示区域后,它会自动清除前一次显示的图像,不需要开发人员编写自己的清除功能的代码。
如图,显示区域为85x85像素,相对于LayerManager坐标系统的坐标为(52, 11)。而LayerManager添加了一个背景图层,相对于LayerManager坐标系统的坐标为(18, 37)。另外添加了一个精灵的图层,其相对于LayerManager坐标系统的坐标为(75, 25)。
LayerManager坐标原点的设置:
LayerManager坐标系统原点默认为屏幕左上角,即屏幕坐标原点为(0,0)。可以更改LayerManager坐标系统的原点在屏幕上的位置,更改相对坐标可以在使用paint()方法把图层绘制在屏幕上的时候指定。paint()方法的原型:
public void paint(Graphics g, int x, int y)
参数x,y指定相对坐标。
指定相对坐标为(17, 17)
例子:
//ManSprite.java
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Administrator
*/
public class ManSprite extends Sprite
{
private int speed = 5;
public ManSprite(Image img, int w, int h)
{
super(img, w, h);
}
public void moveRight()
{
move(speed, 0);
}
public void moveTo(int x, int y)
{
this.setPosition(x, y);
}
}
//MyGameCanvas.java
import javax.microedition.lcdui.game.*;
import javax.microedition.lcdui.*;
/**
*
* @author Allan
*/
public class MyGameCanvas extends GameCanvas implements Runnable
{
private Graphics g;
private int rate = 150;
private boolean exit = false;
//准备精灵
private Image manImg;
private ManSprite man;
//准备背景TiledLayer
private Image bgImg;
private TiledLayer background;
private LayerManager lm;
private int[] right_seq = {9, 10, 11};
private int drawX = 0;
private int drawY = 0;
private int dx = 0;
private int dy = 0;
public MyGameCanvas()
{
super(true);
g = getGraphics();
//准备精灵
manImg = loadImage("/res/man.png");
man = new ManSprite(manImg, 32, 48);
man.setFrameSequence(right_seq);
drawX = getWidth()/2;
drawY = getHeight() - manImg.getHeight()/3;
//准备背景TiledLayer
bgImg = loadImage("/res/bg.png");
background = new TiledLayer(1, 1, bgImg, 586, 121);
background.setCell(0, 0, 1);
//图层管理
lm = new LayerManager();
//先将man加入LayerManager,则man最先加入,最先显示
man.setPosition(drawX, drawY);
lm.append(man);
//移动背景到屏幕下方
background.move(0, this.getHeight()-bgImg.getHeight());
lm.append(background);
new Thread(this).start();
}
//获取图片的方法
public Image loadImage(String pic)
{
Image tmp = null;
try
{
tmp = Image.createImage(pic);
}
catch (Exception e)
{
System.out.println("************");
}
return tmp;
}
//绘制屏幕的方法
public void render(Graphics g)
{
//绘出LayerManager
lm.paint(g, 0, 0);
//绘出Off-Screen
flushGraphics();
}
public void run()
{
long startTime = 0;
long endTime = 0;
while (!exit)
{
startTime = System.currentTimeMillis();
man.nextFrame();
man.moveTo(drawX + dx, drawY);
lm.setViewWindow(dx, 0,
getWidth(), getHeight());
render(g);
//当显示区域已经移动到差一个屏幕宽度
if (dx {
dx = dx + 5;
}
else
{
dx = 0;
}
endTime = System.currentTimeMillis();
if ((endTime-startTime) {
try
{
Thread.sleep(rate - (endTime-startTime));
}
catch (Exception e)
{
}
}
}
}
}
运行效果:

第9章 无线网络开发

MIDP提供了一组通用的网络开发接口,用来针对不同的无线网络应用可以采取不同的开发接口。基于CLDC的网络支持是由统一网络连接框架(Generic Connection Frameword, 简称GCF)定义的。其相关类、接口以及异常都放置在javax.microedtion.io包中。

在CLDC之中定义了七个接口,它们分别是:

1. Connection

2. StreamConnectionNotifier

3. InputConnection

4. OutputConnection

5. DatagramConnection

6. StreamConnection

7. ContentConnection

直接继承自Connection的有四个类:StreamConnectionNotifier、DatagramConnection、InputConnection和OutputStream。其中,StreamConnectionNotifier只要提供Socket开发接口。DatagramConnection提供UDP开发接口。由于需要对网络传输的数据输入和输出进行控制,因此提供了InputConnection和OutputConnection开发接口。

通用的网络开发接口都是继承自Connection接口。

GCF继承体系

MIDP2.0中,新添加了3个网络开发接口:

z javax.microedition.io.SocketConnection 负责TCP/IP方面的网络开发

z javax.microedition.io.ServerSocketConnection 负责TCP/IP方面的网络开发

z javax.microedition.io.UDPDatagramConnection 负责UDP方面的开发

网络开发中,最重要的一个连接类是HttpConnection接口, 其继承自ContentConnection。HttpConnection中定义了大量的基本联网和获取数据的操作。Http联网功能是MIDP规范中要求厂商必须支持的连接方式,而其它方式,则取决于厂商与网络服务商的设备支持情况。

也就是说,只有HTTP传输协议才是能够在各家平台上使用的对外沟通的方式。

GCF规范提出不管使用何种网络或者本地文件的连接方式,所有的连接都使用Connector的open(URL)方法创建一个新的网络连接(Connection.open(url)方法将返回一个Connection对象):

URL的格式如下;

://:@:/;

协议如: http、https、socket方式等。资源所在主机代表资源所在位置的主机名称或者IP地址。资源路径的格式和使用的协议有关,有些协议会有额外的参数需要设定。

1) 创建Http连接

Connector.open(“http://www.sun.com.cn”);

2) 创建Socket连接

Connector.open(“socket://127.0.0.1:8080”);

3) 创建Datagram连接

Connector.open(“datagram://www.sun.com.cn:9000”);

4) 创建本地文件连接

Connector.open(“file:/input.txt”);

Connector.open()方法有3个重载的方法:

public static Connection open(String name)

public static Connection open(String name, int mode)

public static Connection open(String name, int mode, Boolean timeout)

参数:

name指定网络地址

mode指定网络连接的方式,分为读、写和读写三种方式

timeout指定了网络连接超时的时间。如果网络连接超时,则抛出异常

一、 MIDP开发HTTP程序

使用Connector.open()方法获得一个网络连接以后,就可以调用相应的Connection类的方法获得需要的信息。

利用Connection的openInputStream()方法可以获得网络传输过来的流数据。

(一) 使用StreamConnection接口

StreamConnection接口继承了InputConnection和OutputConnection接口,因此创建一个StreamConnection对象可以返回一个获得网络传输数据的流对象,可以使用返回的流对象对数据进行整理,再选取需要的数据显示在屏幕上。

流程如下:

1. 创建一个StreamConnection类型的网络连接

StreamConnection sc = null;

sc = (StreamConnection)Connector.open(url);

2. 创建了StreamConnection类型的网络连接以后,可以调用openInputStream()方法返回一个InputStream对象,通过该对象获得流数据。也可以使用openDataInputStream()方法返回一个DataInputStream对象。

InputStream is = null;

is = sc.openInputStream();

3. 通过数据对象操作获得的数据:

StringBuffer sb = new StringBuffer();

int ch;

while ((ch = is.read()) != -1)

{

sb.append((char)ch);

}

System.out.println(sb.toString());

4. 操作数据完成,关闭连接对象。

is.close();

练习:用StreamConnection实现读取网页内容。

(二) 使用HttpConnection接口

使用HttpConnection接口的开发流程与使用StreamConnection接口基本一样。由于HttpConnection接口继承了StreamConnection接口,不但拥有StreamConnection接口的功能,还扩展了许多功能。

例:

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

import javax.microedition.io.*;

import java.io.*;

/**

*

* @author Allan

* @version

*/

public class HttpConnectionMidlet extends MIDlet

{

private Display display;

public HttpConnectionMidlet()

{

display = Display.getDisplay(this);

}

public void startApp()

{

TextBox tb = new TextBox("Browser", "", 10000, TextField.ANY);

String url = "http://openzone.ipchina.org/index.jsp";

HttpConnection hc = null;

try

{

hc = (HttpConnection)Connector.open(url);

InputStream is = hc.openInputStream();

StringBuffer sb = new StringBuffer();

int ch;

while ((ch = is.read()) != -1)

{

sb.append((char)ch);

}

//System.out.println(sb.toString());

tb.setString(sb.toString());

display.setCurrent(tb);

}

catch (Exception e)

{

}

finally

{

if (hc != null)

{

try

{

hc.close();

}

catch (Exception e)

{

}

}

}

}

public void pauseApp()

{

}

public void destroyApp(boolean unconditional)

{

}

}

(三) 显示服务器信息

HttpConnection接口提供了多个获得服务器信息的方法。

获得了一个HttpConnection的连接对象以后,不需要使用openInputStream()方法就可以直接返回服务器信息(访问资源的信息)。

例:

import javax.microedition.midlet.*;

import javax.microedition.lcdui.*;

import javax.microedition.io.*;

import java.io.*;

/**

*

* @author Administrator

* @version

*/

public class ServerInfoMidlet extends MIDlet

{

private Display display;

public ServerInfoMidlet()

{

display = Display.getDisplay(this);

}

public void startApp()

{

Form f = new Form("");

String url = "http://www.google.com.cn/index.html";

HttpConnection hc = null;

StringBuffer sb = new StringBuffer();

try

{

hc = (HttpConnection)Connector.open(url);

//要显示的服务器的信息

sb.append("Host: " + hc.getHost());

sb.append("/nPost: " + hc.getPort());

sb.append("/nDate: " + hc.getDate());

sb.append("/nProtocol: " + hc.getProtocol());

sb.append("/nFile: " + hc.getFile());

sb.append("/nType: " + hc.getType());

//在屏幕上显示信息

f.append(sb.toString());

display.setCurrent(f);

}

catch (Exception e)

{

e.printStackTrace();

}

finally

{

if (hc != null)

{

try

{

hc.close();

}

catch (Exception e)

{

}

}

}

}

public void pauseApp()

{

}

public void destroyApp(boolean unconditional)

{

}

}

(四) HTTP连接方式的简单应用

1. 下载观看图片

从网络上下载图片是通过二进制数据传输的,因此可以使用Image的重载方法:

Image createImage(byte[] imageData, int imageOffset, int imageLength)

Image createImage(InputStream stream) MIDP2.0提供

将图片的数据存储到字节数组imageData或者输入流对象stream中,就可以使用createImage()方法将它们转换为Image对象,以便在屏幕上显示出来。

使用方法1:

import javax.microedition.lcdui.*;

import javax.microedition.io.*;

import java.io.*;

/**

*

* @author Allan

*/

public class MyForm extends Form implements CommandListener, Runnable

{

private TextField tfUrl;

//下载的图像

private ImageItem iiImage;

public MyForm()

{

super("");

tfUrl = new TextField("图片地址", "", 100, TextField.URL);

iiImage = new ImageItem("Image", null, Item.LAYOUT_CENTER, "图片位置");

iiImage.setPreferredSize(getWidth(), 100);

append(tfUrl);

append(iiImage);

addCommand(new Command("下载", Command.OK, 1));

setCommandListener(this);

}

public Image getImage(String url)

{

HttpConnection hc = null;

//供返回的image对象

Image image = null;

try

{

hc = (HttpConnection)Connector.open(url);

DataInputStream dis = hc.openDataInputStream();

byte[] imageData;

int len = (int)hc.getLength();

if (len != -1)

{

imageData = new byte[len];

dis.readFully(imageData);

}

else

{

//未知长度,则按照字节的方式读取数据

ByteArrayOutputStream baos = new ByteArrayOutputStream();

int ch;

while ((ch = dis.read()) != -1)

{

baos.write(ch);

}

imageData = baos.toByteArray();

baos.close();

}

if (dis != null)

{

dis.close();

}

//根据二进制数据创建一个Image

return Image.createImage(imageData, 0, imageData.length);

}

catch (Exception e)

{

e.printStackTrace();

return null;

}

finally

{

if (hc != null)

{

try

{

hc.close();

}

catch (Exception e)

{

}

}

}

}

public void commandAction(Command c, Displayable s)

{

String cmd = c.getLabel();

if (cmd.equals("下载"))

{

new Thread(this).start();

}

}

public void run()

{

Image tmp = this.getImage(tfUrl.getString().trim());

iiImage.setImage(tmp);

}

}

2. 方法二:

public Image getImage2(String url)

{

HttpConnection hc = null;

//供返回的image对象

Image image = null;

try

{

hc = (HttpConnection)Connector.open(url);

//判断连接是否成功

if (hc.getResponseCode() != HttpConnection.HTTP_OK)

{

return null;

}

//取得图像二进制数据

DataInputStream dis = hc.openDataInputStream();

image = Image.createImage(dis);

dis.close();

//根据二进制数据创建一个Image

return image;

}

catch (Exception e)

{

e.printStackTrace();

return null;

}

finally

{

if (hc != null)

{

try

{

hc.close();

}

catch (Exception e)

{

}

}

}

}

(五) 练习:文本文件查看器,选择保存将其保存到Restor Store中,可供以后观看。

(六) 用HTTP方式与服务器交互信息

使用Get方式与服务器交互信息,则具体的开发流程:

1. 设置Get访问方式的Http地址:

String url = “http://127.0.0.1:8080/MyTest/hello?username=abc&password=456”;

2. 创建HttpConnection类型的网络连接。

HttpConnection hc = (HttpConnection)Connector.open(url);

3. 调用HttpConnection的setRequestMethod()方法设置与服务器交互信息的类型为GET类型:

hc.setRequestMetho(HttpConnection.GET);

4. 服务器获得请求后,会调用doGet()方法,但MIDlet并不知道服务器是否正确获得请求,可以使用下面语句判断:

if (hc.getResponseCode() == HttpConnection.HTTP_OK)

{

//读取服务器信息

… …

}

5. 读取信息后,显示结果。

(七) 使用Socket网络开发接口

MIDP提供了对开发Socket程序的支持,但是由于Socket程序并不是MIDP2.0中要求移动设备厂商必须支持的协议,所以有可能实际应用并不能使用,所以需要根据当地的设备情况进行选择性的开发。

1. 什么是Socket? 客户机/服务器?

可以把Socket看成是两个手机程序进行通讯连接中的一个端点,一个程序将一段信息写入Socket,该Socket将这段信息发送给另一个Socket中,使这段信息能传送到另一个手机程序中。

手机的Socket传输方式可以理解为手机的客户机/手机的服务器的交流模式。

客户机/服务器在分布处理过程中,使用基于连接的网络通信模式。该通信模式首先在客户机和服务器之间定义一套通信协议,并创建一Socket类,利用这个类建立一条可靠的链接;然后,客户机/服务器再在这条链接上可靠地传输数据。

客户机发出请求,服务器监听来自客户机的请求,并为客户机提供响应服务。这就是典型的“请求——应答”模式。

手机客户机/服务器典型运作过程:

1) 手机服务器监听响应端口的输入;

2) 手机客户机发出一个请求;

3) 手机服务器接收到此请求,处理请求,并把请求结果返回给手机客户机;

4) 重复上述过程,直至完成一次信息交互过程。

利用以上过程,可以使用MIDP编写作为服务器和客户机的手机应用程序。一个作为服务器端的手机应用程序在负责监听另外一个作为客户机的手机程序的请求,为每个

作为客户机请求的手机程序建立Socket连接,从而为作为客户机的手机程序提供服务。

2. 开发Socket点到点程序

点到点程序使用的是服务器、客户机的“请求——应答”模式,所以开发Socket程序可以分为开发服务器端和客户端程序两种

开发手机服务器端程序步骤如下:

步骤一:建立服务器端的Socket监听端口,监听所有客户机的连接请求。创建一个具体的连接是使用ServerSocketConnection类来实现的。

监听端口的字符串格式:

socket://:port

由于服务器从本地端口监听客户端连接,所以不需要指定本机器的IP地址,只需要指定监听的端口port就可以了。

创建一个监听端口为8859的ServerSocketConnection的代码如下:

String url = “socket://:8859”;

ServerSocketConnection ssc;

ssc = (ServerSocketConnection)Connector.open(url);

步骤二:建立收发客户机信息的SocketConnection。ServerSocket仅仅是创建监听端口,具体的信息交流需要通过SocketConnection来实现。可以调用ServerSocketConnection的acceptAndOpen()方法返回一个SocketConnection类的对象。然后利用这个返回的对象操作客户端传来的信息:

SocketConnection sc;

sc = (SocketConnection)scc.acceptAndOpen();

acceptAndOpen()一直监听客户端是否请求连接服务器,如果没有发现任何请求,则会一直监听,直到有连接请求才会返回一个SocketConnection对象。

步骤三:可以通过返回的SocketConnection对象设置服务器的监听属性:

sc.setSocketOption(DELAY, 0); //设置延迟

sc.setSocketOption(LINGER, 0); //设置生存时间

sc.setSocketOption(KEEPALIVE, 0); //持续状态

sc.setSocketOption(RCVBUF, 128); //获得字节数

sc.setSocketOption(SNDBUF, 128); //发送字节数

步骤四:通过服务器端监听客户机连接成功后的SocketConnection对象与客户机进行数据的接收和发送。

DataInputStream dis = sc.openDataInputStream();

DataOutputStream dos = sc.openDataOutputStream();

String result = dis.readUTF();

dos.writeUTF(result);

步骤五:关闭/释放资源

dis.close();

dos.close();

sc.close();

ssc.close();

开发手机客户端程序步骤如下:

步骤一:建立手机客户端的Socket连接端口,必须指定连接地址,其格式如下:

socket://主机地址:端口号

例:

String url = “socket://127.0.0.1:8859”;

SocketConnection sc;

sc = (SocketConnection)Connector.open(url);

步骤二:利用SocketConnection的read()方法和write()方法接收和发送数据。

步骤三:释放各种资源。

(八) 开发Datagram程序

MIDP提供了对Datagram程序的支持,但是由于Datagram程序并不是MIDP2.0中要求移动厂商必须支持的协议,所以有可能实际应用并不能使用,因此像Socket程序的开发一样,需要根据当地的设备情况进行选择性的开发。

1、 Datagram是什么?

Socket是针对TCP/IP协议开发的,是一种面向连接的保证可靠传输的协议。发送方与接收方在成对的两个Socket之间建立连接,以便在TCP基础上进行通信。

Datagram(数据报)则是针对UDP协议进行开发的,是非面向连接的,不能保证数据可靠到达。

每个数据报都是一个独立的信息包,包括完整的源地址或目的地址,它在网络上以任何可能的路径传往目的地,因此能否到达目的地,到达目的地的时间以及内容的正确性都是不能被保证的。

使用UDP时,每个数据报中都给出了完整的地址信息,因此无需建立发送方和接收方的连接。

使用UDP传输数据时是有大小限制的,每个被传输的数据报必须限定在64KB之内。而TCP则没有这方面的限制。

协议

面向连接

可靠传输

数据大小限制

TCP

UDP

64KB

UDP协议不可或缺的原因:

z 可靠传输需要付出代价,对数据内容的正确性检验占用计算机处理时间和网络的带宽,TCP的传输效率不如UDP。

z 许多应用程序并不需要严格的传输可靠性,比如视频会议系统,并不要求音频/视频数据的绝对正确,只要保证连贯就可以了,这种情况使用UDP更合理。

2、 开发Datagram程序流程

MIDP的Datagram点到点程序和开发Socket点到点的程序流程相似,都是C/S的交流方式。

Datagram程序继承于DataInput和DataOutput类,Sun公司开发这类的目的是为了提供一个简单的途径读取和发送二进制数据,替代使用getData()和sendData()方法。

使用Datagram的read/write方法的时候,读取数据的指针会自动增加。与Socket程序的读取数据不同的是Datagram程序每次写入一个数据报之前,都要用reset()方法复位,例如下面的代码写入了数据包信息:

DatagramConnection connection;

datagram = connection.new Datagram(max);

//重设并准备重新获得数据

datagram.reset();

//writeUTF会自动增加数据报的长度

datagram.writeUTF(“hello the world!”);

connection.send(datagram);

下面的代码读取一个单个的数据报信息:

datagram = connection.new Datagram(max);

connection.receive(datagram);

message = datagram.readUTF();

服务器端监听的地址编写格式是”datagram://:8859”。

 类似资料: