如何在Ubuntu QML应用中使用Push Notification

我们知道目前Ubuntu手机平台有些类似iPhone平台,是一个单任务的操作系统,虽然系统本身具有多任务的功能。如果当前的应用被推到后台的话,应用将会被自动挂起,而不会被系统所运行。在这个时候如果我们的应用需要等待一个消息,比如就想微信之类的信息,我们就要使用Ubuntu平台所提供的Push Notification机制来实现我们的类似多任务的东西。当通知被收到后,我们就可以直接点击接受到的通知,应用又会被重新运行到前台。

关于Push notification,在我们的开发者网站上,有一篇文章(client)和一篇文章(server)详细介绍了它的机制。这里我不想讲太多的东西。有兴趣的同学们可以详读那篇文章。今天在这里,我来和大家分析一个具体的实例,以更好地了解如何在Ubuntu手机上实现这个功能。

在上述的图中可以看出来,整个系统的组成分两部分:客户端及服务器端。在服务器端,又分为一个PushSever (https://push.ubuntu.com)及一个App Server。App server是用来管理我们的用户的Nick Name及Token的。在它的里面,有一个数据库。

为了测试,开发者必须有一个Ubuntu One的账号。我们需要在手机的“系统设置”里的账号中创建这个账号。

当一个QML应用在使用:

import Ubuntu.PushNotifications 0.1

PushClient {
    id: pushClient
    Component.onCompleted: {
        notificationsChanged.connect(messageList.handle_notifications)
        error.connect(messageList.handle_error)
    }
    appId: "com.ubuntu.developer.push.hello_hello"
}

当我们使用上面的API后,push server将向我们的客户端发送一个token。这个token依赖于手机自己的参数及上面所看到的“appId”。利用这个token,我们可以向我们的应用服务器注册,并存于应用服务器端中。当我们需要发送信息的时候,我们必须注册一个类似nickname的东西。这个nickname将和我们手机客户端的token绑定。每当另外一个nickname想像我们发送信息时,应用服务器端可以通过数据库的查询来得到我们的token,从而更进一步通过push server来向我们的客服端推送信息。如果我们的客户端想向其它的客户端发送信息,这其中的道理,也是和刚才一样。

目前,在我们的开发者网站并没有PushClient的具体的介绍。我们可以使用在文章“ 如何得到QML package的详细API接口”中的方法来了解这个API。

Push server是用来推送信息。它位于 https://push.ubuntu.com。它只有一个endpoint:/notify。为了向一个用户发送推送信息。应用服务器可以向Push Sever发送一个含有“Content-type: application/json”的HTTP POST信息来推送我们的信息。下面是一个POST body的一个样板内容:

{
        "appid": "com.ubuntu.music_music",
        "expire_on": "2014-10-08T14:48:00.000Z",
        "token": "LeA4tRQG9hhEkuhngdouoA==",
        "clear_pending": true,
        "replace_tag": "tagname",
        "data": {
                "message": "foobar",
                "notification": {
                        "card": {
                                "summary": "yes",
                                "body": "hello",
                                "popup": true,
                                "persist": true
                        }
                        "sound": "buzz.mp3",
                        "tag": "foo",
                        "vibrate": {
                                "duration": 200,
                                "pattern": (200, 100),
                                "repeat": 2
                        }
                        "emblem-counter": {
                                "count": 12,
                                "visible": true
                        }
                }
        }
}
appid: ID of the application that will receive the notification, as described in the client side documentation.
expire_on: Expiration date/time for this message, in ISO8601 Extendend format
token: The token identifying the user+device to which the message is directed, as described in the client side documentation.
clear_pending: Discards all previous pending notifications. Usually in response to getting a "too-many-pending" error.
replace_tag: If there‘s a pending notification with the same tag, delete it before queuing this new one.
data: A JSON object.

从上面的信息格式,我们可以看出来,token是非常重要的一个信息。有了它,我们就可以向我们需要的终端发送推送信息。

我们可以利用我们的SDK来创建一个简单的例程。下面简单介绍一下我们的主要的文件main.qml:

import QtQuick 2.0
import Qt.labs.settings 1.0
import Ubuntu.Components 0.1
import Ubuntu.Components.ListItems 0.1 as ListItem
import Ubuntu.PushNotifications 0.1
import "components"

MainView {
    id: "mainView"
    // objectName for functional testing purposes (autopilot-qt5)
    objectName: "mainView"

    // Note! applicationName needs to match the "name" field of the click manifest
    applicationName: "com.ubuntu.developer.ralsina.hello"

    automaticOrientation: true
    useDeprecatedToolbar: false

    width: units.gu(100)
    height: units.gu(75)

    Settings {
        property alias nick: chatClient.nick
        property alias nickText: nickEdit.text
        property alias nickPlaceholder: nickEdit.placeholderText
        property alias nickEnabled: nickEdit.enabled
    }

    states: [
        State {
            name: "no-push-token"
            when: (pushClient.token == "")
            PropertyChanges { target: nickEdit; readOnly: true}
            PropertyChanges { target: nickEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: false}
            PropertyChanges { target: loginButton; enabled: false}
            PropertyChanges { target: loginButton; text: "Login"}
        },
        State {
            name: "push-token-not-registered"
            when: ((pushClient.token != "") && (chatClient.registered == false))
            PropertyChanges { target: nickEdit; readOnly: false}
            PropertyChanges { target: nickEdit; text: ""}
            PropertyChanges { target: nickEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: false}
            PropertyChanges { target: loginButton; enabled: true}
            PropertyChanges { target: loginButton; text: "Login"}
        },
        State {
            name: "registered"
            when: ((pushClient.token != "") && (chatClient.registered == true))
            PropertyChanges { target: nickEdit; readOnly: true}
            PropertyChanges { target: nickEdit; text: "Your nick is " + chatClient.nick}
            PropertyChanges { target: messageEdit; focus: true}
            PropertyChanges { target: messageEdit; enabled: true}
            PropertyChanges { target: loginButton; enabled: true}
            PropertyChanges { target: loginButton; text: "Logout"}
        }
    ]

    state: "no-push-token"

    ChatClient {
        id: chatClient
        onError: {messageList.handle_error(msg)}
        token: {
            var i = {
                "from" : "",
                "to" :  "",
                "message" : "Token: " + pushClient.token
            }

            if ( pushClient.token )
                messagesModel.insert(0, i);
            console.log("token is changed!");
            return pushClient.token;
        }
    }

    PushClient {
        id: pushClient
        Component.onCompleted: {
            notificationsChanged.connect(messageList.handle_notifications)
            error.connect(messageList.handle_error)
            onTokenChanged: {
                console.log("token: +" + pushClient.token );
                console.log("foooooo")
            }
        }
        appId: "com.ubuntu.developer.ralsina.hello_hello"

    }

    TextField {
        id: nickEdit
        placeholderText: "Your nickname"
        inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
        anchors.left: parent.left
        anchors.right: loginButton.left
        anchors.top: parent.top
        anchors.leftMargin: units.gu(.5)
        anchors.rightMargin: units.gu(1)
        anchors.topMargin: units.gu(.5)
        onAccepted: { loginButton.clicked() }
    }

    Button {
        id: loginButton
        anchors.top: nickEdit.top
        anchors.right: parent.right
        anchors.rightMargin: units.gu(.5)
        onClicked: {
            if (chatClient.nick) { // logout
                chatClient.nick = ""
            } else { // login
                chatClient.nick = nickEdit.text
            }
        }
    }

    TextField {
        id: messageEdit
        inputMethodHints: Qt.ImhNoAutoUppercase | Qt.ImhNoPredictiveText | Qt.ImhPreferLowercase
        anchors.right: parent.right
        anchors.left: parent.left
        anchors.top: nickEdit.bottom
        anchors.topMargin: units.gu(1)
        anchors.rightMargin: units.gu(.5)
        anchors.leftMargin: units.gu(.5)
        placeholderText: "Your message"
        onAccepted: {
            console.log("sending " + text)
            var idx = text.indexOf(":")
            var nick_to = text.substring(0, idx).trim()
            var msg = text.substring(idx+1, 9999).trim()
            var i = {
                "from" :  chatClient.nick,
                "to" :  nick_to,
                "message" : msg
            }
            var o = {
                enabled: annoyingSwitch.checked,
                persist: persistSwitch.checked,
                popup: popupSwitch.checked,
                sound: soundSwitch.checked,
                vibrate: vibrateSwitch.checked,
                counter: counterSlider.value
            }
            chatClient.sendMessage(i, o)
            i["type"] = "sent"
            messagesModel.insert(0, i)
            text = ""
        }
    }
    ListModel {
        id: messagesModel
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Register by typing your nick and clicking Login."
        }
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Send messages in the form \"destination: hello\""
        }
        ListElement {
            from: ""
            to: ""
            type: "info"
            message: "Slide from the bottom to control notification behaviour."
        }
    }

    UbuntuShape {
        anchors.left: parent.left
        anchors.right: parent.right
        anchors.bottom: notificationSettings.bottom
        anchors.top: messageEdit.bottom
        anchors.topMargin: units.gu(1)
        ListView {
            id: messageList
            model: messagesModel
            anchors.fill: parent
            delegate: Rectangle {
                MouseArea {
                    anchors.fill: parent
                    onClicked: {
                        if (from != "") {
                            messageEdit.text = from + ": "
                            messageEdit.focus = true
                        }
                    }
                }
                height: label.height + units.gu(2)
                width: parent.width
                Rectangle {
                    color: {
                        "info": "#B5EBB9",
                        "received" : "#A2CFA5",
                        "sent" : "#FFF9C8",
                        "error" : "#FF4867"}[type]
                    height: label.height + units.gu(1)
                    anchors.fill: parent
                    radius: 5
                    anchors.margins: units.gu(.5)
                    Text {
                        id: label
                        text: "<b>" + ((type=="sent")?to:from) + ":</b> " + message
                        wrapMode: Text.Wrap
                        width: parent.width - units.gu(1)
                        x: units.gu(.5)
                        y: units.gu(.5)
                        horizontalAlignment: (type=="sent")?Text.AlignRight:Text.AlignLeft
                    }
                }
            }

            function handle_error(error) {
                messagesModel.insert(0, {
                     "from" :  "",
                     "to" :  "",
                     "type" :  "error",
                     "message" : "<b>ERROR: " + error + "</b>"
                })
            }

            function handle_notifications(list) {
                list.forEach(function(notification) {
                    var item = JSON.parse(notification)
                    item["type"] = "received"
                    messagesModel.insert(0, item)
                })
            }
        }
    }

    Panel {
        id: notificationSettings
        anchors {
            left: parent.left
            right: parent.right
            bottom: parent.bottom
        }
        height: item1.height * 9
        UbuntuShape {
            anchors.fill: parent
            color: Theme.palette.normal.overlay
            Column {
                id: settingsColumn
                anchors.fill: parent
                ListItem.Header {
                    text: "<b>Notification Settings</b>"
                }
                ListItem.Standard {
                    id: item1
                    text: "Enable Notifications"
                    control: Switch {
                        id: annoyingSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Enable Popup"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: popupSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Persistent"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: persistSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Make Sound"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: soundSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Vibrate"
                    enabled: annoyingSwitch.checked
                    control: Switch {
                        id: vibrateSwitch
                        checked: true
                    }
                }
                ListItem.Standard {
                    text: "Counter Value"
                    enabled: annoyingSwitch.checked
                    control: Slider {
                        id: counterSlider
                        value: 42
                    }
                }
                Button {
                    text: "Set Counter Via Plugin"
                    onClicked: { pushClient.count = counterSlider.value; }
                }
                Button {
                    text: "Clear Persistent Notifications"
                    onClicked: { pushClient.clearPersistent([]); }
                }
            }
        }
    }
}

这里,在上面创建一个nickname的输入框及一个login按钮。紧接着,我们创建一个输入信息的对话框。再紧接着,我们创建了一个listview来显示状态,提示信息,或来往的信息。

ChatClient.qml文件的定义如下:

import QtQuick 2.0
import Ubuntu.Components 0.1

Item {
    property string nick
    property string token
    property bool registered: false
    signal error (string msg)
    onNickChanged: {
        if (nick) {
            console.log("Nick is changed!");
            register()
        } else {
            registered = false
        }
    }
    onTokenChanged: {
        console.log("Token is changed!");
        register()
    }

    function register() {
        console.log("registering ", nick, token);
        if (nick && token) {
            console.log("going to make a request!");

            var req = new XMLHttpRequest();
            req.open("post", "http://direct.ralsina.me:8001/register", true);
//            req.open("post", "http://127.0.0.1:8001/register", true);
            req.setRequestHeader("Content-type", "application/json");
            req.onreadystatechange = function() { // Call a function when the state changes.
                if(req.readyState == 4) {
                    if (req.status == 200) {
                        console.log("response: " + JSON.stringify(req.responseText));
                        registered = true;
                    } else {
                        error(JSON.parse(req.responseText)["error"]);
                    }
                }
            }

            console.log("content: " + JSON.stringify(JSON.stringify({"nick" : nick.toLowerCase(),
                                                                     "token": token
                                                                    })));
            req.send(JSON.stringify({
                "nick" : nick.toLowerCase(),
                "token": token
            }))
        }
    }

    /* options is of the form:
      {
          enabled: false,
          persist: false,
          popup: false,
          sound: "buzz.mp3",
          vibrate: false,
          counter: 5
      }
    */
    function sendMessage(message, options) {
        var to_nick = message["to"]
        var data = {
            "from_nick": nick.toLowerCase(),
            "from_token": token,
            "nick": to_nick.toLowerCase(),
            "data": {
                "message": message,
                "notification": {}
            }
        }
        if (options["enabled"]) {
            data["data"]["notification"] = {
                "card": {
                    "summary": nick + " says:",
                    "body": message["message"],
                    "popup": options["popup"],
                    "persist": options["persist"],
                    "actions": ["appid://com.ubuntu.developer.ralsina.hello/hello/current-user-version"]
                }
            }
            if (options["sound"]) {
                data["data"]["notification"]["sound"] = options["sound"]
            }
            if (options["vibrate"]) {
                data["data"]["notification"]["vibrate"] = {
                    "duration": 200
                }
            }
            if (options["counter"]) {
                data["data"]["notification"]["emblem-counter"] = {
                    "count": Math.floor(options["counter"]),
                    "visible": true
                }
            }
        }
        var req = new XMLHttpRequest();
        req.open("post", "http://direct.ralsina.me:8001/message", true);
        req.setRequestHeader("Content-type", "application/json");
        req.onreadystatechange = function() {//Call a function when the state changes.
            if(req.readyState == 4) {
                if (req.status == 200) {
                    registered = true;
                } else {
                    error(JSON.parse(req.responseText)["error"]);
                }
            }
        }
        req.send(JSON.stringify(data))
    }
}

这个是用来向应用服务器发送注册信息及发送信息的。这里我们使用了一个已经建立好的应用服务器在http://direct.ralsina.me:8001。

这里,我们必须在手机或者我们的模拟器中创建一个Ubuntu One的账号,否则应用将不会运行成功。

我们同时运行我们的手机和模拟器,我们可以看到如下的画面:

   

  

整个“hello”的源码在:git clone https://gitcafe.com/ubuntu/example-client.git

整个server的源码在地址:git clone https://gitcafe.com/ubuntu/example-server.git

为了能够运行应用服务器,我们必须在服务器上安装相应的component,并选好自己的口地址(比如8001),这个在服务器代码中的config.js中可以找到:

module.exports = config = {
    "name" : "pushAppServer"
    ,"app_id" : "appEx"
    ,"listen_port" : 8000
    ,"mongo_host" : "localhost"
    ,"mongo_port" : 27017
    ,"mongo_opts" : {}
    ,"push_url": "https://push.ubuntu.com"
    ,"retry_batch": 5
    ,"retry_secs" : 30
    ,"happy_retry_secs": 5
    ,"expire_mins": 120
    ,"no_inbox": true
    ,"play_notify_form": true
}

然后运行:

$nodejs server.js

这样服务器就搭建好了。

时间: 2024-10-29 19:07:36

如何在Ubuntu QML应用中使用Push Notification的相关文章

如何在Ubuntu QML应用中播放视频

在这篇文章中,我们将介绍如何在Ubuntu QML应用中播放一个视频.为了实现方便,我们可以事先用手机录下一个视频,并置于我们已经创建好的项目中. 首先,我们利用Ubuntu SDK来创建一个"QML app with Simple UI (qmake)"的项目.我们修改我们的Main.qml文件如下: import QtQuick 2.0 import Ubuntu.Components 1.1 import QtMultimedia 5.0 /*! \brief MainView

如何在Ubuntu QML应用中播放音乐

昨天我看见一个开发者发了一个问题,询问如何播放声音.目前由于一些原因在模拟器中没法测试.其实,播放声音很容易.如果使用qmake的话,可能需要做一些修改才可以正确地在手机上播放. 我们首先使用SDK来创建一个简单的项目(QML app with Simple UI "qmake").我们记得修改我们的Main.qml如下: import QtQuick 2.0 import Ubuntu.Components 1.1 import QtMultimedia 5.0 /*! \brief

如何在Ubuntu QML应用中震动(vibration)

对于有些QML应用来说,震动是非常重要的一个功能.特别是对一下游戏来说.那么我们怎么在QML应用中震动呢? 我们官方有一个API HapticsEffect,这个API的功能就是让我们的应用来震动的.使用这个API非常容易: import QtQuick 2.0 import Ubuntu.Components 1.1 import QtFeedback 5.0 /*! \brief MainView with a Label and Button elements. */ MainView {

如何在Ubuntu QML应用中进行语言录音

在QML API中,目前并没有一个相应的API来进行录音.我们必须使用Qt C++ API QAudioRecorder来进行录音的工作.在这篇文章中,我们来介绍如何使用这个API来进行录音. 首先,我们来创建一个"QML App with C++ plugin (qmake)"模版的应用.注意qmake的项目必须是在15.04及以上的target上才可以运行. 为了录音,我创建了一个叫做"AudioRecorder"的类: audiorecorder.h #ifn

如何在Ubuntu QML应用中判断应用的方位(landscape或portrait)

我们知道对于一些应用来说,判断方位可以使得我们可以重新定位我们的应用的布局,以使得我们的应用在不同的方位中更加合理及好看.在这篇文章中,我们来介绍如何来侦测应用方位的变化. 我们首先来创建一个我们自己的简单的QML应用.对于大多数的QML应用来说,一般是含有一个"MainView"的: MainView { id: root // objectName for functional testing purposes (autopilot-qt5) objectName: "m

如何在Ubuntu QML应用中实现一个垂直的Slider

我们在使用Ubuntu SDK中的Slider的时候,我们发现,它没有orientation的属性尽管在Qt官方网站的slider是有这个属性的.在默认的情况下,这个Slider是水平的.那么我们该如实现这个呢? 我们的任何一个QML Item都有一个属性叫做rotation.我们可以通过这个属性来得到一个旋转90度的水平Slider.这样我们就可以用如下的代码来实现了: import QtQuick 2.0 import Ubuntu.Components 1.1 /*! \brief Mai

如何在Ubuntu 14.04中安装最新版Eclipse

想必很多开发人员都知道,Ubuntu 软件源中提供的并不是最新版本的 Eclipse,本教程就教大家如何在 Ubuntu 14.04 中快速安装 Eclipse 官方发布的最新版本. 到目前为止,Eclipse 的官方最新版本为 Eclipse Kepler (4.3.2),我们可以使用如下步骤在 Ubuntu 14.04 或其它 Ubuntu 版本中进行快速安装. 1.安装OpenJDK Java 7 如果你的系统中没有安装 Java,我们需要按如下步骤事先安装好 OpenJDK Java 7

转:如何在Ubuntu 14.04中安装最新版Eclipse

想必很多开发人员都知道,Ubuntu 软件源中提供的并不是最新版本的 Eclipse,本教程就教大家如何在 Ubuntu 14.04 中快速安装 Eclipse 官方发布的最新版本. 到目前为止,Eclipse 的官方最新版本为 Eclipse Kepler (4.3.2),我们可以使用如下步骤在 Ubuntu 14.04 或其它 Ubuntu 版本中进行快速安装. 1.安装OpenJDK Java 7 如果你的系统中没有安装 Java,我们需要按如下步骤事先安装好 OpenJDK Java 7

如何在Ubuntu 16.04中创建GIF动图

导读 FFmpeg 是一款开源的音.视转换器,使用 FFmpeg 我们可以非常容易地转换和录制音视频文件,而 ImageMagick 是一款用于创建.编辑和合并位图图像的一款开源软件. 大家经常在新浪微薄.QQ.facebook.twitter 中看到有趣的 GIF 动图吧,GIF 文件比视频小.比静态 JPG 图片形像生动,非常适于互联网上的搞笑帖子.产品展示和功能步骤演示,所以此小教程将教大家如何在 Ubuntu 16.04 LTS 桌面系统中制作.转换 GIF 效果图片.其实并不难,只需一