“多年以前,我构建了自认为是有史以来销量最高的黑莓应用:BBSmart Email Viewer。今年我35岁,那款应用也已经过去了很多年,现在是时候谈一谈我是如何实现的了!我将BBSmart Email Viewer的源代码放在了GitHub上,还有我作为独立黑莓应用开发者时构建的其他应用。”
以下为译文:
多年以前,我构建了自认为是有史以来销量最高的黑莓应用:BBSmart Email Viewer。这样说有点奇怪,而且这只是在曾经存在过的黑莓应用开发者的小众社区之内,在这个社区外的反响说实话我并不清楚。但是这个应用给我的人生带来了巨大的影响。关于这一点尽管我有很多话可以讲,但是有一点是我一直以来都想写出来的,那就是这个应用使用了一个很少有人能想到的技巧。今年我35岁,那款应用也已经过去了很多年,现在是时候谈一谈了!我对BBSmart早已失去了商业兴趣,所以我将BBSmart Email Viewer的源代码放在了GitHub上,还有我在自己的公司BBSmart作为独立黑莓应用开发者时构建的其他应用(其中许多应用也都非常成功,只是没有BBSmart Email Viewer那样成功而已)。如果你愿意看一看,请不要对我的代码太苛刻,毕竟那只是刚开始学编程的23岁程序员的作品而已:)
BBSmart Email Viewer是什么?BBSmart Email Viewer是一款黑莓的邮件客户端。尽管众所周知黑莓以邮件服务为中心,但原生的邮件应用有很多欠考虑的地方,特别是在渲染HTML邮件的方面——它无法正确渲染HTML邮件,会显示很多乱七八糟的HTML标签。BBSmart Email Viewer有很多很酷的附加功能(回复模板、便签、任务和日历集成等),但最主要的卖点是它能正确渲染HTML邮件。
尽管BBSmart Email Viewer只是一个第三方邮件客户端,但它能够无缝地替换原生客户端,拥有原生客户端的一切功能。这貌似不太可能,不仅是因为黑莓没有提供实现邮件客户端所需的绝大多数核心功能的API,而且像今天的安卓那样允许开发者替换核心应用的API也没有。那么,我是怎样实现的呢?
史上销量最高的?没错!至少我这样认为。当时的移动环境与今天有很大区别。当时安卓和iPhone都还没有出现。黑莓上也没有原生的“应用程序商店”,人们只能去类似于MobiHand、Handango和CrackBerry之类的网站上购买应用程序,或者使用运营商预装的商店书签(如AT&T、T-Mobile等)。由于销售非常碎片化,所以很难得出确切的答案。但是,我有理由相信这个应用程序是有史以来销量最高的黑莓应用。BBSmart Email Viewer发布于2007年,一经发布就占据了所有销量排行榜的第一名。这个应用受到了多家媒体的报道,评论的赞美不绝于耳,我也接受了很多采访。但是这个应用程序的独特之处在于,它占据了各大主要网站的销量最佳排行榜的第一名长达一两年之久(我没有精确统计)。一年多的时间里,BBSmart Email Viewer一直是销量最高的黑莓应用。这个应用在企业里也卖出了很多,包括几万分授权,还有一份与美国的运营商签订的批量部署合约。由于这些销量,我对黑莓开发者的行业非常熟悉,也熟悉了这个行业的所有知名人物。在BBSmart的事情沉淀之后,我加入了黑莓,作为开发者关系团队的一员,之后成了黑莓的开发者关系团队的总监。我听到的、看到的一切都表明,BBSmart Email Viewer的销量超过了其他一切应用。
成就一切的魔法#1那么,为什么BBSmart Email Viewer能够无缝替换原生邮件客户端呢?有一天,我在浏览黑莓API文档时发现了一个很有意思的东西:你可以注册一个监听器,监视用户在原生邮件客户端中打开一封邮件的行为。BBSmart Email Viewer会在一开始就注册监听器:Session.addViewListener(this);当用户打开邮件时,该监听器就会被触发,调用其open处理函数。从这个处理函数里竟然能够获得当前UI栈的引用,只需调用UiApplcation.getUiApplication()即可。有了这个引用,就可以对当前显示栈中的任何画面做任何事情。你可以从当前栈中移除画面,加入新的画面,也可以深入修改任何画面中的任何UI组件(删除、替换等等)。这一点非常危险,也非常强大。因为从任何全局的事件处理函数中都可以做到这一点,所以在黑莓上可以创建一个应用,随时替换或更改任何正在运行的应用程序的任何部分。例如,理论上甚至可以注册一个频繁触发的全局事件处理函数,时刻唤醒并检查“HSBC Bank”这个应用是否在运行,如果在运行且用户位于转账画面,则更改“收款银行账号”中的值……(旁注:后来我用这个时刻唤醒功能构建了BBSmart Alarms Pro——一个支持多个闹钟的应用。注册RealtimeClockListener就可以以分钟的精度唤醒。)不论如何,对于我来说,我需要做的就是获得底层打开邮件的事件(由原始的打开邮件事件发送),格式化,然后在显示栈上放一个全新的界面,原生的邮件界面依然在后台运行,但被新的界面完全遮住。UiApplication.getUiApplication().pushScreen( new EmailViewScreen(m, formatter));当然,在显示邮件时我也做了许多工作来清理邮件内容,最后在一个HTMLField里显示邮件内容。当用户关闭邮件后,我会同时移除我自己的界面和原生的邮件界面,所以从用户的角度来看,看起来就像是原生邮件界面从来没有启动过一样:private void exitToMessageList() { // Close this and the previously opened // email view screen UiApplication.getUiApplication().popScreen( UiApplication.getUiApplication().getActiveScreen()); UiApplication.getUiApplication().popScreen( UiApplication.getUiApplication().getActiveScreen());}public boolean onClose() { if (this.getScreenBelow() == mailScreen) { exitToMessageList(); } else { close(); } return true;}
成就一切的魔法#2在完成这些工作后,我就可以显示自己的邮件界面(而不是显示原生的邮件界面)。但仍有一个问题:怎样才能在没有API支持的情况下实现所有原生客户端的功能,如打开邮件附件、回复、跳到下一封或前一封未读邮件等?关键的一点是黑莓的菜单。如果你不熟悉黑莓,那我来介绍一下:黑莓上有一个独立的物理菜单键,按下后可以弹出“应用程序内菜单”,其中显示所有全局和上下文相关(取决于当前的活跃点)的菜单项。
当然,原生邮件应用已经包含了所有原生的邮件菜单选项。所以我同样使用了前面得到的原生画面的引用,当用户在BBSmart Email Viewer里选择“Reply”,我就会获得原生画面的引用,根据该引用获得原生画面的菜单,最后“调用”相应的菜单项。例如,下面是BBSmart EmailViewer中的“Reply”菜单项的定义:private MenuItem replyMenuItem = new MenuItem(replyMenuItemText, 0, 0) { public void run() { findDefaultMenuItem(“Reply”).run(); if (UiApplication.getUiApplication().getActiveScreen() .getScreenBelow() != mailScreen) { close(); } }};其中findDefaultMenuItem和mailScreenMenu的定义为:private MenuItem findDefaultMenuItem(String name) { final int M_SIZE = mailScreenMenu.getSize(); MenuItem item; for (int i = 0; i < M_SIZE; i ) { item = mailScreenMenu.getItem(i); if (item.toString().equals(name)) { return item; } } return null;}mailScreen = UiApplication.getUiApplication().getActiveScreen();mailScreenMenu = mailScreen.getMenu(0);仔细管理原生邮件菜单,BBSmart Email Viewer就可以提供原生邮件应用中的所有功能。
推荐阅读??75.58 亿美元成交!美国最大规模 5G 毫米波频谱拍卖??不用掉一根头发!用 Flutter Dart 快速构建一款绝美移动 App??超轻量级中文OCR,支持竖排文字识别、ncnn推理,总模型仅17M
??和黑客斗争的 6 天!
??一文了解 Spring Boot 服务监控,健康检查,线程信息,JVM堆信息,指标收集,运行情况监控!
??用 3 个“鸽子”,告诉你闪电网络是怎样改变加密消息传递方式的!