Look into it ~

present
随笔 - 32, 文章 - 0, 评论 - 3, 引用 - 0
数据加载中……

MIDP2.0及MIDP数字签名

本文档是 WoTrust 根据 Forum Nokia 提供的技术文档《MIDP 2.0: Tutorial On Signed MIDlets》翻译整理的,请同时参考此英文原文文档。请用户在编写 MIDlet 和签名 MIdlet 之前阅读此文档,以便对 MIDP2.0 的安全机制有一个深刻的理解,有助于用户能用好 MIDlet 代码签名证书。

一、概述

MIDP2.0 采用了全新的安全机制,这对于需要调用一个敏感的(重要的)函数和 API 的 MIDlet 开发者来讲是必须了解的,如:网络连接 API 、消息 API 和推 (Push) 函数等,还有一些可选的 MIDP 包也有许多受限制的 API 。

虽然购买代码签名证书需要费 用,但签名 MIDlet 对开发者来讲是收益非浅的,因为许多受保护的 API 都是需要签名的,以保护开发者和用户的利益。当然,有些应用是不需要签名的,如有些不需要联网的仅用到一些图形 API 的小游戏软件。但一些重要的应用,如:连接网络、发送短消息 ( 短信和彩信 ) 或访问移动终端 ( 智能手机、 PDA 等,以下简称为手机 ) 上的 PIM( 个人信息管理 ) 数据等等都需要签名。

数字签名 MIDlet 的好处包括:

(1) 基于 MIDlet 的安全策略,某些功能是必须签名才能使用的,而有些功能虽然不签名也可以使用,但必须要求用户在使用时确认和修改其安全策略,如:写用户数据缺省是不允许没有签名的 MIDlet 操作的;

(2) 基于手机的系统安全和移动网络的安全考虑,某些手机制造商、移动运营商等可能拒绝没有签名的 MIDlet 在手机上安装和运行;

(3) 大大改善用户体验,让用户使用方便,使得用户不会遭遇调用受保护 API 时的安全警告的烦恼;

(4) 出于安全考虑,安装没有签名的 MIDlet 是会有安全警告的,而相反,安装已经签名的 MIDlet 则不会出现烦人的警告,手机会自动验证签名而顺利地安装成功;

(5) 已经签名的 MIDlet 将使得用户能改善其低安全策略设置,提高手机的安全性;

(6) 确保已经签名的 MIDlet 不会被非法篡改和非法盗用。

二、 MIDP 2.0 安全机制

MIDP 是一个开放的平台,使得任何人都可以为支持 MIDP 的设备开发各种应用软件,一般都是移动终端设备。 MIDlet 套件可以以匿名方式通过网络下载,非常方便,但这也会带来许多安全问题和隐私信息保护问题,用户会问: MIDlet 能把用户的个人信息发给不知道的服务器吗?会自动产生没有授权的呼叫或短消息而给用户带来费用吗?恶意软件会破坏手机?等等。

除了 Java 语言的安全特性外, MIDP 还增加了许多安全考虑。 MIDP 2.0 比 MIDP 1.0 增强了安全策略,把 API 分为普通 API 和敏感 API ,如:通过 HTTP 协议访问移动网络,由于会给用户产生费用, 所以被列为 敏感 API 。 MIDlet 2.0 推出了可信任 MIDlet(trusted) 和不可信任 MIDlet(untrusted) 的概念,一个不可信任 MIDlet 只能访问有限的 API ,同时还需要用户手动确认并修改其安全策略;而可信任 MIDlet 则自动继承系统中的安全策略而获得访问许可。

许可 (Permissions) 用于需要身份认证的 敏感 API 。 MIDP 2.0 要求调用 敏感 API 之前必须获得必要的许可,这些许可包的命名同 J2SE 许可,如: HTTP 连接许可同样称为: javax.microedition.io.Connector.http 。 有关许可的文档同意归类在受保护 API 中。

2.1 Protection Domains( 保护域 )

保护域是 MIDP 2.0 中一个非常重要的安全概念,一个保护域就是一个许可集和一种交互模式,这些许可既可以是自己继承的,也可能是用户设置的,前者称为允许 (allowed) ,而后者称为用户允许 (user permission) 。当一个 MIDlet 被安装后,它被分配到一个指定的保护域而获得它的许可和交互模式。

而用户允许则需要用户自己决定是否同意,用户既拒绝一个许可,也可以同意。用户允许有 3 种交互模式: blanket( 普遍适用 ) 、 session( 短期适用 ) 和 oneshot( 本次适用 ) , 普遍适用 模式就是 MIDlet 安装时获得的许可一直有效,除非用户取消这些许可;而 短期适用 模式则是指第一次调用 API 时需要用户允许,有效期到此 MIDlet 套件运行结束;而 本次适用 模式则在每次调用 API 时都要求用户允许。保护域为用户许可定义了缺省的交互模式。

一个 MIDlet 套件使用 MIDlet-Permissions 和 MIDlet-Permissions-Opt 属性来明确地定义其许可,可以是在 JAD 文件中定义,也可以在 manifest 文件中定义。其中: MIDlet-Permissions 定义了 MIDlet 套件中必须具有的许可,而 MIDlet-Permissions-Opt 则定义希望具有的许可。如:一个应用软件的基本要求是要有 http 连接才能正常工作,同时,也可以使用 https 连接 ( 服务器部署了 SSL 证书 ) 来增强安全性,但不是必须的,这样,这个应用软件的应用描述可以是这样:

MIDlet-Permissions: javax.microedition.io.Connector.http

MIDlet-Permissions-Opt: javax.microedition.io.Connector.https

请注意:一个 MIDlet 所要求的许可必须是安装时分配的保护域所具有的许可的子集。如: Nokia S60 MIDP Emulator Prototype 2.0 (SDK) 有一个叫做“ minimum ”的域,此域没有任何许可。所以,如果一个含有许多许可的已经签名的 MIDlet 如果被安装到此域,则会安装失败,因为此域不支持这些许可。同样,如果一个许可的名称有拼写错误,则一样会导致安装失败,因为域中没有此拼写错误的许可。

MIDP 2.0 为 GSM/UTMS 设备定义了 4 种保护域: manufacturer( 设备制造商 ) , operator( 移动运营商 ) , trusted third party( 可信任的第三方 ) , and untrusted( 不受信任域 ) , 除了 untrusted 域外,每个保护域都对应一组根证书,用于签名 MIDlet 的签名证书的根证书必须包含在这些根证书中,使用不同的签名证书签名的 MIDlet 将被自动归类予根证书所属的保护域,根证书与保护域的关系是:一个保护域可以有许多个根证书,而一个根证书只能对应于一个保护域。

具体来讲, manufacturer 域属于设备制造商,其根证书是设备制造商自己的根证书;而 operator 域运营商,一般使用其 SIM 卡中的根证书;而 trusted third party 域则预置了全球知名的数字证书颁发机构 (CA) 的根证书,用于验证由 CA 颁发的 MIDlet 签名证书;而 untrusted 域没有根证书,将用于没有签名的 MIDlet 和 MIDP 1.0 。

Thawte 和 VeriSign 的根证书已经预置在 trusted third party 域 中,其 Java 代码签名证书可以用于签名 MIDlet 。当然,用户也可以选择使用设备制造商和移动运营商颁发的证书,只要其根证书已经包含在手机的 4 个保护域中。据 WoTrust 了解,大多数摩托罗拉 (Motorola) 手机只支持设备制造商域,所以,只能向 Motorola 申请签名服务了。

请注意:由于 MIDP 2.0 也在不断地修改和增补,所以,可能不用的移动网络运营商有不同的保护域和许可,用户可能需要向移动运营商了解详细信息。而最简单的方法是检查目标用户所使用的手机的根证书是否有计划购买的 MIDlet 签名证书的根证书。

2.2 Untrusted MIDlet ( 不受信任的 MIDlet)

MIDP 2.0 定义了那些 API 是 untrusted 的,这些 Jar 文件的来源和完整性是不能被手机验证的。但这并不意味着这些 MIDlet 不能被安装和运行,而是运行这些 MIDlet 需要用户人工确认允许。而所有 MIDP 1.0 的 MIDlets 都被定义为 untrusted 。

untrusted 的 MIDlets 只能调用一个不需要许可保护的 API ,如:
java.util
java.lang
java.io
javax.microedition.rms
javax.microedition.midlet
javax.microedition.lcdui
javax.microedition.lcdui.game
javax.microedition.media
javax.microedition.media.control

如果 untrusted MIDlet 套件试图调用一个被保护的 API 而且没有被人工允许,则会产生一个 SecurityException 而被 MIDlet 按照安全策略处理。请注意: Nokia 的 UI API 是不被保护的,包括类: com.nokia.mid.sound 和 com.nokia.mid.ui 。

2.3 Trusted MIDlets ( 可信任的 MIDlets)

如果手机能验证 MIDlet 的身份和完整性 ( 也就是已经数字签名 ) ,则会自动分配一个合适的保护 域这种 MIDlet 套件就称为可信任的 MIDlet 。一个可信任的 MIDlet 套件所要求的许可将被准许,只要所属的保护域拥有这种许可,假如许可: javax.microedition.io.Connector.http 已经在所属保护域中是允许的,则 MIDlet 在打开一个 http 连接时是不需要用户确认的。

请不要混淆了可信任的 MIDlet 套件和可信任的保护域的不同,每个可信任的 MIDlet 套件依据安全策略被分配到一个特定的保护域。

您需要使用一个手机中已经预置的根证书的证书颁发机构颁发的代码签名证书来签名 MIDlet ,否则将不能通过身份验证。成功签名后的 JAD 文件中一定会包含有整个签名证书的证书链,属性名称为: MIDlet-Certificate-1-1 就是您的签名证书,而 MIDlet-Certificate-1-2 就是 CA 的中级根证书,而 MIDlet-Certificate-1-3 就是 CA 的顶级根证书。同时还会有一个 MIDlet-Jar-RSA-SHA1 属性就是 JAR 文件的摘要。

当一个 MIDlet 被下载或被安装时, MIDlet 应用管理器首先会检查 JAD 文件中是否包含了 MIDlet-Jar-RSA-SHA1 属 性,如果有,则启动如下验证过程:首先会读出 MIDlet-Certificate-1-1 、 MIDlet-Certificate-1-2 和 MIDlet-Certificate-1-3 属性中的证书,并与已经预置的根证书相比较,如果证书链能被根证书验证,则表明开发者身份已经被验证。接着就会使用用户证书来解密 MIDlet-Jar-RSA-SHA1 属 性的摘要,再计算出已经下载的 Jar 文件的摘要,比较两个摘要是否相等,如果相等,则表明 MIDlet 代码自签名后没有被修改。这样,既验证了身份又检查了完整性的 MIDlet 会被分配到所属根证书所对应的保护域中。但是,如果 MIDlet 中的许可属性 ( MIDlet-Permissions ) 中有一个或多个不属于所属的保护域,则仍然不允许安装。而如果 MIDlet 中的可选许可属性 ( MIDlet-Permissions-Opt ) 中有一个或多个不属于所属的保护域,会允许安装。可见,正确设置许可属性和可选许可属性非常重要。

2.4 Function Groups ( 功能分组 )

为了简化用户管理操作, MIDlet 把一些类似功能分组,这样,用户只需对功能组设置许可即可。如:许可 “Net Access”( 网络访问 ) 组来代替许可 javax.microedition.io.Connector.http ,这对于简化手机的交互操作非常有用。

MIDP 2.0 和 JTWI 定义了如下 7 个功能组:

(1) Net Access: 包括所有网络连接许可;

(2) Messaging: 包括所有与发送和接收短消息 ( 短信和彩信 等 ) 相关的许可;

(3) Auto Invocation : 包括与自动启动 MIDlet 相关的许可,如: Push Registration

(4) Local Connectivity : 包括与本地连接相关的许可,如: IrDA 或 蓝牙;

(5) Multimedia Recording : 包括与允许录音、照相、摄像等相关的许可;

(6) Read User Data : 包括读取用户数据相关的许可,如:通讯录、日程表等;

(7) Write User Data : 包括写用户数据相关的许可。

不同的手机支持不同的功能组,如: Multimedia Recording 就不会包含在没有摄录装置的手机中。当然,也有可能将来会增加更多的功能组。

功能组也同时定义了不同的域的不同交互方式,如:在不信任域, “Net Access” ( 网络访问 ) 被设置为 session( 短期适用 ) 或 denied( 拒绝 ) ,而在可信任域则可以设置为 oneshot 、 blanket 和 denied 的。

三、仿真器和手机的缺省安全设置

让我们来看看具体的使用 Thawte 或 VeriSign 代码签名证书签名后的 MIDlet 在 trusted third party 域中的所有缺省许可,如下图 1 所示,点击 NDS 3.0 的“ Config Emulators ”就可以看到仿真器在 trusted third party 域的缺省安全设置是“ Ask first time ”,即第 1 次使用是需要确认:


如下图 2 所示,您可以下拉所有功能组的许可设置,如“ Network Access ”就有 4 个选项可以修改: Ask first time 、 Ask every time 、 Always allowed 和 Not allowed :


而如下图 3 所示,在“ Real Life ”模式,也就是实际手机的运行模式,可以看出:定义的 7 个功能组都是“ Always allowed ” ( 总是允许 ) ,这就显示出 MIDlet 签名对于开发商来讲是多么的重要,将大大方便了用户的使用,再也不需要用户操作烦人的系列确认了。

posted @ 2008-08-15 14:53 LukeW 阅读(311) | 评论 (0)编辑 收藏

树形结构

树形结构(tree)是比较常用的数据结构了,MIDP中没有它的身影,不然我就不用写这篇文章了。
代码如下:
/**
 *
 * 
@author hunhun1981
 
*/
public class HTree {
 
 
private HNode root;
 
 
private HNode current;
 
 
private int currDepth;
 
 
private int maxDepth;
 
 
public HTree(Object rootValue) {
  root 
= new HNode(null, rootValue);
  current 
= root;
 }
 
 
public void goRoot() {
  current 
= root;
  currDepth 
= 0;
 }
 
 
public boolean goChild(int index) {
  
if (current.childList != null) {
   
if (current.childList.size() > 0
     
&& index < current.childList.size()) {
    current 
= (HNode) current.childList.elementAt(index);
    currDepth
++;
    
if (currDepth > maxDepth) {
     maxDepth 
= currDepth;
    }
    
return true;
   }
  }
  
return false;
 }
 
 
public void goBack() {
  
if (current.father != null) {
   current 
= current.father;
   currDepth–;
  }
 }
 
 
public Object getCurrent() {
  
return current.value;
 }
 
 
public int getCurrentDepth() {
  
return currDepth;
 }
 
 
public int getMaxDepth() {
  
return maxDepth;
 }
 
 
public Object[] getChilds() {
  
if (current.childList != null) {
   
if (current.childList.size() > 0) {
    Object[] ret 
= new Object[current.childList.size()];
    
for (int i = 0; i < ret.length; i++) {
     ret[i] 
= ((HNode) current.childList.elementAt(i)).value;
    }
    
return ret;
   }
  }
  
return null;
 }
 
 
public Object getChild(int index) {
  
if (current.childList != null) {
   
if (current.childList.size() > 0
     
&& index < current.childList.size()) {
    
return ((HNode) current.childList.elementAt(index)).value;
   }
  }
  
return null;
 }
 
 
public void addChild(Object obj) {
  
if (current.childList == null) {
   current.childList 
= new Vector();
  }
  current.childList.addElement(
new HNode(current, obj));
 }
 
 
public void addChilds(Object[] objs) {
  
if (current.childList == null) {
   current.childList 
= new Vector();
  }
  
for (int i = 0; i < objs.length; i++) {
   current.childList.addElement(
new HNode(current, objs[i]));
  }
 }
 
 
public int hasChild() {
  
if (current.childList == null || current.childList.size() <= 0) {
   
return 0;
  } 
else {
   
return current.childList.size();
  }
 }
 
 
private class HNode {
 
  
public Vector childList;
 
  
public HNode father;
 
  
public Object value;
 
  
public HNode(HNode father, Object value) {
   
this.value = value;
   
this.father = father;
   
this.childList = null;
  }
 }
}


这个类实现简单,没有包含复杂的功能,仅仅用来做树形数据的存储还是不错的。比如游戏中用来管理场景,管理资源;应用中用来作分类数据的表现等等。完全足以胜任。
使用方法如下:
HTree tree = new HTree(”root”);//会自动创建一个默认的根节点
tree.addChild(”天才”);//在根节点添加新的节点
tree.addChild(”白痴”);
tree.goChild(
0);//进入到当前节点的第一个节点(天才)。
tree.addChild(”天才1号”);//在当前节点(天才)添加新的节点
tree.addChild(”天才2号”);
tree.goBack();
//返回当前节点(天才)的父节点(根)
tree.goChild(1);//进入到当前节点的第二个节点(白痴)。
tree.addChild(”白痴1号”);//在当前节点(白痴)添加新的节点
tree.addChild(”白痴2号”);
tree.goRoot();
//完成创建后将当前节点设置为根节点。

上面的代码创建了一棵完整的树,当然,您可以使用任何对象代替这里存储的String对象。
还有一些方法,一看函数名大概都能明白,就不再唠叨了。
遍历的方法于上面创建树的方法相似,总之,要注意当前节点的位置,以免下次使用时处在错误的位置。
有兴趣的朋友可以扩展一下遍历方法。不过我觉得没必要。因为J2ME环境下更需要的是树形结构,而不是强大的tree对象。

总之,我比较倾向于简单实现,希望它不太让人觉得简陋就好。从实用出发,它还是能够满足大部分受限平台的需求的。

posted @ 2008-08-15 14:51 LukeW 阅读(187) | 评论 (0)编辑 收藏

URLEncoding

URLEncoding是用于解决链接字符串中包含中文字符的一种转换编码。各种编程环境下几乎带有它的库函数。

不过,J2ME除外。

好在JAVA的源代码中带有这个类,我们把它拷贝到J2ME环境下编译到我们的应用当中就可以了。

该文件位于JDK的目录下src.zip文件中,名叫URLEncoder.java。

但是,这个文件还需要做很多修改才能使用在J2ME环境中。

先警告大家,有几个真机(其中一个就是索爱的,好像是k500c),不管输入什么样的Encodeing都会出错,甚至是“UTF-8”。所以我一怒之下 去除了Encodeing参数。(这可是在实际应用中得出的结论,不去掉的话可以在大部分情况下正常使用,但是,现实总是有点缺陷)

修改后的代码如下,大家请放心使用。如果有兴趣,可以比较两个代码,看看我改动了什么地方。


import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
public class HURLEncoder {
 
 
private static boolean[] dontNeedEncoding;
 
 
static {
  dontNeedEncoding 
= new boolean[256];
 
  
for (int i = 0; i < 256; i++) {
   
boolean b = ((i >= ‘0′) && (i <= ‘9′))
     
|| ((i >= ‘A’) && (i <= ‘Z’)) || ((i >= ‘a’) && (i <= ‘z’));
 
   dontNeedEncoding[i] 
= b;
  }
 
  dontNeedEncoding[’ ‘] 
= true;
  dontNeedEncoding[’
-'] = true;
  dontNeedEncoding[’_'] = true;
  dontNeedEncoding[’.'] = true;
  dontNeedEncoding[’*'] = true;
 }
 
 
public static String encode(String s) {
 
  
boolean wroteUnencodedChar = false;
 
  StringBuffer writer 
= new StringBuffer();
 
  StringBuffer out 
= new StringBuffer(s.length());
 
  
for (int i = 0; i < s.length(); i++) {
   
char c = s.charAt(i);
 
   
if ((c < 256&& dontNeedEncoding[c]) {
    
if (c == ‘ ‘) {
     c 
= ‘+’;
    }
 
    out.append((
char) c);
    wroteUnencodedChar 
= true;
   } 
else {
    
try {
     
if (wroteUnencodedChar) {
      writer 
= new StringBuffer();
      wroteUnencodedChar 
= false;
     }
 
     writer.append(c);
 
     
if (c >= 0xD800 && c <= 0xDBFF) {
      
if ((i + 1< s.length()) {
       
int d = (int) (s.charAt(i + 1));
 
       
if (d >= 0xDC00 && d <= 0xDFFF) {
        writer.append(d);
        i
++;
       }
      }
     }
 
    } 
catch (Exception e) {
     writer 
= new StringBuffer();
     
continue;
    }
 
    String str 
= writer.toString();
 
    ByteArrayOutputStream baos 
= new ByteArrayOutputStream();
    DataOutputStream dos 
= new DataOutputStream(baos);
    
try {
     dos.writeUTF(str);
     dos.flush();
    } 
catch (Exception e) {
     e.printStackTrace();
    }
 
    
byte[] temp = baos.toByteArray();
    
byte[] ba = new byte[temp.length - 2];
    
for (int ix = 0; ix < ba.length; ix++) {
     ba[ix] 
= temp[ix + 2];
    }
 
    
for (int j = 0; j < ba.length; j++) {
     out.append(’
%');
 
     
char ch = forDigit((ba[j] >> 4& 0xF16);
     out.append(ch);
 
     ch 
= forDigit(ba[j] & 0xF16);
     out.append(ch);
    }
 
    writer 
= new StringBuffer();
    
try {
     dos.close();
     baos.close();
    } 
catch (Exception e) {
     e.printStackTrace();
    }
   }
  }
 
  
return out.toString();
 }
 
 
private static char forDigit(int digit, int radix) {
  
if ((digit >= radix) || (digit < 0)) {
   
return ‘0′;
  }
  
if (digit < 10) {
   
return (char) (’0′ + digit);
  }
  
return (char) (’A’ + digit - 10);
 }


posted @ 2008-08-15 14:49 LukeW 阅读(1497) | 评论 (0)编辑 收藏

GB2312转换为UTF-8

摩托罗拉的部分手机(a1200,e60等),不支持gb2312编码。曾经给我造成了不少麻烦。现在,大家可以分享解决这个问题的一些经验。

关于gb2312,unicode,utf-8的一些资料,大家请自行搜索。一下列举几个比较好的资源网址。
http://baike.baidu.com/view/25492.htm
http://www.utf.com.cn/article/s45
http://www.utf.com.cn/article/s74
http://www.haiyan.com/steelk/navigator/ref/gb2312/gbindex.htm

要点:
1,gb2312于unicode或者utf-8之间并不存在直接的映射关系。所以我们只能通过查表法来进行转换。
2,utf-8是unicode用于网络传输的一种形式,它与unicode之间是可以通过运算来进行转换的。
3,j2me环境使用的都是utf-8编码,但是请注意,j2me中的utf-8编码比较特殊,在整个编码前面对了两个字节,用于存放字符串的长度。

过程:
1,制作映射表gb2312-unicode,应为汉字的unicode比utf-8要小,这样做出的表也会小一些,而且对于unicode的可扩展性也强一些。
2,先将gb2312编码串通过查表,转换为unicode。
3,然后通过运算,将unicode转换为utf-8,以便在j2me环境下使用。

我修改了Herong Yang大侠的一个映射表生成函数,原文请参考http://www.herongyang.com/gb2312/gb2312_unicode.html
它的作用是生成一个二进制的gb2312到unicode的查找表,它按照gb2312的分区,分块特性,将其对应的unicode按顺序存入指定的位置。
这样我们只需要根据gb2312的编码,计算出索引就可以获取编码对应的unicode了。
由于是修改的代码,没脸贴出来,大家有需求可以直接参考Herong Yang的文章,然后根据自己需求修改并生成自己的映射表。

这里我把自己这个转换表文件以及访问代码公开。
http://download.csdn.net/source/263609
转帖请注明。这是个傻瓜化的代码,在java中给它gb2312的byte数组,它就给你构造出字符串。
用在不支持gb2312的手机上非常方便。这个转换表的大小是15228byte,对j2me来说还是可以接受的。

如果有朋友需要沟通,可以发邮件到hunhun1981@hotmail.com

import java.io.InputStream;
 
public class HGB2312 {
 
    
private byte[] map = new byte[15228];
 
    
private byte[] buffer;
    
private int index;
 
    
public HGB2312() throws Exception {
        InputStream is 
= getClass().getResourceAsStream("/gb2u.dat");
        is.read(map);
        is.close();
    }
 
    
public String gb2utf8(byte[] gb) throws Exception {
        buffer 
= new byte[gb.length + gb.length / 2 + 3];
        index 
= 0;
        
int c, h, l, ind;
        
for (int i = 0; i < gb.length;) {
            
if (gb[i] >= 0) {
                fillBuffer(gb[i
++]);
            } 
else {
                h 
= 256 + gb[i++];
                l 
= 256 + gb[i++];
                h 
= h - 0xA0 - 1;
                l 
= l - 0xA0 - 1;
                
if (h < 9) {
                    ind 
= (h * 94 + l) << 1;
                    c 
= (byte2Int(map[ind]) << 8 | byte2Int(map[ind + 1]));
                    fillBuffer(c);
                } 
else if (h >= 9 && h <= 14) {
                    fillBuffer(
0);
                } 
else if (h > 14) {
                    h 
-= 6;
                    ind 
= (h * 94 + l) << 1;
                    c 
= (byte2Int(map[ind]) << 8 | byte2Int(map[ind + 1]));
                    fillBuffer(c);
                } 
else {
                    fillBuffer(
0);
                }
            }
        }
        
// ind = index - 2;
        
// h = (byte) ((ind >> 8) & 0x7F);
        
// l = (byte) (ind & 0xFF);
        
// buffer[0] = h;
        
// buffer[1] = l;
 
        
return new String(buffer, 0, index, "UTF-8");
    }
 
    
private void fillBuffer(int value) {
        
if (value <= 0x0000007F) {
            buffer[index
++= (byte) value;
        } 
else if (value >= 0x00000080 && value <= 0x000007FF) {
            
byte b1 = (byte) (0x60 | (value >> 6));
            
byte b2 = (byte) (0x80 | (value & 0x3F));
            buffer[index
++= b1;
            buffer[index
++= b2;
        } 
else if (value >= 0x00000800 && value <= 0x0000FFFF) {
            
byte b1 = (byte) (0xE0 | (value >> 12));
            
byte b2 = (byte) (0x80 | ((value >> 6& 0x3F));
            
byte b3 = (byte) (0x80 | (value & 0x3F));
            buffer[index
++= b1;
            buffer[index
++= b2;
            buffer[index
++= b3;
        } 
else if (value >= 0x00010000 && value <= 0x001FFFFF) {
            
byte b1 = (byte) (0x1E | (value >> 18));
            
byte b2 = (byte) (0x80 | ((value >> 12& 0x3F));
            
byte b3 = (byte) (0x80 | ((value >> 6& 0x3F));
            
byte b4 = (byte) (0x80 | (value & 0x3F));
            buffer[index
++= b1;
            buffer[index
++= b2;
            buffer[index
++= b3;
            buffer[index
++= b4;
        } 
else if (value >= 0x00200000 && value <= 0x03FFFFFF) {
            
byte b1 = (byte) (0x3E | (value >> 24));
            
byte b2 = (byte) (0x80 | ((value >> 18& 0x3F));
            
byte b3 = (byte) (0x80 | ((value >> 12& 0x3F));
            
byte b4 = (byte) (0x80 | ((value >> 6& 0x3F));
            
byte b5 = (byte) (0x80 | (value & 0x3F));
            buffer[index
++= b1;
            buffer[index
++= b2;
            buffer[index
++= b3;
            buffer[index
++= b4;
            buffer[index
++= b5;
        } 
else if (value >= 0x04000000 && value <= 0x7FFFFFFF) {
            
byte b1 = (byte) (0x7E | (value >> 30));
            
byte b2 = (byte) (0x80 | ((value >> 24& 0x3F));
            
byte b3 = (byte) (0x80 | ((value >> 18& 0x3F));
            
byte b4 = (byte) (0x80 | ((value >> 12& 0x3F));
            
byte b5 = (byte) (0x80 | ((value >> 6& 0x3F));
            
byte b6 = (byte) (0x80 | (value & 0x3F));
            buffer[index
++= b1;
            buffer[index
++= b2;
            buffer[index
++= b3;
            buffer[index
++= b4;
            buffer[index
++= b5;
            buffer[index
++= b6;
        }
    }
 
    
private int byte2Int(byte b) {
        
if (b < 0) {
            
return 256 + b;
        } 
else {
            
return b;
        }
    }
}

posted @ 2008-08-15 14:47 LukeW 阅读(606) | 评论 (0)编辑 收藏

修改png图的调色板

今天在硬盘上挖出这个存放了几年的代码。又回忆起3年前的那个j2me手机游戏程序员……

这个算法是参考一位高人的文章,直接读取并修改png格式图片的调色板,然后生成新的调色板替代原来的。
这样可以实现游戏中常见的变色效果,可以解决游戏容量有限,不能存放太多精灵图片的问题。

具体过程其实并不复杂,大家可以先搜索资料,先看看png图片的格式定义。这个算法正是找到调色板区,根据原有格式修改之后,生成新的crc校验码,然后替换原来的调色板。这样就可以用一个png图片,创建多个变色副本。
public class PalettedImage {
 
    
public Image getPalettedImage(byte[] data, int[] originalColors,
            
int[] palettedColors) {
        
byte[] tempData = new byte[data.length];
        System.arraycopy(data, 
0, tempData, 0, data.length);
        Image img 
= null;
        
int[] parameter = { 000 };
        analyze(tempData, parameter);
        
for (int i = 0; i < originalColors.length; i++) {
            replaceColor(tempData, parameter, originalColors[i],
                    palettedColors[i]);
        }
        fillData(tempData, parameter);
        
try {
            img 
= Image.createImage(tempData, 0, data.length);
        } 
catch (Exception e) {
            System.out.println(
"getPalettedImage  &&  " + e.toString());
        }
        
return img;
    }
 
    
private void analyze(byte[] data, int[] para) {
        
int offset = 8;
        
int chunkLen = 0;
        
while (data[offset + 4!= 0x50 || data[offset + 5!= 0x4c
                
|| data[offset + 6!= 0x54 || data[offset + 7!= 0x45) {
            chunkLen 
= readInt(data, offset);
            offset 
+= (4 + 4 + chunkLen + 4);
        }
        chunkLen 
= readInt(data, offset);
        para[
2= chunkLen / 3;
        para[
0= offset + 8;
        para[
1= offset + 8 + chunkLen;
    }
 
    
private int readInt(byte[] data, int offset) {
        
return ((data[offset] & 0xFF<< 24)
                
| ((data[offset + 1& 0xFF<< 16)
                
| ((data[offset + 2& 0xFF<< 8| (data[offset + 3& 0xFF);
    }
 
    
private void replaceColor(byte[] data, int[] para, int oldColor,
            
int newColor) {
        
byte rr = (byte) ((oldColor >> 16& 0xff);
        
byte gg = (byte) ((oldColor >> 8& 0xff);
        
byte bb = (byte) (oldColor & 0xff);
        
for (int i = 0, offset = para[0], temp = 0; i < para[2]; i++, offset += 3) {
            
if (rr == data[offset] && gg == data[offset + 1]
                    
&& bb == data[offset + 2]) {
                data[offset] 
= (byte) ((newColor >> 16& 0xff);
                data[offset 
+ 1= (byte) ((newColor >> 8& 0xff);
                data[offset 
+ 2= (byte) (newColor & 0xff);
                
break;
            }
        }
    }
 
    
private void fillData(byte[] data, int[] para) {
        
int checksum = update_crc(data, para[0- 4, para[2* 3 + 4);
        data[para[
1]] = (byte) ((checksum >> 24& 0xff);
        data[para[
1+ 1= (byte) ((checksum >> 16& 0xff);
        data[para[
1+ 2= (byte) ((checksum >> 8& 0xff);
        data[para[
1+ 3= (byte) ((checksum) & 0xff);
    }
 
    
private int update_crc(byte[] buf, int off, int len) {
        
int c = 0xffffffff;
        
int n, k;
        
int xx;
        
int[] crc_table = new int[256];
        
for (n = 0; n < 256; n++) {
            xx 
= n;
            
for (k = 0; k < 8; k++) {
                
if ((xx & 1== 1) {
                    xx 
= 0xedb88320 ^ (xx >>> 1);
                } 
else {
                    xx 
= xx >>> 1;
                }
            }
            crc_table[n] 
= xx;
        }
 
        
for (n = off; n < len + off; n++) {
            c 
= crc_table[(c ^ buf[n]) & 0xff^ (c >>> 8);
        }
        
return (c ^ 0xffffffff);
    }
 
}


接口就是getPalettedImage()函数,只需要输入原始图片的byte数组,以及需要替换颜色的颜色值还有目标颜色值就行了。因为可以同时替换多个颜色,所以输入参数是代表颜色的整形的数组。总之,要保证原始颜色与目标颜色一一对应就好了。方法简单实用。

欢迎大家使用并留下宝贵的意见。当然,也可以修改一下这个函数,做一些特殊的效果。这里就不多说了。
不过这个代码用处已经不大,因为现在的手机基本上都支持midp2.0所以可以使用更方便的方法替换颜色。

总之,再次感谢这位已经被我忘掉名字的大侠,关键代码是他写的,我只是修改整理而已。

posted @ 2008-08-15 14:34 LukeW 阅读(600) | 评论 (0)编辑 收藏

Autostarting MIDlets in JP-7 phones using PushRegistry

In Sony Ericsson Java Platform 7 (JP-7) phones, such as the K610 or W850, a new push functionality is introduced. PushRegistry Alarm and PushRegistry SMS are supported throughout the Java Platforms, with JP-4 adding PushRegistry CBS and now JP-7 includes PushRegistry auto start.

Note: the auto start functionality is not supported by the first released K610 and K800 JP-7 phones. Please use the phone's upgrade service to ensure that you are using the latest firmware version.

The auto start functionality can be set static by including it in the .jad file or it can be set dynamically in the code. Two example MIDlets are included as an example of this.

Download the source code and examples here>>

To enable the auto start functionality in the .jad file include the line:
//MIDlet-Push-<n>: <ConnectionURL>, <MIDletClassName>, <AllowedSender>
MIDlet-Push-1: autostart://:, AutoStartStatic, *


To make the MIDlet auto start from the source code the following methods can be used to register and un-register the MIDlet.
//Registers the pushRegistry
public void Register(){
        
// List of registered push connections.
        String connections[];
        
// Check to see if the connection has been registered.
        
// This is a dynamic connection allocated on first
        
// time execution of this MIDlet.
        connections = PushRegistry.listConnections(false);
        
if (connections.length == 0) {
                
try {
                        
//Register so the MIDlet will wake up when phone is started.
                        PushRegistry.registerConnection("autostart://:""AutoStartDyn""*");
                        sDisplayString 
= "MIDlet is registered";
                } 
catch (Exception ex) {
                        System.out.println(
"Exception: " + ex);
                        sDisplayString 
= "Fail: " + ex;
                }
        } 
else {
                sDisplayString 
= "Already registered";
        }
        displayForm.deleteAll();
        displayForm.append(sDisplayString);
}
 
//Unregisters the pushRegistry
public void Unregister(){
        
if (PushRegistry.unregisterConnection("autostart://:")){
                System.out.println(
"The pushRegistry is unregistered");
                sDisplayString 
= "MIDlet is unregistered.";
        }
else{
                System.out.println(
"There is no pushRegistry to unregister");
                sDisplayString 
= "No MIDlet to unregister or failed to unregister";
        }
        displayForm.deleteAll();
        displayForm.append(sDisplayString);
}



To find out if the MIDlet is started via pushRegistry or manually you can inspect the connections registered by pushRegistry. Below is a small example of how to handle pushRegistry autostart or manual startup.
    private void handlePushActivation() {
        String[] connections 
= PushRegistry.listConnections(true);
        
if (connections != null && connections.length > 0) {
            
for (int i = 0; i < connections.length; i++) {
                
if (connections[i].startsWith("autostart")) {
                    alert(
"Application autostarted");
                }
            }
        } 
else {
            alert(
"User started the application");
        }
    }

posted @ 2008-08-15 10:57 LukeW 阅读(337) | 评论 (0)编辑 收藏

Using simultaneous sounds

The code sample below describes how to play two sounds at the same time. This feature is supported by the Sony Ericsson JP-5 platform and onwards.

Only one wav file can be played simultaniously but several midi files might be played at the same time. The number of existing players is only limited by available memory, and you have the possibility to play more than one player at the same time. The example below shows how to do this using one midi file and one wav file.

The code is straight-forward - just load the resource, create the player and start playing the file.

Here's the sample:
InputStream is = getClass().getResourceAsStream(file);
InputStream is1 
= getClass().getResourceAsStream(file1);

player 
= Manager.createPlayer(is, " audio/midi");
player.setLoopCount(
-1);
player.prefetch();
player.realize();

player1 
= Manager.createPlayer(is1, "audio/x-wav");
player1.setLoopCount(
1);
player1.prefetch();
player1.realize();

player.start();
player1.start();

example code

posted @ 2008-08-15 10:34 LukeW 阅读(107) | 评论 (0)编辑 收藏

Serializing an Image

Creating an image from an array of data is an easy task, but to create a byte-array of data from an image is a little more complicated. But it's required if you want to send a modified image to a server.

To create a byte-array of data from an image, we can use the getRGB(..) method in the image class in MIDP 2.0. From the getRGB method we get an int-array of data containing all the ARGB values of each pixel in the image. Integers in java are four bytes, so we need to split each int value into four byte values.

To mask out each of the bytes in the int we can use the 'AND &' operator and the 'shift-right >>' operator. Here's an example:
int ARGB = 0xFFFFFFFF;  // AARRGGBB 
int a = (ARGB & 0xFF000000); 
int r = (ARGB & 0x00FF0000); 
int g = (ARGB & 0x0000FF00); 
int b = (ARGB & 0x000000FF);
 
// Here we move each bit to the right.
= (a >> 24); // a = 0x000000FF 
= (r >> 16); // r = 0x000000FF 
= (g >> 8); // g = 0x000000FF
= b;        // b = 0x000000FF


When we convert the integer to a byte, there are some problems since there are only signed bytes in Java. A byte may contain values between -128 and 127 and from our integer we'll have a value between 0 and 255.

Here are some conversion examples:

Integer val 
127 = byte val 127.
Integer val 
128 = byte val -128.
Integer val 
255 = byte val -1.

byte ba = (byte)(a);  // if a=0x000000FF (255), then ba = -1
byte br = (byte)(r); 
byte bg = (byte)(g); 
byte bb = (byte)(b);


We have to loop though each pixel in the image and get each pixel value to our byte-array. When that's done, we can send the image to a server where we can convert the byte-array back to a integer-array and then show our picture.

So, when we convert the byte back to an integer we have to check if the byte value is less than zero.
int a, r, g, b;
if(ba<0)
     a 
= 256 - a;


Now our new integer value will be between 0 and 255 and we just have to use the 'shift-left <<' operator and add the values together to get our new int-array.
= (a << 24);
= (r << 16);
= (g << 8);
= (b);
 
  
0xFF000000 (a)
+ 0x00FF0000 (r)
+ 0x0000FF00 (g)
+ 0x000000FF (b)
= 0xFFFFFFFF (argb)
 
int ARGB = a+r+g+b;

After the integer-array is recreated we can use the createRGBImage(..) method in the Image class to create our image.

Be aware that the byte-array is quite large as it contains all of the ARGB values for each pixel. If an image is 100*60 px, where each pixel is four bytes, the byte array will be 24kb.
example code

posted @ 2008-08-15 10:29 LukeW 阅读(141) | 评论 (0)编辑 收藏

Fade in and out images in MIDP 2.0

This tip describes how to change the alpha value of an image to make it appear blended. There's also an example MIDlet with source code.

In MIDP 2.0 there's a new method in the Image class, getRGB(...) that will get all Alpha, Red, Green, Blue (ARGB) values from the image to an int array. We can use this method, and the resulting array, to change the alpha value of the image.

Integers in J2ME are four bytes, each pixel in the image is described by the ARGB values where each color is one byte 0 to 255. If the alpha value is 0, the corresponding pixel will be transparent, if the alpha is 255, the pixel will be opaque.

To get a specific color from the int array, it's possible to use the AND '&' operator and to accomplish the blending effect we need to get the colors, without the alpha value, from the int array.
FF = 11111111 = 255
0xFFFFFFFF - Alpha = 255, Red =255 Green = 255, Blue = 255
(
0xFFFFFFFF & 0x00FFFFFF= 0x00FFFFFF


By doing this we'll only get the RGB colors from the int array, alpha equals zero.

Now when we just have the RGB colors and alpha equals zero, we can just add our new alpha value.

If we have the alpha value 255 and want to add this to our color, we need to use the shift left operator.

To change:
(00000000 00000000 00000000 11111111) to 
(
11111111 00000000 00000000 00000000)
use the shift left 
'<<' operator.
(
0xFF << 24= 0xFF000000.


With this knowledge we now can change the alpha value or do masking for specific colors.

  • Use the getRGB(...) method in the image class to get all the ARGB values from the image to a int array.
  • Use the blend function below to change the alpha value for each value in the array.
  • Use the createRGBImage(...) method in the image class to create a new image from the edited int array.

There are examples on how to use the getRGB and createRGBImage methods in the MIDlet below.
public static void blend(int[] raw, int alphaValue){
    
int len = raw.length;
    
// Start loop through all the pixels in the image.
    for(int i=0; i<len; i++){
        
int a = 0;
        
int color = (raw[i] & 0x00FFFFFF); // get the color of the pixel.
        a = alphaValue;     // set the alpha value we want to use 0-255.
 
        a 
= (a<<24);    // left shift the alpha value 24 bits.
        
// if color = 00000000 11111111 11111111 00000000 (0xFFFF00 = Yellow)
        
// and alpha= 01111111 00000000 00000000 00000000
        
// then c+a = 01111111 11111111 11111111 00000000
        
// and the pixel will be blended.
        color += a;
        raw[i] 
= color;
    }
}

example code

posted @ 2008-08-15 10:18 LukeW 阅读(188) | 评论 (0)编辑 收藏

Fast stream reading in Java

To increase the performance of your Java™ application when reading from an InputStream, there are a few key areas to look into. If possible, don't make any reallocations of memory. Allocate the input buffer once. Let the Java™ VM do the bulk of the reading, i.e. read data in big chunks. Some coding examples show a loop that reads data a small amount at a time. Using a too small buffer is inefficient. A much better technique is to use a relatively large buffer.

If you know the maximum content length, the most optimal read would be a single line, like this:
len = instream.read( buf, 0, MAX_BUFFER_SIZE );

In this case we assume that the buffer has already been allocated with a size set to MAX_BUFFER_SIZE.

If the content is of varying size, we have to make a tradeoff between performance and memory usage. If you keep your buffer size just above the average content length, then the number of reallocations of the data buffer and the number of reads will be kept at a minimum. There is no defined size of what a large buffer is but a good rule of thumb might be to keep the buffer size at most about 0,5 to 1 MB.

posted @ 2008-08-15 10:11 LukeW 阅读(132) | 评论 (0)编辑 收藏

仅列出标题
共3页: 上一页 1 2 3