BlackBerry 应用程序开发者指南


By Taiguo Zhang (confach@gmail.com) 1 BlackBerryBlackBerryBlackBerry BlackBerry JavaJavaJava Java 开发环境 版本 4.1.04.1.04.1.0 4.1.0 BlackBerryBlackBerryBlackBerry BlackBerry 应用程序开发者指南 第一卷:基础 By Taiguo Zhang (confach@gmail.com) 2 声明 本文档由 Taiguo Zhang 个人翻译完成。如有转载,复制,摘抄,必须向 Taiguo Zhang 提出。 本文英文文档归 RIM 公司所有。如有任何问题,请与 RIM 联系。 如果本文档侵犯 RIM 的权利,请 RIM 立即通知我,我将予以删除和其他处理。 联系方式: Email: confach@gmail.com MSN: confach@hotmail.com Blogs: http://confach.cnblogs.com http://www.36sign.com http://www.inblackberry.com 欢迎大家和我联系。 By Taiguo Zhang (confach@gmail.com) 3 目录 声明................................................................................................................................2 目录................................................................................................................................3 第1章BlackBerry API................................................................................................. 9 使用 BlackBerry API.............................................................................................. 9 BlackBerry API.............................................................................................. 10 CLDCAPI......................................................................................................12 MIDPAPI.......................................................................................................12 PDAPAPI.......................................................................................................12 在BlackBerry 设备上使用 Java.......................................................................... 13 限制................................................................................................................13 多线程............................................................................................................13 持久数据........................................................................................................13 网络通信........................................................................................................14 流....................................................................................................................14 集合................................................................................................................15 事件监听者....................................................................................................16 系统功能........................................................................................................16 使用工具........................................................................................................17 应用程序控制.......................................................................................................17 第2章 编写 BlackBerry Java 应用程序................................................................... 19 应用程序管理.......................................................................................................19 编写一个例程.......................................................................................................19 扩展 UiApplication 基类...............................................................................19 定义 main()................................................................................................... 19 定义一个构造子............................................................................................20 定义 main 屏幕..............................................................................................20 代码实例........................................................................................................20 重用一般代码.......................................................................................................21 代码实例........................................................................................................22 使用 BlackBerry IDE............................................................................................24 创建一个工作空间........................................................................................24 创建一个项目................................................................................................25 创建源文件....................................................................................................25 By Taiguo Zhang (confach@gmail.com) 4 和BlackBerry IDE 集成源文件管理工具....................................................25 编译项目........................................................................................................26 生成 API 文档................................................................................................27 使用命令行...........................................................................................................28 使用蓝牙开发环境...............................................................................................28 利用一个 BlackBerry 设备模拟器使用蓝牙开发环境................................28 使用 Eclipse 开发环境......................................................................................... 28 启动 JDWP.....................................................................................................29 连接 Eclipse 开发环境.................................................................................. 29 设置连接时间................................................................................................31 使用 Eclipse 开发环境进行调试.................................................................. 31 编程指南...............................................................................................................31 编写高效的代码............................................................................................31 减小代码大小................................................................................................35 在BlackBerry 设备上使用时间....................................................................37 建议的实践....................................................................................................38 第3章 创建用户接口(UI)....................................................................................40 UIAPI....................................................................................................................40 显示 UI 组件.........................................................................................................40 显示屏幕(Screen).......................................................................................... 40 显示对话框....................................................................................................43 显示域(Field)............................................................................................44 管理 UI 组件.........................................................................................................51 管理布局........................................................................................................51 管理 UI 交互..................................................................................................52 管理前台事件................................................................................................53 管理绘图区域................................................................................................54 创建客户定制的 UI 组件.....................................................................................55 创建定制的上下文菜单项............................................................................64 创建定制的布局管理器................................................................................67 创建列表........................................................................................................71 操作图片...............................................................................................................75 使用未处理(raw)的图像数据..................................................................75 使用编码的图像............................................................................................76 使用图像对象画图...............................................................................................79 使用图形上下文............................................................................................80 创建一个与标准的 BlackBerry UI 一致的界面......................................... 80 用颜色绘制....................................................................................................80 代码实例........................................................................................................83 监听 UI 对象的改变.............................................................................................85 监听 field 属性的变化...................................................................................86 监听焦点的改变............................................................................................86 监听滚动事件................................................................................................87 第4章 使用音频........................................................................................................88 By Taiguo Zhang (confach@gmail.com) 5 播放一个支持的音频格式的曲调.......................................................................88 语音记事 API........................................................................................................88 第5章 支持的媒体内容(Media Content)............................................................89 PME 内容..............................................................................................................89 PME API 概览................................................................................................89 媒体加载........................................................................................................89 播放媒体内容.......................................................................................................91 下载内容........................................................................................................91 播放 PME 内容..............................................................................................92 代码实例........................................................................................................93 监听媒体引擎事件...............................................................................................94 监听媒体引擎事件........................................................................................95 注册监听者....................................................................................................95 在后台加载内容............................................................................................96 跟踪下载进度................................................................................................96 代码实例........................................................................................................98 创建一个定制的连接.........................................................................................101 实现一个定制的 connector......................................................................... 101 注册一个定制的连接器..............................................................................102 代码实例......................................................................................................103 第6章 连接网络......................................................................................................106 HTTP 和Socket 连接.........................................................................................106 使用 HTTP 连接.................................................................................................106 打开一个 HTTP 连接..................................................................................106 设置 HTTP 请求方式..................................................................................107 设置或获取头字段......................................................................................107 发送和接受数据..........................................................................................107 代码实例......................................................................................................107 使用 HTTPS 连接...............................................................................................113 打开一个 HTTPS 连接................................................................................113 指定代理或终端到终端(end_to_end)模型........................................... 113 使用 socket 连接.................................................................................................114 指定 TCP 的设置.........................................................................................114 打开一个 socket 连接..................................................................................114 在socket 连接上发送和接收数据..............................................................115 关闭连接......................................................................................................115 使用端口连接.....................................................................................................115 打开一个 USB 接口或序列端口连接........................................................ 115 在端口连接上发送数据..............................................................................116 在端口连接上接收数据..............................................................................116 关闭端口连接..............................................................................................116 使用蓝牙序列端口连接.....................................................................................116 打开一个蓝牙序列端口连接......................................................................117 在蓝牙序列端口连接上发送数据..............................................................117 By Taiguo Zhang (confach@gmail.com) 6 在蓝牙序列端口连接上接收数据..............................................................117 关闭一个端口连接......................................................................................118 代码实例......................................................................................................118 第7章 使用数据报(Datagram)连接.................................................................. 125 数据报连接.........................................................................................................125 使用 UDP 连接...................................................................................................125 获得一个数据报连接..................................................................................126 使用 Mobitex 网络............................................................................................. 127 打开一个 Mobitex 数据报连接.................................................................. 127 监听数据报状态事件..................................................................................128 获得数据报信息..........................................................................................128 获取无线和网络信息..................................................................................128 代码实例......................................................................................................128 发送和接收短消息.............................................................................................135 发送 SMS.....................................................................................................136 接收 SMS 消息............................................................................................137 第8章 本地化应用程序..........................................................................................140 资源文件.............................................................................................................140 资源继承......................................................................................................141 为应用程序加入本地化支持.............................................................................141 增加资源头文件..........................................................................................141 加入资源内容文件......................................................................................141 加入资源......................................................................................................142 代码实例......................................................................................................142 从资源文件中获取字符串.................................................................................146 实现资源接口..............................................................................................146 获取资源包(bundle)...............................................................................146 使用资源创建菜单项..................................................................................146 用合适的资源替代文本字符串..................................................................147 代码实例......................................................................................................147 为应用程序组(suite)管理资源文件............................................................. 150 创建资源项目..............................................................................................150 指定输出文件名..........................................................................................150 创建初始化文件..........................................................................................150 增加文件到合适的资源项目中..................................................................151 第9章 IT 策略(Policy).......................................................................................152 IT 策略................................................................................................................ 152 获取客户策略.....................................................................................................152 监听策略的改变..........................................................................................152 代码实例.............................................................................................................153 第10 章 创建 Client/Server Push 应用程序............................................................154 Push 应用程序.................................................................................................... 154 Client/Server push 请求......................................................................................154 存储 push..................................................................................................... 155 By Taiguo Zhang (confach@gmail.com) 7 代码转换(Transcoding).......................................................................... 155 信任模式......................................................................................................155 发送一个 RIM push 请求............................................................................156 发送一个 PAP push 请求.............................................................................156 发送 PAP push 取消请求.............................................................................158 发送一个 PAP push 查询请求.....................................................................159 决定 BlackBerry 设备是否在 push 覆盖范围内........................................160 编写一个客户端 push 应用程序....................................................................... 161 创建一个监听线程......................................................................................161 打开一个输入连接......................................................................................161 关闭流连接通知..........................................................................................161 代码实例......................................................................................................161 编写一个服务器端 push 应用程序................................................................... 165 构造 push URL............................................................................................ 165 连接 BES......................................................................................................165 为HTTP POST 请求设置属性................................................................... 165 写数据到服务器连接..................................................................................165 读取服务器响应..........................................................................................166 断开连接......................................................................................................166 代码实例......................................................................................................166 Push 应用程序疑难解答.................................................................................... 168 第11 章 使用位置信息............................................................................................ 170 位置 API..............................................................................................................170 获得 GPS 位置的方法.................................................................................170 为选择 GPS 位置方法指定原则.................................................................170 获取 BlackBerry 设备的位置......................................................................171 注册一个位置监听者..................................................................................172 代码实例.............................................................................................................173 第12 章 打包和部署................................................................................................187 使用 BlackBerry 桌面软件部署应用程序.........................................................187 创建一个应用程序加载文件......................................................................187 无线部署应用程序.............................................................................................187 部署.jar 文件................................................................................................188 部署.cod 文件.............................................................................................. 188 第13 章 测试和调试................................................................................................192 测试应用程序.....................................................................................................192 使用设备模拟器测试应用程序..................................................................192 使用一个已连接的 BlackBerry 设备测试应用程序..................................194 测试 HTTP 网络连接..................................................................................196 使用调试工具.....................................................................................................200 分析代码覆盖..............................................................................................200 使用 profiler.................................................................................................200 查找内存泄漏..............................................................................................201 附录:.alx 文件的格式................................................................................................203 By Taiguo Zhang (confach@gmail.com) 8 .alx 文件..............................................................................................................203 嵌套模块......................................................................................................204 指定一个 BlackBerry 设备版本..................................................................205 .alx 文件元素...............................................................................................205 附录:MDS 服务参考................................................................................................. 208 HTTP 请求..........................................................................................................208 HTTP 响应..........................................................................................................209 Push 请求响应状态码................................................................................. 209 RIM Push 请求响应码.................................................................................209 HTTPS 支持........................................................................................................210 HTTPS 认证(Certificate)管理................................................................210 编码转化器.........................................................................................................210 编码转化器 API...........................................................................................211 选择编码转化器..........................................................................................212 映射编码转化器..........................................................................................214 创建编码转化器.................................................................................................216 将HTML 标记和内容转化为大写.............................................................216 编译和安装编码转化器.....................................................................................220 术语表........................................................................................................................221 索引............................................................................................................................224 By Taiguo Zhang (confach@gmail.com) 9 1 第111 1 章BlackBerryBlackBerryBlackBerry BlackBerry APIAPIAPI API 使用使用使用 使用 BlackBerryBlackBerryBlackBerry BlackBerry APIAPIAPI API BlackBerry Java 开发环境(简称 JDE)的设计提供了一套完整的 API和工具,来开发在 BlackBerry 设备上运行的 Java 应用程序。 BlackBerry 设备包含了一个基于 CLDC1.1 以及 MIDP 的Java ME(Java Platform Micro Edition)运行时环境。BlackBerry API扩展提供了额外的功能,并且和 BlackBerry 集成得更紧 密。 你可以在你的应用程序中使用 CLDC/MIDP 和BlackBerry API。为了能让你的应用程序 在任何采用 JTWI(Java Technology for Wireless Industry,无线领域的Java 技术)的设备上 运行,你仅需要使用 CLDC 和MIDPAPI来编写标准的 MIDP 应用程序。 BlackBerry 手持设备软件组件 为了查看 API 参考。点击任务栏的开始>程序>ResearchResearchResearch Research InInIn In Motion>BlackBerryMotion>BlackBerryMotion>BlackBerry Motion>BlackBerry JDEJDEJDE JDE 4.1.0>API4.1.0>API4.1.0>API 4.1.0>API JavaJavaJava Java DocDocDoc Doc ReferenceReferenceReference Reference 使用 BlackBerryBlackBerryBlackBerry BlackBerry APIAPIAPI API 在BlackBerry BlackBerry BlackBerry BlackBerry 设备上使用 JavaJavaJava Java 应用程序控制 By Taiguo Zhang (confach@gmail.com) 10 BlackBerryBlackBerryBlackBerry BlackBerry APIAPIAPI API BlackBerry API为访问 BlackBerry 特性提供了用户界面,本地化,网络,以及其他功能。 注:访问某些特性,如高级的加密,同步,以及消息的额外 API,是受限制的。为了 使用这些 API,你必须收到专门来自 Research In Motion 的认证中心编写的许可。为了得到更 多信息,参看 BlackBerry 应用程序开发者指南第一卷:基础 第二卷:高级。 BlackBerryBlackBerryBlackBerry BlackBerry APIAPIAPI API 包 描述 net.rim.blackberry.api.browser 应用程序可以调用 BlackBerry 浏览器,为了得到更 多信息,参看 BlackBerry 应用程序开发者指南 第 二卷:高级。 net.rim.blackberry.api.invoke 允许应用程序调用 BlackBerry 应用程序,如任务, 消息,备忘录以及电话。为了得到更多信息,参看 BlackBerry 应用程序开发者指南 第二卷:高级。 net.rim.blackberry.api.mail 定义了必要的功能来将内部的 RIM 消息系统对象 组件转化为和 Mail API兼容并可移植的对象。同时 也提供了发送,接收,以及访问消息的功能。为了 得到更多信息,参看 BlackBerry 应用程序开发者 指南 第二卷:高级 net.rim.blackberry.api.mail.event 定义了消息事件以及监听者(Listener)接口来管理 邮件事件。为了得到更多信息,参看 BlackBerry 应 用程序开发者指南 第二卷:高级 net.rim.blackberry.api.menuitem 允许应用程序在 BlackBerry 的应用程序例如地址 本,日历以及消息中增加客户定制的菜单项,为了 得到更多信息,参看 BlackBerry 应用程序开发者 指南 第二卷:高级 net.rim.blackberry.api.options 允许应用程序在 BlackBerry 设备的选项中增加选 项条目。为了得到更多信息,参看 BlackBerry 应 用程序开发者指南 第二卷:高级 net.rim.blackberry.api.pdap 允许应用程序和 BlackBerry 个人信息管理(PIM) 交互,PIM 包括地址本,任务,日历。MIDP 包 javax.microedition.pim 提供了类似的功能。为了得 到更多信息,参看 BlackBerry 应用程序开发者指 南 第二卷:高级 net.rim.blackberry.api.phone 提供了访问电话应用程序的高级特性。为了得到更 多信息,参看 BlackBerry 应用程序开发者指南 第 二卷:高级 net.rim.blackberry.api.phone.phonelogs 提供了访问电话呼叫历史记录的功能。为了得到更 多信息,参看 BlackBerry 应用程序开发者指南 第 二卷:高级 By Taiguo Zhang (confach@gmail.com) 11 net.rim.device.api.bluetooth 允许BlackBerry 应用程序在一个蓝牙序列端口连 接的基础上与打开蓝牙无线技术的设备进行通信。 为了得到更多信息,参看 103 页的“使用蓝牙序列 端口连接”. net.rim.device.api.browser.field 允许程序在界面上显示浏览器的字段。为了得到更 多信息,参看 BlackBerry 应用程序开发者指南 第 二卷:高级 net.rim.device.api.browser.plugin 允许程序增加额外支持的 MIME 类型到 BlackBerry 浏览器上。为了得到更多信息,参看 BlackBerry 应 用程序开发者指南 第二卷:高级 net.rim.device.api.collection net.rim.device.api.collection.util 为管理数据集合定义了接口和实用类。为了得到更 多信息,参看 13 页的“集合”. net.rim.device.api.compress 提供实用类来进行 GZip 和ZLib 数据压缩。① net.rim.device.api.i18n 提供类来支持BlackBerry设备上应用程序的本地化。 为了得到更多信息,参看13 页 的“本地化应用程序”. net.rim.device.api.io 提供一个定制的 BlackBerry 类库来管理数据的输入 和输出。 net.rim.device.api.mime 提供与 MIME 编码的数据流一起工作的类。 net.rim.device.api.notification 提供触发事件的通知以及响应系统以及程序的事件 的方法。为了得到更多信息,参看 BlackBerry 应用 程序开发者指南 第二卷:高级 net.rim.device.api.servicebook 允许程序增加,删除,以及访问服务约定(Service Book )②的接口。为了得到更多信息,参看 BlackBerry 应用程序开发者指南 第二卷:高级 net.rim.device.api.system 提供访问系统级的功能,包括键盘和滑轮的事件监 听者,图像创建和支持,和应用程序控制。 net.rim.device.api.ui 提供增强的功能来控制 BlackBerry 用户界面,包括 屏幕和控件布局管理,控件类型支持,焦点,滚动, 以及改变监听者。为了得到更多信息,参看 39 页的 “用户界面 API”. net.rim.device.api.ui.component 提供了创建 UI程序的界面组件库。为了得到更多信 息,参看 39 页的“显示用户界面组件”。 net.rim.device.api.ui.container 提供创建 UI程序的界面管理组件的库。为了得到更 多信息,参看 49 页的“管理用户界面组件”。 net.rim.device.api.ui.text 提供类对文本字符串进行过滤,包含多种类型的数 据,例如电话号码或 URL。 net.rim.device.api.util 提供实用的方法和接口,包含数组,哈希表,字符 匹配。 ① 也许有人会问,为什么 BlackBerry 需要压缩数据,又在什么地方用到呢?非常简单,就是减小数据所占 用的空间,最好的一个例子是 BES 发送邮件,译者注。 ② 服务订阅,Service Book,这是 BlackBerry 使用中一个非常重要的概念。功能就是你订阅的服务,例如你 订阅了 MMS 的功能,它会在你 BlackBerry 手持设备上出现。译者注。 By Taiguo Zhang (confach@gmail.com) 12 CLDCCLDCCLDC CLDC APIAPIAPI API MIDPMIDPMIDP MIDP APIAPIAPI API PDAPPDAPPDAP PDAP APIAPIAPI API CLDCCLDCCLDC CLDC APIAPIAPI API 包 描述 java.io 提供数据流的系统输入和输出。 java.lang 提供 Java 编程语言基础类。 java.lang.ref 提供引用对象类,它们支持一定程度上的垃圾回收。 java.util 包含集合类,时间,以及多样的实用类。 javax.microedition.io 包含一般连接的类。 MIDPMIDPMIDP MIDP APIAPIAPI API 包 描述 javax.microedition.lcdui 包含 MIDP 用户界面 API,它为MIDP 应用程序的用户界 面实现提供了一组特性。 javax.microedition.lcdui.game 包含了可以为 BlackBerry 设备进行丰富游戏内容开发的 类。 javax.microedition.midlet 定义了MIDP应用程序以及应用程序和应用程序运行的环 境之间的交互。 注:BlackBerry IDE 可以在启动时,使参数传递到一个 BlackBerry CLDC 应用程序中。 javax.microedition.pki 定义了用来验证安全连接信息的证书。 javax.microedition.rms 为MIDlet 提供一种机制来存储和取得持久性数据。 MIDPMIDPMIDP MIDP APIAPIAPI API 包 描述 javax.microedition.pim .提供标准机制来访问 PIM 信息。 By Taiguo Zhang (confach@gmail.com) 13 在在在 在 BlackBerry BlackBerry BlackBerry BlackBerry 设备上使用设备上使用设备上使用 设备上使用 JavaJavaJava Java 编译源代码,打包为.cod文件,并将.cod文件加载到BlackBerry设备上,通过虚拟机运行。 注:.cod文件名控制在128字节。 如CLDC中描述的那样,BlackBerry IDE使用一个分割的VM架构。为了降低内存的数量以及 BlackBerry设备需要的处理能力,部分类加载过程,称为预验证,它在Java代码加载到 BlackBerry之前发生。在将源代码打包为.cod文件之前,自动验证它。在类加载到BlackBerry 设备时完成验证的提示。 限制 在CLDC1.1 中描述的那样 BlackBerry 虚拟机有以下限制:  没有对象的析构(finalization)  没有用户类的加载  没有反射,因此不支持 RMI 和Jini 网络技术。  没有原生方法(Native method)  没有 Runtime.exec()执行外部的进程 多线程 BlackBerry Java 环境提供一个真正的多线程环境来运行应用程序。这个环境允许多个应用程 序同时运行,允许事件广播到多个应用程序,以及长操作和监听线程在背后运行。 持久数据 存储在闪存中的数据在 BlackBerry 重新设置之间持久保存。在 BlackBerry 设备上存储数据 可以采用以下二种方式中的一种:  使用 MIDP 记录存储  使用 BlackBerry 持久模型 为了得到关于使用 BlackBerry API 存储持久数据的更多信息,参看 BlackBerry 应用程序开 发者指南第一卷:基础 第二卷:高级。 By Taiguo Zhang (confach@gmail.com) 14 网络通信 BlackBerry JDE根据 MIDP2.0,实现了网络通信。它提供多种连接选项,包括通过使用 HTTP 代理连接在公司防火墙背后安全连接的能力。 BlackBerry JDE 提供了以下几种连接类型:  流连接(StreamConnection 接口,包括: 1. HTTP 连接(HttpConnection 接口) 2. HTTPS 连接(HttpsConnection 接口) 3. Socket 连接(SocketConnection 接口) 4. 安全 socket 连接(SecureConnection 接口) 5. 序列连接到 BlackBerry 设备的一个通信接口(CoomConnection 接口)  数据报连接(DtagramConnection 接口),包含 1. UDP数据报连接(UDPDatagramConnection 接口) Javax.microedition.io.PushRegistry 类对 BlackBerry 设备保持了一些进入的连接。 流 BlackBerry JDE 为包含在 CLDC java.io 包里的流提供了标准的接口和类。 MIME编码 BlackBerry IDE 提供了 MIMEInputStream 和MIMEOutputStream 类来读写一个 MIME 编码的 数据流。 压缩 在net.rim.device.api.compress 包 里 ,BlackBerry JDE 提供类来读取使用 Zlib 或者 GZip 格式压缩的数据流。这些类的行为如 Java 标准版本里的 java.util.zip 包里对应的类一样。 缺省的,压缩是允许的,BlackBerry 设备可以写有效的 GZip 和Zlib 文件为这样压缩文件 的内容。解压缩同样也是支持的。 类 描述 MIMEInputStream 实现一个流来读取一个 MIME 消息,然后根据 MIME 标准格式化和分解这个消息为其部分 MIMEOutputStream 实现一个输出流,这个流可以根据 MIME 标准格 式化输出为其部分。本类不会完成实际的数据编 码,因此你必须在写入它到本数据流治安编码它。 By Taiguo Zhang (confach@gmail.com) 15 集合 BlackBerry IDE 提供了一组接口和实用类来管理 BlackBerry 设备上的集合。 net.rim.device.api.collection 包包含了许多接口,这些接口为某些特定类型数据类型多 定义了种类型的集合,例如列表,数组以及映射。这些接口定义了与 Java 标准版本集合框 架的 list,set 和map 接口类似的功能。 在你自己的类中实现这些接口,或者使用在 net.rim.device.api.collection.util 包里提 供的使用类。 向量 标准的 java.util.Vector 实现了一个大小可以改变的对象数组。BlackBerry JDE 也提供了 合适的类,例如 rim.device.api.util.IntVector 和rim.device.api.util.ByteVector 来 对主要类型进行工作。这些类看起来和普通的 Vector 一样,除了它们优化了在任何位置上插 入的项。相反,如果你使用标准的大 Vector 作随机的改变,大量的数据会在闪存和RAM 移 动。 列表 BlackBerry JDE 在net.rim.device.api.collection.util 包里提供了一些类来管理元素的 列表 哈希表 除了 CLDC 提供的标准 java.util.Hashtable 之外,BlackBerry JDE 包含了特定的 net.rim.device.api.collection.util.LongHashtableCollection 类,这个类提供了使用长整形作为关 键字的哈希表集合。一个 LongHashtableCollection 对象,写操作作为一个映射(使用一个关 类 描述 SortedReadableList 和 UnsortedReadableList 使用这些类来维护已排序的和未排序的元素 列表。SortedReadableList 类需要你使用一 个比较对象来排序列表中的元素。增加到列 表中的每一个元素必须被比较对象视为有效 的。 IntSortedReadableList 和 LongSortedReadableList 使用这些类自动排序整形列表或与长整形关 键字相关的元素。 BigSortedReadableList 和 BigUnsortedReadableList 使用这些类来存储大的数据集合(大于 10或 者15K).这些类不会存储数据到一个数组中, 因此你可以对大数据集合更有效的做出随意 改变。 ReadableListCombiner 使用这个类合并 2个或者更多的 ReadableList 对象并且将他们作为一单个 ReadableList 来存储。 ReadableListUtil 此类提供一些有用的方法如 getAt()和 getIndex()。我们可以使用此类得到只读列表 中的数据。 By Taiguo Zhang (confach@gmail.com) 16 键字-元素对),读操作作为一个映射或者作为一个集合(在集合里作为一个数组来得到数 据)。 事件监听者 事件监听者接口根据事件类型划分。每个应用程序注册来接收特定类型的事件。应用程序事 件队列然后调度事件到一个合适的监听者。 应用程序可以实现合适的监听者接口或者在各种 Screen 对象里重写监听者方法。大多数应 用程序实现了 KeyListener 和TrackwheelListener 接 口 ,而且注册了监听者来接收键盘和滑 轮的事件。键盘和滑轮是用户和应用程序交互的主要方式。 下列的事件监听者放在 net.rim.device.api.system 包中 系统功能 net.rim.device.api.system 包的类提供了访问 Java VM和BlackBerry 设备上系统资源的 能力。 监听者接口 事件类型 AlertListener 实现接口来监听 alert 事件 BluetoothSerialPortListener 实现接口来监听蓝牙序列端口事件,例如打 开一个蓝牙序列端口连接作为服务器或者客 户端。 GlobalEventListener 实现接口来监听可以广播到所有应用程序的 全局事件。 HolsterListener 实现接口来监听套装事件,例如 BlackBerry 设备从套装中插入和移开。 IOPortListener 实现接口监听 I/O 端口事件。 KeyListener 实现接口监听键盘事件,例如用户按住或释 放一个键。 RealTimeClockListener 实现本接口来监听实时时钟事件,例如时钟 更新。 SerialPortListener 实现此接口监听序列化端口事件,例如对于 一个已经和计算机序列端口连接的 BlackBerry 设备,一个在数据正在被发送到 序列化端口连接状态中的改变。 SystemListener 实现此接口来监听系统事件,例如电池状态 和电源的改变。 TrackwheelListener 实现本接口监听滑轮事件,例如按住滑轮。 USBPortListener 实现本接口监听 USB 端口事件,例如对于一 个已经和计算机 USB 端口连接的 BlackBerry 设备,数据正被发送到 USB 端口 连接的状态。 By Taiguo Zhang (confach@gmail.com) 17 得到信号信息 RadioInfo 提供了访问信号状态信息的能力。 得到设备信息 DeviceInfo 类可以访问下列 BlackBerry 设备的信息:  电池电源和状态  Blackerry 设备号  空闲时间  平台版本 系统事件通知用户 当一个事件,例如一条新消息到来的时候,Alert 类允许应用程序通知用户。 监视内存使用情况 使用一个 Memory 类提供的静态方法来得到 VM内存使用统计信息。 Memory 类很多实用方法返回一个 MemoryStats 对象。使用 MemoryStats 类提供的实用方 法得到 BlackBerry 设备上内存和可用存储空间的详细信息。 日志事件 EventLogger 允许应用程序在持久存储里存储事件日志。BlackBerry设备维护事件队列,以 至当日志满时,会删除最早的事件,并增加新的事件。用户可以按住 AltAltAlt Alt +lglg lglg lglg lglg 键来查看 BlackBerry 设备的系统事件日志。 使用工具 BlackBerry JDE在net.rim.device.api.util 包里提供了一组实用工具,这些类里的许多类提 供了和 Java 标准版本里相似的功能。  Comparator 接口定义了对象集合上的顺序的方法。  Arrays 提供方法来操作数组,例如排序,查找,以及作为列表来查看数组。  BitSet 类维护 bit 的集合。 net.rim.device.api.util 包包含了多个类来管理特定类型的数据集合,包括向量,哈希表,映 射以及栈。 应用程序控制应用程序控制应用程序控制 应用程序控制 应用程序控制允许系统管理员操作以下动作:  控制内部连接(公司防火墙背后的连接)  控制外部连接  控制本地连接(序列和 USB 连接)  控制访问键存储(key store)  控制访问特殊的 API. By Taiguo Zhang (confach@gmail.com) 18  阻止第三方应用程序存在 BlackBerry 设备上。 为了得到更多信息,参看BlackBerry Enterprise Server Handheld Management Guide 的应 用程序管理。 受限制访问的 API,API,API, API, 类,,, , 和方法 使用了下列受限的 API,类,以及方法的应用程序可以加载到 BlackBerry 设备,但是如果他 们访问了一个没有在应用程序控制下得到允许的 API 时,在运行时会抛出一个 ControlledAccessException 或者 NoClassDefFoundError 的异常。 类,方法或 APIAPIAPI API 缺省值 应用程序菜单项 API(net.rim.blackberry.api.menuitem) 允许 蓝牙 API(net.rim.device.api.bluetooth) 允许 Connector.open() (javax.microedition.io) 提示 注:内部和外部的连接由不同 的应用程序控制策略来管理 DeviceKeyStore 类 (net.rim.device.api.crypto.keystore) 允许 EventInjector 类 (net.rim.device.api.system) 不允许 HTTP Filter API(net.rim.device.api.io.http) 不允许 Notification API(net.rim.device.api.notification) 允许 电API 和 呼叫API(用来调用电话应用程序) (net.rim.blackberry.api.phone 和net.rim.blackberry.api.invoke) 允许(缺省,用户提示) 电话日志 API(net.rim.blackberry.api.phone.phonelogs) 允许(缺省,用户提示) PIMAPI(net.rim.blackberry.api.pdap) 允许 RuntimeStore 类 (net.rim.device.api.system) 允许 SerialPort 类 (net.rim.device.api.system) 允许 Session 类 (net.rim.blackberry.api.mail) 允许 StringPatternRepository 类 (net.rim.device.api.util) 允许 USBPort 类 (net.rim.device.api.system) 允许 By Taiguo Zhang (confach@gmail.com) 19 222 2 第222 2 章 编写BlackBerryBlackBerryBlackBerry BlackBerry Java Java Java Java 应用程序 应用程序管理应用程序管理应用程序管理 应用程序管理 当BlackBerry 设备启动时,VM加载应用程序管理器,它管理在 BlackBerry 设备上所 有运行的程序。对于其他 Java 程序,应用程序管理器的功能类似操作系统事件的中心调度 员一样。 提供用户界面的应用程序扩展了 net.rim.device.api.ui.UiApplication 类。这个类为应用程 序提供方法来注册事件监听者,管理线程以及 UI组件。 没有提供用户界面的应用程序扩展了 net.rim.device.api.system.Application 类。 BlackBerry 应用程序开始于 main()函数。当一个程序开始时,它的 main()线程调用 enterEventDispatcher()来开始处理事件。这个线程运行所有绘图以及事件处理的代码,以及 登等待应用程序队列里地事件。 当应用程序管理器接收到一个事件时,它将这个事件拷贝到合适的队列里,这个队列可 以允许应用程序管理器指挥消息到特定的程序中。例如,前台的应用程序仅接收用户输入的 消息。 编写一个例程编写一个例程编写一个例程 编写一个例程 扩展UiApplication UiApplication UiApplication UiApplication 基类 每个提供用户接口的应用程序扩展了 UiApplication 基类,UiApplication 类为应用程序定义 了方法来建立一个事件线程,并且显示和维护 Screen 对象。 定义 main()main()main() main() 在main()中,为应用程序创建一个新的对象。调用 enterEventDispatcher()使应用程序进入 事件线程并且开始处理消息。 应用程序管理 编写一个例程 重用一般代码 使用BlackBerryBlackBerryBlackBerry BlackBerry IDEIDEIDE IDE 使用命令行 使用蓝牙开发环境 使用Eclipse Eclipse Eclipse Eclipse 开发环境 编程指南 By Taiguo Zhang (confach@gmail.com) 20 定义一个构造子 为你的应用程序定义缺省的构造子。缺省的构造子调用 UiApplication.pushScreen()以显示当 应用程序启动时出现的屏幕。在本例中,屏幕使一个新的 HelloWorldScreen 实例,它在下节 的代码中定义: 定义main main main main 屏幕 为了定义应用程序 UI的主屏幕,扩展 MainScreen 类。MainScreen 类是 Screen 的 子 类 , 它实现了 TrackwheelListener 和KeyboardListener 接口,这些接口接收和响应用户交互。如 果你扩展 Screen 类或者其子类中的一个,你并不是必须实现 TrackwheelListener 和 KeyboardListener 接口。 你的类至少应该重写 2个MainScreen 的方法:缺省的构造子和 onClose(). 在这个例子中,构造子调用了 MainScreen 的构造子。缺省地,MainScreen 提供下列特性:  由一个 Close Close Close Close 菜单项的缺省菜单。  当你点击 Close Close Close Close 或者按 Escape Escape Escape Escape 时,缺省的是关闭动作。为了提供客户定制行为, 例如显示一个对话框提示,当用户点击 Close Close Close Close 菜单项或者按 Escape Escape Escape Escape 按钮,重写 onClose().  一个 RichTextField 的实例,一个可以接收焦点的只读富文本域 为了得到更多关于 增加 UI组件到屏幕中的信息,参看 40 页的“提供屏幕导航”  一个 Select Select Select Select 菜单项的上下文菜单• 为了得到更多信息,参看 60 页的“创建定制的 上下文菜单“ 代码实例 接下来的例子创建了一个屏幕,它包含了一个富文本域。当富文本域接收到焦点时,菜 单保安一个 Close 菜单项和一个 Select 上下文菜单项。 例: HelloWorld.java /** * HelloWorld.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ public static void main(String[] args) { HelloWorld theApp = new HelloWorld(); theApp.enterEventDispatcher(); } public HelloWorld() { pushScreen(new HelloWorldScreen()); } By Taiguo Zhang (confach@gmail.com) 21 packagepackagepackage package com.rim.samples.docs.helloworld; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import com.rim.samples.docs.resource.*; publicpublicpublic public classclassclass class HelloWorld extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { HelloWorld theApp = newnewnew new HelloWorld(); theApp.enterEventDispatcher(); } publicpublicpublic public HelloWorld() { pushScreen(newnewnew new HelloWorldScreen()); } } finalfinalfinal final classclassclass class HelloWorldScreen extendsextendsextends extends MainScreen { publicpublicpublic public HelloWorldScreen() { supersupersuper super (); LabelField title = newnewnew new LabelField(“HelloWorld Sample”, LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(newnewnew new RichTextField(“Hello World!”)); } publicpublicpublic public booleanbooleanboolean boolean onClose() { Dialog.alert(“Goodbye!”); System.exit(0); returnreturnreturn return truetruetrue true ; } } 重用一般代码重用一般代码重用一般代码 重用一般代码 抽象基类可以使你跨越多个类实现和重用一般功能。每个应用程序可以扩展单个基类。在 BlackBerry IDE,加入基类到一个库项目中。为每个应用程序创建一个独立的项目,定义库 项目的依赖。 By Taiguo Zhang (confach@gmail.com) 22 代码实例 本指南的例程扩展了 BaseApp 类,它实现下面的功能:  扩展 UiApplication 类  实现 KeyListener 和TrackwheelListener 接口  定义变量,例如一般的菜单项  定义一个方法创建应用程序菜单。  为菜单选择定义一个方法  定义一个抽象方法退出程序 例: BaseApp.java /* ** BaseApp.java ** Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. **/ packagepackagepackage package com.rim.samples.docs.baseapp; importimportimport import net.rim.device.api.i18n.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import com.rim.samples.docs.resource.*; publicpublicpublic public abstractabstractabstract abstract classclassclass class BaseApp extendsextendsextends extends UiApplication implementsimplementsimplements implements BaseAppResource, KeyListener, TrackwheelListener { privateprivateprivate private MenuItem _closeItem; privateprivateprivate private staticstaticstatic static ResourceBundle _resources = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME); /* Constructor for the abstract base class. */ publicpublicpublic public BaseApp() { _closeItem = newnewnew new MenuItem(_resources, MENUITEM_CLOSE, 200000, 10) { publicpublicpublic public voidvoidvoid void run() { onExit(); System.exit(0); } }; } By Taiguo Zhang (confach@gmail.com) 23 /* Override this method to add custom menu items. */ protectedprotectedprotected protected voidvoidvoid void makeMenu( Menu menu, intintint int instance) { Field focus = UiApplication.getUiApplication(). getActiveScreen().getLeafFieldWithFocus(); ififif if (focus != nullnullnull null ){ ContextMenu contextMenu = focus.getContextMenu(); ififif if (!contextMenu.isEmpty()) { menu.add(contextMenu); menu.addSeparator(); } } menu.add(_closeItem); } /* Invoked when the trackwheel is clicked. */ publicpublicpublic public booleanbooleanboolean boolean trackwheelClick( intintint int status, intintint int time ){ Menu menu = newnewnew new Menu(); makeMenu( menu, 0); menu.show(); returnreturnreturn return truetruetrue true ; } /* Invoked when the trackwheel is released. */ publicpublicpublic public booleanbooleanboolean boolean trackwheelUnclick( intintint int status, intintint int time ){ returnreturnreturn return falsefalsefalse false ; } /* Invoked when the trackwheel is rolled. */ publicpublicpublic public booleanbooleanboolean boolean trackwheelRoll(intintint int amount, intintint int status, intintint int time) { returnreturnreturn return falsefalsefalse false ; } publicpublicpublic public booleanbooleanboolean boolean keyChar(charcharchar char key, intintint int status, intintint int time) { /* Intercept the ESC key and exit the application. */ booleanbooleanboolean boolean retval = falsefalsefalse false ; switchswitchswitch switch (key) { casecasecase case Characters.ESCAPE: onExit(); System.exit(0); retval = truetruetrue true ; breakbreakbreak break ; } By Taiguo Zhang (confach@gmail.com) 24 returnreturnreturn return retval; } /* Implementation of KeyListener.keyDown(). */ publicpublicpublic public booleanbooleanboolean boolean keyDown(intintint int keycode, intintint int time) { returnreturnreturn return falsefalsefalse false ; } /* Implementation of KeyListener.keyRepeat(). */ publicpublicpublic public booleanbooleanboolean boolean keyRepeat(intintint int keycode, intintint int time) { returnreturnreturn return falsefalsefalse false ; } /* Implementation of KeyListener.keyStatus(). */ publicpublicpublic public booleanbooleanboolean boolean keyStatus(intintint int keycode, intintint int time) { returnreturnreturn return falsefalsefalse false ; } /* Implementation of KeyListener.keyUp(). */ publicpublicpublic public booleanbooleanboolean boolean keyUp(intintint int keycode, intintint int time) { returnreturnreturn return falsefalsefalse false ; } protectedprotectedprotected protected abstractabstractabstract abstract voidvoidvoid void onExit(); } 使用使用使用 使用 BlackBerryBlackBerryBlackBerry BlackBerry IDEIDEIDE IDE 为了编写,调试和编译应用程序,使用 BlackBerry IDE,它是 BlackBerry JDE 的一部分。 注:BlackBerry 版本 4.1 使用了 Sun JDK 5.0 创建一个工作空间 1. 在BlackBerry IDE,选择 File File File File 菜单,点击 NewNewNew New WorkspaceWorkspaceWorkspace Workspace 2. 在WorkspaceWorkspaceWorkspace Workspace name name name name 域,输入一个没有文件扩展名的名字。 3. 在CreateCreateCreate Create ininin in thisthisthis this directory directory directory directory 域,输入一个文档。 4. 点击 OK.OK.OK. OK. By Taiguo Zhang (confach@gmail.com) 25 创建一个项目 注:在包含工作空间的文件夹下的子目录中创建工程。 1. 在BlackBerry IDE 的Project Project Project Project 菜单,点击 CreateCreateCreate Create NewNewNew New Project.Project.Project. Project. 2. 在ProjectProjectProject Project name name name name 域,输入一个没有文件扩张名的项目名称。 3. 在CreateCreateCreate Create projectprojectproject project ininin in thisthisthis this directory directory directory directory 域,输入在此文件夹下创建项目的文件夹名称。 4. 点击 OKOKOK OK 。 5. 在工作空间文件区域里,双击项目名称来设置项目属性。 为了得到更多关于项目属性的信息,参看 BlackBerry Integrated Development Environment Online Help。 创建源文件 注:保存源文件到和项目文件相同的文件夹下。和所有 Java 程序一样,为符合你的类 使用的包层次关系的源代码创建文件结构, 1. 在BlackBerry IDE 的File File File File 菜单,点击 NewNewNew New 。 2. 在SourceSourceSource Source filefilefile file name name name name 域,输入一个带.java 文件扩展的文件名。 3. 在CreateCreateCreate Create sourcesourcesource source filefilefile file ininin in thisthisthis this directory directory directory directory 域,输入文件夹名。 4. 点击 OKOKOK OK 。 5. 在编辑器区域,右击文件,然后点击 InsertInsertInsert Insert intointointo into projectprojectproject project . 6. 选择一个项目。 7. 点击 OK。 和BlackBerryBlackBerryBlackBerry BlackBerry IDE IDE IDE IDE 集成源文件管理工具 你可以通过不同的源文件控制程序来使用 BlackBerry IDE。BlackBerry IDE 允许你为源文 件控制程序设置“check out”,“add new file” 和“revert”选项。在你为某一特定的源文件控制程 序配置好选项后,BlackBerry IDE 可以自动 check out 文件,运行预先设置的命令,恢复改 变,以及增加新创建的文件到源文件控制程序里。 1. 在BlackBerry IDE 的File File File File 菜单,点击 EditEditEdit Edit ->PreferencesPreferencesPreferences Preferences . 2. 点击 SourceSourceSource Source ControlControlControl Control 标签。 3. 点击 CheckCheckCheck Check out out out out 标签。 4. 在CheckCheckCheck Check out out out out 域,输入命令以能打开一个文件来编辑。例如输入: p4 edit %1 注:%1 参数代表了名称和一个文件的绝对路径。例如:对于一个在 c:\mypath 的 foo.java 文件,当BlackBerry IDE 执行命令 checkout %1,时,BlackBerry IDE 实际上运行命 令checkout c:\mypath\foo.java。 5. 点击 AddAddAdd Add file file file file 标签。 6. 在AddAddAdd Add newnewnew new file file file file 域,输入命令以增加一个新的文件到源文件控制程序中。例如输入: By Taiguo Zhang (confach@gmail.com) 26 p4 add %1 7. 点击 RevertRevertRevert Revert file file file file 标签. 8. 在RevertRevertRevert Revert changes changes changes changes 域,输入命令撤销一个源文件控制程序中的文件。例如输入: p4 revert %1 9. 单击 OKOKOK OK 。 编译项目 当你编译项目时,BlackBerry IDE 编译你的源文件到 Java 字节编码(Bytecode),进行预验 证,然后将类打包为一个.cod 文件。 注 :在Java ME中,字节编码验证分为2个步骤。已经编译的代码在它加载到 BlackBerry 设备之前预先验证,因此类加载时 BlackBerry 设备仅必须进行基本的验证。BlackBerry IDE 在编译项目时自动进行预验证。 当你编译一个项目时,如果需要,BlackBerry 也编译项目依赖的任何库。 缺省的,已经编译的.cod 文件使用项目名。为了改变这个名字,双击项目文件,点击 BuildBuildBuild Build 标签,输入 outputoutputoutput output filefilefile file namenamename name (输出文件名)。 混淆应用程序 和传统的编译器不一样,BlackBerry 平台的编译器因为受限的无线环境而优化了。在这里无 线环境的目标是最小化应用程序的大小。作为结果的.cod 文件提供了大量的类似于其他真正 混淆包的混淆服务,以致能减小.cod 文件本身的大小。例如,下面的信息将从.cod 文件中移 除:  所有调试的信息  本地变量名  源代码的行数  私有方法和成员名 同样,RIM 相信没有必要为应用程序提供混淆,除了已经存在的 BlackBerry 平台编译的所 有应用程序缺省提供的混淆。事实上,RIM 没有对自己的产品进行任何额外的混淆。 对通过第三方工具混淆的支持没有集成到 BlackBerry JDE.同样,需要一个命令行过程来混 动作 过程 附加信息 编译所有项目 > 在Build Build Build Build 菜单,点击 BuildBuildBuild Build AllAllAll All 为了排除一个项目,设置项目属性。 编译所有活动的 项目 > 在Build Build Build Build 菜单,点击BuildBuildBuild Build 。 在工作空间,活动的项目名是加粗的。 为了改变哪些项目是活动的,在 Project Project Project Project 菜单,点击 SetSetSet Set ActiveActiveActive Active ProjecProjecProjec Projec tststs ts . 编译单一项目 1. 选择一个项目 2. 在Build Build Build Build 菜单,点击BuilBuilBuil Buil ddd d SelectedSelectedSelected Selected 。 ――― 创建一个工作空 间makefile >在Build Build Build Build 菜单,点击 GenerGenerGener Gener ateateate ate MakefileMakefileMakefile Makefile andandand and Resource.Resource.Resource. Resource. ――― By Taiguo Zhang (confach@gmail.com) 27 淆.cod 文件供 BlackBerry 设备上使用。 混淆一个.cod .cod .cod .cod 文件 1. 在BlackBerry IDE,创建应用程序 提示:在这个过程中将项目文件放到一个独立的目录中。 2. 创建临时的目录 3. 将BlackBerry IDE 创建的 jar 文件拷贝到一个临时目录。 4. 释放.jar 文件的内容到一个临时目录。例如,在命令窗口输入下面的命令:*jar*jar*jar *jar xvfxvfxvf xvf SampleApplication.jarSampleApplication.jarSampleApplication.jar SampleApplication.jar 5. 删除释放为.jar 文件部分的.cod 文件。 6. 删除.jar 文件 7. 混淆在临时目录下包含的类文件。 8. 使用下面的命令对临时目录的内容运行预验证工具: *preverify.exe*preverify.exe*preverify.exe *preverify.exe -verbose-verbose-verbose -verbose -d-d-d -d ... . -classpath-classpath-classpath -classpath ..\lib\net_rim_api.jar;..\lib\net_rim_api.jar;..\lib\net_rim_api.jar; ..\lib\net_rim_api.jar; 9. 在已混淆(和预验证)的类文件上运行 rapc rapc rapc rapc 来创建一个.cod 文件。使用下面的命令: *rapc.exe*rapc.exe*rapc.exe *rapc.exe -verbose-verbose-verbose -verbose import=..\lib\net_rim_api.jarimport=..\lib\net_rim_api.jarimport=..\lib\net_rim_api.jar import=..\lib\net_rim_api.jar listing=SampleApplication.lstlisting=SampleApplication.lstlisting=SampleApplication.lst listing=SampleApplication.lst codename=SampleApplicationcodename=SampleApplicationcodename=SampleApplication codename=SampleApplication SampleApplication.rapcSampleApplication.rapcSampleApplication.rapc SampleApplication.rapc C:\yourTempDir\SampleApplication.classC:\yourTempDir\SampleApplication.classC:\yourTempDir\SampleApplication.class C:\yourTempDir\SampleApplication.class 生成API API API API 文档 使用一个 BlackBerry IDE 宏给代码加入注释。 一旦启用这个功能,如果你在一个函数声明前的任何一行输入/**,BlackBerry IDE 生成下面 的注释: /** * . *@param menu . *@param instance . *@return . */ 如果你在其他行输入/**, the BlackBerry IDE 生成下面的注释:: /** * . */ BlackBerry IDE 预先加载.作为查询字符串,再查询第一个实例,然后选择这个 实例,这个特性允许你输入描述,然后按 F3 转移到后面的参数。 因为 javadocs 宏依赖于分解浏览的信息,仅在成功完成一个编译后增加 javadocs。如果你的 文件包含一个语法错误,并且在上面你正试着插入注释,宏不会得到函数声明。 增加一个新的编辑器宏 1. 在Edit Edit Edit Edit 菜单,点击 PreferencesPreferencesPreferences Preferences 。 2. 点击 Editor Editor Editor Editor 标签。 3. 点击 Macros Macros Macros Macros 按钮。 By Taiguo Zhang (confach@gmail.com) 28 4. 从WhenWhenWhen When III I type type type type 下拉列表中,选择/**./**./**. /**. 5. 在ReplaceReplaceReplace Replace ititit it with with with with 文本框里,输入@javadoc@javadoc@javadoc @javadoc . 6. 在同一行或者每个函数声明的前一行输入/**。例如,在下面的代码段,在单词”protected” 开头的插入点输入/**. /** protected int makeMenu(Menu menu, int instance) {...} 使用命令行使用命令行使用命令行 使用命令行 BlackBerry JDE 包含一个命令行编译器 RAPC。RAPC 编译.java 和.jar 文件到可以运行在 BlackBerry 设备模拟器或者加载到 BlackBerry 设备上的.cod 文件。 Rapc.exe 在BlackBerry JDE 安装目录下的 Bin 下面。 RAPC 接收下面的命令行选项。 使用蓝牙开发环境使用蓝牙开发环境使用蓝牙开发环境 使用蓝牙开发环境 为了利用狼牙开发环境使用 BlackBerry 设备模拟器,你需要从 CSR (参看 http://www.btdesigner.com/devcasira.htm)得到普通开发系统。 利用一个 BlackBerry BlackBerry BlackBerry BlackBerry 设备模拟器使用蓝牙开发环境 1. 打开 BlackBerry IDE 2. 在主菜单,选择 EditEditEdit Edit >PreferencesPreferencesPreferences Preferences . 3. 选择 Simulator Simulator Simulator Simulator 标签。 4. 选择 Ports Ports Ports Ports 标签。 5. 在CommunicationCommunicationCommunication Communication portportport port type type type type 域,选择合适的端口类型(参看 Casira Endpoint 文 档 )。 6. 在Serial Port 域,输入端口信息。 7. 点击 OKOKOK OK 。 使用使用使用 使用 Eclipse Eclipse Eclipse Eclipse 开发环境开发环境开发环境 开发环境 Java 调试有线协议(Java Debug Wire Protocol, JDWP)的程序为 BlackBerry 模拟器提供一个 接口。当你启动 JDWP 时,你可以使用第三方集成的开发环境。 选项 描述 import 指明 RIMAPI和其他依赖的库 codename 指明应用程序名(这典型是.jar 文件名) midlet 指明应用程序是否是 MIDlet jad 指明 JAD 文件名 \filename_1.java[附加.java 文件如果需要>] 指明.java 文件名,如果正在编译 java 文 件 。 \JAR_filename.jar 指明.jar 文件名,如果正在编译一个,jar 文件。 By Taiguo Zhang (confach@gmail.com) 29 启动JDWPJDWPJDWP JDWP >点击开始>程序>ResearchResearchResearch Research InInIn In MotionMotionMotion Motion >BlackBerryBlackBerryBlackBerry BlackBerry JDEJDEJDE JDE 4.1.04.1.04.1.0 4.1.0 >JDWPJDWPJDWP JDWP . 注:在启动 JDWP 之前,你必须从 BlackBerry IDE 启动 BlackBerry 设备模拟器至少一 次。你仅需要启动 JDWP 一次。为了启动一个设备模拟器,在 Eclipse 开发环境中,点击 RunRunRun Run >DebugDebugDebug Debug . 连接Eclipse Eclipse Eclipse Eclipse 开发环境 注:在完成本节的任务之前,安装和配置 Eclipse 开发环境。 完成下面的步骤: 1. 扩展 Sun JRE。 2. 加入 API文档。 3. 设置 Builder。 4. 设置项目变量。 扩展SunSunSun Sun JREJREJRE JRE 1. 建立你的工作空间和项目。 2. 启动 Eclipse 平台。 3. 在Eclipse 任务栏,点击 WWW W iii i ndowndowndow ndow >PreferencesPreferencesPreferences Preferences . 4. 扩展 Java Java Java Java 项。 5. 选择 InstalledInstalledInstalled Installed JREsJREsJREs JREs 。 6. 点击 AddAddAdd Add 。 7. 在Add JRE 的窗口的 JREJREJRE JRE type type type type 域,选择 StandardStandardStandard Standard VMVMVM VM 。 8. 在JREJREJRE JRE name name name name 域,为 JRE 输入一个名字。 9. 在JREJREJRE JRE homehomehome home directory directory directory directory 域,输入 Sun JRE 的位置。例如: C:\Java\jdk1.5.0_02\jre. 10. 务必使 UseUseUse Use defaultdefaultdefault default systemsystemsystem system libraries libraries libraries libraries 域没有选。 11. 点击 AddAddAdd Add ExternalExternalExternal External JARsJARsJARs JARs 。 12. 浏览你的 BlackBerry IDE 安装目录下的 lib lib lib lib 目录,例如: C:\Program Files\Research In Motion\BlackBerry JDE 4.1.0\lib 13. 选择 net_rim_api.jarnet_rim_api.jarnet_rim_api.jar net_rim_api.jar . 14. 点击 OpenOpenOpen Open 。 加入API API API API 文档 1. 加入 RIM.jar 到你的项目。 2. 在Add JRE 窗口,扩展 net_rim_api.jar net_rim_api.jar net_rim_api.jar net_rim_api.jar 文件。 3. 选择 JavadocJavadocJavadoc Javadoc locationlocationlocation location . 4. 点击 EditEditEdit Edit 。 5. 点击 BrowserBrowserBrowser Browser 。 6. 找到找到你的 BlackBerry IDE 目录下的 docs\api docs\api docs\api docs\api 目录。例如: By Taiguo Zhang (confach@gmail.com) 30 C:\Program Files\Research In Motion\BlackBerry JDE 4.1.0\docs\api 7. 点击 OKOKOK OK 。 8. 点击 OKOKOK OK 。 9. 在Installed JREs 窗口,选择新创建的 JRE,缺省的是 RIMRIMRIM RIM JVMJVMJVM JVM 。 10. 在Add JRE 窗口,点击 OKOKOK OK 。 设置BuilderBuilderBuilder Builder 1. 在Eclipse 任务栏,点击 ProjectProjectProject Project >PropertiesPropertiesProperties Properties . 2. 选择 BuildersBuildersBuilders Builders 。 3. 点击 NewNewNew New 。 4. 在Choose configuration type 窗口,选择 ProgramProgramProgram Program . 5. 点击 OK。 6. 在New_Builder 窗口属性的 Name Name Name Name 域,为你的 Builder 输入一个名字。 7. 在Location Location Location Location 域,点击 BrowserBrowserBrowser Browser FileFileFile File SystemSystemSystem System 。 8. 找到你的 BlackBerry IDE 目录下的 Bin Bin Bin Bin 目录,例如: C:\Program Files\Research In Motion\BlackBerry JDE 4.1.0\bin 9. 选择 rapc.exe rapc.exe rapc.exe rapc.exe 文件。 10. 点击 OpenOpenOpen Open 。 设置项目变量 1. 在WorkingWorkingWorking Working Directory Directory Directory Directory 域,点击 VariablesVariablesVariables Variables 。 2. 在Select Variable 窗口,选择 buildbuildbuild build projectprojectproject project . 3. 点击 OKOKOK OK 。 4. 在Arguments Arguments Arguments Arguments 域,输入: -quiet-quiet-quiet -quiet [desired[desired[desired [desired spacespacespace space separatedseparatedseparated separated java,java,java, java, class,class,class, class, jar,jar,jar, jar, ororor or jadjadjad jad files]files]files] files] import=import=import= import= ””” ” C\ProgramC\ProgramC\Program C\Program Files\ResearchFiles\ResearchFiles\Research Files\Research InInIn In Motion\BlackBerryMotion\BlackBerryMotion\BlackBerry Motion\BlackBerry JDEJDEJDE JDE 4.1.0\lib\net_rim_api.jar4.1.0\lib\net_rim_api.jar4.1.0\lib\net_rim_api.jar 4.1.0\lib\net_rim_api.jar ””” ” codename=C:\Development\ProjectNamecodename=C:\Development\ProjectNamecodename=C:\Development\ProjectName codename=C:\Development\ProjectName 例如: -quiet-quiet-quiet -quiet C:\Development\TestProject\*.javaC:\Development\TestProject\*.javaC:\Development\TestProject\*.java C:\Development\TestProject\*.java import=import=import= import= ””” ” C:\ProgramC:\ProgramC:\Program C:\Program Files\ResearchFiles\ResearchFiles\Research Files\Research InInIn In Motion\BlackBerryMotion\BlackBerryMotion\BlackBerry Motion\BlackBerry JDEJDEJDE JDE 4.1.0\lib\net_rim_api.jar4.1.0\lib\net_rim_api.jar4.1.0\lib\net_rim_api.jar 4.1.0\lib\net_rim_api.jar ””” ” codename=C:\Development\TestProject.codename=C:\Development\TestProject.codename=C:\Development\TestProject. codename=C:\Development\TestProject. 5. 点击 OKOKOK OK 。 6. 在New_Builder 窗口属性里,点击 BuildBuildBuild Build Options Options Options Options 标签。 7. 在Run the builder 部分,验证下面的选项是否选择了。   AfterAfterAfter After aaa a “““ “ CleanCleanClean Clean ””” ”   DuringDuringDuring During manualmanualmanual manual buildsbuildsbuilds builds  DuringDuringDuring During autoautoauto auto buildsbuildsbuilds builds 8. 点击 OKOKOK OK 。 9. 在属性窗口,点击 OKOKOK OK 。 注:RAPC 不支持通配符,如果输入的路径发生错误,使用空格分隔文件列。例如将 C:\Development\TestProject\*.javaC:\Development\TestProject\*.javaC:\Development\TestProject\*.java C:\Development\TestProject\*.java 代替为 C:\Development\A.javaC:\Development\A.javaC:\Development\A.java C:\Development\A.java C:\Development\B.javaC:\Development\B.javaC:\Development\B.java C:\Development\B.java . By Taiguo Zhang (confach@gmail.com) 31 设置连接时间 当在 Eclipse 开发环境里调试时,为了阻止连接超时,为正在调试的程序设置超时连接值。 1. 在Eclipse 的任务栏,点击 WindowsWindowsWindows Windows >PreferencesPreferencesPreferences Preferences . 2. 扩展 Java Java Java Java 项。 3. 选择 DebugDebugDebug Debug 。 4. 在Communication Communication Communication Communication 部分的 DebuggerDebuggerDebugger Debugger timeout timeout timeout timeout 域,输入值。 5. 在LaunchLaunchLaunch Launch timeout timeout timeout timeout 域输入值。 注:你在 DebuggerDebuggerDebugger Debugger timeout timeout timeout timeout 和LaunchLaunchLaunch Launch timeout timeout timeout timeout 设置的值依赖你的计算机处理速度。如 果设置这些域之后连接问题继续出现,增加超时的值。 使用Eclipse Eclipse Eclipse Eclipse 开发环境进行调试 1. 在Eclipse 点击 RunRunRun Run >DebugDebugDebug Debug . 2. 选择 RemoteRemoteRemote Remote JavaJavaJava Java ApplicationApplicationApplication Application . 3. 点击 NewNewNew New 。 4. 点击 SSS S ooo o urce urce urce urce 标签。 5. 确认你的程序是否列出。 6. 点击 CloseCloseClose Close 。 7. 打开 JDWP JDWP JDWP JDWP 程序,为了得到更多信息,参看 27 页的“启动 JDWP“。 8. 在Eclipse 任务栏,点击 RunRunRun Run >DebugDebugDebug Debug . 9. 在RemoteRemoteRemote Remote JavaJavaJava Java ApplicatioApplicatioApplicatio Applicatio n项下面,选择一个应用程序。 10. 点击 DebugDebugDebug Debug 。 注:如果出现下面的错误信息:“Failed to connect to remote VM. Connection timed out”,增加调试超时时间。为得到更多信息参看 29 页的”设置连接时 间”. 编程指南编程指南编程指南 编程指南 编写高效的代码 使用本地变量 不管什么时候,尽量使用本地变量。访问本地变量比访问类的成员高效。 使用速记判断 Boolean Boolean Boolean Boolean 条件 为了代替第一个例子中没有必要的判断 Boolean 条件,使用第二个例子中的速记。最后编译 的代码会更短: ififif if ( boolean_expression == truetruetrue true ){ returnreturnreturn return truetruetrue true ; } By Taiguo Zhang (confach@gmail.com) 32 使类为 finalfinalfinal final 当你创建一个代码库时,如果你知道他们永远不会被扩展,那么将他们标记为 final。final 关键字的出现允许编译器生成更高效的代码。 注:缺省,BlackBerry JDE 编译器标记你应用程序.cod 文件中不会扩展的类为 final。 使用int int int int 代替longlonglong long 在Java 中,一个 long 代表的是 64 位的整数。因为 BlackBerry 设备使用的是一个 32 位的处 理器,如果你是用 int 代替 long,操作将会快 2-4倍。 避免垃圾回收 避免调用 System.gc()进行垃圾回收。这个操作会占用许多时间,特别是在内存受限的 BlackBerry 设备上。让虚拟机进行垃圾回收。 对字符串使用静态变量 当定义 String 类型的静态字段(也成类字段),可以用静态变量(非 final)代替 常 量(final) 加快程序速度。反之,对于原始数据类型,例如 int,也成立。 例如,你可能创建一个如下的 String 对象: 对于这个静态常量(由 final 关键字标识),你使用常量的每个时候都会创建一个临时的 String 对象。在字节代码中,编译器去掉”x”,代替它的是字符串“example”,以致每 次引用”x”时VM都会进行一次哈希表查询。 相比之下,度于静态变量(非final 关键字),字符串只创建一次。仅当初始化“x”时,VM 才进行哈希表查询。 注:你可以使用公共常量(也就是 final 字段),但是标记变量为私有。 避免String(String)String(String)String(String) String(String) 的构造子 避免使用 java.lang.String(String)构造子,因为它创建了一个没有必要的 String 对象,这个对象是作为参数提供的一个字符串的拷贝。因为 String 对象创建后不可以修 改,所以拷贝典型没有必要。 注:当使用字符串构造子时,编译器会由警告。 在Java 程序里,每个引用的字符串都作为一个 java.lang.String 类的对象。换言之, 你可以编写如下面的代码来创建一个 String。 elseelseelse else { returnreturnreturn return falsefalsefalse false ; } // Do this returnreturnreturn return ( boolean_expression ); privateprivateprivate private staticstaticstatic static finalfinalfinal final String x = "example"; String str = newnewnew new String("abc");// 避免. String str = newnewnew new String("found " + n + " items");// 避免. By Taiguo Zhang (confach@gmail.com) 33 编写有效的循环 在一个循环外考虑循环不变的代码。 在这个实现中,在每次的迭代中 vector.size()被调用,这是低效的。如果你的容器可能 不止一个元素,将容器的大小赋值给本地变量。下面的代码移除了循环不变的代码: 另外,如果你迭代的项的顺序并不重要,你可以向后迭代来避免栈上多余的本地变量,并且 可以使比较更加快速。 优化子表达式 假如你使用相同的表达式 2次,不要依赖编译器为你优化。使用本地变量,如下代码: 优化除法操作 除法操作在 BlackBerry 设备上可能慢,因为处理器没有硬件触发指令。 在你的代码中,当一个正数除以 2时,使用向右移一位(>>1)代替.仅当你知道你正在处理 的正数时使用“向右移位”(>>). 避免java.util.Enumerationjava.util.Enumerationjava.util.Enumeration java.util.Enumeration 避免使用 java.util.Enumeration 对象,除非你想隐藏数据(换句话说,为了返回一 个数据的枚举代替数据本身。 String str = "abc";// 建议. String str = "found " + n + " items";// 建议. //避免 forforfor for ( intintint int i = 0; i < vector.size(); i++ ){ ... } // 建议 intintint int size = vector.size(); forforfor for ( intintint int i = 0; i < size; ++i ){ ... } forforfor for ( intintint int i = vector.size() - 1; i >= 0; --i ){ ... } one( i+1 ); two( i+1 );// Avoid. intintint int tmp = i+1; one( tmp ); two( tmp );// Prefer. midpoint = width / 2; // Avoid. intintint int = width >> 1; // Prefer. // Avoid. forforfor for (Enumeration e = v.elements(); e.hasMoreElements();) { o = e.nextElement(); ... } By Taiguo Zhang (confach@gmail.com) 34 为一个 Enumeration 对象请求一个向量或者哈希表速度慢,并且创建了不必要的垃圾。 代替它的是,迭代元素本身,如下面的例子: 如果向量可能被其他线程修改,同步迭代,如下例子所示: 注:Java SE使用一个 Iterator 对象实现类似的功能,但是 iterator 在Java ME 不可 用。 使用instanceof instanceof instanceof instanceof 进行转型 使用 instanceof 代替捕捉一个 ClassCastException 异常来判断转型是否成功。 使用 instanceof 比用 try/catch 要快。当转型失败发生异常时才使用 try/catch。 在紧跟由一个 instanceof 检查的条件语句的第一个代码块里,BlackBerry IDE 编译器和虚拟 机被优化为仅对一个类检查。在由一个 instanceof 检查的条件语句后面的转型利用了这个优 化。 例如,编译器可以优化第一个例子,但是第二个不能: // Prefer. forforfor for ( intintint int i = v.size() - 1; i >=0; --i ){ o = v.elementAt( i ); ... } synchronizedsynchronizedsynchronized synchronized ( v ){ forforfor for ( intintint int i = v.size() - 1; i >=0; --i ){ o = v.elementAt( i ); ... } } // Avoid. trytrytry try { (String)x.whatever(); } catchcatchcatch catch ( ClassCastException e ){ ... } // Prefer. ififif if ( x instanceofinstanceofinstanceof instanceof String ){ (String)x.whatever(); } elseelseelse else { ... } // Prefer. ififif if ( a instanceofinstanceofinstanceof instanceof ){ instance = ()a; By Taiguo Zhang (confach@gmail.com) 35 使用instanceof instanceof instanceof instanceof 判断条件 为了编写较小而快的代码,如果使用 instanceof 判断条件,不要显式判断一个变量是否 为null。当”e”为null 时,表达式 e instanceof 判断为 false。 避免使用 StringBuffer.append(StringBuffer)StringBuffer.append(StringBuffer)StringBuffer.append(StringBuffer) StringBuffer.append(StringBuffer) CLDC 不包含 StringBuilder.append(StringBuilder)方法。采用将一个 string buffer 加到另一个的方法会创建一个 String 的中间对象。代替它的是,应用程序可以使 用net.rim.device.api.util.StringUtilities.append( StringBuffer dst, StringBuffer src[, int offset, int length ]). 减小代码大小 当编写应用程序时可以采用下面的指南来减小编译后代码的大小。 x.method(instance); instance.method(x, y, z); } // Avoid. ififif if ( a instanceofinstanceofinstanceof instanceof ){ x.method( ()a ); } // Avoid. ififif if ( e != nullnullnull null && e instanceofinstanceofinstanceof instanceof ExampleClass ){ ififif if ( e == nullnullnull null || !( e instanceofinstanceofinstanceof instanceof ExampleClass) // Prefer. ififif if ( e instanceofinstanceofinstanceof instanceof ExampleClass ){...} ififif if (!( e instanceofinstanceofinstanceof instanceof ExampleClass )){...} // Avoid. publicpublicpublic public synchronizedsynchronizedsynchronized synchronized StringBuffer append(Object obj) { returnreturnreturn return append(String.valueOf(obj)); } // Prefer. publicpublicpublic public synchronizedsynchronizedsynchronized synchronized StringBuffer append(Object obj) { ififif if (obj instanceofinstanceofinstanceof instanceof StringBuffer) { StringBuffer sb = (StringBuffer)obj; net.rim.device.api.util.StringUtilities.append( thisthisthis this , sb, 0, sb ) returnreturnreturn return thisthisthis this ; } returnreturnreturn return append(String.valueOf(obj)); } By Taiguo Zhang (confach@gmail.com) 36 设置适合的访问方式 当你创建代码库时,为字段和方法使用合适的访问权限可以显著减小编译后代码的大小。特 殊的是,完成以下操作:  不管什么时候,只要可能就将字段声明为 private。除了好的编码实践外,这可以使编译 器优化.cod 文件。  当可能时,使用缺省(包)的访问方式来代替 public 访问(也就是,忽略 public 和protected 关键字) 避免创建接口 当创建 API库时,避免创建接口,除非你预知 API的多个实现。接口会产生更大更慢的代 码。 使用内部的静态类 当创建一个内部的类隐藏一个在其他类里的类时,但是内部类没有引用外部类对象,声明这 个内部类为 static。这个操作压缩了对外部类引用的创建。 例如,下面的代码需要一个对外部类对象的引用。 比较而言,下面的代码仅仅定义了内部类名的范围: 前一个例子是下面的缩写版本: 当在内部类的方法里需要访问外部类数据时,仅仅使用一个非静态的内部类。如果为命名范 围使用一个类,那么使这个类为 static。 避免没有必要的初始化 在类里避免没有必要的字段初始化,这些类里,字段有缺省值。如果在一个类里没有初始化 一个字段,它会自动使用下面的缺省值初始化字段。 // Avoid. classclassclass class outer { intintint int i; classclassclass class inner { inner() {} intintint int example() { returnreturnreturn return i;} } } // Prefer. classclassclass class outer { staticstaticstatic static classclassclass class inner { ... } } classclassclass class outer { ... } classclassclass class outer$inner { ... } By Taiguo Zhang (confach@gmail.com) 37  对象引用初始化为 null  int,byte 或long 初始化为 0  boolean 初始化为 false 例如,下面的代码段没有不同: 注::: : 在一个方法里,必须显式初始化本地变量。 导入单独的类 一个应用程序仅使用了来自一个包的少量的类,这个程序应该导入单独的类,而不是整个 库。 在BlackBerry BlackBerry BlackBerry BlackBerry 设备上使用时间 在对时间敏感的应用程序里,不要为任何事物依赖时间区域,除了显示本地时间给用户。 BlackBerryBlackBerryBlackBerry BlackBerry 设备钟 BlackBerry 设备操作系统从 January 1, 1970 (UTC)的午夜以毫秒来计算绝对时间。时间一般 以CPU 周期或毫秒来计量的。 系统时间区域改变 如果因为性能原因正在缓存对时间敏感的对象,那么记住 BlackBerry 设备上的系统时间区 域可能会改变。 当时间区域改变时,系统会发送一个全局的事件消息给应用程序。GlobalEventListener 的实现,包括 eventOccurred() ,会接受这个事件。利用 invoking Application.addGlobalEventListener()注册你的实现。 // Avoid. classclassclass class BadExample { privateprivateprivate private intintint int fieldsCount = 0; // Avoid. privateprivateprivate private Field _fieldWithFocus = nullnullnull null ;// Avoid. privateprivateprivate private booleanbooleanboolean boolean _validLayout = falsefalsefalse false ;// Avoid. } // Prefer. classclassclass class BetterExample { privateprivateprivate private intintint int fieldsCount;// Prefer. privateprivateprivate private Field _fieldWithFocus; // Prefer. privateprivateprivate private booleanbooleanboolean boolean _validLayout;// Prefer. } // Avoid. importimportimport import net.rim.blackberry.api.browser.*; // Prefer. importimportimport import net.rim.blackberry.api.browser.Browser; publicpublicpublic public voidvoidvoid void eventOccurred( longlonglong long guid, intintint int data0, intintint int data1, Object By Taiguo Zhang (confach@gmail.com) 38 决定手持设备上的网络时间 调用RadioInfo.GetNetworkTime(long deviceTime)得到以毫秒计量的对应网络 报告时间,然后调整本地时间。deviceTime 参数代表现在的毫秒级时间。 建议的实践 使用多线程 有效的利用 BlackBerry 操作系统多线程的能力。特殊地,为网络连接或长操作(大于 0.1 秒 ) 创建线程。为监听者使用背后(Background)线程,或者当程序启动时使用在背后运行地其 他进程。 最小化内存地使用 为了最小化运行时内存,使用下面地指南:  使用原始类型(如 int 或Boolean)代替对象(如 String 或Integer)。  不要全部依赖垃圾回收。避免快速地创建多个对象。当完成使用他们时,将对象引用设 置为 null。尽可能重用对象。  将大地操作一到 Server 上,例如,在发送数据到 BlackBerry 设备之前,完成对数据地 过滤或排序。 避免返回 nullnullnull null 如果你正在编写一个公共地方法返回一个对象,仅在下面地条件下它可以返回一个 null:  在正常地程序运行期间,null 对象时期望的。  Javadoc @return 参数描述了 null 是一个可能的返回值。 如果一个 null 返回值不是正常期望的,那么程序将抛出一个合适的异常强迫调用者显式的 处理这个问题。调用者不期望检验一个 null 的返回值,除非文档说明了。 避免传递 null null null null 给方法 不要传递一个 null 参数给 API 方法,除非 API 引用显式说明了方法支持他们。 小心传递 null null null null 参数给构造子 当传递 null 参数给构造子时,为了避免混淆,将 null 转化为合适的对象: new someObject ((someObject)null ); 如果一个类有两个或多个构造子,传递 null 参数可能不会唯一识别哪一个构造子将会使 用。结果编译器会报错。在 API 参考里,并不是所有的构造子都会出现,因为有些构造子 仅供内部使用。 通过转化 null 为合适的对象,你可以明确指明编译器会使用哪一个构造子。如果后续的 API 发行版本增加了新的构造子,它也可向前兼容。 使用long long long long 标记唯一标志符 object0,Object object1 ){ ififif if ( guid == DateTimeUtilities.GUID_TIMEZONE_CHANGED ){ _cal.setTimeZone( TimeZone.getDefault() ); } } By Taiguo Zhang (confach@gmail.com) 39 使用一个 long 的标志符代替 String 标志符来标记唯一的常数,如 GUID,哈希表键值, 以及状态或上下文标志。 对于跨越第三方应用程序的标志符,为了保留其独立性,使用基于 string 生成的哈希生 成的键值。在输入字符串里,包含了足够的信息提供唯一性。例如,使用一个完全信任的包 名,如 com.rim.samples.docs.helloworld。 转化一个 string string string string 为longlonglong long 1. 在BlackBerry IDE 文本编辑器里,输入一个字符串。 2. 选择字符串。 3. 右击字符串。 4. 选择 ConvertConvertConvert Convert ””” ” StringStringString String ””” ” tototo to LongLongLong Long . 正确退出应用程序 在调用 System.exit(int status)之前,你的程序应该完成任何清理,例如移除在运行 时存储的程序不在需要的对象。 打印栈跟踪(StackStackStack Stack tracetracetrace trace ) 当VM发现代码使用 catch(Exception e)捕获异常时,VM优化为排除栈跟踪。如果捕 获到 Throwable,它不会排除栈跟踪。 例如,下面的代码不会排除栈跟踪: 为了打印栈跟踪,编写类似下面的代码: 当你调试时为了查看栈跟踪,捕获一个 Throwable 实例。 catchcatchcatch catch (IOException e) { e.printStackTrace() } catchcatchcatch catch (Throwable t) { t.printStackTrace(); } By Taiguo Zhang (confach@gmail.com) 40 333 3 第333 3 章 创建用户接口(UIUIUI UI ) UIUIUI UI APIAPIAPI API 当你为 BlackBerry 设备编写应用程序时,使用下面 2组UIAPI的一组:  MIDPUIAPI(javax.microedition.lcdui 包)  BlackBerry UIAPI(net.rim.device.api.ui 包) 如果你正在编写一个在任何 MIDP 兼容设备上运行的应用程序,请使用 MIDPUIAPI.如果 你正在编写专门运行在 BlackBerry 设备上的应用程序,那就使用 BlackBerry UIAPI吧。 BlackBerry API提供了访问 BlackBerry 设备的特定特性的功能,并且也允许更成熟的 UI布 局(layout)和交互。 注::: : 不要在同一个程序里既使用 MIDPUIAPI,又使用 BlackBerry UIAPI,否则会抛出 异常。在应用程序中,UI框架支持一中类型的 UI对象。 显示显示显示 显示 UI UI UI UI 组件组件组件 组件 显示屏幕(Screen)(Screen)(Screen) (Screen) UI 的主要结构是 Screen。一个应用程序一次只能显示一个屏幕。 注::: : 不要使用 Screen 对象来输入文本。Screen 对象没有明确实现此功能,它需要复杂的 输入方法,例如国际化的键盘和 7100 系列的设备。为实现无缝得集成不同输入方法,扩展 Field 或者其任一子类。参看 53 页“创建定制的域”得到更多信息。 显示栈(StackStackStack Stack ) Screen 对象在一个一组有序的 Screen 显示栈里得到维护。在栈顶的 Screen 对象是显示给用 户的活动 Screen。当应用程序显示一个 Screen 时,它将这个 Screen 压入到栈顶。当关闭一 UIUIUI UI APIAPIAPI API 显示UIUIUI UI 组件。 管理UIUIUI UI 组件 创建客户定制的 UIUIUI UI 组件 操作图片 使用图像对象画图 监听UIUIUI UI 对象的改变 By Taiguo Zhang (confach@gmail.com) 41 个Screen,将这个 Screen 从栈里移出,然后显示栈里的下一个 Screen,如果必要会重绘它。 注::: : 每个 Screen 在栈里只出现一次。如果同一个 Screen 压入到栈不止一次,VM会抛出 一个运行时异常。当用户完成和 Screen 交互,应用程序必须将 Screen 从栈里移出,以致内 存不必再用。不要在同一时间里使用多个 Screen,因为每个 Screen 使用独立的线程。 Screen Screen Screen Screen 的类型 在多数情况下,创建一个 Screen 最有效的方法是创建一个扩展 Screen 或其任一子类, FullScreen 或MainScreen 的类。 响应用户交互 BlackBerry API提供一个和 Java 标准版本类似的事件监听框架。特殊的,2个监听接口使程 序接收和响应用户交互:TrackWheelListener 和KeyboardListnener。Screen 类 和其子类都实现了这些方法。 提供screen screen screen screen 导航(navigationnavigationnavigation navigation ) BlackBerry 应用程序为用户提供一个菜单来完成操作。避免使用按钮(Button)或其他占据 Screen 空间的 UI组件。 注::: : 按滑轮访问菜单。 当创建一个 FullScreen 或Screen ,在构造子里指明 DEFAULT_MENU 和 DEFAULT_CLOSE 参数来提供缺省的导航。 FullScreen fullScreen = newnewnew new FullScreen(DEFAULT_MENU | DEFAULT_CLOSE); 类 描述 Screen 使用 Screen 类定义一个管理器布局 Screen 上的 UI组件,并且使用在超类 Field 定义的常数的样式(Style)定义一明确的 Screen。 FullScreen 缺省的,一个 FullScreen 包含单个垂直①的域管理器(Field Manager)。使 用 一个 FullScreen 提供了一个空的 Screen,在这个空的 Screen 上,你可以增加 UI组件到这个标准的垂直布局里。如果需要另外类型的布局,例如水平的或 对角的,使用一个 Screen 类,并且在里面增加一个管理器。 MainScreen MainScreen 类提供常见的标准 BlackBerry 应用程序常见特性。对你的应用程 序的第一个 Screen,使用一个 MainScreen 对象来保持和其他 BlackBerry 应 用程序的统一。MainScreen 提供一下的 UI组件:  Screen 标题的缺省位置,标题后的一个 SeperatorField  一个包含在 VerticalManager 里的滚动的主界面。  有一个 Close Close Close Close 菜单项的菜单。  当用户点击 Close Close Close Close 菜单项或者按 Escape 键时缺省的关闭操作。 参数 描述 DEFAULT_MENU 这个参数增加一个缺省的菜单,它包含了不同的菜单项,这依赖域用 户的上下文环境。例如,如果一个 EditField 获得焦点,将显示 CCC C ututut ut , Copy Copy Copy Copy 和Paste Paste Paste Paste 菜单项。所有已选择的域提供 Select Select Select Select 和CancelCancelCancel Cancel ① 在BlackBerry Screen 布局里,有水平,垂直,对话框以及流四种。译者注。 By Taiguo Zhang (confach@gmail.com) 42 当创建一个 MainScreen 时,缺省的导航会自动提供。 增加菜单项 创建 MenuItem 对象。 MenuItem 构造子接受下面的 3个参数: run()定义了当用户点击菜单项发生的操作的实现。如果你没有使用本地资源,重写 toString()方法来指定菜单项的名字。 为了在应用程序加入上下文菜单给 field,调用 getLeafFieldWithFocus(),并且调 用getContextMenu(),其返回值决定哪一个 Field 接收 makeMenu()里的客户化菜 单项。为了得到更多信息,参看 60页的“创建客户化的上下文菜单”。 当增加你自己的菜单项时,显式的定义一个 Close Close Close Close 菜单项。 为了增加菜单项到 Screen 里,重写 Screen.makeMenu()方法: 如果你扩展 Screen 或其任一子类,那么当用户点击滑轮时,缺省的 TrackwheelListener 实现调用 makeMenu( )。 如果你没有扩展 Screen,那么实现 TrackwheelListener。特殊 地 ,trackwheelClick ()的实现创建一个新的菜单,增加菜单项以及在 Screen 上显示菜单。 注::: : 为了创建菜单项提供附加的功能,请扩展 MenuItem 类。为了得到更多信息,参 Selection Selection Selection Selection 菜单项。 DEFAULT_CLOSE 这个参数增加一个缺省行为的 Close Close Close Close 菜单项到菜单,当用户点击 CloCloClo Clo sesese se 菜单项或者按 Escapes Escapes Escapes Escapes 按钮,如果 Screen 上的任何东西改变,一个确 认的对话框将会出现。如果这个 Screen 是栈里的唯一一个 Screen,应 用程序将关闭。 privateprivateprivate private MenuItem viewItem = newnewnew new MenuItem("View Message", 100, 10) { publicpublicpublic public voidvoidvoid void run() { Dialog.inform("This is today’s message"); } }; 参数 描述 text 菜单项的名称 ordinal 菜单项的顺序;一个越大的值表明了这个菜 单项越靠近菜单的底部。 priority 接收缺省焦点的菜单项优先级 protectedprotectedprotected protected voidvoidvoid void makeMenu(Menu menu, intintint int instance) { menu.add(viewItem); menu.add(closeItem); } publicpublicpublic public booleanbooleanboolean boolean trackwheelClick(intintint int status, intintint int time) { Menu appMenu = newnewnew new Menu(); makeMenu(appMenu, 0); // Add menu items. appMenu.show(); // Display the menu on screen. returnreturnreturn return truetruetrue true ; } By Taiguo Zhang (confach@gmail.com) 43 看60 页“创建客户化的上下文菜单”。 显示对话框 PopupScreen 类通过使用它的子类,Dialog 和Status,来提供创建对话框和状态 Screen 的特性。Popup screen 不会压入到显示栈中,为了显示一个 popup screen,调用 Dialog.ask(int)或Status.show(). 为了控制对话框的布局,使用 DialogFieldManager 对象,为了得到更多的信息,参看 50 页的“为一个 PopupScreeen 指定布局”。 为了显示一个对话框,使用下面的一个参数来调用 Dialog.ask(): 为了指定一个对话框的缺省的响应,使用一个接受 defaultChoice 作为参数的 Dialog.ask()版本。 显示状态消息 调用 Status.Show()显示一个状态消息。缺省的,状态屏幕保留其屏幕 2秒钟。 Status.show("Status screen message"); 参看 API 参考获取 Status.Show()的版本信息,它使你可以指定额外的参数,例如不同的图 标或者保持状态对话框可见的时间长短。你可以创建模态的状态对话框(需要用户取消它们), 也可以创建计时的状态对话框(在指定的时间后自动取消)。 参数 描述 D_OK 显示一个字符串,并且提示用户点击 OK。 D_SAVE 实现一个字符串,并且提示用户点击 SaveSaveSave Save , DiscardDiscardDiscard Discard ,或者 CancelCancelCancel Cancel ;按Escape Escape Escape Escape 取消。 D_DELETE 显示一个字符串,并且提示用户点击 DeletDeletDelet Delet eee e 或者 CancelCancelCancel Cancel ;按Escape Escape Escape Escape 撤销。 D_YES_NO 显示一个字符串,并且提示用户点击 Yes Yes Yes Yes 或 NoNoNo No 。 intintint int response = Dialog.ask(Dialog.D_SAVE); ififif if (Dialog.SAVE == response || Dialog.CANCEL == response) returnreturnreturn return falsefalsefalse false ; ififif if ( Dialog.DISCARD == response ) _item.deleteItem(_itemIndex); intintint int response = Dialog.ask(Dialog.D_YES_NO, "Are you sure?", Dialog.NO); By Taiguo Zhang (confach@gmail.com) 44 显示域(FieldFieldField Field ) 所有 UI组件以包含在管理器里的成矩形的 field 的形式表现。Field 的大小取决于它的 布局需求。管理器为它们包含的 field 提供滚动(条)。 BlackBerry JDE在net.rim.device.api.ui.component 包里提供一个预创建接口 控件和组件的库。多数情况下,你可以使用这些对象构建 UI应用程序。 为了创建指定的 field 控件(如包含多个元素的文本 field),扩展 Field 类或者其任 意子类来创建你自己定制的类型。为得到更多信息,参看 53页的“创建定制的 field”。 注:参看 API 参考获取更多关于指定 field 类的有效、支持的格式的信息。如果使用一 个不支持的格式实例化一个 Field,将抛出一个 IllegalArgumentException 异常。 BitmapBitmapBitmap Bitmap FieldFieldField Field 一个 BitmapField 包含了位图。当使用 Graphics 对象绘图时使用 BitmapField。为了修 改一个 field 的内容,调用 BitmapField 的绘图方法。为得到更多信息,参看 73页的“使 用graphics 对象绘图”. 有4种预定义的位图:  Bitmap.INFORMATION  Bitmap.QUESTION  Bitmap.EXCLAMATION  Bitmap.HOURGLASS 为了使用原始的.gif 或.png 作为位图,调用 getBitmapResource(). 注:一个二进制资源的大小,如一个.png 文件,不能超过 63,000 字节。 ButtonButtonButton Button FieldFieldField Field ButtonField 包含了用户选择来完成操作的按钮。使用 ButtonField 可以创建超出菜单的 扩展交互的界面。 Bitmap myBitmap = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION); BitmapField myBitmapField = newnewnew new BitmapField(myBitmap.getPredefinedBitmap(myBitmap)); ... mainScreen.add(myBitmapField); privateprivateprivate private staticstaticstatic static finalfinalfinal final Bitmap myBitmap = Bitmap.getBitmapResource("customBitmap.gif"); ... BitmapField bitmapField = newnewnew new BitmapField(myBitmap); mainScreen.add(bitmapField); By Taiguo Zhang (confach@gmail.com) 45 为了给 button 增加功能,扩展 ButtonField 并且覆写 trackwheelClick()方法,以 让它能完成一个操作来代替调用菜单。当用户点击 button 后为了接受消息,使用一个 FieldChangeListener 对象。为得到更多信息,参看 78页的“监听 UI对象的改变”. ChoiceChoiceChoice Choice FieldFieldField Field Choice field 类似于下拉列表。这里有 2种choice field:包含整数的和包含可以转 化为字符串的对象。 你也可以显示一组选项作为 check box 或者 radio button。为了得到更多信息,参看 45页的“option field”。 为了从 ChoiceField 里选择一个值,用户可以完成下面的操作:  点击 field,并且按 Space 键。  按住 Alt 键,滚动滑轮。  打开菜单,选择 Change Option。 OptionOptionOption Option FieldFieldField Field OptionField 允许用户从列表种选择条目。为允许用户从选择列表中选择多个条目,使 用CheckBoxField 。为允许用户从选择列表中仅选择一个条目,使用 ButtonField mySubmitButton = newnewnew new ButtonField("Submit"); ButtonField myResetButton = newnewnew new ButtonField("Reset"); mainScreen.add(mySubmitButton); mainScreen.add(myResetButton); 类 描述 NumericChoiceField NumericChoiceField 是一个包含了一组 数字的ChoiceField 。 NumericChoiceField 实例典型的用在较 少的数字范围内(到 20个之多)。 NumericChoiceField myNumericChoice = newnewnew new NumericChoiceField( "Select a number: ", 1, 20, 10); mainScreen.add(myNumericChoice); 注:对于大数量的数字,使用GaugeField, 为得到更多信息,参看 47 页的“Gauge Field)。 ObjectChoiceField ObjectChoiceField 是一个包含了对象 的ChoiceField。这个 Field 的所有对象 必须实现 Object.toStirng()以提供他 们自己的字符串表现形式。 By Taiguo Zhang (confach@gmail.com) 46 RadioButtonField。 DateDateDate Date FieldFieldField Field 在你的应用程序中,一个 DateField 显示当前的日期和时间。 当创建一个 DateField 时,调用 System.currentTimeMillis()得到当前时间。 Date Field 缺省为可编辑的。为了创建一个用户不能编辑的 Date Field,在其构造子 中指定 Field.READONLY 参数。 将为可编辑的 Date Field 提供一个缺省的 ChangeChangeChange Change Options Options Options Options 菜单项。 类 描述 CheckBoxField 每个CheckBoxField 是一个独立的对象, 与其他的可选框无关。 CheckboxField myCheckbox = newnewnew new CheckboxField("First checkbox", truetruetrue true ); CheckboxField myCheckbox2 = newnewnew new CheckboxField("Second checkbox", falsefalsefalse false ); ... mainScreen.add(myCheckbox); mainScreen.add(myCheckbox2); RadioButtonField 多个 RadioButtonField 对象组合在一个 RadioButtonGroup 中,这样用户一次只 能选择一个选项。 RadioButtonGroup rbGroup = newnewnew new RadioButtonGroup(); RadioButtonField rbField = newnewnew new RadioButtonField("First field"); RadioButtonField rbField2 = newnewnew new RadioButtonField("Second field"); ... rbGroup.add(rbField); rbGroup.add(rbField2); ... mainScreen.add(rbField); mainScreen.add(rbField2); 类型 描述 DATE 显示年月日 DATE_TIME 显示年月日,时分秒 TIME 显示时分秒 DateField dateField = newnewnew new DateField("Date: ", System.currentTimeMillis(), DateField.DATE_TIME); mainScreen.add(dateField); By Taiguo Zhang (confach@gmail.com) 47 EditEditEdit Edit FieldFieldField Field 一个EditField 允许用户在此 Field 里输入文本。AutoTextEditField, EditField, 和 PasswordEditField 都扩展了 BasicEditField. 注:net.rim.device.api.ui.component.TextField 类,扩展了 Field 类,并且是抽象的。实例 化它的子类,例如RichTextField 或 EditField,就是创建一个显示文本或允许用户输入文本的 UI Field。 你可以应用下面的过滤项(filter)到 Edit Field 中。 过滤项 描述 DEFAULT_MAXCHARS 限制 field 中字符的个数。对于 Edit Field,字 符的最大个数缺省为 15。 FILTER_DEFAULT 这个是缺省的文本输入过滤项。当构造子需 要此过滤项,但是你又不想应用任何特定的 过滤项时,那么使用它。 FILTER_EMAIL 仅允许有效的 internet 消息地址字符(例如, 用户可以仅输入一个@标记).它会自动将文 本格式化为 internet 消息地址格式(例如,当 用户第一次按 Space 键时,一个@符号会出 现,用户接着按 Space 键,每次.’s也随之出 现。 FILTER_HEXADECIMAL 仅允许数字和 A到F的字母。 FILTER_INTEGER 仅允许数字和负号“-“。 FILTER_LOWERCASE 将字符转化为小写 FILTER_NUMERIC 仅允许输入数字 FILTER_PHONE 仅允许输入有效电话号码字符,数字,连接 号(-),加号和减号,左括号和右括号,以及”x”. FILTER_PIN_ADDRESS 仅接受在 PIN 地址上输入的有效字符。 FILTER_UPPERCASE 将字母转化为大写。 FILTER_URL 仅允许有效的 URL字符。它也自动格式化 field。(当用户按 Space 键时它将插入一个节 点). JUMP_FOCUS_AT_END 改变 field 的行为,以致当 field 获得焦点,并 且用户试图滚动时,焦点移动到 field 的末端 (代替移动到下一个 field)。 NO_NEWLINE 忽略文本中的换行和回车,例如用户从其他 地方拷贝和粘贴的文本。 类 描述 RichTextField RichTextField 类创建一个只读的 Field,它可 以体格式化为各种不同的字体以及样式, RichTextField 虽然不可以编辑,但是可以获 取焦点。 mainScreen.add(newnewnew new RichTextField( By Taiguo Zhang (confach@gmail.com) 48 "RichTextField")); BasicEditField BasicEditField 是EditField, 和 PasswordEditField 的基类。 BasicEditField 是一个可编辑的文本 Field,它没有缺省的格式,但是却可以接 受过滤。 BasicEditField bf = newnewnew new BasicEditField( "BasicEditField: ", "", 10, EditField.FILTER_UPPERCASE); mainScreen.add(bf); EditField EditField 是一个可以编辑的文本 Field,它扩 展了 BasicEditField。EditField 允许用户当问 特殊的字符。例如,用户按住 AAA A 键,并且滚 动滑轮来选择各种样式的 A字符以及Æ字 符。EditField 类接受样式,但是有些样式会 让EditField 失去其功能(例如 EditField.FILTER_PHONE). mainScreen.add(newnewnew new EditField("EditField: ","", 10, EditField.FILTER_DEFAULT)); PasswordEditField PasswordEditField 扩展了 BasicEditField,提 供下面的功能:  让用户输入显示为星号(*)。  自动文本(AutoText)(或其他自动格式 化)不会应用。  剪切或拷贝不支持 下面的样例使用了一个构造子,允许你为 PasswordEditField 提供了一个缺省的初始化 值。 mainScreen.add(newnewnew new PasswordEditField("PasswordEditFi eld: ","")); AutoTextEditField AutoTextEditField 应用了 AutoText 引擎指定 的格式。在本Field 中输入的任何文本都会根 据BlackBerry 设备上的 AutoText 数据库说明 来进行格式化。 某些过滤让一些 AutoText 输入没有效果。例 如 ,FILTTER_LOWERCASE render 一个包含 大写无效的 AutoText 输入。 mainScreen.add(newnewnew new AutoTextEditField("AutoTextEditFi By Taiguo Zhang (confach@gmail.com) 49 GaugeGaugeGauge Gauge FieldFieldField Field Gauge 允许你创建数值的可视表现。GaugeField 显示一个进度条或允许用户选择数字。你可 以使用一个 Label 作为它的前缀,并显示 gauge 的当前值。例如,组合一个 GaugeField 和一 个NumericChoiceField 来创建一个用户制作的数字选择的图形化表现。 为了创建一个交互的 GaugeField,使用 Field.FOCUSABLE 和Field.EDITABLE 样式实例化 field。 LabelLabelLabel Label (标签)和 SeparatorSeparatorSeparator Separator (分隔)FieldFieldField Field 一个 LabelField 允许你增加文本标签到屏幕中。LabelField 是可读的。缺省的,它不 能获得焦点。大部分应用程序在它们的第一个屏幕上使用 LabelField 来显示一个静态的 标题。 一个 SeparatorField 是一个静态的水平线,它跨越屏幕的宽度。使用SeparatorField 将屏幕上的相关内容和菜单分组。 MainScreen 缺省的在标题后显示一个分割线。 ListListList List FieldFieldField Field List 允许你创建子项的目录,通过此用户可以滚动并选择单个或多个条目。BlackBerry 地址簿就是 List 对象的一个例子。 你不可以直接将内容加入到 field 条目中。你的 ListField 的ListFieldCallback 和TreeField 的TreeFieldCallback 的实现会绘图 field。 eld: ","")); GaugeField staticGauge = newnewnew new GaugeField("1: ", 1, 100, 20, GaugeField.NO_TEXT); GaugeField percentGauge = newnewnew new GaugeField("Percent: ", 1, 100, 29, GaugeField.PERCENT) GaugeField interactiveGauge = newnewnew new GaugeField("Gauge: ", 1, 100, 60, Field.FOCUSABLE | Field.EDITABLE); ... mainScreen.add(staticGauge); mainScreen.add(percentGauge); mainScreen.add(interactiveGauge); LabelField title = newnewnew new LabelField( "UI Component Sample", LabelField.ELLIPSIS)); mainScreen.setTitle(title); 类 描述 ListField ListField 包含了数行可选条目。为了显示 ListField 的内容,为了列表设置 ListFieldCallback。为了得到更多信息,参看 66页的“创建一个回调对象”. String fieldOne = newnewnew new String("Mark Guo"); By Taiguo Zhang (confach@gmail.com) 50 TreeTreeTree Tree FieldFieldField Field TreeField 包含父节点和子节点,并且显示一个折叠夹或它们(例如文档或信息折叠夹) 之间的树关系。所有节点都是缺省可见的。为了指明一个折叠夹是否可以折叠,调用 TreeField 对象的 setExpand()方法。 图标显示在包含有子节点的每个节点边上以明确节点是打开的还是折叠的。 TreeFieldCallback 的实现加入 field 到树中。为了获得更多关于回调的信息,参看 66页的“创建一个回调对象”. String fieldTwo = newnewnew new String("Amy Krul"); ... ListField myList = newnewnew new ListField(); ListCallback myCallback = newnewnew new ListCallback(); myList.setCallback(myCallback); myCallback.add(myList, fieldOne); myCallback.add(myList, fieldTwo); ... mainScreen.add(myList); 注:为了使用户选择列表中的多个条目,指定 ListField 作 为 MULTI_SELECT.ListFieldCallback.add() 加入列表元素到向量中,并且调用 List.insert()决定适当的位置。 ObjectListField ObjectListField 是一个包含以对象作为子项 的list field。所有包含在 list 里的对象必须 实现Object.toString(),以提供他们自己的 字符串表现。在接口上,ObjectListField 以和 一个标准的 ListField 一样提供。 String fieldOne = newnewnew new String("Main folder"); ... TreeCallback myCallback = newnewnew new TreeCallback(); TreeField myTree = newnewnew new TreeField(myCallback, Field.FOCUSABLE); intintint int node1 = myTree.addChildNode(0, fieldOne); intintint int node2 = myTree.addChildNode(0, fieldTwo); intintint int node3 = myTree.addChildNode(node2, fieldThree); intintint int node4 = myTree.addChildNode(node3, fieldFour); ... intintint int node10 = myTree.addChildNode(node1, fieldTen); myTree.setExpanded(node4, falsefalsefalse false ); ... mainScreen.add(myTree); By Taiguo Zhang (confach@gmail.com) 51 管理管理管理 管理 UI UI UI UI 组件组件组件 组件 管理布局 使用 BlackBerry API布局管理器来安排屏幕上的组件。 下面四个类扩展了 Manager 类,以提供预定义的布局管理器:  VerticalFieldManager  HorizontalFieldManager  FlowFieldManager  DialogFieldManager MainScreen 和FullScreen 缺省的都使用了一个 VerticalFieldManager。仅为这些 类定义一个布局管理器实例提供了不同的布局。 注:为了创建一个定制的布局管理器,请扩展 Manager。为了得到更多信息,参看 63页的“创建定制的布局管理器”。 为一个指定的 Screen 实例定义布局管理器,完成下面的操作:  实例化合适的 Manager 子类。  加入 UI组件到布局管理器中。  加入布局管理器到屏幕中。 Manager 类定义了多个系统样式的常数,这些系统样式定义了如滚动和对齐的行为。当创 建布局管理器时,使用这些样式作为参数。为得到更多信息,参看 API 参考的 net.rim.device.api.ui.Manager。 垂直组织 fieldfieldfield field VerticalFieldManager 垂直地组织 field。所有 field 在一新地线(line)上开始。 为了可以垂直滚动,提供 Manager.VERTICAL_SCROLL 参数。 privateprivateprivate private classclassclass class TreeCallback implementsimplementsimplements implements TreeFieldCallback { publicpublicpublic public voidvoidvoid void drawTreeItem(TreeField _tree, Graphics g, intintint int node, intintint int y, intintint int width, intintint int indent) { String text = (String)_tree.getCookie(node); g.drawText(text, indent, y); } } VerticalFieldManager vfm = newnewnew new VerticalFieldManager(Manager.VERTICAL_SCROLL); vfm.add(bitmapField); vfm.add(bitmapField2); ... mainScreen.add(vfm) VerticalFieldManager vfm = newnewnew new VerticalFieldManager(Manager.VERTICAL_ By Taiguo Zhang (confach@gmail.com) 52 缺省地,BitmapField 对象在 VerticalFieldManager 中是左对齐的。 水平组织 fieldfieldfield field HorizonFieldManager 水平组织field 。为了可以水平滚动,提供 Manager.HORIZONTAL_SCROLL 样式。如果没有包含 HORIZONTAL_SCROLL 参数, field 水平排列他们自己,可能会超出屏幕宽度,但是用户不能滚动到超出屏幕右边的内 容。 BlackBerry 设备没有显示水平滚动指示器或滚动条。 水平垂直组织 FieldFieldField Field FlowFieldManager 先水平组织 field,然后再垂直组织。先水平组织 Field,直到没 有足够空间放另外一个 field,然后管理器在下一行上水平的安排它们。首页屏幕(Home Screen)就是一个 FlowFieldManager 的例子。 指定一个 PopupScreen PopupScreen PopupScreen PopupScreen 的布局 DialogFieldManager 指定了 PopupScreen 对象的布局。它管理了一个图标,一个消 息,以及一列定制的 field 的布局。图标和消息相互靠近的出现在布局上方,定制的 field 出现在消息的下方。这个布局是 PopupScreen 对象的标准布局。为了创建定制的对话框, 扩展 DialogFieldManager。 管理UI UI UI UI 交互 一个时间只有一个线程(通常是事件调配线程)可以得到 UI的访问权。通过下列方式,背 SCROLL); vfManager.add(bitmapField); vfManager.add(bitmapField2); ... mainScreen.add(vfManager); HorizontalFieldManager hfm = newnewnew new HorizontalFieldManager(Manager.HORIZONTAL_SCROLL); FlowFieldManager flManager = newnewnew new FlowFieldManager(Manager.FIELD_HCENTER); BitmapField bitmapField = newnewnew new BitmapField(Bitmap.getBitmapResource("x.gif")); RichTextField message = newnewnew new RichTextField("Dialog manager message", Field.NON_FOCUSABLE); LabelField dialogChoice = newnewnew new LabelField("Choice one", Field.FOCUSABLE); ... DialogFieldManager dialogManager = newnewnew new DialogFieldManager(); dialogManager.setMessage(message); dialogManager.setIcon(bitmapField); dialogManager.addCustomField(dialogChoice); By Taiguo Zhang (confach@gmail.com) 53 后(Background)线程也可从主事件处理或 UI绘制代码的外部访问 UI:  获取并保持事件锁。  使用 invokeLater()或 invokeAndWait()在事件调配线程上运行。 获取并保持事件锁 当它处理一个消息时,事件调配者在事件线程上设置一个事件锁。在没有打断事件调配者处 理的情况下,背后线程(也就是,非事件调配线程)在短事件内通过获取这个锁可以访问 UI。 为了得到事件锁,调用 Application.getEventLock()。和这个对象同步,序列化访问 UI。在 短期内保持这个锁,因为锁会暂停事件调配者。一个应用程序应该永远不要在 EventLock 对 象上调用 notify()或wait()。 在事件调配线程上运行 如果保持事件锁不合适,创建一个实现 Runnable 接口的类。在事件调配者上通过下面的 3 种方法之一调用它的 run()方法:  调用 invokeAndWait(Runnable),以致在事件调配线程上立即调用 run()。这个调用会阻 塞直到 run()完成为止。  调用 invokeLater(Runnable),以致在所有等候的事件处理后,在事件调配线程上调用 run()。  调用 invokeLater(Runnable,long,boolean)以致在某一指定时间后,事件调配线程上调 用run()。在这里,在将 Runnable 加入到事件队列之前,时间指定了等待时间的长短。 如果 repeat 为true,每隔 time 毫秒后,Runnable 加入到事件队列中, 管理前台事件 系统调用 Application.activate()将应用程序带到前台。 大多数的应用程序不需要重写 activate()。应用程序应该完成应用程序构造子的任何初始化, 包括任何必需的 UiApplicaiton.pushScreen()调用.因为对同一个应用程序,activate()能够调用 多次,因此在在这个方法中,应用程序不应该完成一次初始化。 当带到前台时,应用程序可以覆写 activate()方法完成其他的附加处理。如果覆写了 activate(), 在方法的定义里调用 super.activate(),以致应用程序能正确得重绘。 classclassclass class MyTimerTask extendsextendsextends extends TimerTask { publicpublicpublic public voidvoidvoid void run() { synchronizedsynchronizedsynchronized synchronized (Application.getEventLock()) { _label.setText("new text " + System.currentTimeMillis()); } } } By Taiguo Zhang (confach@gmail.com) 54 管理绘图区域 使用XYRect XYRect XYRect XYRect 对象 Graphics 对象代表了应用程序可用的整个绘图表面。为了界定这个区域,将它分为多个 XYRect 对象。XYRect 在图形上下文(graphics context)的顶端创建一个矩形区域、一个 XYRect 对象有 2个XYPoint 对象组成。第一个 XYPoint 对象代表了 XYRect 左上方的坐标, 第二个 XYPoint 对象代表了右下方的坐标。每个 XYPoint 代表了一个由 X,Y坐标构成的 屏幕的坐标。 Rectangle 对象将 XYRect 对象的上下文绘制区域界定为(10,10)与(50,50)之间 的区域。 为了开始对 XYRect 对象进行绘图调用,调用 pushContext()或pushRegion( ); 当开始用 pushContext()进行绘图调用时,指定区域原点不要调整绘图偏移(Drawing offset)。 当你首先调用 pushRegion()来调用绘图方法时,区域源(Region Origin)需调整绘图偏移, 左上方的 XYPoint 对象代表了区域源。所有绘图都通过这个数来偏移。 在下面的例子中,pushContext()将XYRect 对象的 10 个象素位放到右边,10 个放在下方。 区域源调整了绘图偏移(XYPoint topLeft = new XYPoint(10, 10)). 旋转(InvertInvertInvert Invert )个区域 旋转一个 Graphics 对象上的一个区域,它保留像素,只是转化像素值的位(也就是 0变为1, 1变为 0)。大多数 field 使用旋转来表示焦点,尽管这样,你可以为定制的 field 创建你自己 的焦点行为。 为了旋转 Graphics 对象的任何一个区域,提供坐标或者旋转一个指定的 XYRect 对象。指定 Graphics 对象的一个区域,并且压入栈中。在调用 pushContext()或pushRegion()后,提供 Graphics 对象的一个区域来旋转。 XYPoint topLeft = newnewnew new XYPoint(10, 10); XYPoint bottomRight = newnewnew new XYPoint(50, 50); XYRect rectangle = newnewnew new XYRect(topLeft, bottomRight); graphics.pushContext(rectangle, 0, 0); graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); graphics.popContext(); graphics.pushRegion(rectangle); graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); graphics.popRegion(); graphics.pushContext(rectangle); By Taiguo Zhang (confach@gmail.com) 55 转化(Translate)(Translate)(Translate) (Translate) 一个区域 为了将一个 Graphics 上下文上的区域移动到另外一个地方。调用 invoke()。 XYRect 将点(1,1)转化为(20,20,)。转 化 后 ,XYRect 的底部扩展了过去图形上下文的 范围,并且重合了。 创建客户定制的创建客户定制的创建客户定制的 创建客户定制的 UI UI UI UI 组件组件组件 组件 你仅能将定制的上下文菜单项和布局增加到一个定制的 field 中。 创建定制的 fieldfieldfield field 为覆写 field 的缺省行为,创建一个定制的 field。 注::: : 不要使用 Screen 对象来输入文本。Screen 对象没有明确的实现此功能,它需要复杂 的输入方法,例如国际化的键盘和 7100 系列的设备。为了实现不同输入方法的无缝集成, 扩展 Field 或者其任一子类。参看 53 页“创建定制的域”得到更多信息。 DrawStyle 接口的实现允许在定制的 field 上绘制样式。为获得更多信息,参看 73 页的“创 建一个与标准 BlackBerry UI一致的接口”。 客户定制的 field 应该实现所有相关的系统样式。例如,USE_ALL_WIDTH 和USE_ALL_HEIGHT 适用于许多 field。 扩展Field Field Field Field 类 扩展 Field 类和任一其子类,指定定制 Field 的特征。 定义按钮的标签,图形,以及样式 你的构造子的实现定义了按钮的标签,图形,以及样式。 graphics.invert(rectangle); // invert the entire XYRect object graphics.popContext(); XYRect rectangle = newnewnew new XYRect(1, 1, 100, 100); XYPoint newLocation = newnewnew new XYPoint(20, 20); rectangle.translate(newLocation); publicpublicpublic public classclassclass class CustomButtonField extendsextendsextends extends Field implementsimplementsimplements implements DrawStyle { publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int RECTANGLE = 1; publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int TRIANGLE = 2; publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int OCTAGON = 3; privateprivateprivate private String _label; privateprivateprivate private intintint int _shape; privateprivateprivate private Font _font; privateprivateprivate private intintint int _labelHeight; privateprivateprivate private intintint int _labelWidth; } By Taiguo Zhang (confach@gmail.com) 56 指定field field field field 中对象的安排 任何扩展 Field 的类必须实现 layout().Field 管理器调用了 layout()方法来决定 field 应该如何根据可用的控件安排它的内容。 定义需要的宽度 注:在大多数情况下,通过覆写 getPreferredWidth(),确保合适的布局出现在定制 的布局管理器里。 getPreferredWidth()的实现计算出定制 Field 的宽度,这个定制 Field 是基于标签 Field 的相对尺寸的。使用相对尺寸来确保标签不会超出标签的尺寸。 publicpublicpublic public CustomButtonField(String label) { thisthisthis this (label, RECTANGLE, 0); } publicpublicpublic public CustomButtonField(String label, intintint int shape) { thisthisthis this (label, shape, 0); } publicpublicpublic public CustomButtonField(String label, longlonglong long style) { thisthisthis this (label, RECTANGLE, style); } publicpublicpublic public CustomButtonField(String label, intintint int shape, longlonglong long style) { supersupersuper super (style); _label = label; _shape = shape; _font = getFont(); _labelHeight = _font.getHeight(); _labelWidth = font.getWidth(); } protectedprotectedprotected protected voidvoidvoid void layout(intintint int width, intintint int height) { _font = getFont(); _labelHeight = _font.getHeight(); _labelWidth = _font.getAdvance(_label); width = Math.min( width, getPreferredWidth() ); height = Math.min( height, getPreferredHeight() ); setExtent( width, height ); } publicpublicpublic public intintint int getPreferredWidth() { switchswitchswitch switch (_shape) { casecasecase case TRIANGLE: ififif if (_labelWidth < _labelHeight) { By Taiguo Zhang (confach@gmail.com) 57 定义需要的高度 注:在大多数情况下,通过覆写 getPreferredHeight(),确保合适的布局出现在定制 的布局管理器里。 getPreferredHeight()的实现计算出定制 Field 的高度,这个定制 Field 是基于标签 Field 的相对尺寸的。它确保了标签不会超出 field 的尺寸。 returnreturnreturn return _labelHeight << 2; } elseelseelse else { returnreturnreturn return _labelWidth << 1; } casecasecase case OCTAGON: ififif if (_labelWidth < _labelHeight) { returnreturnreturn return _labelHeight + 4; } elseelseelse else { returnreturnreturn return _labelWidth + 8; } casecasecase case RECTANGLE: defaultdefaultdefault default : returnreturnreturn return _labelWidth + 8; } } publicpublicpublic public intintint int getPreferredHeight() { switchswitchswitch switch (_shape){ casecasecase case TRIANGLE: ififif if (_labelWidth < _labelHeight){ returnreturnreturn return _labelHeight << 1; } elseelseelse else { returnreturnreturn return _labelWidth; } casecasecase case RECTANGLE: returnreturnreturn return _labelHeight + 4; casecasecase case OCTAGON: returnreturnreturn return getPreferredWidth(); } returnreturnreturn return 0; } By Taiguo Zhang (confach@gmail.com) 58 定义定制 field field field field 的外观 paint()的实现定义了 BlackBerry 设备屏幕上的定制 Field 的外观,不管什么时候 Field 的域标记为无效,Field 管理器都调用 paint()来重绘 Field。 技巧:验证 paint()是否是有效率的,因为不管什么时候 field 发生变化,UI框架调 用paint()方法。对于大数量的 field,使用 Graphics.getClippingRect()并在可见的区域 里绘图来保存绘制时间。 protectedprotectedprotected protected voidvoidvoid void paint(Graphics graphics) { intintint int textX, textY, textWidth; intintint int w = getWidth(); switchswitchswitch switch (_shape) { casecasecase case TRIANGLE: intintint int h = (w>>1); intintint int m = (w>>1)-1; graphics.drawLine(0, h-1, m, 0); graphics.drawLine(m, 0, w-1, h-1); graphics.drawLine(0, h-1, w-1, h-1); textWidth = Math.min(_labelWidth,h); textX = (w - textWidth) >> 1; textY = h >> 1; breakbreakbreak break ; casecasecase case OCTAGON: intintint int x = 5*w/17; intintint int x2 = w-x-1; intintint int x3 = w-1; raphics.drawLine(0, x, 0, x2); graphics.drawLine(x3, x, x3, x2); graphics.drawLine(x, 0, x2, 0); graphics.drawLine(x, x3, x2, x3); graphics.drawLine(0, x, x, 0); graphics.drawLine(0, x2, x, x3); graphics.drawLine(x2, x3, x3, x2); textWidth = Math.min(_labelWidth, w - 6); textX = (w-textWidth) >> 1; textY = (w-_labelHeight) >> 1; breakbreakbreak break ; casecasecase case RECTANGLE: defaultdefaultdefault default : graphics.drawRect(0, 0, w, getHeight()); textX = 4; textY = 2; textWidth = w - 6; breakbreakbreak break ; By Taiguo Zhang (confach@gmail.com) 59 处理焦点事件 为了支持焦点事件,使用 Field.FOCUSABLE 样式以及实现 Field.moveFocus().如果你想 你的 Field 接收焦点,覆写 Field.isFocusable()返回 true。 当Field 获得焦点时,UI框架调用 onFocus(),当 Field 失去焦点时,调用 unFocus(). 如果你的 field 对于这些事件需要特定的行为,覆写这些方法。框架调用 moveFocus()来 处理 field 的焦点移动事件。它对应 trackwheelRoll 事件,覆写 drawFocus( )。 实现set set set set 和get get get get 方法 Field 的get 和set 方法的实现,增加了 Field 的能力。 注:所有 get 和set 方法应该在 field 加入到一个 Screen 的前后工作。例如,如果 现在屏幕上的 field 合适的调用了 invalidate()或updateLayout()setLabel(),应该使 用一个新值来修改其显示。 代码实例 CustomButtonField.java 创建了具有多个图形的 button field。 实例: CustomButtonField.java /** * CustomButtonField.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ } graphics.drawText(_label, textX, textY, (intintint int )( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.HALIGN_MASK ), textWidth ); } publicpublicpublic public String getLabel() { returnreturnreturn return _label; } publicpublicpublic public intintint int getShape() { returnreturnreturn return _shape; } publicpublicpublic public voidvoidvoid void setLabel(String label) { _label = label; _labelWidth = _font.getAdvance(_label); updateLayout(); } publicpublicpublic public voidvoidvoid void setShape(intintint int shape) { _shape = shape; updateLayout(); } By Taiguo Zhang (confach@gmail.com) 60 packagepackagepackage package com.rim.samples.docs.custombuttons; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.system.*; /** * CustomButtonField is a class that creates button fields of various * shapes. This sample demonstrates how to create custom UI fields. */ publicpublicpublic public classclassclass class CustomButtonField extendsextendsextends extends Field implementsimplementsimplements implements DrawStyle { publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int RECTANGLE = 1; publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int TRIANGLE = 2; publicpublicpublic public staticstaticstatic static finalfinalfinal final intintint int OCTAGON = 3; privateprivateprivate private String _label; privateprivateprivate private intintint int _shape; privateprivateprivate private Font _font; privateprivateprivate private intintint int _labelHeight; privateprivateprivate private intintint int _labelWidth; /* Constructs a button with specified label, and the default style and shape. */ publicpublicpublic public CustomButtonField(String label) { thisthisthis this (label, RECTANGLE, 0); } /* Constructs a button with specified label and shape, and the default style. */ publicpublicpublic public CustomButtonField(String label, intintint int shape) { thisthisthis this (label, shape, 0); } /* Constructs a button with specified label and style, and the default shape. */ publicpublicpublic public CustomButtonField(String label, longlonglong long style) { thisthisthis this (label, RECTANGLE, style); } /* Constructs a button with specified label, shape, and style */ publicpublicpublic public CustomButtonField(String label, intintint int shape, longlonglong long style) { supersupersuper super (style); _label = label; _shape = shape; _font = getFont(); _labelHeight = _font.getHeight(); By Taiguo Zhang (confach@gmail.com) 61 _labelWidth = _font.getAdvance(_label); } /* Method that draws the focus indicator for this button and ** inverts the inside region of the shape. * **/ protectedprotectedprotected protected voidvoidvoid void drawFocus(Graphics graphics, booleanbooleanboolean boolean on) { switchswitchswitch switch (_shape){ casecasecase case TRIANGLE: intintint int w = getWidth(); intintint int h = w >> 1; forforfor for (intintint int i=h-1; i>=2; --i) { graphics.invert(i, h - i, w -(i << 1), 1); } breakbreakbreak break ; casecasecase case RECTANGLE: graphics.invert(1, 1, getWidth() - 2, getHeight() - 2); breakbreakbreak break ; casecasecase case OCTAGON: intintint int x3 = getWidth(); intintint int x = 5 * x3 / 17; intintint int x2 = x3 - x; x3 = x3 - 1; x2 = x2 - 1; graphics.invert(1, x, getWidth() - 2, x2 - x + 1); forforfor for (intintint int i=1; i>1); intintint int m = (w>>1)-1; graphics.drawLine(0, h-1, m, 0); By Taiguo Zhang (confach@gmail.com) 64 graphics.drawLine(m, 0, w-1, h-1); graphics.drawLine(0, h-1, w-1, h-1); textWidth = Math.min(_labelWidth,h); textX = (w - textWidth) >> 1; textY = h >> 1; breakbreakbreak break ; casecasecase case OCTAGON: intintint int x = 5*w/17; intintint int x2 = w-x-1; intintint int x3 = w-1; graphics.drawLine(0, x, 0, x2); graphics.drawLine(x3, x, x3, x2); graphics.drawLine(x, 0, x2, 0); graphics.drawLine(x, x3, x2, x3); graphics.drawLine(0, x, x, 0); graphics.drawLine(0, x2, x, x3); graphics.drawLine(x2, 0, x3, x); graphics.drawLine(x2, x3, x3, x2); textWidth = Math.min(_labelWidth, w - 6); textX = (w-textWidth) >> 1; textY = (w-_labelHeight) >> 1; breakbreakbreak break ; casecasecase case RECTANGLE: defaultdefaultdefault default : graphics.drawRect(0, 0, w, getHeight()); textX = 4; textY = 2; textWidth = w - 6; breakbreakbreak break ; } graphics.drawText(_label, textX, textY, (intintint int )( getStyle() & DrawStyle.ELLIPSIS | DrawStyle.HALIGN_MASK ),textWidth ); } } 创建定制的上下文菜单项 在Field 类里,创建定制的上下文菜单项。为得到更多关于实现的菜单的信息,参看 39 页 的“显示屏幕”. privateprivateprivate private MenuItem myContextMenuItemA = newnewnew new MenuItem( _resources, MENUITEM_ONE, 200000, 10) { By Taiguo Zhang (confach@gmail.com) 65 提供一个上下文菜单 在主应用程序类里,覆写 makeContextMenu()方法提供一个上下文菜单。 创建应用程序菜单 在主应用程序类里,覆写makeMenu()方法创建应用程序菜单,并且无论合十,当特定的 field 获取焦点时,更新上下文菜单。 代码实例 实例: ContextMenuSample.java /** * ContextMenuSample.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ publicpublicpublic public voidvoidvoid void run() { onMyMenuItemA(); } }; privateprivateprivate private MenuItem myContextMenuItemB = newnewnew new MenuItem( _resources, MENUITEM_ONE, 200000, 10) { publicpublicpublic public voidvoidvoid void run() { onMyMenuItemB(); } }; protectedprotectedprotected protected voidvoidvoid void makeContextMenu(ContextMenu contextMenu) { contextMenu.addItem(myContextMenuItemA); contextMenu.addItem(myContextMenuItemB); } protectedprotectedprotected protected voidvoidvoid void makeMenu(Menu menu) { Field focus = UiApplication.getUiApplication().getActiveScreen() .getLeafFieldWithFocus(); ififif if (focus != nullnullnull null ){ ContextMenu contextMenu = focus.getContextMenu(); ififif if (!contextMenu.isEmpty()) { menu.add(contextMenu); menu.addSeparator(); } } } By Taiguo Zhang (confach@gmail.com) 66 packagepackagepackage package com.rim.samples.docs.contextmenus; importimportimport import net.rim.device.api.i18n.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import com.rim.samples.docs.baseapp.*; publicpublicpublic public classclassclass class ContextMenuSample extendsextendsextends extends BaseApp implementsimplementsimplements implements ContextMenuSampleResource { privateprivateprivate private MyContextField myContextField; privateprivateprivate private staticstaticstatic static ResourceBundle _resources = ResourceBundle.getBundle( ContextMenuSampleResource.BUNDLE_ID, ContextMenuSampleResource.BUNDLE_NAME); publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { ContextMenuSample app = newnewnew new ContextMenuSample(); app.enterEventDispatcher(); } // Inner class to define a new field. privateprivateprivate private classclassclass class MyContextField extendsextendsextends extends RichTextField { privateprivateprivate private MenuItem myContextMenuItemA = newnewnew new MenuItem( _resources, MENUITEM_ONE, 200000, 10) { publicpublicpublic public voidvoidvoid void run() { onMyMenuItemA(); } }; privateprivateprivate private MenuItem myContextMenuItemB = newnewnew new MenuItem( _resources, MENUITEM_TWO, 200000, 10) { publicpublicpublic public voidvoidvoid void run() { onMyMenuItemB(); } }; privateprivateprivate private voidvoidvoid void onMyMenuItemA() { // Perform an action when user selects menu item. } privateprivateprivate private voidvoidvoid void onMyMenuItemB() { // Perform an action when user selects menu item. } By Taiguo Zhang (confach@gmail.com) 67 protectedprotectedprotected protected voidvoidvoid void makeContextMenu(ContextMenu contextMenu) { contextMenu.addItem(myContextMenuItemA); contextMenu.addItem(myContextMenuItemB); } MyContextField(String text) { supersupersuper super (text); } } protectedprotectedprotected protected voidvoidvoid void makeMenu(Menu menu) { supersupersuper super .makeMenu(menu, 0); // Implemented by BaseApp. } publicpublicpublic public ContextMenuSample() { MainScreen mainScreen = newnewnew new MainScreen(); MyContextField myContextField = newnewnew new MyContextField(“Field label: “); mainScreen.add(myContextField); mainScreen.addKeyListener(thisthisthis this ); mainScreen.addTrackwheelListener(thisthisthis this ); pushScreen(mainScreen); } publicpublicpublic public voidvoidvoid void onExit() { // Perform action when application closes. } } 创建定制的布局管理器 Manager 对象管理 UI组件的位置以及决定屏幕上的 field 如何安排。 创建一个定制的布局管理器 扩展 Manager 类或其任一子类 返回一个优先的 Field Field Field Field 宽度 覆写 getPreferredWidth(),以致它能为管理器返回一个优先的 Field 宽度。 getPreferredWidth()的实现可以返回不同的值,取决于布局管理器的目的。例如,如果管 classclassclass class DiagonalManager extendsextendsextends extends Manager { publicpublicpublic public DiagonalManager(longlonglong long style){ supersupersuper super (style); } ... } By Taiguo Zhang (confach@gmail.com) 68 理器扩展了 HorizontalFieldManager,getPreferredWidth()返回所有 field 宽度的总和。如果 扩展了 VerticalFieldManager,getPreferredWidth()返回最宽 field 的宽度。 注:TextField 和Manger 使用了指派给他们的整个宽度。为组织 2个或更多的水平 上的对象,分别覆写它们各自的 getPreferredWidth()方法。为了组织多个水平上的 TextField,覆写 layout(). 返回一个优先 Field Field Field Field 高度 覆写 getPreferredHeight(),以致它能为管理器返回一个优先的 Field 高度。 指定子 Field Field Field Field 如何安排 subLayout()方法指定了管理器如何在屏幕上组织 field。它得到管理器中 field 的个数,然后 为子 field 设置合适的位置以及布局。 layout()调用了 subLayout()方法,subLayout()方法通过调用每个管理器包含的 field 的 setPositionChild ()以及 LayoutChild(),控制每个子 field 如何加到屏幕上。 publicpublicpublic public intintint int getPreferredWidth() { intintint int width = 0; intintint int numberOfFields = getFieldCount(); forforfor for (intintint int i=0; i 0) { // action to perform if trackwheel is rolled up } elseelseelse else { // action to perform if trackwheel is rolled down } } ififif if (index == thisthisthis this .getFieldWithFocusIndex()) returnreturnreturn return supersupersuper super .nextFocus(direction, alt); elseelseelse else returnreturnreturn return index; } By Taiguo Zhang (confach@gmail.com) 70 importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; classclassclass class DiagonalManager extendsextendsextends extends Manager { publicpublicpublic public DiagonalManager(longlonglong long style) { supersupersuper super (style); } publicpublicpublic public intintint int getPreferredWidth() { intintint int width = 0; intintint int numberOfFields = getFieldCount(); forforfor for (intintint int i=0; i 0) { // Action to perform if trackwheel is rolled up. } elseelseelse else { // Action to perform if trackwheel is rolled down. } } ififif if (index == thisthisthis this .getFieldWithFocusIndex()) returnreturnreturn return supersupersuper super .nextFocus(direction, alt); elseelseelse else returnreturnreturn return index; } } 创建列表 一个ListField 包含了多列可选项。为了使用户可以选择列表中多项,声明列表为 MULTI_SELECT. 创建一个回调对象 ListFieldCallback 对象为列表控制所有重绘任务。每次要求 Field 显示列表中的一个条目。必 要的方法也会在回调对象中调用。 ListFieldCallback 接口的实现创建了一个回调对象。系统调用这个接口的方法绘制列表的行, 获得一个指定的列表元素,或决定列表的宽度。 允许Field Field Field Field 重绘一行 drawListRow()的实现允许 Field 重绘一行。传递到 drawListRow()的图形上下文代表整个列 表。相应地,drawText()必须指明绘制哪一行。 privateprivateprivate private classclassclass class ListCallback implementsimplementsimplements implements ListFieldCallback { // The listElements vector contain the entries in the list. privateprivateprivate private Vector listElements = newnewnew new Vector(); ... } publicpublicpublic public voidvoidvoid void drawListRow(ListField list, Graphics g, intintint int index, intintint int y, intintint int w) { String text = (String)listElements.elementAt(index); By Taiguo Zhang (confach@gmail.com) 72 允许Field Field Field Field 从列表中得到一个条目(EntryEntryEntry Entry ) get()的实现允许 field 从列表中得到一个条目。本方法返回一个包含在有明确索引行中的对 象。 为列表返回一个优先的宽度 getPreferredWidth()的实现为列表返回一个优先的宽度。在下面的实现中,getPreferredWidth() 返回整个屏幕的绘制宽度。 getPreferredWidth()的实现返回一个不同的值,这依赖 field 管理器的类型。例如,如果管理 器扩展了 HorizontalFieldManager,getPreferredWidth()返回所有 field 宽度的总和。如果扩 展了 VerticalFieldManager,getPreferredWidth()返回最宽 field 的宽度。 指派回调以及加入条目到列表中 创建列表对象,并且将回调指派这个对象。 创建列表对象 为了列表创建 ListField 对象以及 ListCallback 对象。 注:ListCallback 是一个定制的 ListFieldCallback 类,这个类在 66 页的“创建一个回调 对象”中创建。 设置回调 调用 setCallback()将ListFieldCallback 与ListField 关联。这个关联允许回调增加列表项到列 表中。 增加列表条目 为了将条目增加到列表中,创建条目,并指定一个索引,并在这个索引上插入每个条目到 ListField 对象中。然后每个 ListField 对象到 ListFieldCallback 中。 g.drawText(text, 0, y, 0, w); } publicpublicpublic public Object get(ListField list, intintint int index) { returnreturnreturn return listElements.elementAt(index); } publicpublicpublic public intintint int getPreferredWidth(ListField list) { returnreturnreturn return Graphics.getScreenWidth(); } ListField myList = newnewnew new ListField(); ListCallback myCallback = newnewnew new ListCallback(); myList.setCallback(myCallback); String fieldOne = newnewnew new String("Field one label"); String fieldTwo = newnewnew new String("Field two label"); By Taiguo Zhang (confach@gmail.com) 73 代码实例 例:SampleListFieldCallback.java /** * SampleListFieldCallback.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.listfields; importimportimport import java.util.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; publicpublicpublic public classclassclass class SampleListFieldCallback extendsextendsextends extends UiApplication { privateprivateprivate private ListField myList; publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { SampleListFieldCallback app = newnewnew new SampleListFieldCallback(); app.enterEventDispatcher(); } privateprivateprivate private staticstaticstatic static classclassclass class ListCallback implementsimplementsimplements implements ListFieldCallback { privateprivateprivate private Vector listElements = newnewnew new Vector(); publicpublicpublic public voidvoidvoid void drawListRow(ListField list, Graphics g, intintint int index, intintint int y, intintint int w) { String text = (String)listElements.elementAt(index); g.drawText(text, 0, y, 0, w); String fieldThree = newnewnew new String("Field three label"); myList.insert(0); myList.insert(1); myList.insert(2); myCallback.insert(fieldOne, 0); myCallback.insert(fieldTwo, 1); myCallback.insert(fieldThree, 2); mainScreen.add(myList); By Taiguo Zhang (confach@gmail.com) 74 } publicpublicpublic public Object get(ListField list, intintint int index) { returnreturnreturn return listElements.elementAt(index); } publicpublicpublic public intintint int indexOfList(ListField list, String p, intintint int s) { returnreturnreturn return listElements.indexOf(p, s); } publicpublicpublic public intintint int getPreferredWidth(ListField list) { returnreturnreturn return Graphics.getScreenWidth(); } publicpublicpublic public voidvoidvoid void insert(String toInsert, intintint int index) { listElements.addElement(toInsert); } publicpublicpublic public voidvoidvoid void erase() { listElements.removeAllElements(); } } publicpublicpublic public SampleListFieldCallback() { MainScreen mainScreen = newnewnew new MainScreen(); myList = newnewnew new ListField(); ListCallback myCallback = newnewnew new ListCallback(); myList.setCallback(myCallback); String fieldOne = “ListField one”; String fieldTwo = “ListField two”; String fieldThree = “ListField three”; myList.insert(0); myCallback.insert(fieldOne, 0); myList.insert(1); myCallback.insert(fieldTwo, 1); myList.insert(2); myCallback.insert(fieldThree, 2); mainScreen.add(myList); pushScreen(mainScreen); } } } By Taiguo Zhang (confach@gmail.com) 75 操作图片操作图片操作图片 操作图片 使用未处理(rawrawraw raw )的图像数据 为了从图像的特定区域获取未处理的图像数据,并存储在一个整数数组中,调用 Bitmap.getARGB().应用程序然后可以直接对未处理的图像数据进行操作。 voidvoidvoid void getARGB(intintint int [] argbData, intintint int offset, intintint int scanLength, intintint int x, intintint int y, intintint int width, intintint int height); 注:getARGB()方法只在彩屏的 BlackBerry 设备适用。 设备模拟器显示图像数据时每一个象素作为一个整数,每个象素中,每个字符(不透明)有 8个位,红,绿以及蓝的值。颜色由 8个整数以 0xAARRGGBB 的形式组成。 获取图像数据 初始化一个整型数组,然后调用 Bitmap.getARGB()将新的或预定义的位图的未处理的图像 数据存储到整型数组中。 比较222 2 个图像 调用 Bitmap.equals()决定 2个位图是否相同。 参数 描述 argbData 整数数组,用来存储 ARGB 数据;每个象素 都以 0xAARRGGBB 的格式存储。 offset 数据的偏移,从这里开始读取。 scanLength 数据数组内每一个扫描行的宽。 x 矩形的左边,从这里开始读取图像数据。 y 矩形的上边,从这里开始读取图像数据。 width 矩形的宽,从这里开始读取图像数据。 height 矩形的高,从这里开始读取图像数据。 Bitmap original = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION); intintint int [] argb = newnewnew new intintint int [original.getWidth() * original.getHeight()]; original.getARGB(argb, 0, original.getWidth(), 0, 0, original.getWidth(), original.getHeight()); ififif if (restored.equals(original)) { System.out.println("Success! Bitmap renders correctly with RGB data."); } elseelseelse else ififif if (!restored.equals(original)) { System.out.println("Bitmap rendered incorrectly with RGB data."); } By Taiguo Zhang (confach@gmail.com) 76 使用编码的图像 net.rim.device.api.system.EncodedImage 类封装了各种格式的编码图像。BlackBerry 设备支持 下面的图像格式:.gif,.png,.wbmp,以及.jpeg.只有彩屏的 BlackBerry 设备才支持.jpeg 图像。 注:JPEGEncodedImage 类需要一个不可用的签名。 使用EncodedImage 的子类,PNGEncodedImage 和WBMPEncodedImage,来分别访问.png 和.wbmp 图像的特定属性。例如,PNGEncodedImage 提供方法来获得图像的色彩深度(Bit Depth),alpha 通道(alpha channel①),以及颜色类型。 在BlackBerry IDE 中,一个应用程序能够直接访问加到工程或者依赖的类库工程中的图像。 访问一个图像 在BlackBerry IDE 中,保存一个图像到你的项目文件夹或者子文件夹,然后增加图像到工程 中。调用 Class.getResourceAsStream()获取图像作为一个字节的输入流。 解码一个图像 为了编码一个图像,调用 EncodedImage.createEncodedImage()。这个方法使用字节数组里 的未处理的图像数据来创建了一个 EncodedImage 的实例。如果作为参数的字节数组布包汉 一个可以识别的图像格式,它将抛出一个 IllegalArgumentException 异常。 privateprivateprivate private InputStream input; ... trytrytry try { input = Class.forName("com.rim.samples.docs.imagedemo.ImageDemo"). getResourceAsStream("/images/example.png"); } catchcatchcatch catch (ClassNotFoundException e) { System.out.println("Class not found"); } privateprivateprivate private bytebytebyte byte [] data = newnewnew new bytebytebyte byte [2430];// Store the contents of the image file. trytrytry try { input.read(data); // Read the image data into the byte array. } catchcatchcatch catch (IOException e) { // Handle exception. } trytrytry try { ①用于指定像素的透明度,译者注 By Taiguo Zhang (confach@gmail.com) 77 注:缺省地,BlackBerry 设备软件监测基于图像格式的 MIME 类型的图像。如果正确 的MIME 类型未能自动的监测到,使用下面 EncodedImage.createEncodedImage()的形式指 定一个特定的 MIME 类型: createEncodedImage(bytebytebyte byte [] data, createEncodedImage(bytebytebyte byte [] data, intintint int offset, intintint int length, String mimeType) 如果图像格式预指定的 MIME 类型不匹配,这个方法抛出一个 IllegalArgumentException 异常。支持的 MIME 类型包括:image/gif, image/png, image/vnd.wap.wbmp, 以及 image/jpeg. 显示一个编码的图像 调用BitmapField.setImage()指定一个编码的图像到一个 BitmapField,然后调用 add()将 BitmaoField 加入到屏幕中。 设置解码模式 调用 EncodedImage.setDecodeMode()来设置图像的解码模式。提供下面模式之一作为方法 的一个参数: 设置缩放因子(scalingscalingscaling scaling factorfactorfactor factor ) 当解码时,为了设置用在缩减一个图像的整数因子,调用 EncodedImage.setScale()。 图像通过作为 scale 参数的整型来缩放。例如,如果你设置缩放因子为 2,图像将缩小到原 大小的 50%。 代码实例 ImageDemo.java 实例从一个包含在项目中的图像获得未处理的数据,然后使用这个未处理 的数据来重新创建一个 EncodedImage。 例:ImageDemo.java EncodedImage image = EncodedImage.createEncodedImage(data, 0, data.length); } catchcatchcatch catch (IllegalArgumentException iae) { System.out.println("Image format not recognized."); } BitmapField field = newnewnew new BitmapField(); field.setImage(image); add(field); 解码模式 描述 DECODE_ALPHA 解码一个 alpha 通道,如果一个存在(这是缺 省的模式)。 DECODE_NATIVE 强制将位图解码未手持设备软件的原生位图 类型。 DECODE_READONLY 将解码的位图标记为只读。 By Taiguo Zhang (confach@gmail.com) 78 /** * ImageDemo.java */ packagepackagepackage package com.rim.samples.docs.imagedemo; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import java.io.*; /* * The ImageDemo.java sample retrieves raw data from an image that * is included in its project, and then uses that raw data to * recreate an EncodedImage. */ publicpublicpublic public classclassclass class ImageDemo extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { ImageDemo app = newnewnew new ImageDemo(); app.enterEventDispatcher(); } publicpublicpublic public ImageDemo() { pushScreen(newnewnew new ImageDemoScreen()); } } finalfinalfinal final classclassclass class ImageDemoScreen extendsextendsextends extends MainScreen { privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int IMAGE_SIZE = 2430; privateprivateprivate private InputStream input; privateprivateprivate private bytebytebyte byte [] data = newnewnew new bytebytebyte byte [IMAGE_SIZE]; publicpublicpublic public ImageDemoScreen() { supersupersuper super (); setTitle(newnewnew new LabelField(“Image Demo Sample”)); trytrytry try { input = Class.forName(“com.rim.samples.docs.imagedemo.ImageDemo”) .getResourceAsStream(“/images/hellokitty.png”); } catchcatchcatch catch (ClassNotFoundException e) { System.out.println(“Class not found”); } ififif if (input == nullnullnull null ){ System.out.println(“Error: input stream is not By Taiguo Zhang (confach@gmail.com) 79 initialized.”); } elseelseelse else ififif if (input != nullnullnull null ){ System.out.println(“OK: input stream is initialized.”); trytrytry try { intintint int code = input.read(data); System.out.println(“Total number of bytes read into buffer: “ + code + “.”); } catchcatchcatch catch (IOException e) { // Handle exception. } trytrytry try { EncodedImage image = EncodedImage.createEncodedImage(data, 0, data.length); add(newnewnew new BitmapField(image.getBitmap())); } catchcatchcatch catch (IllegalArgumentException iae) { System.out.println(“Image format not recognized.”); } } } } 使用图像对象画图使用图像对象画图使用图像对象画图 使用图像对象画图 Graphics 对象允许应用程序完成绘图功能和描绘(rendering)操作。使用 Graphics 类绘制 整个屏幕或一个 BitmapField 。如果你的应用程序不包含任何 field ,调用 Screen.getGraphics()来获得整个屏幕的绘图上下文。 为了绘制一个指定的 BitmapField,应用程序通过传入一个 field 到Graphics 的 构造子中, 来为一个指定的 field 获得一个绘图上下文.当绘制一个 BitmapField 时,field 管理器在 field 重绘时传递一个绘图上下文给 field。为了完成绘制一个定制的 field,当你扩展 Field 类时覆 写Graphics.paint()方法。 Graphics 类允许你绘制图形,例如弧线,直线,矩形以及圆。 By Taiguo Zhang (confach@gmail.com) 80 使用图形上下文 为了利用 Graphics 类绘制,为每各自的 field 或整个屏幕获得一个图形上下文。 为了为各自的 Field 获取一个图形上下文,调用 Graphics 的构造子。 为了为整个屏幕获得一个图形上下文,调用 Screen.getGraphics( )。 为使用任何图形上下文绘图,你的方法务必要在 field 或屏幕的界限内完成它们的绘制功能。 如果你的图形上下文没有应用到整个屏幕,加入 BitmapField 到屏幕中。 创建一个与标准的 BlackBerryBlackBerryBlackBerry BlackBerry UI UI UI UI 一致的界面 DrawStyle 接口提供了 Graphics 和Field 对象使用的接口。DrawStyle 的实现允许你创建一个 与标准 BlackBerry UI一致的接口。如果你正扩展 Field 类来创建一个定制的 Field,你的代 码需要接受合适的样式,这样它与标准的 BlackBerry 应用程序类似。 DrawStyle 作为 style 参数应用在 field 上,如下面的例子: 你可以在下面的对象中使用 DrawStyle 元素:  BitmapField  ButtonField  DateField  Graphics  LabelField  ObjectListField 用颜色绘制 利用彩色绘制只在彩屏的 BlackBerry 设备上适用。为了判断 BlackBerry 设备是否支持彩色 显示,调用 Graphics.isColor(). 为了决定 BlackBerry 设备支持的颜色象素,调用 Graphics.numColors( )。 设置alpha alpha alpha alpha 值 Bitmap surface = newnewnew new Bitmap(100, 100); BitmapField surfaceField = newnewnew new BitmapField (surface); Graphics graphics = newnewnew new Graphics(surface); Graphics graphics = Screen.getGraphics(); graphics.fillRect(10, 10, 30, 30); graphics.drawRect(15, 15, 30, 30); mainScreen.add(surfaceField); ButtonField buttonField = newnewnew new ButtonField(DrawStyle.ELLIPSIS); By Taiguo Zhang (confach@gmail.com) 81 全局 alpha 值决定他和绘制区域中象素的透明度,0(0x0000)是完全透明(不可见),255 (0x00FF)是完全不透明。为了设置或得到全局 alpha 值,调用 Graphics.setGlobalAlpha() 或Graphics.getGlobalAlpha(). 注:BlackBerry 为特定的光栅操作使用 alpha 值。文本和绘制操作不会用到。 决定光栅操作的支持 为决定一个 Graphics 对象是否支持一个特定的光栅操作,调用 Graphics.isRopSupported(int), 使用下面提供的常数之一作为参数。 绘制一个路径(Path)(Path)(Path) (Path) 为了绘制一组阴影填充的路径,调用 Graphics.drawShadedFilledPath(): publicpublicpublic public voidvoidvoid void drawShadedFilledPath( intintint int [] xPts, intintint int [] yPts, bytebytebyte byte [] pointTypes, intintint int [] colors, intintint int [] offsets) 下面的例子绘制了一个从蓝色到红色混合的路径。 常数 光栅操作 ROP_CONST_GLOBALALPHA 将一个使用一个全局 alpha 常量值的前台常 量颜色和目标象素混合。 ROP_SRC_GLOBALALPHA 将一个使用一个全局 alpha 常量值的源位图 和目标象素混合。 参数 描述 xPts 有序的列表定义了每个在路径里的顶点的 x值。 yPts 有序的列表定义了每个在路径里的顶点的 y值。 pointTypes 为每个定义的(x,y)点指定一个下面的常数。如果 pointTypes 为null,所有点 缺省为 Graphics.CURVEDPATH_END_POINT.  Graphics.CURVEDPATH_END_POINT  Graphics.CURVEDPATH_QUADRATIC_BEZIER_CONTROL_POINT  Graphics.CURVEDPATH_CUBIC_BEZIER_CONTROL_POINT colors 有序的列表为每个顶点以 0x00RRGGBB 格式定义颜色值。如果是 null,将 绘制一个以当前前景颜色的路径。 offsets 列表在 xPts 和yPts 数组里,定义了每个路径的起点。null 描述了单个路径 , 这个路径的起点在(xPts[offsets[i]],yPts[offsets[i]]),终点在(xPts[offsets[i+1]]- 1,yPts[offsets[i+1]]-1)。 Bitmap surface = newnewnew new Bitmap(240, 160); BitmapField surfaceField = newnewnew new BitmapField(surface); add(surfaceField); Graphics graphics = newnewnew new Graphics(surface); intintint int []X_PTS = { 0, 0, 240, 240 }; By Taiguo Zhang (confach@gmail.com) 82 使用绘制格式 为了将绘制格式打开或关闭,调用 Graphics.setDrawingStyle(int drawStyle, Boolean on),在这里,on 指定是否打开(true)或关闭(false)绘制格式.为了判断一个绘制格式是否已 经设置,调用 Graphics.isDrawingStyleSet(int drawStyle). 像印花一样使用单色位图 fieldfieldfield field 通过用颜色提交不透明的区域,STAMP_MONOCHROME 选项允许应用程序使用单色位图, 如同印花一样。这个选项用于位图,这个位图是 1位的,并且有定义的 alpha。 从未处理的数据绘制一图像 1. 创建一空的位图。在本例中,类型和大小都复制到一个已经存在的位图。 2. 使用新建的位图创建一个 Graphics 对象作为绘图表面。 3. 调用 Graphics.rawRGB(),并使用从源处来的未处理的数据绘制一个新的图像。 intintint int []Y_PTS = { 20, 50, 50, 20 }; intintint int [] drawColors = { 0x0000CC, 0x0000CC, 0xCC0000, 0xCC0000 }; trytrytry try { graphics.drawShadedFilledPath(X_PTS, Y_PTS, nullnullnull null , drawColors, nullnullnull null ); } catchcatchcatch catch (IllegalArgumentException iae) { System.out.println("Bad arguments."); } 常数 描述 DRAWSTYLE_AALINES 直线的访混淆图像的格式,通过 setDrawingStyle() 和 isDrawingStyleSet() 使 用。 DRAWSTYLE_AAPOLYGONS 多边形的访混淆图像的格式,通过 setDrawingStyle() 和 isDrawingStyleSet() 使 用。 DRAWSTYLE_FOCUS 当绘制已完成,系统为焦点绘制设置的格式。 DRAWSTYLE_SELECT 当绘制已完成,系统为选择绘制设置的格式。 BitmapField field = newnewnew new BitmapField(original, BitmapField.STAMP_MONOCHROME); Bitmap restored = newnewnew new Bitmap(original.getType(), original.getWidth(), original.getHeight()); Graphics graphics = newnewnew new Graphics(restored); trytrytry try { graphics.drawRGB(argb, 0, restored.getWidth(), 0, 0, restored.getWidth(), restored.getHeight()); } By Taiguo Zhang (confach@gmail.com) 83 使用位图类型 注:下面关于位图类型的详情只提供类型信息。应用程序应不要依赖位图的实际位格 式作为格式,因在手持设备的软件的未来版本可能会变化。 为了决定 Bitmap 类型,调用 Bitmap.getType()。这个方法返回下面常数中的一个:  在黑白屏幕的 BlackBerry 设备上,数据存储在列中,因此,Bitmap.getType()返回 COLUMNWISE_MONOCHROME。头 2个字节代表了位图第一个列里的头 16 个象素。  在彩屏的 BlackBerry 设备上,数据保存在行里,因此 itmap.getType()为黑白图片返回 ROWWISE_MONOCHROME,为彩色图片返回 ROWWISE_16BIT_COLOR。在黑白图 片里,头 2个字节代表了位图的第一行的头 16 个象素,从左到右。在彩色图片里,头2 个字节代表了第一个象素。 下面 2个Bitmap 的构造子允许你指定一个 type 参数:  Bitmap(int type, int width, int height)  Bitmap(int type, int width, int height, byte[] data) 为了获取 BlackBerry 设备的缺省位图类型,调用静态方法: Bitmap.getDefaultType() 代码实例 DrawDemo.java 从预定义的位图里获取未处理的数据,然后使用这些数据绘制一个新的位 图。最后显示原始的和恢复的图像。 catchcatchcatch catch (Exception e) { System.out.println("Error occurred during drawing: " + e); } } 位图类型 描述 COLUMNWISE_MONOCHROME 数据存储在列中,每个象素一个位:0是白色 , 1是黑色。在一个字节里,最上方的象素在低 的有效字节里。低有限的字节包含了一列里 的最上方的象素。 ROWWISE_MONOCHROME 数据存储在行里,一个象素一个位:0代表黑 色,1代表白色。在宽度上,每行是 4个字节 的倍数。在一个字节里,最左边的象素在低 的有效字节里。低有限的字节包含了一行里 的最左边的象素。 ROWWISE_16BIT_COLOR 据存储在行里,一个象素有 2个字节:0是黑 色,0xffff(65535)是白色。在宽度上,每行是 4字节的倍数。 By Taiguo Zhang (confach@gmail.com) 84 例:DrawDemo.java /* * DrawDemo.java * Copyright (C) 2002-2005 Research In Motion Limited. */ packagepackagepackage package com.rim.samples.docs.drawing; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; /* The DrawDemo.java sample retrieves raw data from a predefined bitmap image, and then draws a new bitmap using the data. It then displays the original and restored images. */ publicpublicpublic public classclassclass class DrawDemo extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { } DrawDemo app = newnewnew new DrawDemo(); app.enterEventDispatcher(); } publicpublicpublic public DrawDemo() { pushScreen(newnewnew new DrawDemoScreen()); } finalfinalfinal final classclassclass class DrawDemoScreen extendsextendsextends extends MainScreen { publicpublicpublic public DrawDemoScreen() { supersupersuper super (); LabelField title = newnewnew new LabelField(“UI Demo”, LabelField.USE_ALL_WIDTH); setTitle(title); Bitmap original = Bitmap.getPredefinedBitmap(Bitmap.INFORMATION); Bitmap restored = newnewnew new Bitmap(original.getType(), original.getWidth(), original.getHeight()); Graphics graphics = newnewnew new Graphics(restored); By Taiguo Zhang (confach@gmail.com) 85 // Retrieve raw data from original image. intintint int [] argb = newnewnew new intintint int [original.getWidth() * original.getHeight()]; original.getARGB(argb, 0, original.getWidth(), 0, 0, original.getWidth(),original.getHeight()); // Draw new image using raw data retrieved from original image. trytrytry try { graphics.drawRGB(argb, 0, restored.getWidth(), 0, 0, restored.getWidth(),restored.getHeight()); } catchcatchcatch catch (Exception e) { System.out.println(“Error occurred during drawing: “ + e); } ififif if (restored.equals(original)) { System.out.println(“Success! Bitmap renders correctly with RGB data.”); } elseelseelse else ififif if (!restored.equals(original)) { System.out.println(“Bitmap rendered incorrectly with RGB data.”); } BitmapField field1 = newnewnew new BitmapField(original, BitmapField.STAMP_MONOCHROME); BitmapField field2 = newnewnew new BitmapField(restored); add(newnewnew new LabelField(“Original bitmap: “)); add(field1); add(newnewnew new LabelField(“Restored bitmap: “)); add(field2); } } 监听监听监听 监听 UI UI UI UI 对象的改变对象的改变对象的改变 对象的改变 UI EventListeners 允许应用程序响应一个 UI对象的改变。这里有 3种类型的 UI事件监听者 : 监听者 描述 FieldChangeListener 当field 的属性改变时发出事件通知 By Taiguo Zhang (confach@gmail.com) 86 监听field field field field 属性的变化 为了监测 field 的变化,实现 FieldChangeListener 接口。调用 setChangeListener()来把你的实 现指派给一个 field。 监听焦点的改变 为了监测 field 之间焦点的改变,指派给他们一个 FocusChangeListener 。实现这个 FocusChangeListener,然后通过调用 setChangeListener()把你的实现指派给一个 Field。一个 FocusChangeListener 关心一个与之相关的明确的 Field 的焦点的获取,失去或改变。 当field 通过实现 focusChanged()获取,失去或改变焦点时, FocusChangeListener 的实现应 该指明 field 将采取什么样的动作。 FocusChangeListener 当Field 获取或失去焦点时发出事件通知。 ScrollChangeListener 当一个管理器的水平或者垂直滚动值变化时 发出事件通知。 privateprivateprivate private classclassclass class FieldListener implementsimplementsimplements implements FieldChangeListener { publicpublicpublic public voidvoidvoid void fieldChanged(Field field, intintint int context) { ififif if (context != FieldChangeListener.PROGRAMMATIC) { // Perform action if user changed field. } elseelseelse else { // Perform action if application changed field. } } } //... FieldListener myFieldChangeListener = newnewnew new FieldListener() myField.setChangeListener(myFieldChangeListener); privateprivateprivate private classclassclass class FocusListener implementsimplementsimplements implements FocusChangeListener { publicpublicpublic public voidvoidvoid void focusChanged(Field field, intintint int eventType) { ififif if (eventType == FOCUS_GAINED){ // Perform action when this field gains the focus. } ififif if (eventType == FOCUS_CHANGED){ // Perform action when the focus changes for this field. } ififif if (eventType == FOCUS_LOST){ // Perform action when this field loses focus. By Taiguo Zhang (confach@gmail.com) 87 监听滚动事件 ScrollChangeListener 接口的实现允许你的 field 管理器管理滚动事件,调用setScrollListener() 将你的实现给一个 Manager。当水平或垂直的(或都有)滚动值发生变化时,scrollChanged() 方法传递一个新的值。 注:典型地,监听滚动变化没有必要,因为你的应用程序可以监听 field 的焦点变化; 尽管这样,ScrollChangeListener 在游戏实现中可能有用。 为将监听者指派给一个 field,调用 field 管理器上的 setScrollListener(). } } } FocusListener myFocusChangeListener = newnewnew new FocusListener(); myField.setChangeListener(myFocusChangeListener); privateprivateprivate private classclassclass class ScrollListener implementsimplementsimplements implements ScrollChangeListener { scrollChanged(Manager manager, intintint int newHoriztonalScroll, intintint int newVerticalScroll){ // Perform action. } } ScrollListener myScrollChangeListener = newnewnew new ScrollListener(); myManager.setScrollListener(myScrollChangeListener); By Taiguo Zhang (confach@gmail.com) 88 444 4 第444 4 章 使用音频 播放一个支持的音频格式的曲调播放一个支持的音频格式的曲调播放一个支持的音频格式的曲调 播放一个支持的音频格式的曲调 在支持标准音频格式的 BlackBerry 设备上,你可以播放下列支持的格式之一的音频文件:  audio/MPEG-1 Layer 3  audio/midi  audio/x-midi  audio/mid BlackBerry 设备使用 Mobile Media API(javax.microedition.media)包来支持标准的音频文件 格式。 为了在运行时确定支持的音频格式,调用 Manager.getSupportedContentTypes().为得到信息, 参看 API参考里的 javax.microedition.media 包。 语音记事语音记事语音记事 语音记事 APIAPIAPI API 在net.rim.device.api.system 包里,语音记事 API由下面的三个方法组成:  Audio.playFile(int audioCodec, int fs, String fileName)  Audio.recordFile(int audioCodec, int fs, String fileName)  Audio.stopFile(int audioCodec, int fs, String fileName) 每个方法都接受一个编码,一个文件系统以及一个文件名。语音记事编码由 Audio.AUDIO_CODEC_VOICENOTE 表现。iDEN™文件系统由 net.rim.device.api.io.FILESYSTEM_PATRIOT 来表现。文件系统是普通的文件系统,因此, 文件名参数由一个没有路径名的文件名组成。 当录音,播放或停止操作失败或完成时,应用程序应该注册一个音频监听者来接收这些消息 。 为了注册一个监听者,实现 net.rim.device.api.system.AudioFileListener. 通过调用 Audio.addListener(Application, AudioListener)来注册。 注:文件系统的大小,目前大约是 250KB,制约者录音的长短,大约是 8到9秒的语 音记事录音。如果录音超过文件系统大小,录音停止并保存文件。 为获得更多信息,参看 API参考里的 net.rim.device.api.system audio 类。 播放一个支持的音频格式的曲调 语音记事 API By Taiguo Zhang (confach@gmail.com) 89 555 5 第555 5 章 支持的媒体内容(MediaMediaMedia Media ContentContentContent Content ) PME PME PME PME 内容内容内容 内容 BlackBerry 设备支持 PME 格式的富(rich)媒体内容。 开发者可以使用 Plazmic Content Developer’s Kit for BlackBerry 来创建 PME 内容。这个工具, 以及附带的文档可以在 Plazmic 网站(www.plazmic.com)找到。 Media Engine API(在net.rim.plazmic.mediaengine 和 net.rim.plazmic.mediaengine.io 包 中 ) 允许应用程序获取和播放存储在 BlackBerry 设备上或网络上的 PME 内容。 注:Media Engine API支持媒体格式 application/x-vnd.rim.pme. Web 服务器必须为 application/x-vnd.rim.pme 设置 MIME 类型。 PMEPMEPME PME API API API API 概览 下面 3个主要类(在 net.rim.plazmic.mediaengine 包里)提供了加载和播放 PME 媒体内容的 能力。 媒体加载 Media Engine API允许应用程序使用下面 4种协议种的一种加载媒体内容: PME PME PME PME 内容 播放媒体内容 监听媒体内容事件 创建定制的连接 类 描述 MediaManager 提供从本地或网络上加载媒体内容的方法。 MediaPlayer 提供播放 PME 媒体的方法。 MediaException 为获取或播放媒体的错误提供异常代码。 协议 描述 http:// http 协议从一个使用 HTTP 连接网络 Web 服 务器下载内容。这个协议需要一个带有 BlackBerry MDS 服务的 BES(BlackBerry Enterprise Server,BlackBerry 企业服务器). By Taiguo Zhang (confach@gmail.com) 90 为使用其他协议,实现定制的 Connector。为获得更多信息,参看 91 页的“创建定制的 Connector”. 播放状态(PlaybackPlaybackPlayback Playback statesstatesstates states ) 为了获取 MediaPlayer 的当前状态,调用 MediaPlayer.getState(). 异常 MediaEngine 和MediaManager 类的方法抛出一个 MediaException 异常,这个异常包含了一 个标准的 HTTP 响应代码或者下面异常代码之一。为了获取与异常相联系的错误代码,调用 MediaException.getCode(). https:// https 协议从一个使用 HTTPS 连接网络 Web 服务器下载内容。这个协议需要一个带有 BlackBerry MDS 服务的 BES(BlackBerry Enterprise Server,BlackBerry 企业服务器). Jar:/// jar 协议加载存储在本地 BlackBerry 设备上的 jar 文件。 jar:///sample.pme 注意:开始的斜线(/)是需要的。 在BlackBerry IDE 中 ,.jar 文件必须加入到调 用应用程序或应用程序依赖的库的相同项目 中。 cod:// cod 协议加载存储在本地 BlackBerry 设备上 的cod 文件。 cod://mediasample/sample.pme 状态 描述 UNREALIZED MediaPlayer 未准备播放媒体。为了转到 REALIZED 状态,调用 MediaPlayer.setMedia (). REALIZED MediaPlayer 准备好播放媒体。为了开始播放 , 并转到STARTED 状态,调用 MediaPlayer.start(). STARTED MediaPlayer 正在播放媒体。为了停止播放和 返回到REALIZED 状态,调用 MediaPlayer.stop(). 异常代码 描述 INVALID_HEADER 媒体格式无效。 REQUEST_TIMED_OUT 请求超时。 INTERRUPTED_DOWNLOAD 应用程序调用 MediaManager.cancel()来取消 下载。 UNSUPPORTED_TYPE 媒体类型(MIME 类型)不支持。 UPGRADE_PALYER 媒体引擎的版本和请求的内容不兼容。 UPGRADE_MEDIA 媒体引擎的版本不在支持请求的内容。 CHECKSUM_MISMACTH 求和校验失败,因此媒体内容不能读取。 By Taiguo Zhang (confach@gmail.com) 91 事件 MediaListener 接口允许应用程序接受或响应下面的事件: 为获得更多信息,参考 85 页的“监听 Media Engine 事件”. 播放媒体内容播放媒体内容播放媒体内容 播放媒体内容 为了获取 BlackBerry 设备或网络上的 PME 内容,使用 MediaManager 的方法。为了播放已 经下载到 BlackBerry 设备的 PME 内容,使用 MediaPlayer 类的方法。 下载内容 为下载 PME 内容,创建一个 MediaManager 对象,然后调用 MediaManager.createMedia(). 注:下面缺省的协议会被支持:http://,https://.jar://,和cod://.为获得更多信息,参看 81 页 的“媒体加载”。 第一次调用 MediaManager.createMedia() ,URL必须是绝对路径,除非首先调用 OUT_OF_BOUNDS 数组出界,或应用程序试图访问一个文件结 尾后的输入流。 事件 描述 MEDIA_REQUEST 媒体已请求加载,当animation 自动请求新内 容或当用户点击媒体内容的超连接时,事件 发生。 MEDIA_REALIZED 媒体已经创建播放了。当 MediaManager.createMediaManager()已经 调用时发生。 MEDIA_COMPLETE 媒体已经加载,并成功播放。 MEDIA_TO 媒体正在加载。 trytrytry try { Object media = manager.createMedia("http://webserver/sample.pme"); } catchcatchcatch catch (IOException ioe) { System.out.println("Error: requested content was not downloaded."); } catchcatchcatch catch (MediaException me) { System.out.println("Error: “ + me.getCode()); } By Taiguo Zhang (confach@gmail.com) 92 MediaManager.setProperty(“URI_BASE”,) 设置基 URL路径。当你之后调用 createMedia()时,前面的 URL作为基 URL。 播放PME PME PME PME 内容 为播放设置 PME PME PME PME 对象 调用 MedialPlayer.setMedia(). 获取一个显示 PME PME PME PME 内容的 UIUIUI UI 对象 调用 MediaPlayer.getUI()。转化 getUI()返回的一个作为 Field 的对象,然后将之加入到屏幕 来显示。 开始播放下载的 PME PME PME PME 内容 调用 MediaPlayer.start()。 注:在调用 MediaPlayer.start()前检查 MediaPlayer 的状态,如果媒体播放器不是 REALIZED 状态,start()方法抛出一个异常。 MediaPlayer player = newnewnew new MediaPlayer(); trytrytry try { player.setMedia(media); } catchcatchcatch catch (MediaException me) { System.out.println("Error: requested content type is not supported.”); } add((Field)player.getUI()); ififif if (player.getState() == MediaPlayer.REALIZED) { trytrytry try { player.start(); } catchcatchcatch catch (MediaException me) { System.out.println("Error occurred during media playback: " + me.getCode() + me.getMessage()); } } By Taiguo Zhang (confach@gmail.com) 93 代码实例 MediaSample.java 实例从一个 Web 服务器获取一个 PME 文件,然后显示它。 例:MediaSample.javaMediaSample.javaMediaSample.java MediaSample.java /** * MediaSample.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.mediasample; importimportimport import java.io.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.plazmic.mediaengine.*; publicpublicpublic public classclassclass class MediaSample extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { MediaSample app = newnewnew new MediaSample(); app.enterEventDispatcher(); } publicpublicpublic public MediaSample() { pushScreen(newnewnew new MediaSampleScreen()); } finalfinalfinal final staticstaticstatic static classclassclass class MediaSampleScreen extendsextendsextends extends MainScreen { publicpublicpublic public MediaSampleScreen() { supersupersuper super (); LabelField title = newnewnew new LabelField(“Media Sample”, LabelField.ELLIPSIS| LabelField.USE_ALL_WIDTH); setTitle(title); MediaPlayer player = newnewnew new MediaPlayer(); MediaManager manager = newnewnew new MediaManager(); trytrytry try { Object media = manager.createMedia(“http://webserver/SVGFILE.pme”); player.setMedia(media); } catchcatchcatch catch (IOException ioe) { } catchcatchcatch catch (MediaException me) { System.out.println(“Error during media loading: “); By Taiguo Zhang (confach@gmail.com) 94 System.out.println(me.getCode()); System.out.println(me.getMessage()); } add((Field)player.getUI()); trytrytry try { player.start(); } catchcatchcatch catch (MediaException me) { System.out.println(“Error occured during media playback: “); System.out.println(me.getCode()); System.out.println(me.getMessage()); } } } } 监听媒体引擎事件监听媒体引擎事件监听媒体引擎事件 监听媒体引擎事件 MediaListener 接口允许应用程序注册接收媒体引擎事件。应用程序可以在注册 MediaPlayer 和MediaEngine 对象上注册监听者。 当应用程序实现监听者时,它可以完成以下的动作:  提供内容下载状态的信息。  在后台下载内容,当完成时播放它。  下载一个 animation 自动请求的内容。 MediaListener 接口包含一个方法,listen 方法。 publicpublicpublic public voidvoidvoid void mediaEvent(Object sender, intintint int event, intintint int eventParam, Object data); 参数 描述 sender 本参数引用了发送事件的对象,如 MediaPlayer 或MediaManager 对象。 event 参数可以是下列事件之一:  MEDIA_REQUESTED:当新的内容 请求时发送事件。  MEDIA_COMPLETE: 当所有计划 好的媒体动作完成时触发事件。  MEDIA_REALIZED : 由 MediaManager 发送,返回下载的媒 体。  MEDIA_IO:由MediaPlayer 发 送 ,提 By Taiguo Zhang (confach@gmail.com) 95 监听媒体引擎事件 MediaListener 接口的实现允许你的应用程序监听一个媒体引擎事件。mediaEvent()的实现 应该处理所有可能的媒体事件。下面的例子使用了一个 switch 语句来处理可能媒体事件。 注册监听者 为了注册你的监听者,调用MediaPlayer 和MediaManager 对象上的 addMediaListener()方法。 供现在进度或状态的信息。 eventParam 不要使用这个参数,因为它可能接收一个任 意值。它存在是为了为额外的事件提供一个 一致的接口。 data 当data 参数是 MEDIA_REQUESTED,data 把请求的 URL作为一个 String 对象。 当data 参数是 MEDIA_REALIZED,data 引用 了创建的媒体对象。 当data 参数是 MEDIA_IO,data 引用了一个 net.rim.plazmic.mediaengine.io.LoadingStatus 对象。 publicpublicpublic public finalfinalfinal final classclassclass class MediaListenerImpl implementsimplementsimplements implements MediaListener { publicpublicpublic public voidvoidvoid void mediaEvent(Object sender, intintint int event, intintint int eventParam, Object data) { switchswitchswitch switch (event) { casecasecase case MEDIA_REQUESTED: // Perform action. breakbreakbreak break ; casecasecase case MEDIA_COMPLETE: // Perform action. breakbreakbreak break ; casecasecase case MEDIA_REALIZED: // Perform action. breakbreakbreak break ; casecasecase case MEDIA_IO: // Perform action. breakbreakbreak break ; } } } privateprivateprivate private MediaListenerImpl _listener = newnewnew new MediaListenerImpl(); privateprivateprivate private MediaPlayer player = newnewnew new MediaPlayer(); privateprivateprivate private MediaManager manager = newnewnew new MediaManager(); player.addMediaListener(_listener); By Taiguo Zhang (confach@gmail.com) 96 在后台加载内容 当实现 MediaListener 时,你可以在背后下载 PME 内容,并且当下载完成后播放内容。 调用 MediaManager.createMediaListener()为将来的播放下载内容。 注:和createMedia()不一样,createMediaLater()不返回一个媒体内容的对象。 在MediaListener.mediaEvent()中,当请求的内容下载时,加入代码来处理 MEDIA_REALIZED 事件。为了注册在 data 参数里指定的内容,调用MediaPlayer.setMedia(data)。为了开始播放 , 调用 MediaPlayer.start( )。 跟踪下载进度 为得到下载进度的信息,使用 net.rim.plazmic.mediaengine.io.LoadingStatus 类。这个类包含 了一些方法来允许你获得媒体内容类型,字节总数,字节读取数,以及内容的源 URL。 manager.addMediaListener(_listener); manager.createMediaLater("http://webserver/sample.pme"); publicpublicpublic public voidvoidvoid void mediaEvent(Object sender, intintint int event, intintint int eventParam, Object data) { switchswitchswitch switch (event) { ... casecasecase case MEDIA_REALIZED: trytrytry try { player.setMedia(data); player.start(); } catchcatchcatch catch (MediaException me) { System.out.println("Error playing media” + me.getCode() +" + " me.getMessage()); } breakbreakbreak break ; } } 状态 描述 LOADING_STARTED 加载开始。 LOADING_READING 数据流正在解析。 LOADING_FINISHED 加载媒体成功。 LOADING_FAILED 媒体记载失败.  为获取详细的错误代码,调用getCode(). 参看 82 页获得更多详情。 By Taiguo Zhang (confach@gmail.com) 97 在mediaEvent()的实现里,当 MEDIA_IO 事件发生时,将 data 参数里的 Object 转化为一个 LoadingStatus 对象。 调用 LoadingStatus.getStatus()来获取下载的状态,然后处理每个状态。 对每个正常的状态,打印一个消息到控制台。 对LOADING_FAILED 状态,完成下面的动作:  调用 LoadingStatus.getCode()获得错误代码。  调用 LoadingStatus.getMessage()获得详细的消息。  调用 LoadingStatus.getSource()获得内容的 URL字符串。  为得到异常信息,调用 getMessage(). publicpublicpublic public voidvoidvoid void mediaEvent(Object sender, intintint int event, intintint int eventParam, Object data) { switchswitchswitch switch (event) { ... casecasecase case MEDIA_IO:{ } ... breakbreakbreak break ; } breakbreakbreak break ; ... switchswitchswitch switch (s.getStatus()) { casecasecase case LoadingStatus.LOADING_STARTED: System.out.println("Loading in progress"); breakbreakbreak break ; casecasecase case LoadingStatus.LOADING_READING: System.out.println("Parsing in progress"); breakbreakbreak break ; casecasecase case LoadingStatus.LOADING_FINISHED: System.out.println("Loading completed"); breakbreakbreak break ; casecasecase case LoadingStatus.LOADING_FAILED: String errorName = nullnullnull null ; intintint int code = s.getCode(); switchswitchswitch switch (code) { casecasecase case MediaException.INVALID_HEADER: errorName = "Invalid header" + "\n" + s.getSource(); breakbreakbreak break ; casecasecase case MediaException.REQUEST_TIMED_OUT: errorName = "Request timed out" + "\n" + s.getSource(); breakbreakbreak break ; casecasecase case MediaException.INTERRUPTED_DOWNLOAD: breakbreakbreak break ; By Taiguo Zhang (confach@gmail.com) 98 代码实例 MediaSample2.java 实例实现了一个监听者在后台下载媒体内容,并显示下载的状态到控制 台。 例:MediaSample2.java /** * MediaSample2.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.mediasample; importimportimport import java.io.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.plazmic.mediaengine.*; importimportimport import net.rim.plazmic.mediaengine.io.*; casecasecase case MediaException.UNSUPPORTED_TYPE: errorName = "Unsupported type" + s.getMessage() + "\n" + s.getSource(); breakbreakbreak break ; defaultdefaultdefault default :{ ififif if (code > 200) { //A code > 200 indicates an HTTP error errorName = "URL not found"; } elseelseelse else { // default unidentified error errorName = "Loading Failed"; } errorName += "\n" + s.getSource() + "\n" + s.getCode()+ ":" + s.getMessage(); breakbreakbreak break ; } } System.out.println(errorName); breakbreakbreak break ; }// End switch s.getStatus(). breakbreakbreak break ; } By Taiguo Zhang (confach@gmail.com) 99 publicpublicpublic public classclassclass class MediaSample2 extendsextendsextends extends UiApplication { privateprivateprivate private MediaPlayer player = newnewnew new MediaPlayer(); privateprivateprivate private MediaManager manager = newnewnew new MediaManager(); privateprivateprivate private MediaListenerImpl _listener = newnewnew new MediaListenerImpl(); privateprivateprivate private MediaSample2Screen _screen; publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { MediaSample2 app = newnewnew new MediaSample2(); app.enterEventDispatcher(); } publicpublicpublic public MediaSample2() { _screen = newnewnew new MediaSample2Screen(); pushScreen(_screen); } publicpublicpublic public finalfinalfinal final classclassclass class MediaListenerImpl implementsimplementsimplements implements MediaListener { publicpublicpublic public voidvoidvoid void mediaEvent(Object sender, intintint int event, intintint int eventParam, Object data) { switchswitchswitch switch (event) { casecasecase case MEDIA_REQUESTED: System.out.println(“Media requested”); breakbreakbreak break ; casecasecase case MEDIA_COMPLETE: System.out.println(“Media completed”); breakbreakbreak break ; casecasecase case MEDIA_REALIZED: trytrytry try { player.setMedia(data); player.start(); } catchcatchcatch catch (MediaException me) { System.out.println(“Error during media loading: “ + me.getCode() + me.getMessage()); } breakbreakbreak break ; casecasecase case MEDIA_IO:{ LoadingStatus s = (LoadingStatus)data; switchswitchswitch switch (s.getStatus()) { casecasecase case LoadingStatus.LOADING_STARTED: System.out.println(“Loading in progress”); breakbreakbreak break ; By Taiguo Zhang (confach@gmail.com) 100 casecasecase case LoadingStatus.LOADING_READING: System.out.println(“Parsing in progress”); breakbreakbreak break ; casecasecase case LoadingStatus.LOADING_FINISHED: System.out.println(“Loading completed”); breakbreakbreak break ; casecasecase case LoadingStatus.LOADING_FAILED: String errorName = nullnullnull null ; intintint int code = s.getCode(); switchswitchswitch switch (code) { casecasecase case MediaException.INVALID_HEADER: errorName = “Invalid header” + “\n” + s.getSource(); breakbreakbreak break ; casecasecase case MediaException.REQUEST_TIMED_OUT: errorName = “Request timed out” + “\n” + s.getSource(); breakbreakbreak break ; casecasecase case MediaException.INTERRUPTED_DOWNLOAD: breakbreakbreak break ; casecasecase case MediaException.UNSUPPORTED_TYPE: errorName = “Unsupported type” + s.getMessage() + “\n” + s.getSource(); breakbreakbreak break ; defaultdefaultdefault default :{ ififif if (code > 200) { //A code > 200 indicates an HTTP error. errorName = “URL not found”; } elseelseelse else { // Default unidentified error. errorName = “Loading Failed”; } errorName += “\n” + s.getSource() + “\n”+ s.getCode() + “:“ + s.getMessage(); breakbreakbreak break ; } } System.out.println(errorName); breakbreakbreak break ; }// End switch s.getStatus(). breakbreakbreak break ; } } By Taiguo Zhang (confach@gmail.com) 101 } } finalfinalfinal final classclassclass class MediaSample2Screen extendsextendsextends extends MainScreen { publicpublicpublic public MediaSample2Screen() { supersupersuper super (); LabelField title = newnewnew new LabelField(“Media Sample”, LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); manager.addMediaListener(_listener); // Change this to the location of a test .pme file. manager.createMediaLater(“http://test.rim.com/SVGBS0001.pme”); add((Field)player.getUI()); } } } 创建一个定制的连接创建一个定制的连接创建一个定制的连接 创建一个定制的连接 MediaManager 使用一个 Connector 对象加载媒体,并打开输入流。缺省的 Connector 支持下 列协议:http://.https://,jar://,以及 cod://。为了增加支持一个定制的协议或者为了覆写缺省的行 为,通过实现 net.rim.plazmic.mediaengine.io.Connector 接口创建一个定制的 Connector 实现一个定制的 connectorconnectorconnector connector 为了完成处理一个定制的协议,实现 Connector 接口,包含 getInputStream()。为了处理一 个标准的协议,调用缺省的 Connector。 setProperty(String name, String value)的实现设置了指定的属性。在本例中,connector 不必设 置任何指定的属性,因此 setProperty()的实现调用了 Connector 上的 setProperty( )。 publicpublicpublic public classclassclass class SampleConnector implementsimplementsimplements implements Connector { Connector delegate; // The default Connector. SampleConnector(Connector delegate) { thisthisthis this .delegate = delegate; } publicpublicpublic public InputStream getInputStream(String uri, ConnectionInfo info) 方法签名 实现 InputStream getInputStream(String, ConnectionInfo) 实现本方法返回一个输入流从指定 URI 读取 内容。 void releaseConnection(ConnectionInfo) 实现本方法释放连接。MediaManager 调用本 方法来通知 Connector 可以释放连接了。 void setProperty(String, String) 实现本方法设置连接指定的属性。 By Taiguo Zhang (confach@gmail.com) 102 throwsthrowsthrows throws IOException, MediaException { InputStream input = nullnullnull null ; ififif if (uri.startsWith("myprotocol://")){ // Perform special tasks. info.setConnection(newnewnew new MyProtocolConnection()); info.setContentType("application/x-vnd.rim.pme"); // openMyInputStream() is a custom method that opens //stream for "myprotocol://". input = openMyInputStream(uri); } elseelseelse else { input = delegate.getInputStream(uri, info); } returnreturnreturn return input; } publicpublicpublic public voidvoidvoid void releaseConnection(ConnectionInfo info) throwsthrowsthrows throws IOException, MediaException { Object o = info.getConnection(); ififif if (o instanceofinstanceofinstanceof instanceof MyProtocolConnection) { ((MyProtocolConnection)o).close(); // Perform cleanup. } elseelseelse else { delegate.releaseConnection(info); } } publicpublicpublic public voidvoidvoid void setProperty(String property, String value) { delegate.setProperty(property, value); } } 注册一个定制的连接器 在你的主要方法里,调用 MediaManager.setConnector()注册你的定制的连接器。 MediaManager manager = newnewnew new MediaManager(); manager.setConnector(newnewnew new CustomPMEConnector(manager.getDefaultConnector())); By Taiguo Zhang (confach@gmail.com) 103 代码实例 CustomPMEConnector.java 实例为实现一个定制的连接器提供了一个框架。 例:CustomPMEConnector.java /* * CustomPMEConnector.java * Copyright (C) 2003-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.mediasample; importimportimport import java.io.*; importimportimport import net.rim.plazmic.mediaengine.*; importimportimport import net.rim.plazmic.mediaengine.io.*; publicpublicpublic public classclassclass class CustomPMEConnector implementsimplementsimplements implements Connector { privateprivateprivate private Connector delegate; privateprivateprivate private InputStream input; CustomPMEConnector(Connector delegate) { thisthisthis this .delegate = delegate; } publicpublicpublic public InputStream getInputStream(String uri, ConnectionInfo info) throwsthrowsthrows throws IOException, MediaException { ififif if (uri.startsWith("myprotocol://")) { // Perform special tasks. info.setConnection(newnewnew new MyProtocolConnection()); info.setContentType("application/x-vnd.rim.pme"); // OpenMyInputStream() is a custom method that opens //stream for “myprotocol://” input = openMyInputStream(uri); } elseelseelse else { input = delegate.getInputStream(uri, info); returnreturnreturn return input; } By Taiguo Zhang (confach@gmail.com) 104 privateprivateprivate private InputStream openMyInputStream(String uri) { InputStream input = nullnullnull null ; } //@todo: open stream here returnreturnreturn return input; } publicpublicpublic public voidvoidvoid void releaseConnection(ConnectionInfo info) throwsthrowsthrows throws IOException, MediaException { Object o = info.getConnection(); ififif if (o instanceofinstanceofinstanceof instanceof MyProtocolConnection) { ((MyProtocolConnection)o).close(); // Perform cleanup. } elseelseelse else { delegate.releaseConnection(info); } } publicpublicpublic public voidvoidvoid void setProperty(String property, String value) { delegate.setProperty(property, value); } // Inner class that defines the connection class. publicpublicpublic public staticstaticstatic static classclassclass class MyProtocolConnection { publicpublicpublic public MyProtocolConnection() { //... } publicpublicpublic public voidvoidvoid void close() { //... } } } By Taiguo Zhang (confach@gmail.com) 105 By Taiguo Zhang (confach@gmail.com) 106 666 6 第666 6 章 连接网络 HTTP HTTP HTTP HTTP 和和和 和 Socket Socket Socket Socket 连接连接连接 连接 尽管你可以通过 socket 连接实现 HTTP,但是最好使用 HTTP 连接,因为 socket 连接不支持 BlackBerry MDS 服务特性,例如 push。也最好使用 HTTP 连接,因为比起那些使用 HTTP 连接的应用程序,使用 socket 连接的应用程序明显需要更多的带宽。 注:如果你使用 socket 连接,将你的应用程序设计为适应断断续续的无线网络连接。 例如,如果你的程序发生错误时,它会重新打开连接。 使用使用使用 使用 HTTP HTTP HTTP HTTP 连接连接连接 连接 注:使用 BlackBerry Internet Service Browser 的java 程序不会启动 HTTP,HTTPS 和 TCP 连接。 打开一个 HTTP HTTP HTTP HTTP 连接 为了打开一个 HTTP 连接,调用 Connector.open(),指定 http 为协议。将返回的对象转化为一 个HTTPConnection 或者 StreamConnection 对象。HttpConnection 是一个 StreamConnection, 它提供访问指定 HTTP 功能,包括 HTTP 头和其他 HTTP 资源。 HTTP HTTP HTTP HTTP 和socket socket socket socket 连接 使用HTTP HTTP HTTP HTTP 连接 使用HTTPS HTTPS HTTPS HTTPS 连接 使用socket socket socket socket 连接 使用端口(port)(port)(port) (port) 连接 使用蓝牙序列端口连接 HttpConnection conn = nullnullnull null ; String URL = "http://www.myServer.com/myContent"; conn = (HttpConnection)Connector.open(URL); By Taiguo Zhang (confach@gmail.com) 107 设置HTTP HTTP HTTP HTTP 请求方式 为设置 HTTP 请求方式(GET 或POST),调用 HttpConnection.setRequestMethod(). 设置或获取头字段 为HTTP 请求或 HTTP 响应消息设置或获取头字段,调用 HttpConnection 上的 getRequestProperty() 或setRequestProperty()。 发送和接受数据 为发送和接受数据,调用HTTPConnection 的openInputStream()和openOutputStream()获得输 入和输出流。 代码实例 HttpFetch.java 实例使用了一个 HTTP 连接来获取数据。它遵循下列步骤: 1. 创建一个连接线程。 2. 定义一个方法获取数据。 3. 定义一个方法将数据显示给用户。 4. 定义一个方法退出应用程序。 5. 定义应用程序构造子。 注:HTTPFetch.java 实例需要你在应用程序工程里创建资源文件,并且定义需要的资 源键值。参看 125 页“本地化应用程序”获得更多信息。 例:HTTPFetch.java /** * HTTPFetch.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.httpfetch; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; conn.setRequestMethod(HttpConnection.POST); conn.setRequestProperty("User-Agent","BlackBerry/3.2.1"); String lang = conn.getRequestProperty("Content-Language"); InputStream in = conn.openInputStream(); OutputStream out = conn.openOutputStream(); By Taiguo Zhang (confach@gmail.com) 108 importimportimport import net.rim.device.api.i18n.*; importimportimport import net.rim.device.api.system.*; importimportimport import javax.microedition.io.*; importimportimport import java.io.*; importimportimport import com.rim.samples.docs.baseapp.*; importimportimport import com.rim.samples.docs.resource.*; publicpublicpublic public classclassclass class HTTPFetch extendsextendsextends extends BaseApp implementsimplementsimplements implements HTTPFetchResource { // Constants. privateprivateprivate private staticstaticstatic static finalfinalfinal final String SAMPLE_PAGE = "http://localhost/testpage/sample.txt”; privateprivateprivate private staticstaticstatic static finalfinalfinal final String[] HTTP_PROTOCOL = {"http://", “http:\\”}; privateprivateprivate private MainScreen _mainScreen; privateprivateprivate private RichTextField _content; /** * Send and receive data over the network on a * separate thread from the main thread of your application. */ ConnectionThread _connectionThread = newnewnew new ConnectionThread(); //statics privateprivateprivate private staticstaticstatic static ResourceBundle _resources = ResourceBundle.getBundle( HTTPFetchResource.BUNDLE_ID, HTTPFetchResource.BUNDLE_NAME); publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { HTTPFetch theApp = newnewnew new HTTPFetch(); theApp.enterEventDispatcher(); } /** * The ConnectionThread class manages the HTTP connection. * Fetch operations are not queued, but if a second fetch request * is made while a previous request is still active, * the second request stalls until the previous request completes. */ privateprivateprivate private classclassclass class ConnectionThread extendsextendsextends extends Thread { privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int TIMEOUT = 500; //ms privateprivateprivate private String _theUrl; By Taiguo Zhang (confach@gmail.com) 109 /* The volatile keyword indicates that because the data is shared, * the value of each variable must always be read and written from memory, * instead of cached by the VM. This technique is equivalent to wrapping * the shared data in a synchronized block, but produces less overhead. */ privateprivateprivate private volatilevolatilevolatile volatile booleanbooleanboolean boolean _start = falsefalsefalse false ; privateprivateprivate private volatilevolatilevolatile volatile booleanbooleanboolean boolean _stop = falsefalsefalse false ; /** * Retrieve the URL. The synchronized keyword makes sure that only one * thread at a time can call this method on a ConnectionThread object. */ publicpublicpublic public synchronizedsynchronizedsynchronized synchronized String getUrl() { returnreturnreturn return _theUrl; } /** * Fetch a page. This method is invoked on the connection thread by * fetchPage(), which is invoked in the application constructor when * the user selects the Fetch menu item. */ publicpublicpublic public voidvoidvoid void fetch(String url) { _start = truetruetrue true ; _theUrl = url; } /** * Close the thread. Invoked when the application exits. */ publicpublicpublic public voidvoidvoid void stop() { _stop = truetruetrue true ; } /** * Open an input stream and extract data. Invoked when the thread * is started. By Taiguo Zhang (confach@gmail.com) 110 */ publicpublicpublic public voidvoidvoid void run() { forforfor for (;;) { // Thread control. whilewhilewhile while (!_start &&!_stop) { // No connections are open for fetch requests, // but the thread has not been stopped. trytrytry try { sleep(TIMEOUT); } catchcatchcatch catch (InterruptedException e) { System.err.println(e.toString()); } } // Exit condition. ififif if (_stop ) { returnreturnreturn return ; } /* Ensure that fetch requests are not missed * while received data is processed. */ synchronizedsynchronizedsynchronized synchronized (thisthisthis this ) { // Open the connection and extract the data. StreamConnection s = nullnullnull null ; trytrytry try { s = (StreamConnection)Connector.open(getUrl()); InputStream input = s.openInputStream(); // Extract data in 256 byte chunks. bytebytebyte byte [] data = newnewnew new bytebytebyte byte [256]; intintint int len = 0; StringBuffer raw = newnewnew new StringBuffer(); whilewhilewhile while (-1 != (len = input.read(data)) ) { raw.append(newnewnew new String(data, 0, len)); } String text = raw.toString(); By Taiguo Zhang (confach@gmail.com) 111 updateContent(text); input.close(); s.close(); } catchcatchcatch catch (IOException e) { System.err.println(e.toString()); // Display the text on the screen. updateContent(e.toString()); } // Reset the start state. _start = falsefalsefalse false ; } } } } // Constructor. publicpublicpublic public HTTPFetch() { _mainScreen = newnewnew new MainScreen(); _mainScreen.setTitle(newnewnew new LabelField( _resources.getString(APPLICATION_TITLE), LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH)); _mainScreen.add(newnewnew new SeparatorField()); _content = newnewnew new RichTextField( _resources.getString(HTTPDEMO_CONTENT_DEFAULT)); _mainScreen.add(_content); _mainScreen.addKeyListener(thisthisthis this ); _mainScreen.addTrackwheelListener(thisthisthis this ); // Start the helper thread. _connectionThread.start(); pushScreen(_mainScreen); fetchPage(SAMPLE_PAGE); } // Retrieve web content. privateprivateprivate private voidvoidvoid void fetchPage(String url) { // Perform basic validation (set characters to lowercase and add http:// or https://). String lcase = url.toLowerCase(); booleanbooleanboolean boolean validHeader = falsefalsefalse false ; intintint int i = 0; forforfor for (i = HTTP_PROTOCOL.length - 1; i >= 0; --i) By Taiguo Zhang (confach@gmail.com) 112 { ififif if (-1 != lcase.indexOf(HTTP_PROTOCOL[i]) ) { validHeader = truetruetrue true ; breakbreakbreak break ; } } ififif if (!validHeader ) { // Prepend the protocol specifier if it is missing. url = HTTP_PROTOCOL[0] + url; } // Create a new thread for connection operations. _connectionThread.fetch(url); } // Display the content. privateprivateprivate private voidvoidvoid void updateContent(finalfinalfinal final String text) { /* This technique creates several short-lived objects but avoids * the threading issues involved in creating a static Runnable and * setting the text. */ UiApplication.getUiApplication().invokeLater(newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { _content.setText(text); } }); } // Close the connection thread when the user closes the application. protectedprotectedprotected protected voidvoidvoid void onExit() { _connectionThread.stop(); } } By Taiguo Zhang (confach@gmail.com) 113 使用使用使用 使用 HTTPS HTTPS HTTPS HTTPS 连接连接连接 连接 注:BlackBerry Internet Service Browser 不允许 Java 应用程序启动 HTTP,HTTPS 和TCP 连接。 打开一个 HTTPS HTTPS HTTPS HTTPS 连接 为打开一个 HTTPS 连接,调用 Connector.open(),指定 https 作为协议。将返回的对象转化 为一个 HttpsConnection; 指定代理或终端到终端(end_to_endend_to_endend_to_end end_to_end )模型 缺省的,连接在代理模型中使用 HTTPS。用户也可以设置一个 BlackBerry 设备选项来使用 缺省的 end_to_end 模型。为获得更多信息,参看 189 页的“HTTPS 支持“。 为了在 end_to_end 模型里打开一个 HTTPS 连接,将下列参数中的一个增加到传递给 Connector.open()的连接字符串中: 注:BlackBerry 设备缺省没有安装 end_to_end 模块。尽管这样,BlackBerry 桌面版 软件 3.6.0 及后续版本中包含了它。当应用程序加载到 BlackBerry 设备时,为了加载模块, 为你的应用程序在.alx 文件中加入下面的标记: HttpsConnection stream = (HttpsConnection)Connector.open("https://host:443/"); 参数 描述 EndToEndRequired 这个参数指定了一个end_to_end HTTPS连接 必须从 BlackBerry 设备到目标服务器中使 用。如果一个 end_to_end 的HTTPS 连接不能 建立,这个连接将会关闭。 EndToEndDesired 这个参数指定一个end_to_end HTTPS连接应 该从BlackBerry 设备到目标服务器中被使 用,如果 BlackBerry 设备支持它的话。如果 BlackBerry 设备不支持 end_to_end TLS,并 且用户许可代理 TLS 连接,那么一个代理连 接将被使用。 HttpsConnection stream = (HttpsConnection)Connector.open("https://host:443/;EndToEndDesire d"); By Taiguo Zhang (confach@gmail.com) 114 为获得更多信息,参看 183 页的”.alx 文件”。 使用使用使用 使用 socket socket socket socket 连接连接连接 连接 注:BlackBerry Internet Service Browser 不允许 Java 应用程序启动 HTTP,HTTPS 和TCP 连接。 指定TCP TCP TCP TCP 的设置 应用程序可以在下面的一种模式下建立一个 TCP socket 连接或一个在 TCP上的 HTTP连接。 注:使用直接 TCP 模式需要你和服务提供商一起紧密工作。联系你的服务提供商确保 支持直接 TCP socket 连接。 为了通过编程指定 TCP 设置,增加可选的 deviceside 参数到传递给 Connector.open()的连 接字符串。 在GSM 网络里,为指定 BlackBerry 设备商的 TCP 设置,用户点击 BlackBerry 设备选项的 TCP。 注:如果 IT策略设置允许 TCP 连接,TCP 才在 BlackBerry 设备选项里显示。 如果 TCP 设置没有指定,下将使用下面缺省的。 参看 API参考的 Connector 获得更多信息。 打开一个 socket socket socket socket 连接 为打开一个 socket 连接,调用 Connector.open(),指定 socket 为其协议。 注:应用程序必须显式的输入他们的本地机器 IP,因为 localhost 不被支持。 模式 描述 代理模式 BES 的MDS 服务特征为 BlackBerry 设备建 立与 Web 服务器的 TCP 连接 直接模式 BlackBerry 设备建立一个直接与 Web 服务器 的TCP 连接。 网络 缺省的 TCP 设置 可选的 TCP 设置 GSM 代理模式 直接模式 iDEN 直接模式 代理模式 privateprivateprivate private staticstaticstatic static String URL = "socket://:4444"; StreamConnection conn = nullnullnull null ; conn = (StreamConnection)Connector.open(URL); By Taiguo Zhang (confach@gmail.com) 115 在socket socket socket socket 连接上发送和接收数据 使用 openInputStream() 和openOutputStream()获得输入和输出流。 关闭连接 调用输入和输出流,以及 socket 连接上的 close()方法。 注:每个 close()方法抛出一个 IOException。应用程序必须实现这个异常的处理。 使用端口连接使用端口连接使用端口连接 使用端口连接 注:当你的应用程序首先访问 net.rim.device.api.system.SerialPort 类或 net.rim.device.api.system.USBPort 类时,检查NoClassDefFoundError。如果系统管理员 使用应用程序管理限制访问序列端口和 USB 接口,这个错误就会抛出。参看 16 页的“应用 程序管理”获得更多信息。 当它们使用一个序列端口或 USB 接口连上一台计算机式,应用程序可以使用一个序列端口或 USB 接口和桌面的应用程序进行通信。连接类型也可以使用来和一个插到序列端口或 USB 接 口的外围设备通信。 注:如果你正在使用端口连接和桌面应用程序通信,你不必让所有其他正在使用序列 端口或 USB 接口的应用程序运行。 打开一个 USB USB USB USB 接口或序列端口连接 调用 Connector.open(),指定 comm 作为协议,COM1 或USB 作为端口。 OutputStreamWriter _out = newnewnew new OutputStreamWriter(conn.openOutputStream()); String data = "This is a test"; intintint int length = data.length(); _out.write(data, 0, length); InputStreamReader _in = newnewnew new InputStreamReader(conn.openInputStream()); charcharchar char [] input = newnewnew new charcharchar char [length]; forforfor for ( intintint int i = 0; i < length; ++i ){ input[i] = (charcharchar char )_in.read(); } _in.close(); _out.close(); conn.close(); By Taiguo Zhang (confach@gmail.com) 116 在端口连接上发送数据 调用 openDataOnputStream() 和openOutputStream()获得输出流。 使用输出流上的写方法来写数据。 在端口连接上接收数据 调用 openDataInputStream() 和openIutputStream()获得输入流。 使用输入流上的读方法来写数据。 注:你不能从在主事件线程上的输入流读取,因为这个操作会阻塞直至数据接收完成。 创建一个独立的线程,在此线程上接收数据。 关闭端口连接 调用输入和输出流,以及端口连接上的 close()方法。 注:每个 close()方法可能抛出一个 IOException 异常。应用程序必须实现异常的处 理。 使用蓝牙序列端口连接使用蓝牙序列端口连接使用蓝牙序列端口连接 使用蓝牙序列端口连接 蓝牙 API(net.rim.device.api.bluetooth)允许应用程序访问蓝牙序列端口配置(Profile)以及 允许启动一个服务器或者客户端蓝牙序列端口连接到一台计算机或其他蓝牙无线技术支持 的设备。 注:当你的应用程序首先访问蓝牙 API 时,会检查 NoClassDefFoundError。如果系统 privateprivateprivate private StreamConnection _conn = (StreamConnection)Connector.open( "comm:COM1;baudrate=9600;bitsperchar=8;parity=none;stopbits=1"); DataOutputStream _dout = _conn.openDataOutputStream(); privateprivateprivate private String data = "This is a test"; _dout.writeChars(test); DataInputStream _din = _conn.openInputStream(); String contents = _din.readUTF(); _din.close(); _dout.close(); conn.close(); By Taiguo Zhang (confach@gmail.com) 117 管理员使用应用程序管理限制访问序列端口和 USB 接口,这个错误就会抛出。参看 16 页的 “应用程序管理”获得更多信息。 BlackBerry 模拟器不支持蓝牙。 打开一个蓝牙序列端口连接 为了打开一个蓝牙序列端口连接,调用 Connector.open(), 它提供由 BluetoothSerialPort.getSerialPortInfo()返回的序列端口信息作为参数。 由这个方法返回的连接字符串指定了作为协议的 btspp:// 以及下面条目之一:  如果你正在打开一个连接作为客户端,由getSerialPortInfo().toString()返回的连接字符串 包含了设备号(device ID)以及 Server 设备正在监听的端口。  如果你正在打开一个连接作为服务器,由getSerialPortInfo().toString()返回的连接字符串 包含了你的 BlackBerry 设备正在监听的端口。 在蓝牙序列端口连接上发送数据 调用 openDataOutputStream()或openOutputStream()获得一个输出流。 注:直到连接建立,这个调用会阻塞。 在输出流上使用写方法来写数据 在蓝牙序列端口连接上接收数据 调用 openDataInputStream()或openInputStream()获得一个输入流。 在输入流上使用读方法来读数据 注:你不能在主事件线程上读取输入流数据,因为这个操作会阻塞直到数据接收完毕。 创建一个独立的线程来接收数据。 BluetoothSerialPortInfo[] info = BluetoothSerialPort.getSerialPortInfo(); StreamConnection _conn = (StreamConnection)Connector.open( info.toString(), Connector.READ_WRITE ); DataOutputStream _dout = _conn.openDataOutputStream(); privateprivateprivate private String data = "This is a test"; _dout.writeChars(test); DataInputStream _din = _conn.openInputStream(); String contents = _din.readUTF(); By Taiguo Zhang (confach@gmail.com) 118 关闭一个端口连接 在输入和输出流以及蓝牙序列端口连接上调用 close()方法。 代码实例 BluetoothSerialPortDemo.java 实例使一个简单蓝牙序列端口应用程序的客户端。这个应用 程序监听在序列端口上的数据,并且当数据到达时提交数据。 例:BluetoothSerialPortDemo.java /** * BluetoothSerialPortDemo.java * Copyright (C) 2004-2005 Research In Motion Limited. */ /* The client side of a simple serial port demonstration application. * This application listens for text on the serial port and ififif if (_bluetoothConnection != nullnullnull null ){ trytrytry try { _bluetoothConnection.close(); } catchcatchcatch catch (IOException ioe) { } } ififif if (_din != nullnullnull null ){ trytrytry try { _din.close(); } catchcatchcatch catch (IOException ioe) { } } ififif if (_dout != nullnullnull null ){ trytrytry try { _dout.close(); } catchcatchcatch catch (IOException ioe) { } } _bluetoothConnection = nullnullnull null ; _din = nullnullnull null ; _dout = nullnullnull null ; By Taiguo Zhang (confach@gmail.com) 119 * renders the data when it arrives. */ packagepackagepackage package com.rim.samples.docs.bluetoothserialportdemo; importimportimport import java.io.*; importimportimport import javax.microedition.io.*; importimportimport import net.rim.device.api.bluetooth.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.i18n.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.util.*; importimportimport import com.rim.samples.docs.baseapp.*; importimportimport import com.rim.samples.docs.resource.*; publicpublicpublic public classclassclass class BluetoothSerialPortDemo extendsextendsextends extends BaseApp implementsimplementsimplements implements BluetoothSerialPortDemoResResource { //statics ------------------------------------------------------- ----------- privateprivateprivate private staticstaticstatic static ResourceBundle _resources; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int INSERT = 1; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int REMOVE = 2; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int CHANGE = 3; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int JUST_OPEN = 4; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int CONTENTS = 5; privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int NO_CONTENTS = 6; staticstaticstatic static { _resources = ResourceBundle.getBundle(BluetoothSerialPortDemoResResource.BUNDLE_ID, BluetoothSerialPortDemoResResource.BUNDLE_NAME); } privateprivateprivate private EditField _infoField; privateprivateprivate private StreamConnection _bluetoothConnection; privateprivateprivate private DataInputStream _din; rivate DataOutputStream _dout; publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { BluetoothSerialPortDemo theApp = newnewnew new BluetoothSerialPortDemo(); theApp.enterEventDispatcher(); } By Taiguo Zhang (confach@gmail.com) 120 //constructor --------------------------------------------------- ----------- publicpublicpublic public BluetoothSerialPortDemo() { MainScreen mainScreen = newnewnew new MainScreen(); mainScreen.setTitle(newnewnew new LabelField(_resources.getString(TITLE), LabelField.USE_ALL_WIDTH)); _infoField = newnewnew new EditField(Field.READONLY); mainScreen.add(_infoField); mainScreen.addKeyListener(thisthisthis this ); mainScreen.addTrackwheelListener(thisthisthis this ); pushScreen(mainScreen); invokeLater(newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { openPort(); } }); } protectedprotectedprotected protected voidvoidvoid void onExit() { closePort(); } // Close the serial port. privateprivateprivate private voidvoidvoid void closePort() { ififif if (_bluetoothConnection != nullnullnull null ) { trytrytry try { _bluetoothConnection.close(); } catchcatchcatch catch (IOException ioe) { } ififif if (_din != nullnullnull null ) { trytrytry try { _din.close(); } catchcatchcatch catch (IOException ioe) { } } By Taiguo Zhang (confach@gmail.com) 121 ififif if (_dout != nullnullnull null ){ trytrytry try { _dout.close(); } catchcatchcatch catch (IOException ioe) { } } _bluetoothConnection = nullnullnull null ; _din = nullnullnull null ; _dout = nullnullnull null ; } } // Open the serial port. privateprivateprivate private voidvoidvoid void openPort() { ififif if (_bluetoothConnection != nullnullnull null ){ closePort(); } newnewnew new InputThread().start(); } privateprivateprivate private classclassclass class InputThread extendsextendsextends extends Thread { publicpublicpublic public voidvoidvoid void run() { trytrytry try { BluetoothSerialPortInfo[] info = BluetoothSerialPort.getSerialPortInfo(); ififif if ( info == nullnullnull null || info.length == 0 ){ invokeAndWait( newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { Dialog.alert( "No bluetooth serial ports available for connection."); onExit(); System.exit(1); } }); _bluetoothConnection = (StreamConnection)Connector.open( info[0].toString(), Connector.READ_WRITE); _din = _bluetoothConnection.openDataInputStream(); _dout = _bluetoothConnection.openDataOutputStream(); } By Taiguo Zhang (confach@gmail.com) 122 catchcatchcatch catch (IOException e) { invokeAndWait( newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { Dialog.alert(“Unable to open serial port”); onExit(); System.exit(1); } }); } catchcatchcatch catch ( UnsupportedOperationException e ){ invokeAndWait( newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { Dialog.alert(“This handheld or simulator does not support bluetooth.”); onExit(); System.exit(1); } }); } trytrytry try { intintint int type, offset, count; String value; _dout.writeInt(JUST_OPEN); _dout.flush(); forforfor for (;;){ type = _din.readInt(); ififif if (type == INSERT){ offset = _din.readInt(); value = _din.readUTF(); insert(value, offset); } elseelseelse else ififif if (type == REMOVE){ offset = _din.readInt(); count = _din.readInt(); remove(offset, count); } elseelseelse else ififif if (type == JUST_OPEN){ // Send contents to desktop. value = _infoField.getText(); ififif if (value == nullnullnull null || value.equals(““)){ _dout.writeInt(NO_CONTENTS); _dout.flush(); } elseelseelse else { _dout.writeInt(CONTENTS); By Taiguo Zhang (confach@gmail.com) 123 _dout.writeUTF(_infoField.getText()); _dout.flush(); } } elseelseelse else ififif if (type == CONTENTS){ String contents = _din.readUTF(); synchronizedsynchronizedsynchronized synchronized (Application.getEventLock()) { _infoField.setText(contents); } } elseelseelse else ififif if (type == NO_CONTENTS){ } elseelseelse else { throwthrowthrow throw newnewnew new RuntimeException(); } } } catchcatchcatch catch (IOException ioe) { invokeLater(newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { Dialog.alert(“Problems reading from or writing to serial port.”); onExit(); System.exit(1); } }); } } } privateprivateprivate private voidvoidvoid void insert(finalfinalfinal final String msg, finalfinalfinal final intintint int offset) { invokeLater(newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { _infoField.setCursorPosition(offset); _infoField.insert(msg); } }); } privateprivateprivate private voidvoidvoid void remove(finalfinalfinal final intintint int offset, finalfinalfinal final intintint int count) { invokeLater(newnewnew new Runnable() { publicpublicpublic public voidvoidvoid void run() { _infoField.setCursorPosition(offset+count); _infoField.backspace(count); } By Taiguo Zhang (confach@gmail.com) 124 }); } /** * Override makeMenu to add custom menu items. */ protectedprotectedprotected protected voidvoidvoid void makeMenu(Menu menu, intintint int instance) { ififif if (_infoField.getTextLength() > 0) { menu.add(newnewnew new MenuItem(_resources, MENUITEM_COPY, 100000, 10) { publicpublicpublic public voidvoidvoid void run() { Clipboard.getClipboard().put(_infoField.getText()); } }); } supersupersuper super .makeMenu(menu, instance); } } By Taiguo Zhang (confach@gmail.com) 125 777 7 第777 7 章 使用数据报(DatagramDatagramDatagram Datagram )连接 数据报连接数据报连接数据报连接 数据报连接 通过利用 UDP(User Datagram Protocal,用户数据报协议),BlackBerry 设备支持数据报连 接。应用程序使用 UDP和标准的网络服务通信。 数据报是应用程序发送到网络的独立数据包。对于 Datagram 的负载字节数组来说,一个 Datagram 对象是一个包装器。为获得这个字节数组的一个引用,调用 getData()方法。 和HTTP 连接不一样,数据报连接不稳定:数据包以任意的顺序到达,并且传输得不到保证 。 应用程序的责任是确保请求数据报的数据负载根据网络服务的标准来格式化,这个标准是在 数据报传播上的。应用程序也必须能解析从服务器返回发送的数据报。 使用数据报连接来发送短消息。为获得更多信息,参看 121 页的“发送和接受 SMS”。 使用使用使用 使用 UDP UDP UDP UDP 连接连接连接 连接 为使用UDP连接,你必须有一个你自己的基础设施来连接无线网络,包括一个G P R S(General Packet Radio Service,通用分组无线业务)网络的 APN(Access Point Name)。 注:数据报连接没有使用 BlackBerry 的基础设施,因此连接没有加密。模拟器的 APN 是net.rim.gprs. javax.microedition.io.DatagramConnection 接口,扩展了 Connection 类,它定义了发送和接受 数据报的连接。Datagram 接口定义了在数据报连接上发送和接受的数据包。 注:使用UDP 连接需要你和服务商紧密联系。联系你的服务商确认 UDP 连接是否支 持。 如果你的服务商不支持多个 PDP 上下文,那么你可能没有建立一个 UDP 连接。一个 PDP 上下文为发送消息的 BlackBerry.net.APN 保留。尽管如此,你可以为 UDP 使用 blackberry.net 当作 APN。联系你的服务商以获得更多信息。 数据报连接 使用UDPUDPUDP UDP 连接 使用Mobitex Mobitex Mobitex Mobitex 网络 发送和接收短消息(SMSSMSSMS SMS ) By Taiguo Zhang (confach@gmail.com) 126 获得一个数据报连接 使用下面的格式调用 Connector.open().指定udp 为你的协议。将返回的对象转化为一个 DatagramConnection。 (DatagramConnection)Connector. open("udp://host:dest_port[;src_port]/apn"); 注:可以在相同的端口上收发 UDP 数据报。 为了在 UDP 连接上发送数据,在连接字符串里指定目标端口。为了在 UDP 连接上接收数据, 在连接字符串里指定源端口。为了接收指定主机的所有端口上的数据报,省略目标端口。 注:为了在一个非 GPRS 的网络里打开一个连接,不要指定 APN。在源端口后包含斜 线”/”.例如 CDMA 网络连接的地址应该是 udp://121.0.0.0:6343/. 创建一个数据报 调用 DatagramConnection.newDatagram(). 将数据加入到数据报中 调用 Datagram.setData().数据的格式由接收它的服务决定。 在UDPUDPUDP UDP 连接上发送数据 在数据报连接山调用 send()。 注:如果应用程序试图在一个 UDP 连接上发送一个数据报,并且接收者没有监听指定的 源端口,一个 IOException 会抛出. 接收UDPUDPUDP UDP 连接上的数据 调用数据报连接上的 receive(). 参数 描述 host 指定点阵 ASCII 十进制格式的主机地址 dest_port 指定了主机地址的目标端口(接受信息时是 可选的)。 src_port 指定本地源端口(可选)。 apn 指定字符串形式的网络 APN。 Datagram outDatagram = conn.newDatagram(buf, buf.length); bytebytebyte byte [] buf = newnewnew new bytebytebyte byte [256]; outDatagram.setData(buf, buf.length); conn.send(outDatagram); bytebytebyte byte [] buf = newnewnew new bytebytebyte byte [256]; Datagram inDatagram = conn.newDatagram(buf, buf.length); conn.receive(inDatagram); By Taiguo Zhang (confach@gmail.com) 127 注:receive()方法会阻塞其他操作,直至它接收完一个数据包.如果你知道正在发送的 数据格式,转化他们为合适的格式. 从数据报提取数据 在数据报连接上调用 getData()方法。如果你知道正在接收的数据类型,将数据转化为合适的 格式。 关闭UDPUDPUDP UDP 连接 和MIDP 框架中所有连接一样,调用输入和输出流以及数据报上的 close()方法, 使用使用使用 使用 Mobitex Mobitex Mobitex Mobitex 网络网络网络 网络 DatagramConnectionBase 类提供了方法来处理 Mobitex 网络上的 BlackBerry 数据报连接以及 传输操作。 打开一个 Mobitex Mobitex Mobitex Mobitex 数据报连接 调用Connector.open(), 然后将返回的对象转化为一个 DatagramConnectionBase 。 DatagramConnectionBase 类实现了 DatagramConnection,并且提供了额外的方法,对注册一 个数据报状态监听者来说,这些方法是必要的。 为提供一个参数给 Connector.open(),连接字符串使用下面的格式: mobitex:: 注:如果你打开一个服务器连接(一个监听者),MAN 留为空白。 String received = newnewnew new String(inDatagram.getData()); 参数 描述 接 受 下 列 值 :“TEXT”,”DATA”,”STATUS”,或” HPDATAHPID(在这些值中,JPID 的格式是 ASCII 十进制) Mobitex 访问号码(Mobitex Access Number), 接受 ASCII 十进制格式。 // The datagram connection is DATA and the MAN is left blank for an incoming // connection. DatagramConnection dc = (DatagramConnection)Connector.open("mobitex:DATA:"); DatagramConnectionBase dcb = (DatagramConnectionBase)dc; By Taiguo Zhang (confach@gmail.com) 128 监听数据报状态事件 如果你想注册一个数据报状态事件监听者,使用一个 DatagramBase,而不是 Datagram 来抓 住(hold)数据。DatagramBase 实现了 Datagram 接口,并且提供额外的方法,这些方法对 注册数据报状态事件监听者是有用的。 注册一个数据报状态监听者 你的 DatagramStatusListener 接口的实现监听事件,例如一个数据报的接收。参看 API 参考 的DatagramStatusListener 以获得完整的数据报状态事件列表。 为了分配一个数据报 ID ,并将之显式指派给 DatagramStatusListener ,调用 DatagramConnectionBase.allocateDatagramId(). 以此种方式预先分配数据报 ID,可以确保你的监听者代码知道与此 ID 相关联的数据报。 获得数据报信息 MobitexAddress 类封装了 Mobitex 地址信息,例如 MAN,消息的类型以及消息的状态。 MobitexPacketHeader 类提供对无线头字段(radio header field)的底层访问。为了对所有地 址操作使用 MobitexPacketHeader 并忽略其他的 MobitexAddress 字段,调用 MobitexAddress.setPacketHeader(). 获取无线和网络信息 MobitexInfo 类提供对象存储普通的无线(radio)状态信息。Mobitex.MobitexCellInfo 类提供 对象存储 Mobitex 蜂窝信息。 代码实例 MobitexDemo.java 代码实例描述了 Mobitex 无线称 API的使用。 例:MobitexDemo.java /* * MobitexDemo.java * // dc is a DatagramConnection. Datagram d = dc.newDatagram(dc.getMaximumLength()); d.setAddress(address); d.setData(raw, 0, raw.length); DatagramBase db = (DatagramBase)d; // An error if this fails. int id = dcb.allocateDatagramId(d); By Taiguo Zhang (confach@gmail.com) 129 * Copyright (C) 2003-2005 Research In Motion Limited */ packagepackagepackage package com.rim.docs.samples.mobitexdemo; importimportimport import javax.microedition.io.*; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.i18n.*; importimportimport import java.util.*; importimportimport import net.rim.device.api.io.*; importimportimport import net.rim.device.api.system.*; importimportimport import java.io.*; importimportimport import com.rim.docs.samples.baseapp.*; /** *A simple mobitex layer sample. */ publicpublicpublic public finalfinalfinal final classclassclass class MobitexDemo extendsextendsextends extends BaseApp implementsimplementsimplements implements MobitexDemoResource { privateprivateprivate private MainScreen _mainScreen; privateprivateprivate private EditField _pin; privateprivateprivate private EditField _data; privateprivateprivate private RichTextField _messages; privateprivateprivate private SenderThread _sendThread; privateprivateprivate private ReceiverThread _receiverThread; // statics ------------------------------------------------------ ------------ privateprivateprivate private staticstaticstatic static ResourceBundle _resources = ResourceBundle.getBundle(MobitexDemoResource.BUNDLE_ID, MobitexDemoResource.BUNDLE_NAME); staticstaticstatic static publicpublicpublic public voidvoidvoid void main(String[] args) { newnewnew new MobitexDemo().enterEventDispatcher(); } // menu items --------------------------------------------------- ------------- // Cache the send menu item for reuse. privateprivateprivate private MenuItem _sendMenuItem = newnewnew new MenuItem(_resources, MOBITEXDEMO_MENUITEM_SEND, 100, 10) { By Taiguo Zhang (confach@gmail.com) 130 publicpublicpublic public voidvoidvoid void run() { // Don’t execute on a blank address. String pin = _pin.getText(); String data = _data.getText(); ififif if ( pin.length() > 0 ) { send(pin, data); } } }; // Cache the clear messages menu item for reuse. privateprivateprivate private MenuItem _clearMessages = newnewnew new MenuItem(_resources, MOBITEXDEMO_MENUITEM_CLEARMESSAGES, 105, 10) { publicpublicpublic public voidvoidvoid void run() { _messages.setText(""); } }; publicpublicpublic public MobitexDemo() { _mainScreen = newnewnew new MainScreen(); _mainScreen.setTitle( newnewnew new LabelField(_resources.getString(MOBITEXDEMO_TITLE), LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH)); _pin = newnewnew new EditField(_resources.getString(MOBITEXDEMO_LABEL_PIN), nullnullnull null , Integer.MAX_VALUE, EditField.FILTER_PIN_ADDRESS); _mainScreen.add(_pin); _data = newnewnew new EditField(_resources.getString(MOBITEXDEMO_LABEL_DATA), nullnullnull null ); _mainScreen.add(_data); _mainScreen.add(newnewnew new SeparatorField()); _messages = newnewnew new RichTextField(_resources.getString(MOBITEXDEMO_CONTENT_DEFAULT)); _mainScreen.add(_messages); _mainScreen.addKeyListener(thisthisthis this );//implemented by the super _mainScreen.addTrackwheelListener(thisthisthis this );//implemented by the super //start the helper threads _sendThread = newnewnew new SenderThread(); By Taiguo Zhang (confach@gmail.com) 131 _sendThread.start(); _receiverThread = newnewnew new ReceiverThread(); _receiverThread.start(); //push the main screen - a method from the UiApplication class pushScreen(_mainScreen); } // methods ------------------------------------------------------ ------------ /*public boolean keyChar(char key, int status, int time) { if ( UiApplication.getUiApplication().getActiveScreen().getLeafFieldWith Focus() == pin && key == Characters.ENTER ) { _sendMenuItem.run(); return true; //I’ve absorbed this event, so return true. } else { return super.keyChar(key, status, time); } }*/ protectedprotectedprotected protected voidvoidvoid void makeMenu(Menu menu, intintint int instance) { menu.add(_sendMenuItem); menu.add(_clearMessages); menu.addSeparator(); supersupersuper super .makeMenu(menu, instance); } privateprivateprivate private voidvoidvoid void send(String pin, String data) { _sendThread.send(pin, data); } privateprivateprivate private voidvoidvoid void message(String msg) { System.out.println(msg); _messages.setText(_messages.getText() + msg + "\n"); By Taiguo Zhang (confach@gmail.com) 132 } // inner classes ------------------------------------------------ ------------- privateprivateprivate private classclassclass class ReceiverThread extendsextendsextends extends Thread { privateprivateprivate private DatagramConnection _dc; // Shut down the thread. publicpublicpublic public voidvoidvoid void stop() { trytrytry try { _dc.close(); } catchcatchcatch catch (IOException e) { MobitexDemo.thisthisthis this .message(e.toString()); } } publicpublicpublic public voidvoidvoid void run() { trytrytry try { // Incoming data connection - leave the MAN blank. _dc = (DatagramConnection)Connector.open("mobitex:DATA:"); forforfor for (;;) { Datagram d = _dc.newDatagram(_dc.getMaximumLength()); _dc.receive(d); DatagramBase db = (DatagramBase)d; MobitexAddress ma = (MobitexAddress)db.getAddressBase(); MobitexPacketHeader mph = ma.getPacketHeader(); StringBuffer sb = newnewnew new StringBuffer(); sb.append("Recieved packet"); sb.append("\nFROM:"); sb.append(mph.getSourceAddress()); sb.append("\nTO:"); sb.append(mph.getDestinationAddress()); sb.append("\nFLAGS:"); sb.append(Integer.toHexString(mph.getPacketFlags())); By Taiguo Zhang (confach@gmail.com) 133 sb.append("\nDATA:"); sb.append(newnewnew new String(db.getData())); MobitexDemo.thisthisthis this .message(sb.toString()); } } catchcatchcatch catch (IOException e) { MobitexDemo.thisthisthis this .message(e.toString()); } } } /** * The ConnectionThread class manages the datagram connection */ privateprivateprivate private classclassclass class SenderThread extendsextendsextends extends Thread implementsimplementsimplements implements DatagramStatusListener { privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int TIMEOUT = 500; //ms privateprivateprivate private Vector _sendQueue = newnewnew new Vector(5); privateprivateprivate private volatilevolatilevolatile volatile booleanbooleanboolean boolean _start = falsefalsefalse false ; privateprivateprivate private volatilevolatilevolatile volatile booleanbooleanboolean boolean _stop = falsefalsefalse false ; // Queue something for sending publicpublicpublic public voidvoidvoid void send(String pin, String data) { synchronizedsynchronizedsynchronized synchronized (_sendQueue){ _sendQueue.addElement(newnewnew new String[] { pin, data }); _start = truetruetrue true ; } } // Shut down the thread. publicpublicpublic public voidvoidvoid void stop() { _stop = truetruetrue true ; } publicpublicpublic public voidvoidvoid void run() { forforfor for (;;){ String pin = nullnullnull null ; String data = nullnullnull null ; // Thread control. synchronizedsynchronizedsynchronized synchronized (_sendQueue){ whilewhilewhile while (!_start &&!_stop) By Taiguo Zhang (confach@gmail.com) 134 { // Sleep for a bit so we don’t spin. trytrytry try { _sendQueue.wait(TIMEOUT); } catchcatchcatch catch (InterruptedException e) { System.err.println(e.toString()); } } ififif if (_start){ String[] a = (String[])_sendQueue.firstElement(); ififif if ( a != nullnullnull null ){ pin = a[0]; data = a[1]; } _start = falsefalsefalse false ; } elseelseelse else ififif if (_stop ){// Exit condition returnreturnreturn return ; } } // Open the connection and extract the data. DatagramConnection dc = nullnullnull null ; trytrytry try { String address = "DATA:" + pin; dc = (DatagramConnection)Connector.open("mobitex:" + address); //an error if this fails DatagramConnectionBase dcb = (DatagramConnectionBase)dc; Datagram d = dc.newDatagram(dc.getMaximumLength()); bytebytebyte byte [] raw = data.getBytes(); d.setAddress(address); d.setData(raw, 0, raw.length); // An error if this fails. DatagramBase db = (DatagramBase)d; // Allocate a datagram ID- if you want to know about status. // For this particular datagram, then we can By Taiguo Zhang (confach@gmail.com) 135 allocate the id // here and log it for later follow up dcb.allocateDatagramId(d); // Set up a status listener. db.setDatagramStatusListener(thisthisthis this ); dcb.send(d); dc.close(); } catchcatchcatch catch (IOException e) { MobitexDemo.thisthisthis this .message(e.toString()); } // We’re done one connection so reset the start state. _start = falsefalsefalse false ; } } publicpublicpublic public voidvoidvoid void updateDatagramStatus(intintint int dgId, intintint int code, Object context) { String msg = "Datagram: " + Integer.toHexString(dgId) + "\nStatus: " + DatagramStatusListenerUtil.getStatusMessage(code); MobitexDemo.thisthisthis this .message(msg); } } protectedprotectedprotected protected voidvoidvoid void onExit() { _sendThread.stop(); _receiverThread.stop(); } } 发送和接收短消息发送和接收短消息发送和接收短消息 发送和接收短消息 使用 UDP在数据报包上发送和接收短消息(SMS)。包含了 BlackBerry 消息头信息的短消 息数据报包,其大小是固定的 160 个字节。 注:短消息不是所有的网络都支持。和你的服务商确认是否相关的网络全部和部分支 持SMS。大多数情况下,GPRS 和支持 CDMA 的BlackBerry 设备支持 SMS。如果服务商提供 SMS 服务,系统管理员也可以使用 IT策略来控制公司用户对 SMS 的使用。管理员可以将 ENABLE_SMS 项设置为 TRUE 或FALSE。缺省值是 TRUE(SMS 可 用 )。 By Taiguo Zhang (confach@gmail.com) 136 发送SMSSMSSMS SMS 打开一个数据报连接来发送 SMSSMSSMS SMS 调用 Connector.open().使用下面格式提供一个连接字符串: DatagramConnection _dc = Connector.open("sms://"); 在这里,是接收者的电话号码-MSISDN(MobileStation ISDN Number)。 你也可以忽略 peer_address,代替它的是调用 Datagram.setAddress()来设置消息的目的 地地址。 创建一个 SMSSMSSMS SMS 调用 DatagramConnection.newDatagram(). 设置SMS SMS SMS SMS 内容 调用 setData( )。 发送一个 SMS SMS SMS SMS 消息 注:在一个与主应用程序线程独立的线程上打开网络连接,这样 UI就不会停止。 如果你没有在连接字符串中指定 peer_address,那么调用 Datagram.setAddress()设置SMS 地址。为了发送 SMS,调用 DatagramConnection.send()。 代码实例 SendSms.java 代码实例描述了如何在独立的线程上发送一条 SMS 消息。 在实例的工作空间中,SendSms.java 实例需要一个服务器端的应用程序来与 BlackBerry 设备 模拟器交互,来模拟发送和接收 SMS 消息。你不能从 BlackBerry 设备模拟器发送一个实际 的SMS。 样例(SMSServer.java)的服务器端程序包含在 BlackBerry JDE 下。为了运行服务器端程序, 运行 run.bat,它在你的 BlackBerry JDE 实例的子目录下。例如: \samples\com\rim\samples\server\smsdemo\. 例:SendSms.java /** * SendSms.java * Copyright (C) 2002-2005 Research In Motion Limited. All rights reserved. */ Datagram smsMessage = conn.newDatagram(buf, buf.length); privateprivateprivate private String _msg = "This is a test message"; bytebytebyte byte [] data = _msg.getBytes(); smsMessage.setData(data, 0, data.length); smsMessage.setAddress("sms://+15555551234"); _dc.send(datagram); By Taiguo Zhang (confach@gmail.com) 137 packagepackagepackage package com.rim.samples.docs.smsdemo; importimportimport import net.rim.device.api.io.*; importimportimport import net.rim.device.api.system.*; importimportimport import javax.microedition.io.*; importimportimport import java.util.*; importimportimport import java.io.*; publicpublicpublic public classclassclass class SendSms extendsextendsextends extends Application { privateprivateprivate private staticstaticstatic static finalfinalfinal final intintint int MAX_PHONE_NUMBER_LENGTH = 32; // Members. privateprivateprivate private String addr = "15195551234"; privateprivateprivate private String msg = "This is a test message."; privateprivateprivate private DatagramConnection _dc = nullnullnull null ; privateprivateprivate private staticstaticstatic static String _openString = "sms://"; publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { newnewnew new SendSms().enterEventDispatcher(); } publicpublicpublic public SendSms() { trytrytry try { _dc = (DatagramConnection)Connector.open(_openString); bytebytebyte byte [] data = msg.getBytes(); Datagram d = _dc.newDatagram(_dc.getMaximumLength()); d.setAddress("//" + addr); _dc.send(d); } catchcatchcatch catch ( IOException e) { } System.exit(0); } } 接收SMS SMS SMS SMS 消息 创建一个独立的监听线程 在一个与主应用程序线程独立的线程上监听消息,以致 UI不会停止。 打开数据报连接 调用 Connector.open().使用下面的格式提供一个字符串连接: _dc = (DatagramConnection)Connector. By Taiguo Zhang (confach@gmail.com) 138 open("sms://"); 在这里:  是接收者的电话号码-MSISDN(MobileStation ISDN Number)  是应用程序接收 SMS 消息的端口号。 获取数据报 创建一个 Datagram 对象存储数据报。为获取 SMS 消息数据报,调用数据报连接上的 receive()。这个操作会阻塞直至数据接收完毕。 从数据报提取数据 为了从 SMS 消息提取地址,调用 Datagram.getAddress().为了从 SMS 消息中提取数据,调用 Datagram.getData(). 代码实例 ReceiveSms.java 描述了如何在一个独立的线程上接收一个 SMS 消息。 例:ReceiveSms.java /** * ReceiveSms.java * Copyright (C) 2002-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.smsdemo; importimportimport import net.rim.device.api.io.*; importimportimport import net.rim.device.api.system.*; importimportimport import javax.microedition.io.*; importimportimport import java.util.*; importimportimport import java.io.*; publicpublicpublic public classclassclass class ReceiveSms extendsextendsextends extends Application { privateprivateprivate private ListeningThread _listener; // Additional code required for complete sample. publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { newnewnew new ReceiveSms().enterEventDispatcher(); } ReceiveSms() { _listener = newnewnew new ListeningThread(); _listener.start(); Datagram d = _dc.newDatagram(160); //SMS messages have a fixed size of 160 bytes _dc.receive(d); String address = d.getAddress(); String msg = newnewnew new String(d.getData()); By Taiguo Zhang (confach@gmail.com) 139 } privateprivateprivate private staticstaticstatic static classclassclass class ListeningThread extendsextendsextends extends Thread { privateprivateprivate private booleanbooleanboolean boolean _stop = falsefalsefalse false ; privateprivateprivate private DatagramConnection _dc; publicpublicpublic public synchronizedsynchronizedsynchronized synchronized voidvoidvoid void stop() { _stop = truetruetrue true ; trytrytry try {_ dc.close(); // Close the connection so the thread returns. } catchcatchcatch catch (IOException e) { System.err.println(e.toString()); } } publicpublicpublic public voidvoidvoid void run() { trytrytry try { _dc = (DatagramConnection)Connector.open("sms://"); forforfor for (;;){ ififif if (_stop ){ returnreturnreturn return ; } Datagram d = _dc.newDatagram(_dc.getMaximumLength()); _dc.receive(d); String address = newnewnew new String(d.getAddress()); String msg = newnewnew new String(d.getData()); System.out.println("Message received:" + msg); System.out.println("From:" + address); System.exit(0); } } catchcatchcatch catch (IOException e) { System.err.println(e.toString()); } } } } By Taiguo Zhang (confach@gmail.com) 140 888 8 第888 8 章 本地化应用程序 资源文件资源文件资源文件 资源文件 将应用程序设计为在代码不改变的情况下它们可以本地化(适合指定的语言以及区域)。为 了代替在你的源代码中包含原文元素,将文本字符串存储到一个独立的资源文件中。在你的 源代码例中,使用唯一的标志符映射到合适的资源。 在独立的资源文件中存储文本字符串有 2个好处:  文本翻译有更高的效率,因为一个给定位置(locale)的所有文本字符串都存储在一个 单独的文件中,这个文件在你的源代码之外。  基于用户的位置,应用程序可以动态的获得合适的文本显示给用户。 BlackBerry JDE 包含了一个内置的资源机制来创建字符串资源。本地化(Localization)API 包含在 net.rim.device.api.i18n 包里。 注:MIDP 应用程序不支持本地化。 一个给定的位置的资源存贮在一个 ResourceBundle 对象中。一个 ResourceBundleFamily 对象包括了一个 ResourceBundle 的集合,它将应用程序的资源进行分组。在不需要新的资 源包下,应用程序可以切换语言,这依赖于用户的位置。 BlackBerry JDE 将每个资源包编译为一个独立已编译的.cod 文件。你可以为应用程序将其 他.cod 文件和适合的.cod 文件一起加载到 BlackBerry 设备上。 资源文件 为应用程序加入本地化支持 从资源文件获取字符串 为应用程序套件管理资源文件 本地化需要的文件 描述 例子 资源头文件 这个文件为每个本地字符串 定义具有描述性的键。 当BlackBerry 编译一个项目 时,它创建一个以 Resource 加到.rrh 文件名末作为文件 名的资源接口。例如,如果你 创建 AppName.rhh,接口就是 AppNameResouce。 AppName.rrh 资源内容文件(根位置) 文件为根(全局)位置将资源 AppName.rrc By Taiguo Zhang (confach@gmail.com) 141 资源继承 在一个基于继承的层次里组织资源。若一个字符串在一个位置里没有定义,下一个最靠近的 字符串会被使用。 为应用程序加入本地化支持为应用程序加入本地化支持为应用程序加入本地化支持 为应用程序加入本地化支持 增加资源头文件 1. 在BlackBerry IDE,打开 File File File File 菜单,点击 NewNewNew New 。 2. 在SourceSourceSource Source filefilefile file name name name name 里,键入一个文件名。 3. 点击 BrowseBrowseBrowse Browse 。 4. 选择一个包含文件的文件夹。 5. 点击 OKOKOK OK 。 6. 在域里,键入包的名字,例如:com.rim.samples.docs.countryinfocom.rim.samples.docs.countryinfocom.rim.samples.docs.countryinfo com.rim.samples.docs.countryinfo . 7. 点击 OKOKOK OK 。 8. 点击 YesYesYes Yes 。 9. 除了包文件的描述,保留出现在文本编辑器的文件。 10. 在右边的区域里,右击加入文件到项目中,然后点击 InsertInsertInsert Insert intointointo into projectprojectproject project 。 加入资源内容文件 在相同的文件夹下创建 3个资源内容文件,在这里,ContryInfo.java 有:CountryInfo.rrc(根 位 置 ),CountryInfo_en.rrc(English),以及 CountryInfo_fr.rrc(French). 1. 在File File File File 菜单,点击 NewNewNew New 。 键映射到字符串值。它和资源 头文件有相同的名字。 资源内容文件(指定的位置) 文件为指定的位置(语言和国 家)将资源键映射到字符串 值。文件和资源头文件有相同 的名字,跟在后面的是下划线 (_)和语言代码,然后是可 选的,一个下划线和国家代 码。 2个字母的语言代码和国家 代码分别在ISO-639 以及ISO -3166 有描述。 AppName_en.rrc AppName_en_GB_rrc AppName_fr.rrc 初始化文件 文件初始化资源包机制。仅当 资源作为一个独立的工程编 译时本文件为才需要 Init.java By Taiguo Zhang (confach@gmail.com) 142 2. 键入文件名以及位置。 3. 点击 OKOKOK OK 。 4. 点击 YesYesYes Yes 。 5. 保留文件为空。 6. 为加入.rrc 文件到你的应用程序项目中,在右边右击文件。 7. 点击 InsertInsertInsert Insert intointointo into projectprojectproject project 。 加入资源 1. 在BlackBerry IDE,双击一个资源头文件。 2. 在Root Root Root Root 标签,键入资源键和应用程序的每个字符串或字符串数组的值。 每行定义了单个资源。Keys Keys Keys Keys 列为资源显示了一个具有描述性的名字。它是在你代码中 使用它获得本地化文本的名字。Values Values Values Values 列为某个指定位置的资源显示文本。 注:为单个资源键增加一组值,在资源编辑器中,右击一个资源,电解Convert to Multiple Values。加入一个或多个值到数组。 3. 为指定一个其他位置里的不同文本字符串,选择位置的标签,例如法语是 frfrfr fr . 4. 在资源的 Value Value Value Value 单元格里,为 locale 输入文本字符串。如果你没有为一个特定 locale 的 资源定义一个值,root 的值将会使用。 注:直接输入 unicode 字符到 Value 单元格中。访问 http://ww.unicode.org 获得更多信息。 5. 设置应用程序标题。 你可以提供一个本地化的应用程序标题显示在主屏幕(Home Screen)上。如果你没有为应 用程序标题提供一个资源,将会使用项目属性窗口上的 Application Application Application Application 标签里的 Title Title Title Title 域的输入 值。 1. 在资源编辑器中,为应用程序标题增加一个资源,例如 APPLICATION_TITLE. 2. 在你支持的每个 locale,为这个资源输入一个值。 注:为了为一个应用程序创建一个快捷键,在你用来作为快捷键的字符后加入 unicode 下划线字符(\u0332)。一个快捷键就是一个在主屏幕上你可以按此键启动应用程序 的键。 3. 在BlackBerry IDE 中,右击应用程序项目,然后点击 PropertiesPropertiesProperties Properties 。 4. 点击 Resource Resource Resource Resource 标签。 5. 选择 TitleTitleTitle Title ResourceResourceResource Resource Available Available Available Available 选项。 6. 在ResourceResourceResource Resource Bundle Bundle Bundle Bundle 下拉列表中,为此应用程序选择资源头文件名。 7. 在TitleTitleTitle Title Id Id Id Id 下拉列表中,为应用程序的标题选择资源,例如 APPLICATION_TITLE. 8. 在DescriptionDescriptionDescription Description ID ID ID ID 下拉列表中,选择一个描述性的 ID。 代码实例 为了指定的 locale,而不是在代码里直接提供文本字符串,CountryInfo.java 实例描述了如何 By Taiguo Zhang (confach@gmail.com) 143 在单独的资源文件中存储文本字符串。在你的源代码中,从资源里获取字符串为用户 locale 显示合适的文本。 例,CountryInfo.java /** * CountryInfo.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.countryinfo; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.i18n.*; importimportimport import com.rim.samples.docs.resource.*; /* This sample demonstrates how to store text strings in separate resource files for specific locales rather than providing text strings directly in the code. In your source code, you retrieve the string from the resource to display the appropriate text for the user locale. */ publicpublicpublic public classclassclass class CountryInfo extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { CountryInfo theApp = newnewnew new CountryInfo(); theApp.enterEventDispatcher(); } publicpublicpublic public CountryInfo() { pushScreen(newnewnew new HelloWorldScreen()); } } finalfinalfinal final classclassclass class HelloWorldScreen extendsextendsextends extends MainScreen implementsimplementsimplements implements CountryInfoResource { privateprivateprivate private InfoScreen _infoScreen; privateprivateprivate private ObjectChoiceField choiceField; privateprivateprivate private intintint int select; privateprivateprivate private staticstaticstatic static ResourceBundle _resources = By Taiguo Zhang (confach@gmail.com) 144 ResourceBundle.getBundle( CountryInfoResource.BUNDLE_ID, CountryInfoResource.BUNDLE_NAME); publicpublicpublic public HelloWorldScreen() { supersupersuper super (); LabelField title = newnewnew new LabelField(_resources.getString(APPLICATION_TITLE), LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(newnewnew new RichTextField(_resources.getString(FIELD_TITLE))); String choices[] = _resources.getStringArray(FIELD_COUNTRIES); choiceField = newnewnew new ObjectChoiceField(_resources.getString(FIELD_CHOICE), choices); add(choiceField); } publicpublicpublic public booleanbooleanboolean boolean onClose() { Dialog.alert(_resources.getString(CLOSE)); System.exit(0); returnreturnreturn return truetruetrue true ; } privateprivateprivate private MenuItem _viewItem = newnewnew new MenuItem(_resources, MENUITEM_VIEW, 110, 10) { publicpublicpublic public voidvoidvoid void run() { select = choiceField.getSelectedIndex(); _infoScreen = newnewnew new InfoScreen(); UiApplication.getUiApplication().pushScreen(_infoScreen); } }; privateprivateprivate private MenuItem _closeItem = newnewnew new MenuItem(_resources, MENUITEM_CLOSE,200000, 10) { publicpublicpublic public voidvoidvoid void run() { onClose(); } }; protectedprotectedprotected protected voidvoidvoid void makeMenu( Menu menu, intintint int instance ){ menu.add(_viewItem); menu.add(_closeItem); } By Taiguo Zhang (confach@gmail.com) 145 privateprivateprivate private classclassclass class InfoScreen extendsextendsextends extends MainScreen { publicpublicpublic public InfoScreen() { supersupersuper super (); LabelField lf = newnewnew new LabelField(); BasicEditField popField = newnewnew new BasicEditField( _resources.getString(FIELD_POP), nullnullnull null , 20, Field.READONLY); BasicEditField langField = newnewnew new BasicEditField( _resources.getString(FIELD_LANG), nullnullnull null , 20, Field.READONLY); BasicEditField citiesField = newnewnew new BasicEditField( _resources.getString(FIELD_CITIES), nullnullnull null , 50, Field.READONLY); add(lf); add(newnewnew new SeparatorField()); add(popField); add(langField); add(citiesField); ififif if (select == 0) { lf.setText(_resources.getString(FIELD_US)); popField.setText(_resources.getString(FIELD_US_POP)); langField.setText(_resources.getString(FIELD_US_LANG)); citiesField.setText(_resources.getString(FIELD_US_CITIES)); } elseelseelse else ififif if (select == 1) { lf.setText(_resources.getString(FIELD_CHINA)); popField.setText(_resources.getString(FIELD_CHINA_POP)); langField.setText(_resources.getString(FIELD_CHINA_LANG)); citiesField.setText(_resources.getString(FIELD_CHINA_CITIES)); } elseelseelse else ififif if (select == 2) { lf.setText(_resources.getString(FIELD_GERMANY)); popField.setText(_resources.getString(FIELD_GERMANY_POP)); langField.setText(_resources.getString(FIELD_GERMANY_LANG)); citiesField.setText( _resources.getString(FIELD_GERMANY_CITIES)); } By Taiguo Zhang (confach@gmail.com) 146 } } } 从资源文件中获取字符串从资源文件中获取字符串从资源文件中获取字符串 从资源文件中获取字符串 实现资源接口 为支持国际化,实现合适的资源接口。BlackBerry IDE 自动编译这个资源头文件(.rhh)的接口 。 接口的名字是加入 Resource 的.rrh 文件名称 获取资源包(bundlebundlebundle bundle ) 为此应用程序声明一个类变量来保持资源包。一个 ResourceBundle 对象为应用程序包含了 所有本地资源,例如字符串。一个应用程序可以在运行时根据它的 locale 选择合适的包。 为获取合适的包族(bundle family),调用 getBundle()。当它创建资源接口作为项目的一部 分时,BlackBerry IDE 创建 BUNDLE_ID 以及 BUNDLE_NAME 常数。 使用资源创建菜单项 为了使用资源创建 MenuItem 对象,使用 MenuItem 的构造子,这个构造子为了菜单项的名 字,接受一个资源包以及一个资源来代替字符串。不要实现 toString(),因为菜单项的文本 由资源提供。 publicpublicpublic public classclassclass class HelloWorldScreen extendsextendsextends extends MainScreen implementsimplementsimplements implements CountryInfoResource { ... } private static ResourceBundle _resources = ResourceBundle.getBundle(BUNDLE_ID, BUNDLE_NAME); privateprivateprivate private MenuItem _viewItem = newnewnew new MenuItem(_resources, MENUITEM_VIEW, 110, 10) { publicpublicpublic public voidvoidvoid void run() { select = choiceField.getSelectedIndex(); _infoScreen = newnewnew new InfoScreen(); UiApplication.getUiApplication().pushScreen(_infoScreen); } By Taiguo Zhang (confach@gmail.com) 147 用合适的资源替代文本字符串 为每个出现在屏幕上的 field,用合适的资源替代文本字符串。调用 getString(),或者 getStringArray()为适合的语言来获取字符串。 代码实例 下面的例子修改 CountryInfo.java 实例,以从资源中获取字符串。 例:CountryInfo.java(支持本地化) /** * CountryInfo.java * Copyright (C) 2001-2005 Research In Motion Limited. All rights reserved. */ packagepackagepackage package com.rim.samples.docs.localization; importimportimport import net.rim.device.api.ui.*; importimportimport import net.rim.device.api.ui.component.*; importimportimport import net.rim.device.api.ui.container.*; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.i18n.*; importimportimport import com.rim.samples.docs.resource.*; publicpublicpublic public classclassclass class CountryInfo extendsextendsextends extends UiApplication { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { CountryInfo theApp = newnewnew new CountryInfo(); theApp.enterEventDispatcher(); } publicpublicpublic public CountryInfo() { pushScreen(newnewnew new HelloWorldScreen()); } } } LabelField title = newnewnew new LabelField(_resources.getString(APPLICATION_TITLE), LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); add(newnewnew new RichTextField(_resources.getString(FIELD_TITLE))); String choices[] = _resources.getStringArray(FIELD_COUNTRIES); choiceField = newnewnew new ObjectChoiceField(_resources.getString(FIELD_CHOICE), choices); By Taiguo Zhang (confach@gmail.com) 148 finalfinalfinal final classclassclass class HelloWorldScreen extendsextendsextends extends MainScreen implementsimplementsimplements implements CountryInfoResource { privateprivateprivate private InfoScreen _infoScreen; privateprivateprivate private ObjectChoiceField choiceField; privateprivateprivate private intintint int select; privateprivateprivate private staticstaticstatic static ResourceBundle _resources = ResourceBundle.getBundle( BUNDLE_ID,BUNDLE_NAME); publicpublicpublic public HelloWorldScreen() { supersupersuper super (); LabelField title = newnewnew new LabelField(_resources.getString(APPLICATION_TITLE), LabelField.ELLIPSIS | LabelField.USE_ALL_WIDTH); setTitle(title); add(newnewnew new RichTextField(_resources.getString(FIELD_TITLE))); String choices[] = _resources.getStringArray(FIELD_COUNTRIES); choiceField = newnewnew new ObjectChoiceField(_resources.getString(FIELD_CHOICE), choices); add(choiceField); } publicpublicpublic public booleanbooleanboolean boolean onClose() { Dialog.alert(_resources.getString(CLOSE)); System.exit(0); returnreturnreturn return truetruetrue true ; } privateprivateprivate private MenuItem _viewItem = newnewnew new MenuItem(_resources, MENUITEM_VIEW, 110, 10) { publicpublicpublic public voidvoidvoid void run() { select = choiceField.getSelectedIndex(); _infoScreen = newnewnew new InfoScreen(); UiApplication.getUiApplication().pushScreen(_infoScreen); } }; privateprivateprivate private MenuItem _closeItem = newnewnew new MenuItem(_resources, MENUITEM_CLOSE, 200000, 10) { publicpublicpublic public voidvoidvoid void run() { onClose(); } }; By Taiguo Zhang (confach@gmail.com) 149 protectedprotectedprotected protected voidvoidvoid void makeMenu( Menu menu, intintint int instance ){ menu.add(_viewItem); menu.add(_closeItem); } privateprivateprivate private classclassclass class InfoScreen extendsextendsextends extends MainScreen { publicpublicpublic public InfoScreen() { supersupersuper super (); LabelField lf = newnewnew new LabelField(); BasicEditField popField = newnewnew new BasicEditField( _resources.getString(FIELD_POP), nullnullnull null , 20, Field.READONLY); BasicEditField langField = newnewnew new BasicEditField( _resources.getString(FIELD_LANG), nullnullnull null , 20, Field.READONLY); BasicEditField citiesField = newnewnew new BasicEditField( _resources.getString(FIELD_CITIES), nullnullnull null , 50, Field.READONLY); add(lf); add(newnewnew new SeparatorField()); add(popField); add(langField); add(citiesField); ififif if (select == 0) { lf.setText(_resources.getString(FIELD_US)); popField.setText(_resources.getString(FIELD_US_POP)); langField.setText(_resources.getString(FIELD_US_LANG)); citiesField.setText(_resources.getString(FIELD_US_CITIES)); } elseelseelse else ififif if (select == 1) { lf.setText(_resources.getString(FIELD_CHINA)); popField.setText(_resources.getString(FIELD_CHINA_POP)); langField.setText(_resources.getString(FIELD_CHINA_LANG)); citiesField.setText(_resources.getString(FIELD_CHINA_CITIES)); } elseelseelse else ififif if (select == 2) { lf.setText(_resources.getString(FIELD_GERMANY)); popField.setText(_resources.getString(FIELD_GERMANY_POP)); By Taiguo Zhang (confach@gmail.com) 150 langField.setText(_resources.getString(FIELD_GERMANY_LANG)); citiesField.setText(_resources.getString(FIELD_GERMANY_CITIES)); } } } } 为应用程序组(为应用程序组(为应用程序组( 为应用程序组( suitesuitesuite suite )管理资源文件)管理资源文件)管理资源文件 )管理资源文件 你可以将资源加入到你的应用程序项目中,或者如果你正在创建一组应用程序,对每个 locale 将资源组织为单独的工程。 创建资源项目 为每个资源包(locale)创建一个项目,包含 root locale。 为每个 locale 给项目一个和 root locale 项目相同的名称,紧跟随一个双下划线(__),语言编 码,以及一个可选的在国家编码后的下划线。例如,,如果 root locale 项目命名为 com_company_app, 那么每个locale 的项目可以明明为 com_company_app__en, com_company_app__en_GB, com_company_app__fr. 指定输出文件名 1. 右击项目,点击 PropertiesPropertiesProperties Properties 。 2. 点击 Build Build Build Build 标签。 3. 在Output file name 域,为编译文件输入一个没有文件扩展名的名字。 注:所有资源 locale 项目的输出文件名,必须和 root locale 一样,跟随一个双 下划线,合适的语言以及国家编码。例如,如果 root locale 的输出名为 com_company_app,法语 locale 的输出文件名必须为 com_company_app__fr. 创建初始化文件 当在一个独立项目中编译资源时,创建一个初始化文件(例如,init.java).BlackBerry IDE 提供一个内置的初始化机制,一时你仅需要创建一个空 main()函数的空初始化类。 packagepackagepackage package com.rim.samples.device.resource; importimportimport import net.rim.device.api.i18n.*; publicpublicpublic public classclassclass class init { publicpublicpublic public staticstaticstatic static voidvoidvoid void main (String[] args) {} } By Taiguo Zhang (confach@gmail.com) 151 增加文件到合适的资源项目中 为每个应用程序创建一个头文件,为每个应用程序,每个支持的 locale 创建一个资源文件。 组织资源文件到项目中。 1. 为增加资源头文件到每个应用程序的项目中以及每个资源项目中。定义应用程序项目以 及它他的资源项目之间的依赖性是有必要的。 2. 在每资源项目中,右击每个.rrh 文件,然后点击 PropertiesPropertiesProperties Properties 。 3. 选择 DependencyDependencyDependency Dependency OnlyOnlyOnly Only ,DoDoDo Do notnotnot not buildbuildbuild build 。 4. 增加资源内容文件(.rrc)到合适 locale 的项目中。 注:如果你支持大量的 locale,为所有资源头文件(.rrh)创建一个单库,并且设置项 目类型为 Library.对于每个项目中资源 locale,定义项目之间的依赖。 By Taiguo Zhang (confach@gmail.com) 152 999 9 第999 9 章 IT IT IT IT 策略(PolicyPolicyPolicy Policy ) ITITIT IT 策略策略策略 策略 BlackBerry IT 策略 API(net.rim.device.api.itpolicy)允许应用程序访问 BlackBerry 设备上的 IT 策略数据库。应用程序可以获取定制的 IT 策略设置相应的改变他们的行为以及功能。 注:管理员使用应用程序控制控制 BlackBerry 设备上的第三方应用程序的出现以及功 能。为获取更多关于应用程序控制的信息,参看 BES手持设备管理指南 每个 IT 策略项由一个描述性的键以及一个值组成。这个值可以为字符串,整型或者 Boolean 型。例如,AllowPhone 策略可以设置一个 true 或false 的值。 在Microsoft Exchange 的BES 3.5 以及后续版本中,手持设备策略设置会无线同步与更新。 在早期的手持设备软件的版本中,当用户把 BlackBerry 设备和桌面同步时,手持设备策略 设置会得到更新。 参看 Microsoft Exchange BES手持设备管理指南获得更多信息。 获取客户策略获取客户策略获取客户策略 获取客户策略 注:IT策略 API 仅允许应用程序为客户(第三方)IT策略项获取值。应用程序不能获 取标准 TT策略项的值。 为根据名称获取一个第三方 IT 策略项的值,使用每个接收一个 String 参数的方法。 publicpublicpublic public staticstaticstatic static String getString( String name ); publicpublicpublic public staticstaticstatic static booleanbooleanboolean boolean getBoolean( String name, booleanbooleanboolean boolean defaultValue ); publicpublicpublic public staticstaticstatic static intintint int getInteger( String name, intintint int defaultValue ); 参数 defaultValue 指定了如果参数没有设置时的返回值。 监听策略的改变 当BlackBerry 设备上 IT 策略数据库得到更新时,一个全局事件会触发。 为使用 IT 策略,应用程序实现了 GlobalEventListener 接口。注册你的实现来接收全局事件。 IT IT IT IT 策略 获取客户策略 监听策略的改变 By Taiguo Zhang (confach@gmail.com) 153 当一个全局事件,例如一个 IT 策略的改变,发生时,GlobalEventListener.eventOccurred()将 会被调用。在 eventOccurred()的实现里,获取 IT 策略项的值来决定值是否已经改变。 代码实例代码实例代码实例 代码实例 ITPolicyDemo.java 实例实现了 IT 策略控制。 例:ITPolicyDemo.java /** * ITPolicyDemo.java * Copyright (C) 2002-2005 Research In Motion Limited. */ packagepackagepackage package com.rim.samples.docs.itpolicy; importimportimport import net.rim.device.api.system.*; importimportimport import net.rim.device.api.itpolicy.*; publicpublicpublic public classclassclass class ITPolicyDemo extendsextendsextends extends Application implementsimplementsimplements implements GlobalEventListener { publicpublicpublic public staticstaticstatic static voidvoidvoid void main(String[] args) { ITPolicyDemo app = newnewnew new ITPolicyDemo(); app.enterEventDispatcher(); } ITPolicyDemo() { thisthisthis this .addGlobalEventListener(thisthisthis this ); booleanbooleanboolean boolean appEnabled = ITPolicy.getBoolean("DemoAppEnabled", truetruetrue true ); System.out.println("App Enabled:" + appEnabled); System.exit(0); } publicpublicpublic public voidvoidvoid void eventOccurred(longlonglong long guid, intintint int data0, intintint int data1, Object obj0, Object obj1) { ififif if (guid == ITPolicy.GUID_IT_POLICY_CHANGED ) { String security = ITPolicy.getString("DemoSecurityLevel"); booleanbooleanboolean boolean appEnabled = ITPolicy.getBoolean("DemoAppEnabled", truetruetrue true ); intintint int retries = ITPolicy.getInteger("DemoAppRetries", 10); } } } By Taiguo Zhang (confach@gmail.com) 154 101010 10 第101010 10 章 创建Client/ServerClient/ServerClient/Server Client/Server Push Push Push Push 应用程序 Push Push Push Push 应用程序应用程序应用程序 应用程序 注:Push 应用程序需要 3.5 以及后续版本的 Microsoft Exchange BES,或2.0 以及后 续版本的 IBM Lotus Domino BES,它们需启用 BlackBerry MDS 服务。 Push 应用程序将新的 web 内容和 alert 发送到指定的用户。用户不必请求下载数据,因为当 信息可用时 push 应用程序递送这个信息。 有2种push 应用程序: 1. 浏览器 push push push push 应用程序:将 Web 内容发送到 BlackBerry 设备上。BlackBerry 浏览器配 置支持 MDS 服务 push 应用程序。WAP 浏览器配置支持 WAP push 应用程序。Internet 浏览器配置不支持 push 应用程序。参看 BlackBerry 浏览器开发指南获取更多关于编写 一个浏览器 push 应用程序的信息。 2. Client/ServerClient/ServerClient/Server Client/Server push push push push 应用程序:将数据 push 到一个 BlackBerry 设备上的客户 Java 应用 程序。Client/Server push 应用程序由一个 BlackBerry 设备上的客户 Client 应用程序和一 个push 内容给它的服务器端应用程序组成。和浏览器 push 应用程序相比,这种方法对 这种你可以发送出去的内容以及数据是如何处理并且显示在 BlackBerry 设备上提供了 更多的控制。 Client/ServerClient/ServerClient/Server Client/Server push push push push 请求请求请求 请求 应用程序可以使用下面的 2种方法将内容 push 到BlackBerry 设备: 1. Push Access Protocol(PAP,push 访问协议),它是 WAP 2.0 里的一部分。 2. RIM push。 注:MDS 服务仅支持 1000 个 push 请求,包括了 RIM 和PAP push 请求。如果它接收超 过1000 个请求,MDS 服务回应服务器一个错误。 这2种push 服务的实现都支持下面的任务: Push Push Push Push 应用程序 Client/ServerClient/ServerClient/Server Client/Server push push push push 请求 编写一个客户端 push push push push 应用程序 编写一个服务器端 push push push push 应用程序 PPP P ush ush ush ush 应用程序疑难解答 By Taiguo Zhang (confach@gmail.com) 155  发送一个服务器端 push 提交(submission)。  为push 提交指定一个信任的模式。  为push 提交指定一个传递前(deliver-before)的时间戳。  请求一个 push 提交的结果通知。 PAP 的实现还支持下面额外的任务:  为push 提交指定一个传递后(deliver-after)时间戳。  取消一个 push 请求提交。  查询一个 push 请求提交的状态。 存储pushpushpush push PAP push 存储在数据库中,但是 RIM push 则存储在 RAM中。如果服务器重启,没有递送 的RIM push 可能会丢失。 代码转换(TranscodingTranscodingTranscoding Transcoding ) 如果可用,BlackBerry MDS Data Optimization(BlackBerry MDS 数据优化)根据它的代码转 换器规则将一个代码转换应用到 push 请求。 Push 请求可以覆写这些规则并使用 transfer-encoding 头来请求一个指定的代码转换器转化。 例如,如果设置了 HTTP 头transfer-encoding:vnd.wap.wml,那么在它把数据 push 到 BlackBerry 设备之前,MDS 数据优化服务运行 wml 代码转换器。 参看 190 页的“代码转换器”获得更多信息。 信任模式 信任模式 描述 透明层信任模式 当push 到达 BlackBerry 设备时,BlackBerry MDS 连接服务启动一个 push 请求指定的 URL连接来通知传送的服务器。手持设备软 件3.6 或以后版本支持透明层确认。 应用程序级信任模式 当push 到达 BlackBerry 设备时,应用程序确 认内容。MDS 连接服务启动一个 push 请求 时指定的 URL连接来通知传送的服务器。如 果遇到错误,MDN 连接服务发送一个错误消 息到服务器。手持设备软件 4.0 或以后版本支 持应用程序级确认 RIM push 提供一个优先应用程序 (Application-preferred)的选项,在手持设备 软件 4.0 或以后版本中,它使用应用程序级确 认,否则就是透明层确认。 注:3.8 以及更早版本的 MDS 服务不可以为 BlackBerry 设备特征查询 BES。为了得到 Blac By Taiguo Zhang (confach@gmail.com) 156 发送一个 RIMRIMRIM RIM push push push push 请求 为了使用 RIM push 将数据 push 到BlackBerry 设备上,使用下面的格式发送一个 HTTPPOST 请求,在这里<<< < destinationdestinationdestination destination >>> > 是目的 PIN 或者 internet 消息地址, 是目的端口, 是 发送到 BlackBerry 的URI, 是字节流: /push?DESTINATION=&PORT=&REQUESTURI= 对于 RIM push 请求,下列的头是有效的: 发送一个 PAPPAPPAP PAP push push push push 请求 为了使用 PAP 将数据 push 到BlackBerry 设备,使用下面的格式发送一个 HTTP POST 请求: kBerry 设备的特征,MDS 连接服务必须在它 接收到一个 push 请求之前接收到一个 HTTP 请求。 为了提供必要的请求,使用 BlackBerry 浏览 器和一个MDS服务浏览器配置浏览一个web 页面。 HTTP 头 描述 X-RIM-Push-ID 这个头指定了一个唯一的消息 ID,它可以用 来取消或检查消息的状态。典型地,以一个 值组合指定 URL,例如 123@blackberry.com. 如果忽略这个头,MDS 服务生成一个唯一的 消息 ID. 注:Push 标识符不得以@ppg.rim.com 结束。 X-RIM-Push-NotifyURL 这个头指定一个 URL来发送一个结果通知。 这个结果通知包含了指定的消息 ID 的X- RIM-Push-ID 头,以及指定 HTTP 响应代码 的X-RIM-Push-Status 头。 X-RIM-Push-Reliability-Mode 这个头指定了内容透明级(TRANSPORT),应 用程序级(APPLICATION)或优先应用程序 (APPLICATION-PREFERRED)的传送信任 模式。 X-RIM-Push-Deliver-Before 这个头指定将内容传送给 BlackBerry 设备的 日期和时间。在这个时间之前没有传送的内 容不会被传送。 X-RIM-Push-Priority 这个头指定了频道 push 消息的优先级。允许 的字符串有 none(缺省),low,medium,以及 high.如果优先级为 low,medium 或high,用户 接收频道更新的通知。如果优先级为 high, 一个状态对话框会伴随着通知。 By Taiguo Zhang (confach@gmail.com) 157 /pap/pap/pap /pap 这个请求是一个 MIME 多部分(multipart)的消息,它由下面的项组成:  一个指定控制实体(entity)XML 文档。  push 内容。 例如,控制实体可能包含 BlackBerry 设备地址,消息 ID,以及传送时间戳信息。 使用 PAP DTD(Document Type Definition)指定下面的属性: XML 控制实体属性 描述 实例 X-Wap-Application-Id 这个实体属性指定了 RIM push 的REQUESTURI HTTP 同等体。 “/” push-id 指定唯一的消息 ID.另 外 , 这个控制实体属性可以用来 取消或检查消息的状态。建 议你在一个值的组合里使用 一个URL,例如, 123@BlackBerry.com. 123@wapforum.org ppg-notify-requested-to 指定发送结果通知的 URL. http://wapforum:8080/ ReceivePAPNotification. deliver-before-timestamp 指定日期和时间,通过它将 内容传送到 BlackBerry 设 备。这个时间之前的没有发 送的内容将会丢失。 以UTC 格式显示时间: YYYY-MM-DDThh:mm:ssZ 在这里:  YYYY是4个数字的年 份。  MM 是2个数字的月 份。  DD是2个数字的日期。  hh 是2个数字的 24 小 时制式的小时。  mm 是2个数字的分  ss 是2个数字的秒  Z描述了时间是UTC格 式。 2004-01-20T22:35:00Z deliver-after-timestamp 指定日期和时间,在这个时 间之后的内容发送到 BlackBerry 设备。这个时间 前的内容不会被传送。 以UTC 格式显示日期与时 间. 2004-01-20T21:35:00Z By Taiguo Zhang (confach@gmail.com) 158 参看 Push Access Protocol(WAP-247-PAP-20010429-a)文档获取更多关于使用 PAP 编写服务器 端push 用用程序。参看 PAP 2.0 DTD 得到关于 WAP Push DTD 的信息。 例::: : PAPPAPPAP PAP pushpushpush push requestrequestrequest request Content-Type: multipart/related; type="application/xml"; boundary=asdlfkjiurwghasf X-Wap-Application-Id: / --asdlfkjiurwghasf Content-Type: application/xml
--asdlfkjiurwghasf Content-Type: text/html Hello, PAP world! --asdlfkjiurwghasf-- 发送PAPPAPPAP PAP push push push push 取消请求 使用下面的头取消一个已经发送到 MDS 服务的 push 提交。 address-value 指定将 PUSH 内容发送到 BlackBerry 设备的地址。 destination 是目的 internet 消息地址或 PIN. WAPPUSH=destionation%3Aport1/ TYPE=USER@blackberry.com Delivery-method 指定内容,透明级,或应用 程序级的传送信任模式 Confirmed;uncomfirmed 头 描述 实例 cancel-message push- id 取消前面提交的 push 消息。 address address-value 指定push 消息 提交的地址。这 个标记是
By Taiguo Zhang (confach@gmail.com) 159 例:PAPPAPPAP PAP push push push push 取消请求 Content-Type: application/xml
发送一个 PAPPAPPAP PAP push push push push 查询请求 为了查询已经发送到 MDS 服务的 push 提交的状态,使用下面的头: Content-Type: application/xml
必需的。 XML 控制实体属性 描述 实例 statusquery-message push-id 指定push 消息的哪个状 态是需要的。 返回的响应是下面消息 状态之一: delivered,pending,undelive red,expired,rejected,timeou t,cancelled,aborted 或 unknown. 你必须在请求 里包含地址属性。 address ddress-vue 指定一个提交的 push 消 息地址。这个标记是必需 的。
net”/> By Taiguo Zhang (confach@gmail.com) 160 决定BlackBerry BlackBerry BlackBerry BlackBerry 设备是否在 push push push push 覆盖范围内 为了从 MDS 服务接收某个特定 BlackBerry 设备的网络覆盖信息,指定下面的头: 例:RIM :RIM :RIM :RIM 网络状态查询请求 Content-Type: application/xml 例:RIM :RIM :RIM :RIM 网络状态查询响应 Content-Type: application/xml

pdf贡献者

taco696

贡献于2012-08-16

下载需要 10 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!