这是本教程的最后一部分,在上一节中已经相当完整了。 我们可能会有其他分期付款,主要用于涵盖诸如“邀请朋友”和其他类似功能的增强功能,但这可能与完成应用程序并让您体验它一样好。
本机推送
到目前为止,我们使用PubNub实现了推功能,该功能非常出色,尤其是当您选择付费选项时,它还可以保留消息并为诸如此类的应用程序提供很多额外的好处。 但是,当应用程序未运行时,PubNub无法推送任何内容,在这种情况下,我们需要OS本机推送来发送消息。
显而易见的问题是“为什么不对所有内容使用OS本机推送?”。
可以这样做,但OS本机推送不如PubNub灵活,可移植,快速或可靠。 它的使用很痛苦,用户可以通过不同意操作系统提示等方式有意或无意地破坏它...正如本教程的其余部分将看到的那样,当PubNub无法达到目标时,我们会退回到本机操作系统推送没有万能药。
PushCallback接口
我们从在我们的主类中实现PushCallback
接口开始,这必须是实际的主类,否则push将不起作用:
public class SocialChat implements PushCallback {
然后我们需要实现以下方法:
@Override
public void push(String value) {
// its a JSON message, otherwise its a notice to the user
if(value.startsWith("{") || value.startsWith("[")) {
try {
JSONObject obj = new JSONObject(value);
// this is still early since we probably didn't login yet so add the messages to the list of pending messages
java.util.List<Message> pendingMessages = (java.util.List<Message>)Storage.getInstance().readObject("pendingMessages");
if(pendingMessages == null) {
pendingMessages = new ArrayList<>();
}
Message m = new Message(obj);
pendingMessages.add(m);
Storage.getInstance().writeObject("pendingMessages", pendingMessages);
addMessage(m);
} catch(JSONException err) {
err.printStackTrace();
}
}
}
@Override
public void registeredForPush(String deviceId) {
}
@Override
public void pushRegistrationError(String error, int errorCode) {
}
您会在这里注意到以下几点:
- 我们真的不需要在
registeredForPush
或pushRegistrationError
任何pushRegistrationError
。 由于我们使用PubNub,即使推送失败,该应用程序仍可以正常运行。registeredForPush
通常用于获取推送键(这不是传递给该方法的参数Push.getPushKey()
)并将其发送到服务器,以便您可以触发对此设备的推送。 由于这里没有真正的服务器,因此我们无法使用该方法。 -
push
方法极大地提升了处理推送消息的能力。 可以随时调用它并接受推送回调。 它接收可见和隐藏的推送消息,并根据其内容决定如何处理它们。 - 我们在推送回调期间不显示消息。 它会在用户有时间登录之前被调用,因此我们只想存储
Message
对象并在以后处理它们。 我们仍然将它们添加到一般商店中,以防用户决定在实际登录之前终止该应用程序
新常数和注册
我们需要将以下变量添加到类中才能继续,我屏蔽并更改了值。 我们将一一介绍它们:
private static final String PUSH_TOKEN = "********-****-****-****-*************";
private static final String GCM_SENDER_ID = "99999999999999";
private static final String GCM_SERVER_API_KEY = "******************-********************";
private static final boolean ITUNES_PRODUCTION_PUSH = false;
private static final String ITUNES_PRODUCTION_PUSH_CERT = "https://domain.com/linkToP12Prod.p12";
private static final String ITUNES_PRODUCTION_PUSH_CERT_PASSWORD = "ProdPassword";
private static final String ITUNES_DEVELOPMENT_PUSH_CERT = "https://domain.com/linkToP12Dev.p12";
private static final String ITUNES_DEVELOPMENT_PUSH_CERT_PASSWORD = "DevPassword";
PUSH_TOKEN
是最简单的,只需登录到Codename One并选择“帐户”标签即可。 它应该出现在“ Update Details
按钮的正上方。 如果不存在,请尝试注销并重新登录。
您可以很容易地从Google获取GCM_SENDER_ID
和GCM_SERVER_API_KEY
。 假定您已按照本教程第2部分中的说明创建Google项目。 如果您跳过了这一步(因为您不需要G +帐户登录),请确保根据第2部分中的说明在Google API控制台中创建一个新项目。
要生成这些,只需转到https://developers.google.com/mobile/add并单击“选择平台”:
选择“ Android App”
输入应用程序和程序包的详细信息
单击“云消息传递”选项,然后单击“启用Google Cloud Messaging”按钮
现在,您应该同时具有GCM_SENDER_ID
和GCM_SERVER_API_KEY
的值,如下所示
多亏了我们新的证书向导,现在可以轻松生成这些标志的iOS部分了!
我们只是通过证书向导并检查标志以启用推送:
启用推送向导
完成该向导并检查enable push标志后,请确保iOS部分也选中了“ Include Push”标志。 当前插件中有一个错误,该错误未自动启用。
您应该收到有关通过电子邮件进行推送的说明,其中应包括可以粘贴到位置的链接和密码。 这应该是无缝的。
其他代码更改
当您推送到设备时,您需要具有设备密钥,该密钥是您要向其发送推送的设备的唯一标识符。 不幸的是,由于我们没有合适的服务器,因此我们需要以某种方式从与我们聊天的人那里传递此密钥。 诀窍是将此密钥嵌入到Message对象中,然后在收到消息时对其进行更新,这意味着我们只能向过去写给我们的人发送推送消息。 并不是一个坏功能,但仍然是一个局限性...
为此,我们需要对Message
类进行以下两个简单更改:
public Message(JSONObject obj) {
try {
time = Long.parseLong(obj.getString("time"));
senderId = obj.getString("fromId");
recepientId = obj.getString("toId");
message = obj.getString("message");
name = obj.getString("name");
picture = obj.getString("pic");
// update the push id for the given user
if(obj.has("pushId")) {
String pushId = obj.getString("pushId");
if(pushId != null) {
Preferences.set("pid-" + senderId, pushId);
}
}
} catch (JSONException ex) {
// will this ever happen?
Log.e(ex);
}
}
public JSONObject toJSON() {
String pushId = Push.getPushKey();
if(pushId != null) {
JSONObject obj = createJSONObject("fromId", senderId,
"toId", recepientId,
"name", name,
"pic", picture,
"time", Long.toString(System.currentTimeMillis()),
"message", message, "pushId", pushId);
return obj;
}
JSONObject obj = createJSONObject("fromId", senderId,
"toId", recepientId,
"name", name,
"pic", picture,
"time", Long.toString(System.currentTimeMillis()),
"message", message);
return obj;
}
这样可以有效地将推送ID添加到我们发送的每封邮件(如果有),并更新联系人推送ID供以后使用。
现在我们需要注册推送,在SocialChat.java
start()
方法的SocialChat.java
添加:
// let the login form show before we register the push so the permission screen doesn't appear on a white
// background
Display.getInstance().callSerially(() -> {
// registering for push after the UI appears
Hashtable args = new Hashtable();
args.put(com.codename1.push.Push.GOOGLE_PUSH_KEY, GCM_SENDER_ID);
Display.getInstance().registerPush(args, true);
});
我们这样做是为了让UI首先出现。
以前在showChatForm
方法中,我们只是通过PubNub发送了一条消息,现在我们希望有一个后备版本,可以通过push发送该消息。 为此,我们需要知道对方没有收到该消息。 为了发现我们现在在PubNub中添加了一个称为“ ACK”的后退消息,它将确认消息的接收,如果未收到ACK,则意味着应该通过本机推送来发送消息…。为此,我们添加了类领域:
/**
* Includes messages that received ACK notices from the receiver
*/
private ArrayList<String> pendingAck = new ArrayList<>();
我们可以这样在listenToMessages
方法中自动删除ACK:
private void listenToMessages() {
try {
pb = new Pubnub("pub------------------------------", "sub-------------------------------");
pb.subscribe(tokenPrefix + uniqueId, new Callback() {
@Override
public void successCallback(String channel, Object message, String timetoken) {
if(message instanceof String) {
pendingAck.remove(channel);
return;
}
Message m = new Message((JSONObject)message);
pb.publish(tokenPrefix + m.getSenderId(), "ACK", new Callback() {});
Display.getInstance().callSerially(() -> {
addMessage(m);
respond(m);
});
}
});
} catch(PubnubException err) {
Log.e(err);
Dialog.show("Error", "There was a communication error: " + err, "OK", null);
}
}
在showChatForm
方法中,我们需要后退以进行推送,这有点大,所以我只在此处发布相关部分:
final Message messageObject = new Message(tokenPrefix + uniqueId, tokenPrefix + d.uniqueId, imageURL, fullName, text);
JSONObject obj = messageObject.toJSON();
String pid = Preferences.get("pid-" + tokenPrefix + d.uniqueId, null);
if(pid != null) {
// if we have a push address for the contact we can send them a push if they aren't reachable...
UITimer timeout = new UITimer(() -> {
if(pendingAck.contains(tokenPrefix + d.uniqueId)) {
pendingAck.remove(tokenPrefix + d.uniqueId);
// send two messages, one hidden with the data as JSON for parsing on the client
// the other one visible with the text that should appear to the user who isn't running
// the app, this will allow him to launch the app and then receive the hidden message immediately
// within the app
String cert = ITUNES_DEVELOPMENT_PUSH_CERT;
String pass = ITUNES_DEVELOPMENT_PUSH_CERT_PASSWORD;
if(ITUNES_PRODUCTION_PUSH) {
cert = ITUNES_PRODUCTION_PUSH_CERT;
pass = ITUNES_PRODUCTION_PUSH_CERT_PASSWORD;
}
if(Push.sendPushMessage(PUSH_TOKEN, text + ";" + obj.toString(),
ITUNES_PRODUCTION_PUSH, GCM_SERVER_API_KEY, cert, pass, 3, pid)) {
t.getUnselectedStyle().setOpacity(255);
t.repaint();
addMessage(messageObject);
} else {
chatArea.removeComponent(t);
chatArea.revalidate();
Dialog.show("Error", "We couldn't reach " + d.name + " thru push", "OK", null);
}
}
});
timeout.schedule(10000, false, write.getComponentForm());
if(!pendingAck.contains(tokenPrefix + d.uniqueId)) {
pendingAck.add(tokenPrefix + d.uniqueId);
}
}
它的工作方式非常简单:
- 如果我们有推送ID,那么我们将创建一个10秒的计时器
- 当计时器过去时,我们检查未决Ack是否仍未决,如果需要,我们需要后退以进行推送
- 我们具有上面
Message
类中的设备推键,因此发送推信息相对容易 - 我们发送类型3推送,其中包括可见和不可见有效负载,并用冒号(;)分隔。 可见的有效负载只是消息的文本,而不可见的有效负载是我们要添加到消息数据库的JSON字符串
聊天应用程序就差不多了!
最后的话
- 您可以在此处查看本教程的最终源代码,window =“ _ blank” 。
当我开始学习本教程时,我还不熟悉Codename One的Parse集成。 在玩了很多并被它吓了一跳之后,我会在它之上构建整个应用程序,并在此过程中进行了很多简化。 它还可以让我跟踪推送ID的保持消息,并消除在PubNub /本地推送之间来回切换的一些问题。
毫无疑问,我仍然会使用PubNub! 对于快速推送网络而言,它的功能惊人且非常方便。 我认为将其与Parse结合将使它成为一个更好的应用程序。
通过Google / Facebook等登录可能是应用程序中最痛苦的部分,而我将推送通知包含在内。 尽管它比以前要简单得多,并且比本机/ Web版本要简单,但我认为主要问题在于网络的不透明性和保持开发人员紧密联系的愿望。 在我们这边,痛苦减轻了,而在创建应用程序和将价值传递给Facebook / Google的乏味上更多。 APK哈希键很痛苦,有些事情像“邀请朋友”之类,由于乏味我不得不避免。
我可能会记住这些想法进行重写,但是我更愿意将其重做为cn1lib而不是应用程序。 最终动机是对应用程序的最终用户支持,因此开发人员可以通过将单个cn1lib集成到我们的应用程序中来与用户进行问题交流。 我不确定我是否有时间去研究类似的东西,但我认为这应该相对容易一些,因为大多数大型组件(推送,云存储等)已经由这些出色的第三方服务来处理。
本系列其他文章
这是一系列持续不断的帖子,包括以下部分:
- 第1部分–初始用户界面
- 第2部分–使用Google登录
- 第3部分–使用Facebook登录
- 第4部分–联系人表格
- 第5部分–聊天表单
- 第6部分–本机推送和整理
翻译自: https://www.javacodegeeks.com/2015/09/building-a-chat-app-with-codename-one-part-6-codename-one.html