J2ME手机游戏设计与开发
上QQ阅读APP看本书,新人免费读10天
设备和账号都新为新人

2.2 建立MIDlet类

Midlet Suite中至少要包含一个MIDlet类,它是整个程序执行的入口处,由于MIDlet类是抽象类,所以需要定义一个类来继承MIDlet类。

首先由于MIDlet类位于包javax.microedtion.midlet中,因此第一步需要在代码程序中引入这个包:

import javax.microedtion.midlet.*;

注意:不要写成“import javax.microedtion.MIDlet.*;”。

接着需要扩展MIDlet类,它的类名就是要编写的主类名,扩展后的代码如下:

import javax.microedition.midlet.MIDlet;
public class MyMIDlet extends MIDlet {
}

从Java语法上说,这样一个空类是可以编译的,但实际上用Eclipse试图编译的时候会拒绝通过,这是因为MIDlet类有3个抽象方法是必须实现的:

protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
}
    protected void pauseApp() {
    }
    protected void startApp() throws MIDletStateChangeException {
    }

当然,可以选择直接新建MIDlet类,那样就简单很多了。

选择“新建”→“J2ME Midlet”命令,如图2-6所示,就会打开如图2-7所示的“J2ME Midlet”设置向导,设置类的名称和相关属性。

图2-6 建立MIDlet类

图2-7 “J2ME Midlet”设置向导

注意:由于建立的是MIDlet类,所以在超类处已经直接显示为“javax.microedition.midlet.MIDlet”类,通过选择“Unimplemented abstract methods”选项可以实现MIDlet类的3个抽象方法。

当设置结束完成后,系统会自动生成如下代码:

import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class Demo extends MIDlet {
public Demo() {
        // TODO 自动生成构造函数存根
}
protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
        // TODO 自动生成方法存根
    }
    protected void pauseApp() {
        // TODO 自动生成方法存根
    }
    protected void startApp() throws MIDletStateChangeException {
        // TODO 自动生成方法存根
    }
}

2.2.1 midlet包介绍

通过javax.microedition.midlet包,MIDP定义了MIDlet的生命周期,以及MIDlet与运行环境的交互方式。javax.microedition.midlet包总共包含两个类:MIDlet和MIDletStateChangeException。

(1)MIDlet是一个抽象类,它是所有MIDP应用程序必须要扩展的父类。MIDlet类确保在运行环境中应用程序的必要方法在适当的时机被执行。开发者必须要实现MIDlet中定义的3个抽象方法,即startApp()、pauseApp()和destroyApp()。

(2)MIDletStateChangeException是一个异常类。它是用来监测MIDP应用程序状态的改变的,当MIDlet从一个状态转移到另外一个状态失败的情况下就会被抛出。

midlet包整个层次结构如图2-8所示。

图2-8 midlet包层次结构

MIDlet是MIDP应用程序的入口类,与Java应用程序中含有main()方法的类相似,在J2ME中管理MIDlet的是应用程序管理软件。

MIDlet初始化后需要有其他的资源与之配合才能正常工作。例如,创建所需的对象(用户界面及功能类等)、从持久性存储空间读取数据、新建线程并行处理任务及建立网络连接读取数据等。

2.2.2 MIDlet类的生命周期

1.MIDlet的状态

J2ME程序都是从MIDlet类开始执行的,系统规定了MIDlet的生命周期。规定MIDlet程序有3种状态:运行状态、暂停状态和销毁状态。

(1)运行状态。当一个MIDlet进入运行状态时,它将获得用于执行任务的所有资源。转移到运行状态之后,所需的线程应该被启动。

(2)暂停状态。当一个MIDlet进入暂停状态时,它应该释放所有持有的资源并停止活动的线程。如果有需要,则应该把数据保存到持久性存储器中,这样在程序重新进入活动状态时可以重用。

(3)销毁状态。当一个MIDlet进入销毁状态时,它应该释放所有资源、停止正在执行的线程并保存持久性的数据。

系统在执行MIDlet程序时,首先构造一个MIDlet类型的对象,然后使程序进入到暂停状态,按照生命周期的规定,系统会自动调用MIDlet对象的startApp()方法使程序进入到运行状态,开始程序的执行。如果在创建MIDlet对象的过程中,或者是调用startApp()的方法中发生了异常,则系统会自动调用MIDlet对象的destroyApp()方法进行到销毁状态,也就是使程序退出。

2.状态转换

应用程序管理软件(AMS)使用如图2-9所示的生命周期方法来控制MIDlet的状态。

默认构造器。当用户启动一个MIDlet时,AMS创建一个新的MIDlet实例,通过调用默认构造器来执行初始化工作。当构造器返回后,MIDlet被置于暂停状态。如果在构造器执行期间抛出异常,那么MIDlet立即进入销毁状态。

图2-9 MIDlet类的生命周期

public void startApp()方法。在MIDlet处于暂停状态的时候,AMS调用startApp()方法指示MIDlet应该进入活动状态。startApp()方法可能会抛出MIDletStateChangeException,这个异常说明MIDlet现在不能启动,但是可能稍后会启动。如果阻止MIDlet启动的错误是短暂性的,那么可以捕获这个异常并从错误中恢复。如果这个错误是非短暂性的,那么应该调用notifyDestroyed()方法,MIDlet进入销毁状态。

public void pauseApp()方法。当MIDlet在活动状态时,AMS调用这个方法指示MIDlet将从活动状态进入暂停状态。

public void destroyApp(boolean unconditional)方法。AMS可以在MIDlet处于活动或者暂停时调用这个方法,指示MIDlet将进入销毁状态。参数unconditional代表了终止请求的类型,如果unconditional为true,那么终止请求是强制执行的。如果unconditional为false,那么终止请求是可自由决定的,MIDlet可以抛出MIDletStateChangeException而持续运行。

需要说明的状态是暂停状态,系统在程序运行过程中,如果手机有来电,则系统会自动地使MIDlet程序进入到暂停状态,在进入到暂停状态以前,系统会自动调用MIDlet对象的pauseApp()方法。当电话接听完毕以后,系统会自动使MIDlet程序进入到运行状态,在进入到运行状态以前,系统还会自动调用startApp()方法使系统进入到运行状态。

3.控制MIDlet的状态改变

AMS控制MIDlet的状态改变,在代码执行到某一点可能需要状态的改变。MIDlet类提供了如下3个方法来允许一个MIDlet来控制它自己的状态。

(1)resumeRequest()方法。MIDlet调用这个方法表明它本身已经准备好进入活动状态了。

(2)notifyPaused()方法。该方法允许MIDlet主动暂停自己。当MIDlet处于活动状态时,它可以调用notifyPaused()方法通知AMS MIDlet已经主动进入暂停状态。在必要的时候,AMS可以调用startApp()方法重新启动MIDlet,也可以调用destroyApp()方法来终止MIDlet。

(3)notifyDestroyed()方法。该方法允许MIDlet主动销毁自己。当MIDlet处于活动状态时,它可以调用notifyDestroyed()方法通知AMS MIDlet已经主动进入了销毁状态。

必须注意的是,如果上述的3个通知方法被调用,那么AMS就不会再调用相应的生命周期方法。例如,如果已经调用了notifyPaused()方法,那么pauseApp()方法就不会主动被调用。也就是相关的生命周期方法应该先于通知方法之前调用。因此,经常在退出应用程序的代码中看到如下的方法:

public void exitMIDlet(){
    try{
        destroyApp(false);
        notifyDestroyed();
    }catch(MIDletStateChangeException ex){
        ex.printStackTrace();
    }
}

如果exitMIDlet()方法中只是调用notifyDestroyed()方法,那么destroyApp()方法就不会被AMS方法自动调用,这样本来由destroyApp()方法完成的销毁和状态保存工作都没有被完成。

2.2.3 编写并运行MIDlet应用程序

这里只是编写一个最简单的J2ME应用程序,在自动生成的框架内输入如下代码:

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;
public class Demo extends MIDlet {
    Display display;
    Form form;
    public Demo() {
        display=Display.getDisplay(this);
        form=new Form("第一个实例");
    }
    protected void destroyApp(boolean arg0) throws MIDletStateChange Exception {
    }
    protected void pauseApp() {
    }
    protected void startApp() throws MIDletStateChangeException {
        display.setCurrent(form);
    }
}

说明:

Display display; 定义Display对象display,是每个J2ME程序必有的对象,用于显示手机屏幕。

Form form;定义Form对象form,是被显示的对象。

display=Display.getDisplay(this);通过调用Display类中的静态方法getDisplay()生成display对象。

form=new Form("第一个J2ME程序!");调用Form类中的构造方法生成form对象。

display.setCurrent(form);display对象调用setCurrent()方法将form对象显示在手机屏幕上。

选择运行菜单下的运行命令,配置运行环境如图2-10所示。或者在左侧包资源管理器中选中MIDlet类并单击鼠标右键选择运行方式中的“Emulated J2ME Midlet”命令,运行结果如图2-11所示。

图2-10 配置运行环境

图2-11 运行结果

2.2.4 打包与混淆

为了在J2ME设备上安装J2ME MIDlet套件,首先必须把它部署为JAD和JAR文件,这一过程称为打包。EclipseME内建了打包生成JAD和JAR文件的支持。有两种打包选项:创建包(Create Package)和创建混淆包(Create Obfuscated Package)。

1.创建包

如果使用创建包,那么将把JAD和JAR文件输出到在首选项(Preferences)中配置的部署目录中。部署的JAR文件包含校验过的类文件和资源文件。在J2ME项目上单击鼠标右键选择“J2ME”下的“Create Package”命令即可,如图2-12所示。

打包之后可以看到输出目录如图2-13所示,在deployed文件夹中生成与项目同名的JAR文件和JAD文件。JAR文件是项目的安装文件,可以将JAR文件复制到终端设备上,并进行安装。JAD文件是项目的信息描述文件,有的手机只需要JAR文件,有的两者都需要。

图2-12 打包菜单

2.创建混淆包

如果使用创建混淆包,则需要安装混淆器,EclipseME会使用在首选项中指定的Proguard工具来混淆部署的JAR文件。混淆能够对MIDlet进行一定程度的保护,增加反编译的难度。更重要的是,混淆后的包通常会更小。

为了产生混淆包,需要正确安装Proguard工具,并在混淆首选项(Obfuscation Preferences)中正确设置其安装目录。Proguard是一个免费、开源的工具,可以从http://proguard.sourceforge.net/下载。

在Eclipse环境下打包,右键单击项目名称,选择“J2ME”→“Create Obfuscated Package”命令后就弹出一个警告框,如图2-14所示。

图2-13 打包后的菜单

图2-14 “Obfuscation Error”对话框

这是因为没有安装混淆器,安装混淆器Proguard至Eclipse的步骤如下。

(1)下载Proguard4.7.zip包,下载地址为http://sourceforge.net/projects/proguard/files/

(2)将下载后的Proguard4.7.zip包解压缩。在Eclipse中操作如下:“窗口”→“首选项”→“J2ME”→“Packaging”→“Obfuscation”,在Proguard Root Directory增加混淆包路径如图2-15所示。

(3)J2ME项目打包:右键单击项目名,选择“J2ME”→“Create Obfuscated Package”命令。

JAR和JAD文件是在MIDP兼容的设备上部署应用所必需的。除此之外,在混淆过程中还产生了一定数量的其他文件。这些文件和生成的JAR文件、JAD文件一起,被放在部署目录中,如图2-16所示。

*_base.jar:这个JAR文件包含混淆之前的初始包,被作为混淆处理的源。

*_base_obf.jar:这个JAR文件包含混淆后的class文件。对这个JAR文件进行预校验处理就产生了最终的混淆并校验过的JAR文件。

pro_map.txt:这个文件包含了类中原名和混淆后的名字的对应关系。利用这个文件,就可以使用Proguard的ReTrace命令,来根据混淆后的追踪输出(stack trace)来重建原始的追踪输出。

pro_seeds.txt:这个文件列出了作为混淆种子(seed)的MIDlet子类。

proguard.cfg:这个文件包含Proguard的混淆参数设置。

图2-15 添加混淆器

图2-16 混淆包文件目录