端到端真实世界 BlackBerry 应用,第五部分






4.67/5 (8投票s)
真实世界 BlackBerry 应用演练,第五部分。
引言
这是我的端到端 BlackBerry 应用演练的第五部分。在本文中,我将介绍以下主题:
- 使用资源文件本地化文本字符串,无需代码更改
- 在 Ribbon 上设置应用程序图标
- 主屏幕命令列表图标
- 修剪缓存的文章列表
- 对 HTTP 请求数据进行编码
- 应用程序“关于”对话框
- 在进行长时间运行时向用户发出警报
使用资源文件本地化文本字符串
虽然深入讨论资源文件超出了本文的范围(有关此主题的更多信息,一个好去处是 BlackBerry Java Development Environment Fundamentals Guide,您可以从 BlackBerry 开发者网站获取),但我要告诉您的是,每个应用程序都需要一个资源头文件、一个用于根(全局)区域设置的资源内容文件,以及一个用于每个支持的特定区域设置的资源内容文件。
资源头文件为每个本地化的字符串定义描述性键。资源内容文件将描述性键映射到全局和特定区域设置的值。
给定区域设置的资源最终存储在 ResourceBundle
对象中。ResourceBundleFamily
对象将应用程序的资源分组为一个 ResourceBundle
对象集合。这是应用程序根据用户的区域设置切换语言的机制。
对于我们的应用程序,我创建了资源头文件 (KnowledgeBase.rrh) 和一个用于根区域设置的资源内容文件 (KnowledgeBase.rrc)。基于这些文件,BlackBerry IDE 将在编译时创建一个资源接口 KnowledgeBaseResource
,我们可以使用它来访问本地化字符串。资源接口的名称与资源头文件的名称相同,并附加了 Resource 一词。
要通过应用程序的资源包访问我们的本地化资源,我们首先需要获取对我们资源包的引用
private ResourceBundle resources =
ResourceBundle.getBundle(KnowledgeBaseResource.BUNDLE_ID,
KnowledgeBaseResource.BUNDLE_NAME);
现在,我们准备像这样检索本地化字符串
String screenTitle = resources.getString(KnowledgeBaseResource.TTL_HOME_SCRN);
正如我在本节开头所说,资源还有很多内容我这里没有涵盖。您应该计划学习更多内容,我建议您从查看 BlackBerry Java Development Environment Fundamentals Guide 开始,该指南可在 BlackBerry 开发者网站上找到。
设置应用程序图标
现在是时候用更漂亮的图标替换默认应用程序图标了。要通过 BlackBerry JDE 设置应用程序图标,请将用作应用程序图标的图像添加到项目中并打开其属性
在“文件属性”小程序中,选择“用作应用程序图标”
而且,当我们运行应用程序时,我们看到新的应用程序图标
请注意,虽然此过程允许您设置应用程序图标,但它不允许您在 Ribbon 中选择或取消选择应用程序时更改图像。我将在以后的文章中介绍如何实现此行为。
主屏幕命令列表图标
最初,主屏幕上的三个命令都以相同的图标(一个绿星)开头
我对主屏幕进行了一些更改,现在每个命令都以反映该命令功能的图标开头
我通过修改主屏幕中 MyObjectListField
类中的代码来实现这一点。简而言之,MyObjectListField
现在有三个私有位图,每个命令一个,当绘制列表行时,会在命令之前绘制相应的位图。代码如下所示
private class MyObjectListField extends ObjectListField {
private Bitmap searchIcon =
Bitmap.getBitmapResource(resources.getString(
KnowledgeBaseResource.IMG_SEARCH));
private Bitmap tagsIcon =
Bitmap.getBitmapResource(resources.getString(
KnowledgeBaseResource.IMG_TAGS_LIST));
private Bitmap recentIcon =
Bitmap.getBitmapResource(resources.getString(
KnowledgeBaseResource.IMG_VIEW_RECENT));
private Bitmap icon;
// We are going to take care of drawing the item.
public void drawListRow(ListField listField, Graphics graphics,
int index, int y, int width) {
if ( mainMenuItems[index] == mnuSearchTitles) {
icon = searchIcon;
}
if ( mainMenuItems[index] == mnuBrowseTags) {
icon = tagsIcon;
}
if ( mainMenuItems[index] == mnuViewRecent) {
icon = recentIcon;
}
if (null != icon) {
int offsetY = (this.getRowHeight() - icon.getHeight())/2;
graphics.drawBitmap(1,y + offsetY, icon.getWidth(),
icon.getHeight(),icon,0,0);
graphics.drawText(mainMenuItems[index],
icon.getWidth() + 2, y, DrawStyle.ELLIPSIS,
width - icon.getWidth() + 2);
} else {
graphics.drawText("- " + mainMenuItems[index], 0,
y, DrawStyle.ELLIPSIS, width -
graphics.getFont().getAdvance("- "));
}
}
}
修剪缓存的文章列表
根据我设定的要求,我们的应用程序应维护一个内存缓存,其中包含最近查看的文章。
应用程序的 DataStore
类已经能够通过 setCachedArticles()
和 getCachedArticles()
方法处理缓存文章的存储和检索。我们还有一个方法可以让用户设置缓存文章的最大数量。
我们缺少的是将文章添加到缓存的机制(一旦用户打开一篇文章)以及修剪缓存,以便文章数量不超过用户定义的最小值。实现此类机制的好地方是 Article Screen。让我们添加一些代码来处理这些可能的情况
- 缓存为空
- 缓存中有一些文章,但仍有空间
- 缓存已满
- 我们正在查看的文章已缓存
当缓存为空时,我们只需要添加当前正在查看的文章
// Handle the case when there's nothing cached.
if (null == cachedItems || cachedItems.length == 0) {
cachedItems = new Article[]{article};
DataStore.setCachedArticles(cachedItems);
return;
}
当缓存不为空但仍有空间时,我们只需要将当前文章插入列表的顶部
// Handle the case when we need to add one more article to the list.
Article[] newList = new Article[cachedItems.length + 1];
// Insert the old items in the new list.
for (int i = 1; i <= cachedItems.length; i++) {
// We're shifting the articles too, to leave room at the top of the list
// for the article we're currently viewing.
newList[i] = cachedItems[i - 1];
}
// Now, move the article we're viewing to the top of the list.
newList[0] = article;
DataStore.setCachedArticles(newList);
当缓存已满时,我们必须为当前文章腾出空间
// Handle the case when the cached need to be trimmed.
Article[] newList = new Article[maxAllowed];
// Insert all the items, except the oldest, in the new list.
for (int i = 1; i < maxAllowed; i++) {
// We're shifting the articles too, to leave room at the top of the list
// for the article we're currently viewing.
newList[i] = cachedItems[i - 1];
}
// Now, move the article we're viewing to the top of the list.
newList[0] = article;
DataStore.setCachedArticles(newList);
当我们正在查看的文章已缓存时,我们直接退出
// Bail if the article we're viewing is already cached.
for (int i = 0; i < cachedItems.length; i++) {
if (article.id.equalsIgnoreCase(cachedItems[i].id)) {
return;
}
}
看起来我们已经处理了所有情况,对吗?嗯,还没有完全。如果缓存中有 N 篇文章,而用户通过“选项”屏幕将缓存的最大文章数量更改为一个小于 N 的数字,会发生什么?要处理这种情况,我们需要向“选项”屏幕添加一些代码
private void trimCachedArticlesList() {
Article[] cached = DataStore.getCachedArticles();
if (null == cached || cached.length == 0) {
return;
}
int maxAllowed = DataStore.getMaxCachedArticles();
if (cached.length <= maxAllowed) {
return;
}
Article[] newList = new Article[maxAllowed];
for (int i = 0; i < maxAllowed; i++) {
newList[i] = cached[i];
}
DataStore.setCachedArticles(newList);
}
现在,我认为我们状态良好。如果我忘记了什么细节,我相信您会让我知道的。:)
对 HTTP 请求数据进行编码
关于忘记细节,回到我添加 HTTP 处理代码时,处理创建 HTTP 请求数据的例程如下所示
public static String createRequestString(final String[] keys, final String[] values) {
StringBuffer requestContents = new StringBuffer("");
if (keys != null) {
for (int i = 0; i < keys.length; i++) {
requestContents.append(keys[i] + "=" + values[i] + "&");
}
}
// Terminate the request with a valid sequence.
requestContents.append("0=0\r\n";);
return requestContents.toString();
}
这段代码看起来都没问题,直到您开始想知道,如果请求数据中的任何实际值(任何 values[i]
)包含保留字符,如“;”、“/”、“?”、“:”、“=”或“&”,会发生什么。而且,这可能像上周对我一样严重,当时一个生产应用程序因为这个编码问题开始“随机”失败。
我们在这里需要做的是确保我们的请求数据被正确编码,以便我们的服务器端应用程序能够理解请求。
在修复我上周的紧急情况时,我偶然发现了 net.rim.blackberry.api.browser.URLEncodedPostData
。URLEncodedPostData
正是这样一个类,它使用 URL 编码来对表单数据进行编码。让我们使用它来解决我们的编码问题
public static byte[] createPostData(final String[] keys, final String[] values) {
URLEncodedPostData postData =
new URLEncodedPostData(URLEncodedPostData.DEFAULT_CHARSET, true);
if (keys != null) {
for (int i = 0; i < keys.length; i++) {
postData.append( keys[i], values[i]);
}
}
return postData.getBytes();
}
应用程序“关于”对话框
我认为“关于”对话框是为我们的应用程序增添亮点的好方法。它由我刚刚添加到主屏幕的“关于”菜单触发。
这里值得注意的是使用 ApplicationDescriptor
来获取应用程序名称和版本号
private MenuItem aboutMenu =
new MenuItem(resources.getString(KnowledgeBaseResource.MNU_ABOUT),150,10) {
public void run() {
String crNotice = resources.getString(KnowledgeBaseResource.LBL_ABOUT);
ApplicationDescriptor descriptor =
ApplicationDescriptor.currentApplicationDescriptor();
String appVersion = descriptor.getName() + " " + descriptor.getVersion();
Dialog.alert(appVersion + "\n" + crNotice);
}
};
当长时间运行时向用户发出警报
我们应用程序的某些功能,例如搜索文章,可能会涉及长时间的执行。重要的是,我们不要通过在没有给用户任何视觉提示的情况下启动长时间运行的操作来惹恼我们的用户,提示他们程序实际上正在工作,他们可能需要等待一段时间才能完成操作。
有些开发者喜欢使用屏幕作为状态指示器,但一个简单的对话框就足够了。
下面是实现方法
Bitmap icon = Bitmap.getBitmapResource(
resources.getString(KnowledgeBaseResource.IMG_DOWNLOAD));
statusDlg = new Dialog(resources.getString(
KnowledgeBaseResource.LBL_DOWNLOADING),null,null,0,icon);
private void sendHttpRequest(String[] keys, String[] values) {
String url = DataStore.getAppServerUrl();
// Make sure we can create an http request.
// If the app. server URL has not been set, we cannot make any http requests.
if (null == url || url.length() == 0) {
Dialog.alert(resources.getString(
KnowledgeBaseResource.ERR_INVALID_APP_SERVER_URL));
return;
}
statusDlg.show();
.
.
.
}
public void processResponse(final int HTTPResponseCode, final String data) {
UiApplication.getUiApplication().invokeLater(new Runnable() {
public void run()
{
statusDlg.close();
.
.
.
}
下一步
在涵盖了上述重要细节之后,我们仍然需要编写服务器端代码来处理传入的 HTTP 请求、从数据库检索信息,并将响应发送回 BlackBerry 设备上的应用程序。
历史
这是本系列之前文章的链接