作者: 邓明轩、王恒进、王志刚
使 用 P ersist ent S t or e 进行存储
在 Bla ck Be rr y 平 台上 还可 以通 过 P er s ist en tS t o r e 存储 数据, 这种 方法 可以 将对 象直接 保 存在设 备内 存中 , 需 要使 用的时 候可 以通 过 AP I 从 设备内 存中 直接 读取 出来 , 读取 出来 看获 得的是一 个 o bjec t 对 象, 需要开 发人 员对 该对 象进 行强制 转换 。
相比 RunT im eSt o r e , P er s ist en tS t o r e 的好处 是可 以持 久保存 数据 ,即 使设 备掉 电数据 也 不会丢 失。 不过 ,使用 P e r s ist en t S t o r e 要求被 保存的 数据必 须实现 P er s ist ab le 接口。 如下 面 代码所 示, 如果 希望 将 My Da t a 实例 保存 在 P er s ist en tS t o r e 中, M yDa t a 类 需要实 现 P er s ist ab le 接口:
public class MyData implements Persistable {
// 其中为 MyData 的 方法和 属性定义 。
}
在 P er s ist en tS t o r e 中保 存的 对象以 一个 长整型 的 ID 作 为标记 , 保存 或者 是获 取该 对象都 以这个 长整 型的 I D 作 为参 数。为 了保 证所 保存 的对 象和其 它应 用保 存的 对象 不冲突 ,可 以 通过 ha s h 算 法通 过包 名生 成一个 长整 型 ID 。 Bla ck Ber ry E cl ips e P lu g-i n 环境 也提 供了一 个方 法将字 符串 转换 成长 整型 ,选中 某一 行字 符串 ,点 击右键 ,选 择“ Co n v er t St r in g t o lo ng ”, 可以将 选中 的字 符串 转换 成长整 数。
无论用 什么 方式 生成 长整 数,对 象 ID 的定 义语 句都 类似于 以下 代码 :
public static long PersistentID = 0x815402392d453a9d L;
定义 了 P er s ist en t ID 后,可 以通 过 P er s ist en tS t o r e 的静 态方 法 g et P er s ist en t Ob ject 获得所 保存的 持久 化对 象, g et P e r s ist en t Ob ject 方法只有一 个参数 ,为 保存 对象 的 ID ,本例 使用 上 面定义 的 P er s ist en t ID 。
获得持 外化 对象 以后 ,可 以通过 该实例 的 g et Co n t en t s 方法获 得真 正保 存在设 备内存 的 对象, 可以通 过 s et Co n t en t s 将内存中 的对 象保 存到 持久化 对象 中。 在获 得对 象的过 程中 记 得要将 返回 的 Ob j ect 实例 强制转 换为 你使 用的类 。 在保存 对象 时记 得要调 用 P ers ist en tSot r e
的 c omm it 方法 完成 保存动 作。
P ers ist en tS t o r e 使用 的代码 片段如 下:
PersistentObject persistentStore ;
persistentStore = PersistentStore. getPersistentObject ( PersistentID );
synchronized ( persistentStore ) {
if ( persistentStore .getContents() == null ) { myData = new MyData(); persistentStore .setContents( myData ); persistentStore .commit();
} else {
myData = ( MyData) persistentStore .getContents();
}
}
}
使 用 SQLit e 进 行 存 储
在 Bla ck Be rry 5 . 0 以 上的 平台上 提供了 对 S ql it e 的 支持, 使开 发人 员可以 在 Bla ck Be rry 手机上 使用 关系 型数 据库 。对于 将 MI Dl et 移植 到 Bl ac kBe rry 上的 开发 人员 而 言,这 无疑 是 一个好 消息 ,使 用关 系型 数据库 保存 数据 可以 让程 序更加 简单 有效 。
当然, 决定 是否 将数 据存 储方式 由之 前的 RMS 转变 为关系 型数 据库 ,具 体要 看应用 的 规模和 数据 类型 。 一 般而 言, 如 果应 用规 模不 大, 数据类 型更 接近 树状 的文 档结构 , 则 不建 议使用 关系 型数 据库 。 反 之, 如果 应用 规模较大 , 而且需 要存 储的 数据 是大 批量的 规整 的数 据,则 使用 关系 型数 据库 比较有 利。
从具体 实现 上讲 ,在 Bla ck Berr y 平 台上 有 Da t ab a s eF a ct o ry 可以用 于创 建或 者是 连接数 据库, 所创 建的 数据 库以一 个文件 的形 式保 存在 设备 中, 可 以是 设备 内存, 也可 以是媒 体卡 。
创 建 或 者 是 连 接 数 据 库 以 后 获 取 了 Da t ab a s e 实 例 , 可 以 通 过 Da t ab a s e 实例 的 cre a t eSt a t emen t 方 法创建 一句 S QL 语句 ,通 过 S t a t emen t 实例 的 pr ep ar e 方法 准备执 行, 然 后通过该实例 的 e x ecut e 方法执行。如 果是查询语句,可以 在 p r epa r e 方法执行后,通 过 g et Cu r so r 获 得查 询结 果的 光标, 再通 过光 标操 作获 得所查 询的 内容 。
下面是 创建 或者 是连 接数 据库的 语句 :
String dbLocation = "/SDCard/databases/SQLite Demo /" ; URI uri = URI.create (dbLocation + "mydb " );
Database db = DatabaseFactory. openOrCreate (uri, new
DatabaseSecurityOptions( false )) ;
下面是 执行 查询 语句 的代 码片段 :
Statement statement = _db .createStatement( "SELECT * FROM Category" );
statement.prepare();
Cursor cursor = statement.getCursor();
下面是 查询 后遍 历结 果的 代码片 段:
Row row;
int id; String nam e;
while (c ur s or .n ex t( ))
{
ro w = c ur so r. ge tR ow () ;
id = r o w. ge tI nt eg er(0);
na me = ro w. ge tS tr in g( 1) ;
}
使用完 之后 记得要 将 S t a t emen t 实例 和 Cu r so r 实例关 闭,如 :
statement. close();
cursor.clo se();
对于 S QLi t e 的使用 , 关键 点是 S QLi t e 是一 个轻 量级 的 S QL 数 据库 , 不能 支持 所有 的 S QL 功能。 一 方面 是在 数据格 式上只 支持 简单 的类 似 In t eg er , T e xt 这 样的 类型, 另 一方面 是所 支 持的 S QL 语句 也有 限,不 支持复 杂的 查询 操作 。
有关 S QLi t e 的更 多信 息可 以在下 面的 网站 中找 到:
h t t p://www .s ql it e.o r g /
使用全局事件 来进行应用交 互
对于应 用通讯 , 只能 共享 数据是 不足 够的 , 在 共享 数据之 后需 要通 知相 应的 应用 , 让 目 标应用 可以 对数 据变 化进 行响应 。
Bla ck Be rry 平台 提供 了事 件模型 ,用 来在 不同 的应 用之间 通信 。 Bla c kBe rry 上的任 何应 用程序 都可 以发 布或 者监 听全局 事件 ,发 布全 局事 件的时 候需 要指 定一 个事 件 ID 号 ,一 个 应用所 发布 的事 件会 被所 有监听 全局 事件 的应 用获 取,不 同的 应用 需要 通过 I D 号判 断是 否 对该事 件进 行处 理。 另外 , 在 发送 全局 事件的 过程 中可以 同时 发送 一些 简单 的数据 , 使用 起 来会更 简单 一些 。 但 是对 于大量 的数 据, 一般 采用 的方式 是先 将数 据保 存到 特定的 共享 空间 中,然 后通 过事 件通 知目 标应用 。
首先需 要定 义一 个全 局事 件,对 于全 局事 件的 定义 , Bla ck Ber ry 有自 己的 定义 规范:
l 定义一 个 ID 变量
l 把 ID 变 量定 义为 静态 的, 从而使 得其 他的 类也 可以 引用到
l 通对包名 做 HA S H 产 生 ID ,使 ID 变得 独一 无二 示例代 码如 下
public static long GLOBAL_ID = 0xba4b84944bb7429eL;
定义了 全局 事件 后可 以将 该事件 发布 , 通 过把 事件 ID 传递 到 po s t G lo ba lE v en t ( ) 方法中 , 我们可 以发 布一 个全 局事 件
Bla ck Be rr y 提 供了 有四 种不 同的方 法来 发布 一个 事件 , 简单的 方法 是只 传入事 件 I D 不带 数据, 相对 复杂 的方 法在 发布事 件时 可以 传入 两个 整数作 为随 事件 发布 的内 容, 更 复杂 的可 以在发 布事 件时 传入 对象 作为发 布的 内容 。 开发 人员 可以根 据自 己程 序的 复杂 程序决 定使 用 什么方 法发 布一 个事 件。
最简单 的发 布事 件的 示例 代码如 下:
ApplicationManager.getApplicationManager()
.postGlobalEvent(GLOBAL_ID);
对于事 件接 受者 来说 ,需 要考虑 和实 现的 关键 点如 下
· 全局监 听应 用程 序必 须要 是一个 自动 启动 应用 程序
· 监听程 序需 要有 类实 现一 个 G lo ba l E v en t List ene r 接口
· 监听程 序需 要添 加 G lo ba lE v en t List ener 实例
实现 Globa lEven t L ist e n e r 和添加 ClobalEve nt L i stene r 的示例代码如下 :
class GlobalEventListenerApp extends UiApplication i mp le me nt s
GlobalEventListener {
public GlobalEventListenerApp() {
addGlobalEventListener ( this );
}
}
对于实 现 G lo ba lE v en t List ener 接口 的类 , 必 然实 现 ev e n t O cc ure d 方法 以响 应全局 事件的
发生。 需要 注意 的一 点是 不管事 件由 谁发 布, 也不 管事件 是系 统事 件还 是程 序发布 的事件 , 只要有 事件 发生 ,一 个注 册过 的 G lo ba lEn v en t List ene r 的 ev en t O cc ur e d 都会被 调用, 这意 味 着开发 人员 需要 自己 通过 事件 ID 进行 判断 , 如 果事 件 ID 属 于需 要处 理的 范围 才对事 件进 行 响应。 示例 代码 如下 :
public void eventOccured( long guid, int data0, int dat1,
Object object0, Object object1 ) {
// 注意这里需要检 查事 件 ID 是不是 需要响 应的事件 。
if (guid == Gl ob al E v e n t F i r i n g A p p .GLOBAL_ID) {
// 在这里完成事件响 应
}
}
接收推送数据
数据推送 是 Bla ck Be rry 平 台的一 大优 势, 当服 务器 端有数 据更 新时 , 应 用服 务器可 以将 数据推 送到 手机上 , 不 需要 手机上 的应 用通 过轮 询的 方式检 查服 务器 上是 否有 需要更 新的 数 据。 有 关数 据推 送的 基本 架构与 服务 器的 推送 代码 , 请参 考 Bla c kB er r y 推送 的相关 文档 , 文 小节只 描述 如何 在 MI Dl et 应用中 加上 手机 上侦 听推 送数据 的方 法。
应用自启动
如果希 望在 手机 端侦 听从 服务器 上推 送的 数据 , 一 般而言 需要 自动 启动 该侦 听应用 , 否 则有可 能该 侦听 应用 没有 启动, 导致 推送 数据 没有 被客户 端程 序接 收。
在 Bla ck B er ry 平台上要自动启动 一个应用程序比较简单, 可以直接通过设置完成, 在 Bla ck Be rr y 项 目中 双击 打开 “ Bla ck B er ry_ App _De s cri pt o r .xm l ” ,在 左边 “ G en er al In f o rm a t io n ” 栏下方 选中 “ Aut o -run o n s t art up ”就 可以 让该 项目中 的应用 在手 机启 动过 程中 自动启 动。
需要注 意的 是选 项“ Aut o -r un o n s t art up ”只 有在 应用 类型为 “ Bla c kBe rry App li c a t io n ”
时可用 ,也 就是 说如 果你 选择应 用类 型为 “ MI Dl et ”的话 就不 能使 用“ Aut o -r un o n st art up ” 选项。
所以需 要创 建一 个 Bla c kBe rry 应用 进行 侦听 ,而 不是 使用标准 的 MI Dl et 。
有关 Bla ck B er r y 应 用项目 的创建 在这 里不 做详 细描 述, 需 要了 解具 体的 步骤 请参考 相关 文档。 创建 了 Bla c kBe rry 项目后 首先 要考 虑应 用的 启动代 码, Bla ck B er ry 应用 与普通 的 ja v a 应用一样 以 m ai n 方 法作为 入口。
为了更 好地 对应 用实 例进 行控制 , 这里 采用 单例模 式, 为类 P us he dD a t aLi s t ener 创建 一
个静态 的单 例获 取方 法, 在 m ai n 函 数中 进行 调用 。
public static void main(String[] args) { PushedDataListener. waitForSingleton ().start();
}
用 于 获 取单 例 的 静 态方法 实 现 如 下 , 一 如 普 通的单 例 获 取 方法 , 该方 法 的返 回 值 为 P us he dD a t aLi s t ener 本 身 。在 该 方 法 中 , 使 用 了 R un t im eSt o r e 将 实 例 保存 起来 , 如 果 在 Run t im eS t o r e 中 已经 有实 例的话 则从 Run t im eSt o r e 中获取 ,没 有则 创建 一个 新的实 例并 返 回。
public static PushedDataListener waitForSingleton() {
// make sure this is a singleton instance
RuntimeStore store = RuntimeStore. getRuntimeStore (); Object o = store.get( RTSID_MY_APP );
if (o == null ) {
store.put( RTSID_MY_APP , new PushedDataListener());
return (PushedDataListener) store.get( RTSID_MY_APP );
} else {
return (PushedDataListener) o;
}
}
推送侦听
从上一 节的 启动 代码 可以 看到, 在应 用实 例得 到后调 用了 s t art 方法。 P us h e dD a t aLi s t ener
类 本 身 不 是 一 个 线 程 , 所 以 这 个 s t art 方 法 需 要 自 己 实 现 , 在 这 个 方 法 中 创 建 了 一 个
List ener Th re a d 的 实例 ,并 启动该 线程 。
List ener Th re a d 实 例的 实现 主要是 创建 侦听 连接 ,并 通过一 个不 结束 的循 环不 断从该 连 接中获 取服 务器 上推 送下 来的数 据。
首先需 要定 义 St r e am Co nn ect io nNot ifi e r 实 例, S t re a m Co nn ect io nNot ifi e r 用于 创建连 接。 其次需 要定 义 St r e am Co nn ect io n 和 In putSt re am ,从 连接中 获取 到输 入流 ,用 于侦听 数据 侦 听。
StreamConnectionNotifier notify = null ; StreamConnection stream = null ; InputStream input = null ;
定义 St r e am Co nn ect io nN ot ifi er 实例 后通 过 Co nn ect o r 的 o pen 方法 打开 连接。 Co nn ect o r
是一个 标准 的类 , 用于连 接不同 连接 , 如 h t t p , so ck et 等 。 连 接不 同网 络的时 候都是 使用 了 o pen 方法 ,在 参数 中传入 U RL 打开 指定 的地 址。网 络的不 同类 型通 过 U RL 参 数的不 同进 行 区分, 如 h t t p 的 U RL 以 “ h t t p:// ” 开 头, 而 so ck et 的 U RL 是以 “ so ck et : // ” 开 头。 推 送数 据 不属于 其它 标准 的协议 , 所以它 的 U R L 格 式比 较特 殊, 格式 为: “ h t t p:// : <po rt > ” 。 虽然 推送
的 U RL 以 “ h t t p:// ” 开头 , 但是 它不 是一 个 h t t p 请 求, 其 中的 “ <po rt > ” 为端 口号, 需要 和 服务器 推送 端约 定使 用同 一个端 口号 。 下面 是打开 边接的 代码 片段 , 其中 “ LIS TEN _U R L ”为 定义的 字符 串变 量, 值为 “ h t t p:// : 9 1 1 ” , 其中 9 1 1 为约定 的端 口。
notify = (StreamConnectionNotifier) Connecto r.open ( LISTEN_URL );
获 得 连 接 后 需 要 将 连 接 强 制 转 换 成 St re am Co nn ect io nNot if ier 。 获 得
St re am Co nn ect io nN ot ifi er 后就通 过一 个循 环不 断侦 听获取 推送 数据 。
循环的 第一 句为 not ify .a cc ept And Op en , 注意 这一句 语句执 行后 该线 程会 开始 等待, 不 再执行 ,直 到有 推送 数据 到达。
推 送 数 据 到 达 后 ac c ept And Op en 方 法 返 回 一 个 St re am Co nn ect io n 实 例 , 通 过 St re am Co nn ect io n 实例的 o penI np ut St re am 方法 可以 获得输 入流 ,然 后通 过输 入流获 得推 送 数据。
注意操 作完 成后 需要 将输 入流 和 St re am Co nn ect io n 关闭, 关闭 后重 新开 始侦 听, 等 待下 一个推 送数 据。
侦听的 代码 片段 如下 :
for (;;) {
stream = notify.acceptAndOpen();
input = stream.openInputStream();
// 在这里通 过对 input 的操 作获得推 送数据
input.close(); stream.close(); stream = null ;
}
另外需 要注 意的 是异 常处 理, 因为 连接 的建 立和 输入 流的处 理可 能会 因为 一些 原因抛 出 异常 。 一种方法是在循环内处 理 异常,处理完了继续循 环 , 但是这无 法 对 C o nn ect o r .o pen 方法进 行处 理。 所以 需要 在循环 外对 异常 进行 处理 , 将 Co nn ect o r .o pen 包含 进来, 本文 的例
子就是 使用 这种 方法 。 但 是, 实际 而言 这种处 理方 式也有 问题 , 当输入 流处 理有问 题的时 候 会跳出 循环 ,不 再侦 听推 送数据 。
最终建 议的 方法 是建 立两 重循环 , 在 两重 循环 都加 上异常 捕获 。 外 层循 环中 的异常 处理 负责处 理 Co nn ect o r .o pen 的异常 ,出 现异 常的 话处 理后重 新通 过 Co nn ect o r .o pen 打 开连 接。 内层循 环对 输入 流异 常进 行处理 , 处 理后 继续 侦听 数据, 而不 需要 重新 通过 Co nn ect o r .o pen 打开连接 。 具 体代 码请 参考 开发环 境附 带的 样例 。 本文 为简化 代码 没有 采用 双重 循环的方式 。
侦听程序与主 程序的交 互
获取侦 听数 据后 需要 做的 工作是 通知 主程 序有 新数 据到达 ,由 主程 序对 数据 进行处理 。 因为侦 听程 序为 后台 程序 , 不负 责界 面更 新等 操作 , 界面 更新 的操 作由 MI Dl e t 主程序 完成 。 可以看 到关 键是 作为 侦听 程序 的 Bla ck Be rr y 应 用与 作为主 程序 的 MI Dl et 如何 交互, 这可 以 使用上 一章 节介 绍的 方法 ,通 过 G lo ba lE v en t 通 知主 程序, 然后 将数 据写 入共 享数据 中。 主 程序在 接收 到事 件通 知后 共享数 据中 获得 推送 数据 再进行 数据 处理 和界 面更 新处理 。 具体 方 法不再 详细 描述 。
推送侦听的完 整代 码
下面是 推送 数据 侦听 的完 整代码 ,不 包括 应用 交互 部分:
package cn.searb;
import java.io.IOException;
import java.io.InputStream;
import javax.microedition.io.Connector;
import javax.microedition.io .StreamConnection;
import javax.microedition.io.StreamConnectionNotifier;
import net.rim.device.api.system.RuntimeStore;
import net.rim.device.api.ui.UiApplication;
public class PushedDataListener extends UiApplication {
public static final long RTSID_MY_APP = 0x68b31bd292413108L;
private static final String LISTEN_URL = " ht tp :/ /: 91 1" ; // th e li st en port
private ListenerThread myThread ;
public static void main(String[] args) { PushedDataListener. waitForSingleton ().start();
}
public PushedDataLi stener() {
myThread = new ListenerThread();
}
public static PushedDataListener waitForSingleton() {
// make sure this is a singleton instance
RuntimeStore store = RuntimeStore. getRuntimeStore (); Object o = store.get( RTSID_MY_APP );
if (o == nu ll ) {
store.put( RTSID_MY_APP , new PushedDataListener());
return (PushedDataListener) store.get( RTSID_MY_APP );
} else {
return (PushedDataListener) o;
}
}
public void start() {
invokeLater( new Runnable() {
public void run() {
myThre ad .start();
}
});
this .enterEventDispatcher();
}
class ListenerThread extends Thread {
public void run() {
System. out .println( "DemoOA BackGroundThread -- running" ); StreamConnectionNotifier notify = null ;
StreamConnection stream = null ; InputStream input = null ;
try {
sleep (1000);
} catch (Exception e) {
}
try {
notify = (StreamConnectionNotifier) Connector.open ( LISTEN_URL );
for (;;) {
stream = notify.acceptAndOpen();
input = stream.openInput Stream();
//
stream.close();
stream = null ;
}
} catch (IOException e) { System. err .println(e.toString());
} finally {
try {
if (stream != null ) {
stream.close();
}
} catch (Exception ex) {
}
try {
if (notify != null ) {
notify.close();
}
} catch (Exception ex) {
}
}
}
}
}
小结
通过本 文的 介绍 读者 可以 发现, 将 MI Dl et 程序 移植 到 Bla ck Be rr y 上 并不 困难 ,同时 开 发人员 也可 以根 据项 目的 时间要 求和 应用 的特 点决 定移植 的程 度。
不过, 无论 如何 ,移植 的 MI Dl et 程序 都不 能够 充分 地发 挥 Bla ck Be rr y 平 台的 优势, 如 果希望 最大 地发 挥 Bla c kBe rry 平台 的优 势, 还 是需要 根据 Bla ck B er r y 平 台的特 点重新 对应 用 进行实 现。
BlackBerry SDK下载
相关链接:
如何将MIDlet 应用移植到BlackBerry (一)
如何将MIDlet 应用移植到BlackBerry (二)
如何将MIDlet 应用移植到BlackBerry (三)
如何将MIDlet 应用移植到BlackBerry (四)