import javax.microedition.lcdui.*;
import java.io.InputStream;
/** index
* <p>Title: </p>
*
* <p>Description: </p>
*
* <p>Copyright: Copyright (c) 2006</p>
*
* <p>Company: </p>
*
* @author not attributable
* @version 1.0
*/
public class DrawMap extends Canvas implements CommandListener {
    public int nMapCell[][]; //地图数据列表
    public int nMapTans[][]; //地图翻转数据
    public int nTileWidth;
    public int nTileHeight;
    public int nMapWidth;
    public int nMapHeight;
    public int bufWidth ;
    public int bufHeight ;
    public int MapX = 0;
    public int bufPosX = 0;
    public int OldbufPosX = 0;
    public int DrawPosX;
    public int ncount=1;
    int bufi,bufj;
    int Maxj;
    boolean isOverMap = false;
    Graphics bufGraphics;
    int ScrW =getWidth();
    int ScrH =getHeight();
    int i, j;
    int index,Windex,Hindex ;
    Image m_image;
    Image bufimage;
    public DrawMap() {
        try {
        m_image = Image.createImage("/1.png");
        InputStream MapData = getClass().getResourceAsStream("/Map.dat");
        nMapWidth = MapData.read();
        MapData.read();
        nMapHeight = MapData.read();
        MapData.read();
        nTileWidth = MapData.read();
        MapData.read();
        nTileHeight = MapData.read();
        MapData.read();
        nMapCell = new int[nMapHeight][nMapWidth];
        nMapTans = new int[nMapHeight][nMapWidth];
        for(i=0; i<nMapHeight; i++)
        {
            for(j=0; j<nMapWidth; j++)
            {
                nMapCell[j] =  MapData.read();
                nMapTans[j] =  MapData.read();
            }
        }
        bufWidth = (((ScrW + nTileWidth) / nTileWidth + 2) * nTileWidth) ;
        bufHeight =(((ScrH + nTileHeight) / nTileHeight + 2) * nTileHeight) ;
        bufHeight = bufHeight > nMapHeight ? nMapHeight * nTileHeight :  bufHeight;
        bufimage = Image.createImage(bufWidth,bufHeight);
        bufGraphics = bufimage.getGraphics();
        //bufGraphics.drawImage(m_image,0,0,0);
        MapX = bufWidth ;
        for (i = 0; i < bufHeight / nTileHeight ; i++)
         {
             for (j = 0 ; j <bufWidth / nTileWidth ; j++ )
             {
                 index = nMapCell[j];
                 Hindex =index / (m_image.getWidth()/nTileHeight);
                 Windex =index % (m_image.getWidth()/nTileWidth);
                 bufGraphics.drawRegion(m_image, Windex * nTileWidth,
                                        Hindex * nTileHeight, nTileWidth,
                                        nTileHeight, 0, j * nTileWidth,
                                        i * nTileHeight, 20);
             }
         }
            jbInit();
        }
        catch(Exception e) {
            e.printStackTrace();
        }
    }
    private void jbInit() throws Exception {
        // Set up this Displayable to listen to command events
        setCommandListener(this);
        // add the Exit command
        addCommand(new Command("Exit", Command.EXIT, 1));
    }

    public void commandAction(Command command, Displayable displayable) {
        /** @todo Add command handling code */
        if (command.getCommandType() == Command.EXIT) {
            // stop the MIDlet
            show.quitApp();
        }
    }
    protected void keyPressed(int keyCode){
        switch (keyCode)
        {
        case KEY_NUM4 :
            bufPosX +=3;
            break;
        case KEY_NUM6 :
           // bufPosX -=3;
            OverMoved(-29,bufGraphics);
            break;
        }
        repaint();
    }
    /*****
     * 只实现了向右滚动。
     */
    public void OverMoved(int MoveX,Graphics g){
        OldbufPosX = bufPosX;
        bufPosX = bufPosX + MoveX;
        System.out.println(nMapWidth*nTileWidth);
        Image tmpImage;
        if (MoveX < 0 && (bufPosX > 0 ? bufPosX : -bufPosX) / nTileWidth >= 1) {
          if (bufPosX > OldbufPosX) {
              Maxj = bufWidth / nTileWidth;
              //MapX = MapX + bufWidth;
              //Maxj = (bufPosX > 0 ? bufPosX : -bufPosX) / nTileWidth;
              //System.out.println(Maxj);
          } else {
              Maxj = (bufPosX > 0 ? bufPosX : -bufPosX) / nTileWidth;
          }

            //Maxj = (bufPosX > 0 ? bufPosX : -bufPosX) / nTileWidth;
            //MapX = bufWidth * ncount / nTileWidth;
            for (i = 0; i < bufHeight / nTileHeight; i++) {
                for (j = (OldbufPosX > 0 ? OldbufPosX : -OldbufPosX) / nTileWidth;j < Maxj; j++) {
                    index = nMapCell[(MapX / nTileWidth + j  ) % nMapWidth];
                    //System.out.println("index="+index);
                    Hindex = index / (m_image.getWidth() / nTileWidth);
                    Windex = index % (m_image.getWidth() / nTileWidth);
                    //System.out.println(j);
                    tmpImage = Image.createImage(m_image, Windex * nTileWidth,
                                                 Hindex * nTileHeight, nTileWidth,
                                                 nTileHeight, 0);
                    g.drawImage(tmpImage, j * nTileWidth, i * nTileHeight, 0);
                }
            }
            if ((bufPosX > 0 ? bufPosX : -bufPosX) >= bufWidth) {
             bufPosX = 0;
             //OldbufPosX = 0;
             MapX = (MapX + bufWidth)%(nMapWidth*nTileWidth);
             //MapX = MapX + bufWidth;
         }
          DrawPosX = bufPosX + bufWidth;
        }
        repaint();
    }

    /******
     * 有问题的算法。
     */
    /*
    public void OverMoved(int MoveX,Graphics g){

        OldbufPosX = bufPosX;
       bufPosX = bufPosX + MoveX;
       Image tmpImage;
        if ( MoveX < 0 &&(bufPosX > 0 ? bufPosX : -bufPosX)/ nTileWidth >=1 ){
            DrawPosX = bufPosX + bufWidth;
           if (bufPosX > OldbufPosX){
               Maxj = bufWidth / nTileWidth;
           }else{
               Maxj = (bufPosX > 0 ? bufPosX : -bufPosX) / nTileWidth;
           }
           MapX = bufWidth * ncount / nTileWidth;
          if ( (bufPosX > 0 ? bufPosX : -bufPosX) >= bufWidth ){
                bufPosX = 0;
                //OldbufPosX = 0;
                if ((ncount + 2) * bufWidth > nMapWidth * nTileWidth) {
                    isOverMap = true;
                }
                ncount ++ ;
                //ncount = (++ncount) %(nMapWidth/(bufWidth/nTileWidth));
          }

            for (i = 0 ; i < bufHeight/nTileHeight; i++)
            {
                for (j = (OldbufPosX > 0 ? OldbufPosX : -OldbufPosX)/nTileWidth ;j < Maxj ; j++){
                    index = nMapCell[(MapX+j)%nMapWidth];
                    //System.out.println("index="+index);
                    Hindex =index / (m_image.getWidth()/nTileWidth);
                    Windex =index % (m_image.getWidth()/nTileWidth);
                    //System.out.println(j);
                    tmpImage = Image.createImage(m_image,Windex*nTileWidth,Hindex*nTileHeight,nTileWidth,nTileHeight,0);
                    g.drawImage(tmpImage,j*nTileWidth,i*nTileHeight,0);
                }
            }
        }
        repaint();
    }
*/
    protected void paint(Graphics g) {
        /** @todo Add paint codes */
        g.setColor(0xffffff);
        g.fillRect(0,0,ScrW,ScrH);
        g.drawImage(bufimage,bufPosX,0,0);
        g.drawImage(bufimage,DrawPosX,0,0);
    }
}

posted @ 2007-04-28 16:27 笨蛋啊帆 阅读(536) | 评论 (1)编辑 收藏

一、J2ME中需要的Java基础知识
       现在有大部分人,都是从零开始学J2ME的,学习J2ME的时候,总是从Java基础开始学习,而且现在讲Java基础的书籍中都是以J2SE来讲基础,这就给学习造成了一些不必要的麻烦,下面将J2ME中用到的和不需要的Java基础知识做一个简单的说明。
       J2ME中使用到的Java基础知识:
    1、Java语法基础:包括基本数据类型、关键字、运算符等等
    2、面向对象的思想:类和对象的概念,继承和多态等等。
    3、异常处理
    4、多线程
  J2ME中没有用到的Java基础知识:
    1、JDK中javac和java命令的使用
    2、Java基础中的很多类在J2ME中没有,或者类中的方法做了大量的精简。所以建议在J2ME中熟悉类库。
    3、Applet、AWT、Swing这些知识在J2ME中根本使用不到。
  简单说这么多,希望学J2ME的朋友们能少走一些弯路,不足之处希望大家积极指正和补充。

二、J2ME中暂时无法完成的功能
  列一些J2ME中暂时无法完成的功能,希望大家能积极补充:
    1、在手机中不更改代码实现移植,主要指游戏。
    2、动态修改按钮文字。
    3、在Canvas上接受中文输入。
    4、操作本地资源、例如地址本、已收短信息等。
    5、制作破坏性的手机病毒。
    6、其他等待大家来补充。

三、J2ME的跨平台性
  J2ME技术源于Java,所以也具有JVM的优势,可以在支持Java的平台上进行移植,但是现在的J2ME技术在跨平台上却做的很糟糕,我们来简单看一下原因:
  1、手机的屏幕尺寸不一:
  这个主要在界面制作上。如果你使用的是高级用户界面,比如你做的是应用开发或者用户登陆、用户注册这样的通用功能时,一般没有什么问题。
  如果你使用的是低级用户界面,比如你做的是游戏,那么你就需要考虑这个问题了。
  2、厂商的扩展API不统一:
  例如Nokia的扩展API类库UI系列,在别的手机上或者没有实现,或者包名不同等等。
  3、手机平台上实现的bug:
  例如Nokia的7650在实现双缓冲上有bug,那么在这种机型上运行的软件就不能使用双缓冲。其他NOKIA上的一些bug,可以参看:http://blog.csdn.net/Mailbomb/archive/2005/03/24/329123.aspx
  4、手机性能问题。
  不同手机的可用内存、最大jar文件都有要求,例如Nokia S40的大部分手机支持的最大jar文件为64K,最大可用内容为210K。
  所以现在的手机软件,特别是游戏都提供支持的机型列表,也才有了手机游戏移植人员的存在。

四、学习J2ME可以从事的工作种类
  现在J2ME技术可以说相当的火暴,这里介绍一些学好了J2ME之后可以从事的工作的种类:
  1、J2ME游戏开发人员
  根据游戏策划或者文档要求,在某种特定的机型(以Nokia S40或S60居多)开发游戏程序。
  这是现在大部分J2ME程序员从事的工作。
  需要熟练掌握:高级用户界面、低级用户界面、线程,如果是网络游戏,还需要熟练网络编程。
  2、J2ME应用开发人员
  现在的移动应用还不是很多,但是还是出现了一些,特别是移动定位以及移动商务相关的内容。
  需要熟练掌握:高级用户界面、线程和网络编程。
  3、J2ME游戏移植人员
  参照源代码,将可以在一个平台上可以运行的游戏移植到其他平台上去。例如将Nokia S40的游戏移植到S60上,或者索爱的T618等等。
  主要是控制屏幕坐标,有些可能需要替换一些API。
  需要熟悉各平台之间的差异以及相关的技术参数,比如屏幕大小、最大jar文件尺寸等等。

五、J2ME程序设计的几个原则
  1、使用面向对象编程。
  虽然使用面向过程编程可以减小文件的尺寸,但是为了以后维护的方便和利于扩展,还是要使用面向对象编程。
  2、使用MVC模式
  将模型、界面和控制分离。现在很多的程序将三者合一,但是如果你做的程序比较大的话,还是建议你进行分离。
  3、自动存储用户设定
  使用RMS来存储用户的信息,例如存储用户上次输入的用户名、密码、用户对于系统的设定等,这样不仅可以减少用户的输入,而且对用户友好。很多程序甚至做了自动登陆等。
  4、一些系统设置允许用户关闭。如背景音乐、背景灯显示等。
  5、将低级用户界面的绘制动作放在一个独立的线程里面去。
  6、在需要大量时间才能完成的工作时,给用户一个等待界面。

六、从模拟器到真机测试
  对于J2ME开发者来说,模拟器给我们带来了很多方便,比如可以在模拟器中调试程序以及很方便的察看程序的效果,但是模拟器也给我们带来了一些问题,比如模拟器实现的bug等等,所以进行真机测试是必须的。
  1、为什么要进行真机测试?
  因为模拟器程序可能存在bug,以及真机的性能有限,所以必须进行真机测试。
  2、如何将程序传输到机器中?
  将程序传输到机器中有如下方式:
    a) OTA下载
    b) 使用数据线传输
    c) 红外传输
    d) 蓝牙
  你可以根据条件,选择合适的方式。
  3、 真机测试主要测什么?
  真机测试的内容很多,主要测试以下几个方面:
    a) 程序的功能
    b) 程序的操作性,是否易操作
    c) 程序的大小,比如Nokia S40系列的手机大部分接受的最大文件尺寸为64K
    d) 程序运行速度,速度是否可以忍受。

七、从WTK到厂商SDK
  对于J2ME爱好者来说,基本上大家都是从SUN的WTK(J2ME Wireless Toolkit)开始的,但是对于实际应用来说,仅仅使用WTK是远远不够的,所以在学习过程中,必须完成从WTK到SDK的跨越。
  1、厂商SDK的下载地址?
  http://blog.csdn.net/Mailbomb/archive/2005/01/01/236606.aspx
  2、厂商SDK和WTK有什么不同?
  厂商SDK最简单的理解就是在WTK的基础上增加了自己的模拟器和自己的扩展API。
  也就是说,你在使用厂商的SDK时,可以使用厂商的扩展类库,例如Nokia的UI类库,和厂商自己的模拟器而已。
  每个厂商的扩展API都不多,而且不尽相同。
  3、如何使用?
  有些厂商SDK的使用都和WTK相同,例如SamSung。
  Nokia提供了独立的界面来开发,但是这个界面在实际开发中使用不多。
  4、厂商SDK的问题
  厂商SDK实现过程中,有一些bug,而且和真机实现不一致。例如NOKIA的混音播放问题等等。

八、在J2ME中获得手机IMEI的方法
  IMEI是Internation mobile entity identification的简称,在手机中输入*#06#可以显示该数字,长度为15位,全球唯一,永远不会冲突,所以可以作为识别用户的一个标志。
  下面是在J2ME中获得IMEI的方法:
  1、MOTO系列的手机可以通过读取系统的IMEI属性获得,代码如下:
             String imei = System.getProperty("IMEI");
  2、SIEMENS系列的手机可以通过读取系统的com.siemens.IMEI属性获得,代码如下:
             String imei = System.getProperty("com.siemens.IMEI");

九、J2ME网络连接中显示问题的解决办法
  在网络编程中,有些时候会出现一些在没有接收到网络数据就显示界面的,造成界面显示不符合要求(例如公告显示,会先显示公告的背景图片再显示公告信息),这里提一个简单的解决办法给大家:
  解决这种情况的方法分成三个步骤:
  1、在需要显示的界面中,调用发送网络数据的方法。每次显示时调用该构造方法,不调用Display的setCurrent方法显示。
  2、显示等待界面(例如进度条等),给用户提示,在进行网络连接。
  3、在处理网络反馈的数据完以后,调用Display的setCurrent方法显示显示当前界面。

十、增强J2ME的String能力——分割字符串
  从JDK1.4以后,String类中新增了split方法来实现字符串的分割,但是在J2ME中却没有该方法(MIDP2.0中也没有实现),但是在实际使用过程中,有些时候的确要用到这种操作,这里将我以前实现的一段代码和大家共享:
/**
* 分割字符串,原理:检测字符串中的分割字符串,然后取子串
* @param original 需要分割的字符串
* @paran regex 分割字符串
* @return 分割后生成的字符串数组
*/

private static String[] split(String original,String regex)
{
     //取子串的起始位置
     int startIndex = 0;
     //将结果数据先放入Vector中
     Vector v = new Vector();
     //返回的结果字符串数组
     String[] str = null;
     //存储取子串时起始位置
     int index = 0;

     //获得匹配子串的位置
     startIndex = original.indexOf(regex);
     //System.out.println("0" + startIndex);
     //如果起始字符串的位置小于字符串的长度,则证明没有取到字符串末尾。
     //-1代表取到了末尾
     while(startIndex < original.length() && startIndex != -1)
     {
           String temp = original.substring(index,startIndex);
           System.out.println(" " + startIndex);
           //取子串
           v.addElement(temp);

           //设置取子串的起始位置
           index = startIndex + regex.length();

           //获得匹配子串的位置
           startIndex = original.indexOf(regex,startIndex + regex.length());
     }

     //取结束的子串
     v.addElement(original.substring(index + 1 - regex.length()));
     //将Vector对象转换成数组
     str = new String[v.size()];
     for(int i=0;i
     {
          str = (String)v.elementAt(i);
     }
     
     //返回生成的数组
     return str;
}

十一、J2ME在低级用户界面上分行显示文字
  在J2ME的低级用户界面开发中,经常会遇到需要在Canvas上显示大量的文字,例如关于界面、游戏说明、游戏公告等信息。如果在设计时,将文字的内容和长度都固定,既不利于修改也不利于维护。下面介绍一个简单的方法,实现一个简单、可维护性强的方式。
  实现方法:
    1、将需要显示的所有信息做成一个字符串。
    2、编写一个将该字符串按照要求转换为字符串数组的方法。
    3、将转换后的数组以循环的方式显示在Canvas上。
  通过这样三个步骤,则修改显示的信息时,只需要修改包含显示信息的字符串即可,自己书写的方法可以按照以前的标准重新分割新的字符串。如果需要修改每行显示的字符个数,则只需要修改自己书写的方法即可。
  通过这样一种实现方式,可以很方便的实现显示一些比较长的文本信息,即使是可变长度的字符串也没有问题。

十二、J2ME中使用记录存储系统(RMS)存储信息
  在MIDP中,没有文件的概念,所以永久存储一般只能依靠记录存储系统实现,关于记录存储系统的简介,可以参看教程:http://www-900.ibm.com/developer ... -wi-rms/index.shtml
  下面是一些记录存储系统的常用编码介绍:
    1、打开记录集:
  打开记录集使用RecordStore里面的静态方法openRecordStore,示例代码如下:
          RecordStore rs = RecordStore.openRecordStore(“username”,true);
  这样就打开了一个名称为rs的记录集,其中username为记录集的名称,该名称可以根据需要来取,第二个参数代表是否则没有时创建新的记录集,true代表在该记录集不存在时,创建新的记录集,false代表不创建。
  如果在打开记录集时,该记录集不存在,则抛出RecordStoreNotFoundException异常,所以检测记录集是否已创建可以使用该异常。
  注意:记录集打开以后记得关闭。
    2、向记录集中写入数据
        2.1增加数据
  向已经打开的记录集中添加数据,需要使用addRecord方法,示例代码:
                 byte[] bytes = {1,2,3};
             int id = rs. addRecord(bytes,0,bytes.length);
  该代码将字节数组bytes的全部内容写入到记录集中,该方法的返回值为该信息的id,注意:id从1开始,而不是从0开始。
  你可以循环使用该方法向记录集中写入多条数据。
        2.2修改数据
  修改已经存在的记录集中指定id的数据,需要使用setRecord方法,示例代码:
                 byte[] bytes = {1,2,3};
             rs. setRecord(1,bytes,0,bytes.length);
  以上代码的作用是将字节数组bytes的全部内容写入到id为1的记录集rs中。
  该操作会覆盖已有的数据。
  说明:有些时候,你需要将信息写入到记录集中的第一条记录中,则可以结合以上两个方法,则第一次时向记录集增加数据,以后来进行修改。
    3、从记录集中读出数据
  从记录集中读取已有数据,需要使用getRecord方法,示例代码:
            byte[] bytes = rs. getRecord(1);
  该代码从记录集rs中读取第一条数据,将读取到的数据放在bytes数组中。
  在读取数据时,可以获得记录集中id的个数,可以使用getNumRecords方法获得
  综合代码为:
       int number = rs. getNumRecords();
       int id = 1;
       if(id >0 && id < number)
       {
       byte[] bytes = rs. getRecord(1);
       }
    4、从记录集中删除记录
  从记录集中删除记录的方法有两种:逻辑删除和物理删除。
  逻辑删除是指给删除的记录打标记。
  物理删除是指从物理上删除该记录,但是该记录的id不能被重用,也就是说该id不会被继续使用。例如一个记录集中有5个记录,假设你删除了id为3的数据,则剩余记录的id依然为1、2、4、5。这给便历带来了一定的麻烦。
    5、便历记录集
  便历记录集,即访问记录集中的所有数据,有两个方法,详见:
http://gceclub.sun.com.cn/NASApp/sme/controller/teclist?tid=0103
    6、其他操作
  删除记录集
  删除记录集不同于删除记录,需要使用deleteRecordStore方法,示例代码:           
                RecordStore. deleteRecordStore(“username”);
  该代码删除名称为username的记录集。

十三、J2ME加密数据的一个第三方开源免费类库介绍
  在J2ME编程中,经常遇到一些数据在存储或者传输时需要加密,下面介绍一个第三方的加密类库的一些资料:
  加密类库的官方主页:http://www.bouncycastle.org/
  介绍的文章:
  中文:http://18900.motorola.com/ewa_portal/develope/jc_j2messl_5_1.jsp
  英文:http://www.javaworld.com/javawor ... -1220-wireless.html
  该文章的源代码包含使用的一些方法。
  备注:因为该类库提供的功能比较强大,所以类库的尺寸比较大,最后在发布时需要将类库中不需要的类删除

十四、如何播放声音
  在J2ME中,处理声音需要使用到Mobile Media API(MMAPI),该包是MIDP1.0的可选包,在MIDP2.0中已经包含了这个包。所以如果你使用MIDP1.0的话,请确认你的运行环境是否支持。
  一般手机支持的声音文件格式为wav、mid和mpg等。具体请查阅你的手机说明文档。
  在声音处理中,有很多处理的方式,这里说一下最常用的情况,播放JAR文件中的wav文件。
  播放声音文件的流程:
    1、按照一定的格式读取声音文件。
  播放JAR文件中的声音文件一般是将声音文件处理成流的形式。示例代码:
             InputStream is = this.getClass().getResourceAsStream("/Autorun.wav");
       其中Autorun.wav文件位于JAR文件的根目录下,如果位于别的目录,需要加上目录名称,如/res /Autorun.wav。
    2、将读取到的内容传递给播放器。
  将流信息传递给播放器,播放器按照一定的格式来进行解码操作,示例代码:
             Player player = Manager.createPlayer(is,"audio/x-wav");
  其中第一个参数为流对象,第二个参数为声音文件的格式。
    3、播放声音。
  使用Player对象的start方法,可以将声音播放出来,示例代码:
             player.start();
  在播放声音时也可以设定声音播放的次数,可以使用Player类中的setLoopCount方法来实现,具体可查阅API文档。
  下面是在NOKIA S60模拟器中测试通过。代码如下:
package sound;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.media.*;
import java.io.*;

public class SoundMIDlet extends MIDlet
{
  private Player player = null;
  /** Constructor */
  public SoundMIDlet()
       {
             try
             {
                   InputStream is = this.getClass().getResourceAsStream("/Autorun.wav");
                   player = Manager.createPlayer(is,"audio/x-wav");
             }
             catch(IOException e)
             {
                   System.out.println("1:" + e);
             }
             catch(MediaException e)
             {
                   System.out.println("2:" + e);
             }
             catch(Exception e)
             {
                   System.out.println("3:" + e);
             }
      }

      /** Main method */
      public void startApp()
      {
             if(player != null)
             {
                   try
                   {
              player.start();
                   }
                   catch(MediaException e)
                   {
              System.out.println("4:" + e);
                   }
             }
       }

       /** Handle pausing the MIDlet */
       public void pauseApp()
       {
       }

       /** Handle destroying the MIDlet */
       public void destroyApp(boolean unconditional)
       {
       }
}

十五、J2ME 3D编程的一些资料
  随着J2ME技术的发展,以及硬件速度的提升,3D游戏程序将慢慢的变成主流,最近想学习这一块的编程,所以收集了一些资料,和大家一起分享:
  1、JSR184
  JSR184是Nokia公司起草的一个关于3D API的规范,下载地址为:
  http://www.forum.nokia.com/main/1,,1_0_10,00.html#jsr184
  2、Nokia的3D编程资料
  http://www.forum.nokia.com/main/1,6566,21,00.html
  3、3D引擎
  一个简单的开放源代码的3D游戏引擎
  http://www.j2me.com.cn/Soft_Show.asp?SoftID=19
  国内一个合作开发3D引擎的项目:
  http://gceclub.sun.com.cn/NASApp ... =11&thread=8593
  4、一款3D游戏产品
  http://games.sina.com.cn/newgames/2004/04/040217696.shtml
  5、支持3D的开发工具
  当前一些高端的手机支持3D开发,支持3D开发的开发工具中,通用的有SUN的J2MEWTK2.2。专用的是厂商提高的支持JSR184的SDK。

十六、3D编程——第一个3D程序
  参考WTK2.2提供的demo,完成了第一个3D程序,虽然很简单,而且有些问题还不是很清楚,还是把代码共享出来和愿意学习J2ME 3D编程的朋友一起学习。
  关于代码的编译和运行说明如下:
  1、以下代码在J2ME WTK2.2下面编译通过。
  2、代码分为两个文件:First3DCanvas.java和First3DMIDlet.java。
  3、使用J2ME WTK2.2建立新的工程,主MIDlet类为:first3d. First3DMIDlet
  4、将代码保存在你的工程目录下的first3d目录下。
  5、将J2ME WTK安装目录下的apps\Demo3D\res\com\superscape\m3g\wtksamples\retainedmode\content目录中的swerve.m3g文件复制到你的工程目录下的res目录下。
  6、你的工程建立后,设置工程,通过WTK界面中的“设置”按钮打开设置窗口,在“API选择”中,设置“目标平台”为:自定义;“简档”为“MIDP2.0”;“配置”为“CLDC1.1”;选中“Mobile 3D Graphics for J2ME(JSR184)”。
  7、这样你就可以编译和运行以下代码了。
  源代码如下:

// First3DMIDlet.java
package first3d;
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
public class First3DMIDlet extends MIDlet
{
      private First3DCanvas displayable = new First3DCanvas();
      public void startApp()
      {
            Display.getDisplay(this).setCurrent(displayable);
      }

      public void pauseApp() {}

      public void destroyApp(boolean unconditional) {}
}

// First3Dcanvas.java
package first3d;
import javax.microedition.lcdui.*;
import javax.microedition.m3g.*;
import java.util.*;
/**
* 第一个3D程序
*/
public class First3DCanvas extends Canvas implements Runnable
{
      /**World对象*/
      private World myWorld = null;
      /**Graphics3D对象*/
      private Graphics3D g3d = Graphics3D.getInstance();
      /**Camera对象*/
      private Camera cam = null;
      private int viewport_x;
      private int viewport_y;
      private int viewport_width;
      private int viewport_height;
      private long worldStartTime = 0;
      //重绘时间
      private int validity = 0;
      
      public First3DCanvas()
      {
            //启动重绘界面的线程
            Thread thread = new Thread(this);
            thread.start();
            try
            {
                   //导入3D图片
                   myWorld = (World) Loader.load("/swerve.m3g")[0];
                   viewport_x = 0;
                   viewport_y = 0;
                   viewport_width = getWidth();
                   viewport_height = getHeight();
                   cam = myWorld.getActiveCamera();
                   //设置cam对象
                   float[] params = new float[4];
                   int type = cam.getProjection(params);
                   if (type != Camera.GENERIC)
                   {
                          //calculate window aspect ratio
                          float waspect = viewport_width / viewport_height;
                          if (waspect < params[1])
                          {
                                float height = viewport_width / params[1];
                                viewport_height = (int) height;
                                viewport_y = (getHeight() - viewport_height) / 2;
                          }
                          else
                          {
                                float width = viewport_height * params[1];
                                viewport_width = (int) width;
                                viewport_x = (getWidth() - viewport_width) / 2;
                          }
                   }
                   worldStartTime = System.currentTimeMillis();
            }
            catch (Exception e) {}
      }
      
      protected void paint(Graphics g)
      {
            //清除背景
            g.setColor(0x00);
            g.fillRect(0, 0, getWidth(), getHeight());
            //和3D对象绑定
            g3d.bindTarget(g);
            g3d.setViewport(viewport_x, viewport_y, viewport_width, viewport_height);
            long startTime = System.currentTimeMillis() - worldStartTime;
            validity = myWorld.animate((int)startTime);
            try
            {
                   g3d.render(myWorld);
            }
            finally
            {
                   g3d.releaseTarget();
            }
      }

      public void run()
      {
            try
            {
                   while(true)
                   {
                         //重绘图形
                         repaint(viewport_x, viewport_y, viewport_width, viewport_height);
                   }
            }
            catch(Exception e){}
      }
}

十七、在J2ME网络编程中使用CMWAP代理
  在中国移动提供的网络连接中,分为CMNET和CMWAP两种,其中CMNET可以无限制的访问互联网络,资费比较贵。CMWAP类似一个HTTP的代码,只能访问支持HTTP的应用,但是资费便宜,稳定性比较差。
  在实际的J2ME网络编程中,一般需要提供以CMWAP代理的方式连接网络,在J2ME中,连接的代码和直接连接有所不同,代码如下:
             HttpConnection http = (HttpConnection)Connector.open(("http://10.0.0.172/"+url);
             http.setRequestProperty("X-Online-Host",ServerName);
  例如你需要访问的地址为:http://www.test.com/login/loginServlet则上面的代码就为:
             HttpConnection http = (HttpConnection)Connector.open(("http://10.0.0.172/" + "login/loginServlet");
             http.setRequestProperty("X-Online-Host","www.test.com");
  在实际使用过程中,只需要使用实际需要访问的地址的域名或者IP来代替ServerName,例如示例中的“www.test.com”,使用后续的地址类代替代码中的url,例如示例中的“login/loginServlet”,就可以实际的使用CMWAP代理来进行连接了。

十八、J2ME中的时间处理全攻略
  时间处理在程序开发中相当常见,下面对于时间处理做一个简单的说明。
  一、时间的表达方式
  时间在J2ME中有两种表达方式:
  1、以和GMT1970年1月1号午夜12点和现在相差的毫秒数来代表
  这种方式适合比较两个时间之间的差值。
  2、以对象的形式来表达
  二、时间处理的相关类
  时间处理在J2ME中涉及三个类:
  1、System类
   long time = System. currentTimeMillis();
  使用该方法可以获得当前时间,时间的表达方式为上面提到的第一种。
  2、Date类
   Date date = new Date();
  获得当前时间,使用对象的形式来进行表达。
  3、Calendar类
   Calendar calendar = Calendar. getInstance();

三、时间处理的具体操作
  1、以上三种表达方式的转换:
  a)将System类获得的时间转换为Date对象
   Date date = new Date(System. currentTimeMillis());
  b)将Date类型的对象转换为Calendar类型的对象
   Calendar calendar = Calendar. getInstance();
   Date date = new Date();
   calendar.setTime(date);
  2、使用Calendar完成一些日期操作:
  Calendar是时间处理中最常用也是功能最强大的类,可以用它来获得某个时间的日期、星期几等信息。
  获得日期:
   Calendar calendar = Calendar. getInstance();
   ……
   int day = calendar.get(Calendar. DATE);
  获得日期、年份、星期的操作和这个类似。
  需要注意的是:Calendar中表示月份的数字和实际相差1,即1月用数字0表示,2月用数字1表示,……12月用数字11表示。

十九、J2ME中随机数字处理全攻略
  在程序中生成随机数字,用处比较,如人工智能领域等等,这里对于在J2ME中生成随机数的操作进行一个简单的整理,希望对大家能有帮助。
  J2ME和J2SE不同,不能使用Math类的random来生成随机数字,只能使用java.util包的Random类来生成随机数字。
  1、创建Random类型的对象:
         Random random = new Random();
   Random random = new Random(10010010);
  以上两种是创建Random对象的方式,第一种使用默认构造方法,和以下的代码作用完全等价:
   Random random = new Random(System. currentTimeMillis());
  相当与使用当前时间作为种子数字来进行创建。
  第二种方式通过自己来指定种子数字来进行创建。
  大家可以根据需要使用以上两种方式的任一种。
  2、生成随机数字:
  创建好了随机对象以后,我们就可以来生成随机数字了:
  生成随机整数:
         int k = random.nextInt();
       生成随机长整数:
         long l = random.nextLong();
  3、生成指定范围的数字:
  例如生成0-10之间的随机数字:
         int k = random.nextInt();
   int j = Math.abs(k % 10);
  首先生成一个随机整数k,然后用k和10取余,最后使用Math类的abs方法取绝对值,获得0-10之间的随机数字。
  获得0-15之间的随机数,类似:
         int k = random.nextInt();
   int j = Math.abs(k % 15);
  获得10-20之间的随机数字:
         int k = random.nextInt();
   int j = Math.abs(k % 10) + 10;

二十、在J2ME手机编程中使用字体
  在J2ME手机编程中,可以通过使用字体类——Font在低级用户界面中,获得更好的表现效果,那么如何使用Font类呢?
  首先,由于手机设备的限制,手机中支持的字体类型很有限,所以在J2ME中只能使用手机支持的默认字体来构造Font类对象。下面是创建Font类的对象时使用的方法:
          getFont(int face,int style,int size);
例如:
          Font font = Font.getFont(Font.FACE_SYSTEM,Font.STYLE_BOLD,Font. SIZE_MEDIUM);
  无论哪一个参数,都只能使用系统设置的数值,这些数值具体的大小在不同的手机上可能不同。下面对于其中的三个参数的取值做详细的介绍:
  face参数指字体的外观,其的取值:
  FACE_MONOSPACE——等宽字体
  FACE_PROPORTIONAL——均衡字体
  FACE_SYSTEM——系统字体
  style参数指字体的样式,其的取值:
  STYLE_BOLD——粗体
  STYLE_ITALIC——斜体
  STYLE_PLAIN——普通
  STYLE_UNDERLINED——下划线
  STYLE_BOLD | STYLE_ITALIC——粗斜体
  STYLE_UNDERLINED | STYLE_BOLD——带下划线粗体
  STYLE_UNDERLINED | STYLE_ITALIC——带下划线斜体
  STYLE_UNDERLINED | STYLE_ITALIC | STYLE_BOLD——带下划线的粗斜体
  size参数指字体的大小,其的取值:
  SIZE_SMALL——小
  SIZE_MEDIUM——中
  SIZE_LARGE——大
  通过上面的参数的值,可以组合出你需要的字体对象。
  下面是一些常用的字体操作:
  1. 获得系统的默认字体:
          Font font = Font.getDefaultFont();
  2. 在panit方法内部,假设Graphics参数的名称为g,则获得当前字体的方法是:
          Font font = g.getFont();
  3. 在panit方法内部,假设Graphics参数的名称为g,则设置当前字体的方法是:
          g.setFont(font);
  其中font为你构造好的字体对象。
  4. 在MIDP2.0中,List可以设置每行的字体格式,方法是:
          list.setFont(0,font);
  则上面的代码是将list中的第一行设置为font类型的字体。

二十一、在J2ME手机程序开发中使用颜色
  在J2ME手机开发过程中,需要经常用到颜色来进行绘制,增强程序的表现效果,下面就介绍一下如何使用颜色。
  由于J2ME技术比较简单,所以没有实现专门的颜色类,而只是使用RGB的概念来代表颜色。这里简单介绍一下RGB的概念,颜色是由红(Red)、绿(Green)、蓝(Blue)三原色组成的,所以可以使用这三个颜色的组合来代表一种具体的颜色,其中R、G、B的每个数值都位于0-255之间。在表达颜色的时候,即可以使用三个数字来表达,也可以使用一个格式如0X00RRGGBB这样格式的十六进制来表达,下面是常见颜色的表达形式:
  红色:(255,0,0)或0x00FF0000
  绿色:(0,255,0)或0x0000FF00
  蓝色:(255,255,255)或0x00FFFFFF
  其他颜色也可以通过上面的方式组合出来。
  知道了颜色的表达方式以后,下面来介绍一下如何在J2ME程序中使用颜色,涉及的方法均在Graphics类中,有以下几个:
  1.getColor():
  获得当前使用的颜色,返回值是0x00RRGGBB格式的数字。例如:
          int color = g.getColor();
  其中g为Graphics类型的对象。
  2.setColor(int RGB):
  设置使用的颜色。例如:
          g.setColor(0x00ff0000);
  3.setColor(int red, int green, int blue)
  和上面的方法作用一样,例如:
          g.setColor(255,0,0);
  在设置了Graphics使用的颜色以后,再进行绘制的时候,就可以绘制指定的颜色了。

二十二、在J2ME联网应用中获得客户端的手机号码
  在J2ME程序开发过程中,为了一定的需要,经常需要来获得用户的手机号码,但是这个功能却在标准的J2ME类库中没有提供。
  在使用中国移动的CMWAP方式连接网络时,中国移动会将用户的手机号码放在一个名称为x-up-calling-line-id的头信息中,可以通过读取该头信息,获得用户的手机号码,具体代码如下:
          String usermphone = http.getHeader("x-up-calling-line-id");
  其中http是HttpConnction类型的对象。

二十三、使用J2ME发送手机短信息
  在程序中,发送短信息的方式一般有三种:
  1、 使用程序在网络上发送短信息,例如各大网站的短信业务。这种方式是通过程序将信息发送给运营商的网关服务器,然后通过运营商的网络发送给手机。
  2、 在计算机中,通过数据线连接到手机,然后通过手机来发送短信息。这种方式是通过使用AT指令来实现。爱立信手机的AT指令你可以在以下地址找到:http://mobilityworld.ericsson.com.cn/development/download_hit.asp
  3、 通过在手机中运行的程序来发送短信息。这个正是本文实现的方式。
  在J2ME中,如果想发送短信息,需要使用WMA包,MIDP2.0中已经包含,MIDP1.0中可以通过厂商提供的扩展API实现,和WMA的类库基本一样。
       下面是使用WMA向指定手机号码发送短信息的一个方法,很简单。当然WMA也提供了其他的方式来发送更多的内容。

// SMSUtil.java
package my.util;
import javax.wireless.messaging.*;
import javax.microedition.io.*;
/**
* 发送文本短信息的方法
*/
public class SMSUtil
{
      /**
      * 给指定号码发送短信息
      * @param content 短信息内容
      * @param phoneNumber 手机号码
      * @return 发送成功返回true,否则返回false
      */
      public static boolean send(String content,String phoneNumber)
      {
            //返回值
            boolean result = true;
            try
            {
                   //地址
                   String address = "sms://+" + phoneNumber;
                   //建立连接
                   MessageConnection conn = (MessageConnection)Connector.open(address);
                   //设置短信息类型为文本,短信息有文本和二进制两种类型
                   TextMessage msg = (TextMessage)conn.newMessage(MessageConnection.TEXT_MESSAGE);
                   //设置信息内容
                   msg.setPayloadText(content);
                   //发送
                   conn.send(msg);
            }
            catch(Exception e)
            {
                   result = false;
                   //未处理
            }
            return result;
      }
}

二十四、使用简单的J2ME程序测试MIDlet的生命周期
  在MIDlet程序学习中,生命周期是一个比较抽象的概念。其实生命周期就是一个简单的规定,规定了MIDlet中的每个方法,什么时候被系统调用。下面是一个示例代码,在每个方法的内部都输出一条语句,可以根据程序的输出结果来验证各方法被调用的顺序,具体代码如下:


//文件名:LifeCircleMIDlet.java
import javax.microedition.midlet.*;
/**
* 测试MIDlet的生命周期
*/
public class LifeCircleMIDlet extends MIDlet
{
      /**
      * 默认构造方法
      */
      public LifeCircleMIDlet()
      {
            System.out.println("默认构造方法");
      }
      /**
      * 启动方法
      */
      public void startApp()
      {
            System.out.println("startApp方法");
      }
      /**
      * 暂停方法
      */
      public void pauseApp()
      {
            System.out.println("pauseApp方法");
      }
      /**
      * 销毁方法
      * @param b
      */
      public void destroyApp(boolean b)
      {
            System.out.println("destroyApp方法");
      }
}
  在J2WTK中运行该程序时,可以使用浏览器中的“MIDlet”菜单中的暂停和恢复菜单,模拟暂停事件。

二十五、使用OTA来发布你的程序
  众所周知,J2ME程序发布的形式主要有:OTA、数据线传输、红外和蓝牙传输等。这里简单说说如何通过OTA来发布你的程序。
  OTA是Over The Air的简写,也就是通过网络下载,这是主要的发布形式之一。现在的百宝箱都是采用这种形式。
  使用OTA来发布程序,需要如下几个步骤:
  1、在你的WEB服务器上添加对于jad和jar文件的MIME支持。
  后缀名:jad
  MIME类型:text/vnd.sun.j2me.app-descriptor
  后缀名:jar
  MIME类型:application/java-archive
  2、发布WML页面:
  例如你的jar文件名test.jad,则最简单的下载页面是:
  <?xml version="1.0"?>
  <!DOCTYPE wml PUBLIC "-//WAPFORUM//DTD WML 1.3//EN"
  "http://www.wapforum.org/DTD/wml13.dtd">
  <wml>
  <card id="card1" title="Download Midlet">
  <a href="test.jad">test</a> _fcksavedurl=""test.jad">test</a>"
  </card>
  </wml>
  你可以将以上代码保存在WEB服务器上,例如保存为text.wml
  3、修改jad文件:
  在jad文件中增加 MIDlet-Jar-URL: http://domain/directory/test.jar
  其中的http://domain/directory/test.jar为你的jar文件的路径。
  经过上面的设置,你就可以将你的wml页面路径作为你的WAP下载页面发布了。用户只需要在手机上输入这个路径就可以访问和下载你的程序了。

posted @ 2007-04-28 16:23 笨蛋啊帆 阅读(194) | 评论 (0)编辑 收藏

本文主要讲述在MIDP1.0情况下处理开发过程遇到的连续按键事件,结合具体的实例给出一种实用的解决方案。

我们知道MIDP中的低级事件处理是通过keyPressed()、keyReleased()和keyRepeated()来处理的,分别在按键被按下、释放和重复按键的时候被触发。当方法被调用的时候,系统会把所按下键的键值传递给上述的三个方法,根据按键的键值我们可以进行相关的处理。在MIDP中定义了如下的按键值分别是: KEY_NUM0, KEY_NUM1, KEY_NUM2, KEY_NUM3, KEY_NUM4, KEY_NUM5, KEY_NUM6, KEY_NUM7, KEY_NUM8, KEY_NUM9, KEY_STAR 和 KEY_POUND。

在游戏开发中为了保证程序的可移植性,通常我们都会把键值转换为游戏动作,在MIDP中定义了如下的游戏动作: UP, DOWN, LEFT, RIGHT, FIRE, GAME_A, GAME_B, GAME_C,GAME_D。转换非常简单,可以通过Canvas提供的方法getGameAction()。

一般来说处理keyPressed()和keyReleased()都比较容易,但是处理按键一直被按下的情况稍微复杂一些。因为我们使用的设备并不一定支持连续按键的事件。你可以通过方法hasRepeatEvents()来检测平台是否支持当按键持续按下的时候产生重复事件。如果支持那么你可以在keyRepeated()的方法中处理相关逻辑,如果不支持那么你必须采取其他的方法。

这里笔者介绍一种通过设置标志位的方式来处理连续按键的方法。其实原理非常的简单,我们通过设置标志位判断按键是否被按下了,比如我们判断LEFT是不是被按下了。当LEFT被按下的时候,我们把成员变量leftPressed设置为true,代码如下:

public void keyPressed(int keyCode)
{
int action = getGameAction(keyCode);

switch (action)
{

case LEFT:
left();
leftPressed = true;
break;

case RIGHT:
right();
rightPressed = true;
break;

default:
break;

}
repaint();

}
当按键被释放的时候,我们就把相关的标记位设置为false。
public void keyReleased(int keyCode)
{
int action = getGameAction(keyCode);

switch (action)
{
case LEFT:
leftPressed = false;
buttonPressed = "";
break;

case RIGHT:
rightPressed = false;
buttonPressed = "";
break;
default:
break;
}
repaint();

}

这样我们在重新绘制屏幕的时候就可以根据标记位的状态进行绘画了:
if (leftPressed)
{
left();
}
if (rightPressed)
{
right();
}

笔者给出一个简单的实例来进行论证,我们制作一个MIDlet,当用户按下LEFT的时候,J2ME字符串向左侧移动,当用户按下RIGHT的时候,J2ME字符串向右侧移动。简单起见,我没有处理DOWN和UP的情况。下面是应用程序截图和源代码。

 

 

 

 

 

package com.j2medev;

import javax.microedition.lcdui.*;
import javax.microedition.midlet.MIDlet;
import javax.microedition.midlet.MIDletStateChangeException;

public class KeyActionMIDlet extends MIDlet
{

private Display display;

private MainCanvas mainCanvas;

protected void startApp() throws MIDletStateChangeException
{

display = Display.getDisplay(this);
mainCanvas = new MainCanvas();
new Thread(mainCanvas).start();
display.setCurrent(mainCanvas);

}

protected void pauseApp()
{

}

protected void destroyApp(boolean arg0) throws MIDletStateChangeException
{

}

}

package com.j2medev;

import javax.microedition.lcdui.*;

public class MainCanvas extends Canvas implements Runnable
{
private String buttonPressed;

private boolean leftPressed;

private boolean rightPressed;

private int px = getWidth() / 2;

public final int py = getHeight() / 2;

public MainCanvas()
{
buttonPressed = " ";
}

private void left()
{
if (px >= 0)
{
px--;
}
buttonPressed = "LEFT";
repaint();
}

private void right()
{
if (px <= getWidth())
{
px++;
}
buttonPressed = "RIGHT";
repaint();
}

public void run()
{
while (true)
{
if (leftPressed)
{
left();
}
if (rightPressed)
{
right();
}

try
{
Thread.sleep(50);
} catch (InterruptedException e)
{
e.printStackTrace();
}
}

}

public void paint(Graphics g)
{
g.setColor(0xFFFFFF);
g.fillRect(0, 0, getWidth(), getHeight());
g.setColor(0x000000);

g.drawString(buttonPressed, 20, 20, Graphics.LEFT | Graphics.TOP);
g.drawString("J2ME", px, py, Graphics.HCENTER | Graphics.TOP);

}

public void keyReleased(int keyCode)
{
int action = getGameAction(keyCode);

switch (action)
{
case LEFT:
leftPressed = false;
buttonPressed = "";
break;

case RIGHT:
rightPressed = false;
buttonPressed = "";
break;
default:
break;
}
repaint();

}

public void keyPressed(int keyCode)
{
int action = getGameAction(keyCode);

switch (action)
{

case LEFT:
left();
leftPressed = true;
break;

case RIGHT:
right();
rightPressed = true;
break;

default:
break;

}
repaint();

}

public void keyRepeated(int keyCode)
{
int action = getGameAction(keyCode);
switch (action)
{
case LEFT:
left();
break;
case RIGHT:
right();
break;

default:
break;
}
repaint();

}
}

posted @ 2007-04-28 09:51 笨蛋啊帆 阅读(209) | 评论 (0)编辑 收藏

尽管支持移动信息设备规范MIDP 1.0的手机大量地出现在市面上,不管是Nokia未来发售的各款手机,还是Motorola T720、V60i,Sony Ericsson P800等手机,每一款都支持MIDP 1.0。但是,Sun也没忘记继续和各家手机厂商制定下一代Java手机的规格—MIDP 2.0。

何谓应用程序管理员


应用程序管理员在规格中也称作Application Management Software。这是一个用来执行J2ME应用程序的程序,它负责管理该装置上所有的J2ME应用程序。

应用程序管理员的设计方式会随着平台的不同而不同,但是大致上可以分成两种方式:

1. 在背后运作,使用者不知道应用程序管理员的存在。这种类型的应用程序管理员概念如图1所示。



图1 背后运作的应用程序管理员


背后运作的应用程序管理员的设计方式,使得一般的J2ME程序看起来和应用程序管理员一样,即使实际上应用程序管理员在背后运作,使用者也很难感受到。这种设计可以在MIDP for Palm之中看到,Java HQ就是这样的东西,如图2所示。



图2 Java HQ


但是,如果是程序开发者一旦安装了Developer.prc,仍然可以透过Java HQ之中的Developer Preference里的MIDlets按钮来观察整个系统之中所安装的每一个Java应用程序。

2. 一个单一的进入点,使用者必须先进入应用程序管理员,然后才能启动个别的Java应用程序。这种类型的应用程序管理员概念如图3所示。



图3 单一进入点的应用程序管理员


这种应用程序管理员设计可以在Motorola A388、Nokia 9210、Nokia 7650的手机上看到。

JAD与JAR


一个完整的MIDP应用程序是由一个JAD文件(纯文字文件)与JAR(ZIP压缩档)所组成。JAD与JAR之间的关系可以用图4简单描述。之所以有这样的设计,主要为了下面两个原因:



图4 JAD与JAR关系图


1. 网络传输费用;

2. 安全性。

MIDP 2.0之后,为了保护JAR不受窜改,同时也让安装程序的人可以确定MIDlet的来源,所以特别增加了安全设计如图5。



图5 安全设计JAD图


其中,MIDlet-Jar-RAS-SHA1属性值为经过base64编码的执行文件数字签章。而MIDlet-Certificate-<n>-<m>属性值为安全证明。在MIDlet安装前,应用程序管理员会使用安全证明来验证公开金钥的可靠性,然后再使用此公开金钥解开数字签章,确认此执行档的来源并确定没有受到非法窜改。

最后请注意,并非每种装置在安装时都要求同时有JAD与JAR,有些装置只需要JAR即可。不过,有JAD和没有JAD的J2ME应用程序在安全性上是有差异的。

MIDP执行环境


根据MIDP规格,所谓MIDP执行环境(MIDP Execution Environment)指的是下面几项所构成的集合:

1. 实作CLDC中所定义的类别函数库的类别档(以Java撰写)及原生程序(Native code,以C撰写)。MIDlet Suite里不能有与CLDC类别函数库同样名称的类别;

2. 实作MIDP中所定义的类别函数库的类别档(以Java撰写)及原生程序(Native code,以C撰写)。MIDlet Suite里不能有与MIDP类别函数库同样名称的类别;

3. 所有来自同一个JAR档中的类别档。包括设计者自己所撰写的类别、其它的JSR(例如Profile或Optional Package),或其它开放的函数库(例如kXML或kSOAP);

4. 所有来自同一个JAR档之中的非类别档(即资源文件),另外,记录管理系统(RMS,MIDP版的数据库管理系统)也是可共享的资源之一;

5. 权限确认与连结外部资源;

6. 描述文件与清单文件的内容。

以上这几点构成所谓的MIDP执行环境。应用程序管理员会保证这些资源都可以在执行时期供MIDlet存取。而且,位于同一个MIDlet Suite的MIDlet会共享同一组MIDP执行环境,并且可以彼此互动。MIDlet可以调用CLDC的类别函数库,也可以调用MIDP的类别函数库,如图6所示。



图6 MIDlet执行环境图


只有存取标准CLDC与MIDP函数库的MIDlet Suite才可以跨平台。通常手机厂商会针对自己的装置开发专属的API,例如Nokia的UI API、SMS API、Camera API。一旦程序使用了这些专属API,那么除非其它厂商也在其虚拟机器之中实作这些API,否则很难达到跨平台的目的。

功能与资源


位于JAR档之中的类别档可以被同一个JAR档之中的MIDlet所使用。同一个JAR档里的资源档可以透过java.lang.Class.getResourceAsStream()来存取。描述档的内容则可以透过javax.microedition.mdilet. MIDlet.getAppProperty()取得,如图7所示。



图7 JAR功能资源图
使用getResourceAsStream()时,必须给定一个URL,由于我们要存取JAR内部的资源,如果使用「/」作为开头,代表绝对路径,「/」代表JAR文件之中的根目录。如果没有使用「/」,则视为相对路径,要看调用getResourceAsStream()类别的所在路径而定,这样容易造成混淆。所以请尽量使用「/」作为开头的绝对路径。

MIDlet的程序结构


如果曾经撰写过Java Applet或Java Servlet,就知道制作Java Applet,必须继承自java.applet.Applet这个类别;制作Java Servlet,则程序必须继承自javax.servlet. http.HttpServlet这个类别。同理,要撰写能够在手机上执行的Java MIDlet必须要继承自javax.microedition.midlet. MIDlet类别,如图8所示。



图8 MIDlet继承体系图


javax.microedition.midlet.MIDlet类别中定义了三个抽象方法,它们分别是:startApp() ==> 至运作状态;pauseApp() ==> 至停止状态;destoryApp() ==> 至消灭状态。应用程序管理员就是透过这三个抽象方法来控制MIDlet的生命周期。因此,所有的MIDlet都必须实现这三个方法,才保证能正常运作。因此,一个MIDlet的程序外壳至少要如下:

import javax.microedition.midlet.*;
            public class HelloMIDlet extends MIDlet
            {
            public HelloMIDlet()
            {
            //建构式
            }
            public void startApp()
            {
            }
            public void pauseApp()
            {
            }
            public void destroyApp(boolean unconditional)
            {
            }
            }


根据MIDP规格,MIDlet中不应该有“public static void main(Straing[] args)”这个方法。如果有则应用程序管理员会忽略不管。

一旦MIDlet被JAM载入之后,首先会先呼叫MIDlet中没有参数的建构式以进行初始化的工作。如果熟悉Java语法一定知道Java语言的一些特性,就是如果没有在程序中加入任何建构式,编译器会自动帮助加入一个预设建构式。但是如果已经撰写了自己的建构式(有任何参数),那么编译器将不会自动帮助加上预设建构式。因此,如果有必要撰写有参数的建构式,别忘了再手动加上一个没有参数的建构式,否则MIDlet将无法正确地初始化。

MIDlet的起始行为


当MIDlet被应用程序管理员产生之后,MIDlet就开始运作,程序设计师应该做的事情如图9所示。



图9 MIDlet起始行为图


我们会使用Display.getDisplay(this)来取得代表该MIDlet显示屏的Display对象。从应用程序管理员呼叫startApp()到MIDlet结束运作这段时间之内,不管何时呼叫Display.getDisplay(this),取得的都是同一份Display对象的参考。

要设定显示在屏幕上的画面,会使用Display对象的参考,并调用其setCurrent()方法,即display.setCurrent( Displayable类别的子类别实体)。因此一个可以运作的MIDlet程序,至少如HelloMIDlet.java,代码为:

import javax.microedition.midlet.*;
            import javax.microedition.lcdui.*;
            public class HelloMIDlet extends MIDlet
            {
            private Display display;
            public HelloMIDlet()
            {
            display = Display.getDisplay(this);
            }
            public void startApp()
            {
            Form t = new Form("画面");
            display.setCurrent(t);
            }
            public void pauseApp()
            {
            }
            public void destroyApp(boolean unconditional)
            {
            }
            }


注意:根据规格MIDlet只能由应用程序管理员产生,我们不能自己在程序里生成其它的MIDlet,并呼叫其生命周期函数,这样做将引发SecurityException异常。

MIDlet的生命周期


前面简单地叙述了应该如何撰写一个MIDlet的轮廓。但事实上,MIDlet的运作稍微复杂一点。所以接下来要仔细探讨MIDlet的运作细节,也就是MIDlet的生命周期。

当MIDlet被应用程序管理员成功地初始化之后,就开始展开了它的生命周期。图10描述了一个MIDlet的生命周期。MIDlet的生命周期完全由应用程序管理员控制,也就是说,当MIDlet要从一个状态变成另外一个状态时,应用程序管理员会呼叫对应的回呼函数(Call Back,也就是MIDlet类别定义的那三个抽象方法)。MIDlet基本上有三种状态,分别是停止状态(Paused)、启动状态(Active)及毁灭状态(Destroyed)。MIDlet开始时一定是先进入停止状态,然后应用程序管理员再将它转换成启动状态,然后调用startApp(),见图10。只有当应用程序管理员认为MIDlet的状态必须改变时,才会呼叫图中的相关函数。



图10 MIDlet生命周期图
以Active状态来说,MIDlet先进入运作状态,然后才调用startApp(),而MIDlet会先调用pauseApp()或destroyApp(),然后再进入停止状态和毁灭状态,这就是之所以Active没有被动式(字尾没有加ed),而Paused和Destroyed都是被动式(字尾有加ed)的真正涵义。

如果MIDlet自己调用这些函数,通常不会发生错误(除非程序本身有逻辑上的错误),但是也不会造成状态的转换,只是一个单纯的函数呼叫而已。如果MIDlet在状态转换回呼函数执行时发生错误,那么就应该丢出MIDletStateChange Exception异常,让应用程序管理员知道该如何处理。

请使用下列程序HelloMIDlet.java代码来验证MIDlet的生命周期:

import javax.microedition.midlet.*;
            import javax.microedition.lcdui.*;
            public class HelloMIDlet extends MIDlet
            {
            private Display display;
            public HelloMIDlet()
            {
            System.out.println("Constructor") ;
            display = Display.getDisplay(this);
            }
            public void startApp()
            {
            System.out.println("startApp Called") ;
            Form t = new Form("画面");
            display.setCurrent(t);
            }
            public void pauseApp()
            {
            System.out.println("pauseApp Called") ;
            }
            public void destroyApp(boolean unconditional)
            {
            System.out.println("destroyApp Called :" + unconditional) ;
            }
            }


执行结果时我们会在讯息显示窗口看到:

Constructor
            startApp Called


此结果表示建构式先被呼叫,然后startApp()才会呼叫。

这时如果我们按下强制关闭应用程序的按钮 ,屏幕上就会出现:

destroyApp Called :true


这代表当我们使用装置上的按钮强制关闭MIDlet时,应用程序管理员会调用destroyApp(),并传入true作为参数。因此,撰写程序时,可以假定destroyApp()传入true时,是硬件强制关闭MIDlet的情形。

从图10中可以看出,startApp()很可能不只是被呼叫一次,而是每次从停止状态重新回到运作状态的时候都会被应用程序管理员调用,所以只需要被初始化一次的动作就不适合放在startApp()之中,请改用建构式做初始化动作。如果startApp()丢出MIDletStateChangeException或RuntimeException或两者的子类别,那么会立刻进入毁灭状态,而且系统会自动调用destroyApp(true)。

由于规格告诉我们,startApp()的执行时间应该尽可能短。如果程序在执行时,发生的错误是可以稍候就解决的(很可能是系统资源暂时不足),那么程序设计师就该直接丢出MIDletStateChangeException。拦截之后,再调用notifyPaused(),稍待一会再藉由异步事件呼叫resumeRequest(),重新试看看。如果发生错误即使稍待一会也无法解决,那么程序设计师就应该直接调用notifyDestroyed()来结束程序。参见如下代码:

import javax.microedition.midlet.*;
            import javax.microedition.lcdui.*;
            public class HelloMIDlet extends MIDlet
            {
            private Display display;
            public HelloMIDlet()
            {
            System.out.println("Constructor") ;
            display = Display.getDisplay(this);
            }
            public void startApp()
            {
            System.out.println("startApp Called") ;
            Form t = new Form("画面");
            display.setCurrent(t);
            throw new RuntimeException() ;
            }
            /*
            public void startApp() throws MIDletStateChangeException
            {
            System.out.println("startApp Called") ;
            Form t = new Form("画面");
            display.setCurrent(t);
            throw new MIDletStateChangeException() ;
            }
            */
            public void pauseApp()
            {
            System.out.println("pauseApp Called") ;
            }
            public void destroyApp(boolean unconditional)
            {
            System.out.println("destroyApp Called :" + unconditional) ;
            }
            }


执行结果如下:

Constructor
            startApp Called
            startApp threw an Exception
            java.lang.RuntimeException
            java.lang.RuntimeException
            at HelloMIDlet.startApp(+33)
            at javax.microedition.midlet.MIDletProxy.startApp(+7)
            at com.sun.midp.midlet.Scheduler.schedule(+266)
            at com.sun.midp.main.Main.runLocalClass(+28)
            at com.sun.midp.main.Main.main(+88)
            destroyApp Called :true


一般来说,应用程序管理员会因为某些状况必须请MIDlet停止运作,例如手机突然来电、闹铃响了或者使用者切换到其它程序执行。在这些情况下,为了避免MIDlet占用太多系统资源,应用程序管理员就会调用该MIDlet的pauseApp()。这样程序设计师应该在pauseApp()之中适时释放一些非必需的资源,等到回到运作状态时,应用程序管理员会重新呼叫startApp(),这时再将这些之前被pauseApp()释放的资源重新找回来。
当MIDlet进入停止状态时,不应该使用任何资源。如果应用程序管理员调用pauseApp()时产生例外情形,MIDlet就应该立刻进入毁灭状态。

同样的情况也发生在destroyApp()。通常此方法被调用的时候,代表MIDlet要被关闭了,所以程序设计师应该在这里释放自己所配置的资源。只要MIDlet进入了毁灭状态,就无法再回头。如果是系统自己调用destroyApp(),那么在destroyApp()执行时万一发生例外,这些例外将被忽略,MIDlet一样会被关闭。根据规格,我们不能在MIDlet之中直接呼叫System.exit()或Runtime.exit()来结束程序的执行。如果这样做,就会引发java.lang.SecurityException异常。

MIDlet管理自己的生命周期


除了由应用程序管理员来控制MIDlet的生命周期之外,MIDlet本身也可以决定自己的状态,但不是自己改变状态,而是MIDlet先呼叫上述相对应的状态改变函数。这些函数会发出讯息通知应用程序管理员,请它来帮助改变MIDlet的状态,但是决定权在应用程序管理员,不保证一定可行。状态改变函数如图11所示:



图11 MIDlet状态改变函式图


对照以上这两张与状态有关的图,举个例子大家可能会比较清楚:假设今天如果是MIDlet主动要将MIDlet的状态由运作状态变成停止状态,那么直接呼叫pauseApp()函数只会执行pauseApp()之中的程序代码,而无法改变MIDlet的状态。MIDlet必须呼叫notifyPaused()以通知应用程序管理员,应用程序管理员收到通知之后,才会让MIDlet进入停止状态。

不过,由MIDlet调用notifyPaused()与应用程序管理员主动要求停止,两者之间是有所差别的。它们主要在于应用程序管理员主动要求停止时,pauseApp()会被呼叫,而由MIDlet调用notifyPaused(),pauseApp()不会被调用。但是两者都会让MIDlet进入停止状态。所以在MIDlet调用notifyPaused()之前,最好自己也先调用pauseApp()比较适当。

同样的情况也发生在notifyDestroyed()与destroyApp()。除非是系统强制关闭MIDlet,否则最好MIDlet先呼叫destroyApp(),然后再呼叫notifyDestroyed()。请应用程序管理员帮助将MIDlet转换到毁灭状态,最后结束MIDlet的运作。单单MIDlet自己呼叫destroyApp()是没有用的。

destroyApp()有个布尔值作为参数,根据MIDP的规格,如果传入true,那么MIDlet不管如何应该无条件释放所有资源,然后让应用程序管理员结束MIDlet的运作,这是系统或硬件强制关闭MIDlet的情形。如果使用者调用notifyDestroyed()来结束MIDlet,那么在调用destroyApp()时,最好传入false,代表这并非系统或硬件强制关闭,这时如果MIDlet不希望结束执行,那么它可以藉由丢出MIDletStateChangeException异常告知呼叫它的人:“我还不想被消灭,请待会再来。”

这里可以看出startApp()、pauseApp()及destroyApp()并非控制MIDlet生命周期的函数,它们只是一个提供初始化资源、释放资源的地方而已。

根据MIDP 规格书中所说,即使MIDlet处于停止状态,它仍然可以处理异步事件(Asynchronous Event),比方说Timer的事件或是其它回呼函数(Callback)。这将涉及到resumeRequest()的使用。resumeRequest()会将MIDlet从停止状态回到运作状态,并调用startApp()。由于当时MIDlet处于停止状态,所以必须依靠异步事件使MIDlet重新回到运作状态。

在一些范例程序中,我们常常只呼叫notifyDestroyed(),而没有呼叫destroyApp(),这是因为范例通常没有释放资源的需求,所以可以不用呼叫。但是如果是正规的程序,建议记得呼叫destroyApp(false)会比较好。

总结


通过本文的讨论,相信大家都可以发现MIDP更成熟了,功能更强了,但是也更复杂了。

附注:本文中出现的Java程序代码已在Java 2 SDK 1.4.x与J2ME Wireless Toolkit 2.0的Win32版本上完成测试。本文所有操作皆在Windows 2000 Professional与Windows XP Professional中文版操作系统上经过测试。本文的范例程序代码请至Http://www.javatwo.net/experts/moli/publish/下载。

名词解释

MIDP: Java API中面向移动终端的集合。通过与J2ME中的面向移动终端产品配置CLDC配合使用,就能够提供J2ME应用程序所需的运行环境。主要用于手机、PDA及双向寻呼机等低价位移动信息终端。

MIDlet: 即一个可以执行的J2ME/MIDP应用程序基本单位。除了继承自javax.microedition.midlet.MIDlet之外,还包括让此类别可以顺利执行的所有其它类别和资源档(只要是非类别档都称作资源档)所构成的集合,所以一般又称做MIDlet应用程序(MIDlet Application)。

MIDlet Suite: 许多MIDlet所构成的集合一般又叫做MIDlet应用程序套件。MIDlet Suite和MIDlet的关系,就好像Office与Word、Excel、PowerPoint、Access的关系。

JAR档(.jar档): 实际上包含MIDlet Suite的档案,属于ZIP档格式。

描述档(.jad档): 用来描述一个MIDlet Suite基本数据,以及该MIDlet Suite内含的MIDlet内含的MIDlet相关信息(类别名称、图标、程序名)的外部档案(不在JAR档内部)。

应用程序管理员(Java Application Manager): 负责将MIDlet Suite安装到机器上执行,以及负责管理MIDlet生命周期的机制(或软件)总称。应用程序管理员会根据使用者的需求安装、启动、停止或移除相对应的MIDlet。

posted @ 2007-04-28 09:31 笨蛋啊帆 阅读(357) | 评论 (0)编辑 收藏

Application demonstration in web browser applet

Your application should run without any changes inside applet.

    * Keep it in mind that if your j2me application requires HTTP access the applet must be signed. By default the applet will only communicate with the host the applet came from (your website).
    * Applet do not allow calls to System.getProperty();

Embedding applet into html page

MicroEmulator comes with three differend Skins:

    * Default 176x220 Full Screen and 176x176 Normal Canvas, Skin 226x471.
    * Minimum 128x128 Full Screen and 128x96 Normal Canvas, Skin 157x285. minimum.jar
    * Large 240x320 Full Screen and 240x266 Normal Canvas, Skin 292x618. large.jar

Html fragment example for Default device:

<applet code="org.microemu.applet.Main"
                width=226 height=471 archive="me-applet.jar,(MIDlet application jar)">
        <param name="midlet" value="(MIDlet application main class)">
</applet>

    * To support Nokia UI nokiaui.jar should be included in the archive attribute.
    * To support Siemens API siemensapi.jar should be included in the archive attribute.

There is additional parameter in the applet definition tag if you want to start the applet with device other than the default one:

  <param name="device" value="(device main class or device.xml)">

    * Minimum phone included in distribution is org/microemu/device/minimum/device.xml
    * Minimum phone with mouse and color is org/microemu/device/minimum/device-color.xml
    * Large phone is org/microemu/device/large/device.xml

Remember to include the new device jar into the applet archive tag

    * minimum phone included in distribution is packaged inside minimum.jar
    * large phone included in distribution is packaged inside large.jar

Example for SimpleDemo MIDlet, Nokia UI support and Minimum device:

<applet code="org.microemu.applet.Main"
                width=157 height=285 archive="me-applet.jar,nokiaui.jar,minimum.jar,simpledemo.jar">
        <param name="midlet" value="org.microemu.midp.examples.simpledemo.SimpleDemo">
        <param name="device" value="org/microemu/device/minimum/device.xml">
</applet>

Example for SimpleDemo MIDlet and Minimum device with mouse and color:

<applet code="org.microemu.applet.Main"
                width=157 height=285 archive="me-applet.jar,minimum.jar,simpledemo.jar">
        <param name="midlet" value="org.microemu.midp.examples.simpledemo.SimpleDemo">
        <param name="device" value="org/microemu/device/minimum/device-color.xml">
</applet>

Example for SimpleDemo MIDlet and Large Skin:

<applet code="org.microemu.applet.Main"
                width=292 height=618 archive="me-applet.jar,large.jar,simpledemo.jar">
        <param name="midlet" value="org.microemu.midp.examples.simpledemo.SimpleDemo">
        <param name="device" value="org/microemu/device/large/device.xml">
</applet>

posted @ 2007-04-02 02:30 笨蛋啊帆 阅读(453) | 评论 (0)编辑 收藏

一、J2ME中需要的Java基础知识    现在有大部分人,都是从零开始学J2ME的,学习J2ME的时候,总是从Java基础开始学习,而且现在讲Java基础的书籍中都是以J2SE来讲基础,这就给学习造成了一些不必要的麻烦,下面将J2ME中用到的和不需要的Java基础知识做一个简单的说明:    J2ME中使用到的Java基础知识:  1、Java语法基础:包括基本数据类型、关键字、运算符等等  2、面向对象的思想:类和对象的概念,继承和多态等等。  3、异常处理  4、多线程    J2ME中没有用到的Java基础知识:  1、JDK中javac和java命令的使用  2、Java基础中的很多类在J2ME中没有,或者类中的方法做了大量的精简。所以建议在J2ME中熟悉类库。  3、Applet、AWT、Swing这些知识在J2ME中根本使用不到。    简单说这么多,希望学J2ME的朋友们能少走一些弯路,不足之处希望大家积极指正和补充。    二、J2ME中暂时无法完成的功能    列一些J2ME中暂时无法完成的功能,希望大家能积极补充:  1、在手机中不更改代码实现移植,主要指游戏。  2、动态修改按钮文字。  3、在Canvas上接受中文输入。  4、操作本地资源、例如地址本、已收短信息等。  5、制作破坏性的手机病毒。  6、其他等待大家来补充。    三、J2ME的跨平台性    J2ME技术源于Java,所以也具有JVM的优势,可以在支持Java的平台上进行移植,但是现在的J2ME技术在跨平台上却做的很糟糕,我们来简单看一下原因:  1、手机的屏幕尺寸不一:  这个主要在界面制作上。    如果你使用的是高级用户界面,比如你做的是应用开发或者用户登陆、用户注册这样的通用功能时,一般没有什么问题。    如果你使用的是低级用户界面,比如你做的是游戏,那么你就需要考虑这个问题了。    2、厂商的扩展API不统一:  例如Nokia的扩展API类库UI系列,在别的手机上或者没有实现,或者包名不同等等。    3、手机平台上实现的bug:  例如Nokia的7650在实现双缓冲上有bug,那么在这种机型上运行的软件就不能使用双缓冲。其他NOKIA上的一些bug,可以参看:http://blog.csdn.net/Mailbomb/archive/2005/03/24/329123.aspx     4、手机性能问题。  不同手机的可用内存、最大jar文件都有要求,例如Nokia S40的大部分手机支持的最大jar文件为64K,最大可用内容为210K。    所以现在的手机软件,特别是游戏都提供支持的机型列表,也才有了手机游戏移植人员的存在。    四、学习J2ME可以从事的工作种类    现在J2ME技术可以说相当的火暴,这里介绍一些学好了J2ME之后可以从事的工作的种类:    1、J2ME游戏开发人员  根据游戏策划或者文档要求,在某种特定的机型(以Nokia S40或S60居多)开发游戏程序。  这是现在大部分J2ME程序员从事的工作。    需要熟练掌握:高级用户界面、低级用户界面、线程,如果是网络游戏,还需要熟练网络编程。    2、J2ME应用开发人员  现在的移动应用还不是很多,但是还是出现了一些,特别是移动定位以及移动商务相关的内容。    需要熟练掌握:高级用户界面、线程和网络编程。    3、J2ME游戏移植人员  参照源代码,将可以在一个平台上可以运行的游戏移植到其他平台上去。例如将Nokia S40的游戏移植到S60上,或者索爱的T618等等。    主要是控制屏幕坐标,有些可能需要替换一些API。    需要熟悉各平台之间的差异以及相关的技术参数,比如屏幕大小、最大jar文件尺寸等等。    五、J2ME程序设计的几个原则    1、使用面向对象编程。  虽然使用面向过程编程可以减小文件的尺寸,但是为了以后维护的方便和利于扩展,还是要使用面向对象编程。    2、使用MVC模式  将模型、界面和控制分离。现在很多的程序将三者合一,但是如果你做的程序比较大的话,还是建议你进行分离。    3、自动存储用户设定  使用RMS来存储用户的信息,例如存储用户上次输入的用户名、密码、用户对于系统的设定等,这样不仅可以减少用户的输入,而且对用户友好。很多程序甚至做了自动登陆等。    4、一些系统设置允许用户关闭。如背景音乐、背景灯显示等。    5、将低级用户界面的绘制动作放在一个独立的线程里面去。    6、在需要大量时间才能完成的工作时,给用户一个等待界面。    六、从模拟器到真机测试    对于J2ME开发者来说,模拟器给我们带来了很多方便,比如可以在模拟器中调试程序以及很方便的察看程序的效果,但是模拟器也给我们带来了一些问题,比如模拟器实现的bug等等,所以进行真机测试是必须的。    1、为什么要进行真机测试?  因为模拟器程序可能存在bug,以及真机的性能有限,所以必须进行真机测试。    2、如何将程序传输到机器中?  将程序传输到机器中有如下方式:  a) OTA下载    b) 使用数据线传输    c) 红外传输    d) 蓝牙  你可以根据条件,选择合适的方式。    3、 真机测试主要测什么?  真机测试的内容很多,主要测试以下几个方面:  a) 程序的功能    b) 程序的操作性,是否易操作    c) 程序的大小,比如Nokia S40系列的手机大部分接受的最大文件尺寸为64K     d) 程序运行速度,速度是否可以忍受。    七、从WTK到厂商SDK     对于J2ME爱好者来说,基本上大家都是从SUN的WTK(J2ME Wireless Toolkit)开始的,但是对于实际应用来说,仅仅使用WTK是远远不够的,所以在学习过程中,必须完成从WTK到SDK的跨越。    1、厂商SDK的下载地址?  http://blog.csdn.net/Mailbomb/archive/2005/01/01/236606.aspx     2、厂商SDK和WTK有什么不同?  厂商SDK最简单的理解就是在WTK的基础上增加了自己的模拟器和自己的扩展API。  也就是说,你在使用厂商的SDK时,可以使用厂商的扩展类库,例如Nokia的UI类库,和厂商自己的模拟器而已。  每个厂商的扩展API都不多,而且不尽相同。    3、如何使用?  有些厂商SDK的使用都和WTK相同,例如SamSung。  Nokia提供了独立的界面来开发,但是这个界面在实际开发中使用不多。    4、厂商SDK的问题  厂商SDK实现过程中,有一些bug,而且和真机实现不一致。例如NOKIA的混音播放问题等等。    八、在J2ME中获得手机IMEI的方法    IMEI是Internation mobile entity identification的简称,在手机中输入*#06#可以显示该数字,长度为15位,全球唯一,永远不会冲突,所以可以作为识别用户的一个标志。    下面是在J2ME中获得IMEI的方法:    1、MOTO系列的手机可以通过读取系统的IMEI属性获得,代码如下:  String imei = System.getProperty("IMEI");     2、SIEMENS系列的手机可以通过读取系统的com.siemens.IMEI属性获得,代码如下:  String imei = System.getProperty("com.siemens.IMEI");     九、J2ME网络连接中显示问题的解决办法    在网络编程中,有些时候会出现一些在没有接收到网络数据就显示界面的,造成界面显示不符合要求(例如公告显示,会先显示公告的背景图片再显示公告信息),这里提一个简单的解决办法给大家:    解决这种情况的方法分成三个步骤:  1、在需要显示的界面中,调用发送网络数据的方法。每次显示时调用该构造方法,不调用Display的setCurrent方法显示。    2、显示等待界面(例如进度条等),给用户提示,在进行网络连接。    3、在处理网络反馈的数据完以后,调用Display的setCurrent方法显示显示当前界面。    十、增强J2ME的String能力——分割字符串    从JDK1.4以后,String类中新增了split方法来实现字符串的分割,但是在J2ME中却没有该方法(MIDP2.0中也没有实现),但是在实际使用过程中,有些时候的确要用到这种操作,这里将我以前实现的一段代码和大家共享:  /**     * 分割字符串,原理:检测字符串中的分割字符串,然后取子串    * @param original 需要分割的字符串    * @paran regex 分割字符串    * @return 分割后生成的字符串数组    */   private static String[] split(String original,String regex)     {     //取子串的起始位置    int startIndex = 0;     //将结果数据先放入Vector中    Vector v = new Vector();     //返回的结果字符串数组    String[] str = null;     //存储取子串时起始位置    int index = 0;     //获得匹配子串的位置    startIndex = original.indexOf(regex);     //System.out.println("0" + startIndex);     //如果起始字符串的位置小于字符串的长度,则证明没有取到字符串末尾。    //-1代表取到了末尾    while(startIndex < original.length() && startIndex != -1)     {     String temp = original.substring(index,startIndex);     System.out.println(" " + startIndex);     //取子串    v.addElement(temp);     //设置取子串的起始位置    index = startIndex + regex.length();     //获得匹配子串的位置    startIndex = original.indexOf(regex,startIndex + regex.length());     }     //取结束的子串    v.addElement(original.substring(index + 1 - regex.length()));     //将Vector对象转换成数组    str = new String[v.size()];     for(int i=0;i   {     str[i] = (String)v.elementAt(i);     }     //返回生成的数组    return str;     }     十一、J2ME在低级用户界面上分行显示文字    在J2ME的低级用户界面开发中,经常会遇到需要在Canvas上显示大量的文字,例如关于界面、游戏说明、游戏公告等信息。如果在设计时,将文字的内容和长度都固定,既不利于修改也不利于维护。下面介绍一个简单的方法,实现一个简单、可维护性强的方式。    实现方法:  1、将需要显示的所有信息做成一个字符串。  2、编写一个将该字符串按照要求转换为字符串数组的方法。  3、将转换后的数组以循环的方式显示在Canvas上。    通过这样三个步骤,则修改显示的信息时,只需要修改包含显示信息的字符串即可,自己书写的方法可以按照以前的标准重新分割新的字符串。如果需要修改每行显示的字符个数,则只需要修改自己书写的方法即可。    通过这样一种实现方式,可以很方便的实现显示一些比较长的文本信息,即使是可变长度的字符串也没有问题。    十二、J2ME中使用记录存储系统(RMS)存储信息    在MIDP中,没有文件的概念,所以永久存储一般只能依靠记录存储系统实现,关于记录存储系统的简介,可以参看教程:http://www-900.ibm.com/developerWorks/cn/java/j-wi-rms/index.shtml     下面是一些记录存储系统的常用编码介绍:    1、打开记录集:  打开记录集使用RecordStore里面的静态方法openRecordStore,示例代码如下:  RecordStore rs = RecordStore.openRecordStore(“username”,true);     这样就打开了一个名称为rs的记录集,其中username为记录集的名称,该名称可以根据需要来取,第二个参数代表是否则没有时创建新的记录集,true代表在该记录集不存在时,创建新的记录集,false代表不创建。    如果在打开记录集时,该记录集不存在,则抛出RecordStoreNotFoundException异常,所以检测记录集是否已创建可以使用该异常。    注意:记录集打开以后记得关闭。    2、向记录集中写入数据    2.1增加数据  向已经打开的记录集中添加数据,需要使用addRecord方法,示例代码:  byte[] bytes = {1,2,3};   int id = rs. addRecord(bytes,0,bytes.length);     该代码将字节数组bytes的全部内容写入到记录集中,该方法的返回值为该信息的id,注意:id从1开始,而不是从0开始。  你可以循环使用该方法向记录集中写入多条数据。    2.2修改数据  修改已经存在的记录集中指定id的数据,需要使用setRecord方法,示例代码:  byte[] bytes = {1,2,3};   rs. setRecord(1,bytes,0,bytes.length);     以上代码的作用是将字节数组bytes的全部内容写入到id为1的记录集rs中。    该操作会覆盖已有的数据。    说明:有些时候,你需要将信息写入到记录集中的第一条记录中,则可以结合以上两个方法,则第一次时向记录集增加数据,以后来进行修改。    3、从记录集中读出数据  从记录集中读取已有数据,需要使用getRecord方法,示例代码:  byte[] bytes = rs. getRecord(1);     该代码从记录集rs中读取第一条数据,将读取到的数据放在bytes数组中。    在读取数据时,可以获得记录集中id的个数,可以使用getNumRecords方法获得    综合代码为:  int number = rs. getNumRecords();   int id = 1;   if(id >0 && id < number){   byte[] bytes = rs. getRecord(1);   }     4、从记录集中删除记录  从记录集中删除记录的方法有两种:逻辑删除和物理删除。  逻辑删除是指给删除的记录打标记。  物理删除是指从物理上删除该记录,但是该记录的id不能被重用,也就是说该id不会被继续使用。例如一个记录集中有5个记录,假设你删除了id为3的数据,则剩余记录的id依然为1、2、4、5。这给便历带来了一定的麻烦。    5、便历记录集  便历记录集,即访问记录集中的所有数据,有两个方法,详见:  http://gceclub.sun.com.cn/NASApp/sme/controller/teclist?tid=0103     6、其他操作  6.1删除记录集  删除记录集不同于删除记录,需要使用deleteRecordStore方法,示例代码:  RecordStore. deleteRecordStore(“username”);     该代码删除名称为username的记录集。    十三、J2ME加密数据的一个第三方开源免费类库介绍    在J2ME编程中,经常遇到一些数据在存储或者传输时需要加密,下面介绍一个第三方的加密类库的一些资料:  加密类库的官方主页:http://www.bouncycastle.org/     介绍的文章:  中文:https://18900.motorola.com/ewa_portal/develope/jc_j2messl_5_1.jsp   英文:http://www.javaworld.com/javaworld/jw-12-2002/jw-1220-wireless.html   该文章的源代码包含使用的一些方法。    备注:因为该类库提供的功能比较强大,所以类库的尺寸比较大,最后在发布时需要将类库中不需要的类删除    十四、如何播放声音    在J2ME中,处理声音需要使用到Mobile Media API(MMAPI),该包是MIDP1.0的可选包,在MIDP2.0中已经包含了这个包。所以如果你使用MIDP1.0的话,请确认你的运行环境是否支持。    一般手机支持的声音文件格式为wav、mid和mpg等。具体请查阅你的手机说明文档。    在声音处理中,有很多处理的方式,这里说一下最常用的情况,播放JAR文件中的wav文件。    播放声音文件的流程:  1、按照一定的格式读取声音文件。    播放JAR文件中的声音文件一般是将声音文件处理成流的形式。示例代码:  InputStream is = this.getClass().getResourceAsStream("/Autorun.wav");     其中Autorun.wav文件位于JAR文件的根目录下,如果位于别的目录,需要加上目录名称,如/res /Autorun.wav。    2、将读取到的内容传递给播放器。  将流信息传递给播放器,播放器按照一定的格式来进行解码操作,示例代码:  Player player = Manager.createPlayer(is,"audio/x-wav");     其中第一个参数为流对象,第二个参数为声音文件的格式。    3、播放声音。  使用Player对象的start方法,可以将声音播放出来,示例代码:  player.start();    在播放声音时也可以设定声音播放的次数,可以使用Player类中的setLoopCount方法来实现,具体可查阅API文档。    下面是在NOKIA S60 模拟器中测试通过。代码如下:  package sound;     import javax.microedition.midlet.*;     import javax.microedition.lcdui.*;     import javax.microedition.media.*;     import java.io.*;     public class SoundMIDlet extends MIDlet {     private Player player = null;     /** Constructor */     public SoundMIDlet() {     try{     InputStream is = this.getClass().getResourceAsStream("/Autorun.wav");     player = Manager.createPlayer(is,"audio/x-wav");     }catch(IOException e){     System.out.println("1:" + e);     }catch(MediaException e){     System.out.println("2:" + e);     }catch(Exception e){     System.out.println("3:" + e);     }   }     /** Main method */     public void startApp() {     if(player != null){     try{     player.start();     }catch(MediaException e){     System.out.println("4:" + e);   }     }   }     /** Handle pausing the MIDlet */     public void pauseApp() {     }     /** Handle destroying the MIDlet */     public void destroyApp(boolean unconditional) {     }     }     十五、J2ME 3D编程的一些资料    随着J2ME技术的发展,以及硬件速度的提升,3D游戏程序将慢慢的变成主流,最近想学习这一块的编程,所以收集了一些资料,和大家一起分享:  1、JSR184   JSR184是Nokia公司起草的一个关于3D API的规范,下载地址为:  http://www.forum.nokia.com/main/1,,1_0_10,00.html#jsr184     2、Nokia的3D编程资料  http://www.forum.nokia.com/main/1,6566,21,00.html     3、3D引擎  一个简单的开放源代码的3D游戏引擎  http://www.j2me.com.cn/Soft_Show.asp?SoftID=19     国内一个合作开发3D引擎的项目:  http://gceclub.sun.com.cn/NASApp/sme/jive/thread.jsp?forum=11& thread=8593     4、一款3D游戏产品  http://games.sina.com.cn/newgames/2004/04/040217696.shtml     5、支持3D的开发工具  当前一些高端的手机支持3D开发,支持3D开发的开发工具中,通用的有SUN的J2MEWTK2.2。专用的是厂商提高的支持JSR184的SDK。    十六、3D编程——第一个3D程序    参考WTK2.2提供的demo,完成了第一个3D程序,虽然很简单,而且有些问题还不是很清楚,还是把代码共享出来和愿意学习J2ME 3D编程的朋友一起学习。    关于代码的编译和运行说明如下:  1、以下代码在J2ME WTK2.2下面编译通过。    2、代码分为两个文件:First3DCanvas.java和First3DMIDlet.java。    3、使用J2ME WTK2.2建立新的工程,主MIDlet类为:first3d. First3DMIDlet     4、将代码保存在你的工程目录下的first3d目录下。    5、将J2ME WTK安装目录下的apps\Demo3D\res\com\superscape\m3g\wtksamples\retainedmode\ content目录中的swerve.m3g文件复制到你的工程目录下的res目录下。    6、你的工程建立后,设置工程,通过WTK界面中的“设置”按钮打开设置窗口,在“API选择”中,设置“目标平台”为:自定义;“简档”为 “MIDP2.0”;“配置”为“CLDC1.1”;选中“Mobile 3D Graphics for J2ME(JSR184)”。    7、这样你就可以编译和运行以下代码了。    源代码如下:  // First3DMIDlet.java     package first3d;     import javax.microedition.midlet.*;     import javax.microedition.lcdui.*;     public class First3DMIDlet extends MIDlet {     private First3DCanvas displayable = new First3DCanvas();     public void startApp() {     Display.getDisplay(this).setCurrent(displayable);     }     public void pauseApp() {}     public void destroyApp(boolean unconditional) {}     }     // First3Dcanvas.java     package first3d;     import javax.microedition.lcdui.*;     import javax.microedition.m3g.*;     import java.util.*;     /**     * 第一个3D程序    */     public class First3DCanvas     extends Canvas     implements Runnable {     /**World对象*/     private World myWorld = null;     /**Graphics3D对象*/     private Graphics3D g3d = Graphics3D.getInstance();     /**Camera对象*/     private Camera cam = null;     private int viewport_x;     private int viewport_y;     private int viewport_width;     private int viewport_height;     private long worldStartTime = 0;     //重绘时间    private int validity = 0;     public First3DCanvas() {     //启动重绘界面的线程    Thread thread = new Thread(this);     thread.start();     try {     //导入3D图片    myWorld = (World) Loader.load("/swerve.m3g")[0];     viewport_x = 0;     viewport_y = 0;     viewport_width = getWidth();     viewport_height = getHeight();     cam = myWorld.getActiveCamera();     //设置cam对象    float[] params = new float[4];     int type = cam.getProjection(params);     if (type != Camera.GENERIC) {     //calculate window aspect ratio     float waspect = viewport_width / viewport_height;     if (waspect < params[1]) {     float height = viewport_width / params[1];     viewport_height = (int) height;     viewport_y = (getHeight() - viewport_height) / 2;     }     else {     float width = viewport_height * params[1];     viewport_width = (int) width;     viewport_x = (getWidth() - viewport_width) / 2;     }     }     worldStartTime = System.currentTimeMillis();     }     catch (Exception e) {}     }     protected void paint(Graphics g) {     //清除背景    g.setColor(0x00);     g.fillRect(0, 0, getWidth(), getHeight());     //和3D对象绑定    g3d.bindTarget(g);     g3d.setViewport(viewport_x, viewport_y, viewport_width, viewport_height);     long startTime = System.currentTimeMillis() - worldStartTime;     validity = myWorld.animate((int)startTime);     try {     g3d.render(myWorld);     }     finally {     g3d.releaseTarget();     }     }     public void run() {     try{     while(true){     //重绘图形    repaint(viewport_x, viewport_y, viewport_width, viewport_height);     }     }catch(Exception e){}     }     }   十七、在J2ME网络编程中使用CMWAP代理    在中国移动提供的网络连接中,分为CMNET和CMWAP两种,其中CMNET可以无限制的访问互联网络,资费比较贵。CMWAP类似一个HTTP的代码,只能访问支持HTTP的应用,但是资费便宜,稳定性比较差。    在实际的J2ME网络编程中,一般需要提供以CMWAP代理的方式连接网络,在J2ME中,连接的代码和直接连接有所不同,代码如下:  HttpConnection http = (HttpConnection)Connector.open(("http://10.0.0.172/"+url);   http.setRequestProperty("X-Online-Host",ServerName);     例如你需要访问的地址为:http://www.test.com/login/loginServlet     则上面的代码就为:  HttpConnection http = (HttpConnection)Connector.open(("http://10.0.0.172/"+   ”login/loginServlet”);   http.setRequestProperty("X-Online-Host",”www.test.com”);     在实际使用过程中,只需要使用实际需要访问的地址的域名或者IP来代替ServerName,例如示例中的“www.test.com”,使用后续的地址类代替代码中的url,例如示例中的“login/loginServlet”,就可以实际的使用CMWAP代理来进行连接了。    十八、J2ME中的时间处理全攻略    时间处理在程序开发中相当常见,下面对于时间处理做一个简单的说明。    一、时间的表达方式  时间在J2ME中有两种表达方式:    1、以和GMT1970年1月1号午夜12点和现在相差的毫秒数来代表  这种方式适合比较两个时间之间的差值。    2、以对象的形式来表达    二、时间处理的相关类  时间处理在J2ME中涉及三个类:    1、System类  long time = System. currentTimeMillis();     使用该方法可以获得当前时间,时间的表达方式为上面提到的第一种。    2、Date类  Date date = new Date();     获得当前时间,使用对象的形式来进行表达。    3、Calendar类  Calendar calendar = Calendar. getInstance();     三、时间处理的具体操作    1、以上三种表达方式的转换:  a)将System类获得的时间转换为Date对象  Date date = new Date(System. currentTimeMillis());     b)将Date类型的对象转换为Calendar类型的对象  Calendar calendar = Calendar. getInstance();   Date date = new Date();   calendar.setTime(date);     2、使用Calendar完成一些日期操作:  Calendar是时间处理中最常用也是功能最强大的类,可以用它来获得某个时间的日期、星期几等信息。    获得日期:  Calendar calendar = Calendar. getInstance();   ……   int day = calendar.get(Calendar. DATE);     获得日期、年份、星期的操作和这个类似。    需要注意的是:Calendar中表示月份的数字和实际相差1,即1月用数字0表示,2月用数字1表示,……12月用数字11表示。    十九、J2ME中随机数字处理全攻略    在程序中生成随机数字,用处比较,如人工智能领域等等,这里对于在J2ME中生成随机数的操作进行一个简单的整理,希望对大家能有帮助。    J2ME和J2SE不同,不能使用Math类的random来生成随机数字,只能使用java.util包的Random类来生成随机数字。    1、创建Random类型的对象:  Random random = new Random();     Random random = new Random(10010010);     以上两种是创建Random对象的方式,第一种使用默认构造方法,和以下的代码作用完全等价:  Random random = new Random(System. currentTimeMillis());   相当与使用当前时间作为种子数字来进行创建。    第二种方式通过自己来指定种子数字来进行创建。    大家可以根据需要使用以上两种方式的任一种。    2、生成随机数字:  创建好了随机对象以后,我们就可以来生成随机数字了:    生成随机整数:  int k = random.nextInt();     生成随机长整数:  long l = random.nextLong();     3、生成指定范围的数字:  例如生成0-10之间的随机数字:  int k = random.nextInt();   int j = Math.abs(k % 10);     首先生成一个随机整数k,然后用k和10取余,最后使用Math类的abs方法取绝对值,获得0-10之间的随机数字。    获得0-15之间的随机数,类似:  int k = random.nextInt();   int j = Math.abs(k % 15);     获得10-20之间的随机数字:  int k = random.nextInt();   int j = Math.abs(k % 10) + 10;     二十、在J2ME手机编程中使用字体    在J2ME手机编程中,可以通过使用字体类——Font在低级用户界面中,获得更好的表现效果,那么如何使用Font类呢?    首先,由于手机设备的限制,手机中支持的字体类型很有限,所以在J2ME中只能使用手机支持的默认字体来构造Font类对象。下面是创建Font类的对象时使用的方法:  getFont(int face,int style,int size);     例如:  Font font = Font.getFont(Font.FACE_SYSTEM,Font.STYLE_BOLD,Font. SIZE_MEDIUM);     无论哪一个参数,都只能使用系统设置的数值,这些数值具体的大小在不同的手机上可能不同。下面对于其中的三个参数的取值做详细的介绍:    face参数指字体的外观,其的取值:  FACE_MONOSPACE——等宽字体  FACE_PROPORTIONAL——均衡字体  FACE_SYSTEM——系统字体    style参数指字体的样式,其的取值:  STYLE_BOLD——粗体  STYLE_ITALIC——斜体  STYLE_PLAIN——普通  STYLE_UNDERLINED——下划线  STYLE_BOLD | STYLE_ITALIC——粗斜体  STYLE_UNDERLINED | STYLE_BOLD——带下划线粗体  STYLE_UNDERLINED | STYLE_ITALIC——带下划线斜体  STYLE_UNDERLINED | STYLE_ITALIC | STYLE_BOLD——带下划线的粗斜体    size参数指字体的大小,其的取值:  SIZE_SMALL——小  SIZE_MEDIUM——中  SIZE_LARGE——大    通过上面的参数的值,可以组合出你需要的字体对象。    下面是一些常用的字体操作:  1. 获得系统的默认字体:  Font font = Font.getDefaultFont();     2. 在panit方法内部,假设Graphics参数的名称为g,则获得当前字体的方法是:  Font font = g.getFont();     3. 在panit方法内部,假设Graphics参数的名称为g,则设置当前字体的方法是:  g.setFont(font);     其中font为你构造好的字体对象。    4. 在MIDP2.0中,List可以设置每行的字体格式,方法是:  list.setFont(0,font);     则上面的代码是将list中的第一行设置为font类型的字体。    二十一、在J2ME手机程序开发中使用颜色    在J2ME手机开发过程中,需要经常用到颜色来进行绘制,增强程序的表现效果,下面就介绍一下如何使用颜色。    由于J2ME技术比较简单,所以没有实现专门的颜色类,而只是使用RGB的概念来代表颜色。这里简单介绍一下RGB的概念,颜色是由红(Red)、绿 (Green)、蓝(Blue)三原色组成的,所以可以使用这三个颜色的组合来代表一种具体的颜色,其中R、G、B的每个数值都位于0-255之间。在表达颜色的时候,即可以使用三个数字来表达,也可以使用一个格式如0X00RRGGBB这样格式的十六进制来表达,下面是常见颜色的表达形式:    红色:(255,0,0)或0x00FF0000     绿色:(0,255,0)或0x0000FF00     蓝色:(255,255,255)或0x00FFFFFF     其他颜色也可以通过上面的方式组合出来。    知道了颜色的表达方式以后,下面来介绍一下如何在J2ME程序中使用颜色,涉及的方法均在Graphics类中,有以下几个:    1.getColor():  获得当前使用的颜色,返回值是0x00RRGGBB格式的数字。例如:  int color = g.getColor();     其中g为Graphics类型的对象。    2.setColor(int RGB):  设置使用的颜色。例如:  g.setColor(0x00ff0000);     3.setColor(int red, int green, int blue)   和上面的方法作用一样,例如:  g.setColor(255,0,0);     在设置了Graphics使用的颜色以后,再进行绘制的时候,就可以绘制指定的颜色了。    二十二、在J2ME联网应用中获得客户端的手机号码    在J2ME程序开发过程中,为了一定的需要,经常需要来获得用户的手机号码,但是这个功能却在标准的J2ME类库中没有提供。    在使用中国移动的CMWAP方式连接网络时,中国移动会将用户的手机号码放在一个名称为x-up-calling-line-id的头信息中,可以通过读取该头信息,获得用户的手机号码,具体代码如下:  String usermphone = http.getHeader("x-up-calling-line-id");     其中http是HttpConnction类型的对象。    二十三、使用J2ME发送手机短信息    在程序中,发送短信息的方式一般有三种:    1、使用程序在网络上发送短信息,例如各大网站的短信业务。这种方式是通过程序将信息发送给运营商的网关服务器,然后通过运营商的网络发送给手机。    2、在计算机中,通过数据线连接到手机,然后通过手机来发送短信息。这种方式是通过使用AT指令来实现。爱立信手机的AT指令你可以在以下地址找到: http://mobilityworld.ericsson.com.cn/development/download_hit.asp     3、 通过在手机中运行的程序来发送短信息。这个正是本文实现的方式。    在J2ME中,如果想发送短信息,需要使用WMA包,MIDP2.0中已经包含,MIDP1.0中可以通过厂商提供的扩展API实现,和WMA的类库基本一样。    下面是使用WMA向指定手机号码发送短信息的一个方法,很简单。当然WMA也提供了其他的方式来发送更多的内容。  // SMSUtil.java     package my.util;     import javax.wireless.messaging.*;     import javax.microedition.io.*;     /**     * 发送文本短信息的方法    */     public class SMSUtil{     /**     * 给指定号码发送短信息    * @param content 短信息内容    * @param phoneNumber 手机号码    * @return 发送成功返回true,否则返回false     */     public static boolean send(String content,String phoneNumber){     //返回值    boolean result = true;     try{     //地址    String address = "sms://+" + phoneNumber;     //建立连接    MessageConnection conn = (MessageConnection)Connector.open(address);     //设置短信息类型为文本,短信息有文本和二进制两种类型    TextMessage msg = (TextMessage)conn.newMessage(MessageConnection.TEXT_MESSAGE);     //设置信息内容    msg.setPayloadText(content);     //发送    conn.send(msg);     }catch(Exception e){     result = false;     //未处理    }     return result;   }   }     二十四、使用简单的J2ME程序测试MIDlet的生命周期    在MIDlet程序学习中,生命周期是一个比较抽象的概念。其实生命周期就是一个简单的规定,规定了MIDlet中的每个方法,什么时候被系统调用。下面是一个示例代码,在每个方法的内部都输出一条语句,可以根据程序的输出结果来验证各方法被调用的顺序,具体代码如下:  //文件名:LifeCircleMIDlet.java     import javax.microedition.midlet.*;     /**     * 测试MIDlet的生命周期    */     public class LifeCircleMIDlet extends MIDlet{     /**     * 默认构造方法    */     public LifeCircleMIDlet(){     System.out.println("默认构造方法");     }     /**     * 启动方法    */     public void startApp(){     System.out.println("startApp方法");     }     /**     * 暂停方法    */     public void pauseApp(){     System.out.println("pauseApp方法");     }     /**     * 销毁方法    * @param b     */     public void destroyApp(boolean b){     System.out.println("destroyApp方法");     }     }     在J2WTK中运行该程序时,可以使用浏览器中的“MIDlet”菜单中的暂停和恢复菜单,模拟暂停事件。    二十五、使用OTA来发布你的程序    众所周知,J2ME程序发布的形式主要有:OTA、数据线传输、红外和蓝牙传输等。这里简单说说如何通过OTA来发布你的程序。    OTA是Over The Air的简写,也就是通过网络下载,这是主要的发布形式之一。现在的百宝箱都是采用这种形式。    使用OTA来发布程序,需要如下几个步骤:  1、在你的WEB服务器上添加对于jad和jar文件的MIME支持。  后缀名:jad   MIME类型:text/vnd.sun.j2me.app-descriptor     后缀名:jar   MIME类型:application/java-archive     2、发布WML页面:  例如你的jar文件名test.jad,则最简单的下载页面是:          test       你可以将以上代码保存在WEB服务器上,例如保存为text.wml     3、修改jad文件:  在jad文件中增加 MIDlet-Jar-URL: http://domain/directory/test.jar   其中的http://domain/directory/test.jar为你的jar文件的路径。    经过上面的设置,你就可以将你的wml页面路径作为你的WAP下载页面发布了。用户只需要在手机上输入这个路径就可以访问和下载你的程序了。

posted @ 2007-03-31 21:56 笨蛋啊帆 阅读(173) | 评论 (0)编辑 收藏

Web 2.0

Zend创建者Andi Gutmans为我们带来了一段非常精炼的Web 2.0诠释,抓住Web 2.0的几个要点,对Ajax、Blog、Wash-Up等Web2.0标志概念以及之间的关系进行了简明扼要的阐述,足以作为Web 2.0的经典定义。(感谢Chris笔录)

Web 2.0由三个部分组成,第一个部分--RIA,Rich Internet Applications(丰富互联网应用程序),Flash、Ajax等网络应用技术便归于RIA旗下--改进强化用户浏览器体验,将桌面操作带入浏览器操作的方法--最典型的,在网页中支持超级托拽功能。

Web 2.0第二个部分--SOA,Service-Oriented Architecture(面向服务架构),也是Web 2.0的核心部分,相关词汇:Feeds、RSS、Web Services、Mash-Up。SOA的核心问题--开放、互通,如何让来自不同服务商的网络应用能够协作运行。作为SOA核心词汇之一,Mash-Up其实就在我们身边--例如,Google开放功能模块API接口的行为直接导致一系列以Google Earth为核心的第三方网络服务产生。

Web 2.0第三个部分--Social Web(网络社交),Web 2.0应用大大提高了终端用户的互动性,用户不再仅仅是服务的使用者,同时成为服务的创造参与者--WiKi、Blog、Tag、Podcast等用户交互行为让Web 2.0应用更能“吸引”用户,并且激发用户行为创造资源和内容。

如何才能创建一个成功的Web 2.0网站/产品?

亦有三点需要注意的问题。

首先,你需要规划一个出色的Ajax支持产品--Ajax特指一系列创建Web 2.0服务的关键技术,是创造丰富用户体验的技术基础,当然,在浏览器支持方面也要IE/火狐通吃。

其次,为你的产品选择合适的编程语言,能够完成你需要创立的网络服务--服务产品是Web 2.0关键,选择好合适的语言是产品的技术基础。

最后,拥有一个可迭代(Iterative)的软件架构,可以非常方便地添加、部署、升级功能特性--这对拥有大量用户的情况下进行持续更新十分必要。”

文中关键词总结:

RIA--Ajax、Flash
SOA--RSS、Feeds、Web Services、Mash-Up,API
Social Web--Wiki、Blog、Tagging、Podcast
Iterative

posted @ 2007-03-31 21:52 笨蛋啊帆 阅读(132) | 评论 (0)编辑 收藏

 
    这篇文章主要谈谈Hibernate的入门开发,例子很简单,就是向数据表中添加用户名和密码。我分别使用了三种方法,一种是直接写代码,写Hbm映射文件等;一种是通过Hbm映射文件来生成代码;一种是通过代码来生成Hbm映射文件。使用了一些自动化工具,XMLBuddy是用来编辑XML文件的, JBoss Eclipse IDE是用来编写Doclet标记的。这篇文章还谈到了一些Eclipse的使用技巧,比如建立“用户库”、“生成 Getter 和 Setter”等。
 
   关于一些软件的使用技巧,我以前的文章都有提及,这里不多说了,必要时会提示一下。
   
 
 
1. 所需软件
Ant 1.6.2        http://ant.apache.org
 
 Eclipse 3.0.1 及 语言包
 
 
XMLBuddy          http://www.xmlbuddy.com/
    下载             XMLBuddy 2.0.38   
 
   看看说明,需要挑选与使用的eclipse相对应的版本,这里选择JBossIDE-1.4.1-e30.zip,这是它的镜像下载地址:
 
Hibernate         http://www.hibernate.org/
     下载            Hibernate 2.1.8
 
   同时也要把 Hibernate Extensions 2.1.3 下载了,它包含一些工具类。
 
 
●  MySQL 4.1.8
     注意需要mysql-connector-java-3.0.16-ga-bin.jar文件
 
 
 
2. 安装和配置 
 
    这里着重指出一下,需要在环境变量中设置 ANT_HOME ,并将其指向Ant安装目录,还要在Path变量值中添加 “%ANT_HOME%\bin;” 可以参考 java环境变量设置
 
 · XMLBuddy 和Jboss-ide 我都是采用links方式安装的,
 
●  建立库文件夹:
 
   在D盘下新建一个java目录,在此目录下新建一个Hibernate子目录,在此子目录下再新建一个lib子目录。
D:\
  -java
     -Hibernate
               -lib
  · 将下载的hibernate-2.1.8.zip解压,如解压后的目录名为hibernate-2.1,将这个目录下的hibernate2.jar复制到先前建立的lib目录下,即D:\java\Hibernate\lib目录;
 
 · 然后将hibernate-2.1目录下的lib子目录中的以下文件也复制到这个lib目录下:
jta.jar  cglib-full-2.0.2.jar  commons-collections-2.1.1.jar  commons-logging-1.0.4.jar commons-lang-1.0.1.jar  dom4j-1.4.jar ehcache-0.9.jar log4j-1.2.8.jar odmg-3.0.jar odmg-3.0.jar
 
 · 解压hibernate-extensions-2.1.3.zip将其子目录tools中的hibernate-tools.jar和子目录lib中的 velocity-1.3.1.jar、jdom.jar也复制到D:\java\Hibernate\lib目录中
 
 ·  解压xdoclet-bin-1.2.2.zip,将其子目录lib中的xdoclet-1.2.2.jar、xdoclet-hibernate- module-1.2.2.jar、xjavadoc-1.1.jar、xdoclet-xdoclet-module-1.2.2.jar 也复制到D:\java\Hibernate\lib目录中
 
· 最后将mysql-connector-java-3.0.16-ga-bin.jar文件复制到这个lib目录下。
 
   这样,需要用到的库文件已经准备好了,如果认为这样比较麻烦,也可以将jar文件全部复制到lib目录。下面介绍如何在Eclipse中设置“用户库”。
 
 
 
●  设置“用户库”
 
· 窗口 ->首选项 ->Java ->构建路径 ->用户库,
 
· 然后单击“添加JAR”
 
   这样,一个用户库文件就做好了,如果要更新Eclipse时,可以将其先导出,然后再导入即可。
这样做,对库文件便于管理,而且如果需要替换或者升级的话都比较方便。

 

 

●  设置“构建路径”

 

· 点击菜单“窗口”->首选项,以下都在“首选项”中配置:

   Java->构建路径
  
 
 
 
 
 
    这里以一个简单的程序来示范Hibernate的配置与功能,在这个例子中的一些操作,实际上会使用一些自动化工具来完成,而不一定亲自手动操作设定,这边完全手动的原因,在于让你可以知道Hibernate的基本流程。
     这是整个项目的结构:
 
好了,下面就开始创建项目了。
  
 
1. 创建项目
 
· 新建一个Java项目:HibernateBegin_1,注意选中“创建单独的源文件夹和输出文件夹”。点击“下一步”,切换到“库”,点击“添加库”,如下图:
 
· 选择“用户库”
 
· 勾选先前设置好的hibernate用户库。
 
 
2. 建立可持久化类 
 
· 下面开始创建一个新类:User ;包名:javamxj.hibernate
然后添加三个变量(斜体),代码如下:
								
										/* 
										 * 采用常规方法开发一个简单的Hibernate实例
										 * 创建日期 2005-3-31
										 * @author javamxj(分享java快乐)
										 * @link  Blog: htpp://javamxj.mblogger.cn  
										 *              htpp://blog.csdn.net/javamxj/ 
										 */
										package
										 javamxj
										.
										hibernate
										;
										public
										class User {    
								
										
												private
												int id;    privateString username;    privateString password;	}
						
· 点击工具栏上的“源代码(S)”,选择其中的“生成 Getter 和 Setter”(右击,在弹出菜单也可选择),如图:
 
点击“确定”后,生成如下代码:
								
										/* 
										 * 采用常规方法开发一个简单的Hibernate实例
										 * 创建日期 2005-3-31
										 * @author javamxj(分享java快乐)
										 * @link  Blog: htpp://javamxj.mblogger.cn  
										 *                 htpp://blog.csdn.net/javamxj/ 
										 */
										package
										 javamxj
										.
										hibernate
										;
										public
										class User {    privateint id;    privateString username;    privateString password;		
								
										
												public
												int getId() {		return id;	}	publicvoid setId(int id) {		this.id = id;	}	publicString getPassword() {		return password;	}	publicvoid setPassword(String password) {		this.password = password;	}	publicString getUsername() {		return username;	}	publicvoid setUsername(String username) {		this.username = username;	}}
						
 
    好了,这个类完成了。它是一个普通的Java对象(Plain Old Java Objects,就是POJOs,有时候也称作Plain Ordinary Java Objects),表示一个数据集合。下面建立一个Hbm文件将这个类映射到数据库的表格上。
 
 
3. 映射文件
 
   在javamxj.hibernate包下,新建一个名称为“User.hbm.xml”的文件,使用XMLBuddy编辑它(参考:利用XMLBuddy在Eclipse中开发XML ),这样比较方便。文件内容如下:
								
										<?
										xml
										version="1.0" encoding="GBK"?><!DOCTYPEhibernate-mappingPUBLIC"-//Hibernate/Hibernate Mapping DTD//EN""http://hibernate.sourceforge.net/hibernate-mapping-2.0.dtd"><hibernate-mapping><class name="javamxj.hibernate.User" table="UserTable"><id name="id"><generator class="assigned"/></id><property name="username"/><property name="password"/></class></hibernate-mapping>
						
    这个XML文件定义了对象属性映射到数据库表的关系,这里采用了assigned(程序设置)方式生成主键。当然还可以使用其它的方式来产生主键,后面的文章就采用了其它的方式产生主键。
 
   < property>标签用于定义Java对象的属性,这里只定义了最简单的方式,由Hibernate自动判断Java对象属性与数据库表名称对应关系。在<property/>标签上的额外设定(像是not null、sql-type等),则可以用于自动产生Java对象与数据库表的工具上。

 
 
4. 配置文件
 
   Hibernate可以使用XML或属性文件来进行配置,配置文件名默认为“hibernate.cfg.xml”(或者hibernate.properties)。
   在src目录下,新建一个hibernate.cfg.xml文件,内容如下:
								
										<?
										xml
										version='1.0' encoding='GBK'?><!DOCTYPEhibernate-configurationPUBLIC"-//Hibernate/Hibernate Configuration DTD//EN""http://hibernate.sourceforge.net/hibernate-configuration-2.0.dtd"><hibernate-configuration><session-factory><!-- 是否将运行期生成的SQL输出到日志以供调试 --><property name="show_sql">true</property><!-- SQL方言,这里设定的是MySQL --><property name="dialect">net.sf.hibernate.dialect.MySQLDialect</property><!-- JDBC驱动程序 --><property name="connection.driver_class">com.mysql.jdbc.Driver</property><!-- JDBC URL, "?useUnicode=true&amp;characterEncoding=GBK" 表示使用GBK进行编码 --><property name="connection.url">	  jdbc:mysql://localhost:3306/HibernateTest?useUnicode=true&amp;characterEncoding=GBK</property><!-- 数据库用户名 --><property name="connection.username">root</property><!-- 数据库密码 --><property name="connection.password">javamxj</property><!-- 指定User的映射文件 --><mapping resource="javamxj/hibernate/User.hbm.xml"/></session-factory></hibernate-configuration>
						
 
注意:这里使用的是“jdbc:mysql://localhost:3306/HibernateTest?useUnicod...”中的HibernateTest数据库,你需要在MySql中建立这个数据库。
 
 
5. 测试程序
 
   在javamxj.hibernate包下,新建一个Test类,内容如下:
								
										/* 
										 * 简单测试一下User类
										 * 创建日期 2005-3-31
										 * @author javamxj(分享java快乐)
										 */
										package
										 javamxj
										.
										hibernate
										;
										import
										 net
										.
										sf
										.
										hibernate
										.
										*
										;
										import
										 net
										.
										sf
										.
										hibernate
										.
										cfg
										.
										*
										;
										public
										class Test {	publicstaticvoid main(String[] args) {		try {			SessionFactory sf = new Configuration().configure()					.buildSessionFactory();			Session session = sf.openSession();			Transaction tx = session.beginTransaction();			User user = new User();			user.setUsername("Blog");			user.setPassword("分享java快乐");			session.save(user);			tx.commit();			session.close();		} catch (HibernateException e) {			e.printStackTrace();		}	}}
						
 
    这里简单说说一下流程,首先初始化Configuration,加载Hibernate的配置信息,然后Configuration取得 SessionFactory对象,并由它来开启一个Session,它代表对象与表格的一次会话操作,而 Transaction则表示一组会话操作,我们只需要直接操作User对象,并进行Session与Transaction的相关操作, Hibernate就会自动完成对数据库的操作。
 
 
6. 配置数据库
 
   在运行测试程序之前,还必须先设置好数据库。
   在MySQL中建立一个HibernateTest数据库,并建立UserTable表,SQL语句如下:
 
CREATETABLE usertable (
  ID int(6) NOTNULL auto_increment,
  username varchar(24) NOTNULLdefault'',
  password varchar(24) NOTNULLdefault'',
  PRIMARYKEY  (ID)
);
    这里,HibernateTest与hibernate.cfg.xml配置文件中的HibernateTest相对应,UserTable与hbm映射文件中的UserTable相对应。
 
 
7. 运行程序
 
   右击Test.java,点击运行,可以看到控制台输出一系列信息,最后一条输出语句应该是:
Hibernate: insert into UserTable (username, password, id) values (?, ?, ?)
   在下篇文章中会介绍如何利用log4j来控制输出信息。
 
   同时,在数据库中可以看到,数据已经添加进表里了:
 

posted @ 2006-12-04 02:13 笨蛋啊帆 阅读(526) | 评论 (0)编辑 收藏

     摘要: JSP动态网页设计之JSP与JavaBean。编程环境:Java 5.0.8+eclipse 3.2 + MyEclipse 5.0 + Tomcat 5.5.17好,学完理论,下面编写代码:打开eclipse,新建一个MyEclipse下面J2EE类的web项目,如图:项目名为:JspJiLinCh06,今天所有代码的树型图如下:新建一个com.coderdream.bean 包,然后新建一个S...  阅读全文

posted @ 2006-12-04 02:09 笨蛋啊帆 阅读(335) | 评论 (0)编辑 收藏

 
[肥肥世家]Linux 由于开放源码衍生出大量的发行版,为用户提供丰富选择的同时又给初学者造成困惑,这么多发行版应该选择哪种才适合自已呢?下面这个网址会给出答案,它会向你提出一些有关Linux和你应用环境的问题,你只要一步步回答,最后它就会根据你的回答为你选择几款最适合你的发行版。

http://www.zegeniestudios.net/ldc/index.php?firsttime=true



posted @ 2006-12-04 02:03 笨蛋啊帆 阅读(143) | 评论 (0)编辑 收藏

仅列出标题
共6页: 上一页 1 2 3 4 5 6 下一页 

posts - 51, comments - 17, trackbacks - 0, articles - 0

Copyright © 笨蛋啊帆