十一月's profileNOVEMBREBlogListsGuestbookMore Tools Help

Blog


    January 31

    MIDP游戏开发中处理文字的换行方案

    在游戏中,尤其是情景类的游戏当中,往往需要大量情节介绍的文字。要在小小的手机屏幕上显示这些文字,就必须对这些文字进行处理,使其能正确的换行,显示在你想要显示的宽度的范围内。下面我就会详细的介绍如何处理文字的换行。

      首先应该计算需要换行的位置。这里我们以文字需要显示的宽度linewd,和“\n”为换行的标志

    static public int ChangLine(String str, Font font, int linewd, boolean fullword)
    {
     // 计算需要换行的位置 str:需要显示的文字 font:文字的字体 linewd:需要显示的宽度
     int len = 0, wd = 0;
     for (int i = 0; i < str.length(); i++)
     {
      if (str.charAt(i) == ’\n’)
      {
       if (i == 0)
        return len + 1;
       else
        return len + 2;
      }
      wd += font.charWidth(str.charAt(i));
      if (wd > linewd)
      {
       if (fullword)
       {
        for (int j = len; j >= 0; j--)
        {
         if (str.charAt(j) < 0x30 || str.charAt(j) >= 128)
         {
          len = j;
          break;
         }
        }
       }
       return len + 1;
      }
      len = i;
     }
     return 0;
    }

      计算好位置后,就开始为文字分行。

    static public void DoLine(String infostr, int len)
    {
     // 为字符串分行,以便于显示
     String tmpStr;
     Vector InfoLines = null;
     InfoLines = new Vector();
     int tmpint; //需要换行的位置
     while (true)
     {
      tmpint = ChangLine(infostr, DefaultFont, len, false);
      if (tmpint == 0)
      {
       InfoLines.addElement(infostr);
       break;
      }
      else
      {
       if (infostr.charAt(tmpint - 1) == ’\n’)
        tmpStr = infostr.substring(0, tmpint - 1);
       else
        tmpStr = infostr.substring(0, tmpint);
        InfoLines.addElement(tmpStr);
        infostr = infostr.substring(tmpint, infostr.length());
      }
     }
    }

    Brew手机开发资源

    听朋友提起 Brew手机开发,这个东东以前还比较陌生。
    随手查了一点资料。

    记录一下:
    http://brew.qualcomm.com/brew/zh/developer/resources/ds/okb.html
    可以下载Brew的sdk以及开发工具

    http://www.cnjm.net/cgi-bin/lbcjm/forums.cgi?forum=30
    一个Brew开发论坛

    另外转一篇brew学习的文章:
    BREW高手之路-解析BREW学习过程

      首先声明,我不是BREW 高手,我只是一位普通的BREW OEM 工作者.只是在自己不断学习BREW 的过程中,总结了一些学习BREW 的方法,或者说是要学好BREW 应该一步步怎么走,循序渐进的看些什么,学些什么,现在将这些心得体会和大家分享,交流。
    (注,以下区分OEM 和Developer,OEM 是指手机OEM 厂商作BREW Porting 的,Developer 是指作BREW 应用开发的,即CP.每一步都分析对于这两种人的必要性)

    . 对BREW 有最基本,最浅显的了解(通过网站关于BREW 的新闻,介绍,对BREW 有个非技术角度的总体概念,知道他出现的用途是什么,在无线移动产业中处于什么位置,有什么优势等等,BREW 可以用来作些什么好玩的东东),激发对BREW 的初步热情.
    ->该步骤同时适合OEM,Developer
    ->参考资料:
    1. 请baidu 上进行BREW 关键字搜索
    2. BREW Overview.pdf

    . 进行BREW 最基本的技术层次的学习(对BREW 最基本的事件驱动机制的了解,如何创建接口,clsid是什么?如何一步步用vc 开发一个动态应用,每一步骤的作用,resource,mif 工具的使用,helloworld程序如何被创建,如何进行事件处理的大致流程,如何将BREW 动态应用转变为mod 文件下载到手机.....等等等等,这些基本的内容可以通过一般的BREW 文档学习到,qualcomm 网站上有一篇很好的brew 基本知识学习文档,study brew from scratch,另外网上有两本关于BREW 的英文ebook,也可作为入门资料,有一本BREW 手机游戏开发的中文书也可以作为该阶段的入门资料)
    ->该步骤同时适合OEM,Developer
    ->参考资料:
    1. brew_app_from_scratch.pdf
    2. BREW Programming guide(海信).pdf
    3. starting_brew.pdf
    4. 深入BREW 游戏开发
    5. Apress.Developing.Software.for.the.QUAL
    COMM.BREW.Platform.eBook-LiB.rar
    6. Wordware.Wireless.Game.Development.In.C.
    Cpp.With.BREW.eBook-LiB.chm

    . 深入理解BREW 接口机制:BREW 采用面向对象的接口体系结构对外提供服务.理解接口机制将有助于理解调用所谓的接口API(实际上是宏映射)是如何最终调用到AEE 层的真正函数,定义一个接口究竟底层作了什么,BREW 中的接口究竟是如何创建的等等(该机制非常非常好的学习文档是aee.h 和example下的mediaplayer,aee.h 里的宏,就是接口机制的实现手段,mediaplayer 更是将brew 的接口机制发挥的淋漓尽致)
    ->该步骤适合有兴趣的Developer,因为并非所有的Developer 需要知道接口机制,将IXXX_XXX 理解为真正的函数,熟练的运用他们,而不知道他们的底层实现机制也不会妨害Developer 的开发,但是对于需要开发Extention Interface 的Developer,理解BREW 接口机制是必须的. 该步骤对于OEM 而言是必须的.因为如果不理解BREW 接口机制的话,BREW OEM 所作的Porting 或者Modify 是盲目的,因为你根本不明白上层调用所谓的接口函数最终是如何映射到底层的,这样你无法清晰地发现,问题究竟在哪里?而对于Porting 新的Static ExtentionInterface,则更加必须掌握接口机制,比如IIMAGE,IMEDIA 这两个抽象接口的OEM 层新format 接口的Portng,你必须掌握接口机制,否则你不可能自己去实现一个接口,让上层很好的调用.
    ->参考资料:
    1. BREW_Architecture_Extensions.pdf
    2. BREW 究竟是什么-BREW 本质之我见.pdf(笔者)
    3. deep in BREW’s Interface .pdf(笔者)
    4. AEE.h
    5. Mediaplayer.c
    6.BREW部分接口源码

    . BREW 面向开发的核心机制的深入熟悉和理解:主要是事件分发,处理机制,Timer 机制,Alarm 机制,Notify 机制,Callback 机制,suspend,resume,background 等等
    ->该步骤同时适合OEM,Developer
    ->参考资料:
    1. deep in Event-Driven.pdf(笔者)
    2. 深入BREW 消息处理机制.pdf(笔者)
    3. SDK 帮助文档

    .BREW 开发代码的熟悉,积累以及SDK API 的熟悉:
    主要是通过阅读别人的应用(Example 是一个很好的示例代码)加深对BREW 接口函数使用的理解,同时再结合自己的不断锻炼,再结合查阅BREW SDK 帮助文档.使得自己能加深对BREW 各接口使用的理解,同时积累开发BREW应用程序的经验 (同时注意有关C++如何开发BREW应用以及注意点)
    ->该步骤适合Developer,同时适合感兴趣的OEM
    ->参考资料:SDK 帮助文档以及别人的应用源码

    .进行(你负责的或者是感兴趣的)BREW 模块(接口)
    的深入学习:这是OEM 的重要学习内容,原则上对一个模块的真正学习,应该包括:从整体上把握该接口的作用,它为开发者提供了什么功能,开发者利用它能做什么?从开发者的角度,最典型的使用该接口的方法和顺序(过程)是如何的?该接口的AEE 层(如果看的到代码的话)和O
    EM 层是具体如何实现的,这点的学习有助于清楚该接口对外提供的服务在底层是究竟如何实现的?如果该接口存在异步调用系统服务的话,它和系统是如何交互的,如何进行异步service 的调用?如果该接口是一个抽象接口,具体实现在OEM 层的话,要清楚该接口实现的特点,如何扩展该接口
    ->原则上只适合OEM,因为Developer 是看不到代码的.
    ->参考资料:
    1. SDK 帮助文档
    2. QCT Release 的BREW 平台代码 (主要是各Interface的具体实现)

    .BREW 模块加载,应用启动机制学习:可以通过AEEModGen.c,AEEAPPGen.c 以及mod 文件的makefile 深入学习BREW App 的创建过程,Module 的加载过程
    ->该步骤同时适合OEM,Developer,对于OEM,因为存在写静态应用的可能性,所以对于静态应用加载的特殊过程(需要提供的特殊load)需要熟悉.
    ->参考资料:
    1. AEEModGen.c,AEEAppGen.c,makefile of mod
    2. 深入BREW 模块加载机制.pdf(笔者)

    .BREW 面向OEM 的核心机制学习:学习如何在Task 中启动BREW,BREW 事件如何分发,启动,关闭一个BREW应用的整个过程,oem_notify 机制,抽象接口机制,Interface Register 机制,BREW 分层概念,app stack,如何传递Key Event,App Context,Memory Manager,Object Manager,Callback,Systemcallback,SystemObject 等等面向OEM 层的BREW 核心机制
    ->该步骤仅适合OEM,因为Developer 不可能使用这些机制.对于OEM,通常某些接口的OEM 层实现需要采用异步机制来调用系统服务,此时将综合运用以上机制,应该熟悉这些机制.
    ->参考资料:
    1. 深入BREW 抽象接口机制.pdf(笔者)
    2. BREW 分层机制阐述.pdf(笔者)
    3. PK 相关文档

    .BREW 接口验证机制的学习: 主要是学习PEK 工具的使用,这样能经常性的来验证BREW 接口Porting 的完整性.同时PEK 中的OAT 源码也是学习接口使用的很好资料.通常,对于一个扩展的接口,需要提供扩展的OAT测试module,所以需要熟悉这些.
    ->该步骤仅适合OEM
    ->参考资料:PEK 相关文档

    10.BREW UI 机制:主要学习BUIT(现在改名为BUIW)和UIOne.这两种机制主要用来进行UI 开发.由于BUIT 大量使用了设计模式,所以初学较难.
    ->同时适合感兴趣的OEM,Developer
    ->参考资料:
    1. BUIT SDK
    2. BUIT Example
    3. AppMgr 3.x

    11.OEM Notes 的经常性关注:经常性登陆BREW OEMExtranet 下载BREW OEM Notes,有助于拓宽解决一些BREW Porting 的方法,思路,即便不是马上能用上,也积累了一些经验
    ->仅适合OEM
    ->参考资料:OEM Extranet 资料

    January 30

    j2me最佳联网方案终结版

    (1) .由于无线设备所能支持的网络协议非常有限,仅限于HTTP,Socket,UDP等几种协议,不同的厂家可能还支持其他网络协议,但是,MIDP  
    1.0规范规定,HTTP协议是必须实现的协议,而其他协议的实现都是可选的。因此,为了能在不同类型的手机上移植,我们尽量采用HTTP作为网络连接的首选协议,这样还能重用服务器端代码。但是,由于HTTP是一个基于文本的效率较低的协议,因此,必须仔细考虑手机和服务器端的通信内容,尽可能地提高效率。 
      对于MIDP应用程序,应当尽量做到: 
      1.发送请求时,附加一个User-Agent头,传入MIDP和自身版本号,以便服务器能识别此请求来自MIDP应用程序,并且根据版本号发送相应的相应。 
     
      2.连接服务器时,显示一个下载进度条使用户能看到下载进度,并能随时中断连接。 
     
      3.由于无线网络连接速度还很慢,因此有必要将某些数据缓存起来,可以存储在内存中,也可以放到RMS中。 
      对于服务器端而言,其输出响应应当尽量做到: 
      1.  
    明确设置Content-Length字段,以便MIDP应用程序能读取HTTP头并判断自身是否有能力处理此长度的数据,如果不能,可以直接关闭连接而不必继续读取HTTP正文。 
     
      2.  
    服务器不应当发送Html内容,因为MIDP应用程序很难解析HTML,XML虽然能够解析,但是耗费CPU和内存资源,因此,应当发送紧凑的二进制内容,用DataOutputStream直接写入并设置Content-Type为application/octet-stream。 
     
      3. 尽量不要重定向URL,这样会导致MIDP应用程序再次连接服务器,增加了用户的等待时间和网络流量。 
     
      4.  
    如果发生异常,例如请求的资源未找到,或者身份验证失败,通常,服务器会向浏览器发送一个显示出错的页面,可能还包括一个用户登录的Form,但是,向MIDP发送错误页面毫无意义,应当直接发送一个404或401错误,这样MIDP应用程序就可以直接读取HTTP头的响应码获取错误信息而不必继续读取相应内容。 
     
      5.  
    由于服务器的计算能力远远超过手机客户端,因此,针对不同客户端版本发送不同响应的任务应该在服务器端完成。例如,根据客户端传送的User-Agent头确定客户端版本。这样,低版本的客户端不必升级也能继续使用。 
      MIDP的联网框架定义了多种协议的网络连接,但是每个厂商都必须实现HTTP连接,在MIDP  
    2.0中还增加了必须实现的HTTPS连接。因此,要保证MIDP应用程序能在不同厂商的手机平台上移植,最好只使用HTTP连接。虽然HTTP是一个基于文本的效率较低的协议,但是由于使用特别广泛,大多数服务器应用的前端都是基于HTTP的Web页面,因此能最大限度地复用服务器端的代码。只要控制好缓存,仍然有不错的速度。 
      SUN的MIDP库提供了Javax.microediton.io包,能非常容易地实现HTTP连接。但是要注意,由于网络有很大的延时,必须把联网操作放入一个单独的线程中,以避免主线程阻塞导致用户界面停止响应。事实上,MIDP运行环境根本就不允许在主线程中操作网络连接。因此,我们必须实现一个灵活的HTTP联网模块,能让用户非常直观地看到当前上传和下载的进度,并且能够随时取消连接。 
      一个完整的HTTP连接为:用户通过某个命令发起连接请求,然后系统给出一个等待屏幕提示正在连接,当连接正常结束后,前进到下一个屏幕并处理下载的数据。如果连接过程出现异常,将给用户提示并返回到前一个屏幕。用户在等待过程中能够随时取消并返回前一个屏幕。 
      我们设计一个HttpThread线程类负责在后台连接服务器,HttpListener接口实现Observer(观察者)模式,以便HttpThread能提示观察者下载开始、下载结束、更新进度条等。HttpListener接口如下: 
    public interface HttpListener { 
      void onSetSize(int size); 
      void onFinish(byte[] data, int size); 
      void onProgress(int percent); 
      void onError(int code, String message); 

      实现HttpListener接口的是继承自Form的一个HttpWaitUI屏幕,它显示一个进度条和一些提示信息,并允许用户随时中断连接: 
    public class HttpWaitUI extends Form implements CommandListener, HttpListener { 
      private Gauge gauge; 
      private Command cancel; 
      private HttpThread downloader; 
      private Displayable displayable; 
      public HttpWaitUI(String url, Displayable displayable) { 
        super("Connecting"); 
        this.gauge = new Gauge("Progress", false, 100, 0); 
        this.cancel = new Command("Cancel", Command.CANCEL, 0); 
        append(gauge); 
        addCommand(cancel); 
        setCommandListener(this); 
        downloader = new HttpThread(url, this); 
        downloader.start(); 
      } 
      public void commandAction(Command c, Displayable d) { 
        if(c==cancel) { 
            downloader.cancel(); 
            ControllerMIDlet.goBack(); 
        } 
      } 
      public void onFinish(byte[] buffer, int size) { … } 
      public void onError(int code, String message) { … } 
      public void onProgress(int percent) { … } 
      public void onSetSize(int size) { … } 

      HttpThread是负责处理Http连接的线程类,它接受一个URL和HttpListener: 
    class HttpThread extends Thread { 
      private static final int MAX_LENGTH = 20 * 1024; // 20K 
      private boolean cancel = false; 
      private String url; 
      private byte[] buffer = null; 
      private HttpListener listener; 
      public HttpThread(String url, HttpListener listener) { 
        this.url = url; 
        this.listener = listener; 
      } 
      public void cancel() { cancel = true; } 

    (2).  
      使用GET获取内容 
      我们先讨论最简单的GET请求。GET请求只需向服务器发送一个URL,然后取得服务器响应即可。在HttpThread的run()方法中实现如下: 
    public void run() { 
      HttpConnection hc = null; 
      InputStream input = null; 
      try { 
        hc = (HttpConnection)Connector.open(url); 
        hc.setRequestMethod(HttpConnection.GET); // 默认即为GET 
        hc.setRequestProperty("User-Agent", USER_AGENT); 
        // get response code: 
        int code = hc.getResponseCode(); 
        if(code!=HttpConnection.HTTP_OK) { 
            listener.onError(code, hc.getResponseMessage()); 
            return; 
        } 
        // get size: 
        int size = (int)hc.getLength(); // 返回响应大小,或者-1如果大小无法确定 
        listener.onSetSize(size); 
        // 开始读响应: 
        input = hc.openInputStream(); 
        int percent = 0; // percentage 
        int tmp_percent = 0; 
        int index = 0; // buffer index 
        int reads; // each byte 
        if(size!=(-1)) 
            buffer = new byte[size]; // 响应大小已知,确定缓冲区大小 
        else 
            buffer = new byte[MAX_LENGTH]; // 响应大小未知,设定一个固定大小的缓冲区 
        while(!cancel) { 
            int len = buffer.length - index; 
            len = len>128 ? 128 : len; 
            reads = input.read(buffer, index, len); 
            if(reads<=0) 
              break; 
            index += reads; 
            if(size>0) { // 更新进度 
              tmp_percent = index * 100 / size; 
              if(tmp_percent!=percent) { 
                percent = tmp_percent; 
                listener.onProgress(percent); 
              } 
            } 
        } 
        if(!cancel && input.available()>0) // 缓冲区已满,无法继续读取 
            listener.onError(601, "Buffer overflow."); 
        if(!cancel) { 
            if(size!=(-1) && index!=size) 
              listener.onError(102, "Content-Length does not match."); 
            else 
              listener.onFinish(buffer, index); 
        } 
      } 
      catch(IOException ioe) { 
        listener.onError(101, "IOException: " + ioe.getMessage()); 
      } 
      finally { // 清理资源 
        if(input!=null) 
            try { input.close(); } catch(IOException ioe) {} 
        if(hc!=null) 
            try { hc.close(); } catch(IOException ioe) {} 
      } 

      当下载完毕后,HttpWaitUI就获得了来自服务器的数据,要传递给下一个屏幕处理,HttpWaitUI必须包含对此屏幕的引用并通过一个setData(DataInputStream  
    input)方法让下一个屏幕能非常方便地读取数据。因此,定义一个DataHandler接口: 
    public interface DataHandler { 
      void setData(DataInputStream input) throws IOException; 

      HttpWaitUI响应HttpThread的onFinish事件并调用下一个屏幕的setData方法将数据传递给它并显示下一个屏幕: 
    public void onFinish(byte[] buffer, int size) { 
      byte[] data = buffer; 
      if(size!=buffer.length) { 
        data = new byte[size]; 
        System.arraycopy(data, 0, buffer, 0, size); 
      } 
      DataInputStream input = null; 
      try { 
        input = new DataInputStream(new ByteArrayInputStream(data)); 
        if(displayable instanceof DataHandler) 
            ((DataHandler)displayable).setData(input); 
        else 
            System.err.println("[WARNING] Displayable object cannot handle  
    data."); 
        ControllerMIDlet.replace(displayable); 
      } 
      catch(IOException ioe) { … } 

      以下载一则新闻为例,一个完整的HTTP GET请求过程如下: 
      首先,用户通过点击某个屏幕的命令希望阅读指定的一则新闻,在commandAction事件中,我们初始化HttpWaitUI和显示数据的NewsUI屏幕: 
    public void commandAction(Command c, Displayable d) { 
      HttpWaitUI wait = new HttpWaitUI("http://192.168.0.1/news.do?id=1", new  
    NewsUI()); 
      ControllerMIDlet.forward(wait); 

      NewsUI实现DataHandler接口并负责显示下载的数据: 
    public class NewsUI extends Form implements DataHandler { 
      public void setData(DataInputStream input) throws IOException { 
        String title = input.readUTF(); 
        Date date = new Date(input.readLong()); 
        String text = input.readUTF(); 
        append(new StringItem("Title", title)); 
        append(new StringItem("Date", date.toString())); 
        append(text); 
      } 

      服务器端只要以String, long,  
    String的顺序依次写入DataOutputStream,MIDP客户端就可以通过DataInputStream依次取得相应的数据,完全不需要解析XML之类的文本,非常高效而且方便。 
      需要获得联网数据的屏幕只需实现DataHandler接口,并向HttpWaitUI传入一个URL即可复用上述代码,无须关心如何连接网络以及如何处理用户中断连接。 
    (3). 
      使用POST发送数据 
      以POST方式发送数据主要是为了向服务器发送较大量的客户端的数据,它不受URL的长度限制。POST请求将数据以URL编码的形式放在HTTP正文中,字段形式为fieldname=value,用&分隔每个字段。注意所有的字段都被作为字符串处理。实际上我们要做的就是模拟浏览器POST一个表单。以下是IE发送一个登陆表单的POST请求: 
    POST http://127.0.0.1/login.do HTTP/1.0 
    Accept: image/gif, image/jpeg, image/pjpeg, */* 
    Accept-Language: en-us,zh-cn;q=0.5 
    Content-Type: application/x-www-form-urlencoded 
    User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1) 
    Content-Length: 28 
    \r\n 
    username=admin&passWord=1234 
      要在MIDP应用程序中模拟浏览器发送这个POST请求,首先设置HttpConnection的请求方式为POST: 
    hc.setRequestMethod(HttpConnection.POST); 
      然后构造出HTTP正文: 
    byte[] data = "username=admin&password=1234".getBytes(); 
      并计算正文长度,填入Content-Type和Content-Length: 
    hc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); 
    hc.setRequestProperty("Content-Length", String.valueOf(data.length)); 
      然后打开OutputStream将正文写入: 
    OutputStream output = hc.openOutputStream(); 
    output.write(data); 
      需要注意的是,数据仍需要以URL编码格式编码,由于MIDP库中没有J2SE中与之对应的URLEncoder类,因此,需要自己动手编写这个encode()方法,可以参考java.net.URLEncoder.java的源码。剩下的便是读取服务器响应,代码与GET一致,这里就不再详述。 
      使用multipart/form-data发送文件 
      如果要在MIDP客户端向服务器上传文件,我们就必须模拟一个POST  
    multipart/form-data类型的请求,Content-Type必须是multipart/form-data。 
      以multipart/form-data编码的POST请求格式与application/x-www-form-urlencoded完全不同,multipart/form-data需要首先在HTTP请求头设置一个分隔符,例如ABCD: 
    hc.setRequestProperty("Content-Type", "multipart/form-data; boundary=ABCD"); 
      然后,将每个字段用“--分隔符”分隔,最后一个“--分隔符--”表示结束。例如,要上传一个title字段"Today"和一个文件C:\1.txt,HTTP正文如下: 
    --ABCD 
    Content-Disposition: form-data; name="title" 
    \r\n 
    Today 
    --ABCD 
    Content-Disposition: form-data; name="1.txt"; filename="C:\1.txt" 
    Content-Type: text/plain 
    \r\n 
    <这里是1.txt文件的内容> 
    --ABCD-- 
    \r\n 
      请注意,每一行都必须以\r\n结束,包括最后一行。如果用Sniffer程序检测IE发送的POST请求,可以发现IE的分隔符类似于---------------------------7d4a6d158c9,这是IE产生的一个随机数,目的是防止上传文件中出现分隔符导致服务器无法正确识别文件起始位置。我们可以写一个固定的分隔符,只要足够复杂即可。 
      发送文件的POST代码如下: 
    String[] props = ... // 字段名 
    String[] values = ... // 字段值 
    byte[] file = ... // 文件内容 
    String BOUNDARY = "---------------------------7d4a6d158c9"; // 分隔符 
    StringBuffer sb = new StringBuffer(); 
    // 发送每个字段: 
    for(int i=0; i   sb = sb.append("--"); 
      sb = sb.append(BOUNDARY); 
      sb = sb.append("\r\n"); 
      sb = sb.append("Content-Disposition: form-data; name=\""+ props
    +  
    "\"\r\n\r\n"); 
      sb = sb.append(URLEncoder.encode(values
    )); 
      sb = sb.append("\r\n"); 

    // 发送文件: 
    sb = sb.append("--"); 
    sb = sb.append(BOUNDARY); 
    sb = sb.append("\r\n"); 
    sb = sb.append("Content-Disposition: form-data; name=\"1\";  
    filename=\"1.txt\"\r\n"); 
    sb = sb.append("Content-Type: application/octet-stream\r\n\r\n"); 
    byte[] data = sb.toString().getBytes(); 
    byte[] end_data = ("\r\n--" + BOUNDARY + "--\r\n").getBytes(); 
    // 设置HTTP头: 
    hc.setRequestProperty("Content-Type", MULTIPART_FORM_DATA + "; boundary=" +  
    BOUNDARY); 
    hc.setRequestProperty("Content-Length", String.valueOf(data.length + file.length  
    + end_data.length)); 
    // 输出: 
    output = hc.openOutputStream(); 
    output.write(data); 
    output.write(file); 
    output.write(end_data); 
    // 读取服务器响应: 
    // TODO... 
    (4). 
      使用Cookie保持Session 
      通常服务器使用Session来跟踪会话。Session的简单实现就是利用Cookie。当客户端第一次连接服务器时,服务器检测到客户端没有相应的Cookie字段,就发送一个包含一个识别码的Set-Cookie字段。在此后的会话过程中,客户端发送的请求都包含这个Cookie,因此服务器能够识别出客户端曾经连接过服务器。 
      要实现与浏览器一样的效果,MIDP应用程序必须也能识别Cookie,并在每个请求头中包含此Cookie。 
      在处理每次连接的响应中,我们都检查是否有Set-Cookie这个头,如果有,则是服务器第一次发送的Session  
    ID,或者服务器认为会话超时,需要重新生成一个Session ID。如果检测到Set-Cookie头,就将其保存,并在随后的每次请求中附加它: 
    String session = null; 
    String cookie = hc.getHeaderField("Set-Cookie"); 
    if(cookie!=null) { 
      int n = cookie.indexOf(';'); 
      session = cookie.substring(0, n); 

      使用Sniffer程序可以捕获到不同的Web服务器发送的Session。WebLogic Server 7.0返回的Session如下: 
    Set-Cookie:  
    JSESSIONID=CXP4FMwOJB06XCByBWfwZBQ0IfkroKO2W7FZpkLbmWsnERuN5u2L!-1200402410;  
    path=/ 
      而Resin 2.1返回的Session则是: 
    Set-Cookie: JSESSIONID= aTMCmwe9F5j9; path=/ 
      运行Asp.Net的IIS返回的Session: 
    Set-Cookie: ASPSESSIONIDQATSASQB=GNGEEJIDMDFCMOOFLEAKDGGP; path=/ 
      我们无须关心Session ID的内容,服务器自己会识别它。我们只需在随后的请求中附加上这个Session ID即可: 
    if(session!=null) 
      hc.setRequestProperty("Cookie", session); 
      对于URL重写来保持Session的方法,在PC客户端可能很有用,但是,由于MIDP程序很难分析出URL中有用的Session信息,因此,不推荐使用这种方法。

    在MIDP2.0中调用平台服务

    在MIDP2.0中提供了非常重要的一项功能:调用外部平台的服务,比如从网络开始下载和安装一个MIDlet套件、发起话音呼叫。如果你做了一个通信录,那么就可以直接调用电话服务来呼叫一个号码。在MIDP2.0中可以通过MIDlet.platformRequest()方法实现上面的功能。

        参考一下Javax.microedition.midlet.MIDlet.platformRequest()的java doc,你可以发现他只有一个参数为String类型的URL,当你调用他的时候,他会把这个URL传递给应用管理软件。应用管理软件来判断这个参数是否合理,是否存在相关的服务可以使用。如果服务可用并且需要MIDlet首先退出才能执行调用的服务的话,那么方法会返回一个boolean类型的true。如果服务并不存在的话会抛出ConnectionNotFoundExcepton。

        在MIDP2.0的规范中,定义了两种服务类型:
    1:如果URL的指向是一个jar文件或者jad文件,例如hello.jad,那么平台会启动一个正常的安装进程来安装。
    2:如果URL是以tel:开始的,例如tel:01062289873,那么参数会传递给电话服务程序去发起一个话音呼叫,被呼叫方就是后面的电话号码。
    设备制造商可以自由的实现其他的平台服务,例如调用web浏览器浏览网页等等。我们必须清楚这个方法并不是堵塞的方法。

        在WTK2.1中提供了对这个方法的支持,你只需要配置一下就可以了。假设你的WTK的安装目录为WTK_HOME,那么进入WTK_HOME/lib,编辑system.config文件,在里面加上一句:com.sun.midp.midlet.platformRequestCommand: "C:\Program Files\MYIE2\MyIE.exe"。注意一定要
    把这句话写在一行内,不要换行。这样当我们把类似这样的URL,http://www.j2medev.com传给platformRequest()的时候,系统就会启动MyIE去打开www.j2medev.com的主页。由于我的手机不能支持MIDP2.0所以不能测试电话呼叫,MIDlet下载等平台服务,如果你的可以的话可以编写代码测试一下。下面是我编写的代码在模拟器中进行了测试,当用户按下invoke按钮的时候,MyIE正常打开http://www.j2medev.com。

    package com.j2medev.mingjava;

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


    public class PlatformTest extends MIDlet implements CommandListener
    {
        private Display display;
        private Form mainForm;
        public static final Command getCommand = new Command("Invoke",Command.ITEM,1);
        public static final String URL = "http://www.j2medev.com";
        
        protected void startApp() throws MIDletStateChangeException
        {
            
            display = Display.getDisplay(this);
            mainForm = new Form("Platform Test");
            mainForm.append("Click the button \"invoke\"");
            mainForm.addCommand(getCommand);
            mainForm.setCommandListener(this);
            display.setCurrent(mainForm);

        }


        protected void pauseApp()
        {
           

        }

     
        protected void destroyApp(boolean arg0) throws MIDletStateChangeException
        {
        }
       
        public void commandAction(Command cmd,Displayable disp)
        {
            if(cmd == getCommand)
            {
                try
                {
                    boolean flag = platformRequest(URL);
                    System.out.println(flag);
           
                }
                catch(ConnectionNotFoundException e)
                {
                    e.printStackTrace();
                }
            }
        }


    }

    使用platformRequest()自动更新MIDlet套件

     

    MIDP 2.0提供了一系列的新特性,其中一个就是调用平台的应用。例如可以调用WAP浏览器访问特定的网址,也可以调用电话应用程序呼叫某个号码。上述两个功能都可以使用MIDlet类的platformRequest()方法实现,platformRequest()接受一个String类型的参数url,如果是呼叫电话号码则url的形式为tel:13810011001。如果是调用WAP浏览器则url的形式类似于http://www.j2medev.com/wap.wml

    本文介绍如何使用platformRequest()方法自动更新MIDlet套件,在MIDP的文档中说明。如果url的形式为一个指定的MIDlet套件,可以是JAD文件,也可以是jar文件。例如http://www.j2medev.com/helloworld.jad。这个时候,此请求会被认为是安装MIDlet套件来对待,这样用户可以控制安装的过程,就像我们直接从WAP浏览器输入地址安装MIDlet套件一样。如果请求的MIDlet套件是当前正在运行的应用程序的升级版,则当前的程序需要先退出,然后执行更新操作。

    如果想让发布的MIDlet套件具备自动升级的功能,那么需要提供一个服务器端程序,服务器端程序能够检测是不是有更新版本的应用程序下载,并且可以将这个结果告诉给客户端。通常这样的服务器端可以实现为Web应用程序,用servlet和MIDlet通信。当用户运行MIDlet的时候,MIDlet首先连接指定的服务器获取信息,如果没有新版软件则正常运行,如果有新版本的软件则是用platformRequest()方法请求安装应用程序。

                         if (update == NEED_UPDATE) {

                                try {

                                       platformRequest("http://www.j2medev.com/wap/autoupdate.jar");

                                       destroyApp(true);

                                       notifyDestroyed();

                                } catch (ConnectionNotFoundException ex) {

                                       ex.printStackTrace();

                                }

                               

                         }else{

                                //do something else

                         }

    这里我们在代码中执行destroyApp(true),让MIDlet套件主动退出。如果想做的更为专业,可以在检测到有新版本的软件的时候,显示一个对话框让用户选择是否自动更新,根据用户的选择执行相关的操作。下面的代码UpdateMIDlet可以用于演示此项功能。

    /**

     * author mingJava

     * Created on 2006-3-8

     */

    package com.j2medev.autoupdate;

     

    import java.util.Random;

    import javax.microedition.io.ConnectionNotFoundException;

    import javax.microedition.lcdui.Display;

    import javax.microedition.lcdui.Form;

    import javax.microedition.midlet.MIDlet;

    import javax.microedition.midlet.MIDletStateChangeException;

     

    public class UpdateMIDlet extends MIDlet {

     

           public static int NEED_UPDATE = 0;

     

           public static int NO_UPDATE = 1;

     

           private Display display = null;

     

           protected void startApp() throws MIDletStateChangeException {

                  if (display == null) {

                         display = Display.getDisplay(this);

                         int update = (new Random().nextInt() >>> 1) % 2;

                         if (update == NEED_UPDATE) {

                                try {

                                       platformRequest("http://www.j2medev.com/wap/autoupdate.jar");

                                       destroyApp(true);

                                       notifyDestroyed();

                                } catch (ConnectionNotFoundException ex) {

                                       ex.printStackTrace();

                                }

                               

                         }else if(update == NO_UPDATE){

                                Form form = new Form("Test");

                                form.append("No update is needed");

                                display.setCurrent(form);

                         }

                  }

           }

     

           protected void pauseApp() {

     

           }

     

           protected void destroyApp(boolean arg0) throws MIDletStateChangeException {

     

           }

     

    }

           为了模拟是否有升级软件的可能,我们在startApp()中随机生成一个随机数update。如果update等于0则代表有升级版本,如果update等于1则代表不需要升级。为了简单起见,这里我们没有编写联网检测升级版本的程序。此程序在Nokia 7610上测试通过,可以自动升级MIDlet套件。

    如何配置wtk才能使用platformRequest

    这个要手机支持,自己应该无法配置.  
       
      在模拟器下如果要访问网页,需要修改lib目录下的system.config文件,添加  
          com.sun.midp.midlet.platformRequestCommand:"D:\Program   Files\Maxthon\Maxthon.exe""  
      这个D:\Program   Files\Maxthon\Maxthon.exe是浏览器所在目录.

    在J2ME和WAP中实现电话呼叫功能

    本文介绍如何在J2ME平台和WAP页面实现电话呼叫的功能。关于WAP和J2ME的区别并不在讨论范畴,读者可以自行查阅资料。

           MIDP 1.0中没有提供电话呼叫的功能。在MIDP 2.0中,javax.microedition.midlet.MIDlet类提供了platformRequest()方法来请求设备来完成特定的请求,请求的内容由platformRequest()方法的String类型的参数url来标识。可能是调用浏览器打开某个特定的wap页面,也可以是呼叫电话。在设备资源可用的时候,系统会把MIDlet放在后台执行,而把特定的应用程序,例如电话程序或者浏览器放在前台来执行。呼叫电话的时候可以使用如下的形式:
    JAVA手机网[www.cnjm.net]

               try{
                   this.platformRequest("tel:13810000000");
               }catch(ConnectionNotFoundException ex){
                   ex.printStackTrace();
               }

    JAVA手机网[www.cnjm.net]
    这里提供了一个简单的例子,在Nokia 7610(支持MIDP 2.0)上测试通过,成功呼叫了指定的电话。

    /*
    * TeleMIDlet.java
    *
    * Created on 2005年12月15日, 下午9:56
    */

    package com.j2medev.call;

    import javax.microedition.io.ConnectionNotFoundException;
    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    JAVA手机网[www.cnjm.net]

    /**
    *
    * @author  Administrator
    * @version
    */
    public class TeleMIDlet extends MIDlet implements CommandListener {
       
       private Display display = null;
       private List main = null;
       
       public void startApp() {
           if(display == null){
               display = Display.getDisplay(this);
               main = new List("测试电话功能", List.IMPLICIT);
               main.append("13810000000", null);//这里的电话号码是虚拟的
    JAVA手机网[www.cnjm.net]
               main.append("13810000001", null);//这里的电话号码是虚拟的
               main.setCommandListener(this);
           }
           display.setCurrent(main);
       }
       
       public void pauseApp() {
       }
       
       public void destroyApp(boolean unconditional) {
       }
       
       public void commandAction(Command cmd,Displayable displayable){
           if(cmd == List.SELECT_COMMAND){
    JAVA手机网[www.cnjm.net]
               String number = main.getString(main.getSelectedIndex());
               try{
                   this.platformRequest("tel:"+number);
               }catch(ConnectionNotFoundException ex){
    JAVA手机网[www.cnjm.net]
                   ex.printStackTrace();
               }
           }
       }
    }

           相比J2ME技术,WAP更类似于Web,是一种服务器端为主的技术。在WML中可以调用设备的WTAI函数来呼叫特定的电话号码,代码如下所示:

    JAVA手机网[www.cnjm.net]
    <input name="phone_no" format="*m" value="13"/>
    <do type="option" label="呼出号">
    <go href="wtai://wp/mc;$(phone_no)"/>
    </do><br/>
    或者直接写入电话号码的方式:
    <a href="wtai://wp/mc;1331597312*">拨打电话</a>


    在MIDP 2.0和WAP平台中都对电话呼叫提供了支持,本文进行了简单的总结,希望对您的实际项目有所帮助。
    来自:http://www.cnjm.net/tech/article1106.html

    模拟器实现触摸屏

    修改C:\WTK25\wtklib\devices\DefaultColorPhone\DefaultColorPhone.properties
    下的touch_screen=true

    How to create thumbnail in Java ME

    Create a thumbnail image from a large image or scale an image in Java ME


    private Image createThumbnail(Image image) {
      int sourceWidth = image.getWidth();
      int sourceHeight = image.getHeight();
      
      int thumbWidth = 64;
      int thumbHeight = -1;
      
      if (thumbHeight == -1)
        thumbHeight = thumbWidth * sourceHeight / sourceWidth;
      
      Image thumb = Image.createImage(thumbWidth, thumbHeight);
      Graphics g = thumb.getGraphics();
      
      for (int y = 0; y < thumbHeight; y++) {
        for (int x = 0; x < thumbWidth; x++) {
          g.setClip(x, y, 1, 1);
          int dx = x * sourceWidth / thumbWidth;
          int dy = y * sourceHeight / thumbHeight;
          g.drawImage(image, x - dx, y - dy,
              Graphics.LEFT | Graphics.TOP);
        }
      }
      
      Image immutableThumb = Image.createImage(thumb);
      
      return immutableThumb;
    }

    You may also use http://www.java-tips.org/java-me-tips/midp/how-to-implement-oom-in-and-oom-out.html

    If you are using S60 devices, you can load the thumbnails generated automatically by the Gallery application. They are located in the "_PAlbTN" sub-folders of Image.

    For more details on this, check Thumbnail_path_for_3rd_edition_devices.

    如何在Java ME平台上获取手机串号

    注意:在Java ME平台上获取手机串号,在某些手机中是可行的,但是某些手机中就是无法获取的。对于即便是可以获取串号的手机来说,也仅仅限于进行了签名的应用程序才可以获得手机串号。

    在诺基亚手机中,我们可以这样获取手机串号: System.getProperty("com.nokia.mid.imei")

    在Series60平台的手几种,这样的获取串号操作需要你的应用程序被认证为网络运营商信任的应用程序或者手机生产商信任的应用程序,并且只有在Series40第三版功能包1之后的手机设备中才能够使用。 On Series 40 phones this requires that your midlet is signed to either operator or manufacturer domain, and this is only available Series 40 3rd Edition FP1 devices and newer.

    在S60平台,有些S60第三版的手机已经实现了这样的功能,如E60,E61,E62,但是不包括3250和5500。对于S60平台,获取串号并不需要签名认证。

    在西门子手机中,我们可以这样获取传号: System.getProperty("com.siemens.IMEI")

    其他手机:

        三星: System.getProperty("com.samsung.imei");
        索尼爱立信: System.getProperty("com.sonyericsson.imei");
        摩托罗拉: System.getProperty("IMEI" );
        诺基亚: System.getProperty("phone.imei");
               System.getProperty("com.nokia.IMEI");
    
     
     

    j2me调用系统程序方法:platformRequest

    try {
                javax.microedition.midlet.MIDlet.platformRequest("http://xxx.xxx");
            } catch (ConnectionNotFoundException ex) {
            }
    http就是调用系统的浏览器浏览了,
    tel:就是调用系统打电话了。

    MIDlet.platformRequest方法可以支持很多的应用调用(如tel、fax、http访问无线网络等)或者应用的安装,但是具体的使用情况要看具体的手机提供商是否在手机上提供此类的功能了。

    详细的解释:

    一、调用过程:

    调用platformRequest方法后,用指定的URL表示的请求被发给了设备:

    情况1:
    当手机平台上相应的能力并且具有有效资源的话,手机设备“可以”将用于处理请求的相应应用调入到前台,并使用户和它进行交互,同时保持当前的MIDlet suite在后台运行。

    情况2:
    如果设备平台没有相应的能力或者没有有效资源,则它“可能”等待处理这个URL请求直到发出该请求的MIDlet suite退出。在这种情况下,当发出要求的MIDlet suite退出时,设备平台“必须”将后台的应用(如果存在的话)切换到前台,以恢复其与用户的交互。

    二、方法调用的影响:

    platformRequest是一种非阻塞的方法,所以该方法不能够将多个请求进行排队。在请求被处理前MIDlet suite必须退出的平台只能处理最后的一个请求。而在MIDlet suite和请求能够同时处理的平台,每个发自MIDlet suite的请求必须被实时的传送给平台软件来处理。

    如果URL所描述的请求引用了一个MIDlet suite(无论是一个应用程序描述还是一个jar文件),处理这个请求的应用将解释URL,将其作为安装某个package的请求。在这种情况下,平台将进行一般MIDlet suite安装,用户也将控制这个安装过程(包括取消安装、下载、确认安装)。如果被安装的MIDlet suite是当前运行的MIDlet suite的更新版本,则在执行升级之前,平台将停止当前运行的低版本MIDlet suite。有一些平台,在安装进行之时当前运行的MIDlet suite可能需要停止。

    如果URL描述的内容是一种tel:<number>的方式,这种方式在RFC2806中有所描述。平台必须解释这个URL将它作为初始化语音呼叫的请求。如果平台提供“电话”程序,则这个请求必须被传送到“电话”应用程序去处理。如果平台提供此功能,则必须具备能够建立本地和全局电话呼叫的能力,并执行DTMF(电话管理功能)传送拨号。但,并不是所有的RFC2806定义的URL格式都被实现,尤其是区号或者电话终端需要的内容。isdn子网,服务提供商和扩展特性可能被忽略掉。

    而且设备可以选择性的支持不在上面列表中的URL的附加定义。

    platformRequest方法可能会对用户的经济造成影响,比如使用无线网络、或者语音呼叫。所以在程序执行之前,平台必须获得用户的允许。这个获得用户允许的实现方式是比较自由的。例如在一些平台上每次询问用户时出现一个对话框。而另外一些平台可能执行相应的应用程序,弹出URL或者电话号码的输入字框并保持静止等待状态,直到用户点击装载或者拨号按钮后才执行。

    January 29

    SD和MMC记忆卡介面技术

    一般而言,MCI包含嗣钤荽嫫鳌⒒赜υ荽嫫鳌⒆柿显荽嫫鳌⒂馐保╰imeout)计数器和侦错逻辑,能够自动传送命令,必要时,还能接收相关的回应和资料。它使用极少的处理器资源。MCI支援串流、区块和多区块资料的读写,可以透过DMA通道做快速的传输,减少对处理器的依赖,和降低对缓冲记忆体的需求。

    典型的MCI的工作速率是主时脉的一半,可以支援的最大时槽(slot)是16个(视不同厂牌而定),而每一个时槽可以用来连接一个MMC匯流排(最多共可连接30片MMC记忆卡)或每一个时槽可以用来连接一片SD记忆卡。在同一个时间内,只能选择一个时槽,因为时槽是多工传输的。在命令暂存器里有一个位元可以用来选择时槽。

    SD记忆卡介面是使用9个脚位,这包含:时脉、命令、4条资料线、3条电源线。MMC记忆卡介面是使用7个脚位,这包含:时脉、命令、1条资料线、3条电力线。有趣的是,有些MCI的SD记忆卡介面也支援MMC读写作业,这就变成SD记忆卡和MMC记忆卡可以共用相同的插槽。SD和MMC记忆卡的最主要差别在于:初始化流程和匯流排佈线架构。

    附图一是MCI的位置图示。附图二是MCI的应用图示。附图一中的脚位名称之意义如下:
    .MCCDA/MCCDB:MMC或SD记忆卡的命令或回应。
    .MCCK:MMC或SD记忆卡的时脉。
    .MCDA0 ~ MCDA3:MMC的位元资料(DAT[0]),SD的位元资料(DAT[0 ~3])。MMC只有1条资料线(data line),SD共有4条资料线,所以,它们的资料匯流排宽度(data bus width)不同。
    .MCDB0 ~ MCDB3:MMC的位元资料(DAT[0]),SD的位元资料(DAT[0 ~3])。

    附图一中与MCI相关的邻近区块之功能叙述如下:
    .可程式I/O(PIO):上述的SD/MMC接脚具有多工传输的功能,这是透过PIO线路达成的。韧体工程师必须先撰写PIO控制器的程式,赋予MCI接脚周边传输的功能。
    .功率管理控制器(PMC):MCI可以参考PMC决定其时脉。当MCI没有被使用时,可以降低MCI的时脉速率,以节省功率。
    .中断:一般的MCI介面都具有一条中断线路,它是连接至中断控制器。因此,在设定MCI中断前,必须先撰写中断控制器的程式。

    附表一是MMC匯流排介面的脚位说明;附表二是SD匯流排介面的脚位说明。附图三是MMC匯流排的连接方式;附图四是SD匯流排的连接方式。附图五是MMC和SD一起被使用时的连接方式。请注意,MMC或SD匯流排的电源必须另外供给,但未在附图三或四中呈现出来。
     



    图一:MCI的位置图
     

    当MCI被设定成SD作业模式时,资料匯流排的宽度可以藉由暂存器(譬如:MCI_SDCR)来选择。清除此暂存器的SDCBUS位元,表示匯流排的宽度是1位元;若将此位元值设为1,则表示宽度是4位元。在MMC作业模式下,只有资料线0有被使用,其它资料线可以当成独立的PIO脚位(与SD或MMC无关)。

    MMC传输作业
    开机重置之后,MMC的初始化是透过一种特殊的匯流排通信协定来完成。此通信协定的信息格式具有下列几种类型:
    .命令:表示开始执行一个作业。它是从主机发出,至单一的MMC记忆卡(单一位址的命令),或至全部有连接的MMC记忆卡(广播命令)。此命令信息是在CMD线上以串列方式传输。
    .回应:它的传送方向和命令信息相反,它是从一个特定位址的MMC记忆卡,或全部的MMC记忆卡同步地传送给主机,是对之前接收到的命令信息之回应。
    .资料:此信息是从主机或MMC记忆卡发出。

    MMC记忆卡的位址是由匯流排控制器在初始化时设定的。它们的唯一的CID号码代表它们自己。MMC有两种资料传输命令,如下所示:
    .序列式命令:这个命令会启动连续性的资料串流。唯有收到停止命令时,传输作业才会结束。这个模式可以减少传送额外的命令。
    .区块式命令:此命令在传送一个资料区块之后,会传送CRC位元。它的读写作业支援单一区块或多区块的传输。和序列式命令类似,在收到停止命令时,多区块传输作业才会结束。
     



    图二:MCI的应用图示
     



    表一:MMC匯流排介面的脚位说明
     



    表二:SD匯流排介面的脚位说明
     



    图三:MMC匯流排的连接方式
     



    图四:SD匯流排的连接方式
     



    图五:MMC和SD一起被使用时的连接方式
     
    MMC的「命令—回应」作业
    重置之后,MCI是关闭的,透过MCI控制暂存器的位元设定,可以将MCI开启。假设MCI_CMDR是控制命令讯息的暂存器,它被设定的各栏位值如附表三所示;ALL_SEND_CID是一个命令名称,它要求所有的MMC记忆卡送出它们自己的CID号码,如附表四的说明。在本范例中,ALL_SEND_CID的引数(argument)栏位之内容是储存在另一个暂存器(MCI_ARGR)中。要传送ALL_SEND_CID命令,韧体工程师必须执行下列步骤:
    .将ALL_SEND_CID的引数值填入MCI_ARGR暂存器中。
    .设定命令暂存器(MCI_CMDR)。

    命令暂存器被写入值之后,ALL_SEND_CID命令就会立即被送出。当传送完成之后,状态暂存器(MCI_SR)的状态位元会生效(assert)。ALL_SEND_CID需要回应,这可以透过读取MCI回应暂存器里的值来达成。根据命令的需要,回应信息的大小可以在48位元至136位元之间。MCI会在这些信息中加入侦错码,以防止在传输中发生资料毁损。
     



    表三:MCI_CMDR暂存器
     



    表四:ALL_SEND_CID命令
     
    MMC的资料作业
    MMC的资料读写作业可以包含:单一区块、多区块、串流的传输。这些作业可以透过DMA来加速传输,这是藉由设定模式暂存器(mode register)的位元值来做切换。区块的长度也必须在模式暂存器中设定。

    下列所述是读取单一区块,使用或不使用DMA时的作业流程。读取作业结束时,可以採用轮询(polling)或中断的方式来处理,本范例中是使用轮询的方法。这些都是属于韧体工程师的程式设计工作。

    .送出SEL_DESEL_CARD命令,来选择MMC记忆卡。
    .送出SET_BLOCKLEN命令。
    .如果是透过DMA来读取,则按照附图六右侧的流程,否则按照附图六左侧的流程。

    MMC的写入作业使用模式暂存器来定义在写入非多区块大小时的填充值(padding value),这是藉由写入模式暂存器的一个位元来决定。例如:对该位元写入0,代表填充值是0x00;若写入1,则代表填充值是0xFF。MMC写入单一区块作业流程的前面两个步骤与上述的MMC读取作业流程相同,如果是透过DMA来读取,则按照附图七右侧的流程,否则按照附图七左侧的流程。
     



    图六:MMC的资料读取作业流程
     



    图七:MMC的资料写入作业流程
     
    SD传输作业
    SD记忆卡包含了版权保护功能,它遵守SDMI标准,可以避免资料被盗拷。由于它的传输速率比较快,所以适用于需要记忆容量较大的应用。除了少数的额外功能以外,SD在尺寸大小、脚位数目和功能、传输通信协定方面,大致上是和MMC相容的。因此,SD的读写作业程序也和上述的MMC读写作业程序类似。

    透过SD控制暂存器(MCI_SDCR),可以选择插槽位置和资料匯流排的宽度。SD匯流排的资料线路数目也可以被动态地设定,开机后的预设值是DAT0;初始化时,韧体工程师可以改变这个值,以符合实体的资料线路之数目。SD控制暂存器如附图八所示。
     



    图八:SD控制暂存器
     
    MMC驱动程式
    对任何一种记忆卡应用而言,它必须具备下列的基本功能:
    .热插拔。
    .当记忆卡插入或移除时,中断服务常式(ISR)必须能够立即发现和反应。
    .当记忆卡插入时,ISR必须能将新加入的记忆卡位址或名称在应用程式中显示出来。移除时,此记忆卡位址或名称也能从应用程式中消失。
    .当记忆卡插入时,记忆卡必须像硬碟一样,被「挂(mount)」在硬碟的根目录底下。同理,当记忆卡被移除时,它就会被「卸下(unmount)」。
    .如果记忆卡内部具有档案系统,它虽然可以被虚拟成硬碟----亦即「区块装置(block device)」,但是当系统发出移除记忆卡的请求时,发现记忆卡早已不存在了,此时,「区块层」程式会不知所措,认为是错误。所以,在系统发出移除记忆卡的请求之前,区块层必须能先侦测出记忆卡是否有存在。而且如果它不存在,就立即终止这项请求。因此,在设计记忆卡的最底层驱动程式时,必须提供一个函式(function),而且它必须指向上层的「区块层」。

    在应用层,若使用Linux 2.6的hotplug「剧本(script)」程式和mmc.agent来设定与控制记忆卡。当移除MMC记忆卡时,mmc_block.c的mmc_blk_issue_rq( )会发出移除命令,如果此时MMC记忆卡有存在,则可以顺利移除,并且执行end_that_request_chunk( )来结束此I/O请求。不过,如果MMC记忆卡不存在,则end_that_request_chunk( )会发出类似下列的错误信息,并停止作业。

    mmcblk0: error 1 sending read/write command
    end_request: I/O error, dev mmcblk0, sector 2
    Buffer I/O error on device mmcblk0, logical block 1
    lost page write due to I/O error on mmcblk0

    如果在移除MMC记忆卡之前,没有正确地先将它的档案系统「卸下」,则MMC记忆卡的内容可能就无法确保。机械式的插拔通常是突然发生的,但从应用层的「剧本」程式至区块层驱动程式(mmc_block.c),需要一点反应时间,如果这时间拖得太久,就会发生上述的错误。

    在Linux 2.6的MMC区块层的mmc_blk_issue_rq( )程式中,有下列一段处理错误的程式码和註解:
    mmc_card_release_host(card);
    /*
    * 这有一点严峻,但是直到我们还能想出更好的处理错误的方法之
    * 前,这已经是我们能够尽力达到的了----尤其是在错误发生之前,有* 些主机无法知道有多少资料已经被传送了。
    */
    spin_lock_irq(&md->lock);
    ………………………………………………………

    这说明了Linux 2.6的MMC区块层驱动程式仍有瑕疵存在,使用时必须注意。理论上,「卸下」MMC记忆卡的档案系统的速度应该要比机械式的插拔速度要快很多才对----这是热插拔装置的理想目标,但是因为各种硬件和作业系统的设计不尽相同,就会发生这样的问题。解决之道,就是要尽量缩短区块层驱动程式的反应时间,并且要随时掌握MMC记忆卡的状态;在移除的瞬间,区块层能知道要「卸下」的档案系统是哪一个,而且先对MMC记忆卡做出回应,以确保MMC记忆卡里的资料之完整性,之后,再去执行最耗时的「卸下」动作。当然,上述的情况都是指MMC记忆卡不处于读写的传输状态中,否则任何系统都无法确保记忆卡里面的资料,即使是硬碟、软碟、USB………等其它I/O技术在此「恶劣」的情况下,也是无能为力的。

    SD驱动程式
    目前Linux 2.6核心尚未支援SD驱动程式。不过,可以试着将Linux 2.6的MMC驱动程式功能延伸,使它也能支援SD记忆卡,其要领大致如下:
    .自动侦测所连接的是SD或MMC记忆卡。因为最多只能连接一片SD记忆卡,所以若连接SD记忆卡时,必须提醒主机进入SD模式。
    .读取SD或MMC暂存器之资料,并读取SD记忆卡的卡片特定资料。
    .支援4-bit模式,这不只可以支援SD传输作业,还能支援MMC传输作业。
    .如果SD记忆卡是唯读装置,可以另外设计一个中断唿叫函式来处理。

    在ftp://ftp.arm.linux.org.uk/pub/armlinux/kernel/v2.6/网站中,可以免费下载支援ARM处理器的MMC/SD驱动程式,这是www.arm.linux.org.uk修改(patch)Linux 2.6核心程式后的结果。简略说明其修改的位置和新增的功能如下:
    mmc.c : 侦测SD记忆卡、将读取到的SD暂存器资料註册。
    mmc_block.c : 检查是否是唯读的记忆卡(不是SD规格)。
    mmc_sysfs.c : 公开SD控制(设定)暂存器的位置
    mmc.h : 增加新的定义。

    下面的起始档是新增的:
    card.h : 新增一些旗标(flag)来代表卡片的类型、唯读属性和新的暂存器。
    host.h : 新增一些旗标来代表匯流排宽度、唯读测试和模式(SD/MMC)。
    protocol.h : 须新增的SD命令。

    这个SD/MMC驱动程式的特点是:1.支援4-bit的资料宽度;2.支援唯读的记忆卡(可以读取唯读开关的状态)。Linux 2.6的MMC驱动程式只支援1-bit的资料宽度和读写的功能。不过,这个驱动程式仍然有一些错误(bug),若传输很小的资料(小于16 bytes)时,可能会失效。此外,请注意,MMC的功能不能从此驱动程式中去除,因为系统在侦测SD之前,会先侦测MMC。

    SDIO简介
    SDIO卡能够延伸一个装置的功能。目前有许多种SDIO卡被开发出来,例如:数字相机、蓝芽、GPS、WLAN都有它们各自的SDIO卡。SDIO 1.0标准定义了两种类型的SDIO卡:1.全速的SDIO卡,传输率可以超过100 Mbps;2.低速的SDIO卡,支援的时脉速率在0至400 KHz之间。SDIO卡只需要SPI和1-bit资料宽度的SD传输模式,4-bit模式是一个选项。低速的SDIO卡可以用最少的硬体支援低速的I/O装置,这些装置包含:数据机、条码扫描机、GPS接收机……等。如果这种记忆卡是一种「组合式(combo)」的卡片(记忆体加SDIO),就必须使用全速的模式和4-bit的传输模式,这是SDIO 1.0标准规定的。附图九是两个4-bit模式的SDIO卡的线路连接图。
     



    图九:两个4-bit模式的SDIO卡的线路连接方式
     

    SDIO的信号传输模式有SPI、1-bit、4-bit三种。在SPI模式中,第8脚位被当成中断信号。其它脚位的功能和通信协定与SD记忆卡的标准规范一样。附表五是SDIO的每个脚位在不同信号模式下的定义。
     



    表五:SDIO的脚位定义
     
    SDIO内部的记忆体映射
    SDIO记忆卡内部具有固定的记忆体映射,这包含暂存器空间或称为「一般资讯区域(common information area;CIA)」,以及特殊功能区域(function unique area)。CIA包含了与SDIO记忆卡有关的资讯,以及一些必要的(mandatory)和可选择的(optional)暂存器,它们都位于固定的位址上。藉此,SDIO的主机(譬如:可携式装置)能够得到SDIO记忆卡的有关资讯,并执行一般性的作业。特殊功能区域储存了许多种不同的功能,这是由供应商定义的,因此,不同厂牌的SDIO记忆卡可能会有不同的功能。附图十是具有许多种不同功能的SDIO记忆卡内部的固定记忆体映射空间。其中,RFU是「保留给未来使用(Reserved for Future Use)」的意思。CIA所包含的暂存器可以开启或关闭I/O作业、处理硬件中断、载入韧体(这是选项)。这些暂存器也提供与SDIO记忆卡功能相关的资讯和要求。CIA支援下列3种暂存器:
    .一般控制暂存器(Card Common Control Register;CCCR):能快速检查SDIO主机,并依照不同的SDIO记忆卡之功能控制它们的启动和中断能力。即使在开机后,SDIO记忆卡的I/O功能尚未被启动,但是CCCR是可以被存取的,这使得SDIO主机于系统初始化后,可以立即启动SDIO记忆卡的I/O功能。
    .基本功能暂存器(Function Basic Register;FBR):每一个I/O功能具有256 bytes的记忆体空间,这使得SDIO主机能够快速地判定每一个I/O功能的能力和要求,并启动韧体下载功能。这个空间位址是从0x00n00至0x00nFF,n是功能编号(从0x1至0x7)。
    .记忆卡资讯结构(Card Information Structure;CIS):CIS提供更完整的记忆卡功能的相关资讯。这是仿照PCMCIA标准所制定的规格。SDIO记忆卡的每一个功能都各有一个CIS区域,以及一个共用的CIS区域;共用的CIS区域储存了所有功能的共同特性,每一个功能的CIS区域则储存了该功能所具备的特性。CCCR和FBR各具有一个指标指向相对应的CIS位址。
     



    图十:SDIO记忆卡内部的固定记忆体映射空间
     

    此外,由于SDIO记忆卡的每一个功能可能需要包含额外的记忆体空间,用来储存驱动程式或应用程式。而且,因为SDIO记忆卡可能必须支援不同的平台,所以每一个驱动程式或应用程式可能会有许多种版本。解决的方法有两种:一种是使用SD的标准规范(如附图十一),来设计「组合卡」;另一种是使用嵌入式的「程式码储存区域(Code Storage Area;CSA)」。
     



    图十一:SD的记忆体映射空间。SMC是「静态记忆体控制器(Static Memory Controller)」、BFC是「暴量传输的(burst)FLASH控制器(Burst Flash Controller)
    January 26

    在MIDP2.0中操作图片像素 转载

      我们知道,在MIDP1.0中,除非我们利用特定厂商的API(比如Nokia),我们是没法对图片的像素进行操作的,但是在MIDP2.0中,Image和Graphics的功能都大大增强了。比如,我们可以获取Image的所有像素值,然后利用程序来修改这些像素(比如说ARGB各自的值),最后再把修改后的像素图绘制出来。通过直接操作图片像素,我们就获得了一种很强大的能力,用编程的方式实现出很多有趣的效果来,而不用额外制作新图片。比如说透明度渐变,颜色反转等。下面就是2个例子,分别实现透明度渐变和颜色反转的功能。


    例题一: 透明度渐变效果的实现

      给定一张图片,假如我们想实现这么一种效果:图片由全透明状态逐渐清晰,最后达到正常状态。要实现这一个过程,我们首先要获取该图片的所有像素值,逐步让这些像素的alpha值从0转变到正常,每改变图片的所有像素值一次,我们就请求刷屏一次,把最新的像素图画出来,这样我们就能实现透明度渐变的效果了。代码实现如下:

    代码:

    import java.io.IOException;

    import javax.microedition.lcdui.Canvas;
    import javax.microedition.lcdui.Display;
    import javax.microedition.lcdui.Graphics;
    import javax.microedition.lcdui.Image;
    import javax.microedition.midlet.MIDlet;
    import javax.microedition.midlet.MIDletStateChangeException;

    /**
    *  
    * @author Jagie
    *  
    */
    JAVA手机网[www.cnjm.net]
    public class ShadowMIDlet extends MIDlet {
    JAVA手机网[www.cnjm.net]
       Canvas c = new ShadowCanvas();

       public ShadowMIDlet() {

       }

       protected void startApp() throws MIDletStateChangeException {
           Display.getDisplay(this).setCurrent(c);

       }

    JAVA手机网[www.cnjm.net]
       protected void pauseApp() {
           // TODO Auto-generated method stub

       }

       protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
    JAVA手机网[www.cnjm.net]
           // TODO Auto-generated method stub
    JAVA手机网[www.cnjm.net]

       }

    }

    /**
    *  
    * @author Jagie
    JAVA手机网[www.cnjm.net]
    *  
    JAVA手机网[www.cnjm.net]
    */
    class ShadowCanvas extends Canvas implements Runnable {
    JAVA手机网[www.cnjm.net]
       int w, h;

       // 原始图片
       Image srcImage;

       // 原始图片的像素数组
       int[] srcRgbImage;

       // 渐变图片的像素数组
       int[] shadowRgbImage;

       int imgWidth, imgHeight;

    JAVA手机网[www.cnjm.net]
       int count;

       public ShadowCanvas() {
    JAVA手机网[www.cnjm.net]
           w = this.getWidth();
           h = this.getHeight();
           try {
               srcImage = Image.createImage("/av.png");
           } catch (IOException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
           }
           imgWidth = srcImage.getWidth();
           imgHeight = srcImage.getHeight();
           // 制造原始图片的像素数组,用一个int来代表每一个像素,按位表示方式是:0xAARRGGBB
           srcRgbImage = new int[imgWidth * imgHeight];
           // 获取原始图片的所有像素,参见MIDP APPI文档
           srcImage.getRGB(srcRgbImage, 0, imgWidth, 0, 0, imgWidth, imgHeight);

           shadowRgbImage = new int[srcRgbImage.length];

           System.arraycopy(srcRgbImage, 0, shadowRgbImage, 0,
                   shadowRgbImage.length);

           // 渐变图片的所有像素已开始都是全透明的
           for (int i = 0; i < shadowRgbImage.length; i++) {
               shadowRgbImage[i] &= 0x00ffffff;
           }

           new Thread(this).start();

       }

    JAVA手机网[www.cnjm.net]
       public void paint(Graphics g) {
           g.setColor(0, 0, 0);
           g.fillRect(0, 0, w, h);
           // 绘制渐变图片
           g.drawRGB(shadowRgbImage, 0, imgWidth, (w - imgWidth) / 2,
    JAVA手机网[www.cnjm.net]
                   (h - imgHeight) / 2, imgWidth, imgHeight, true);

           g.setColor(0, 255, 0);
           g.drawString("count=" + count, w / 2, 30, Graphics.HCENTER
                   | Graphics.TOP);
       }

       public void run() {
           while (true) {

               boolean changed = false;
               // 改变渐变图片的每一个像素
               for (int i = 0; i < shadowRgbImage.length; i++) {
                   // 获取渐变图片的某一像素的alpha值
                   int alpha = (shadowRgbImage[i] & 0xff000000) >>> 24;
                   // 原始图片的对应像素的alpha值
                   int oldAlpha = (srcRgbImage[i] & 0xff000000) >>> 24;

                   if (alpha < oldAlpha) {
    JAVA手机网[www.cnjm.net]
                       // alpha值++
                       shadowRgbImage[i] = ((alpha + 1) << 24)
                               | (shadowRgbImage[i] & 0x00ffffff);
                       changed = true;
    JAVA手机网[www.cnjm.net]
                   }
               }

               try {
                   Thread.sleep(10);
               } catch (InterruptedException e) {
                   // TODO Auto-generated catch block
    JAVA手机网[www.cnjm.net]
                   e.printStackTrace();
               }
               count++;
               repaint();
               // 当所有像素的alpha值都达到原始值后,线程运行结束
               if (!changed) {
                   System.out.println("over");
                   break;
               }
           }

       }

    }

    透明度渐变效果如下:

     
     

    例题二:颜色反转

      在手机游戏中,我们经常会碰上这样一种情况,比如我方飞机和敌方飞机外观是完全一样的,唯一的区别就是颜色不同,比如说敌方飞机是红色的,而我方飞机是绿色的。在MIDP1.0中,我们就只好制作2张图片来表示2种飞机,自然,这样会造成jar空间的极大浪费。但是在MIDP2.0中,通过对图片直接进行像素操作,反转RGB中的一个值,我们只需要一张图片就够了,样例代码如下:

    ColorMIDlet.java

    代码:

    JAVA手机网[www.cnjm.net]
    import java.io.IOException;

    import javax.microedition.lcdui.Canvas;
    import javax.microedition.lcdui.Display;
    import javax.microedition.lcdui.Graphics;
    import javax.microedition.lcdui.Image;
    import javax.microedition.midlet.MIDlet;
    import javax.microedition.midlet.MIDletStateChangeException;

    JAVA手机网[www.cnjm.net]
    /**
    *  
    * @author Jagie
    *  
    */
    public class ColorMIDlet extends MIDlet {
       Canvas c = new ColorCanvas();

       public ColorMIDlet() {
           super();
    JAVA手机网[www.cnjm.net]
           // TODO Auto-generated constructor stub
    JAVA手机网[www.cnjm.net]
       }

       protected void startApp() throws MIDletStateChangeException {
           Display.getDisplay(this).setCurrent(c);

    JAVA手机网[www.cnjm.net]
       }

       protected void pauseApp() {
           // TODO Auto-generated method stub

       }

       protected void destroyApp(boolean arg0) throws MIDletStateChangeException {
           // TODO Auto-generated method stub

       }
    JAVA手机网[www.cnjm.net]

    }

    /**
    *  
    * @author Jagie
    *  
    */
    JAVA手机网[www.cnjm.net]
    class ColorCanvas extends Canvas {
       Image srcImage;

       int[] targetImage1;

       int[] targetImage2;

       public ColorCanvas() {
           try {
               srcImage = Image.createImage("/av.png");
           } catch (IOException e) {
               // TODO Auto-generated catch block
               e.printStackTrace();
    JAVA手机网[www.cnjm.net]
           }

           targetImage1 = GraphicsUtil.flipImageColor(srcImage,
                   GraphicsUtil.SHIFT_RED_TO_BLUE);
    JAVA手机网[www.cnjm.net]

           targetImage2 = GraphicsUtil.flipImageColor(srcImage,
                   GraphicsUtil.SHIFT_RED_TO_GREEN);

       }
    JAVA手机网[www.cnjm.net]

       public void paint(Graphics g) {

           g.setColor(0, 0, 0);
           g.fillRect(0, 0, this.getWidth(), this.getHeight());
           g.setColor(0x00ff00);
           g.drawString("origin:", getWidth() / 2, 0, Graphics.TOP
                   | Graphics.HCENTER);
           g.drawImage(srcImage, 30, 20, Graphics.LEFT | Graphics.TOP);
           g.drawString("SHIFT_RED_TO_BLUE:", getWidth() / 2,
                   srcImage.getHeight() + 20, Graphics.TOP | Graphics.HCENTER);
    JAVA手机网[www.cnjm.net]
           g.drawRGB(targetImage1, 0, srcImage.getWidth(), 30, srcImage
    JAVA手机网[www.cnjm.net]
                   .getHeight() + 40, srcImage.getWidth(), srcImage.getHeight(),
                   true);
           g.drawString("SHIFT_RED_TO_GREEN:", getWidth() / 2, srcImage
                   .getHeight() * 2 + 40, Graphics.TOP | Graphics.HCENTER);
    JAVA手机网[www.cnjm.net]
           g.drawRGB(targetImage2, 0, srcImage.getWidth(), 30, srcImage
                   .getHeight() * 2 + 60, srcImage.getWidth(), srcImage
                   .getHeight(), true);

    JAVA手机网[www.cnjm.net]
       }

    }


    GraphicsUtil.java


    import javax.microedition.lcdui.Image;

    /**
    *  
    * @author Jagie
    *  
    */
    public class GraphicsUtil {

       public static final int SHIFT_RED_TO_GREEN = 0;

       public static final int SHIFT_RED_TO_BLUE = 1;

       public static final int SHIFT_GREEN_TO_BLUE = 2;

       public static final int SHIFT_GREEN_TO_RED = 3;
    JAVA手机网[www.cnjm.net]

       public static final int SHIFT_BLUE_TO_RED = 4;

       public static final int SHIFT_BLUE_TO_GREEN = 5;

       public static int[] flipImageColor(Image source, int shiftType) {
           // we start by getting the image data into an int array - the number
           // of 32-bit ints is equal to the width multiplied by the height
           int[] rgbData = new int[(source.getWidth() * source.getHeight())];
    JAVA手机网[www.cnjm.net]
           source.getRGB(rgbData, 0, source.getWidth(), 0, 0, source.getWidth(),
                   source.getHeight());

           // now go through every pixel and adjust it's color
           for (int i = 0; i < rgbData.length; i++) {
               int p = rgbData[i];

               // split out the different byte components of the pixel by
               // applying
               // a mask so we only get what we need, then shift it to make it
               // a normal number we can play around with
               int a = ((p & 0xff000000) >> 24);
    JAVA手机网[www.cnjm.net]
               int r = ((p & 0x00ff0000) >> 16);
    JAVA手机网[www.cnjm.net]
               int g = ((p & 0x0000ff00) >> 8);
               int b = ((p & 0x000000ff) >> 0);

               int ba = a, br = r, bb = b, bg = g; // backup copies
    JAVA手机网[www.cnjm.net]

               // flip the colors around according to the operation required
               switch (shiftType) {
               case SHIFT_RED_TO_GREEN:
                   g = r;
    JAVA手机网[www.cnjm.net]
                   r = bg;
                   break;
    JAVA手机网[www.cnjm.net]
               case SHIFT_RED_TO_BLUE:
                   b = r;
                   r = bb;
                   break;
               case SHIFT_GREEN_TO_BLUE:
                   g = b;
    JAVA手机网[www.cnjm.net]
                   b = bg;
                   break;
               case SHIFT_GREEN_TO_RED:
                   g = r;
                   r = bg;
                   break;
               case SHIFT_BLUE_TO_RED:
                   b = r;
    JAVA手机网[www.cnjm.net]
                   r = bb;
                   break;
               case SHIFT_BLUE_TO_GREEN:
                   b = g;
                   g = bb;
                   break;
               }

               // shift all our values back in
               rgbData[i] = (a << 24) | (r << 16) | (g << 8) | b;
           }

           return rgbData;
       }

    }
    JAVA手机网[www.cnjm.net]

    安全高效的使用PNG图 转载

            众所周知,J2ME程序使用的最多的图片格式为PNG格式,如何在程序中使用PNG图片对于程序来说有很多的技巧,我将以我的一些经验技巧来讨论有关PNG图片在程序中的应用。

            通常情况,一个项目开始的时候策划出了需求,美工出了图片,程序员的代码也开始写了,程序员需要图片时,美工给的图片都为一张张静态的图片,然后通过引擎(或者一些工具)导成程序需要的动画序列,和图片数组,程序在Canvas中把图片数组按照图片序列标志的顺序、位置、桢数表现出来。动画是组成游戏的非常重要的部分。因而图片资源的大小、存储方式等对程序生成的jar文件的大小和耗费内存的多少有非常大的影响。在forum.nokia.com和J2ME WTK2.2的一些文档中我们可以看到一些关于图片资源如何优化的例子,在此我不予详述,但是提及,重点讲述我们的项目经验。

             在一些文档中建议我们把所有的资源都放在一张足够大的PNG图片里面,我们对图片进行分割,这样做有非常大的好处,但是有一些缺点,比如我们把一张大图片读入我们的程序里面的时候,我们在菜单部分仅仅需要和菜单那部分资源,不需要其他的资源,这样我们读出的部分显得就非常的浪费内存,我们可以采取把各种图片资源分别存放到几个大图片中,这样我们需要的时候把需要的部分从jar中读到内存,不需要的时候释放出去,这样可以保证一些运算内存比较小的设备使用很多图片资源,不会发生out of memory的异常或者错误。举个例子,一个游戏有菜单、玩游戏、排行榜这样三个部分,完全可以把图片分成三组存储,和菜单相关的存储为menu.png,游戏中的存储为game.png,排行榜需要的图片存储为range.png,我们进入菜单状态只读区menu.png这样程序浪费的内存相当少,进入游戏时先释放掉menu.png占用的资源,再调入game.png。在项目中这是非常好的应用实例。

            在我们的项目中,有时不需要使用切割图像,我们乐于使用一些大小一样的矩形方块状的图片(一些小的公司没有良好的引擎设计时,一般采用这种方式,一些大公司有专门做引擎的e,所以一般采用上面的方法且优化了上面的办法)。因为一些压缩算法、和图片存储格式等众多原因造成了如下状况:把很多png图形放到一张png图片里面省更多的空间。我们如何省空间呢?答案是:自己设计一个资源读取器,把需要的所有png图片读取成2进制码并且按照我们能够简单使用的格式写成一个二进制文件,我们只需要在程序中读取这个二进制文件,在把里面的png还原出来即可。我在我们的项目中发现,单独使用21张14*14的png图像,与巴这21张png重新写成一个二进制文件(不采用任何压缩算法),后者比前者在jar中节省了10KB。所以说,我们在做游戏的时候,如果没有非常好的引擎,可以采用我们办法来节省空间——把松散的图片用程序写成一个二进制文件,在j2me程序中把这些资源读取出来。

           综上,我叙述了两种不同的节省资源的方法,前一种需要比较强大的引擎支持,后一种则不需要,但是后一种确实节省的不如前一种多,但比单纯的用好多png图要节省的多而且不需要复杂的引擎。
    January 25

    正则表达式简记

      想必很多人都对正则表达式都头疼。今天,我以我的认识,加上网上一些文章,希望用常人都可以理解的表达方式来和大家分享学习经验。

      开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明:

      "^The": 开头一定要有"The"字符串;

      "of despair$": 结尾一定要有"of despair" 的字符串;

      那么,

      "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。

      "notice": 匹配包含notice的字符串。

      你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。

      接着,说说 '*', '+',和 '?',

      他们用来表示一个字符可以出现的次数或者顺序。 他们分别表示:

      "zero or more"相当于{0,},

      "one or more"相当于{1,},

      "zero or one."相当于{0,1}, 这里是一些例子:

      "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等);

      "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等。);

      "ab?":和ab{0,1}同义,可以没有或者只有一个b;

      "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串。

      要点, '*', '+',和 '?'只管它前面那个字符。

      你也可以在大括号里面限制字符出现的个数,比如

      "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb");

      "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等。);

      "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb")。

      现在我们把一定几个字符放到小括号里,比如:

      "a(bc)*": 匹配 a 后面跟0个或者一个"bc";

      "a(bc){1,5}": 一个到5个 "bc."

      还有一个字符 '│', 相当于OR 操作:

      "hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串;

      "(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串;

      "(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;

      一个点('.')可以代表所有的单一字符,不包括"\n"

      如果,要匹配包括"\n"在内的所有单个字符,怎么办?

      对了,用'[\n.]'这种模式。

      "a.[0-9]": 一个a加一个字符再加一个0到9的数字

      "^.{3}$": 三个任意字符结尾 .

      中括号括住的内容只匹配一个单一的字符

      "[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样);

      想必很多人都对正则表达式都头疼。今天,我以我的认识,加上网上一些文章,希望用常人都可以理解的表达方式来和大家分享学习经验。

      开篇,还是得说说 ^ 和 $ 他们是分别用来匹配字符串的开始和结束,以下分别举例说明:

      "^The": 开头一定要有"The"字符串;

      "of despair$": 结尾一定要有"of despair" 的字符串;

      那么,

      "^abc$": 就是要求以abc开头和以abc结尾的字符串,实际上是只有abc匹配。

      "notice": 匹配包含notice的字符串。

      你可以看见如果你没有用我们提到的两个字符(最后一个例子),就是说 模式(正则表达式) 可以出现在被检验字符串的任何地方,你没有把他锁定到两边。

      接着,说说 '*', '+',和 '?',

      他们用来表示一个字符可以出现的次数或者顺序。 他们分别表示:

      "zero or more"相当于{0,},

      "one or more"相当于{1,},

      "zero or one."相当于{0,1}, 这里是一些例子:

      "ab*": 和ab{0,}同义,匹配以a开头,后面可以接0个或者N个b组成的字符串("a", "ab", "abbb", 等);

      "ab+": 和ab{1,}同义,同上条一样,但最少要有一个b存在 ("ab", "abbb", 等。);

      "ab?":和ab{0,1}同义,可以没有或者只有一个b;

      "a?b+$": 匹配以一个或者0个a再加上一个以上的b结尾的字符串。

      要点, '*', '+',和 '?'只管它前面那个字符。

      你也可以在大括号里面限制字符出现的个数,比如

      "ab{2}": 要求a后面一定要跟两个b(一个也不能少)("abb");

      "ab{2,}": 要求a后面一定要有两个或者两个以上b(如"abb", "abbbb", 等。);

      "ab{3,5}": 要求a后面可以有2-5个b("abbb", "abbbb", or "abbbbb")。

      现在我们把一定几个字符放到小括号里,比如:

      "a(bc)*": 匹配 a 后面跟0个或者一个"bc";

      "a(bc){1,5}": 一个到5个 "bc."

      还有一个字符 '│', 相当于OR 操作:

      "hi│hello": 匹配含有"hi" 或者 "hello" 的 字符串;

      "(b│cd)ef": 匹配含有 "bef" 或者 "cdef"的字符串;

      "(a│b)*c": 匹配含有这样多个(包括0个)a或b,后面跟一个c的字符串;

      一个点('.')可以代表所有的单一字符,不包括"\n"

      如果,要匹配包括"\n"在内的所有单个字符,怎么办?

      对了,用'[\n.]'这种模式。

      "a.[0-9]": 一个a加一个字符再加一个0到9的数字

      "^.{3}$": 三个任意字符结尾 .

      中括号括住的内容只匹配一个单一的字符

      "[ab]": 匹配单个的 a 或者 b ( 和 "a│b" 一样);

      现在, 用户名的开始和结束都不能是句点。 服务器也是这样。 还有你不能有两个连续的句点他们之间至少存在一个字符,好现在我们来看一下怎么为用户名写一个匹配模式:

      ^[_a-zA-Z0-9-]+$

      现在还不能允许句号的存在。 我们把它加上:

      ^[_a-zA-Z0-9-]+(\.[_a-zA-Z0-9-]+)*$

      上面的意思就是说: "以至少一个规范字符(除了。)开头,后面跟着0个或者多个以点开始的字符串。"

      简单化一点, 我们可以用 eregi()取代 ereg()。eregi()对大小写不敏感, 我们就不需要指定两个范围 "a-z" 和 "A-Z" ? 只需要指定一个就可以了:

      ^[_a-z0-9-]+(\.[_a-z0-9-]+)*$

      后面的服务器名字也是一样,但要去掉下划线:

      ^[a-z0-9-]+(\.[a-z0-9-]+)*$

      好。 现在只需要用“@”把两部分连接:

      ^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$

      这就是完整的email认证匹配模式了,只需要调用

      eregi(‘^[_a-z0-9-]+(\.[_a-z0-9-]+)*@[a-z0-9-]+(\.[a-z0-9-]+)*$ ’,$eamil)

      就可以得到是否为email了。

      正则表达式的其他用法

      提取字符串

      ereg() and eregi() 有一个特性是允许用户通过正则表达式去提取字符串的一部分(具体用法你可以阅读手册)。 比如说,我们想从 path/URL 提取文件名 ? 下面的代码就是你需要:

      ereg("([^\\/]*)$", $pathOrUrl, $regs);

      echo $regs[1];

      高级的代换

      ereg_replace() 和 eregi_replace()也是非常有用的: 假如我们想把所有的间隔负号都替换成逗号:

      ereg_replace("[ \n\r\t]+", ",", trim($str));

      最后,我把另一串检查EMAIL的正则表达式让看文章的你来分析一下。

      "^[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+'.'@'.'[-!#$%&\'*+\\/0-9=?A-Z^_`a-z{|}~]+\.'.'[-!#$%&\'*+\\./0-9=?A-Z^_`a-z{|}~]+$"

      如果能方便的读懂,那这篇文章的目的就达到了。

    为基于J2ME的手机开发移动3D游戏之保留模式

      一、简述

      既然现在你已对3D API比较熟悉并了解了3D图形是如何加入到移动Java应用程序中的。下面将继续告诉你怎样使用3D造型软件以使编码和设计更为简单。

      如今,3D图形几乎是任何一部游戏的关键部分,甚至一些应用程序也通过用3D形式来描述信息而获得了成功。如前文中所述,以立即模式和手工编码建立所有的3D对象的方式进行开发速度很慢且很复杂。应用程序中多边形的所有角点必须在数组中独立编码。在JSR 184中,这称为立即模式。

      另外一种更高级的模式称为保留模式,它允许设计者使用诸如3D Max Studio等3D建模软件来设计场景图,然后把它们应用在程序中。

      二、3D编辑器

      现在,最流行的商业动画制作软件应是3D Studio Max,它支持输出模型或场景图到M3G格式(JSR 184中指定的文件格式)。该文件格式是专门制订的,以适用于移动设备的特有需要。然而,3D Studio Max非常昂贵,即使它是一个很好的工具,也可能并不适合于任何一个人。
    Superscape公司有他自己的Swerve产品家族(Swerve Studio,Swerve Client,Swerve Content),以帮助软件开发者来开发基于3D Java的本机应用程序。遗憾的是,Swerve Studio仅适于有限数目的对Superscape非常熟悉的开发者。

      还有一个自由工具可以选择使用:Blender。Blender是一个开源的3D造型工具,其实它的功能相当强大。你可以用Blender来进行任何3D设计-从简单的造型到完整的动画制作。尽管现在还没有输出工具来输出Blender模型到M3G文件中,但是可能很快就出现一些可用的工具(因为Blender是开源的)。

      三、建模

      如何在MIDP应用程序中使用M3G 文件呢?首先,你需要一个已有某种3D模型的M3G文件。你可以用Google引擎快速查找一下,也可以使用和WirelessToolkit 2.2(在Demo3D 文件夹下)开发包一起发布的现成文件。在本文中,我们将对Sun的Pogoroo例程(编者注:Sun开发工具包自带例程)作深度修改(简化)。我们不让它动起来或者做任何奇特的事情,而仅仅在屏幕上展示各个对象。

      四、加载World

      首先,要从M3D文件中加载World。在pogoroo.m3g文件中,你会看到一只袋鼠在一根弹簧单高跷杆上跳跃,其身边是一片绿茵。下面的列表1调用了加载器类的方法load()。

      列表1. 加载


    try {
     //从M3D文件中加载World
     myWorld = (World)Loader.load("/pogoroo.m3g")[0];
     getObjects();
     setupAspectRatio();
    }
    catch(Exception e) {
     e.printStackTrace();
    }

      五、从3D世界中取得对象

      3D世界已经被加载,现在你必须从中取得各个对象(见列表2)。这里,3D世界中有四个对象,其中之一是有关动画(袋鼠在单脚跳)的信息。你可以使用World的find()方法来取得这些对象。

      列表2. 从3D World中取得对象


    try {
     tRoo = (Group) myWorld.find(POGOROO);
     tCams = (Group) myWorld.find(CAMERA);
     acRoo = (Group) myWorld.find(TRANSFORM);
     animRoo = (AnimationController) myWorld.find(ROO);
     //取得动画的长度
     AnimationTrack track = acRoo.getAnimationTrack(0);
     animLength = 1000; // 缺省长度为1秒
     if (track != null) {
      KeyframeSequence ks = track.getKeyframeSequence();
      if (ks != null) animLength = ks.getDuration();
     }

    }
    catch(Exception e) {
     e.printStackTrace();
    }

      六、设置窗口宽高比例

      你必须设置窗口的宽高比例以使对象能够正确着色。列表3中的代码是未改动的-基本上同Sun的例子一样。首先,检查画布的宽度和高度,然后根据相机的类型来计算宽高比例。

      列表3. 设置宽高比例


    void setupAspectRatio() {
     viewport_x = 0;
     viewport_y = 0;
     viewport_width = myCanvas.getWidth();
     viewport_height = myCanvas.getHeight();
     Camera cam = myWorld.getActiveCamera();
     float[] params = new float[4];
     int type = cam.getProjection(params);
     if(type != Camera.GENERIC) {
      //计算窗口的宽高比
      float waspect=viewport_width/viewport_height;
      if (waspect   float height = viewport_width/params[1];
       viewport_height=(int)height;
       viewport_y=(myCanvas.getHeight()-viewport_height)/2;
      }
      else {
       float width = viewport_height*params[1];
       viewport_width=(int)width;
       viewport_x=(myCanvas.getWidth()-viewport_width)/2;
      }
     }
    }

      七、刷新视图

      为了刷新视图,你可以用TimerTask来调用画布的repaint()方法。另一种方法是直接使用线程,然后创建ExampleCanvas(画布类的名字)来实现Runnable接口。

      列表4. 刷新视图


    private class RefreshTask extends TimerTask
    {
     public void run(){
      if(myCanvas != null && myGraphics3D != null && myWorld != null) {
       int startTime = (int)System.currentTimeMillis();
       int validity = myWorld.animate(startTime);
       myCanvas.repaint(viewport_x, viewport_y, viewport_width, viewport_height);
      }
     }
    }

    为J2ME开发移动3D游戏之立即模式

      一、简述

      现在,移动游戏和移动应用开发极为热门!游戏中需要有时髦漂亮的图形,其设计标准比以前任何时候都要高。本文将告诉你怎样用酷毙的移动3D图形API为J2ME设备开发3D图形游戏。

      如果你在用MIDP1.0进行用户接口编程,那么有两条路你可以选择:使用高级的UI类或者一切由你自己从头开始。作为游戏开发者,第一种选择往往是不可能的;这是为什么游戏开发者不得不为他们的高级游戏开发自己的3D引擎的原因。无疑,这需要付出大量的时间和努力,而缺乏浮点数支持的CLDC 1.0(MIDP 1.0正是建于其上)对问题的解决没有多大帮助。

      在MIDP 2.0中,有一个可选的叫移动3D图形API的软件包,或者叫JSR 184。该API是第一个基于Java标准开发的移动设备上的三维图形软件包。该API既有高级又有低级图形特征;其中,高级特征称为保留模式,低级特征称为立即模式。保留模式使得开发者有可能使用场景图形并使场景中的物体根据虚拟相机和灯光的位置进行自身的着色。立即模式能够允许应用程序直接进行物体绘制。如果需要,可以在同一个应用程序中使用这两种模式。

      本文着重介绍立即模式。

      二、3D API

      让我们以列举和解释该3D API中的类作为开始。除了这些API外,JSR 184还包含了一个场景图形结构和一个相应的文件格式以有效地管理和配置3D内容。该文件格式定义了一种m3g文件,这种文件典型地从3D建模文件应用程序中转换而来。

      表1.3D API类

    描述
    AnimationController 控制动画顺序。
    AnimationTrack 把一个KeyframeSequence同一个AnimationController相关联。
    Appearance 定义一个网眼(Mesh)或一个Spring3D的着色属性的一组对象。
    Background 定义视图是怎样被清除的。
    Camera 一个场景图顶点,它定义了场景中观察者的位置以及从3D到2D的投影。
    CompositingMode 一个Appearance类,它封装了每一个像素的合成属性。
    Fog 一个Appearance类,它包含了雾化的有关属性。
    Graphics3D 一个单独的3D图形上下文。所有的着色操作都是在该类中的render()方法中实现的。
    Group 一个场景图形结点,它存储了一个无序的结点集作为它的子结点。
    Image2D 一个二维图像,可用于纹理,背景,或者精灵图像。
    IndexBuffer 该类定义了如何把顶点连接起来以形成一个几何体。
    KeyframeSequence 封装了一系列的具有时间戳和矢量值的关键帧的动画数据。
    Light 描述了不同类型的光源。
    Loader  下载和反串行化图形结点及结点成分,以及整个场景图形。
    Material 封装了进行光学计算的材质属性。
    Mesh 描述了一个3D对象,它是用多边形面定义的。
    MorphingMesh 描述了一个顶点-变形的多边形网眼。
    Node 所有场景图形结点的抽象基类。其五个具体子类是:Camera,Mesh,Sprite3D,Light和Group。
    Object3D 所有可以成为3D世界中组成部分的对象的抽象基类。
    olygonMode 封装了多边形级别属性。
    RayIntersection 存储了对于分割的Mesh或Sprite3D的引用,以及有关分割点的信息。
    SkinnedMesh 描述了一个框架动画的多边形网眼。
    Sprite3D 用3D位置来描述一个2D图像。
    Texture2D 封装了一个2D纹理图像和一个属性集合,这些属性指出该图像是如何应用到子网眼上的。
    Transform 一个通用的4x4的浮点数矩阵,用来描述一个变换。
    Transformable Node和Texture2D类的抽象基类。
    TriangleStripArray 定义了一个三角形带数组。
    VertexArray 一个整型矢量数组,描述了顶点位置,法线,颜色或者纹理坐标。
    VertexBuffer 存储对于VertexArrays的引用,它包含了一个顶点集的位置,颜色,法线,以及纹理坐标。
    World 一个特别的Group结点,它作为场景图最顶层的容器。
      三、举例

      我们将开发一个简单的旋转一个多边形的3D应用程序为例。该多边形是一个立方体,它的纹理是一张旧汽车相片。列表1展示了例程midlet的主要类-应用程序的中心类。该类负责创建应用程序并建立起运行MyCanvas的计时器。

      列表1. MIDletMain类

    import javax.microedition.midlet.*;
    import javax.microedition.lcdui.*;
    import java.util.*;

    public class MIDletMain extends MIDlet {
     static MIDletMain midlet;
     MyCanvas d = new MyCanvas();
     Timer iTimer = new Timer();

     public MIDletMain() {
      this.midlet = this;
     }
     public void startApp() {
      Display.getDisplay(this).setCurrent(d);
      iTimer.schedule( new MyTimerTask(), 0, 40 );
     }
     public void pauseApp() {}
     public void destroyApp(boolean unconditional) {}
     public static void quitApp() {
      midlet.destroyApp(true);
      midlet.notifyDestroyed();
      midlet = null;
     }

     class MyTimerTask extends TimerTask {
      public void run() {
       if( d != null ) {
        d.repaint();
       }
      }
     }
    }

      列表2显示了MyCanvas类,该类包含了应用程序的所有图形逻辑。init()方法负责结点的创建,纹理文件的装载并设置纹理,外观和背景也被一起设置。paint()方法负责着色并旋转立方体。图1展示了正在一个模拟器中运行的实际程序。

      列表2. MyCanvas类

    import javax.microedition.lcdui.*;
    import javax.microedition.m3g.*;

    public class MyCanvas extends Canvas {

     private Graphics3D graphics3d;
     private Camera camera;
     private Light light;
     private float angle = 0.0f;
     private Transform transform = new Transform();
     private Background background = new Background();
     private VertexBuffer vbuffer;
     private IndexBuffer indexbuffer;
     private Appearance appearance;
     private Material material = new Material();
     private Image image;

     public MyCanvas() {
      // 创建Displayable对象以探听命令事件
      setCommandListener(new CommandListener() {
       public void commandAction(Command c, Displayable d) {
        if (c.getCommandType() == Command.EXIT) {
         MIDletMain.quitApp();}}
        });
        try { init();}
        catch(Exception e) { e.printStackTrace();}
       }

      /**
      * 组件的初始化
      */
      private void init() throws Exception {
       addCommand(new Command("Exit", Command.EXIT, 1));
       graphics3d = Graphics3D.getInstance();

       camera = new Camera();
       camera.setPerspective( 60.0f,(float)getWidth()/ (float)getHeight(),1.0f,1000.0f );

       light = new Light();
       light.setColor(0xffffff);
       light.setIntensity(1.25f);

       short[] vert = {
        5, 5, 5, -5, 5, 5, 5,-5, 5, -5,-5, 5,
        -5, 5,-5, 5, 5,-5, -5,-5,-5, 5,-5,-5,
        -5, 5, 5, -5, 5,-5, -5,-5, 5, -5,-5,-5,
        5, 5,-5, 5, 5, 5, 5,-5,-5, 5,-5, 5,
        5, 5,-5, -5, 5,-5, 5, 5, 5, -5, 5, 5,
        5,-5, 5, -5,-5, 5, 5,-5,-5, -5,-5,-5 };

       VertexArray vertArray = new VertexArray(vert.length / 3, 3, 2);
       vertArray.set(0, vert.length/3, vert);

       //立方体的各个结点法线
       byte[] norm = {
        0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127,
        0, 0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127,
        -127, 0, 0, -127, 0, 0, -127, 0, 0, -127, 0, 0,
        127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0, 0,
        0, 127, 0, 0, 127, 0, 0, 127, 0, 0, 127, 0,
        0,-127, 0, 0,-127, 0, 0,-127, 0, 0,-127, 0 };

       VertexArray normArray = new VertexArray(norm.length / 3, 3, 1);
       normArray.set(0, norm.length/3, norm);

       //各个结点的纹理坐标
       short[] tex = {
        1, 0, 0, 0, 1, 1, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1,
        1, 0, 0, 0, 1, 1, 0, 1 };

       VertexArray texArray = new VertexArray(tex.length / 2, 2, 2);
       texArray.set(0, tex.length/2, tex);

       int[] stripLen = { 4, 4, 4, 4, 4, 4 };

       // 对象的VertexBuffer
       VertexBuffer vb = vbuffer = new VertexBuffer();
       vb.setPositions(vertArray, 1.0f, null);
       vb.setNormals(normArray);
       vb.setTexCoords(0, texArray, 1.0f, null);

       indexbuffer = new TriangleStripArray( 0, stripLen );

       //纹理图像
       image = Image.createImage( "/pic1.png" );
       Image2D image2D = new Image2D( Image2D.RGB, image );
       Texture2D texture = new Texture2D( image2D );
       texture.setFiltering(Texture2D.FILTER_NEAREST,Texture2D.FILTER_NEAREST);
       texture.setWrapping(Texture2D.WRAP_CLAMP,Texture2D.WRAP_CLAMP);
       texture.setBlending(Texture2D.FUNC_MODULATE);

       // 创建外观(Appearance)对象
       appearance = new Appearance();
       appearance.setTexture(0, texture);
       appearance.setMaterial(material);
       material.setColor(Material.DIFFUSE, 0xFFFFFFFF);
       material.setColor(Material.SPECULAR, 0xFFFFFFFF);
       material.setShininess(100.0f);

       background.setColor(0xffffcc);
      }

      protected void paint(Graphics g) {
       graphics3d.bindTarget(g, true,
       Graphics3D.DITHER | Graphics3D.TRUE_COLOR);
       graphics3d.clear(background);

       //设置照相机
       Transform transform = new Transform();
       transform.postTranslate(0.0f, 0.0f, 30.0f);
       graphics3d.setCamera(camera, transform);

       //设置灯光
       graphics3d.resetLights();
       graphics3d.addLight(light, transform);

       //设置旋转
       angle += 1.0f;
       transform.setIdentity();
       transform.postRotate(angle, 1.0f, 1.0f, 1.0f);

       graphics3d.render(vbuffer, indexbuffer, appearance, transform);
       graphics3d.releaseTarget();
     }
    }


    图1 正在一个模拟器中运行的应用程序


      四、小结

      JSR 184对于可以运行MIDP 2.0的设备来说,是一个节省时间和空间的可选的软件开发包。它允许开发者使用两种图形方式-保留模式和立即模式-来产生3D图形。本文集中讲述了立即模式,并给出一个例子程序来说明怎样使用3D。

     

    谈谈国内手机游戏制作和国外的比较 (转载)

    谈谈国内手机游戏制作和国外的比较
    adaken

        我做手游已经3、4个年头了,经历过行业里的风风雨雨、分分合合……其他的这次先不说,单纯从产品质量的角度来看,我认为,国内手游的整体水平 (这里单指Kjava、Brew等视频游戏)这几年虽然不至于倒退,但是说实话,真没有什么太大的提高。反观目前世界上一批领先的手机游戏开发商,他们的产品却越做越好,公司也越做越大。

      看到这里,可能很多人都会马上提出驳论。没关系,请继续看完再发表:)

      因为是随笔,所以我把能想到的都罗列出来,供大家探讨。

      1、产品创意

      谈到创意,说实话,我觉得这可能是我们和老外们差距最小的一点。因为目前各种信息都共享了,游戏行业的东西更是如此,每天都会有很多新东西塞进我们的大脑。再加上我们有些负责创意、策划的同志们,凭着对各种平台游戏的了如指掌,稍微动动脑子mix一把,就弄出了不少很有意思的点子……但遗憾的是,很多这样的游戏并没有变成产品,变成产品的那些一半以上没有上线,上线的那些一半因为制作中的某些环节,也已经面目全非,最初的那点创意根本没有体现出来!~所以我总在对自己说,国内现在不缺(或者说‘暂不需要’)好的创意。希望做游戏策划的同志们不要把主要精力放在这里了。

      2、游戏策划

      游戏设计文档,是一款游戏产品进行开发的根本依据。也是一名策划最应该花心思的地方。……不可否认,我们的策划大部分都没有受过正规的培训(不是不想,而是找不到),而且入行时间大都很短,没有丰富的开发经验,等等原因吧……所以,造成的现状是,设计文档写得乱七八糟、主次不分、避重就轻。(说到这里,我本想举几个事例,但是这样会把文章拉的很长,还是放弃了,但我会在以后专门就这个问题写些具体的东西。)我曾见过某外国公司撰写一款掌机游戏设计文档,长达190多页。里面并没有长篇大论地分析当前市场形势、如何宣传推广,对物品、道具等内容也是简洁明了的描述,甚至只写了前面的几个,后面再进行补充。重点全部是游戏中的各种交互和规则,细致到连游戏中的对话哪里需要空格,那里换行都有说明……相反,我也看到国内一些公司的设计文档,说实话,要是不告诉我这是设计文档,我还以为是枪手们写的游戏评论呢!~洋洋洒洒、夸夸其谈,大半的篇幅用来告诉读者这个游戏哪里好玩、哪里创新。我就奇怪了,程序们看到这样的东西,居然还真能开发出产品来,在此~给国内踏实肯干的程序员们鞠个躬。总结:要想高效地开发出好产品,必须要把设计文档写好!

      3、游戏美术

      对于我们手游的美术水准,我认为是:全球中等水平上的参差不起。主要原因是:国内的开发商对美术环节往往不重视,导致美术人员的工作情绪普遍比较失落,没有进行创新、再学习的意愿……像一部印刷机一样,不断地揣摩、翻译策划人员心目中的游戏景象,而没有主动性的创作。不过,我认为,‘美术制作’是整个游戏开发过程中最容易快速提高的环节,只要制作公司的高层意识到这一点,再经过有效地培训,应该会有一个比较好的改观。但是,我不希望看到,假如有一天,国外优秀的手机游戏大量涌进进入我们的市场,当用户们的选择告诉我们‘该重视游戏画面了’的时候,再意识到这些就已经晚了……

      4、程序开发

      看看市场上那些取得成功的国产网络游戏,就应该明白,我们并不缺少优秀的程序员。但在手游行业里,我认为,这些程序员的心态出了问题,主观或客观的原因,让他们不能全身心地投入开发。整个行业的急功近利、生产周期的不断压缩、工作中的交流不畅……等等等等。因为我本人没有直接和国外的程序员打过交道,对他们的工作方法等内容也知之甚少,所以不敢在此妄言什么。只是有一点,让我想了很久:国外很多优秀游戏的策划或是制作人本身也是程序员,或者是程序员出身,这种情况在国内还是不多的。

      5、整体包装

      游戏的Demo出来了,开发团队内部欣喜若狂,一边忙着测试和移植,一边联系合作伙伴准备申报吧!……直到游戏全部完工,甚至游戏顺利上线,除了那几张粗糙的截图和几百字牵强的评论,玩家似乎看不到关于这款游戏更全面、更深入、更吸引人的东西了……这就好比我们准备在超市购买一瓶饮料,在这之前如果你根本不清楚这个瓶子里装的液体是什么颜色、什么味道、也没有任何的介绍和比较诱人的宣传包装,你会毫不犹豫地掏钱买下它么?……至少我不会,更何况,现在一款游戏的价格绝对比一瓶饮料的贵出很多。我要说的是,一款游戏的完成,不仅是jad、jar、几张截图、几份文档的完成。而是需要全方位的包装,比如:网站上的游戏专区、精美的宣传图片、与主流媒体合作推广……等等。说得简单一点,看看gameloft、mr.goodliving、com2us人家的网站吧,一种强烈的、对那些游戏的获取感会油然而生! ~这些其实是很表面的东西,但我们早晚都要做的,行业的快速发展势必造成优胜劣汰,早点学会并做到,就会让自己在发展中处于主动。

      6、……

      其实想写的东西还有很多,但从一名游戏策划的角度来看,写到这里就足够了。当然,欢迎朋友们继续补充和交流。

      结尾,真心的希望我们国内的游戏产业、手机游戏产业,能迅速发展壮大,做出一些能够‘感动’玩家的精品。


    来源:电玩俱乐部

    J2ME内存占用详解及优化方法

          在S40上经常出现OutOfMemoryError异常。
          首先了解一下分析内存占用的方法,一般有两种:模拟器自带工具和Runtime类方法。
          模拟器自带工具:WTK带了一个Memory Monitor,首先运行他你的程序会慢的一塌糊涂,这对游戏开发者来说简直是无法忍受的。而且根本无法显示正确的内存占用量,所以一般使用的是7210模拟器自带的内存监视器,模拟的很准,但唯一的缺点是内存太少,才200K。也见过某些人使用3220的模拟器监视内存,好像内存稍微大一点,我还没来得及尝试就再也不用为老40写程序了,庆幸。
       Runtime类方法:经常用这个语句System.out.println(Runtime.getRuntime().freeMemory());后来集成进了我的引擎,他能够显示当前剩余内存。不记得有多少次我用它在老40上来寻找内存占用峰值。
           
           了解了分析内存的方法,来看看内存占用的罪魁祸首:程序和资源。
           
    JAVA手机网[www.cnjm.net]
           程序:类会被编译成class字节码文件随MIDlet的启动加载进内存,而且是一次性全部加入。也就是说MIDlet里类个数越多、单个类程序越长、类内字符串常量及数据越多,编译后的class文件就越大,载入后占用的内存也越多。我经常在MIDlet类的构造函数里用Runtime方法来查看MIDlet启动后整个程序占用内存量。
           优化方法:
           1.某些同志将MIDlet程序写成两个类来减少内存占用量,但是以牺牲Java的OOP特性为代价的。在程序比较大时这种弊端将尤为显见。而且CoCoMo曾经遇到过单个类过大,载入时间过长而违反百宝箱有关Logo 6秒时间限制的情形。因而我现在的程序加带引擎一般都是6-7个类。
           2.尽量编写优雅的代码,减少函数数量,在程序发布时去掉try catch,最大限度的减少程序行数,这一般都是在老40上没有办法的办法,现在CoCoMo已经不靠这个来省内存了。
           3.将数据及字符串写进文件,在用时方载入内存,不用时设为null。
           4.I/O操作getClass().getResourceAsStream(file);、数据库操做RecordStore.openRecordStore(name, true);、声音创建Manager.createPlayer();、图像创建Image.createImage(file);会在短时间内占用大量内存且过后释放,如果MIDlet程序内存剩余量不足则会在这些函数频繁调用时发生内存溢出,产生所谓的内存峰值,尤其在老40上比较普遍。当你再次与讨厌的OutOfMemoryError碰面时,多用Runtime查找内存峰值发生位置并尽量将这些语句分开调用,并灵活运用System.gc()来及时回收。
           
           资源:
             图片:是占用内存的大户,尤其是手机游戏图片资源众多。对图片资源在内存中占用量的计算成为J2ME游戏开发者的经常性工作,CoCoMo来解释一下如何计算图片在内存中的占用量:
           内存占用量=宽*高*像素字节数,其中像素字节数因机型而异。
           例如一张64*64的图片在7210上的内存占用量=64*64*1.5=6144(字节)=6K、在S60上的内存占用量=64*64*2=8192(字节)=8K。像素字节数因机型而异,例如7210是4096色机型,也就是说用12位来表示一个像素,所以乘上1.5,而S60是65536色的机型,用16位来表示一个像素,所以乘上2。
           优化方法:
           有些人认为压缩图片可以节省内存,这种想法是错误的。根据上面的解释图片载入内存后只和宽高有关系,和图片数据量大小没有任何关系,压缩图片只能减少jar大小而不能减少内存占用量。
    JAVA手机网[www.cnjm.net]
           1.静态法:减小图片大小,宽高小了结果当然小了。根据这个思路出现了动画编辑器之类的工具,像gameloft的波斯王子,人物被分割后使人体的部位可以重用,各部位紧凑放置都是为了较少图片大小,充分利用图片中的每一寸空间。
           2.动态法:减少同一时刻载入内存的图片数。CoCoMo曾经在火影武士项目中遇到过这种情况,当时有6种怪物,如果同时载入内存在老40上肯定爆掉了,但是每关只出现两到三种怪物,所以每一关只需要载入该关出现的怪物图片即可。现在想起来当时做这个项目在老40上溢出频出,真把我搞死了。
          声音:声音也是比较耗用内存的资源,声音中音轨所占的byte会转化成字节流被载入到内存中。因而减少音轨所占byte即可减少内存耗用量。目前gameloft的做法是用声音转化工具将mid转化为ott,然后变为ByteArrayInputStream字节流来创建Player。
     
     
    转载自CoCoMo的空间。