2006年03月29日

http://www.forum.nokia.com/
http://developer.sonyericsson.com/site/global/docstools/java/p_java.jsp
http://www.siemens-mobile.com/developer
http://www.my-onetouch.com/
http://www.motocoder.com
http://developer.samsungmobile.com/eng/front_zone/bbs/bbs_main.jsp?p_menu_id=1500
http://www.nec-mfriend.com/cn/spec/download.php

 

2006年02月25日

accwiz.exe > Accessibility Wizard for walking you through setting up your machine for your mobility needs. 辅助工具向导

acsetups.exe > ACS setup DCOM server executable

actmovie.exe > Direct Show setup tool 直接显示安装工具

append.exe > Allows programs to open data in specified directories as if they were in the current directory. 允许程序打开制定目录中的数据

arp.exe > NETWORK Display and modify IP – Hardware addresses 显示和更改计算机的IP与硬件物理地址的对应列表

at.exe > AT is a scheduling utility also included with UNIX 计划运行任务

atmadm.exe > Displays statistics for ATM call manager. ATM调用管理器统计

attrib.exe > Display and modify attributes for files and folders 显示和更改文件和文件夹属性

autochk.exe > Used to check and repair Windows File Systems 检测修复文件系统

autoconv.exe > Automates the file system conversion during reboots 在启动过程中自动转化系统

autofmt.exe > Automates the file format process during reboots 在启动过程中格式化进程

autolfn.exe > Used for formatting long file names 使用长文件名格式

bootok.exe > Boot acceptance application for registry bootvrfy.exe > Bootvrfy.exe, a program included in Windows 2000 that notifies the system that startup was successful. Bootvrfy.exe can be run on a local or remote computer. 通报启动成功
cacls.exe > Displays or modifies access control lists (ACLs) of files. 显示和编辑ACL

calc.exe > Windows Calculators 计算器

cdplayer.exe > Windows CD Player CD播放器

change.exe > Change { User | Port | Logon } 与终端服务器相关的查询

charmap.exe > Character Map 字符映射表

chglogon.exe > Same as using "Change Logon" 启动或停用会话记录

chgport.exe > Same as using "Change Port" 改变端口(终端服务)

chgusr.exe > Same as using "Change User" 改变用户(终端服务)

chkdsk.exe > Check the hard disk for errors similar to Scandisk 3 Stages must specify a Drive Letter 磁盘检测程序

chkntfs.exe > Same as using chkdsk but for NTFS NTFS磁盘检测程序

cidaemon.exe > Component of Ci Filer Service 组成Ci文档服务

cipher.exe > Displays or alters the encryption of directories [files] on NTFS partitions. 在NTFS上显示或改变加密的文件或目录

cisvc.exe > Content Index — It’s the content indexing service for I 索引内容

ckcnv.exe > Cookie Convertor 变换Cookie

cleanmgr.exe > Disk Cleanup, popular with Windows 98 磁盘清理

cliconfg.exe > SQL Server Client Network Utility SQL客户网络工具 clipbrd.exe > Clipboard viewer for Local will allow you to connect to other clipboards 剪贴簿查看

clipsrv.exe > Start the clipboard Server 运行Clipboard服务

clspack.exe > CLSPACK used to create a file listing of system packages 建立系统文件列表清

cluster.exe > Display a cluster in a domain 显示域的集群

_cmd_.exe > Famous command prompt 没什么好说的!

cmdl32.exe > Connection Manager Auto-Download 自动下载连接管理

cmmgr32.exe > Connection Manager 连接管理器

cmmon32.exe > Connection Manager Monitor 连接管理器监视

cmstp.exe > Connection Manager Profile Manager 连接管理器配置文件安装程序

comclust.exe > about cluster server 集群

comp.exe > ComClust Add, Remove, or Join a cluster. 比较两个文件和文件集的内容*

compact.exe > Displays or alters the compression of files on NTFS partitions. 显示或改变NTFS分区上文件的压缩状态

conime.exe > Console IME IME控制台

control.exe > Starts the control panel 控制面板

convert.exe > Convert File System to NTFS 转换文件系统到NTFS

convlog.exe > Converts MS IIS log files 转换IIS日志文件格式到NCSA格式

cprofile.exe > Copy profiles 转换显示模式

cscript.exe > MS Windows Scripts Host Version 5.1 较本宿主版本

csrss.exe > Client Server Runtime Process 客户服务器Runtime进程

csvde.exe > Comma Separated Variable Import/Export Utility 日至格式转换程序

dbgtrace.exe > 和Terminal Server相关

dcomcnfg.exe > Display the current DCOM configuration. DCOM配置属性

dcphelp.exe > ?

dcpromo.exe > Promote a domain controller to ADSI AD安装向导

ddeshare.exe > Display DDE shares on local or remote computer DDE共享

ddmprxy.exe >

debug.exe > Runs Debug, a program testing and editing tool. 就是DEBUG啦!

dfrgfat.exe > Defrag FAT file system FAT分区磁盘碎片整理程序

dfrgntfs.exe > Defrag NTFS file system NTFS分区磁盘碎片整理程序

dfs_cmd_.exe > configures a Dfs tree 配置一个DFS树

dfsinit.exe > Distributed File System Initialization 分布式文件系统初始化

dfssvc.exe > Distributed File System Server 分布式文件系统服务器

diantz.exe > MS Cabinet Maker 制作CAB文件

diskperf.exe > Starts physical Disk Performance counters 磁盘性能计数器

dllhost.exe > dllhost is used on all versions of Windows 2000. dllhost is the hedost process for all COM+ applications. 所有COM+应用软件的主进程
dllhst3g.exe >

qwinsta.exe > Display information about Terminal Sessions. 显示终端服务的信息

rasadmin.exe > Start the remote access admin service 启动远程访问服务

rasautou.exe > Creates a RAS connection 建立一个RAS连接

rasdial.exe > Dial a connection 拨号连接

rasphone.exe > Starts a RAS connection 运行RAS连接

rcp.exe > Copies a file from and to a RCP service. 在 Windows 2000 计算机和运行远程外壳端口监控程序 rshd 的系统之间复制文件

rdpclip.exe > RdpClip allows you to copy and paste files between a terminal session and client console session. 再终端和本地复制和粘贴文件

recover.exe > Recovers readable information from a bad or defective disk 从坏的或有缺陷的磁盘中恢复可读取的信息。

redir.exe > Starts the redirector service 运行重定向服务

regedt32.exe > 32-bit register service 32位注册服务

regini.exe > modify registry permissions from within a script 用脚本修改注册许可

register.exe > Register a program so it can have special execution characteristics. 注册包含特殊运行字符的程序

regsvc.exe >

regsvr32.exe > Registers and unregister’s dll’s. As to how and where it register’s them I dont know. 注册和反注册DLL

regtrace.exe > Options to tune debug options for applications failing to dump trace statements
Trace 设置
regwiz.exe > Registration Wizard 注册向导

remrras.exe >

replace.exe > Replace files 用源目录中的同名文件替换目标目录中的文件。

reset.exe > Reset an active section 重置活动部分

rexec.exe > Runs commands on remote hosts running the REXEC service. 在运行 REXEC 服务的远程计算机上运行命令。rexec 命令在执行指定命令前,验证远程计算机上的用户名,只有安装了 TCP/IP 协议后才可以使用该命令。

risetup.exe > Starts the Remote Installation Service Wizard. 运行远程安装向导服务

route.exe > display or edit the current routing tables. 控制网络路由表

routemon.exe > no longer supported 不再支持了!

router.exe > Router software that runs either on a dedicated DOS or on an OS/2 system. Route软件在 DOS或者是OS/2系统

rsh.exe > Runs commands on remote hosts running the RSH service 在运行 RSH 服务的远程计算机上运行命令

rsm.exe > Mounts and configures remote system media 配置远程系统媒体

rsnotify.exe > Remote storage notification recall 远程存储通知回显

rsvp.exe > Resource reservation protocol 源预约协议

runas.exe > RUN a program as another user 允许用户用其他权限运行指定的工具和程序

rundll32.exe > Launches a 32-bit dll program 启动32位DLL程序

runonce.exe > Causes a program to run during startup 运行程序再开始菜单中

rwinsta.exe > Reset the session subsystem hardware and software to known initial values 重置会话子系统硬件和软件到最初的值

savedump.exe > Does not write to e:\winnt\user.dmp 不写入User.dmp中

scardsvr.exe > Smart Card resource management server 子能卡资源管理服务器

schupgr.exe > It will read the schema update files (.ldf files) and upgrade the schema. (part of ADSI) 读取计%

http://mywebpages.comcast.net/ohommes/jScience/download.html

2005年12月09日

手机游戏策划模板

文章来源:http://spaces.msn.com/members/xqwww/Blog/cns!1p1YYUu39nrlsvF-6BUVMwrQ!301.entry

[转贴]

 游戏运行的平台
1. 游戏预定支持的手机类型
Nokia 系列:6108 7650 3650
索尼爱立信 系列: T628 T618
摩托罗拉:
2. 游戏屏幕的尺寸
128X128 pixels
3. 游戏安装包的大小
不超过 100 K
4. 预定开发周期
前期关卡设计和美工 10 天
程序员和美工同步 5 天
测试和完善 3 天
二. 策划大纲
1.游戏的定位
2.游戏背景
3.游戏内容
4.游戏策划
1)开始界面
主菜单新的游戏旧的进度游戏说明游戏设置游戏积分关于游戏
点击开始打开游戏的主菜单
游戏的公司Logo界面进入后,直接弹出开始界面和主菜单。
新的游戏:
旧的进度:
游戏说明:
游戏设置:
游戏积分:
关于游戏:
2)游戏背景介绍
打开新的游戏,即可进入游戏的背景说明…..
3)选择游戏的难度
普通级
恶梦级
地狱级
4)主游戏界面(2*倍平面地图)
5. 游戏参数设定
6. 游戏效果草图
期望45度倾斜视角立体视图的效果
7. 游戏的风格,属性和视觉的感受
8. 游戏特色的解说(商业卖点)
三.预计开发进度
1. demo版本发布时间
天 内(可以出一个在 机器上的演示版本(部分功能不全))
2.正式版本发布时间
天 内(可以出一个在 机器上的正式版本)

3. 商业化版本发布时间
天 内(可以出一个在 机器上test通过并修正bug且商业化)
四.开发需求设定
1. 美工需求
美工的风格需要带有强烈的卡通色彩
1)场景:
2)物品:
3)动画:
4)人物:
5)界面
6)按钮
2. 音效需求
背景音乐
碰撞音乐
打斗音乐
提示音乐
过关音乐

3. 开发团队所负责的成员
项目负责人员:
策划人员:
美工人员:
程序人员:
音乐制作人员:
测试人员:
4.项目的基本的开发进度
五.游戏性能测试
1.图像率新速度
2.文件安装完大小
3.文件未安装大小
4.游戏所占有的内存
5.游戏所带的文件清单

 游戏运行的平台
1. 游戏预定支持的手机类型
Nokia 系列:6108 7650 3650
索尼爱立信 系列: T628 T618
摩托罗拉:
2. 游戏屏幕的尺寸
128X128 pixels
3. 游戏安装包的大小
不超过 100 K
4. 预定开发周期
前期关卡设计和美工 10 天
程序员和美工同步 5 天
测试和完善 3 天
二. 策划大纲
1.游戏的定位
2.游戏背景
3.游戏内容
4.游戏策划
1)开始界面
主菜单新的游戏旧的进度游戏说明游戏设置游戏积分关于游戏
点击开始打开游戏的主菜单
游戏的公司Logo界面进入后,直接弹出开始界面和主菜单。
新的游戏:
旧的进度:
游戏说明:
游戏设置:
游戏积分:
关于游戏:
2)游戏背景介绍
打开新的游戏,即可进入游戏的背景说明…..
3)选择游戏的难度
普通级
恶梦级
地狱级
4)主游戏界面(2*倍平面地图)
5. 游戏参数设定
6. 游戏效果草图
期望45度倾斜视角立体视图的效果
7. 游戏的风格,属性和视觉的感受
8. 游戏特色的解说(商业卖点)
三.预计开发进度
1. demo版本发布时间
天 内(可以出一个在 机器上的演示版本(部分功能不全))
2.正式版本发布时间
天 内(可以出一个在 机器上的正式版本)

3. 商业化版本发布时间
天 内(可以出一个在 机器上test通过并修正bug且商业化)
四.开发需求设定
1. 美工需求
美工的风格需要带有强烈的卡通色彩
1)场景:
2)物品:
3)动画:
4)人物:
5)界面
6)按钮
2. 音效需求
背景音乐
碰撞音乐
打斗音乐
提示音乐
过关音乐

3. 开发团队所负责的成员
项目负责人员:
策划人员:
美工人员:
程序人员:
音乐制作人员:
测试人员:
4.项目的基本的开发进度
五.游戏性能测试
1.图像率新速度
2.文件安装完大小
3.文件未安装大小
4.游戏所占有的内存
5.游戏所带的文件清单

 游戏运行的平台
1. 游戏预定支持的手机类型
Nokia 系列:6108 7650 3650
索尼爱立信 系列: T628 T618
摩托罗拉:
2. 游戏屏幕的尺寸
128X128 pixels
3. 游戏安装包的大小
不超过 100 K
4. 预定开发周期
前期关卡设计和美工 10 天
程序员和美工同步 5 天
测试和完善 3 天
二. 策划大纲
1.游戏的定位
2.游戏背景
3.游戏内容
4.游戏策划
1)开始界面
主菜单新的游戏旧的进度游戏说明游戏设置游戏积分关于游戏
点击开始打开游戏的主菜单
游戏的公司Logo界面进入后,直接弹出开始界面和主菜单。
新的游戏:
旧的进度:
游戏说明:
游戏设置:
游戏积分:
关于游戏:
2)游戏背景介绍
打开新的游戏,即可进入游戏的背景说明…..
3)选择游戏的难度
普通级
恶梦级
地狱级
4)主游戏界面(2*倍平面地图)
5. 游戏参数设定
6. 游戏效果草图
期望45度倾斜视角立体视图的效果
7. 游戏的风格,属性和视觉的感受
8. 游戏特色的解说(商业卖点)
三.预计开发进度
1. demo版本发布时间
天 内(可以出一个在 机器上的演示版本(部分功能不全))
2.正式版本发布时间
天 内(可以出一个在 机器上的正式版本)

3. 商业化版本发布时间
天 内(可以出一个在 机器上test通过并修正bug且商业化)
四.开发需求设定
1. 美工需求
美工的风格需要带有强烈的卡通色彩
1)场景:
2)物品:
3)动画:
4)人物:
5)界面
6)按钮
2. 音效需求
背景音乐
碰撞音乐
打斗音乐
提示音乐
过关音乐

3. 开发团队所负责的成员
项目负责人员:
策划人员:
美工人员:
程序人员:
音乐制作人员:
测试人员:
4.项目的基本的开发进度
五.游戏性能测试
1.图像率新速度
2.文件安装完大小
3.文件未安装大小
4.游戏所占有的内存
5.游戏所带的文件清单


2005年11月07日
作者:张桂权
 
随着Java技术的成熟以及广泛的应用,JME(Java Micro Edition)自然也成了一个流行的手机或者手持设备程序开发的工具了。相信国内一定有像我一样的JME技术的爱好者。但是,由于国内缺乏相关的技术资源(还是不少,但是都是CopyRight了,所以我们还好无能为力了),以下是我喜欢的几个有关JME技术的网站,希望对你有所帮组,无论你是JME技术爱好者,还是正在从事相关开发的朋友还是即将起步学习的学弟学妹。

1、J2ME Gamer
http://www.j2megamer.com
这是一格很有商业价值的有关JME技术的网站,在这里你还可以学到许多JME手机游戏开发的知识。这是学习JME 游戏开发的首选网站。学习资源真的很多哦。

2、J2ME.org
http://www.j2me.org
从名字就可以看出这是一个
是针对J2ME的各个方面内容讨论论坛,绝不局限于2ME游戏开发。

3、GameDev.net
http://www.gamedev.net
这不是一个专业的游戏开发网站,也不是JME技术论坛,而是一个内容丰富多彩的网站。因为在这里你能了解到好多游戏设计之外的(诸如,只为招聘等)信息,还有技术文章的评论等等。

4、Gamasutra
http://www.gamasutra.com
这是号称“游戏制造的艺术和科学”的网站。上面有许多关于游戏开发精彩文章,还有新闻、特色文章、游戏培训等方面的信息。

5、Game Developer Magazine
http://www.gdmag.com
这是美国著名的游戏开发杂志《Game Developer Magazine》的官方网站,其中也有一些精彩的技术性文章可以学习参考。还告诉你一个好消息gamedeveloper还是《程序员》杂志社的合作伙伴呢。今年他们联合推出了一本不错的游戏开发杂志《游戏创造》。感觉还不错哦,因为国内一直都没有相关的书籍。有机会可以拿来看看,说不定还会给你一个惊喜呢!

6、JavaWorld
http://www.javaworld.com
javaworld顾名思义就是“Java的世界”,这儿自然有好多Java技术文章了,其中有一部分是JME的。很不错的,有些文章还特别有意思的,有空来看看。

7、Java.sun.com
http://java.sun.com
最后该说说SUN了。SUN的游动业务是基于Java的但是他在开发工具方面做得不够好所以有点逊色哦。不过提到Java、JME、JEE等一切与JAVA有关的东西多不得不提他哦,毕竟是他推出Java这么优秀的程序设计语言/或者说技术。Java之父还在他这儿效劳呢 !这儿有不少的技术文章,够你看了 !

至今,我看看到较好的就者们几个网站了。以后找到的时候再追加 !赶快去学习,有好东东等你拿呢!


编写灵活的RMS应用

MIDP应用程序的标准持久化方案就是使用RMS。RMS类似于一个小型数据库,RecordStore相当于数据库的表,每个“表”由若干记录(Record)构成,一条记录就是一个用int表示的记录号RecordID和用byte[]表示的内容。记录号可以看作是“主键”,byte[]数组存储内容。

RMS提供的记录操作可以实现根据ID直接获得记录,或者枚举出一个表中的所有记录。

枚举记录是非常低效的,因为只能比较byte[]数据来确定该记录是否是所需的记录。通过ID获得记录是高效而方便的,类似于SQL语句“SELECT byteArrayData FROM recordStoreName WHERE RecordID=?”。然而,通常应用程序很难知道某条记录的ID号,而RMS记录的“主键”又仅限于int类型,无法使用其他类型如String作为“主键”来查找。因此,对于需要存取不同类型对象的应用程序而言,就需要一个灵活的RMS操作框架。

我们的基本设想是,如果能使用String作为“主键”来查找记录,就能非常方便地获得所需的内容。例如,应用程序设置可以通过"sys.settings"获得byte[]数组,并依次读取出设置,用户登录信息可以通过"user.info"获得byte[]数组,再分解出用户名和口令。

因此,我们实现一个StorageHandler类,提供唯一的RMS访问接口,使得其他类完全不必考虑底层的RMS操作,只需提供能标识自身的一个String即可。

如果我们能实现一种类似于数据库索引的查找表,就能根据String关键字查找某条记录。因此,我们使用一个名为"index"的RecordStore来存储所有的索引,每一条索引都指向某一条具体记录的ID,设计一个IndexEntry表示一条索引:

class IndexEntry {
    private int selfId;   // IndexEntry的ID
    private int recordId; // 对应记录的ID
    private String key;   // 访问记录的Key
}

根据索引查找,分3步进行:

1.在名为"index"的RecordStore中根据String查找对应的IndexEntry。
2.取出IndexEntry,获得记录ID号。
3.根据ID号获得另一个RecordStore的记录,然后就可以读取、更新和删除该记录。

如下图所示:

由于IndexEntry保存的数据很少,为了加快查找速度,可以在应用程序启动时,把所有的IndexEntry读入一个Vector,在后面的操作中更新这个Vector并与RecordStore保持同步。

为了处理不同类型的数据,所有可通过StorageHandler存取的类都必须实现一个Storable接口:

public interface Storable {
    String getKey();
    void getData(DataOutputStream output) throws IOException;
    void setData(DataInputStream input) throws IOException;
}

前面已经提到,在MIDP应用程序中,序列化一个类的最佳方法是使用DataInputStream和DataOutputStream。因此,需要持久化的类可以通过getData()和setData()方法非常方便地存取。假定应用程序的类UserInfo保存了用户的登录名、口令和是否自动登录的信息:

public class UserInfo {
    String username;
    String password;
    boolean autoLogin;
}

为了能将UserInfo存入RMS,需要实现Storable接口:

class UserInfo implements Storable {
    String username;
    String password;
    boolean autoLogin;
    public String getKey() { return "user.info"; } // 提供一个唯一标识符即可
    public void getData(DataOutputStream output) throws IOException {
        output.writeUTF(username);
        output.writeUTF(password);
        output.writeBoolean(autoLogin);
    }
    public void setData(DataInputStream input) throws IOException {
        username = input.readUTF();
        password = input.readUTF();
        autoLogin = input.readBoolean();
    }
    // getters here…
}

要保存UserInfo,只需调用StorageHandler的保存方法:

StorageHandler.storeOrUpdate(userinfo);

要读取UserInfo,调用StorageHandler的读取方法:

UserInfo userinfo = new UserInfo();
StorageHandler.load(userinfo);

这样,需要读取或保存数据的类完全不必涉及底层的RMS操作,大大简化了应用程序的设计,增强了源代码的可复用性与可维护性。


编写反应灵敏的联网提示界面

由于无线设备所能支持的网络协议非常有限,仅限于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; }
}

使用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即可复用上述代码,无须关心如何连接网络以及如何处理用户中断连接。

使用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<props.length; i++) {
    sb = sb.append("–");
    sb = sb.append(BOUNDARY);
    sb = sb.append("\r\n");
    sb = sb.append("Content-Disposition: form-data; name=\""+ props[i] + "\"\r\n\r\n");
    sb = sb.append(URLEncoder.encode(values[i]));
    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…

使用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信息,因此,不推荐使用这种方法。

屏幕导航

除了游戏程序,在通常的MIDP应用程序中,通常会有很多个Screen或Canvas,这些屏幕一般靠命令来实现切换,比如用户点击“Next”应该跳到下一屏,点击“Back”应该返回到上一屏。当屏幕数量相当可观时,如何在各个屏幕之间导航就值得好好考虑了。

经典的MVC模式可用于屏幕导航,Model用于存储应用程序数据,而View则是各个Displayable对象,Controller需要单独的一个类实现。由于MIDlet类本身在生命周期内就只有一个实例,因此MIDlet类就非常适合作为Controller。SUN在blueprints示例程序SmartTicket中应用了非常复杂的MVC,完全可以满足MIDP应用程序的导航需要,但是可以看出,缺点是很明显的:

一是每一个事件都需要一个唯一标识,switch-case语句会随着屏幕的增加而增加,Controller变得难以维护。二是Controller引用了所有的View,这些View在程序启动时就被初始化导致很大的内存开销,而不管它们是否会被显示。三是大量的Model对象以及异常处理都使得整个应用程序的逻辑大大复杂。

实际上,MIDP应用程序的很多屏幕并不需要复杂的Controller和Model,我们的目标是满足基本的灵活性的同时保持结构简单。因此,另外两种导航方法是用二叉树和堆栈实现,这里我们只讨论用堆栈实现的MIDP导航框架,其基本思想是:每当前进到下一个屏幕时,先将下一个屏幕压栈,然后再显示;当返回到上一个屏幕时,先从堆栈中弹出当前屏幕,再从堆栈中取出上一个屏幕并显示。因此,每个屏幕只需要指定要显示的下一个屏幕,而不需记住上一个屏幕。这种堆栈导航模型特别适合有规律的“前进”、“后退”屏幕。

由于MIDlet类运行期只有一个实例,因此,使用MIDlet类作为控制器相当合适。此外,我们在一个静态变量中保存了MIDlet实例,使得访问MIDlet更加方便:

public class ControllerMIDlet extends MIDlet {
    private static ControllerMIDlet instance = null;

    private Display display = null;
    private Stack ui = new Stack();

    public ControllerMIDlet() { instance = this; }

    protected void startApp() {}
    protected void pauseApp() {}
    protected void destroyApp(boolean unconditional) {}

    public static void goBack() {
        instance.ui.pop();
        Object obj = instance.ui.peek();
        instance.display.setCurrent((Displayable)obj);
    }

    public static void forward(Displayable next) {
        instance.ui.push(next);
        instance.display.setCurrent(next);
    }
}

让我们更详细地研究一下实际的应用程序可能出现的几种屏幕跳转情况。最简单的情况是,从一个屏幕前进到另一个屏幕,且返回时仍回到原先的屏幕,这种情况完全符合堆栈的FIFO特点,可以直接调用ControllerMIDlet的forward和goBack方法即可。例如,要显示一个帮助屏幕:

对于一个联网的应用程序,另一种情况是有一个暂时的等待屏幕。下面是一个在线浏览图片的屏幕:

与上面的情况所不同的是,如果用户在屏幕3选择“返回”,则应当回到屏幕1而不是屏幕2,因此,对于屏幕2到屏幕3的切换,就不能forward,我们使用replace,抛弃屏幕2,从而实现屏幕3直接可以goBack到屏幕1:

public static void replace(Displayable next) {
    instance.ui.pop();
    instance.ui.push(next);
    instance.display.setCurrent(next);
}

堆栈的变化如下:

对于某些更为复杂的情况,例如,登录过程,如果允许用户选择自动登录,则屏幕跳转如下:

如果用户不选择自动登录,则屏幕跳转如下:

对于这种情况,解决方案是,即使用户选择了自动登录,LoginUI屏幕也要被压入堆栈中,但是不显示出来,因此,我们定义了另一个forward(Displayable d1, Displayable d2)方法,它将d1和d2依次压入堆栈,但只显示d2。在返回时,如果用户取消,则返回到LoginUI。总之,通过定义多个导航方法,就可以实现各种操作。

这种基于堆栈的导航模型非常适用于有规律的“前进”,“后退”屏幕,而且只在需要的时候生成新的屏幕。无需关心屏幕状态,因为返回时上一个屏幕的状态被完整地保存在堆栈中。

堆栈模型的缺点是数据由不同的屏幕处理,对于一些流程而言,可能需要将每个屏幕的数据依次传递给下一个屏幕,越往后的屏幕其构造方法的参数可能也越多。

对于联网操作等涉及到多线程等待屏幕的情况,我们将在后面给出一个完整的解决方案,并集成到堆栈导航框架中,使应用程序本身完全不用涉及到多线程联网操作,只需专注于自身逻辑。


获取设备支持的可选API

J2ME规范包括了许多可选包,如支持多媒体功能的MMAPI,支持消息接收和发送的WMA,支持3D游戏的M3G API。如果某一款手机支持某个可选API,MIDP应用程序就可以使用它。但是,让用户回答“本机是否支持MMAPI”是不友好的,发布几个不同版本不但增加了开发的工作量,也让用户难以选择。因此,应用程序应该自己检测手机是否支持某一API,从而在运行期决定是否可以使用此API。

MIDP 1.0和2.0应用程序都可以通过System.getProperty(String key)检测某一个属性的信息。如果该属性有效,将返回对应的字符串,否则,返回null,表示系统不支持此功能。

例如,System.getProperty("microedition.profiles")可能的返回值是"MIDP-1.0"或"MIDP-2.0"。

以下是常见的系统属性和可选API的属性,右侧列出了可能的返回值:

系统信息

 

 

 

microedition.platform

平台名称,如j2me

microedition.configuration

CLDCCDC版本,如CLDC-1.0

microedition.profiles

MIDP版本,如MIDP-1.0

microedition.encoding

默认的系统编码,如GBK

microedition.locale

默认的区域设置,如zh-CN

MMAPI相关

 

 

 

microedition.media.version

MMAPI的版本,如1.1

supports.mixing

是否支持混音,如true

supports.audio.capture

是否支持音频捕获,如true

supports.video.capture

是否支持视频捕获,如true

supports.recording

是否支持录音,如true

audio.encodings

音频编码格式,如encoding=pcm encoding=pcm&rate=8000&bits=8&channels=1

video.snapshot.encodings

拍摄图片的编码格式,如encoding=jpeg encoding=png

streamable.contents

支持的流媒体格式,如audio/x-wav

WMA相关

 

 

 

wireless.messaging.sms.smsc

返回SMS的服务中心,如+8613800010000

wireless.messaging.mms.mmsc

返回MMS的服务中心,如http://mmsc.monternet.com

其他

 

 

 

microedition.m3g.version

返回Mobile 3D的版本,如1.0

bluetooth.api.version

返回蓝牙API的版本,如1.0

microedition.io.file.FileConnection.version

返回FileConnection的版本,如1.0

microedition.pim.version

返回PIM的版本,如1.0

例如,如果用户的手机内置了数码相机,并且支持MMAPI,我们就可以在MIDP程序中拍摄照片。因此,在应用程序启动时就应该判断是否启用拍照功能以及用户手机支持的图片编码格式:

boolean supports_take_photo = false;
boolean supports_jpeg_encoding = false;
boolean supports_png_encoding = false;
boolean supports_gif_encoding = false;
if(System.getProperty("microedition.media.version")!=null) {
    if("true".equals(System.getProperty("supports.video.capture")))
        supports_take_photo = true;
        String all_encoding = System.getProperty("video.snapshot.encodings");
        if(all_encoding!=null) {
            if(all_encoding.indexOf("jpeg")!=(-1))
                supports_jpeg_encoding = true;
            if(all_encoding.indexOf("png")!=(-1))
                supports_png_encoding = true;
            if(all_encoding.indexOf("gif")!=(-1))
                supports_gif_encoding = true;
        }
    }
}

概述

J2ME是Sun发布的运行在小型设备上的微型版Java的一系列标准,其中,最重要的标准便是运行在手机上的MIDP应用程序了。到目前为止,MIDP一共发布了两个版本:MIDP 1.0(JSR37)和MIDP 2.0(JSR118),2.0版本可以向后兼容1.0版本,也就是说,支持MIDP 2.0的手机可以同时运行MIDP 1.0和MIDP 2.0的应用程序。本文将重点讲述开发MIDP应用程序时非常有用的一些设计模式,开发技巧以及如何调试、优化J2ME应用程序。

本文将讨论J2ME开发的以下内容:

  • 如何自动适应用户手机配置
  • 如何在屏幕间导航
  • 如何实现一个灵活的联网应用
  • 如何实现一个灵活的RMS应用
  • 如何调试并优化J2ME程序

避免OutOfMemoryError

对于MIDP应用程序来说,由于手机设备上的资源非常有限,较弱的CPU计算能力,有限的内存(从几十KB到几百KB,虽然少数高端手机拥有超过1M的动态内存),很小的屏幕尺寸,因此,为了让一个MIDP应用程序能够不加改动地在多种不同手机上运行,程序必须有能力根据系统配置自动调整运行时的参数。比如,对于内存非常小的手机,如果从网络下载一幅较大的图像,需要分配巨大的缓冲区,就可能导致OutOfMemoryError错误,使应用程序直接终止,这会使用户感到不知所措,或者丢失用户的重要数据。
因此,在试图分配一块大内存之前,首先使用System.gc()尝试让垃圾收集器释放无用对象占用的内存,然后,使用Runtime.getRuntime().freeMemory()方法获得可用的内存空间。如果可用空间太小,给用户一个“内存不足,无法完成操作”的Alert提示,从而尽可能地避免OutOfMemoryError错误。

// 示例代码:
System.gc();
int max_size = 102400; // 100KB
int free_size = (int)Runtime.getRuntime().freeMemory();
if(max_size>free_size*2/3) {
    // TODO: Alert!
}
else {
    byte[] buffer = new byte[max_size];
    // TODO: Download image…
}

减少图片以减小JAR文件大小

许多手机会因为JAR文件太大而无法运行MIDP应用程序,而减小JAR文件尺寸的有效方法之一是减少不必要的图片,例如,启动时的LOGO图片可以用文字来代替,列表项可以只显示文字而不显示图片。为了能适应不同配置的手机,我们的代码就应该编写得更加灵活。例如,从JAR包中加载图片时:

Image image = null;
try {
    image = Image.createImage("/logo.png");
}
catch(Exception ioe) {}
if(image==null) {
    g.setColor(0);
    g.drawString("info", getWidth()/2, getHeight()/2, Graphics.HCENTER|Graphics.BASELINE);
}
else {
    g.drawImage(image, getWidth()/2, getHeight()/2, Graphics.HCENTER|Graphics.VCENTER);
}

如果加载失败,程序会以文字方式显示,这样,对于低配置的手机,只需要把美化界面的图片删除掉,再重新打包即可得到一个可发布的尺寸较小的JAR包,同时应用程序的代码并没有改动。

类似的,在加载List之类的UI组件时:

Image image = null;
try {
    image = Image.createImage("/logo.png");
}
catch(Exception ioe) {}
append("label", image);

这使得有无图片仅仅影响界面美观,而不影响应用程序的功能。